USB: implement usb_enable_autosuspend
Alan Stern [Fri, 8 Jan 2010 17:56:54 +0000 (12:56 -0500)]
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: Alan Stern <stern@rowland.harvard.edu>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>

Documentation/usb/power-management.txt
drivers/usb/core/driver.c
drivers/usb/core/hub.c
drivers/usb/core/quirks.c
drivers/usb/core/sysfs.c
include/linux/usb.h

index 3bf6818..e3fa189 100644 (file)
@@ -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
 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
 autosuspend there are still problems.  For example, there are
 experimental patches adding autosuspend support to the usbhid driver,
@@ -384,6 +389,19 @@ autosuspend, there's no delay for an autoresume.
        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
 during autosuspend.  For example, there's not much point
 autosuspending a keyboard if the user can't cause the keyboard to do a
index 2b39583..057eeab 100644 (file)
@@ -1415,6 +1415,48 @@ static int usb_resume_both(struct usb_device *udev, pm_message_t msg)
 
 #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
  * its autosuspend state.
  */
index bfa6123..746f26f 100644 (file)
@@ -1224,6 +1224,9 @@ static int hub_probe(struct usb_interface *intf, const struct usb_device_id *id)
        desc = intf->cur_altsetting;
        hdev = interface_to_usbdev(intf);
 
+       /* Hubs have proper suspend/resume support */
+       usb_enable_autosuspend(hdev);
+
        if (hdev->level == MAX_TOPO_LEVEL) {
                dev_err(&intf->dev,
                        "Unsupported bus topology: hub nested too deep\n");
index 0b68922..4314f25 100644 (file)
@@ -103,11 +103,10 @@ void usb_detect_quirks(struct usb_device *udev)
                dev_dbg(&udev->dev, "USB quirks for this device: %x\n",
                                udev->quirks);
 
-       /* By default, disable autosuspend for all non-hubs */
-#ifdef CONFIG_USB_SUSPEND
-       if (udev->descriptor.bDeviceClass != USB_CLASS_HUB)
-               udev->autosuspend_disabled = 1;
-#endif
+       /* By default, disable autosuspend for all devices.  The hub driver
+        * will enable it for hubs.
+        */
+       usb_disable_autosuspend(udev);
 
        /* For the present, all devices default to USB-PERSIST enabled */
 #if 0          /* was: #ifdef CONFIG_PM */
index 5a1a0e2..313e241 100644 (file)
@@ -389,34 +389,25 @@ set_level(struct device *dev, struct device_attribute *attr,
        struct usb_device *udev = to_usb_device(dev);
        int len = count;
        char *cp;
-       int rc = 0;
-       int old_autosuspend_disabled;
+       int rc;
 
        cp = memchr(buf, '\n', count);
        if (cp)
                len = cp - buf;
 
        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 &&
-                       strncmp(buf, on_string, len) == 0) {
-               udev->autosuspend_disabled = 1;
-               rc = usb_external_resume_device(udev, PMSG_USER_RESUME);
+                       strncmp(buf, on_string, len) == 0)
+               rc = usb_disable_autosuspend(udev);
 
-       } else if (len == sizeof auto_string - 1 &&
-                       strncmp(buf, auto_string, len) == 0) {
-               udev->autosuspend_disabled = 0;
-               rc = usb_external_resume_device(udev, PMSG_USER_RESUME);
+       else if (len == sizeof auto_string - 1 &&
+                       strncmp(buf, auto_string, len) == 0)
+               rc = usb_enable_autosuspend(udev);
 
-       } else
+       else
                rc = -EINVAL;
 
-       if (rc)
-               udev->autosuspend_disabled = old_autosuspend_disabled;
        usb_unlock_device(udev);
        return (rc < 0 ? rc : count);
 }
index 332eaea..e6419ac 100644 (file)
@@ -542,6 +542,9 @@ extern struct usb_device *usb_find_device(u16 vendor_id, u16 product_id);
 
 /* USB autosuspend and autoresume */
 #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 void usb_autopm_put_interface(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)
 
 #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)
 { return 0; }
 static inline int usb_autopm_get_interface_async(struct usb_interface *intf)