Let userspace discover what is keeping a port powered. Here are the states and the actions userspace can take to enable port power off. PORT=<port sysfs directory> 'active': attached child device is actively using the port echo auto > $PORT/<child>/power/control 'pm_qos_no_power_off': child inactive, pm qos flag keeps power enabled echo 0 > $PORT/power/pm_qos_no_power_off 'wakeup enabled': port kept powered to handle remote wakeup events echo 0 > $PORT/<child>/power/wakeup 'persist disabled': child device may not cleanly recover from suspend/resume cycle echo 1 > $PORT/<child>/power/persist 'off': port power is off Reading this file makes an attempt to settle in-flight pm_runtime transitions. Signed-off-by: Dan Williams <dan.j.williams@xxxxxxxxx> --- Documentation/ABI/testing/sysfs-bus-usb | 19 ++++++++++ drivers/usb/core/port.c | 60 +++++++++++++++++++++++++++++-- 2 files changed, 75 insertions(+), 4 deletions(-) diff --git a/Documentation/ABI/testing/sysfs-bus-usb b/Documentation/ABI/testing/sysfs-bus-usb index 1430f584b266..91bfcfcfbfe3 100644 --- a/Documentation/ABI/testing/sysfs-bus-usb +++ b/Documentation/ABI/testing/sysfs-bus-usb @@ -148,6 +148,25 @@ Description: The file will read "hotplug", "wired" and "not used" if the information is available, and "unknown" otherwise. +What: /sys/bus/usb/devices/.../(hub interface)/portX/power/power_state +Date: November 2013 +Contact: Dan Williams <dan.j.williams@xxxxxxxxx> +Description: + There are several gating factors that prevent a usb_port + from being powered down. Let userspace discover what is + keeping a port powered. + + 'active': attached child device is actively using the + port + 'pm_qos_no_power_off': child inactive, pm qos flag keeps + power enabled + 'wakeup enabled': child inactive, port kept powered to + handle remote wakeup events + 'persist disabled': child inactive, but child device + will not cleanly recover from a + suspend/resume cycle + 'off': port power is off + What: /sys/bus/usb/devices/.../power/usb2_lpm_l1_timeout Date: May 2013 Contact: Mathias Nyman <mathias.nyman@xxxxxxxxxxxxxxx> diff --git a/drivers/usb/core/port.c b/drivers/usb/core/port.c index 6f20c0c8fe34..f7d1cd8a97d3 100644 --- a/drivers/usb/core/port.c +++ b/drivers/usb/core/port.c @@ -152,6 +152,55 @@ struct device_type usb_port_device_type = { .pm = &usb_port_pm_ops, }; +#ifdef CONFIG_PM +static ssize_t power_state_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + const char *state; + struct usb_port *port_dev = to_usb_port(dev); + struct usb_device *child = usb_port_get_child(port_dev); + + if (child) { + pm_runtime_barrier(&child->dev); + put_device(&child->dev); + } + + pm_runtime_barrier(dev); + + if (pm_runtime_active(dev)) + state = "active"; + else + state = power_on_reason(port_dev); + + if (!state) + state = "off"; + + return sprintf(buf, "%s\n", state); +} +static DEVICE_ATTR_RO(power_state); + +static int add_port_power_state(struct usb_port *port_dev) +{ + return sysfs_add_file_to_group(&port_dev->dev.kobj, + &dev_attr_power_state.attr, + power_group_name); +} + +static void remove_port_power_state(struct usb_port *port_dev) +{ + sysfs_remove_file_from_group(&port_dev->dev.kobj, + &dev_attr_power_state.attr, + power_group_name); +} +#else +static int add_port_power_state(struct usb_port *port_dev) +{ + return 0; +} + +static void remove_port_power_state(struct usb_port *port_dev) { } +#endif + int usb_hub_create_port_device(struct usb_hub *hub, int port1) { struct usb_port *port_dev = NULL; @@ -174,6 +223,8 @@ int usb_hub_create_port_device(struct usb_hub *hub, int port1) if (retval) goto error_register; + add_port_power_state(port_dev); + pm_runtime_set_active(&port_dev->dev); /* It would be dangerous if user space couldn't @@ -193,9 +244,10 @@ exit: return retval; } -void usb_hub_remove_port_device(struct usb_hub *hub, - int port1) +void usb_hub_remove_port_device(struct usb_hub *hub, int port1) { - device_unregister(&hub->ports[port1 - 1]->dev); -} + struct usb_port *port_dev = hub->ports[port1 - 1]; + remove_port_power_state(port_dev); + device_unregister(&port_dev->dev); +} -- 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