[RFC PATCH 3/3] usb : Add sysfs files to control usb port's power

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



This patch is to add two sys files for each usb hub ports to control port's power,
e.g port1_power_control and port1_power_state both for port one. Control port's
power through setting or clearing PORT_POWER feature.

port1_power_control has three options. "auto", "on" and "off"
"auto" - if port without device, power off the port.
"on" - port must be power on.
"off" - port must be power off.

Signed-off-by: Lan Tianyu <tianyu.lan@xxxxxxxxx>
---
 drivers/usb/core/hub.c   |   38 +++++++++++
 drivers/usb/core/sysfs.c |  167 +++++++++++++++++++++++++++++++++++++++++++++-
 drivers/usb/core/usb.c   |    2 +
 drivers/usb/core/usb.h   |    6 ++
 include/linux/usb.h      |   12 +++
 5 files changed, 222 insertions(+), 3 deletions(-)

diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index cec5bd1..7f0adf6 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -48,6 +48,7 @@ struct usb_hub_port {
 	acpi_handle		port_acpi_handle;
 #endif
 	enum usb_port_connect_type connect_type;
+	enum port_power_policy port_power_policy;
 };
 
 struct usb_hub {
@@ -1493,6 +1494,12 @@ static int hub_configure(struct usb_hub *hub,
 	if (hub->has_indicators && blinkenlights)
 		hub->indicator [0] = INDICATOR_CYCLE;
 
+	/*
+	 * Create the ports' power related sys files.
+	 * The function requires the hub's port number.
+	 */
+	usb_create_sysfs_dev_files(hdev);
+
 	usb_acpi_bind_hub_ports(hdev);
 	hub_activate(hub, HUB_INIT);
 	return 0;
@@ -4985,6 +4992,37 @@ usb_get_hub_port_connect_type(struct usb_device *hdev,	int port1)
 	return hub->port_data[port1 - 1].connect_type;
 }
 
+enum port_power_policy
+usb_get_hub_port_power_policy(struct usb_device *hdev, int port1)
+{
+	struct usb_hub *hub = hdev_to_hub(hdev);
+	return hub->port_data[port1 - 1].port_power_policy;
+}
+
+void usb_set_hub_port_power_policy(struct usb_device *hdev, int port1,
+	enum port_power_policy value)
+{
+	struct usb_hub *hub = hdev_to_hub(hdev);
+	hub->port_data[port1 - 1].port_power_policy = value;
+}
+
+void usb_set_hub_port_power(struct usb_device *hdev, int port1, bool enabled)
+{
+	if (enabled)
+		set_port_feature(hdev, port1, USB_PORT_FEAT_POWER);
+	else
+		clear_port_feature(hdev, port1, USB_PORT_FEAT_POWER);
+}
+
+int usb_get_hub_port_power_state(struct usb_device *hdev, int port1)
+{
+	struct usb_hub *hub = hdev_to_hub(hdev);
+	u16 portstatus, portchange;
+
+	hub_port_status(hub, port1, &portstatus, &portchange);
+	return port_is_power_on(hub, portstatus);
+}
+
 #ifdef CONFIG_ACPI
 /**
  * usb_set_hub_port_acpi_handle - Set the usb port's acpi handle
diff --git a/drivers/usb/core/sysfs.c b/drivers/usb/core/sysfs.c
index 566d9f9..8703c7e 100644
--- a/drivers/usb/core/sysfs.c
+++ b/drivers/usb/core/sysfs.c
@@ -16,6 +16,14 @@
 #include <linux/usb/quirks.h>
 #include "usb.h"
 
+static const char on_string[] = "on";
+static const char off_string[] = "off";
+static const char auto_string[] = "auto";
+static char power_control_name[USB_MAXCHILDREN][25];
+static char power_state_name[USB_MAXCHILDREN][25];
+static struct device_attribute power_control_attr[USB_MAXCHILDREN];
+static struct device_attribute power_state_attr[USB_MAXCHILDREN];
+
 /* Active configuration fields */
 #define usb_actconfig_show(field, multiplier, format_string)		\
 static ssize_t  show_##field(struct device *dev,			\
@@ -376,9 +384,6 @@ 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 void warn_level(void) {
 	static int level_warned;
 
@@ -524,6 +529,85 @@ static void remove_power_attributes(struct device *dev)
 
 #endif	/* CONFIG_USB_SUSPEND */
 
+static ssize_t show_port_power_state(struct device *dev,
+	struct device_attribute *attr, char *buf)
+{
+	struct usb_device *udev = to_usb_device(dev);
+	const char *result;
+	int port1;
+
+	if (sscanf(attr->attr.name, "port%d_power_control", &port1) != 1)
+		return -EINVAL;
+
+	if (usb_get_hub_port_power_state(udev, port1))
+		result = on_string;
+	else
+		result = off_string;
+	return sprintf(buf, "%s\n", result);
+}
+
+static ssize_t show_port_power_control(struct device *dev,
+	struct device_attribute *attr, char *buf)
+{
+	struct usb_device *udev = to_usb_device(dev);
+	enum port_power_policy policy;
+	int port1;
+	const char *result;
+
+	if (sscanf(attr->attr.name, "port%d_power_control", &port1) != 1)
+		return -EINVAL;
+
+	policy = usb_get_hub_port_power_policy(udev, port1);
+	switch (policy) {
+	case USB_PORT_POWER_AUTO:
+		result = auto_string;
+		break;
+	case USB_PORT_POWER_ON:
+		result = on_string;
+		break;
+	case USB_PORT_POWER_OFF:
+		result = off_string;
+		break;
+	default:
+		return -EINVAL;
+	}
+	return sprintf(buf, "%s\n", result);
+}
+
+static ssize_t
+store_port_power_control(struct device *dev, struct device_attribute *attr,
+		const char *buf, size_t count)
+{
+	struct usb_device *udev = to_usb_device(dev);
+	char *cp;
+	int port1;
+	int len = count;
+
+	if (sscanf(attr->attr.name, "port%d_power_control", &port1) != 1)
+		return -EINVAL;
+
+	cp = memchr(buf, '\n', count);
+	if (cp)
+		len = cp - buf;
+
+	if (len == sizeof auto_string - 1 &&
+		strncmp(buf, auto_string, len) == 0) {
+		if (!usb_get_hub_child(udev, port1))
+			usb_set_hub_port_power(udev, port1, false);
+		usb_set_hub_port_power_policy(udev, port1, USB_PORT_POWER_AUTO);
+	} else if (len == sizeof on_string - 1
+		&& strncmp(buf, on_string, len) == 0) {
+		usb_set_hub_port_power_policy(udev, port1, USB_PORT_POWER_ON);
+		usb_set_hub_port_power(udev, port1, true);
+	} else if (len == sizeof off_string - 1
+		&& strncmp(buf, off_string, len) == 0) {
+		usb_set_hub_port_power_policy(udev, port1, USB_PORT_POWER_OFF);
+		usb_set_hub_port_power(udev, port1, false);
+	} else
+		return -EINVAL;
+
+	return count;
+}
 
 /* Descriptor fields */
 #define usb_descriptor_attr_le16(field, format_string)			\
@@ -771,6 +855,9 @@ void usb_remove_sysfs_dev_files(struct usb_device *udev)
 	remove_power_attributes(dev);
 	remove_persist_attributes(dev);
 	device_remove_bin_file(dev, &dev_bin_attr_descriptors);
+
+	if (udev->descriptor.bDeviceClass == USB_CLASS_HUB)
+		usb_remove_sysfs_hub_port_power(udev);
 }
 
 /* Interface Accociation Descriptor fields */
@@ -945,3 +1032,77 @@ void usb_remove_sysfs_intf_files(struct usb_interface *intf)
 	device_remove_file(&intf->dev, &dev_attr_interface);
 	intf->sysfs_files_created = 0;
 }
+
+void usb_create_sysfs_hub_port_power(struct usb_device *udev)
+{
+	int i, rc;
+
+	if (udev->descriptor.bDeviceClass == USB_CLASS_HUB) {
+		for (i = 0; i < udev->maxchild && !rc; i++) {
+			rc = sysfs_add_file_to_group(&udev->dev.kobj,
+				&power_control_attr[i].attr,
+				power_group_name);
+			if (rc) {
+				while (--i > 0)
+					sysfs_remove_file_from_group(&udev->dev.kobj,
+						&power_control_attr[i].attr,
+						power_group_name);
+				break;
+			}
+		}
+
+		for (i = 0; i < udev->maxchild && !rc; i++) {
+			rc = sysfs_add_file_to_group(&udev->dev.kobj,
+				&power_state_attr[i].attr,
+				power_group_name);
+			if (rc) {
+				while (--i > 0)
+					sysfs_remove_file_from_group(&udev->dev.kobj,
+						&power_state_attr[i].attr,
+						power_group_name);
+				break;
+			}
+		}
+
+	}
+}
+
+void usb_remove_sysfs_hub_port_power(struct usb_device *udev)
+{
+	int i;
+
+	for (i = 0; i < udev->maxchild; i++) {
+		sysfs_remove_file_from_group(&udev->dev.kobj,
+			&power_control_attr[i].attr,
+			power_group_name);
+		sysfs_remove_file_from_group(&udev->dev.kobj,
+			&power_state_attr[i].attr,
+			power_group_name);
+	}
+}
+
+int __init usb_sysfs_init(void)
+{
+	int i;
+	char name[25];
+
+	for (i = 0; i < USB_MAXCHILDREN; i++) {
+		sprintf(name, "port%d_power_control", i + 1);
+		strcpy(power_control_name[i], name);
+		power_control_attr[i] = (struct device_attribute) {
+			.attr = { .name = power_control_name[i], .mode = 0644 }, \
+			.show	= show_port_power_control, \
+			.store	= store_port_power_control \
+		};
+
+		sprintf(name, "port%d_power_state", i + 1);
+		strcpy(power_state_name[i], name);
+		power_state_attr[i] = (struct device_attribute) {
+			.attr = { .name = power_state_name[i], .mode = 0644 }, \
+			.show	= show_port_power_state, \
+		};
+
+	}
+
+	return 0;
+}
diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c
index 25d0c61..b8908d5 100644
--- a/drivers/usb/core/usb.c
+++ b/drivers/usb/core/usb.c
@@ -1011,6 +1011,8 @@ static int __init usb_init(void)
 		return 0;
 	}
 
