Commit 979d5199 authored by David Brownell's avatar David Brownell Committed by Greg Kroah-Hartman

[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: default avatarDavid Brownell <dbrownell@users.sourceforge.net>
Signed-off-by: default avatarGreg 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(-)
parent 9293677a
...@@ -36,6 +36,7 @@ void dpm_runtime_resume(struct device * dev) ...@@ -36,6 +36,7 @@ void dpm_runtime_resume(struct device * dev)
runtime_resume(dev); runtime_resume(dev);
up(&dpm_sem); up(&dpm_sem);
} }
EXPORT_SYMBOL(dpm_runtime_resume);
/** /**
......
...@@ -1143,10 +1143,20 @@ static int hcd_submit_urb (struct urb *urb, gfp_t mem_flags) ...@@ -1143,10 +1143,20 @@ static int hcd_submit_urb (struct urb *urb, gfp_t mem_flags)
else switch (hcd->state) { else switch (hcd->state) {
case HC_STATE_RUNNING: case HC_STATE_RUNNING:
case HC_STATE_RESUMING: case HC_STATE_RESUMING:
doit:
usb_get_dev (urb->dev); usb_get_dev (urb->dev);
list_add_tail (&urb->urb_list, &ep->urb_list); list_add_tail (&urb->urb_list, &ep->urb_list);
status = 0; status = 0;
break; 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: default:
status = -ESHUTDOWN; status = -ESHUTDOWN;
break; break;
...@@ -1294,12 +1304,6 @@ static int hcd_unlink_urb (struct urb *urb, int status) ...@@ -1294,12 +1304,6 @@ static int hcd_unlink_urb (struct urb *urb, int status)
goto done; 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 */ /* insist the urb is still queued */
list_for_each(tmp, &ep->urb_list) { list_for_each(tmp, &ep->urb_list) {
if (tmp == &urb->urb_list) if (tmp == &urb->urb_list)
...@@ -1459,6 +1463,8 @@ static int hcd_hub_resume (struct usb_bus *bus) ...@@ -1459,6 +1463,8 @@ static int hcd_hub_resume (struct usb_bus *bus)
hcd = container_of (bus, struct usb_hcd, self); hcd = container_of (bus, struct usb_hcd, self);
if (!hcd->driver->hub_resume) if (!hcd->driver->hub_resume)
return -ENOENT; return -ENOENT;
if (hcd->state == HC_STATE_RUNNING)
return 0;
hcd->state = HC_STATE_RESUMING; hcd->state = HC_STATE_RESUMING;
status = hcd->driver->hub_resume (hcd); status = hcd->driver->hub_resume (hcd);
if (status == 0) if (status == 0)
...@@ -1471,6 +1477,50 @@ static int hcd_hub_resume (struct usb_bus *bus) ...@@ -1471,6 +1477,50 @@ static int hcd_hub_resume (struct usb_bus *bus)
return status; 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 * usb_hcd_resume_root_hub - called by HCD to resume its root hub
* @hcd: host controller for this root hub * @hcd: host controller for this root hub
...@@ -1478,7 +1528,7 @@ static int hcd_hub_resume (struct usb_bus *bus) ...@@ -1478,7 +1528,7 @@ static int hcd_hub_resume (struct usb_bus *bus)
* The USB host controller calls this function when its root hub is * The USB host controller calls this function when its root hub is
* suspended (with the remote wakeup feature enabled) and a remote * suspended (with the remote wakeup feature enabled) and a remote
* wakeup request is received. It queues a request for khubd to * 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) void usb_hcd_resume_root_hub (struct usb_hcd *hcd)
{ {
......
...@@ -355,6 +355,7 @@ extern long usb_calc_bus_time (int speed, int is_input, ...@@ -355,6 +355,7 @@ extern long usb_calc_bus_time (int speed, int is_input,
extern struct usb_bus *usb_alloc_bus (struct usb_operations *); 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_hcd_resume_root_hub (struct usb_hcd *hcd);
extern void usb_set_device_state(struct usb_device *udev, extern void usb_set_device_state(struct usb_device *udev,
......
...@@ -449,11 +449,18 @@ static void hub_power_on(struct usb_hub *hub) ...@@ -449,11 +449,18 @@ static void hub_power_on(struct usb_hub *hub)
msleep(max(pgood_delay, (unsigned) 100)); 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->quiescing = 1;
hub->activating = 0; 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); usb_kill_urb(hub->urb);
if (hub->has_indicators) if (hub->has_indicators)
cancel_delayed_work(&hub->leds); cancel_delayed_work(&hub->leds);
...@@ -467,6 +474,7 @@ static void hub_activate(struct usb_hub *hub) ...@@ -467,6 +474,7 @@ static void hub_activate(struct usb_hub *hub)
hub->quiescing = 0; hub->quiescing = 0;
hub->activating = 1; hub->activating = 1;
hub->resume_root_hub = 0;
status = usb_submit_urb(hub->urb, GFP_NOIO); status = usb_submit_urb(hub->urb, GFP_NOIO);
if (status < 0) if (status < 0)
dev_err(hub->intfdev, "activate --> %d\n", status); dev_err(hub->intfdev, "activate --> %d\n", status);
...@@ -1959,6 +1967,18 @@ static int hub_resume(struct usb_interface *intf) ...@@ -1959,6 +1967,18 @@ static int hub_resume(struct usb_interface *intf)
return 0; 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) void usb_resume_root_hub(struct usb_device *hdev)
{ {
struct usb_hub *hub = hdev_to_hub(hdev); struct usb_hub *hub = hdev_to_hub(hdev);
...@@ -2616,21 +2636,30 @@ static void hub_events(void) ...@@ -2616,21 +2636,30 @@ static void hub_events(void)
intf = to_usb_interface(hub->intfdev); intf = to_usb_interface(hub->intfdev);
hub_dev = &intf->dev; 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 hdev->state, hub->descriptor
? hub->descriptor->bNbrPorts ? hub->descriptor->bNbrPorts
: 0, : 0,
/* NOTE: expects max 15 ports... */ /* NOTE: expects max 15 ports... */
(u16) hub->change_bits[0], (u16) hub->change_bits[0],
(u16) hub->event_bits[0]); (u16) hub->event_bits[0],
i ? ", resume root" : "");
usb_get_intf(intf); usb_get_intf(intf);
i = hub->resume_root_hub;
spin_unlock_irq(&hub_event_lock); spin_unlock_irq(&hub_event_lock);
/* Is this is a root hub wanting to be resumed? */ /* Is this is a root hub wanting to reactivate the downstream
if (i) * ports? If so, be sure the interface resumes even if its
usb_resume_device(hdev); * 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 /* Lock the device, then check to see if we were
* disconnected while waiting for the lock to succeed. */ * disconnected while waiting for the lock to succeed. */
......
...@@ -1427,6 +1427,7 @@ static int usb_generic_suspend(struct device *dev, pm_message_t message) ...@@ -1427,6 +1427,7 @@ static int usb_generic_suspend(struct device *dev, pm_message_t message)
/* USB devices enter SUSPEND state through their hubs, but can be /* USB devices enter SUSPEND state through their hubs, but can be
* marked for FREEZE as soon as their children are already idled. * 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->driver == &usb_generic_driver) {
if (dev->power.power_state.event == message.event) if (dev->power.power_state.event == message.event)
...@@ -1435,10 +1436,6 @@ static int usb_generic_suspend(struct device *dev, pm_message_t message) ...@@ -1435,10 +1436,6 @@ static int usb_generic_suspend(struct device *dev, pm_message_t message)
status = device_for_each_child(dev, NULL, verify_suspended); status = device_for_each_child(dev, NULL, verify_suspended);
if (status) if (status)
return status; return status;
if (message.event == PM_EVENT_FREEZE) {
dev->power.power_state = message;
return 0;
}
return usb_suspend_device (to_usb_device(dev)); return usb_suspend_device (to_usb_device(dev));
} }
...@@ -1471,14 +1468,22 @@ static int usb_generic_resume(struct device *dev) ...@@ -1471,14 +1468,22 @@ static int usb_generic_resume(struct device *dev)
{ {
struct usb_interface *intf; struct usb_interface *intf;
struct usb_driver *driver; struct usb_driver *driver;
struct usb_device *udev;
int status; int status;
if (dev->power.power_state.event == PM_EVENT_ON) if (dev->power.power_state.event == PM_EVENT_ON)
return 0; 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 */ /* 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)); return usb_resume_device (to_usb_device(dev));
}
if ((dev->driver == NULL) || if ((dev->driver == NULL) ||
(dev->driver_data == &usb_generic_driver_data)) (dev->driver_data == &usb_generic_driver_data))
...@@ -1487,11 +1492,14 @@ static int usb_generic_resume(struct device *dev) ...@@ -1487,11 +1492,14 @@ static int usb_generic_resume(struct device *dev)
intf = to_usb_interface(dev); intf = to_usb_interface(dev);
driver = to_usb_driver(dev->driver); 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; /* if driver was suspended, it has a resume method;
* however, sysfs can wrongly mark things as suspended * however, sysfs can wrongly mark things as suspended
* (on the "no suspend method" FIXME path above) * (on the "no suspend method" FIXME path above)
*/ */
mark_active(intf);
if (driver->resume) { if (driver->resume) {
status = driver->resume(intf); status = driver->resume(intf);
if (status) { if (status) {
......
...@@ -19,6 +19,7 @@ extern void usb_lock_all_devices(void); ...@@ -19,6 +19,7 @@ extern void usb_lock_all_devices(void);
extern void usb_unlock_all_devices(void); extern void usb_unlock_all_devices(void);
extern void usb_kick_khubd(struct usb_device *dev); 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 void usb_resume_root_hub(struct usb_device *dev);
extern int usb_hub_init(void); extern int usb_hub_init(void);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment