diff options
author | Alan Stern <stern@rowland.harvard.edu> | 2007-03-20 21:59:39 +0300 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@suse.de> | 2007-04-28 00:28:37 +0400 |
commit | 2add5229d77a3de08015feef437653e02372162f (patch) | |
tree | 436109572453656747d6e5b3aec14939b1202ec3 /drivers/usb/core | |
parent | 13f6be01db9ada144f28241f939f4f3f8ec8e40b (diff) | |
download | linux-2add5229d77a3de08015feef437653e02372162f.tar.xz |
USB: add power/level sysfs attribute
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>
Diffstat (limited to 'drivers/usb/core')
-rw-r--r-- | drivers/usb/core/driver.c | 15 | ||||
-rw-r--r-- | drivers/usb/core/quirks.c | 2 | ||||
-rw-r--r-- | drivers/usb/core/sysfs.c | 81 |
3 files changed, 90 insertions, 8 deletions
diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c index 884179f1e163..9b6a60fafddb 100644 --- a/drivers/usb/core/driver.c +++ b/drivers/usb/core/driver.c @@ -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 diff --git a/drivers/usb/core/quirks.c b/drivers/usb/core/quirks.c index f08ec85a6d64..739f520908aa 100644 --- a/drivers/usb/core/quirks.c +++ b/drivers/usb/core/quirks.c @@ -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 } diff --git a/drivers/usb/core/sysfs.c b/drivers/usb/core/sysfs.c index 731001f7d2c1..2ea47a38aefa 100644 --- a/drivers/usb/core/sysfs.c +++ b/drivers/usb/core/sysfs.c @@ -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,22 +194,95 @@ 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); } |