+	usb_sysfs_init();
+
 	retval = usb_debugfs_init();
 	if (retval)
 		goto out;
diff --git a/drivers/usb/core/usb.h b/drivers/usb/core/usb.h
index da78c15..5737f46 100644
--- a/drivers/usb/core/usb.h
+++ b/drivers/usb/core/usb.h
@@ -158,10 +158,16 @@ extern void usb_notify_add_device(struct usb_device *udev);
 extern void usb_notify_remove_device(struct usb_device *udev);
 extern void usb_notify_add_bus(struct usb_bus *ubus);
 extern void usb_notify_remove_bus(struct usb_bus *ubus);
+extern int usb_sysfs_init(void);
 extern enum usb_port_connect_type
 	usb_get_hub_port_connect_type(struct usb_device *hdev, int port1);
 extern void usb_set_hub_port_connect_type(struct usb_device *hdev, int port1,
 	enum usb_port_connect_type type);
+extern void usb_create_sysfs_hub_port_power(struct usb_device *udev);
+extern void usb_remove_sysfs_hub_port_power(struct usb_device *udev);
+extern enum port_power_policy
+	usb_get_hub_port_power_policy(struct usb_device *hdev, int port1);
+extern int usb_get_hub_port_power_state(struct usb_device *hdev, int port1);
 
 #ifdef CONFIG_ACPI
 extern int usb_acpi_register(void);
diff --git a/include/linux/usb.h b/include/linux/usb.h
index 92f8898..58fa7b8 100644
--- a/include/linux/usb.h
+++ b/include/linux/usb.h
@@ -424,6 +424,12 @@ enum usb_port_connect_type {
 	USB_PORT_NOT_USED,
 };
 
+enum port_power_policy {
+	USB_PORT_POWER_AUTO = 0,
+	USB_PORT_POWER_ON,
+	USB_PORT_POWER_OFF,
+};
+
 /**
  * struct usb_device - kernel's representation of a USB device
  * @devnum: device number; address on a USB bus
@@ -570,6 +576,12 @@ static inline struct usb_device *interface_to_usbdev(struct usb_interface *intf)
 	return to_usb_device(intf->dev.parent);
 }
 
+extern enum port_power_policy
+usb_get_hub_port_power_policy(struct usb_device *hdev, int port1);
+extern void usb_set_hub_port_power_policy(struct usb_device *hdev,
+	int port1, enum port_power_policy value);
+extern void usb_set_hub_port_power(struct usb_device *hdev,
+	int port1, bool enabled);
 extern struct usb_device *usb_get_dev(struct usb_device *dev);
 extern void usb_put_dev(struct usb_device *dev);
 extern struct usb_device *usb_get_hub_child(struct usb_device *hdev,
-- 
1.7.6.rc2.8.g28eb

--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux