USB: add power/level sysfs attribute
authorAlan Stern <stern@rowland.harvard.edu>
Tue, 20 Mar 2007 18:59:39 +0000 (14:59 -0400)
committerGreg Kroah-Hartman <gregkh@suse.de>
Fri, 27 Apr 2007 20:28:37 +0000 (13:28 -0700)
This patch (as874) adds another piece to the user-visible part of the
USB autosuspend interface.  The new power/level sysfs attribute allows
users to force the device on (with autosuspend off), force the device
to sleep (with autoresume off), or return to normal automatic operation.

Signed-off-by: Alan Stern <stern@rowland.harvard.edu>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
Documentation/ABI/testing/sysfs-bus-usb
drivers/usb/core/driver.c
drivers/usb/core/quirks.c
drivers/usb/core/sysfs.c
include/linux/usb.h

index 00a84326325f867155a4d4ecf6a22bab93fdc7e4..f9937add033d833e869fb996565bb74ff2f5233d 100644 (file)
@@ -13,3 +13,29 @@ Description:
 
                The autosuspend delay for newly-created devices is set to
                the value of the usbcore.autosuspend module parameter.
+
+What:          /sys/bus/usb/devices/.../power/level
+Date:          March 2007
+KernelVersion: 2.6.21
+Contact:       Alan Stern <stern@rowland.harvard.edu>
+Description:
+               Each USB device directory will contain a file named
+               power/level.  This file holds a power-level setting for
+               the device, one of "on", "auto", or "suspend".
+
+               "on" means that the device is not allowed to autosuspend,
+               although normal suspends for system sleep will still
+               be honored.  "auto" means the device will autosuspend
+               and autoresume in the usual manner, according to the
+               capabilities of its driver.  "suspend" means the device
+               is forced into a suspended state and it will not autoresume
+               in response to I/O requests.  However remote-wakeup requests
+               from the device may still be enabled (the remote-wakeup
+               setting is controlled separately by the power/wakeup
+               attribute).
+
+               During normal use, devices should be left in the "auto"
+               level.  The other levels are meant for administrative uses.
+               If you want to suspend a device immediately but leave it
+               free to wake up in response to I/O requests, you should
+               write "0" to power/autosuspend.
index 884179f1e16380415df5aaa5986dc80b931feaf0..9b6a60fafddb8a457540330d4dea85efe68e460f 100644 (file)
@@ -872,8 +872,10 @@ static int usb_resume_device(struct usb_device *udev)
 
 done:
        // dev_dbg(&udev->dev, "%s: status %d\n", __FUNCTION__, status);
-       if (status == 0)
+       if (status == 0) {
+               udev->autoresume_disabled = 0;
                udev->dev.power.power_state.event = PM_EVENT_ON;
+       }
        return status;
 }
 
@@ -970,7 +972,7 @@ static int autosuspend_check(struct usb_device *udev)
        udev->do_remote_wakeup = device_may_wakeup(&udev->dev);
        if (udev->pm_usage_cnt > 0)
                return -EBUSY;
