USB: EHCI & UHCI: fix race between root-hub suspend and port resume
authorAlan Stern <stern@rowland.harvard.edu>
Fri, 8 Jan 2010 16:18:20 +0000 (11:18 -0500)
committerGreg Kroah-Hartman <gregkh@suse.de>
Thu, 28 Jan 2010 23:20:37 +0000 (15:20 -0800)
commit cec3a53c7fe794237b582e8e77fc0e48465e65ee upstream.

This patch (as1321) fixes a problem with EHCI and UHCI root-hub
suspends: If the suspend occurs while a port is trying to resume, the
resume doesn't finish and simply gets lost.  When remote wakeup is
enabled, this is undesirable behavior.

The patch checks first to see if any port resumes are in progress, and
if they are then it fails the root-hub suspend with -EBUSY.

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

index 740835bb85758ff260557d2b38dda2911d29dc25..ef9b0383fed3cbd077b953a9a50e3b5276d464cf 100644 (file)
@@ -119,9 +119,26 @@ static int ehci_bus_suspend (struct usb_hcd *hcd)
        del_timer_sync(&ehci->watchdog);
        del_timer_sync(&ehci->iaa_watchdog);
 
-       port = HCS_N_PORTS (ehci->hcs_params);
        spin_lock_irq (&ehci->lock);
 
+       /* Once the controller is stopped, port resumes that are already
+        * in progress won't complete.  Hence if remote wakeup is enabled
+        * for the root hub and any ports are in the middle of a resume or
+        * remote wakeup, we must fail the suspend.
+        */
+       if (hcd->self.root_hub->do_remote_wakeup) {
+               port = HCS_N_PORTS(ehci->hcs_params);
+               while (port--) {
+                       if (ehci->reset_done[port] != 0) {
+                               spin_unlock_irq(&ehci->lock);
+                               ehci_dbg(ehci, "suspend failed because "
+                                               "port %d is resuming\n",
+                                               port + 1);
+                               return -EBUSY;
+                       }
+               }
+       }
+
        /* stop schedules, clean any completed work */
        if (HC_IS_RUNNING(hcd->state)) {
                ehci_quiesce (ehci);
@@ -137,6 +154,7 @@ static int ehci_bus_suspend (struct usb_hcd *hcd)
         */
        ehci->bus_suspended = 0;
        ehci->owned_ports = 0;
+       port = HCS_N_PORTS(ehci->hcs_params);
        while (port--) {
                u32 __iomem     *reg = &ehci->regs->port_status [port];
                u32             t1 = ehci_readl(ehci, reg) & ~PORT_RWC_BITS;
index 59bed3c4259dc2d4916ba447074c1590d01ee98f..dc25f4e594898a980508cbc17737a38576cd453d 100644 (file)
@@ -750,7 +750,20 @@ static int uhci_rh_suspend(struct usb_hcd *hcd)
        spin_lock_irq(&uhci->lock);
        if (!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags))
                rc = -ESHUTDOWN;
-       else if (!uhci->dead)
+       else if (uhci->dead)
+               ;               /* Dead controllers tell no tales */
+
+       /* Once the controller is stopped, port resumes that are already
+        * in progress won't complete.  Hence if remote wakeup is enabled
+        * for the root hub and any ports are in the middle of a resume or
+        * remote wakeup, we must fail the suspend.
+        */
+       else if (hcd->self.root_hub->do_remote_wakeup &&
+                       uhci->resuming_ports) {
+               dev_dbg(uhci_dev(uhci), "suspend failed because a port "
+                               "is resuming\n");
+               rc = -EBUSY;
+       } else
                suspend_rh(uhci, UHCI_RH_SUSPENDED);
        spin_unlock_irq(&uhci->lock);
        return rc;