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

From: Alan Stern

Date: Mon Nov 09 2026 - 11:17:16 EST


On Mon, Nov 09, 2026 at 05:39:35PM +0800, Pengfei Lv wrote:
> 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/
> ---

This is looking pretty good. However...

> 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;
> }

The usb_ep0_reinit() call belongs here, right after
udev->ep0.desc.wMaxPacketSize is set.

>
> 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);

Not here. And not inside the retry loop -- ep0_reinit should be called
only once since the maxpacket value is changed only once.

> +
> 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);

And also not here.

> +
> maxp0 = get_bMaxPacketSize0(udev, buf, 8, retries == 0);
> if (maxp0 < 0) {
> retval = maxp0;
> --
> 2.34.1

Alan Stern