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>
This commit is contained in:
Alan Stern 2008-11-12 16:19:49 -05:00 коммит произвёл Greg Kroah-Hartman
Родитель d4f373e57d
Коммит 9ac39f28b5
6 изменённых файлов: 117 добавлений и 7 удалений

Просмотреть файл

@ -313,11 +313,13 @@ three of the methods listed above. In addition, a driver indicates
that it supports autosuspend by setting the .supports_autosuspend flag that it supports autosuspend by setting the .supports_autosuspend flag
in its usb_driver structure. It is then responsible for informing the in its usb_driver structure. It is then responsible for informing the
USB core whenever one of its interfaces becomes busy or idle. The USB core whenever one of its interfaces becomes busy or idle. The
driver does so by calling these three functions: driver does so by calling these five functions:
int usb_autopm_get_interface(struct usb_interface *intf); int usb_autopm_get_interface(struct usb_interface *intf);
void usb_autopm_put_interface(struct usb_interface *intf); void usb_autopm_put_interface(struct usb_interface *intf);
int usb_autopm_set_interface(struct usb_interface *intf); int usb_autopm_set_interface(struct usb_interface *intf);
int usb_autopm_get_interface_async(struct usb_interface *intf);
void usb_autopm_put_interface_async(struct usb_interface *intf);
The functions work by maintaining a counter in the usb_interface The functions work by maintaining a counter in the usb_interface
structure. When intf->pm_usage_count is > 0 then the interface is structure. When intf->pm_usage_count is > 0 then the interface is
@ -330,10 +332,12 @@ associated with the device itself rather than any of its interfaces.
This field is used only by the USB core.) This field is used only by the USB core.)
The driver owns intf->pm_usage_count; it can modify the value however The driver owns intf->pm_usage_count; it can modify the value however
and whenever it likes. A nice aspect of the usb_autopm_* routines is and whenever it likes. A nice aspect of the non-async usb_autopm_*
that the changes they make are protected by the usb_device structure's routines is that the changes they make are protected by the usb_device
PM mutex (udev->pm_mutex); however drivers may change pm_usage_count structure's PM mutex (udev->pm_mutex); however drivers may change
without holding the mutex. pm_usage_count without holding the mutex. Drivers using the async
routines are responsible for their own synchronization and mutual
exclusion.
usb_autopm_get_interface() increments pm_usage_count and usb_autopm_get_interface() increments pm_usage_count and
attempts an autoresume if the new value is > 0 and the attempts an autoresume if the new value is > 0 and the
@ -348,6 +352,14 @@ without holding the mutex.
is suspended, and it attempts an autosuspend if the value is is suspended, and it attempts an autosuspend if the value is
<= 0 and the device isn't suspended. <= 0 and the device isn't suspended.
usb_autopm_get_interface_async() and
usb_autopm_put_interface_async() do almost the same things as
their non-async counterparts. The differences are: they do
not acquire the PM mutex, and they use a workqueue to do their
jobs. As a result they can be called in an atomic context,
such as an URB's completion handler, but when they return the
device will not generally not yet be in the desired state.
There also are a couple of utility routines drivers can use: There also are a couple of utility routines drivers can use:
usb_autopm_enable() sets pm_usage_cnt to 0 and then calls usb_autopm_enable() sets pm_usage_cnt to 0 and then calls

Просмотреть файл

@ -1341,6 +1341,19 @@ void usb_autosuspend_work(struct work_struct *work)
usb_autopm_do_device(udev, 0); 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 * usb_autosuspend_device - delayed autosuspend of a USB device and its interfaces
* @udev: the usb_device to autosuspend * @udev: the usb_device to autosuspend
@ -1491,6 +1504,45 @@ void usb_autopm_put_interface(struct usb_interface *intf)
} }
EXPORT_SYMBOL_GPL(usb_autopm_put_interface); 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 * usb_autopm_get_interface - increment a USB interface's PM-usage counter
* @intf: the usb_interface whose counter should be incremented * @intf: the usb_interface whose counter should be incremented
@ -1536,6 +1588,37 @@ int usb_autopm_get_interface(struct usb_interface *intf)
} }
EXPORT_SYMBOL_GPL(usb_autopm_get_interface); 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 * usb_autopm_set_interface - set a USB interface's autosuspend state
* @intf: the usb_interface whose state should be set * @intf: the usb_interface whose state should be set
@ -1563,6 +1646,9 @@ EXPORT_SYMBOL_GPL(usb_autopm_set_interface);
void usb_autosuspend_work(struct work_struct *work) void usb_autosuspend_work(struct work_struct *work)
{} {}
void usb_autoresume_work(struct work_struct *work)
{}
#endif /* CONFIG_USB_SUSPEND */ #endif /* CONFIG_USB_SUSPEND */
/** /**

Просмотреть файл

@ -1374,8 +1374,9 @@ static void usb_stop_pm(struct usb_device *udev)
usb_autosuspend_device(udev->parent); usb_autosuspend_device(udev->parent);
usb_pm_unlock(udev); usb_pm_unlock(udev);
/* Stop any autosuspend requests already submitted */ /* Stop any autosuspend or autoresume requests already submitted */
cancel_rearming_delayed_work(&udev->autosuspend); cancel_delayed_work_sync(&udev->autosuspend);
cancel_work_sync(&udev->autoresume);
} }
#else #else

