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:
parent
13f6be01db
commit
2add5229d7
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue