USB: EHCI & UHCI: fix race between root-hub suspend and port resume

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>
Cc: stable <stable@kernel.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>

authored by Alan Stern and committed by Greg Kroah-Hartman cec3a53c 1b9a38bf

+33 -2
+19 -1
drivers/usb/host/ehci-hub.c
··· 120 120 del_timer_sync(&ehci->watchdog); 121 121 del_timer_sync(&ehci->iaa_watchdog); 122 122 123 - port = HCS_N_PORTS (ehci->hcs_params); 124 123 spin_lock_irq (&ehci->lock); 124 + 125 + /* Once the controller is stopped, port resumes that are already 126 + * in progress won't complete. Hence if remote wakeup is enabled 127 + * for the root hub and any ports are in the middle of a resume or 128 + * remote wakeup, we must fail the suspend. 129 + */ 130 + if (hcd->self.root_hub->do_remote_wakeup) { 131 + port = HCS_N_PORTS(ehci->hcs_params); 132 + while (port--) { 133 + if (ehci->reset_done[port] != 0) { 134 + spin_unlock_irq(&ehci->lock); 135 + ehci_dbg(ehci, "suspend failed because " 136 + "port %d is resuming\n", 137 + port + 1); 138 + return -EBUSY; 139 + } 140 + } 141 + } 125 142 126 143 /* stop schedules, clean any completed work */ 127 144 if (HC_IS_RUNNING(hcd->state)) { ··· 155 138 */ 156 139 ehci->bus_suspended = 0; 157 140 ehci->owned_ports = 0; 141 + port = HCS_N_PORTS(ehci->hcs_params); 158 142 while (port--) { 159 143 u32 __iomem *reg = &ehci->regs->port_status [port]; 160 144 u32 t1 = ehci_readl(ehci, reg) & ~PORT_RWC_BITS;
+14 -1
drivers/usb/host/uhci-hcd.c
··· 749 749 spin_lock_irq(&uhci->lock); 750 750 if (!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags)) 751 751 rc = -ESHUTDOWN; 752 - else if (!uhci->dead) 752 + else if (uhci->dead) 753 + ; /* Dead controllers tell no tales */ 754 + 755 + /* Once the controller is stopped, port resumes that are already 756 + * in progress won't complete. Hence if remote wakeup is enabled 757 + * for the root hub and any ports are in the middle of a resume or 758 + * remote wakeup, we must fail the suspend. 759 + */ 760 + else if (hcd->self.root_hub->do_remote_wakeup && 761 + uhci->resuming_ports) { 762 + dev_dbg(uhci_dev(uhci), "suspend failed because a port " 763 + "is resuming\n"); 764 + rc = -EBUSY; 765 + } else 753 766 suspend_rh(uhci, UHCI_RH_SUSPENDED); 754 767 spin_unlock_irq(&uhci->lock); 755 768 return rc;