summaryrefslogtreecommitdiff
path: root/drivers/usb
diff options
context:
space:
mode:
authorMathias Nyman <mathias.nyman@linux.intel.com>2021-11-16 01:16:30 +0300
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2021-12-08 10:45:02 +0300
commitb13daa1c3d425498e7233332f806035bac53c152 (patch)
treeaaa70c406d82a1201f83e1a87e30ff8e9ce455d2 /drivers/usb
parent339e9af5fc264003dec2ece442aa384e9990338b (diff)
downloadlinux-b13daa1c3d425498e7233332f806035bac53c152.tar.xz
usb: hub: Fix usb enumeration issue due to address0 race
commit 6ae6dc22d2d1ce6aa77a6da8a761e61aca216f8b upstream. xHC hardware can only have one slot in default state with address 0 waiting for a unique address at a time, otherwise "undefined behavior may occur" according to xhci spec 5.4.3.4 The address0_mutex exists to prevent this across both xhci roothubs. If hub_port_init() fails, it may unlock the mutex and exit with a xhci slot in default state. If the other xhci roothub calls hub_port_init() at this point we end up with two slots in default state. Make sure the address0_mutex protects the slot default state across hub_port_init() retries, until slot is addressed or disabled. Note, one known minor case is not fixed by this patch. If device needs to be reset during resume, but fails all hub_port_init() retries in usb_reset_and_verify_device(), then it's possible the slot is still left in default state when address0_mutex is unlocked. Cc: <stable@vger.kernel.org> Fixes: 638139eb95d2 ("usb: hub: allow to process more usb hub events in parallel") Signed-off-by: Mathias Nyman <mathias.nyman@linux.intel.com> Link: https://lore.kernel.org/r/20211115221630.871204-1-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/usb')
-rw-r--r--drivers/usb/core/hub.c14
1 files changed, 11 insertions, 3 deletions
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index 92057a3f346f..c2bfac432505 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -4460,8 +4460,6 @@ hub_port_init(struct usb_hub *hub, struct usb_device *udev, int port1,
if (oldspeed == USB_SPEED_LOW)
delay = HUB_LONG_RESET_TIME;
- mutex_lock(hcd->address0_mutex);
-
/* Reset the device; full speed may morph to high speed */
/* FIXME a USB 2.0 device may morph into SuperSpeed on reset. */
retval = hub_port_reset(hub, port1, udev, delay, false);
@@ -4748,7 +4746,6 @@ fail:
hub_port_disable(hub, port1, 0);
update_devnum(udev, devnum); /* for disconnect processing */
}
- mutex_unlock(hcd->address0_mutex);
return retval;
}
@@ -4893,6 +4890,9 @@ static void hub_port_connect(struct usb_hub *hub, int port1, u16 portstatus,
unit_load = 100;
status = 0;
+
+ mutex_lock(hcd->address0_mutex);
+
for (i = 0; i < SET_CONFIG_TRIES; i++) {
/* reallocate for each attempt, since references
@@ -4929,6 +4929,8 @@ static void hub_port_connect(struct usb_hub *hub, int port1, u16 portstatus,
if (status < 0)
goto loop;
+ mutex_unlock(hcd->address0_mutex);
+
if (udev->quirks & USB_QUIRK_DELAY_INIT)
msleep(2000);
@@ -5017,6 +5019,7 @@ static void hub_port_connect(struct usb_hub *hub, int port1, u16 portstatus,
loop_disable:
hub_port_disable(hub, port1, 1);
+ mutex_lock(hcd->address0_mutex);
loop:
usb_ep0_reinit(udev);
release_devnum(udev);
@@ -5043,6 +5046,8 @@ loop:
}
done:
+ mutex_unlock(hcd->address0_mutex);
+
hub_port_disable(hub, port1, 1);
if (hcd->driver->relinquish_port && !hub->hdev->parent) {
if (status != -ENOTCONN && status != -ENODEV)
@@ -5572,6 +5577,8 @@ static int usb_reset_and_verify_device(struct usb_device *udev)
bos = udev->bos;
udev->bos = NULL;
+ mutex_lock(hcd->address0_mutex);
+
for (i = 0; i < SET_CONFIG_TRIES; ++i) {
/* ep0 maxpacket size may change; let the HCD know about it.
@@ -5581,6 +5588,7 @@ static int usb_reset_and_verify_device(struct usb_device *udev)
if (ret >= 0 || ret == -ENOTCONN || ret == -ENODEV)
break;
}
+ mutex_unlock(hcd->address0_mutex);
if (ret < 0)
goto re_enumerate;