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
5 changed files with 118 additions and 8 deletions
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue