xHCI: keep track of ports being resumed and indicate in hub_status_data
authorAndiry Xu <andiry.xu@amd.com>
Fri, 13 Apr 2012 18:54:30 +0000 (02:54 +0800)
committerSarah Sharp <sarah.a.sharp@linux.intel.com>
Thu, 3 May 2012 20:10:17 +0000 (13:10 -0700)
This commit adds a bit-array to xhci bus_state for keeping track of
which ports are undergoing a resume transition. If any of the bits
are set when xhci_hub_status_data() is called, the routine will return
a non-zero value even if no ports have any status changes pending.
This will allow usbcore to handle races between root-hub suspend and
port wakeup.

This patch should be backported to kernels as old as 3.4, that contain
the commit 879d38e6bc36d73b0ac40ec9b0d839fda9fa8b1a "USB: fix race
between root-hub suspend and remote wakeup".

Signed-off-by: Andiry Xu <andiry.xu@amd.com>
Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com>
Cc: Alan Stern <stern@rowland.harvard.edu>
Cc: stable@vger.kernel.org
drivers/usb/host/xhci-hub.c
drivers/usb/host/xhci-ring.c
drivers/usb/host/xhci.c
drivers/usb/host/xhci.h

index 673ad120c43e2f7e363ff385960215ed85153ebc..89850a82d51bac7c9bf74f1644937aa372b93ac4 100644 (file)
@@ -558,6 +558,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
                                xhci_dbg(xhci, "Resume USB2 port %d\n",
                                        wIndex + 1);
                                bus_state->resume_done[wIndex] = 0;
+                               clear_bit(wIndex, &bus_state->resuming_ports);
                                xhci_set_link_state(xhci, port_array, wIndex,
                                                        XDEV_U0);
                                xhci_dbg(xhci, "set port %d resume\n",
@@ -845,7 +846,12 @@ int xhci_hub_status_data(struct usb_hcd *hcd, char *buf)
        /* Initial status is no changes */
        retval = (max_ports + 8) / 8;
        memset(buf, 0, retval);
-       status = 0;
+
+       /*
+        * Inform the usbcore about resume-in-progress by returning
+        * a non-zero value even if there are no status changes.
+        */
+       status = bus_state->resuming_ports;
 
        mask = PORT_CSC | PORT_PEC | PORT_OCC | PORT_PLC | PORT_WRC;
 
@@ -885,15 +891,11 @@ int xhci_bus_suspend(struct usb_hcd *hcd)
        spin_lock_irqsave(&xhci->lock, flags);
 
        if (hcd->self.root_hub->do_remote_wakeup) {
-               port_index = max_ports;
-               while (port_index--) {
-                       if (bus_state->resume_done[port_index] != 0) {
-                               spin_unlock_irqrestore(&xhci->lock, flags);
-                               xhci_dbg(xhci, "suspend failed because "
-                                               "port %d is resuming\n",
-                                               port_index + 1);
-                               return -EBUSY;
-                       }
+               if (bus_state->resuming_ports) {
+                       spin_unlock_irqrestore(&xhci->lock, flags);
+                       xhci_dbg(xhci, "suspend failed because "
+                                               "a port is resuming\n");
+                       return -EBUSY;
                }
        }
 
index 3d9422f16a20b66f2eb54aaffacebaf209b76868..cb1de111daeef8695ebccae6e3cacc404a837b47 100644 (file)
@@ -1377,6 +1377,7 @@ static void handle_port_status(struct xhci_hcd *xhci,
                        xhci_dbg(xhci, "resume HS port %d\n", port_id);
                        bus_state->resume_done[faked_port_index] = jiffies +
                                msecs_to_jiffies(20);
+                       set_bit(faked_port_index, &bus_state->resuming_ports);
                        mod_timer(&hcd->rh_timer,
                                  bus_state->resume_done[faked_port_index]);
                        /* Do the rest in GetPortStatus */
index 36641a7f23719058d5ebfe463feb00ac085b99f3..5910048b0a2e5272a7e137f8b04b5f30554ab35e 100644 (file)
@@ -152,7 +152,7 @@ int xhci_reset(struct xhci_hcd *xhci)
 {
        u32 command;
        u32 state;
-       int ret;
+       int ret, i;
 
        state = xhci_readl(xhci, &xhci->op_regs->status);
        if ((state & STS_HALT) == 0) {
@@ -175,7 +175,15 @@ int xhci_reset(struct xhci_hcd *xhci)
         * xHCI cannot write to any doorbells or operational registers other
         * than status until the "Controller Not Ready" flag is cleared.
         */
-       return handshake(xhci, &xhci->op_regs->status, STS_CNR, 0, 250 * 1000);
+       ret = handshake(xhci, &xhci->op_regs->status, STS_CNR, 0, 250 * 1000);
+
+       for (i = 0; i < 2; ++i) {
+               xhci->bus_state[i].port_c_suspend = 0;
+               xhci->bus_state[i].suspended_ports = 0;
+               xhci->bus_state[i].resuming_ports = 0;
+       }
+
+       return ret;
 }
 
 #ifdef CONFIG_PCI
index 3d69c4b2b54277d57e6cb15cbf403127d70aeb1f..ce1edd7246aa4fb3430c3f4391753658999d5585 100644 (file)
@@ -1362,6 +1362,8 @@ struct xhci_bus_state {
        u32                     suspended_ports;
        u32                     port_remote_wakeup;
        unsigned long           resume_done[USB_MAXCHILDREN];
+       /* which ports have started to resume */
+       unsigned long           resuming_ports;
 };
 
 static inline unsigned int hcd_index(struct usb_hcd *hcd)