Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2add5229 authored by Alan Stern's avatar Alan Stern Committed by Greg Kroah-Hartman
Browse files

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: default avatarAlan Stern <stern@rowland.harvard.edu>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@suse.de>
parent 13f6be01
Loading
Loading
Loading
Loading
+26 −0
Original line number Diff line number Diff line
@@ -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.
+12 −3
Original line number Diff line number Diff line
@@ -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
+1 −1
Original line number Diff line number Diff line
@@ -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
}

+77 −4
Original line number Diff line number Diff line
@@ -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);
+2 −0
Original line number Diff line number Diff line
@@ -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)