Просмотреть файл

@ -402,6 +402,7 @@ struct usb_device *usb_alloc_dev(struct usb_device *parent,
#ifdef CONFIG_PM #ifdef CONFIG_PM
mutex_init(&dev->pm_mutex); mutex_init(&dev->pm_mutex);
INIT_DELAYED_WORK(&dev->autosuspend, usb_autosuspend_work); INIT_DELAYED_WORK(&dev->autosuspend, usb_autosuspend_work);
INIT_WORK(&dev->autoresume, usb_autoresume_work);
dev->autosuspend_delay = usb_autosuspend_delay * HZ; dev->autosuspend_delay = usb_autosuspend_delay * HZ;
dev->connect_time = jiffies; dev->connect_time = jiffies;
dev->active_duration = -jiffies; dev->active_duration = -jiffies;

Просмотреть файл

@ -45,6 +45,7 @@ extern int usb_suspend(struct device *dev, pm_message_t msg);
extern int usb_resume(struct device *dev); extern int usb_resume(struct device *dev);
extern void usb_autosuspend_work(struct work_struct *work); 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_suspend(struct usb_device *dev);
extern int usb_port_resume(struct usb_device *dev); extern int usb_port_resume(struct usb_device *dev);
extern int usb_external_suspend_device(struct usb_device *udev, extern int usb_external_suspend_device(struct usb_device *udev,

Просмотреть файл

@ -398,6 +398,7 @@ struct usb_tt;
* @urbnum: number of URBs submitted for the whole device * @urbnum: number of URBs submitted for the whole device
* @active_duration: total time device is not suspended * @active_duration: total time device is not suspended
* @autosuspend: for delayed autosuspends * @autosuspend: for delayed autosuspends
* @autoresume: for autoresumes requested while in_interrupt
* @pm_mutex: protects PM operations * @pm_mutex: protects PM operations
* @last_busy: time of last use * @last_busy: time of last use
* @autosuspend_delay: in jiffies * @autosuspend_delay: in jiffies
@ -476,6 +477,7 @@ struct usb_device {
#ifdef CONFIG_PM #ifdef CONFIG_PM
struct delayed_work autosuspend; struct delayed_work autosuspend;
struct work_struct autoresume;
struct mutex pm_mutex; struct mutex pm_mutex;
unsigned long last_busy; unsigned long last_busy;
@ -513,6 +515,8 @@ extern struct usb_device *usb_find_device(u16 vendor_id, u16 product_id);
extern int usb_autopm_set_interface(struct usb_interface *intf); extern int usb_autopm_set_interface(struct usb_interface *intf);
extern int usb_autopm_get_interface(struct usb_interface *intf); extern int usb_autopm_get_interface(struct usb_interface *intf);
extern void usb_autopm_put_interface(struct usb_interface *intf); extern void usb_autopm_put_interface(struct usb_interface *intf);
extern int usb_autopm_get_interface_async(struct usb_interface *intf);
extern void usb_autopm_put_interface_async(struct usb_interface *intf);
static inline void usb_autopm_enable(struct usb_interface *intf) static inline void usb_autopm_enable(struct usb_interface *intf)
{ {
@ -539,8 +543,13 @@ static inline int usb_autopm_set_interface(struct usb_interface *intf)
static inline int usb_autopm_get_interface(struct usb_interface *intf) static inline int usb_autopm_get_interface(struct usb_interface *intf)
{ return 0; } { return 0; }
static inline int usb_autopm_get_interface_async(struct usb_interface *intf)
{ return 0; }
static inline void usb_autopm_put_interface(struct usb_interface *intf) static inline void usb_autopm_put_interface(struct usb_interface *intf)
{ } { }
static inline void usb_autopm_put_interface_async(struct usb_interface *intf)
{ }
static inline void usb_autopm_enable(struct usb_interface *intf) static inline void usb_autopm_enable(struct usb_interface *intf)
{ } { }
static inline void usb_autopm_disable(struct usb_interface *intf) static inline void usb_autopm_disable(struct usb_interface *intf)