mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-10-07 17:19:02 +00:00
usb: hub: Fix usb enumeration issue due to address0 race
commit6ae6dc22d2
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:638139eb95
("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>
This commit is contained in:
parent
00f1038c72
commit
5bf3a0c778
1 changed files with 11 additions and 3 deletions
|
@ -4628,8 +4628,6 @@ hub_port_init(struct usb_hub *hub, struct usb_device *udev, int port1,
|
||||||
if (oldspeed == USB_SPEED_LOW)
|
if (oldspeed == USB_SPEED_LOW)
|
||||||
delay = HUB_LONG_RESET_TIME;
|
delay = HUB_LONG_RESET_TIME;
|
||||||
|
|
||||||
mutex_lock(hcd->address0_mutex);
|
|
||||||
|
|
||||||
/* Reset the device; full speed may morph to high speed */
|
/* Reset the device; full speed may morph to high speed */
|
||||||
/* FIXME a USB 2.0 device may morph into SuperSpeed on reset. */
|
/* FIXME a USB 2.0 device may morph into SuperSpeed on reset. */
|
||||||
retval = hub_port_reset(hub, port1, udev, delay, false);
|
retval = hub_port_reset(hub, port1, udev, delay, false);
|
||||||
|
@ -4940,7 +4938,6 @@ hub_port_init(struct usb_hub *hub, struct usb_device *udev, int port1,
|
||||||
hub_port_disable(hub, port1, 0);
|
hub_port_disable(hub, port1, 0);
|
||||||
update_devnum(udev, devnum); /* for disconnect processing */
|
update_devnum(udev, devnum); /* for disconnect processing */
|
||||||
}
|
}
|
||||||
mutex_unlock(hcd->address0_mutex);
|
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5170,6 +5167,9 @@ static void hub_port_connect(struct usb_hub *hub, int port1, u16 portstatus,
|
||||||
unit_load = 100;
|
unit_load = 100;
|
||||||
|
|
||||||
status = 0;
|
status = 0;
|
||||||
|
|
||||||
|
mutex_lock(hcd->address0_mutex);
|
||||||
|
|
||||||
for (i = 0; i < PORT_INIT_TRIES; i++) {
|
for (i = 0; i < PORT_INIT_TRIES; i++) {
|
||||||
|
|
||||||
/* reallocate for each attempt, since references
|
/* reallocate for each attempt, since references
|
||||||
|
@ -5206,6 +5206,8 @@ static void hub_port_connect(struct usb_hub *hub, int port1, u16 portstatus,
|
||||||
if (status < 0)
|
if (status < 0)
|
||||||
goto loop;
|
goto loop;
|
||||||
|
|
||||||
|
mutex_unlock(hcd->address0_mutex);
|
||||||
|
|
||||||
if (udev->quirks & USB_QUIRK_DELAY_INIT)
|
if (udev->quirks & USB_QUIRK_DELAY_INIT)
|
||||||
msleep(2000);
|
msleep(2000);
|
||||||
|
|
||||||
|
@ -5294,6 +5296,7 @@ static void hub_port_connect(struct usb_hub *hub, int port1, u16 portstatus,
|
||||||
|
|
||||||
loop_disable:
|
loop_disable:
|
||||||
hub_port_disable(hub, port1, 1);
|
hub_port_disable(hub, port1, 1);
|
||||||
|
mutex_lock(hcd->address0_mutex);
|
||||||
loop:
|
loop:
|
||||||
usb_ep0_reinit(udev);
|
usb_ep0_reinit(udev);
|
||||||
release_devnum(udev);
|
release_devnum(udev);
|
||||||
|
@ -5320,6 +5323,8 @@ static void hub_port_connect(struct usb_hub *hub, int port1, u16 portstatus,
|
||||||
}
|
}
|
||||||
|
|
||||||
done:
|
done:
|
||||||
|
mutex_unlock(hcd->address0_mutex);
|
||||||
|
|
||||||
hub_port_disable(hub, port1, 1);
|
hub_port_disable(hub, port1, 1);
|
||||||
if (hcd->driver->relinquish_port && !hub->hdev->parent) {
|
if (hcd->driver->relinquish_port && !hub->hdev->parent) {
|
||||||
if (status != -ENOTCONN && status != -ENODEV)
|
if (status != -ENOTCONN && status != -ENODEV)
|
||||||
|
@ -5839,6 +5844,8 @@ static int usb_reset_and_verify_device(struct usb_device *udev)
|
||||||
bos = udev->bos;
|
bos = udev->bos;
|
||||||
udev->bos = NULL;
|
udev->bos = NULL;
|
||||||
|
|
||||||
|
mutex_lock(hcd->address0_mutex);
|
||||||
|
|
||||||
for (i = 0; i < PORT_INIT_TRIES; ++i) {
|
for (i = 0; i < PORT_INIT_TRIES; ++i) {
|
||||||
|
|
||||||
/* ep0 maxpacket size may change; let the HCD know about it.
|
/* ep0 maxpacket size may change; let the HCD know about it.
|
||||||
|
@ -5848,6 +5855,7 @@ static int usb_reset_and_verify_device(struct usb_device *udev)
|
||||||
if (ret >= 0 || ret == -ENOTCONN || ret == -ENODEV)
|
if (ret >= 0 || ret == -ENOTCONN || ret == -ENODEV)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
mutex_unlock(hcd->address0_mutex);
|
||||||
|
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
goto re_enumerate;
|
goto re_enumerate;
|
||||||
|
|
Loading…
Reference in a new issue