Change since v7: Update date of adding new attributes' description

Change since v5: Add usb_autopm_get/put_interface() around the port
power operations in the control attribute's callback to deal with
situation that control attribute is changed when the usb hub is suspended.

Change since v4: Add clear PORT_POWER if power policy is "off" in the
hub_power_on(). Return -EIO if set/clear PORT_POWER fails in the 
store_port_power_control()

This patch is to add two attributes for each usb hub ports to control port's 
power.
Control port's power through setting or clearing PORT_POWER feature.

control has two options. "auto", "on" and "off"
"on" - port power must be on.
"off" - port power must be off.

state reports usb port's power state
"on" - power on
"off" - power off
"error" - can't get power state

Acked-by: Alan Stern <[email protected]>
Signed-off-by: Lan Tianyu <[email protected]>
---
 Documentation/ABI/testing/sysfs-bus-usb |   24 ++++++
 drivers/usb/core/hub.c                  |  133 ++++++++++++++++++++++++++++++-
 2 files changed, 156 insertions(+), 1 deletions(-)

diff --git a/Documentation/ABI/testing/sysfs-bus-usb 
b/Documentation/ABI/testing/sysfs-bus-usb
index d5bb5be..479f94a 100644
--- a/Documentation/ABI/testing/sysfs-bus-usb
+++ b/Documentation/ABI/testing/sysfs-bus-usb
@@ -215,3 +215,27 @@ Contact:   Lan Tianyu <[email protected]>
 Description:
                The /sys/bus/usb/devices/.../(hub interface)/portX
                is usb port device's sysfs directory.
+
+What:          /sys/bus/usb/devices/.../(hub interface)/portX/control
+Date:          July 2012
+Contact:       Lan Tianyu <[email protected]>
+Description:
+               The /sys/bus/usb/devices/.../(hub interface)/portX/control
+               attribute allows user space to control the power policy on
+               the usb port.
+
+               All ports have one of the following two values for control
+               "on" - port power must be on.
+               "off" - port power must be off.
+
+What:          /sys/bus/usb/devices/.../(hub interface)/portX/state
+Date:          July 2012
+Contact:       Lan Tianyu <[email protected]>
+Description:
+               The /sys/bus/usb/devices/.../(hub interface)/portX/state
+               attribute allows user space to check hub port's power state.
+
+               All ports have three following states
+               "on"      -    port power on
+               "off"     -    port power off
+               "error"   -    can't get the hub port's power state
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index 375217f..12c7f14 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -37,11 +37,17 @@
 #endif
 #endif
 
