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

usb: core: stop USB enumeration if too many retries

When a broken USB accessory connects to a USB host, usbcore might
keep doing enumeration retries. If the host has a watchdog mechanism,
the kernel panic will happen on the host.

This patch provides an attribute early_stop to limit the numbers of retries
for each port of a hub. If a port was marked with early_stop attribute,
unsuccessful connection attempts will fail quickly. In addition, if an
early_stop port has failed to initialize, it will ignore all future
connection events until early_stop attribute is clear.

Signed-off-by: Ray Chi <raychi@google.com>
Reviewed-by: Alan Stern <stern@rowland.harvard.edu>
Link: https://lore.kernel.org/r/20221107072754.3336357-1-raychi@google.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

authored by

Ray Chi and committed by
Greg Kroah-Hartman
430d57f5 04914233

+102
+11
Documentation/ABI/testing/sysfs-bus-usb
··· 264 264 attached to the port will not be detected, initialized, 265 265 or enumerated. 266 266 267 + What: /sys/bus/usb/devices/.../<hub_interface>/port<X>/early_stop 268 + Date: Sep 2022 269 + Contact: Ray Chi <raychi@google.com> 270 + Description: 271 + Some USB hosts have some watchdog mechanisms so that the device 272 + may enter ramdump if it takes a long time during port initialization. 273 + This attribute allows each port just has two attempts so that the 274 + port initialization will be failed quickly. In addition, if a port 275 + which is marked with early_stop has failed to initialize, it will ignore 276 + all future connections until this attribute is clear. 277 + 267 278 What: /sys/bus/usb/devices/.../power/usb2_lpm_l1_timeout 268 279 Date: May 2013 269 280 Contact: Mathias Nyman <mathias.nyman@linux.intel.com>
+60
drivers/usb/core/hub.c
··· 3081 3081 return status; 3082 3082 } 3083 3083 3084 + /* 3085 + * hub_port_stop_enumerate - stop USB enumeration or ignore port events 3086 + * @hub: target hub 3087 + * @port1: port num of the port 3088 + * @retries: port retries number of hub_port_init() 3089 + * 3090 + * Return: 3091 + * true: ignore port actions/events or give up connection attempts. 3092 + * false: keep original behavior. 3093 + * 3094 + * This function will be based on retries to check whether the port which is 3095 + * marked with early_stop attribute would stop enumeration or ignore events. 3096 + * 3097 + * Note: 3098 + * This function didn't change anything if early_stop is not set, and it will 3099 + * prevent all connection attempts when early_stop is set and the attempts of 3100 + * the port are more than 1. 3101 + */ 3102 + static bool hub_port_stop_enumerate(struct usb_hub *hub, int port1, int retries) 3103 + { 3104 + struct usb_port *port_dev = hub->ports[port1 - 1]; 3105 + 3106 + if (port_dev->early_stop) { 3107 + if (port_dev->ignore_event) 3108 + return true; 3109 + 3110 + /* 3111 + * We want unsuccessful attempts to fail quickly. 3112 + * Since some devices may need one failure during 3113 + * port initialization, we allow two tries but no 3114 + * more. 3115 + */ 3116 + if (retries < 2) 3117 + return false; 3118 + 3119 + port_dev->ignore_event = 1; 3120 + } else 3121 + port_dev->ignore_event = 0; 3122 + 3123 + return port_dev->ignore_event; 3124 + } 3125 + 3084 3126 /* Check if a port is power on */ 3085 3127 int usb_port_is_power_on(struct usb_hub *hub, unsigned int portstatus) 3086 3128 { ··· 4838 4796 do_new_scheme = use_new_scheme(udev, retry_counter, port_dev); 4839 4797 4840 4798 for (retries = 0; retries < GET_DESCRIPTOR_TRIES; (++retries, msleep(100))) { 4799 + if (hub_port_stop_enumerate(hub, port1, retries)) { 4800 + retval = -ENODEV; 4801 + break; 4802 + } 4803 + 4841 4804 if (do_new_scheme) { 4842 4805 struct usb_device_descriptor *buf; 4843 4806 int r = 0; ··· 5293 5246 status = 0; 5294 5247 5295 5248 for (i = 0; i < PORT_INIT_TRIES; i++) { 5249 + if (hub_port_stop_enumerate(hub, port1, i)) { 5250 + status = -ENODEV; 5251 + break; 5252 + } 5253 + 5296 5254 usb_lock_port(port_dev); 5297 5255 mutex_lock(hcd->address0_mutex); 5298 5256 retry_locked = true; ··· 5666 5614 if (!pm_runtime_active(&port_dev->dev)) 5667 5615 return; 5668 5616 5617 + /* skip port actions if ignore_event and early_stop are true */ 5618 + if (port_dev->ignore_event && port_dev->early_stop) 5619 + return; 5620 + 5669 5621 if (hub_handle_remote_wakeup(hub, port1, portstatus, portchange)) 5670 5622 connect_change = 1; 5671 5623 ··· 5983 5927 mutex_lock(hcd->address0_mutex); 5984 5928 5985 5929 for (i = 0; i < PORT_INIT_TRIES; ++i) { 5930 + if (hub_port_stop_enumerate(parent_hub, port1, i)) { 5931 + ret = -ENODEV; 5932 + break; 5933 + } 5986 5934 5987 5935 /* ep0 maxpacket size may change; let the HCD know about it. 5988 5936 * Other endpoints will be handled by re-enumeration. */
+4
drivers/usb/core/hub.h
··· 90 90 * @is_superspeed cache super-speed status 91 91 * @usb3_lpm_u1_permit: whether USB3 U1 LPM is permitted. 92 92 * @usb3_lpm_u2_permit: whether USB3 U2 LPM is permitted. 93 + * @early_stop: whether port initialization will be stopped earlier. 94 + * @ignore_event: whether events of the port are ignored. 93 95 */ 94 96 struct usb_port { 95 97 struct usb_device *child; ··· 105 103 u32 over_current_count; 106 104 u8 portnum; 107 105 u32 quirks; 106 + unsigned int early_stop:1; 107 + unsigned int ignore_event:1; 108 108 unsigned int is_superspeed:1; 109 109 unsigned int usb3_lpm_u1_permit:1; 110 110 unsigned int usb3_lpm_u2_permit:1;
+27
drivers/usb/core/port.c
··· 18 18 19 19 static const struct attribute_group *port_dev_group[]; 20 20 21 + static ssize_t early_stop_show(struct device *dev, 22 + struct device_attribute *attr, char *buf) 23 + { 24 + struct usb_port *port_dev = to_usb_port(dev); 25 + 26 + return sysfs_emit(buf, "%s\n", port_dev->early_stop ? "yes" : "no"); 27 + } 28 + 29 + static ssize_t early_stop_store(struct device *dev, struct device_attribute *attr, 30 + const char *buf, size_t count) 31 + { 32 + struct usb_port *port_dev = to_usb_port(dev); 33 + bool value; 34 + 35 + if (kstrtobool(buf, &value)) 36 + return -EINVAL; 37 + 38 + if (value) 39 + port_dev->early_stop = 1; 40 + else 41 + port_dev->early_stop = 0; 42 + 43 + return count; 44 + } 45 + static DEVICE_ATTR_RW(early_stop); 46 + 21 47 static ssize_t disable_show(struct device *dev, 22 48 struct device_attribute *attr, char *buf) 23 49 { ··· 263 237 &dev_attr_quirks.attr, 264 238 &dev_attr_over_current_count.attr, 265 239 &dev_attr_disable.attr, 240 + &dev_attr_early_stop.attr, 266 241 NULL, 267 242 }; 268 243