USB: xHCI: Missed Service Error Event process
authorAndiry Xu <andiry.xu@amd.com>
Thu, 22 Jul 2010 22:23:25 +0000 (15:23 -0700)
committerGreg Kroah-Hartman <gregkh@suse.de>
Tue, 10 Aug 2010 21:35:41 +0000 (14:35 -0700)
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: Andiry Xu <andiry.xu@amd.com>
Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
drivers/usb/host/xhci-mem.c
drivers/usb/host/xhci-ring.c
drivers/usb/host/xhci.h

index 6d8f7e32932a8de894236eea9b02ec041b711c3d..64d036804715b6554f46f24c39256b39221d975c 100644 (file)
@@ -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;
 
index 5bb12fed9d2d4c4227269a82511f757e338a1d58..4c3501003b8eee21c2c85ec6fa5d1aa90b403aaa 100644 (file)
@@ -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;
 }
 
index 5bc03d1c2beb2342ee6623b50c760efdd9e6d3b4..f4dfb26a65aaea2ec52b3e3a695a4bbf5d4db93f 100644 (file)
@@ -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 {