Change since v1: move sysfs files from usb hub device sysfs directory to usb hub interface sysfs directory. This patch is to add two sys files for each usb hub ports to control port's power. Control port's power through setting or clearing PORT_POWER feature. The new sys files is under the usb hub interface sysfs portx directory. ls /sys/bus/usb/devices/1-0:1.0 bAlternateSetting bInterfaceClass bInterfaceNumber bInterfaceProtocol bInterfaceSubClass bNumEndpoints driver ep_81 modalias power subsystem supports_autosuspend uevent port1 port2 port3 port4 ls /sys/bus/usb/devices/1-0:1.0/port1 control state control has three options. "auto", "on" and "off" "auto" - if port without device, power off the port. (This patch only cover ports without device) For ports with device, there is a proposal that if the device was not in the suspend at that pointer, the power would remain on but it would power off when it was suspended. If the the device was in the suspend, "auto" means the device could be put into much lower state so the device need to resume and suspend again. "on" - port must be power on. "off" - port must be power off. state reports usb ports' power state "on" - power on "off" - power off "error" - can't get power state Signed-off-by: Lan Tianyu <tianyu.lan@xxxxxxxxx> --- drivers/usb/core/hub.c | 181 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 181 insertions(+), 0 deletions(-) diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 2ed5244..612eb2d 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -41,12 +41,23 @@ #endif #endif +enum port_power_policy { + USB_PORT_POWER_AUTO = 0, + USB_PORT_POWER_ON, + USB_PORT_POWER_OFF, +}; + struct usb_hub_port { void *port_owner; struct usb_device *child; #ifdef CONFIG_ACPI acpi_handle port_acpi_handle; #endif + struct attribute_group port_attr_group; + struct attribute *port_attrs[3]; + struct device_attribute port_control_attr; + struct device_attribute port_state_attr; + enum port_power_policy port_power_policy; enum usb_port_connect_type connect_type; }; @@ -97,6 +108,13 @@ struct usb_hub { struct usb_hub_port *port_data; }; +static const char on_string[] = "on"; +static const char off_string[] = "off"; +static const char auto_string[] = "auto"; +static char port_name[USB_MAXCHILDREN][10]; +static void usb_hub_port_sysfs_add(struct usb_hub *hub); +static void usb_hub_port_sysfs_remove(struct usb_hub *hub); + static inline int hub_is_superspeed(struct usb_device *hdev) { return (hdev->descriptor.bDeviceProtocol == USB_HUB_PR_SS); @@ -466,6 +484,14 @@ static int set_port_feature(struct usb_device *hdev, int port1, int feature) NULL, 0, 1000); } +static 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); +} + /* * USB 2.0 spec Section 11.24.2.7.1.10 and table 11-7 * for info about using port indicators @@ -1493,6 +1519,7 @@ static int hub_configure(struct usb_hub *hub, if (hub->has_indicators && blinkenlights) hub->indicator [0] = INDICATOR_CYCLE; + usb_hub_port_sysfs_add(hub); usb_acpi_bind_hub_ports(hdev); hub_activate(hub, HUB_INIT); return 0; @@ -1531,6 +1558,7 @@ static void hub_disconnect(struct usb_interface *intf) hub->error = 0; hub_quiesce(hub, HUB_DISCONNECT); + usb_hub_port_sysfs_remove(hub); usb_set_intfdata (intf, NULL); hub->hdev->maxchild = 0; @@ -2558,6 +2586,27 @@ 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); + u16 portstatus; + int ret; + + mutex_lock(&hub->status_mutex); + ret = get_hub_status(hub->hdev, &hub->status->hub); + if (ret < 4) { + dev_err (hub->intfdev, + "%s failed (err = %d)\n", __func__, ret); + if (ret >= 0) + ret = -EIO; + mutex_unlock(&hub->status_mutex); + return ret; + } else + portstatus = le16_to_cpu(hub->status->hub.wHubStatus); + mutex_lock(&hub->status_mutex); + 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) */ @@ -4499,6 +4548,137 @@ 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); + struct usb_hub_port *hub_port = container_of(attr, + struct usb_hub_port, port_state_attr); + const char *result; + int port1; + + if (sscanf(hub_port->port_attr_group.name, "port%d", &port1) != 1) + return -EINVAL; + + if (usb_get_hub_port_power_state(udev, port1) == 1) + result = on_string; + else if (!usb_get_hub_port_power_state(udev, port1)) + result = off_string; + else + result = "error"; + return sprintf(buf, "%s\n", result); +} + +static ssize_t show_port_power_control(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_hub_port *hub_port = container_of(attr, + struct usb_hub_port, port_control_attr); + enum port_power_policy policy; + int port1; + const char *result; + + if (sscanf(hub_port->port_attr_group.name, "port%d", &port1) != 1) + return -EINVAL; + + policy = hub_port->port_power_policy; + 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->parent); + struct usb_hub_port *hub_port = container_of(attr, + struct usb_hub_port, port_control_attr); + char *cp; + int port1; + int len = count; + + if (sscanf(hub_port->port_attr_group.name, "port%d", &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 (hub_port->child == NULL) + usb_set_hub_port_power(udev, port1, false); + hub_port->port_power_policy = USB_PORT_POWER_AUTO; + } else if (len == sizeof on_string - 1 + && strncmp(buf, on_string, len) == 0) { + hub_port->port_power_policy = 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) { + hub_port->port_power_policy = USB_PORT_POWER_OFF; + usb_set_hub_port_power(udev, port1, false); + } else + return -EINVAL; + + return count; +} + +static void usb_hub_port_sysfs_add(struct usb_hub *hub) +{ + int i, rc; + + for (i = 0; i < hub->hdev->maxchild; i++) { + hub->port_data[i].port_control_attr = (struct device_attribute) + __ATTR(control, 0777, show_port_power_control, + store_port_power_control); + hub->port_data[i].port_state_attr = (struct device_attribute) + __ATTR(state, 0777, show_port_power_state, NULL); + + hub->port_data[i].port_attrs[0] = &hub->port_data[i].port_control_attr.attr; + hub->port_data[i].port_attrs[1] = &hub->port_data[i].port_state_attr.attr; + hub->port_data[i].port_attrs[2] = NULL; + hub->port_data[i].port_attr_group = (struct attribute_group) { + .name = port_name[i], + .attrs = hub->port_data[i].port_attrs + }; + + rc = sysfs_create_group(&hub->intfdev->kobj, + &hub->port_data[i].port_attr_group); + if (rc < 0) + dev_err(hub->intfdev, "can't create sys " \ + "files for port%d\n", i + 1); + } +} + +static void usb_hub_port_sysfs_remove(struct usb_hub *hub) +{ + int i; + + for (i = 0; i < hub->hdev->maxchild; i++) + sysfs_remove_group(&hub->intfdev->kobj, + &hub->port_data[i].port_attr_group); +} + +static void usb_hub_port_sysfs_init(void) +{ + int i; + + for (i = 0; i < USB_MAXCHILDREN; i++) + sprintf(port_name[i], "port%d", i + 1); +} + static const struct usb_device_id hub_id_table[] = { { .match_flags = USB_DEVICE_ID_MATCH_DEV_CLASS, .bDeviceClass = USB_CLASS_HUB}, @@ -4531,6 +4711,7 @@ int usb_hub_init(void) return -1; } + usb_hub_port_sysfs_init(); khubd_task = kthread_run(hub_thread, NULL, "khubd"); if (!IS_ERR(khubd_task)) return 0; -- 1.7.6.rc2.8.g28eb -- 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