USB: add asynchronous autosuspend/autoresume support
This patch (as1160b) adds support routines for asynchronous autosuspend
and autoresume, with accompanying documentation updates. There
already are several potential users of this interface, and others are
likely to arise as autosuspend support becomes more widespread.
Signed-off-by: Alan Stern <stern@rowland.harvard.edu>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c
index 8c08130..23b3c7e 100644
--- a/drivers/usb/core/driver.c
+++ b/drivers/usb/core/driver.c
@@ -1341,6 +1341,19 @@
usb_autopm_do_device(udev, 0);
}
+/* usb_autoresume_work - callback routine to autoresume a USB device */
+void usb_autoresume_work(struct work_struct *work)
+{
+ struct usb_device *udev =
+ container_of(work, struct usb_device, autoresume);
+
+ /* Wake it up, let the drivers do their thing, and then put it
+ * back to sleep.
+ */
+ if (usb_autopm_do_device(udev, 1) == 0)
+ usb_autopm_do_device(udev, -1);
+}
+
/**
* usb_autosuspend_device - delayed autosuspend of a USB device and its interfaces
* @udev: the usb_device to autosuspend
@@ -1492,6 +1505,45 @@
EXPORT_SYMBOL_GPL(usb_autopm_put_interface);
/**
+ * usb_autopm_put_interface_async - decrement a USB interface's PM-usage counter
+ * @intf: the usb_interface whose counter should be decremented
+ *
+ * This routine does essentially the same thing as
+ * usb_autopm_put_interface(): it decrements @intf's usage counter and
+ * queues a delayed autosuspend request if the counter is <= 0. The
+ * difference is that it does not acquire the device's pm_mutex;
+ * callers must handle all synchronization issues themselves.
+ *
+ * Typically a driver would call this routine during an URB's completion
+ * handler, if no more URBs were pending.
+ *
+ * This routine can run in atomic context.
+ */
+void usb_autopm_put_interface_async(struct usb_interface *intf)
+{
+ struct usb_device *udev = interface_to_usbdev(intf);
+ int status = 0;
+
+ if (intf->condition == USB_INTERFACE_UNBOUND) {
+ status = -ENODEV;
+ } else {
+ udev->last_busy = jiffies;
+ --intf->pm_usage_cnt;
+ if (udev->autosuspend_disabled || udev->autosuspend_delay < 0)
+ status = -EPERM;
+ else if (intf->pm_usage_cnt <= 0 &&
+ !timer_pending(&udev->autosuspend.timer)) {
+ queue_delayed_work(ksuspend_usb_wq, &udev->autosuspend,
+ round_jiffies_relative(
+ udev->autosuspend_delay));
+ }
+ }
+ dev_vdbg(&intf->dev, "%s: status %d cnt %d\n",
+ __func__, status, intf->pm_usage_cnt);
+}
+EXPORT_SYMBOL_GPL(usb_autopm_put_interface_async);
+
+/**
* usb_autopm_get_interface - increment a USB interface's PM-usage counter
* @intf: the usb_interface whose counter should be incremented
*
@@ -1537,6 +1589,37 @@
EXPORT_SYMBOL_GPL(usb_autopm_get_interface);
/**
+ * usb_autopm_get_interface_async - increment a USB interface's PM-usage counter
+ * @intf: the usb_interface whose counter should be incremented
+ *
+ * This routine does much the same thing as
+ * usb_autopm_get_interface(): it increments @intf's usage counter and
+ * queues an autoresume request if the result is > 0. The differences
+ * are that it does not acquire the device's pm_mutex (callers must
+ * handle all synchronization issues themselves), and it does not
+ * autoresume the device directly (it only queues a request). After a
+ * successful call, the device will generally not yet be resumed.
+ *
+ * This routine can run in atomic context.
+ */
+int usb_autopm_get_interface_async(struct usb_interface *intf)
+{
+ struct usb_device *udev = interface_to_usbdev(intf);
+ int status = 0;
+
+ if (intf->condition == USB_INTERFACE_UNBOUND)
+ status = -ENODEV;
+ else if (udev->autoresume_disabled)
+ status = -EPERM;
+ else if (++intf->pm_usage_cnt > 0 && udev->state == USB_STATE_SUSPENDED)
+ queue_work(ksuspend_usb_wq, &udev->autoresume);
+ dev_vdbg(&intf->dev, "%s: status %d cnt %d\n",
+ __func__, status, intf->pm_usage_cnt);
+ return status;
+}
+EXPORT_SYMBOL_GPL(usb_autopm_get_interface_async);
+
+/**
* usb_autopm_set_interface - set a USB interface's autosuspend state
* @intf: the usb_interface whose state should be set
*
@@ -1563,6 +1646,9 @@
void usb_autosuspend_work(struct work_struct *work)
{}
+void usb_autoresume_work(struct work_struct *work)
+{}
+
#endif /* CONFIG_USB_SUSPEND */
/**
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index b19cbfc..95fb310 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -1374,8 +1374,9 @@
usb_autosuspend_device(udev->parent);
usb_pm_unlock(udev);
- /* Stop any autosuspend requests already submitted */
- cancel_rearming_delayed_work(&udev->autosuspend);
+ /* Stop any autosuspend or autoresume requests already submitted */
+ cancel_delayed_work_sync(&udev->autosuspend);
+ cancel_work_sync(&udev->autoresume);
}
#else
diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c
index 400fa4c..44f2fc7 100644
--- a/drivers/usb/core/usb.c
+++ b/drivers/usb/core/usb.c
@@ -402,6 +402,7 @@
#ifdef CONFIG_PM
mutex_init(&dev->pm_mutex);
INIT_DELAYED_WORK(&dev->autosuspend, usb_autosuspend_work);
+ INIT_WORK(&dev->autoresume, usb_autoresume_work);
dev->autosuspend_delay = usb_autosuspend_delay * HZ;
dev->connect_time = jiffies;
dev->active_duration = -jiffies;
diff --git a/drivers/usb/core/usb.h b/drivers/usb/core/usb.h
index 9a1a45a..b60ebb4d 100644
--- a/drivers/usb/core/usb.h
+++ b/drivers/usb/core/usb.h
@@ -45,6 +45,7 @@
extern int usb_resume(struct device *dev);
extern void usb_autosuspend_work(struct work_struct *work);
+extern void usb_autoresume_work(struct work_struct *work);
extern int usb_port_suspend(struct usb_device *dev);
extern int usb_port_resume(struct usb_device *dev);
extern int usb_external_suspend_device(struct usb_device *udev,