Skip to content

Commit ff2f078

Browse files
AlanSterngregkh
authored andcommitted
USB: fix race between root-hub wakeup & controller suspend
This patch (as1395) adds code to hcd_pci_suspend() for handling wakeup races. This is another general race pattern, similar to the "open vs. unregister" race we're all familiar with. Here, the race is between suspending a device and receiving a wakeup request from one of the device's suspended children. In particular, if a root-hub wakeup is requested at about the same time as the corresponding USB controller is suspended, and if the controller is enabled for wakeup, then the controller should either fail to suspend or else wake right back up again. During system sleep this won't happen very much, especially since host controllers generally aren't enabled for wakeup during sleep. However it is definitely an issue for runtime PM. Something like this will be needed to prevent the controller from autosuspending while waiting for a root-hub resume to take place. (That is, in fact, the common case, for which there is an extra test.) Signed-off-by: Alan Stern <[email protected]> Signed-off-by: Greg Kroah-Hartman <[email protected]>
1 parent ee0b9be commit ff2f078

File tree

3 files changed

+18
-1
lines changed

3 files changed

+18
-1
lines changed

drivers/usb/core/hcd-pci.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,8 +388,20 @@ static int hcd_pci_suspend(struct device *dev)
388388
if (hcd->driver->pci_suspend) {
389389
bool do_wakeup = device_may_wakeup(dev);
390390

391+
/* Optimization: Don't suspend if a root-hub wakeup is
392+
* pending and it would cause the HCD to wake up anyway.
393+
*/
394+
if (do_wakeup && HCD_WAKEUP_PENDING(hcd))
395+
return -EBUSY;
391396
retval = hcd->driver->pci_suspend(hcd, do_wakeup);
392397
suspend_report_result(hcd->driver->pci_suspend, retval);
398+
399+
/* Check again in case wakeup raced with pci_suspend */
400+
if (retval == 0 && do_wakeup && HCD_WAKEUP_PENDING(hcd)) {
401+
if (hcd->driver->pci_resume)
402+
hcd->driver->pci_resume(hcd, false);
403+
retval = -EBUSY;
404+
}
393405
if (retval)
394406
return retval;
395407
}

drivers/usb/core/hcd.c

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1940,6 +1940,7 @@ int hcd_bus_resume(struct usb_device *rhdev, pm_message_t msg)
19401940

19411941
dev_dbg(&rhdev->dev, "usb %s%s\n",
19421942
(msg.event & PM_EVENT_AUTO ? "auto-" : ""), "resume");
1943+
clear_bit(HCD_FLAG_WAKEUP_PENDING, &hcd->flags);
19431944
if (!hcd->driver->bus_resume)
19441945
return -ENOENT;
19451946
if (hcd->state == HC_STATE_RUNNING)
@@ -1993,8 +1994,10 @@ void usb_hcd_resume_root_hub (struct usb_hcd *hcd)
19931994
unsigned long flags;
19941995

19951996
spin_lock_irqsave (&hcd_root_hub_lock, flags);
1996-
if (hcd->rh_registered)
1997+
if (hcd->rh_registered) {
1998+
set_bit(HCD_FLAG_WAKEUP_PENDING, &hcd->flags);
19971999
queue_work(pm_wq, &hcd->wakeup_work);
2000+
}
19982001
spin_unlock_irqrestore (&hcd_root_hub_lock, flags);
19992002
}
20002003
EXPORT_SYMBOL_GPL(usb_hcd_resume_root_hub);

include/linux/usb/hcd.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ struct usb_hcd {
9898
#define HCD_FLAG_SAW_IRQ 1
9999
#define HCD_FLAG_POLL_RH 2 /* poll for rh status? */
100100
#define HCD_FLAG_POLL_PENDING 3 /* status has changed? */
101+
#define HCD_FLAG_WAKEUP_PENDING 4 /* root hub is resuming? */
101102

102103
/* The flags can be tested using these macros; they are likely to
103104
* be slightly faster than test_bit().
@@ -106,6 +107,7 @@ struct usb_hcd {
106107
#define HCD_SAW_IRQ(hcd) ((hcd)->flags & (1U << HCD_FLAG_SAW_IRQ))
107108
#define HCD_POLL_RH(hcd) ((hcd)->flags & (1U << HCD_FLAG_POLL_RH))
108109
#define HCD_POLL_PENDING(hcd) ((hcd)->flags & (1U << HCD_FLAG_POLL_PENDING))
110+
#define HCD_WAKEUP_PENDING(hcd) ((hcd)->flags & (1U << HCD_FLAG_WAKEUP_PENDING))
109111

110112
/* Flags that get set only during HCD registration or removal. */
111113
unsigned rh_registered:1;/* is root hub registered? */

0 commit comments

Comments
 (0)