USB: EHCI: update toggle state for linked QHs
authorAlan Stern <stern@rowland.harvard.edu>
Wed, 27 May 2009 22:21:56 +0000 (18:21 -0400)
committerGreg Kroah-Hartman <gregkh@suse.de>
Tue, 16 Jun 2009 04:44:46 +0000 (21:44 -0700)
This patch (as1245) fixes a bug in ehci-hcd.  When an URB is queued
for an endpoint whose QH is already in the LINKED state, the QH
doesn't get refreshed.  As a result, if usb_clear_halt() was called
during the time that the QH was linked but idle, the data toggle value
in the QH doesn't get reset.

The symptom is that after a clear_halt, data gets lost and transfers
time out.  This problem is starting to show up now because the
"ehci-hcd unlink speedups" patch causes QHs with no queued URBs to
remain linked for a suitable time.

The patch utilizes the new endpoint_reset mechanism to fix the
problem.  When an endpoint is reset, the new method forcibly unlinks
the QH (if necessary) and safely updates the toggle value.  This
allows qh_update() to be simplified and avoids using usb_device's
toggle bits in a rather unintuitive way.

Signed-off-by: Alan Stern <stern@rowland.harvard.edu>
CC: David Brownell <david-b@pacbell.net>
Tested-by: David <david@unsolicited.net>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
drivers/usb/host/ehci-au1xxx.c
drivers/usb/host/ehci-fsl.c
drivers/usb/host/ehci-hcd.c
drivers/usb/host/ehci-ixp4xx.c
drivers/usb/host/ehci-orion.c
drivers/usb/host/ehci-pci.c
drivers/usb/host/ehci-ppc-of.c
drivers/usb/host/ehci-ps3.c
drivers/usb/host/ehci-q.c

