Re: [PATCH 3/3] usb : Add sysfs files to control usb port's power

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Mon, Jun 11, 2012 at 10:24:34AM +0800, Lan Tianyu wrote:
> 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 two options. "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
> 
> Signed-off-by: Lan Tianyu <tianyu.lan@xxxxxxxxx>
> ---
>  Documentation/ABI/testing/sysfs-bus-usb |   33 ++++++
>  drivers/usb/core/hub.c                  |  168 ++++++++++++++++++++++++++++++-
>  2 files changed, 200 insertions(+), 1 deletions(-)
> 
> diff --git a/Documentation/ABI/testing/sysfs-bus-usb b/Documentation/ABI/testing/sysfs-bus-usb
> index 6df4e6f..c2fb9d7 100644
> --- a/Documentation/ABI/testing/sysfs-bus-usb
> +++ b/Documentation/ABI/testing/sysfs-bus-usb
> @@ -208,3 +208,36 @@ Description:
>  		such as ACPI. This file will read either "removable" or
>  		"fixed" if the information is available, and "unknown"
>  		otherwise.
> +
> +What:		/sys/bus/usb/devices/.../(hub interface)/portX
> +Date:		June 2012
> +Contact		Lan Tianyu <tianyu.lan@xxxxxxxxx>
> +Description:
> +		The /sys/bus/usb/device/.../(hub interface)/portX directory
> +		contains attributes allowing user space to check and modify
> +		properties of the associated USB port.
> +
> +What:		/sys/bus/usb/devices/.../(hub interface)/portX/control
> +Date:		June 2012
> +Contact		Lan Tianyu <tianyu.lan@xxxxxxxxx>
> +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:		June 2012
> +Contact		Lan Tianyu <tianyu.lan@xxxxxxxxx>
> +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 2ed5244..e83b0f5 100644
> --- a/drivers/usb/core/hub.c
> +++ b/drivers/usb/core/hub.c
> @@ -41,12 +41,20 @@
>  #endif
>  #endif
>  
> +enum port_power_policy {
> +	USB_PORT_POWER_ON = 0,
> +	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 device_attribute	port_control_attr;
> +	struct device_attribute	port_state_attr;
> +	enum port_power_policy port_power_policy;

Why do you need an attribute per port here?  Shouldn't they just be
static variables?  Why duplicate them for every port?

>  	enum usb_port_connect_type connect_type;
>  };
>  
> @@ -97,6 +105,12 @@ struct usb_hub {
>  	struct usb_hub_port	*port_data;
>  };
>  
> +static const char on_string[] = "on";
> +static const char off_string[] = "off";
> +static char port_name[USB_MAXCHILDREN][8];
> +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);
> @@ -847,7 +861,9 @@ 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->port_data[port1 - 1].port_power_policy
> +				== USB_PORT_POWER_ON)
> +			set_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);
> @@ -1493,6 +1509,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 +1548,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 +2576,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) */
> @@ -4499,6 +4536,134 @@ 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);
> +	struct usb_hub *hub = dev_get_drvdata(dev);
> +	int port1 = hub_port - hub->port_data + 1;
> +	int power_state;
> +	const char *result;
> +
> +	power_state = usb_get_hub_port_power_state(udev, port1);
> +	if (power_state == 1)
> +		result = on_string;
> +	else if (!power_state)
> +		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);
> +	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 *udev = to_usb_device(dev->parent);
> +	struct usb_hub_port *hub_port = container_of(attr,
> +		struct usb_hub_port, port_control_attr);
> +	char *cp;
> +	struct usb_hub *hub = dev_get_drvdata(dev);
> +	int port1 = hub_port - hub->port_data + 1;
> +	int len = count;
> +
> +	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;
> +		set_port_feature(udev, port1, USB_PORT_FEAT_POWER);
> +	} else if (len == sizeof off_string - 1
> +		&& strncmp(buf, off_string, len) == 0) {
> +		hub_port->port_power_policy = USB_PORT_POWER_OFF;
> +		clear_port_feature(udev, port1, USB_PORT_FEAT_POWER);
> +	} else
> +		return -EINVAL;
> +
> +	return count;
> +}
> +
> +static void usb_hub_port_sysfs_add(struct usb_hub *hub)
> +{
> +	int i, rc;
> +	struct attribute *port_attrs[3];
> +	struct attribute_group port_attr_group;
> +
> +	for (i = 0; i < hub->hdev->maxchild; i++) {
> +		hub->port_data[i].port_control_attr = (struct device_attribute)
> +			__ATTR(control, S_IRUGO | S_IWUSR,
> +			show_port_power_control,
> +			store_port_power_control);
> +		hub->port_data[i].port_state_attr = (struct device_attribute)
> +			__ATTR(state, S_IRUGO, show_port_power_state, NULL);
> +
> +		port_attrs[0] = &hub->port_data[i].port_control_attr.attr;
> +		port_attrs[1] = &hub->port_data[i].port_state_attr.attr;
> +		port_attrs[2] = NULL;
> +		port_attr_group = (struct attribute_group) {
> +			.name = port_name[i],
> +			.attrs = port_attrs
> +		};
> +
> +		rc = sysfs_create_group(&hub->intfdev->kobj,
> +			&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;
> +	struct attribute *port_attrs[3];
> +	struct attribute_group port_attr_group;
> +
> +	for (i = 0; i < hub->hdev->maxchild; i++) {
> +		port_attrs[0] = &hub->port_data[i].port_control_attr.attr;
> +		port_attrs[1] = &hub->port_data[i].port_state_attr.attr;
> +		port_attrs[2] = NULL;
> +		port_attr_group = (struct attribute_group) {
> +			.name = port_name[i],
> +			.attrs = port_attrs
> +		};
> +		sysfs_remove_group(&hub->intfdev->kobj,
> +			&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 +4696,7 @@ int usb_hub_init(void)
>  		return -1;
>  	}
>  
> +	usb_hub_port_sysfs_init();

Did you just race userspace with these attributes?  Are you sure?

thanks,

greg k-h
--
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


[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux