[PATCH] root hub changes (lesser half)
This patch collects various small updates related to root hubs, to shrink
later patches which build on them.
- For root hub suspend/resume support:
* Make the existing usb_hcd_resume_root_hub() routine respect pmcore
locking, exporting and using the dpm_runtime_resume() method.
* Add a new usb_hcd_suspend_root_hub() to pair with that routine.
(Essential to make OHCI autosuspend behave again...)
* HC_SUSPENDED by itself only refers to the root hub's downstream ports.
So let HCDs see root hub URBs unless the parent device is suspended.
- Remove an assertion we no longer need (and now, also don't want).
- Generic suspend/resume updates to work better with swsusp.
* Ignore the FREEZE vs SUSPEND distinction for hardware; trying to
use it breaks the swsusp snapshots it's supposed to help (sigh).
* On resume, mark devices as resumed right away, but then
do nothing else if the device is marked NOTATTACHED.
These changes shouldn't be very noticable by themselves.
Signed-off-by: David Brownell <dbrownell@users.sourceforge.net>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
drivers/base/power/runtime.c | 1
drivers/usb/core/hcd.c | 64 ++++++++++++++++++++++++++++++++++++++-----
drivers/usb/core/hcd.h | 1
drivers/usb/core/hub.c | 45 ++++++++++++++++++++++++------
drivers/usb/core/usb.c | 20 +++++++++----
drivers/usb/core/usb.h | 1
6 files changed, 111 insertions(+), 21 deletions(-)
diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c
index 375382f..de59bb5 100644
--- a/drivers/usb/core/hcd.c
+++ b/drivers/usb/core/hcd.c
@@ -1143,10 +1143,20 @@
else switch (hcd->state) {
case HC_STATE_RUNNING:
case HC_STATE_RESUMING:
+doit:
usb_get_dev (urb->dev);
list_add_tail (&urb->urb_list, &ep->urb_list);
status = 0;
break;
+ case HC_STATE_SUSPENDED:
+ /* HC upstream links (register access, wakeup signaling) can work
+ * even when the downstream links (and DMA etc) are quiesced; let
+ * usbcore talk to the root hub.
+ */
+ if (hcd->self.controller->power.power_state.event == PM_EVENT_ON
+ && urb->dev->parent == NULL)
+ goto doit;
+ /* FALL THROUGH */
default:
status = -ESHUTDOWN;
break;
@@ -1294,12 +1304,6 @@
goto done;
}
- /* running ~= hc unlink handshake works (irq, timer, etc)
- * halted ~= no unlink handshake is needed
- * suspended, resuming == should never happen
- */
- WARN_ON (!HC_IS_RUNNING (hcd->state) && hcd->state != HC_STATE_HALT);
-
/* insist the urb is still queued */
list_for_each(tmp, &ep->urb_list) {
if (tmp == &urb->urb_list)
@@ -1459,6 +1463,8 @@
hcd = container_of (bus, struct usb_hcd, self);
if (!hcd->driver->hub_resume)
return -ENOENT;
+ if (hcd->state == HC_STATE_RUNNING)
+ return 0;
hcd->state = HC_STATE_RESUMING;
status = hcd->driver->hub_resume (hcd);
if (status == 0)
@@ -1471,6 +1477,50 @@
return status;
}
+/*
+ * usb_hcd_suspend_root_hub - HCD autosuspends downstream ports
+ * @hcd: host controller for this root hub
+ *
+ * This call arranges that usb_hcd_resume_root_hub() is safe to call later;
+ * that the HCD's root hub polling is deactivated; and that the root's hub
+ * driver is suspended. HCDs may call this to autosuspend when their root
+ * hub's downstream ports are all inactive: unpowered, disconnected,
+ * disabled, or suspended.
+ *
+ * The HCD will autoresume on device connect change detection (using SRP
+ * or a D+/D- pullup). The HCD also autoresumes on remote wakeup signaling
+ * from any ports that are suspended (if that is enabled). In most cases,
+ * overcurrent signaling (on powered ports) will also start autoresume.
+ *
+ * Always called with IRQs blocked.
+ */
+void usb_hcd_suspend_root_hub (struct usb_hcd *hcd)
+{
+ struct urb *urb;
+
+ spin_lock (&hcd_root_hub_lock);
+ usb_suspend_root_hub (hcd->self.root_hub);
+
+ /* force status urb to complete/unlink while suspended */
+ if (hcd->status_urb) {
+ urb = hcd->status_urb;
+ urb->status = -ECONNRESET;
+ urb->hcpriv = NULL;
+ urb->actual_length = 0;
+
+ del_timer (&hcd->rh_timer);
+ hcd->poll_pending = 0;
+ hcd->status_urb = NULL;
+ } else
+ urb = NULL;
+ spin_unlock (&hcd_root_hub_lock);
+ hcd->state = HC_STATE_SUSPENDED;
+
+ if (urb)
+ usb_hcd_giveback_urb (hcd, urb, NULL);
+}
+EXPORT_SYMBOL_GPL(usb_hcd_suspend_root_hub);
+
/**
* usb_hcd_resume_root_hub - called by HCD to resume its root hub
* @hcd: host controller for this root hub
@@ -1478,7 +1528,7 @@
* The USB host controller calls this function when its root hub is
* suspended (with the remote wakeup feature enabled) and a remote
* wakeup request is received. It queues a request for khubd to
- * resume the root hub.
+ * resume the root hub (that is, manage its downstream ports again).
*/
void usb_hcd_resume_root_hub (struct usb_hcd *hcd)
{
diff --git a/drivers/usb/core/hcd.h b/drivers/usb/core/hcd.h
index 1f1ed62..eb21f13 100644
--- a/drivers/usb/core/hcd.h
+++ b/drivers/usb/core/hcd.h
@@ -355,6 +355,7 @@
extern struct usb_bus *usb_alloc_bus (struct usb_operations *);
+extern void usb_hcd_suspend_root_hub (struct usb_hcd *hcd);
extern void usb_hcd_resume_root_hub (struct usb_hcd *hcd);
extern void usb_set_device_state(struct usb_device *udev,
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index 6600644..3c8d8d1 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -449,11 +449,18 @@
msleep(max(pgood_delay, (unsigned) 100));
}
-static void hub_quiesce(struct usb_hub *hub)
+static inline void __hub_quiesce(struct usb_hub *hub)
{
- /* stop khubd and related activity */
+ /* (nonblocking) khubd and related activity won't re-trigger */
hub->quiescing = 1;
hub->activating = 0;
+ hub->resume_root_hub = 0;
+}
+
+static void hub_quiesce(struct usb_hub *hub)
+{
+ /* (blocking) stop khubd and related activity */
+ __hub_quiesce(hub);
usb_kill_urb(hub->urb);
if (hub->has_indicators)
cancel_delayed_work(&hub->leds);
@@ -467,6 +474,7 @@
hub->quiescing = 0;
hub->activating = 1;
+ hub->resume_root_hub = 0;
status = usb_submit_urb(hub->urb, GFP_NOIO);
if (status < 0)
dev_err(hub->intfdev, "activate --> %d\n", status);
@@ -1959,6 +1967,18 @@
return 0;
}
+void usb_suspend_root_hub(struct usb_device *hdev)
+{
+ struct usb_hub *hub = hdev_to_hub(hdev);
+
+ /* This also makes any led blinker stop retriggering. We're called
+ * from irq, so the blinker might still be scheduled. Caller promises
+ * that the root hub status URB will be canceled.
+ */
+ __hub_quiesce(hub);
+ mark_quiesced(to_usb_interface(hub->intfdev));
+}
+
void usb_resume_root_hub(struct usb_device *hdev)
{
struct usb_hub *hub = hdev_to_hub(hdev);
@@ -2616,21 +2636,30 @@
intf = to_usb_interface(hub->intfdev);
hub_dev = &intf->dev;
- dev_dbg(hub_dev, "state %d ports %d chg %04x evt %04x\n",
+ i = hub->resume_root_hub;
+
+ dev_dbg(hub_dev, "state %d ports %d chg %04x evt %04x%s\n",
hdev->state, hub->descriptor
? hub->descriptor->bNbrPorts
: 0,
/* NOTE: expects max 15 ports... */
(u16) hub->change_bits[0],
- (u16) hub->event_bits[0]);
+ (u16) hub->event_bits[0],
+ i ? ", resume root" : "");
usb_get_intf(intf);
- i = hub->resume_root_hub;
spin_unlock_irq(&hub_event_lock);
- /* Is this is a root hub wanting to be resumed? */
- if (i)
- usb_resume_device(hdev);
+ /* Is this is a root hub wanting to reactivate the downstream
+ * ports? If so, be sure the interface resumes even if its
+ * stub "device" node was never suspended.
+ */
+ if (i) {
+ extern void dpm_runtime_resume(struct device *);
+
+ dpm_runtime_resume(&hdev->dev);
+ dpm_runtime_resume(&intf->dev);
+ }
/* Lock the device, then check to see if we were
* disconnected while waiting for the lock to succeed. */
diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c
index e89dbd4..2493e7d 100644
--- a/drivers/usb/core/usb.c
+++ b/drivers/usb/core/usb.c
@@ -1427,6 +1427,7 @@
/* USB devices enter SUSPEND state through their hubs, but can be
* marked for FREEZE as soon as their children are already idled.
+ * But those semantics are useless, so we equate the two (sigh).
*/
if (dev->driver == &usb_generic_driver) {
if (dev->power.power_state.event == message.event)
@@ -1435,10 +1436,6 @@
status = device_for_each_child(dev, NULL, verify_suspended);
if (status)
return status;
- if (message.event == PM_EVENT_FREEZE) {
- dev->power.power_state = message;
- return 0;
- }
return usb_suspend_device (to_usb_device(dev));
}
@@ -1471,14 +1468,22 @@
{
struct usb_interface *intf;
struct usb_driver *driver;
+ struct usb_device *udev;
int status;
if (dev->power.power_state.event == PM_EVENT_ON)
return 0;
+ /* mark things as "on" immediately, no matter what errors crop up */
+ dev->power.power_state.event = PM_EVENT_ON;
+
/* devices resume through their hubs */
- if (dev->driver == &usb_generic_driver)
+ if (dev->driver == &usb_generic_driver) {
+ udev = to_usb_device(dev);
+ if (udev->state == USB_STATE_NOTATTACHED)
+ return 0;
return usb_resume_device (to_usb_device(dev));
+ }
if ((dev->driver == NULL) ||
(dev->driver_data == &usb_generic_driver_data))
@@ -1487,11 +1492,14 @@
intf = to_usb_interface(dev);
driver = to_usb_driver(dev->driver);
+ udev = interface_to_usbdev(intf);
+ if (udev->state == USB_STATE_NOTATTACHED)
+ return 0;
+
/* if driver was suspended, it has a resume method;
* however, sysfs can wrongly mark things as suspended
* (on the "no suspend method" FIXME path above)
*/
- mark_active(intf);
if (driver->resume) {
status = driver->resume(intf);
if (status) {
diff --git a/drivers/usb/core/usb.h b/drivers/usb/core/usb.h
index 3741a99..7add46e 100644
--- a/drivers/usb/core/usb.h
+++ b/drivers/usb/core/usb.h
@@ -19,6 +19,7 @@
extern void usb_unlock_all_devices(void);
extern void usb_kick_khubd(struct usb_device *dev);
+extern void usb_suspend_root_hub(struct usb_device *hdev);
extern void usb_resume_root_hub(struct usb_device *dev);
extern int usb_hub_init(void);