USB: EHCI: change deschedule logic for interrupt QHs
authorAlan Stern <stern@rowland.harvard.edu>
Wed, 19 Aug 2009 16:22:44 +0000 (12:22 -0400)
committerGreg Kroah-Hartman <gregkh@suse.de>
Wed, 23 Sep 2009 13:46:37 +0000 (06:46 -0700)
This patch (as1281) changes the way ehci-hcd deschedules interrupt
QHs, copying the approach used for async QHs.  The caller is no longer
responsible for rescheduling the QH if its queue is non-empty; instead
the reschedule is done directly by intr_deschedule(), after calling
qh_completions().  This is exactly the same as how end_unlink_async()
works.

ehci_urb_dequeue() and intr_deschedule() now correctly handle the case
where they are called while another interrupt URB for the same QH is
being given back.  This was a surprisingly large blind spot.  And
scan_periodic() now respects the new needs_rescan flag.

Signed-off-by: Alan Stern <stern@rowland.harvard.edu>
CC: David Brownell <david-b@pacbell.net>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
drivers/usb/host/ehci-hcd.c
drivers/usb/host/ehci-q.c
drivers/usb/host/ehci-sched.c

index d7e85b6231b3e41928b5af883e7b4b0064b8790a..4f89d7ffd53a23130307ceb7d4af0e27e8c726be 100644 (file)
@@ -934,8 +934,9 @@ static int ehci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status)
                        break;
                switch (qh->qh_state) {
                case QH_STATE_LINKED:
+               case QH_STATE_COMPLETING:
                        intr_deschedule (ehci, qh);
-                       /* FALL THROUGH */
+                       break;
                case QH_STATE_IDLE:
                        qh_completions (ehci, qh);
                        break;
@@ -944,23 +945,6 @@ static int ehci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status)
                                        qh, qh->qh_state);
                        goto done;
                }
-
-               /* reschedule QH iff another request is queued */
-               if (!list_empty (&qh->qtd_list)
-                               && HC_IS_RUNNING (hcd->state)) {
-                       rc = qh_schedule(ehci, qh);
-
-                       /* An error here likely indicates handshake failure
-                        * or no space left in the schedule.  Neither fault
-                        * should happen often ...
-                        *
-                        * FIXME kill the now-dysfunctional queued urbs
-                        */
-                       if (rc != 0)
-                               ehci_err(ehci,
-                                       "can't reschedule qh %p, err %d",
-                                       qh, rc);
-               }
                break;
 
        case PIPE_ISOCHRONOUS:
@@ -1079,12 +1063,10 @@ ehci_endpoint_reset(struct usb_hcd *hcd, struct usb_host_endpoint *ep)
                         * while the QH is active.  Unlink it now;
                         * re-linking will call qh_refresh().
                         */
-                       if (eptype == USB_ENDPOINT_XFER_BULK) {
+                       if (eptype == USB_ENDPOINT_XFER_BULK)
                                unlink_async(ehci, qh);
-                       } else {
+                       else
                                intr_deschedule(ehci, qh);
-                               (void) qh_schedule(ehci, qh);
-                       }
                }
        }
        spin_unlock_irqrestore(&ehci->lock, flags);
index 57a84795c43fddd6b513d41eefabae300a799f54..00ad9ce392ed6871c35a31b429865909874f33e5 100644 (file)
@@ -299,7 +299,6 @@ __acquires(ehci->lock)
 static void start_unlink_async (struct ehci_hcd *ehci, struct ehci_qh *qh);
 static void unlink_async (struct ehci_hcd *ehci, struct ehci_qh *qh);
 
-static void intr_deschedule (struct ehci_hcd *ehci, struct ehci_qh *qh);
 static int qh_schedule (struct ehci_hcd *ehci, struct ehci_qh *qh);
 
 /*
@@ -555,14 +554,9 @@ halt:
                         * That should be rare for interrupt transfers,
                         * except maybe high bandwidth ...
                         */
-                       if ((cpu_to_hc32(ehci, QH_SMASK)
-                                       & hw->hw_info2) != 0) {
-                               intr_deschedule (ehci, qh);
-                               (void) qh_schedule (ehci, qh);
-                       } else {
-                               /* Tell the caller to start an unlink */
-                               qh->needs_rescan = 1;
-                       }
+
+                       /* Tell the caller to start an unlink */
+                       qh->needs_rescan = 1;
                        break;
                /* otherwise, unlink already started */
                }
index 327437af212269c95dc529d8921652e2f1a8f1f2..3ea05936851ff72568ff1e655300eee6bd013df9 100644 (file)
@@ -615,8 +615,19 @@ static int qh_unlink_periodic(struct ehci_hcd *ehci, struct ehci_qh *qh)
 
 static void intr_deschedule (struct ehci_hcd *ehci, struct ehci_qh *qh)
 {
-       unsigned        wait;
-       struct ehci_qh_hw *hw = qh->hw;
+       unsigned                wait;
+       struct ehci_qh_hw       *hw = qh->hw;
+       int                     rc;
+
+       /* If the QH isn't linked then there's nothing we can do
+        * unless we were called during a giveback, in which case
+        * qh_completions() has to deal with it.
+        */
+       if (qh->qh_state != QH_STATE_LINKED) {
+               if (qh->qh_state == QH_STATE_COMPLETING)
+                       qh->needs_rescan = 1;
+               return;
+       }
 
        qh_unlink_periodic (ehci, qh);
 
@@ -636,6 +647,24 @@ static void intr_deschedule (struct ehci_hcd *ehci, struct ehci_qh *qh)
        qh->qh_state = QH_STATE_IDLE;
        hw->hw_next = EHCI_LIST_END(ehci);
        wmb ();
+
+       qh_completions(ehci, qh);
+
+       /* reschedule QH iff another request is queued */
+       if (!list_empty(&qh->qtd_list) &&
+                       HC_IS_RUNNING(ehci_to_hcd(ehci)->state)) {
+               rc = qh_schedule(ehci, qh);
+
+               /* An error here likely indicates handshake failure
+                * or no space left in the schedule.  Neither fault
+                * should happen often ...
+                *
+                * FIXME kill the now-dysfunctional queued urbs
+                */
+               if (rc != 0)
+                       ehci_err(ehci, "can't reschedule qh %p, err %d\n",
+                                       qh, rc);
+       }
 }
 
 /*-------------------------------------------------------------------------*/
@@ -2213,7 +2242,8 @@ restart:
                                type = Q_NEXT_TYPE(ehci, q.qh->hw->hw_next);
                                q = q.qh->qh_next;
                                modified = qh_completions (ehci, temp.qh);
-                               if (unlikely (list_empty (&temp.qh->qtd_list)))
+                               if (unlikely(list_empty(&temp.qh->qtd_list) ||
+                                               temp.qh->needs_rescan))
                                        intr_deschedule (ehci, temp.qh);
                                qh_put (temp.qh);
                                break;