runtime_resume(dev);
up(&dpm_sem);
}
+EXPORT_SYMBOL(dpm_runtime_resume);
/**
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;
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)
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)
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
* 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)
{
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,
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);
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);
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);
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. */
/* 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)
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));
}
{
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))
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) {
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);