Commit d18240db authored by Andiry Xu's avatar Andiry Xu Committed by Greg Kroah-Hartman

USB: xHCI: Missed Service Error Event process

This patch adds mechanism to process Missed Service Error Event.
Sometimes the xHC is unable to process the isoc TDs in time, it will
generate Missed Service Error Event. In this case some TDs on the ring are
not processed and missed. When encounter a Missed Servce Error Event, set
the skip flag of the ep, and process the missed TDs until reach the next
processed TD, then clear the skip flag.
Signed-off-by: default avatarAndiry Xu <andiry.xu@amd.com>
Signed-off-by: default avatarSarah Sharp <sarah.a.sharp@linux.intel.com>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@suse.de>
parent 986a92d4
......@@ -1124,6 +1124,7 @@ int xhci_endpoint_init(struct xhci_hcd *xhci,
virt_dev->num_rings_cached--;
xhci_reinit_cached_ring(xhci, virt_dev->eps[ep_index].new_ring);
}
virt_dev->eps[ep_index].skip = false;
ep_ring = virt_dev->eps[ep_index].new_ring;
ep_ctx->deq = ep_ring->first_seg->dma | ep_ring->cycle_state;
......
......@@ -1675,6 +1675,16 @@ static int handle_tx_event(struct xhci_hcd *xhci,
"still with TDs queued?\n",
TRB_TO_SLOT_ID(event->flags), ep_index);
goto cleanup;
case COMP_MISSED_INT:
/*
* When encounter missed service error, one or more isoc tds
* may be missed by xHC.
* Set skip flag of the ep_ring; Complete the missed tds as
* short transfer when process the ep_ring next time.
*/
ep->skip = true;
xhci_dbg(xhci, "Miss service interval error, set skip flag\n");
goto cleanup;
default:
if (xhci_is_vendor_info_code(xhci, trb_comp_code)) {
status = 0;
......@@ -1685,60 +1695,108 @@ static int handle_tx_event(struct xhci_hcd *xhci,
goto cleanup;
}
/* This TRB should be in the TD at the head of this ring's TD list */
if (list_empty(&ep_ring->td_list)) {
xhci_warn(xhci, "WARN Event TRB for slot %d ep %d with no TDs queued?\n",
TRB_TO_SLOT_ID(event->flags), ep_index);
xhci_dbg(xhci, "Event TRB with TRB type ID %u\n",
(unsigned int) (event->flags & TRB_TYPE_BITMASK)>>10);
xhci_print_trb_offsets(xhci, (union xhci_trb *) event);
goto cleanup;
}
td = list_entry(ep_ring->td_list.next, struct xhci_td, td_list);
/* Is this a TRB in the currently executing TD? */
event_seg = trb_in_td(ep_ring->deq_seg, ep_ring->dequeue,
td->last_trb, event_dma);
if (!event_seg) {
/* HC is busted, give up! */
xhci_err(xhci, "ERROR Transfer event TRB DMA ptr not part of current TD\n");
return -ESHUTDOWN;
}
event_trb = &event_seg->trbs[(event_dma - event_seg->dma) / sizeof(*event_trb)];
do {
/* This TRB should be in the TD at the head of this ring's
* TD list.
*/
if (list_empty(&ep_ring->td_list)) {
xhci_warn(xhci, "WARN Event TRB for slot %d ep %d "
"with no TDs queued?\n",
TRB_TO_SLOT_ID(event->flags), ep_index);
xhci_dbg(xhci, "Event TRB with TRB type ID %u\n",
(unsigned int) (event->flags & TRB_TYPE_BITMASK)>>10);
xhci_print_trb_offsets(xhci, (union xhci_trb *) event);
if (ep->skip) {
ep->skip = false;
xhci_dbg(xhci, "td_list is empty while skip "
"flag set. Clear skip flag.\n");
}
ret = 0;
goto cleanup;
}
/* Now update the urb's actual_length and give back to the core */
/* Was this a control transfer? */
if (usb_endpoint_xfer_control(&td->urb->ep->desc))
ret = process_ctrl_td(xhci, td, event_trb, event, ep,
&status);
else
ret = process_bulk_intr_td(xhci, td, event_trb, event, ep,
&status);
td = list_entry(ep_ring->td_list.next, struct xhci_td, td_list);
/* Is this a TRB in the currently executing TD? */
event_seg = trb_in_td(ep_ring->deq_seg, ep_ring->dequeue,
td->last_trb, event_dma);
if (event_seg && ep->skip) {
xhci_dbg(xhci, "Found td. Clear skip flag.\n");
ep->skip = false;
}
if (!event_seg &&
(!ep->skip || !usb_endpoint_xfer_isoc(&td->urb->ep->desc))) {
/* HC is busted, give up! */
xhci_err(xhci, "ERROR Transfer event TRB DMA ptr not "
"part of current TD\n");
return -ESHUTDOWN;
}
cleanup:
inc_deq(xhci, xhci->event_ring, true);
xhci_set_hc_event_deq(xhci);
if (event_seg) {
event_trb = &event_seg->trbs[(event_dma -
event_seg->dma) / sizeof(*event_trb)];
/*
* No-op TRB should not trigger interrupts.
* If event_trb is a no-op TRB, it means the
* corresponding TD has been cancelled. Just ignore
* the TD.
*/
if ((event_trb->generic.field[3] & TRB_TYPE_BITMASK)
== TRB_TYPE(TRB_TR_NOOP)) {
xhci_dbg(xhci, "event_trb is a no-op TRB. "
"Skip it\n");
goto cleanup;
}
}
/* FIXME for multi-TD URBs (who have buffers bigger than 64MB) */
if (ret) {
urb = td->urb;
/* Leave the TD around for the reset endpoint function to use
* (but only if it's not a control endpoint, since we already
* queued the Set TR dequeue pointer command for stalled
* control endpoints).
/* Now update the urb's actual_length and give back to
* the core
*/
if (usb_endpoint_xfer_control(&urb->ep->desc) ||
(trb_comp_code != COMP_STALL &&
trb_comp_code != COMP_BABBLE))
kfree(td);
usb_hcd_unlink_urb_from_ep(xhci_to_hcd(xhci), urb);
xhci_dbg(xhci, "Giveback URB %p, len = %d, status = %d\n",
urb, urb->actual_length, status);
spin_unlock(&xhci->lock);
usb_hcd_giveback_urb(xhci_to_hcd(xhci), urb, status);
spin_lock(&xhci->lock);
}
if (usb_endpoint_xfer_control(&td->urb->ep->desc))
ret = process_ctrl_td(xhci, td, event_trb, event, ep,
&status);
else
ret = process_bulk_intr_td(xhci, td, event_trb, event,
ep, &status);
cleanup:
/*
* Do not update event ring dequeue pointer if ep->skip is set.
* Will roll back to continue process missed tds.
*/
if (trb_comp_code == COMP_MISSED_INT || !ep->skip) {
inc_deq(xhci, xhci->event_ring, true);
xhci_set_hc_event_deq(xhci);
}
if (ret) {
urb = td->urb;
/* Leave the TD around for the reset endpoint function
* to use(but only if it's not a control endpoint,
* since we already queued the Set TR dequeue pointer
* command for stalled control endpoints).
*/
if (usb_endpoint_xfer_control(&urb->ep->desc) ||
(trb_comp_code != COMP_STALL &&
trb_comp_code != COMP_BABBLE))
kfree(td);
usb_hcd_unlink_urb_from_ep(xhci_to_hcd(xhci), urb);
xhci_dbg(xhci, "Giveback URB %p, len = %d, "
"status = %d\n",
urb, urb->actual_length, status);
spin_unlock(&xhci->lock);
usb_hcd_giveback_urb(xhci_to_hcd(xhci), urb, status);
spin_lock(&xhci->lock);
}
/*
* If ep->skip is set, it means there are missed tds on the
* endpoint ring need to take care of.
* Process them as short transfer until reach the td pointed by
* the event.
*/
} while (ep->skip && trb_comp_code != COMP_MISSED_INT);
return 0;
}
......
......@@ -720,6 +720,14 @@ struct xhci_virt_ep {
struct timer_list stop_cmd_timer;
int stop_cmds_pending;
struct xhci_hcd *xhci;
/*
* Sometimes the xHC can not process isochronous endpoint ring quickly
* enough, and it will miss some isoc tds on the ring and generate
* a Missed Service Error Event.
* Set skip flag when receive a Missed Service Error Event and
* process the missed tds on the endpoint ring.
*/
bool skip;
};
struct xhci_virt_device {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment