From: Lan Tianyu <tianyu.lan@intel.com>
To: gregkh@linuxfoundation.org, lenb@kernel.org
Cc: linux-usb@vger.kernel.org, linux-acpi@vger.kernel.org,
stern@rowland.harvard.edu, sarah.a.sharp@linux.intel.com,
Lan Tianyu <tianyu.lan@intel.com>
Subject: [RFC PATCH 3/3] usb : Add sysfs files to control usb port's power
Date: Fri, 25 May 2012 13:48:02 +0800 [thread overview]
Message-ID: <1337924882-17332-4-git-send-email-tianyu.lan@intel.com> (raw)
In-Reply-To: <1337924882-17332-1-git-send-email-tianyu.lan@intel.com>
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@intel.com>
---
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
next prev parent reply other threads:[~2012-05-25 5:54 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
2012-05-25 5:47 [RFC PATCH 0/3] USB/ACPI: Add usb port power off mechanism Lan Tianyu
[not found] ` <1337924882-17332-1-git-send-email-tianyu.lan-ral2JQCrhuEAvxtiuMwx3w@public.gmane.org>
2012-05-25 5:48 ` [RFC PATCH 1/3] xhci: Add clear PORT_POWER feature process in the xhci_hub_control() Lan Tianyu
2012-05-28 10:28 ` Andiry Xu
2012-05-28 5:54 ` Lan Tianyu
2012-05-25 5:48 ` [RFC PATCH 2/3] USB/ACPI: Add usb port's acpi power control in the xhci PORT_POWER feature request process Lan Tianyu
2012-05-25 5:48 ` Lan Tianyu [this message]
2012-05-25 15:41 ` [RFC PATCH 3/3] usb : Add sysfs files to control usb port's power Alan Stern
2012-05-28 5:35 ` Lan Tianyu
2012-05-28 15:14 ` Alan Stern
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=1337924882-17332-4-git-send-email-tianyu.lan@intel.com \
--to=tianyu.lan@intel.com \
--cc=gregkh@linuxfoundation.org \
--cc=lenb@kernel.org \
--cc=linux-acpi@vger.kernel.org \
--cc=linux-usb@vger.kernel.org \
--cc=sarah.a.sharp@linux.intel.com \
--cc=stern@rowland.harvard.edu \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).