Skip to content

Commit

Permalink
usb : Add sysfs files to control port power.
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Lan Tianyu authored and Greg Kroah-Hartman committed Sep 10, 2012
1 parent 3a22b87 commit ca9c9d0
Show file tree
Hide file tree
Showing 2 changed files with 156 additions and 1 deletion.
24 changes: 24 additions & 0 deletions Documentation/ABI/testing/sysfs-bus-usb
Original file line number Diff line number Diff line change
Expand Up @@ -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
133 changes: 132 additions & 1 deletion drivers/usb/core/hub.c
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,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 {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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) */
Expand Down Expand Up @@ -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},
Expand Down

0 comments on commit ca9c9d0

Please sign in to comment.