Commit 088f7fec authored by Alan Stern's avatar Alan Stern Committed by Greg Kroah-Hartman

USB: implement usb_enable_autosuspend

This patch (as1326) adds usb_enable_autosuspend() and
usb_disable_autosuspend() routines for use by drivers.  If a driver
knows that its device can handle suspends and resumes correctly, it
can enable autosuspend all by itself.  This is equivalent to the user
writing "auto" to the device's power/level attribute.

The implementation differs slightly from what it used to be.  Now
autosuspend is disabled simply by doing usb_autoresume_device() (to
increment the usage counter) and enabled by doing
usb_autosuspend_device() (to decrement the usage counter).

The set_level() attribute method is updated to use the new routines,
and the USB Power-Management documentation is updated.

The patch adds a usb_enable_autosuspend() call to the hub driver's
probe routine, allowing the special-case code for hubs in quirks.c to
be removed.
Signed-off-by: default avatarAlan Stern <stern@rowland.harvard.edu>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@suse.de>
parent 0c4db6df
...@@ -229,6 +229,11 @@ necessary operations by hand or add them to a udev script. You can ...@@ -229,6 +229,11 @@ necessary operations by hand or add them to a udev script. You can
also change the idle-delay time; 2 seconds is not the best choice for also change the idle-delay time; 2 seconds is not the best choice for
every device. every device.
If a driver knows that its device has proper suspend/resume support,
it can enable autosuspend all by itself. For example, the video
driver for a laptop's webcam might do this, since these devices are
rarely used and so should normally be autosuspended.
Sometimes it turns out that even when a device does work okay with Sometimes it turns out that even when a device does work okay with
autosuspend there are still problems. For example, there are autosuspend there are still problems. For example, there are
experimental patches adding autosuspend support to the usbhid driver, experimental patches adding autosuspend support to the usbhid driver,
...@@ -384,6 +389,19 @@ autosuspend, there's no delay for an autoresume. ...@@ -384,6 +389,19 @@ autosuspend, there's no delay for an autoresume.
Other parts of the driver interface Other parts of the driver interface
----------------------------------- -----------------------------------
Drivers can enable autosuspend for their devices by calling
usb_enable_autosuspend(struct usb_device *udev);
in their probe() routine, if they know that the device is capable of
suspending and resuming correctly. This is exactly equivalent to
writing "auto" to the device's power/level attribute. Likewise,
drivers can disable autosuspend by calling
usb_disable_autosuspend(struct usb_device *udev);
This is exactly the same as writing "on" to the power/level attribute.
Sometimes a driver needs to make sure that remote wakeup is enabled Sometimes a driver needs to make sure that remote wakeup is enabled
during autosuspend. For example, there's not much point during autosuspend. For example, there's not much point
autosuspending a keyboard if the user can't cause the keyboard to do a autosuspending a keyboard if the user can't cause the keyboard to do a
......
...@@ -1415,6 +1415,48 @@ static int usb_resume_both(struct usb_device *udev, pm_message_t msg) ...@@ -1415,6 +1415,48 @@ static int usb_resume_both(struct usb_device *udev, pm_message_t msg)
#ifdef CONFIG_USB_SUSPEND #ifdef CONFIG_USB_SUSPEND
/**
* usb_enable_autosuspend - allow a USB device to be autosuspended
* @udev: the USB device which may be autosuspended
*
* This routine allows @udev to be autosuspended. An autosuspend won't
* take place until the autosuspend_delay has elapsed and all the other
* necessary conditions are satisfied.
*
* The caller must hold @udev's device lock.
*/
int usb_enable_autosuspend(struct usb_device *udev)
{
if (udev->autosuspend_disabled) {
udev->autosuspend_disabled = 0;
usb_autosuspend_device(udev);
}
return 0;
}
EXPORT_SYMBOL_GPL(usb_enable_autosuspend);
/**
* usb_disable_autosuspend - prevent a USB device from being autosuspended
* @udev: the USB device which may not be autosuspended
*
* This routine prevents @udev from being autosuspended and wakes it up
* if it is already autosuspended.
*
* The caller must hold @udev's device lock.
*/
int usb_disable_autosuspend(struct usb_device *udev)
{
int rc = 0;
if (!udev->autosuspend_disabled) {
rc = usb_autoresume_device(udev);
if (rc == 0)
udev->autosuspend_disabled = 1;
}
return rc;
}
EXPORT_SYMBOL_GPL(usb_disable_autosuspend);
/* Internal routine to adjust a device's usage counter and change /* Internal routine to adjust a device's usage counter and change
* its autosuspend state. * its autosuspend state.
*/ */
......
...@@ -1224,6 +1224,9 @@ static int hub_probe(struct usb_interface *intf, const struct usb_device_id *id) ...@@ -1224,6 +1224,9 @@ static int hub_probe(struct usb_interface *intf, const struct usb_device_id *id)
desc = intf->cur_altsetting; desc = intf->cur_altsetting;
hdev = interface_to_usbdev(intf); hdev = interface_to_usbdev(intf);
/* Hubs have proper suspend/resume support */
usb_enable_autosuspend(hdev);
if (hdev->level == MAX_TOPO_LEVEL) { if (hdev->level == MAX_TOPO_LEVEL) {
dev_err(&intf->dev, dev_err(&intf->dev,
"Unsupported bus topology: hub nested too deep\n"); "Unsupported bus topology: hub nested too deep\n");
......
...@@ -103,11 +103,10 @@ void usb_detect_quirks(struct usb_device *udev) ...@@ -103,11 +103,10 @@ void usb_detect_quirks(struct usb_device *udev)
dev_dbg(&udev->dev, "USB quirks for this device: %x\n", dev_dbg(&udev->dev, "USB quirks for this device: %x\n",
udev->quirks); udev->quirks);
/* By default, disable autosuspend for all non-hubs */ /* By default, disable autosuspend for all devices. The hub driver
#ifdef CONFIG_USB_SUSPEND * will enable it for hubs.
if (udev->descriptor.bDeviceClass != USB_CLASS_HUB) */
udev->autosuspend_disabled = 1; usb_disable_autosuspend(udev);
#endif
/* For the present, all devices default to USB-PERSIST enabled */ /* For the present, all devices default to USB-PERSIST enabled */
#if 0 /* was: #ifdef CONFIG_PM */ #if 0 /* was: #ifdef CONFIG_PM */
......
...@@ -389,34 +389,25 @@ set_level(struct device *dev, struct device_attribute *attr, ...@@ -389,34 +389,25 @@ set_level(struct device *dev, struct device_attribute *attr,
struct usb_device *udev = to_usb_device(dev); struct usb_device *udev = to_usb_device(dev);
int len = count; int len = count;
char *cp; char *cp;
int rc = 0; int rc;
int old_autosuspend_disabled;
cp = memchr(buf, '\n', count); cp = memchr(buf, '\n', count);
if (cp) if (cp)
len = cp - buf; len = cp - buf;
usb_lock_device(udev); usb_lock_device(udev);
old_autosuspend_disabled = udev->autosuspend_disabled;
/* Setting the flags without calling usb_pm_lock is a subject to
* races, but who cares...
*/
if (len == sizeof on_string - 1 && if (len == sizeof on_string - 1 &&
strncmp(buf, on_string, len) == 0) { strncmp(buf, on_string, len) == 0)
udev->autosuspend_disabled = 1; rc = usb_disable_autosuspend(udev);
rc = usb_external_resume_device(udev, PMSG_USER_RESUME);
} else if (len == sizeof auto_string - 1 && else if (len == sizeof auto_string - 1 &&
strncmp(buf, auto_string, len) == 0) { strncmp(buf, auto_string, len) == 0)
udev->autosuspend_disabled = 0; rc = usb_enable_autosuspend(udev);
rc = usb_external_resume_device(udev, PMSG_USER_RESUME);
} else else
rc = -EINVAL; rc = -EINVAL;
if (rc)
udev->autosuspend_disabled = old_autosuspend_disabled;
usb_unlock_device(udev); usb_unlock_device(udev);
return (rc < 0 ? rc : count); return (rc < 0 ? rc : count);
} }
......
...@@ -542,6 +542,9 @@ extern struct usb_device *usb_find_device(u16 vendor_id, u16 product_id); ...@@ -542,6 +542,9 @@ extern struct usb_device *usb_find_device(u16 vendor_id, u16 product_id);
/* USB autosuspend and autoresume */ /* USB autosuspend and autoresume */
#ifdef CONFIG_USB_SUSPEND #ifdef CONFIG_USB_SUSPEND
extern int usb_enable_autosuspend(struct usb_device *udev);
extern int usb_disable_autosuspend(struct usb_device *udev);
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 int usb_autopm_get_interface_async(struct usb_interface *intf);
...@@ -565,6 +568,11 @@ static inline void usb_mark_last_busy(struct usb_device *udev) ...@@ -565,6 +568,11 @@ static inline void usb_mark_last_busy(struct usb_device *udev)
#else #else
static inline int usb_enable_autosuspend(struct usb_device *udev)
{ return 0; }
static inline int usb_disable_autosuspend(struct usb_device *udev)
{ return 0; }
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) static inline int usb_autopm_get_interface_async(struct usb_interface *intf)
......
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