-       if (udev->autosuspend_delay < 0)
+       if (udev->autosuspend_delay < 0 || udev->autosuspend_disabled)
                return -EPERM;
 
        if (udev->actconfig) {
@@ -1116,6 +1118,8 @@ static int usb_resume_both(struct usb_device *udev)
        struct usb_interface    *intf;
        struct usb_device       *parent = udev->parent;
 
+       if (udev->auto_pm && udev->autoresume_disabled)
+               return -EPERM;
        cancel_delayed_work(&udev->autosuspend);
        if (udev->state == USB_STATE_NOTATTACHED)
                return -ENODEV;
@@ -1486,9 +1490,14 @@ static int usb_suspend(struct device *dev, pm_message_t message)
 
 static int usb_resume(struct device *dev)
 {
+       struct usb_device       *udev;
+
        if (!is_usb_device(dev))        /* Ignore PM for interfaces */
                return 0;
-       return usb_external_resume_device(to_usb_device(dev));
+       udev = to_usb_device(dev);
+       if (udev->autoresume_disabled)
+               return -EPERM;
+       return usb_external_resume_device(udev);
 }
 
 #else
index f08ec85a6d647eafbb6d8961a278783136cf0682..739f520908aa939b30a09e54bd3a9627b6ceb0a5 100644 (file)
@@ -42,7 +42,7 @@ static void usb_autosuspend_quirk(struct usb_device *udev)
 {
 #ifdef CONFIG_USB_SUSPEND
        /* disable autosuspend, but allow the user to re-enable it via sysfs */
-       udev->autosuspend_delay = 0;
+       udev->autosuspend_disabled = 1;
 #endif
 }
 
index 731001f7d2c1077bed64c18a08e5ff7770a9849f..2ea47a38aefa47fa935cebd2d7be24cbef713e32 100644 (file)
@@ -11,6 +11,7 @@
 
 
 #include <linux/kernel.h>
+#include <linux/string.h>
 #include <linux/usb.h>
 #include "usb.h"
 
@@ -184,9 +185,8 @@ set_autosuspend(struct device *dev, struct device_attribute *attr,
        if (value >= 0)
                usb_try_autosuspend_device(udev);
        else {
-               usb_lock_device(udev);
-               usb_external_resume_device(udev);
-               usb_unlock_device(udev);
+               if (usb_autoresume_device(udev) == 0)
+                       usb_autosuspend_device(udev);
        }
        return count;
 }
@@ -194,21 +194,94 @@ set_autosuspend(struct device *dev, struct device_attribute *attr,
 static DEVICE_ATTR(autosuspend, S_IRUGO | S_IWUSR,
                show_autosuspend, set_autosuspend);
 
+static const char on_string[] = "on";
+static const char auto_string[] = "auto";
+static const char suspend_string[] = "suspend";
+
+static ssize_t
+show_level(struct device *dev, struct device_attribute *attr, char *buf)
+{
+       struct usb_device *udev = to_usb_device(dev);
+       const char *p = auto_string;
+
+       if (udev->state == USB_STATE_SUSPENDED) {
+               if (udev->autoresume_disabled)
+                       p = suspend_string;
+       } else {
+               if (udev->autosuspend_disabled)
+                       p = on_string;
+       }
+       return sprintf(buf, "%s\n", p);
+}
+
+static ssize_t
+set_level(struct device *dev, struct device_attribute *attr,
+               const char *buf, size_t count)
+{
+       struct usb_device *udev = to_usb_device(dev);
+       int len = count;
+       char *cp;
+       int rc = 0;
+
+       cp = memchr(buf, '\n', count);
+       if (cp)
+               len = cp - buf;
+
+       usb_lock_device(udev);
+
+       /* 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;
+               udev->autoresume_disabled = 0;
+               rc = usb_external_resume_device(udev);
+
+       } else if (len == sizeof auto_string - 1 &&
+                       strncmp(buf, auto_string, len) == 0) {
+               udev->autosuspend_disabled = 0;
+               udev->autoresume_disabled = 0;
+               rc = usb_external_resume_device(udev);
+
+       } else if (len == sizeof suspend_string - 1 &&
+                       strncmp(buf, suspend_string, len) == 0) {
+               udev->autosuspend_disabled = 0;
+               udev->autoresume_disabled = 1;
+               rc = usb_external_suspend_device(udev, PMSG_SUSPEND);
+
+       } else
+               rc = -EINVAL;
+
+       usb_unlock_device(udev);
+       return (rc < 0 ? rc : count);
+}
+
+static DEVICE_ATTR(level, S_IRUGO | S_IWUSR, show_level, set_level);
+
 static char power_group[] = "power";
 
 static int add_power_attributes(struct device *dev)
 {
        int rc = 0;
 
-       if (is_usb_device(dev))
+       if (is_usb_device(dev)) {
                rc = sysfs_add_file_to_group(&dev->kobj,
                                &dev_attr_autosuspend.attr,
                                power_group);
+               if (rc == 0)
+                       rc = sysfs_add_file_to_group(&dev->kobj,
+                                       &dev_attr_level.attr,
+                                       power_group);
+       }
        return rc;
 }
 
 static void remove_power_attributes(struct device *dev)
 {
+       sysfs_remove_file_from_group(&dev->kobj,
+                       &dev_attr_level.attr,
+                       power_group);
        sysfs_remove_file_from_group(&dev->kobj,
                        &dev_attr_autosuspend.attr,
                        power_group);
index cc24d089faa04a79e121f348bba318d27cb13ef5..5e8e144afbaefa549b62c972ee3e4b407f2a92cd 100644 (file)
@@ -398,6 +398,8 @@ struct usb_device {
 
        unsigned auto_pm:1;             /* autosuspend/resume in progress */
        unsigned do_remote_wakeup:1;    /* remote wakeup should be enabled */
+       unsigned autosuspend_disabled:1; /* autosuspend and autoresume */
+       unsigned autoresume_disabled:1;  /*  disabled by the user */
 #endif
 };
 #define        to_usb_device(d) container_of(d, struct usb_device, dev)