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>
This commit is contained in:
Alan Stern
2007-03-20 14:59:39 -04:00
committed by Greg Kroah-Hartman
parent 13f6be01db
commit 2add5229d7
5 changed files with 118 additions and 8 deletions

View 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

View 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
}

View 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);