index bf69f473910785465c8c20ef4be46ea7acab2946..c3a778bd359c9b883bd6e361e78951818fd3a280 100644 (file)
@@ -97,6 +97,7 @@ static const struct hc_driver ehci_au1xxx_hc_driver = {
        .urb_enqueue            = ehci_urb_enqueue,
        .urb_dequeue            = ehci_urb_dequeue,
        .endpoint_disable       = ehci_endpoint_disable,
+       .endpoint_reset         = ehci_endpoint_reset,
 
        /*
         * scheduling support
index 01c3da34f678e4a52107f64bca1674bb7e64c807..bf86809c5120795eac8521b68e2159d2fef37127 100644 (file)
@@ -309,6 +309,7 @@ static const struct hc_driver ehci_fsl_hc_driver = {
        .urb_enqueue = ehci_urb_enqueue,
        .urb_dequeue = ehci_urb_dequeue,
        .endpoint_disable = ehci_endpoint_disable,
+       .endpoint_reset = ehci_endpoint_reset,
 
        /*
         * scheduling support
index a2ca9cbf2809fab36cd57da4b6329e65335153be..2b72473544d31d798034ed5aa92cb555384a8055 100644 (file)
@@ -1024,6 +1024,51 @@ done:
        return;
 }
 
+static void
+ehci_endpoint_reset(struct usb_hcd *hcd, struct usb_host_endpoint *ep)
+{
+       struct ehci_hcd         *ehci = hcd_to_ehci(hcd);
+       struct ehci_qh          *qh;
+       int                     eptype = usb_endpoint_type(&ep->desc);
+
+       if (eptype != USB_ENDPOINT_XFER_BULK && eptype != USB_ENDPOINT_XFER_INT)
+               return;
+
+ rescan:
+       spin_lock_irq(&ehci->lock);
+       qh = ep->hcpriv;
+
+       /* For Bulk and Interrupt endpoints we maintain the toggle state
+        * in the hardware; the toggle bits in udev aren't used at all.
+        * When an endpoint is reset by usb_clear_halt() we must reset
+        * the toggle bit in the QH.
+        */
+       if (qh) {
+               if (!list_empty(&qh->qtd_list)) {
+                       WARN_ONCE(1, "clear_halt for a busy endpoint\n");
+               } else if (qh->qh_state == QH_STATE_IDLE) {
+                       qh->hw_token &= ~cpu_to_hc32(ehci, QTD_TOGGLE);
+               } else {
+                       /* It's not safe to write into the overlay area
+                        * while the QH is active.  Unlink it first and
+                        * wait for the unlink to complete.
+                        */
+                       if (qh->qh_state == QH_STATE_LINKED) {
+                               if (eptype == USB_ENDPOINT_XFER_BULK) {
+                                       unlink_async(ehci, qh);
+                               } else {
+                                       intr_deschedule(ehci, qh);
+                                       (void) qh_schedule(ehci, qh);
+                               }
+                       }
+                       spin_unlock_irq(&ehci->lock);
+                       schedule_timeout_uninterruptible(1);
+                       goto rescan;
+               }
+       }
+       spin_unlock_irq(&ehci->lock);
+}
+
 static int ehci_get_frame (struct usb_hcd *hcd)
 {
        struct ehci_hcd         *ehci = hcd_to_ehci (hcd);
index 9c32063a0c2f6611fc938760ea84906d1f3f9666..a44bb4a949543d797f336827edb8ac6fa088d8ac 100644 (file)
@@ -51,6 +51,7 @@ static const struct hc_driver ixp4xx_ehci_hc_driver = {
        .urb_enqueue            = ehci_urb_enqueue,
        .urb_dequeue            = ehci_urb_dequeue,
        .endpoint_disable       = ehci_endpoint_disable,
+       .endpoint_reset         = ehci_endpoint_reset,
        .get_frame_number       = ehci_get_frame,
        .hub_status_data        = ehci_hub_status_data,
        .hub_control            = ehci_hub_control,
index 17dc15407a0789f43edd12adc88e5e76ef555a83..770dd9aba62a9f07aabd9863e59fc4acae6a81dd 100644 (file)
@@ -149,6 +149,7 @@ static const struct hc_driver ehci_orion_hc_driver = {
        .urb_enqueue = ehci_urb_enqueue,
        .urb_dequeue = ehci_urb_dequeue,
        .endpoint_disable = ehci_endpoint_disable,
+       .endpoint_reset = ehci_endpoint_reset,
 
        /*
         * scheduling support
index e74948898f76f00f7232411572d983439c78c878..f3683e1da16134b3c20a6239b5edfd406af923aa 100644 (file)
@@ -388,6 +388,7 @@ static const struct hc_driver ehci_pci_hc_driver = {
        .urb_enqueue =          ehci_urb_enqueue,
        .urb_dequeue =          ehci_urb_dequeue,
        .endpoint_disable =     ehci_endpoint_disable,
+       .endpoint_reset =       ehci_endpoint_reset,
 
        /*
         * scheduling support
index ef732b704f53d996aed213bc1563c10063e89978..fbd272288fc2cf799ef1d075adea282dd01aa863 100644 (file)
@@ -61,6 +61,7 @@ static const struct hc_driver ehci_ppc_of_hc_driver = {
        .urb_enqueue            = ehci_urb_enqueue,
        .urb_dequeue            = ehci_urb_dequeue,
        .endpoint_disable       = ehci_endpoint_disable,
+       .endpoint_reset         = ehci_endpoint_reset,
 
        /*
         * scheduling support
index 1ba9f9a8c3088bd607fb95b5599e0982c6c8c9d2..4b4df23efddf47fd31977e143dfc628f8378be38 100644 (file)
@@ -65,6 +65,7 @@ static const struct hc_driver ps3_ehci_hc_driver = {
        .urb_enqueue            = ehci_urb_enqueue,
        .urb_dequeue            = ehci_urb_dequeue,
        .endpoint_disable       = ehci_endpoint_disable,
+       .endpoint_reset         = ehci_endpoint_reset,
        .get_frame_number       = ehci_get_frame,
        .hub_status_data        = ehci_hub_status_data,
        .hub_control            = ehci_hub_control,
index 1976b1b3778cd3de2d7946733fbda37edb6a7108..3192f683f8073293a6366b532ced3ff0dd5007f1 100644 (file)
@@ -93,22 +93,6 @@ qh_update (struct ehci_hcd *ehci, struct ehci_qh *qh, struct ehci_qtd *qtd)
        qh->hw_qtd_next = QTD_NEXT(ehci, qtd->qtd_dma);
        qh->hw_alt_next = EHCI_LIST_END(ehci);
 
-       /* Except for control endpoints, we make hardware maintain data
-        * toggle (like OHCI) ... here (re)initialize the toggle in the QH,
-        * and set the pseudo-toggle in udev. Only usb_clear_halt() will
-        * ever clear it.
-        */
-       if (!(qh->hw_info1 & cpu_to_hc32(ehci, 1 << 14))) {
-               unsigned        is_out, epnum;
-
-               is_out = !(qtd->hw_token & cpu_to_hc32(ehci, 1 << 8));
-               epnum = (hc32_to_cpup(ehci, &qh->hw_info1) >> 8) & 0x0f;
-               if (unlikely (!usb_gettoggle (qh->dev, epnum, is_out))) {
-                       qh->hw_token &= ~cpu_to_hc32(ehci, QTD_TOGGLE);
-                       usb_settoggle (qh->dev, epnum, is_out, 1);
-               }
-       }
-
        /* HC must see latest qtd and qh data before we clear ACTIVE+HALT */
        wmb ();
        qh->hw_token &= cpu_to_hc32(ehci, QTD_TOGGLE | QTD_STS_PING);
@@ -850,7 +834,6 @@ done:
        qh->qh_state = QH_STATE_IDLE;
        qh->hw_info1 = cpu_to_hc32(ehci, info1);
        qh->hw_info2 = cpu_to_hc32(ehci, info2);
-       usb_settoggle (urb->dev, usb_pipeendpoint (urb->pipe), !is_input, 1);
        qh_refresh (ehci, qh);
        return qh;
 }
@@ -881,7 +864,7 @@ static void qh_link_async (struct ehci_hcd *ehci, struct ehci_qh *qh)
                }
        }
 
-       /* clear halt and/or toggle; and maybe recover from silicon quirk */
+       /* clear halt and maybe recover from silicon quirk */
        if (qh->qh_state == QH_STATE_IDLE)
                qh_refresh (ehci, qh);