aboutsummaryrefslogtreecommitdiffstats
path: root/usb/usb-fix-race-between-root-hub-wakeup-controller-suspend.patch
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.patch98
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? */