[PATCH 36/78] USB: add power/level sysfs attribute

Greg Kroah-Hartman <[email protected]>
Newsgroups gmane.linux.usb.devel
Message-ID <[email protected]>
From: Alan Stern <[email protected]>

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 <[email protected]>
Signed-off-by: Greg Kroah-Hartman <[email protected]>
---
 Documentation/ABI/testing/sysfs-bus-usb |   26 ++++++++++
 drivers/usb/core/driver.c               |   15 +++++-
 drivers/usb/core/quirks.c               |    2 +-
 drivers/usb/core/sysfs.c                |   81 +++++++++++++++++++++++++++++--
 include/linux/usb.h                     |    2 +
 5 files changed, 118 insertions(+), 8 deletions(-)

diff --git a/Documentation/ABI/testing/sysfs-bus-usb b/Documentation/ABI/testing/sysfs-bus-usb
index 00a8432..f9937ad 100644
--- a/Documentation/ABI/testing/sysfs-bus-usb
+++ b/Documentation/ABI/testing/sysfs-bus-usb
@@ -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 <[email protected]>
+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.
diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c
index 1d13eae..d2a0e4a 100644
--- a/drivers/usb/core/driver.c
+++ b/drivers/usb/core/driver.c
@@ -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
diff --git a/drivers/usb/core/quirks.c b/drivers/usb/core/quirks.c
index f08ec85..739f520 100644
--- a/drivers/usb/core/quirks.c
+++ b/drivers/usb/core/quirks.c
@@ -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
 }
 
diff --git a/drivers/usb/core/sysfs.c b/drivers/usb/core/sysfs.c
index 731001f..2ea47a3 100644
--- a/drivers/usb/core/sysfs.c
+++ b/drivers/usb/core/sysfs.c
@@ -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,22 +194,95 @@ 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);
 }
diff --git a/include/linux/usb.h b/include/linux/usb.h
index cc24d08..5e8e144 100644
--- a/include/linux/usb.h
+++ b/include/linux/usb.h
@@ -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)
-- 
1.5.1.2


-------------------------------------------------------------------------
This SF.net email is sponsored by DB2 Express
Download DB2 Express C - the FREE version of DB2 express and take
control of your XML. No limits. Just data. Click to get it now.
http://sourceforge.net/powerbar/db2/
_______________________________________________
[email protected]
To unsubscribe, use the last form field at:
https://lists.sourceforge.net/lists/listinfo/linux-usb-devel
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.