usb : Add sysfs files to control port power.
authorLan Tianyu <tianyu.lan@intel.com>
Wed, 5 Sep 2012 05:44:38 +0000 (13:44 +0800)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Mon, 10 Sep 2012 20:04:01 +0000 (13:04 -0700)
This patch adds two sysfs files for each usb hub port to allow userspace
to control the port power policy.

For an upcoming Intel xHCI roothub, this will translate into ACPI calls
to completely power off or power on the port.  As a reminder, when these
ports are completely powered off, the USB host and device will see a
physical disconnect.  All future USB device connections will be lost,
and the device will not be able to signal a remote wakeup.

The control sysfs file can be written to with two options:
"on" - port power must be on.
"off" - port must be off.

The state sysfs file reports usb port's power state:
"on" - powered on
"off" - powered off
"error" - can't get power state

For now, let userspace dictate the port power off policy.  Future
patches may add an in-kernel policy.

Acked-by: Alan Stern <stern@rowland.harvard.edu>
Signed-off-by: Lan Tianyu <tianyu.lan@intel.com>
Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Documentation/ABI/testing/sysfs-bus-usb
drivers/usb/core/hub.c

index b6fbe514a869a311bea16c1918061128c8f9d6dc..13f3eeee1599a68f4400cdb7f4f81c59924f2782 100644 (file)
@@ -227,3 +227,27 @@ Contact:   Lan Tianyu <tianyu.lan@intel.com>
 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:          August 2012
+Contact:       Lan Tianyu <tianyu.lan@intel.com>
+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:          August 2012
+Contact:       Lan Tianyu <tianyu.lan@intel.com>
+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
index 4ffe8371adac0099c87e2e1a0c5400023bba34c9..3def91eecaad9efe85a67497d53edb3b43b85972 100644 (file)
 #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 {
@@ -93,6 +99,10 @@ struct usb_hub {
        struct usb_port         **ports;
 };
 
+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);
@@ -845,7 +855,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);
@@ -1263,6 +1278,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;
        dev_set_name(&port_dev->dev, "port%d", port1);
 
@@ -2627,6 +2643,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) */
@@ -4633,6 +4668,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},