diff options
Diffstat (limited to 'usb/usb-fix-race-between-root-hub-wakeup-controller-suspend.patch')
| -rw-r--r-- | usb/usb-fix-race-between-root-hub-wakeup-controller-suspend.patch | 98 |
1 files changed, 98 insertions, 0 deletions
diff --git a/usb/usb-fix-race-between-root-hub-wakeup-controller-suspend.patch b/usb/usb-fix-race-between-root-hub-wakeup-controller-suspend.patch new file mode 100644 index 00000000000000..b51d05c1839473 --- /dev/null +++ b/usb/usb-fix-race-between-root-hub-wakeup-controller-suspend.patch @@ -0,0 +1,98 @@ +From stern@rowland.harvard.edu Wed Jul 7 14:55:46 2010 +From: Alan Stern <stern@rowland.harvard.edu> +Date: Fri, 25 Jun 2010 14:02:35 -0400 (EDT) +Subject: USB: fix race between root-hub wakeup & controller suspend +To: Greg KH <greg@kroah.com> +Message-ID: <Pine.LNX.4.44L0.1006251248220.1604-100000@iolanthe.rowland.org> + + +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 <stern@rowland.harvard.edu> +Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> + +--- + drivers/usb/core/hcd-pci.c | 12 ++++++++++++ + drivers/usb/core/hcd.c | 5 ++++- + include/linux/usb/hcd.h | 2 ++ + 3 files changed, 18 insertions(+), 1 deletion(-) + +--- a/drivers/usb/core/hcd-pci.c ++++ b/drivers/usb/core/hcd-pci.c +@@ -388,8 +388,20 @@ static int hcd_pci_suspend(struct device + if (hcd->driver->pci_suspend) { + bool do_wakeup = device_may_wakeup(dev); + ++ /* Optimization: Don't suspend if a root-hub wakeup is ++ * pending and it would cause the HCD to wake up anyway. ++ */ ++ if (do_wakeup && HCD_WAKEUP_PENDING(hcd)) ++ return -EBUSY; + retval = hcd->driver->pci_suspend(hcd, do_wakeup); + suspend_report_result(hcd->driver->pci_suspend, retval); ++ ++ /* Check again in case wakeup raced with pci_suspend */ ++ if (retval == 0 && do_wakeup && HCD_WAKEUP_PENDING(hcd)) { ++ if (hcd->driver->pci_resume) ++ hcd->driver->pci_resume(hcd, false); ++ retval = -EBUSY; ++ } + if (retval) + return retval; + } +--- a/drivers/usb/core/hcd.c ++++ b/drivers/usb/core/hcd.c +@@ -1940,6 +1940,7 @@ int hcd_bus_resume(struct usb_device *rh + + dev_dbg(&rhdev->dev, "usb %s%s\n", + (msg.event & PM_EVENT_AUTO ? "auto-" : ""), "resume"); ++ clear_bit(HCD_FLAG_WAKEUP_PENDING, &hcd->flags); + if (!hcd->driver->bus_resume) + return -ENOENT; + if (hcd->state == HC_STATE_RUNNING) +@@ -1993,8 +1994,10 @@ void usb_hcd_resume_root_hub (struct usb + unsigned long flags; + + spin_lock_irqsave (&hcd_root_hub_lock, flags); +- if (hcd->rh_registered) ++ if (hcd->rh_registered) { ++ set_bit(HCD_FLAG_WAKEUP_PENDING, &hcd->flags); + queue_work(pm_wq, &hcd->wakeup_work); ++ } + spin_unlock_irqrestore (&hcd_root_hub_lock, flags); + } + EXPORT_SYMBOL_GPL(usb_hcd_resume_root_hub); +--- a/include/linux/usb/hcd.h ++++ b/include/linux/usb/hcd.h +@@ -98,6 +98,7 @@ struct usb_hcd { + #define HCD_FLAG_SAW_IRQ 1 + #define HCD_FLAG_POLL_RH 2 /* poll for rh status? */ + #define HCD_FLAG_POLL_PENDING 3 /* status has changed? */ ++#define HCD_FLAG_WAKEUP_PENDING 4 /* root hub is resuming? */ + + /* The flags can be tested using these macros; they are likely to + * be slightly faster than test_bit(). +@@ -106,6 +107,7 @@ struct usb_hcd { + #define HCD_SAW_IRQ(hcd) ((hcd)->flags & (1U << HCD_FLAG_SAW_IRQ)) + #define HCD_POLL_RH(hcd) ((hcd)->flags & (1U << HCD_FLAG_POLL_RH)) + #define HCD_POLL_PENDING(hcd) ((hcd)->flags & (1U << HCD_FLAG_POLL_PENDING)) ++#define HCD_WAKEUP_PENDING(hcd) ((hcd)->flags & (1U << HCD_FLAG_WAKEUP_PENDING)) + + /* Flags that get set only during HCD registration or removal. */ + unsigned rh_registered:1;/* is root hub registered? */ |
