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

@ -13,3 +13,29 @@ Description:
The autosuspend delay for newly-created devices is set to The autosuspend delay for newly-created devices is set to
the value of the usbcore.autosuspend module parameter. 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.

View File

@ -872,8 +872,10 @@ static int usb_resume_device(struct usb_device *udev)
done: done:
// dev_dbg(&udev->dev, "%s: status %d\n", __FUNCTION__, status); // 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; udev->dev.power.power_state.event = PM_EVENT_ON;
}
return status; return status;
} }
@ -970,7 +972,7 @@ static int autosuspend_check(struct usb_device *udev)
udev->do_remote_wakeup = device_may_wakeup(&udev->dev); udev->do_remote_wakeup = device_may_wakeup(&udev->dev);
if (udev->pm_usage_cnt > 0) if (udev->pm_usage_cnt > 0)
return -EBUSY; return -EBUSY;
if (udev->autosuspend_delay < 0) if (udev->autosuspend_delay < 0 || udev->autosuspend_disabled)
return -EPERM; return -EPERM;
if (udev->actconfig) { if (udev->actconfig) {
@ -1116,6 +1118,8 @@ static int usb_resume_both(struct usb_device *udev)
struct usb_interface *intf; struct usb_interface *intf;
struct usb_device *parent = udev->parent; struct usb_device *parent = udev->parent;
if (udev->auto_pm && udev->autoresume_disabled)
return -EPERM;
cancel_delayed_work(&udev->autosuspend); cancel_delayed_work(&udev->autosuspend);
if (udev->state == USB_STATE_NOTATTACHED) if (udev->state == USB_STATE_NOTATTACHED)
return -ENODEV; return -ENODEV;
@ -1486,9 +1490,14 @@ static int usb_suspend(struct device *dev, pm_message_t message)
static int usb_resume(struct device *dev) static int usb_resume(struct device *dev)
{ {
struct usb_device *udev;
if (!is_usb_device(dev)) /* Ignore PM for interfaces */ if (!is_usb_device(dev)) /* Ignore PM for interfaces */
return 0; 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 #else

View File

@ -42,7 +42,7 @@ static void usb_autosuspend_quirk(struct usb_device *udev)
{ {
#ifdef CONFIG_USB_SUSPEND #ifdef CONFIG_USB_SUSPEND
/* disable autosuspend, but allow the user to re-enable it via sysfs */ /* disable autosuspend, but allow the user to re-enable it via sysfs */
udev->autosuspend_delay = 0; udev->autosuspend_disabled = 1;
#endif #endif
} }

View File

@ -11,6 +11,7 @@
#include <linux/kernel.h> #include <linux/kernel.h>
#include <linux/string.h>
#include <linux/usb.h> #include <linux/usb.h>
#include "usb.h" #include "usb.h"
@ -184,9 +185,8 @@ set_autosuspend(struct device *dev, struct device_attribute *attr,
if (value >= 0) if (value >= 0)
usb_try_autosuspend_device(udev); usb_try_autosuspend_device(udev);
else { else {
usb_lock_device(udev); if (usb_autoresume_device(udev) == 0)
usb_external_resume_device(udev); usb_autosuspend_device(udev);
usb_unlock_device(udev);
} }
return count; return count;
} }
@ -194,21 +194,94 @@ set_autosuspend(struct device *dev, struct device_attribute *attr,
static DEVICE_ATTR(autosuspend, S_IRUGO | S_IWUSR, static DEVICE_ATTR(autosuspend, S_IRUGO | S_IWUSR,
show_autosuspend, set_autosuspend); 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 char power_group[] = "power";
static int add_power_attributes(struct device *dev) static int add_power_attributes(struct device *dev)
{ {
int rc = 0; int rc = 0;
if (is_usb_device(dev)) if (is_usb_device(dev)) {
rc = sysfs_add_file_to_group(&dev->kobj, rc = sysfs_add_file_to_group(&dev->kobj,
&dev_attr_autosuspend.attr, &dev_attr_autosuspend.attr,
power_group); power_group);
if (rc == 0)
rc = sysfs_add_file_to_group(&dev->kobj,
&dev_attr_level.attr,
power_group);
}
return rc; return rc;
} }
static void remove_power_attributes(struct device *dev) 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, sysfs_remove_file_from_group(&dev->kobj,
&dev_attr_autosuspend.attr, &dev_attr_autosuspend.attr,
power_group); power_group);

View File

@ -398,6 +398,8 @@ struct usb_device {
unsigned auto_pm:1; /* autosuspend/resume in progress */ unsigned auto_pm:1; /* autosuspend/resume in progress */
unsigned do_remote_wakeup:1; /* remote wakeup should be enabled */ 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 #endif
}; };
#define to_usb_device(d) container_of(d, struct usb_device, dev) #define to_usb_device(d) container_of(d, struct usb_device, dev)