[PATCH v3] usb: core: hub: Re-guess ep0 maxpacket size after reset

From: Pengfei Lv

Date: Mon Nov 09 2026 - 04:44:09 EST


Per USB 2.0 spec section 5.5.3, the ep0 maxpacket size is fixed for
all device speeds except full-speed devices, where it can be 8, 16,
32, or 64 bytes and can only be determined by reading the device
descriptor. During initial enumeration the USB core guesses 64 bytes
for full-speed devices; after reading the descriptor, the real value
is stored in udev->ep0.desc.wMaxPacketSize and applied to hardware by
usb_ep0_reinit().

Commit ff33299ec8bb ("USB: core: Fix race by not overwriting
udev->descriptor in hub_port_init()") guarded the ep0 maxpacket guess
block with "if (initial)", so on the reset path the guess is skipped
and the host keeps using the value from the previous enumeration. If
a buggy full-speed device responds with one 8-byte packet and NAKs
all further polling, the HC doesn't recognize a short transfer but
keeps polling for the rest of the descriptor till unlink, and the
device cannot be re-enumerated.

This was observed in production on a 6.6 LTS kernel with a full-speed
USB banking token (bMaxPacketSize0 = 8) in a virtual-machine USB
passthrough scenario; the reset re-enumeration hangs 100% of the time
after the VM issues USBDEVFS_RESET. The device is not strictly
compliant (it should continue answering IN tokens), but this class of
USB banking token is widely deployed in enterprise environments, and
the regression breaks a configuration that previously worked.

The 18-byte GET_DESCRIPTOR that runs after SET_ADDRESS (wLength=18)
is unaffected: the device sends the full 18 bytes as 8 + 8 + 2, and
the final 2-byte packet is always a short packet regardless of
maxpacket. This is why the device completes initial enumeration yet
hangs on reset re-enumeration.

Fix this by removing the "if (initial)" guard so that the ep0
maxpacket size is re-guessed on every reset, and by calling
usb_ep0_reinit() before each device-descriptor read so that the
re-guessed value is applied to hardware before the GET_DESCRIPTOR
data stage.

Fixes: ff33299ec8bb ("USB: core: Fix race by not overwriting udev->descriptor in hub_port_init()")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Pengfei Lv <lvpengfei20@xxxxxxxxxx>

---

v3: Per review by Alan Stern and Michal Pecio, drop the !initial guard
and move the usb_ep0_reinit() calls from after the re-guess block to
right before each get_bMaxPacketSize0() invocation, so that ep0 is
re-initialised before every descriptor read. The commit message is
reworded to be HCD-agnostic.

v2: https://lore.kernel.org/linux-usb/20261106031625.1824732-1-lvpengfei20@xxxxxxxxxx/
---
drivers/usb/core/hub.c | 52 +++++++++++++++++++++++-------------------
1 file changed, 28 insertions(+), 24 deletions(-)

diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index 199d3f535a89..d86bbba1457c 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -4969,31 +4969,29 @@ hub_port_init(struct usb_hub *hub, struct usb_device *udev, int port1,
}
oldspeed = udev->speed;

- if (initial) {
- /* USB 2.0 section 5.5.3 talks about ep0 maxpacket ...
- * it's fixed size except for full speed devices.
+ /* USB 2.0 section 5.5.3 talks about ep0 maxpacket ...
+ * it's fixed size except for full speed devices.
+ */
+ switch (udev->speed) {
+ case USB_SPEED_SUPER_PLUS:
+ case USB_SPEED_SUPER:
+ udev->ep0.desc.wMaxPacketSize = cpu_to_le16(512);
+ break;
+ case USB_SPEED_HIGH: /* fixed at 64 */
+ udev->ep0.desc.wMaxPacketSize = cpu_to_le16(64);
+ break;
+ case USB_SPEED_FULL: /* 8, 16, 32, or 64 */
+ /* to determine the ep0 maxpacket size, try to read
+ * the device descriptor to get bMaxPacketSize0 and
+ * then correct our initial guess.
*/
- switch (udev->speed) {
- case USB_SPEED_SUPER_PLUS:
- case USB_SPEED_SUPER:
- udev->ep0.desc.wMaxPacketSize = cpu_to_le16(512);
- break;
- case USB_SPEED_HIGH: /* fixed at 64 */
- udev->ep0.desc.wMaxPacketSize = cpu_to_le16(64);
- break;
- case USB_SPEED_FULL: /* 8, 16, 32, or 64 */
- /* to determine the ep0 maxpacket size, try to read
- * the device descriptor to get bMaxPacketSize0 and
- * then correct our initial guess.
- */
- udev->ep0.desc.wMaxPacketSize = cpu_to_le16(64);
- break;
- case USB_SPEED_LOW: /* fixed at 8 */
- udev->ep0.desc.wMaxPacketSize = cpu_to_le16(8);
- break;
- default:
- goto fail;
- }
+ udev->ep0.desc.wMaxPacketSize = cpu_to_le16(64);
+ break;
+ case USB_SPEED_LOW: /* fixed at 8 */
+ udev->ep0.desc.wMaxPacketSize = cpu_to_le16(8);
+ break;
+ default:
+ goto fail;
}

speed = usb_speed_string(udev->speed);
@@ -5062,6 +5060,9 @@ hub_port_init(struct usb_hub *hub, struct usb_device *udev, int port1,
goto fail;
}

+ /* Sync the guessed ep0 maxpacket size to hardware if needed */
+ usb_ep0_reinit(udev);
+
maxp0 = get_bMaxPacketSize0(udev, buf,
GET_DESCRIPTOR_BUFSIZE, retries == 0);
if (maxp0 > 0 && !initial &&
@@ -5127,6 +5128,9 @@ hub_port_init(struct usb_hub *hub, struct usb_device *udev, int port1,
if (do_new_scheme)
break;

+ /* Sync the guessed ep0 maxpacket size to hardware if needed */
+ usb_ep0_reinit(udev);
+
maxp0 = get_bMaxPacketSize0(udev, buf, 8, retries == 0);
if (maxp0 < 0) {
retval = maxp0;
--
2.34.1