+enum port_power_policy {
+       USB_PORT_POWER_ON = 0,
+       USB_PORT_POWER_OFF,
+};
+
 struct usb_port {
        struct usb_device *child;
        struct device dev;
        struct dev_state *port_owner;
        enum usb_port_connect_type connect_type;
+       enum port_power_policy port_power_policy;
 };
 
 struct usb_hub {
@@ -95,6 +101,10 @@ struct device_type usb_port_device_type = {
        .name =         "usb_port",
 };
 
+static const char on_string[] = "on";
+static const char off_string[] = "off";
+static const struct attribute_group *port_dev_group[];
+
 static inline int hub_is_superspeed(struct usb_device *hdev)
 {
        return (hdev->descriptor.bDeviceProtocol == USB_HUB_PR_SS);
@@ -847,7 +857,12 @@ static unsigned hub_power_on(struct usb_hub *hub, bool 
do_delay)
                dev_dbg(hub->intfdev, "trying to enable port power on "
                                "non-switchable hub\n");
        for (port1 = 1; port1 <= hub->descriptor->bNbrPorts; port1++)
-               set_port_feature(hub->hdev, port1, USB_PORT_FEAT_POWER);
+               if (hub->ports[port1 - 1]->port_power_policy
+                               == USB_PORT_POWER_ON)
+                       set_port_feature(hub->hdev, port1, USB_PORT_FEAT_POWER);
+               else
+                       clear_port_feature(hub->hdev, port1,
+                                       USB_PORT_FEAT_POWER);
 
        /* Wait at least 100 msec for power to become stable */
        delay = max(pgood_delay, (unsigned) 100);
@@ -1260,6 +1275,7 @@ static int usb_hub_create_port_device(struct usb_hub *hub,
 
        hub->ports[port1 - 1] = port_dev;
        port_dev->dev.parent = hub->intfdev;
+       port_dev->dev.groups = port_dev_group;
        port_dev->dev.type = &usb_port_device_type;
        port_dev->dev.release = usb_port_device_release;
        dev_set_name(&port_dev->dev, "port%d", port1);
@@ -2609,6 +2625,25 @@ static int port_is_power_on(struct usb_hub *hub, 
unsigned portstatus)
        return ret;
 }
 
+static int usb_get_hub_port_power_state(struct usb_device *hdev, int port1)
+{
+       struct usb_hub *hub = hdev_to_hub(hdev);
+       struct usb_port_status data;
+       u16 portstatus;
+       int ret;
+
+       ret = get_port_status(hub->hdev, port1, &data);
+       if (ret < 4) {
+               dev_err(hub->intfdev,
+                       "%s failed (err = %d)\n", __func__, ret);
+               if (ret >= 0)
+                       ret = -EIO;
+               return ret;
+       } else
+               portstatus = le16_to_cpu(data.wPortStatus);
+       return port_is_power_on(hub, portstatus);
+}
+
 #ifdef CONFIG_PM
 
 /* Check if a port is suspended(USB2.0 port) or in U3 state(USB3.0 port) */
@@ -4550,6 +4585,102 @@ static int hub_thread(void *__unused)
        return 0;
 }
 
+static ssize_t show_port_power_state(struct device *dev,
+       struct device_attribute *attr, char *buf)
+{
+       struct usb_device *udev = to_usb_device(dev->parent->parent);
+       struct usb_interface *intf = to_usb_interface(dev->parent);
+       int port1, power_state;
+       const char *result;
+
+       sscanf(dev_name(dev), "port%d", &port1);
+       usb_autopm_get_interface(intf);
+       power_state = usb_get_hub_port_power_state(udev, port1);
+       usb_autopm_put_interface(intf);
+       if (power_state == 1)
+               result = on_string;
+       else if (!power_state)
+               result = off_string;
+       else
+               result = "error";
+       return sprintf(buf, "%s\n", result);
+}
+static DEVICE_ATTR(state, S_IRUGO, show_port_power_state, NULL);
+
+static ssize_t show_port_power_control(struct device *dev,
+       struct device_attribute *attr, char *buf)
+{
+       struct usb_port *hub_port = to_usb_port(dev);
+       const char *result;
+
+       switch (hub_port->port_power_policy) {
+       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 *hdev = to_usb_device(dev->parent->parent);
+       struct usb_interface *intf = to_usb_interface(dev->parent);
+       struct usb_port *hub_port = to_usb_port(dev);
+       int port1, ret, len = count;
+       char *cp;
+
+       sscanf(dev_name(dev), "port%d", &port1);
+       cp = memchr(buf, '\n', count);
+       if (cp)
+               len = cp - buf;
+       if (len == sizeof on_string - 1
+                       && strncmp(buf, on_string, len) == 0) {
+               hub_port->port_power_policy = USB_PORT_POWER_ON;
+               usb_autopm_get_interface(intf);
+               ret = set_port_feature(hdev, port1, USB_PORT_FEAT_POWER);
+               usb_autopm_put_interface(intf);
+               if (ret < 0)
+                       return -EIO;
+       } else if (len == sizeof off_string - 1
+                       && strncmp(buf, off_string, len) == 0) {
+               struct usb_hub *hub = hdev_to_hub(hdev);
+
+               hub_port->port_power_policy = USB_PORT_POWER_OFF;
+               usb_autopm_get_interface(intf);
+               hub_port_logical_disconnect(hub, port1);
+               ret = clear_port_feature(hdev, port1, USB_PORT_FEAT_POWER);
+               usb_autopm_put_interface(intf);
+               if (ret < 0)
+                       return -EIO;
+       } else
+               return -EINVAL;
+
+       return count;
+}
+static DEVICE_ATTR(control, S_IWUSR | S_IRUGO, show_port_power_control,
+               store_port_power_control);
+
+static struct attribute *port_dev_attrs[] = {
+       &dev_attr_control.attr,
+       &dev_attr_state.attr,
+       NULL,
+};
+
+static struct attribute_group port_dev_attr_grp = {
+       .attrs = port_dev_attrs,
+};
+
+static const struct attribute_group *port_dev_group[] = {
+       &port_dev_attr_grp,
+       NULL,
+};
+
 static const struct usb_device_id hub_id_table[] = {
     { .match_flags = USB_DEVICE_ID_MATCH_DEV_CLASS,
       .bDeviceClass = USB_CLASS_HUB},
-- 
1.7.6.rc2.8.g28eb

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

Reply via email to