[PATCH] USB: add power/level sysfs attribute

Alan Stern <[email protected]>
Newsgroups gmane.linux.usb.devel
Message-ID <[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]>

---

Index: usb-2.6/Documentation/ABI/testing/sysfs-bus-usb
===================================================================
--- usb-2.6.orig/Documentation/ABI/testing/sysfs-bus-usb
+++ usb-2.6/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.
Index: usb-2.6/drivers/usb/core/driver.c
===================================================================
--- usb-2.6.orig/drivers/usb/core/driver.c
+++ usb-2.6/drivers/usb/core/driver.c
@@ -872,8 +872,10 @@ static int usb_resume_device(struct usb_
 
 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_
 	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_de
 	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 *de
 
 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
Index: usb-2.6/drivers/usb/core/sysfs.c
===================================================================
--- usb-2.6.orig/drivers/usb/core/sysfs.c
+++ usb-2.6/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, stru
 	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, stru
 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);
 }
Index: usb-2.6/include/linux/usb.h
===================================================================
--- usb-2.6.orig/include/linux/usb.h
+++ usb-2.6/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)
Index: usb-2.6/drivers/usb/core/quirks.c
===================================================================
--- usb-2.6.orig/drivers/usb/core/quirks.c
+++ usb-2.6/drivers/usb/core/quirks.c
@@ -41,7 +41,7 @@ static void usb_autosuspend_quirk(struct
 {
 #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
 }
 


-------------------------------------------------------------------------
Take Surveys. Earn Cash. Influence the Future of IT
Join SourceForge.net's Techsay panel and you'll get the chance to share your
opinions on IT & business topics through brief surveys-and earn cash
http://www.techsay.com/default.php?page=join.php&p=sourceforge&CID=DEVDEV
_______________________________________________
[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.