USB: EHCI: don't depend on hardware for tracking port resets and resumes
authorAlan Stern <stern@rowland.harvard.edu>
Thu, 1 Aug 2013 21:09:28 +0000 (17:09 -0400)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 1 Aug 2013 21:37:07 +0000 (05:37 +0800)
In theory, an EHCI controller can turn off the PORT_RESUME or
PORT_RESET bits in a port status register all by itself (and some
controllers actually do this).  We shouldn't depend on these bits
being set correctly.

This patch rearranges the code in ehci-hcd that handles completion of
port resets and resumes.  We guarantee that ehci->reset_done[portnum]
is nonzero if a reset or resume is in progress, and that the portnum
bit is set in ehci->resuming_ports if the operation is a resume.  (To
help enforce this guarantee, the patch prevents suspended ports from
being reset.)  Therefore it's not necessary to look at the port status
bits to learn what's going on.

The patch looks bigger than it really is, because it changes the
indentation level of a sizeable region of code.  Most of what it
actually does is interchange some tests.  The only functional changes
are testing reset_done and resuming_ports rather than PORT_RESUME and
PORT_RESET, removing a now-unnecessary check for spontaneous
resets of the PORT_RESUME and PORT_RESET bits, and preventing a
suspended or resuming port from being reset.

Signed-off-by: Alan Stern <stern@rowland.harvard.edu>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/usb/host/ehci-hub.c

index 6d0e59306ed9c98640b4406cd55c907b31952c72..6e69ee1a3371af09e99ffeaaae4465bca93c63e4 100644 (file)
@@ -867,11 +867,11 @@ static int ehci_hub_control (
                        }
                }
 
-               /* whoever resumes must GetPortStatus to complete it!! */
-               if (temp & PORT_RESUME) {
+               /* no reset or resume pending */
+               if (!ehci->reset_done[wIndex]) {
 
                        /* Remote Wakeup received? */
-                       if (!ehci->reset_done[wIndex]) {
+                       if (temp & PORT_RESUME) {
                                /* resume signaling for 20 msec */
                                ehci->reset_done[wIndex] = jiffies
                                                + msecs_to_jiffies(20);
@@ -882,35 +882,32 @@ static int ehci_hub_control (
                                                ehci->reset_done[wIndex]);
                        }
 
-                       /* resume completed? */
-                       else if (time_after_eq(jiffies,
-                                       ehci->reset_done[wIndex])) {
-                               clear_bit(wIndex, &ehci->suspended_ports);
-                               set_bit(wIndex, &ehci->port_c_suspend);
-                               ehci->reset_done[wIndex] = 0;
-                               usb_hcd_end_port_resume(&hcd->self, wIndex);
-
-                               /* stop resume signaling */
-                               temp &= ~(PORT_RWC_BITS |
-                                               PORT_SUSPEND | PORT_RESUME);
-                               ehci_writel(ehci, temp, status_reg);
-                               clear_bit(wIndex, &ehci->resuming_ports);
-                               retval = ehci_handshake(ehci, status_reg,
-                                          PORT_RESUME, 0, 2000 /* 2msec */);
-                               if (retval != 0) {
-                                       ehci_err(ehci,
-                                               "port %d resume error %d\n",
+               /* reset or resume not yet complete */
+               } else if (!time_after_eq(jiffies, ehci->reset_done[wIndex])) {
+                       ;       /* wait until it is complete */
+
+               /* resume completed */
+               } else if (test_bit(wIndex, &ehci->resuming_ports)) {
+                       clear_bit(wIndex, &ehci->suspended_ports);
+                       set_bit(wIndex, &ehci->port_c_suspend);
+                       ehci->reset_done[wIndex] = 0;
+                       usb_hcd_end_port_resume(&hcd->self, wIndex);
+
+                       /* stop resume signaling */
+                       temp &= ~(PORT_RWC_BITS | PORT_SUSPEND | PORT_RESUME);
+                       ehci_writel(ehci, temp, status_reg);
+                       clear_bit(wIndex, &ehci->resuming_ports);
+                       retval = ehci_handshake(ehci, status_reg,
+                                       PORT_RESUME, 0, 2000 /* 2msec */);
+                       if (retval != 0) {
+                               ehci_err(ehci, "port %d resume error %d\n",
                                                wIndex + 1, retval);
-                                       goto error;
-                               }
-                               temp = ehci_readl(ehci, status_reg);
+                               goto error;
                        }
-               }
+                       temp = ehci_readl(ehci, status_reg);
 
                /* whoever resets must GetPortStatus to complete it!! */
-               if ((temp & PORT_RESET)
-                               && time_after_eq(jiffies,
-                                       ehci->reset_done[wIndex])) {
+               } else {
                        status |= USB_PORT_STAT_C_RESET << 16;
                        ehci->reset_done [wIndex] = 0;
 
@@ -933,11 +930,6 @@ static int ehci_hub_control (
                                        ehci_readl(ehci, status_reg));
                }
 
-               if (!(temp & (PORT_RESUME|PORT_RESET))) {
-                       ehci->reset_done[wIndex] = 0;
-                       clear_bit(wIndex, &ehci->resuming_ports);
-               }
-
                /* transfer dedicated ports to the companion hc */
                if ((temp & PORT_CONNECT) &&
                                test_bit(wIndex, &ehci->companion_ports)) {
@@ -1058,7 +1050,7 @@ static int ehci_hub_control (
                                                status_reg);
                        break;
                case USB_PORT_FEAT_RESET:
-                       if (temp & PORT_RESUME)
+                       if (temp & (PORT_SUSPEND|PORT_RESUME))
                                goto error;
                        /* line status bits may report this as low speed,
                         * which can be fine if this root hub has a