Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

usb: hub: port: add sysfs entry to switch port power

In some cases the port of an hub needs to be disabled or switched off
and on again. E.g. when the connected device needs to be re-enumerated.
Or it needs to be explicitly disabled while the rest of the usb tree
stays working.

For this purpose this patch adds an sysfs switch to enable/disable the
port on any hub. In the case the hub is supporting power switching, the
power line will be disabled to the connected device.

When the port gets disabled, the associated device gets disconnected and
removed from the logical usb tree. No further device will be enumerated
on that port until the port gets enabled again.

Reviewed-by: Alan Stern <stern@rowland.harvard.edu>
Signed-off-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Link: https://lore.kernel.org/r/20220607114522.3359148-1-m.grzeschik@pengutronix.de
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

authored by

Michael Grzeschik and committed by
Greg Kroah-Hartman
f061f43d 5fd6c4f0

+117 -19
+11
Documentation/ABI/testing/sysfs-bus-usb
··· 253 253 only if the system firmware is capable of describing the 254 254 connection between a port and its connector. 255 255 256 + What: /sys/bus/usb/devices/.../<hub_interface>/port<X>/disable 257 + Date: June 2022 258 + Contact: Michael Grzeschik <m.grzeschik@pengutronix.de> 259 + Description: 260 + This file controls the state of a USB port, including 261 + Vbus power output (but only on hubs that support 262 + power switching -- most hubs don't support it). If 263 + a port is disabled, the port is unusable: Devices 264 + attached to the port will not be detected, initialized, 265 + or enumerated. 266 + 256 267 What: /sys/bus/usb/devices/.../power/usb2_lpm_l1_timeout 257 268 Date: May 2013 258 269 Contact: Mathias Nyman <mathias.nyman@linux.intel.com>
+20 -19
drivers/usb/core/hub.c
··· 613 613 return ret; 614 614 } 615 615 616 - static int hub_port_status(struct usb_hub *hub, int port1, 616 + int usb_hub_port_status(struct usb_hub *hub, int port1, 617 617 u16 *status, u16 *change) 618 618 { 619 619 return hub_ext_port_status(hub, port1, HUB_PORT_STATUS, ··· 1126 1126 u16 portstatus, portchange; 1127 1127 1128 1128 portstatus = portchange = 0; 1129 - status = hub_port_status(hub, port1, &portstatus, &portchange); 1129 + status = usb_hub_port_status(hub, port1, &portstatus, &portchange); 1130 1130 if (status) 1131 1131 goto abort; 1132 1132 ··· 2855 2855 &portstatus, &portchange, 2856 2856 &ext_portstatus); 2857 2857 else 2858 - ret = hub_port_status(hub, port1, &portstatus, 2858 + ret = usb_hub_port_status(hub, port1, &portstatus, 2859 2859 &portchange); 2860 2860 if (ret < 0) 2861 2861 return ret; ··· 2956 2956 * If the caller hasn't explicitly requested a warm reset, 2957 2957 * double check and see if one is needed. 2958 2958 */ 2959 - if (hub_port_status(hub, port1, &portstatus, &portchange) == 0) 2959 + if (usb_hub_port_status(hub, port1, &portstatus, 2960 + &portchange) == 0) 2960 2961 if (hub_port_warm_reset_required(hub, port1, 2961 2962 portstatus)) 2962 2963 warm = true; ··· 3009 3008 * If a USB 3.0 device migrates from reset to an error 3010 3009 * state, re-issue the warm reset. 3011 3010 */ 3012 - if (hub_port_status(hub, port1, 3011 + if (usb_hub_port_status(hub, port1, 3013 3012 &portstatus, &portchange) < 0) 3014 3013 goto done; 3015 3014 ··· 3075 3074 } 3076 3075 3077 3076 /* Check if a port is power on */ 3078 - static int port_is_power_on(struct usb_hub *hub, unsigned portstatus) 3077 + int usb_port_is_power_on(struct usb_hub *hub, unsigned int portstatus) 3079 3078 { 3080 3079 int ret = 0; 3081 3080 ··· 3141 3140 } 3142 3141 /* Is the device still present? */ 3143 3142 else if (status || port_is_suspended(hub, portstatus) || 3144 - !port_is_power_on(hub, portstatus)) { 3143 + !usb_port_is_power_on(hub, portstatus)) { 3145 3144 if (status >= 0) 3146 3145 status = -ENODEV; 3147 3146 } else if (!(portstatus & USB_PORT_STAT_CONNECTION)) { 3148 3147 if (retries--) { 3149 3148 usleep_range(200, 300); 3150 - status = hub_port_status(hub, port1, &portstatus, 3149 + status = usb_hub_port_status(hub, port1, &portstatus, 3151 3150 &portchange); 3152 3151 goto retry; 3153 3152 } ··· 3410 3409 u16 portstatus, portchange; 3411 3410 3412 3411 portstatus = portchange = 0; 3413 - ret = hub_port_status(hub, port1, &portstatus, 3412 + ret = usb_hub_port_status(hub, port1, &portstatus, 3414 3413 &portchange); 3415 3414 3416 3415 dev_dbg(&port_dev->dev, ··· 3588 3587 while (delay_ms < 2000) { 3589 3588 if (status || *portstatus & USB_PORT_STAT_CONNECTION) 3590 3589 break; 3591 - if (!port_is_power_on(hub, *portstatus)) { 3590 + if (!usb_port_is_power_on(hub, *portstatus)) { 3592 3591 status = -ENODEV; 3593 3592 break; 3594 3593 } 3595 3594 msleep(20); 3596 3595 delay_ms += 20; 3597 - status = hub_port_status(hub, port1, portstatus, portchange); 3596 + status = usb_hub_port_status(hub, port1, portstatus, portchange); 3598 3597 } 3599 3598 dev_dbg(&udev->dev, "Waited %dms for CONNECT\n", delay_ms); 3600 3599 return status; ··· 3654 3653 usb_lock_port(port_dev); 3655 3654 3656 3655 /* Skip the initial Clear-Suspend step for a remote wakeup */ 3657 - status = hub_port_status(hub, port1, &portstatus, &portchange); 3656 + status = usb_hub_port_status(hub, port1, &portstatus, &portchange); 3658 3657 if (status == 0 && !port_is_suspended(hub, portstatus)) { 3659 3658 if (portchange & USB_PORT_STAT_C_SUSPEND) 3660 3659 pm_wakeup_event(&udev->dev, 0); ··· 3679 3678 * stop resume signaling. Then finish the resume 3680 3679 * sequence. 3681 3680 */ 3682 - status = hub_port_status(hub, port1, &portstatus, &portchange); 3681 + status = usb_hub_port_status(hub, port1, &portstatus, &portchange); 3683 3682 } 3684 3683 3685 3684 SuspendCleared: ··· 3792 3791 u16 portstatus, portchange; 3793 3792 int status; 3794 3793 3795 - status = hub_port_status(hub, port1, &portstatus, &portchange); 3794 + status = usb_hub_port_status(hub, port1, &portstatus, &portchange); 3796 3795 if (!status && portchange) 3797 3796 return 1; 3798 3797 } ··· 4555 4554 struct usb_port *port_dev = hub->ports[port1 - 1]; 4556 4555 4557 4556 for (total_time = 0; ; total_time += HUB_DEBOUNCE_STEP) { 4558 - ret = hub_port_status(hub, port1, &portstatus, &portchange); 4557 + ret = usb_hub_port_status(hub, port1, &portstatus, &portchange); 4559 4558 if (ret < 0) 4560 4559 return ret; 4561 4560 ··· 5241 5240 * but only if the port isn't owned by someone else. 5242 5241 */ 5243 5242 if (hub_is_port_power_switchable(hub) 5244 - && !port_is_power_on(hub, portstatus) 5243 + && !usb_port_is_power_on(hub, portstatus) 5245 5244 && !port_dev->port_owner) 5246 5245 set_port_feature(hdev, port1, USB_PORT_FEAT_POWER); 5247 5246 ··· 5558 5557 clear_bit(port1, hub->event_bits); 5559 5558 clear_bit(port1, hub->wakeup_bits); 5560 5559 5561 - if (hub_port_status(hub, port1, &portstatus, &portchange) < 0) 5560 + if (usb_hub_port_status(hub, port1, &portstatus, &portchange) < 0) 5562 5561 return; 5563 5562 5564 5563 if (portchange & USB_PORT_STAT_C_CONNECTION) { ··· 5595 5594 USB_PORT_FEAT_C_OVER_CURRENT); 5596 5595 msleep(100); /* Cool down */ 5597 5596 hub_power_on(hub, true); 5598 - hub_port_status(hub, port1, &status, &unused); 5597 + usb_hub_port_status(hub, port1, &status, &unused); 5599 5598 if (status & USB_PORT_STAT_OVERCURRENT) 5600 5599 dev_err(&port_dev->dev, "over-current condition\n"); 5601 5600 } ··· 5639 5638 u16 unused; 5640 5639 5641 5640 msleep(20); 5642 - hub_port_status(hub, port1, &portstatus, &unused); 5641 + usb_hub_port_status(hub, port1, &portstatus, &unused); 5643 5642 dev_dbg(&port_dev->dev, "Wait for inactive link disconnect detect\n"); 5644 5643 continue; 5645 5644 } else if (!udev || !(portstatus & USB_PORT_STAT_CONNECTION)
+3
drivers/usb/core/hub.h
··· 121 121 bool must_be_connected); 122 122 extern int usb_clear_port_feature(struct usb_device *hdev, 123 123 int port1, int feature); 124 + extern int usb_hub_port_status(struct usb_hub *hub, int port1, 125 + u16 *status, u16 *change); 126 + extern int usb_port_is_power_on(struct usb_hub *hub, unsigned int portstatus); 124 127 125 128 static inline bool hub_is_port_power_switchable(struct usb_hub *hub) 126 129 {
+83
drivers/usb/core/port.c
··· 17 17 18 18 static const struct attribute_group *port_dev_group[]; 19 19 20 + static ssize_t disable_show(struct device *dev, 21 + struct device_attribute *attr, char *buf) 22 + { 23 + struct usb_port *port_dev = to_usb_port(dev); 24 + struct usb_device *hdev = to_usb_device(dev->parent->parent); 25 + struct usb_hub *hub = usb_hub_to_struct_hub(hdev); 26 + struct usb_interface *intf = to_usb_interface(hub->intfdev); 27 + int port1 = port_dev->portnum; 28 + u16 portstatus, unused; 29 + bool disabled; 30 + int rc; 31 + 32 + rc = usb_autopm_get_interface(intf); 33 + if (rc < 0) 34 + return rc; 35 + 36 + usb_lock_device(hdev); 37 + if (hub->disconnected) { 38 + rc = -ENODEV; 39 + goto out_hdev_lock; 40 + } 41 + 42 + usb_hub_port_status(hub, port1, &portstatus, &unused); 43 + disabled = !usb_port_is_power_on(hub, portstatus); 44 + 45 + out_hdev_lock: 46 + usb_unlock_device(hdev); 47 + usb_autopm_put_interface(intf); 48 + 49 + if (rc) 50 + return rc; 51 + 52 + return sysfs_emit(buf, "%s\n", disabled ? "1" : "0"); 53 + } 54 + 55 + static ssize_t disable_store(struct device *dev, struct device_attribute *attr, 56 + const char *buf, size_t count) 57 + { 58 + struct usb_port *port_dev = to_usb_port(dev); 59 + struct usb_device *hdev = to_usb_device(dev->parent->parent); 60 + struct usb_hub *hub = usb_hub_to_struct_hub(hdev); 61 + struct usb_interface *intf = to_usb_interface(hub->intfdev); 62 + int port1 = port_dev->portnum; 63 + bool disabled; 64 + int rc; 65 + 66 + rc = strtobool(buf, &disabled); 67 + if (rc) 68 + return rc; 69 + 70 + rc = usb_autopm_get_interface(intf); 71 + if (rc < 0) 72 + return rc; 73 + 74 + usb_lock_device(hdev); 75 + if (hub->disconnected) { 76 + rc = -ENODEV; 77 + goto out_hdev_lock; 78 + } 79 + 80 + if (disabled && port_dev->child) 81 + usb_disconnect(&port_dev->child); 82 + 83 + rc = usb_hub_set_port_power(hdev, hub, port1, !disabled); 84 + 85 + if (disabled) { 86 + usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_C_CONNECTION); 87 + if (!port_dev->is_superspeed) 88 + usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_C_ENABLE); 89 + } 90 + 91 + if (!rc) 92 + rc = count; 93 + 94 + out_hdev_lock: 95 + usb_unlock_device(hdev); 96 + usb_autopm_put_interface(intf); 97 + 98 + return rc; 99 + } 100 + static DEVICE_ATTR_RW(disable); 101 + 20 102 static ssize_t location_show(struct device *dev, 21 103 struct device_attribute *attr, char *buf) 22 104 { ··· 235 153 &dev_attr_location.attr, 236 154 &dev_attr_quirks.attr, 237 155 &dev_attr_over_current_count.attr, 156 + &dev_attr_disable.attr, 238 157 NULL, 239 158 }; 240 159