* [PATCH] usb: core: hub: Re-guess ep0 maxpacket size on reset
@ 2026-11-04 12:44 Pengfei Lv
2026-11-04 12:57 ` sashiko-bot
2026-11-04 14:59 ` Alan Stern
0 siblings, 2 replies; 4+ messages in thread
From: Pengfei Lv @ 2026-11-04 12:44 UTC (permalink / raw)
To: gregkh
Cc: stern, linux-usb, linux-kernel, stable, xuhaiyang5, xuetao09,
caiyadong, suzhuangluan, Pengfei Lv
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 the initial enumeration of a full-speed device,
the USB core guesses 64 bytes (hub_port_init() with initial=true);
after reading the descriptor, the real value (e.g. 8) is stored in
udev->ep0.desc.wMaxPacketSize and propagated to the xHCI ep0 context
via an Evaluate Context command (xhci_check_maxpacket()).
After a port reset (usb_reset_and_verify_device() -> hub_port_init()
with initial=false), the "if (initial)" guard added by commit
ff33299ec8bb ("USB: core: Fix race by not overwriting udev->descriptor in hub_port_init()") skips the ep0 maxpacket
guess block, so udev->ep0.desc.wMaxPacketSize retains the real value
(e.g. 8). The xHCI Reset Device command preserves the ep0 context
as well, so the xHC ep0 max_packet also stays at 8.
When the subsequent re-enumeration then uses the "new scheme" and
issues a 64-byte GET_DESCRIPTOR (get_bMaxPacketSize0 with size=64),
xhci_check_maxpacket() sees hw==sw==8 and does not issue an Evaluate
Context command. The xHC queues a Data TRB of length 64 but applies
ep0 max_packet=8 for short-packet detection. If the device's first
data packet is exactly 8 bytes and it sends no ZLP, the xHC does not
recognise a short packet, keeps issuing IN tokens for the remaining
56 bytes, and the control transfer hangs in the Data stage until URB
timeout. (A standard 18-byte descriptor returned as 8+8+2 is not
affected because the final 2-byte packet is a short packet.)
Fix this by removing the "if (initial)" guard so that the ep0
maxpacket size is re-guessed from udev->speed on every reset. For
full-speed devices this re-establishes the 64-byte guess, which makes
xhci_check_maxpacket() issue an Evaluate Context command that brings
the xHC ep0 context back to max_packet=64 before the GET_DESCRIPTOR
data stage; the data stage then terminates correctly via short-packet
detection, and the ep0 maxpacket is subsequently corrected to the
real value as usual.
For high-speed, low-speed, and SuperSpeed-or-faster devices the
re-guess is a no-op (guessed value equals the fixed real value).
The descriptor-immutability checks (!initial && maxp0 !=
udev->descriptor.bMaxPacketSize0) remain unchanged.
Fixes: ff33299ec8bb ("USB: core: Fix race by not overwriting udev->descriptor in hub_port_init()")
Cc: stable@vger.kernel.org
Signed-off-by: Pengfei Lv <lvpengfei20@huawei.com>
---
drivers/usb/core/hub.c | 46 ++++++++++++++++++++----------------------
1 file changed, 22 insertions(+), 24 deletions(-)
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index 199d3f535a89..51f66b24b353 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);
--
2.34.1
^ permalink raw reply related [flat|nested] 4+ messages in thread
* Re: [PATCH] usb: core: hub: Re-guess ep0 maxpacket size on reset
2026-11-04 12:44 [PATCH] usb: core: hub: Re-guess ep0 maxpacket size on reset Pengfei Lv
@ 2026-11-04 12:57 ` sashiko-bot
2026-11-04 14:59 ` Alan Stern
1 sibling, 0 replies; 4+ messages in thread
From: sashiko-bot @ 2026-11-04 12:57 UTC (permalink / raw)
To: Pengfei Lv; +Cc: linux-usb
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] usb: core: hub: missing usb_ep0_reinit() in hub_port_init() after ep0 guess
--
commit 8488ba17cb81d9272c99646f158ad82af5c82843
Author: Pengfei Lv <lvpengfei20@huawei.com>
usb: core: hub: Re-guess ep0 maxpacket size on reset
This commit removes the if (initial) guard around the ep0 maxpacket size
initialization in hub_port_init() so that udev->ep0.desc.wMaxPacketSize is
re-guessed from udev->speed during port resets as well as initial
enumeration.
> diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
> index 199d3f535a891..51f66b24b3538 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;
[Severity: High]
Does hub_port_init() also need to call usb_ep0_reinit(udev) after resetting
udev->ep0.desc.wMaxPacketSize to 64 when !initial?
The commit message notes that re-establishing the 64-byte guess makes
xhci_check_maxpacket() issue an Evaluate Context command before the
GET_DESCRIPTOR data stage. However, commit e34900f46cd6 ("xhci: Reconfigure
endpoint 0 max packet size only during endpoint reset") removed that check
from xhci_urb_enqueue() and replaced it with xhci_check_ep0_maxpacket(),
which is only invoked from xhci_endpoint_reset() via usb_ep0_reinit():
drivers/usb/host/xhci.c:xhci_endpoint_reset() {
if (usb_endpoint_xfer_control(&host_ep->desc) && ep_index == 0) {
...
xhci_check_ep0_maxpacket(xhci, vdev);
/* Nothing else should be done here for ep0 during ep reset */
return;
}
}
During a port reset in usb_reset_and_verify_device(), usb_ep0_reinit(udev)
is called before hub_port_init():
drivers/usb/core/hub.c:usb_reset_and_verify_device() {
for (i = 0; i < SET_CONFIG_TRIES; ++i) {
/* ep0 maxpacket size may change; let the HCD know about it.
* Other endpoints will be handled by re-enumeration. */
usb_ep0_reinit(udev);
ret = hub_port_init(parent_hub, udev, port1, i, &descriptor);
}
On the first attempt (i == 0), udev->ep0.desc.wMaxPacketSize still holds the
pre-reset value (e.g. 8) when usb_ep0_reinit(udev) runs, matching the xHC
output EP0 context, so xhci_check_ep0_maxpacket() does nothing.
Then hub_port_init() resets the port (leaving the slot in SLOT_STATE_DEFAULT
with max_packet = 8) and sets udev->ep0.desc.wMaxPacketSize = cpu_to_le16(64)
in software without calling usb_ep0_reinit(udev). When do_new_scheme is true,
hub_enable_device() -> xhci_enable_device() -> xhci_setup_device() sees the
slot is already in SLOT_STATE_DEFAULT and returns early without updating the
EP0 context:
drivers/usb/host/xhci.c:xhci_setup_device() {
if (setup == SETUP_CONTEXT_ONLY) {
if (GET_SLOT_STATE(le32_to_cpu(slot_ctx->dev_state)) ==
SLOT_STATE_DEFAULT) {
xhci_dbg(xhci, "Slot already in default state\n");
goto out;
}
}
}
As a result, when get_bMaxPacketSize0() submits the 64-byte GET_DESCRIPTOR
URB on i == 0, the xHC hardware EP0 context still has max_packet = 8, so the
first hub_port_init() call still times out across all GET_DESCRIPTOR_TRIES *
GET_MAXPACKET0_TRIES attempts before usb_reset_and_verify_device() retries
with i == 1.
In addition, for standard full-speed devices with bMaxPacketSize0 = 8 whose
18-byte descriptor read succeeds on i == 0 in do_new_scheme,
hub_set_address() calls usb_ep0_reinit(udev) while
udev->ep0.desc.wMaxPacketSize is still 64, issuing an Evaluate Context
command to change max_packet from 8 to 64 right before hub_port_init() sets
wMaxPacketSize back to 8 and calls usb_ep0_reinit(udev) a second time.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261104124402.1269650-1-lvpengfei20@huawei.com?part=1
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH] usb: core: hub: Re-guess ep0 maxpacket size on reset
2026-11-04 12:44 [PATCH] usb: core: hub: Re-guess ep0 maxpacket size on reset Pengfei Lv
2026-11-04 12:57 ` sashiko-bot
@ 2026-11-04 14:59 ` Alan Stern
2026-11-04 17:36 ` Michal Pecio
1 sibling, 1 reply; 4+ messages in thread
From: Alan Stern @ 2026-11-04 14:59 UTC (permalink / raw)
To: Pengfei Lv
Cc: gregkh, linux-usb, linux-kernel, stable, xuhaiyang5, xuetao09,
caiyadong, suzhuangluan
On Wed, Nov 04, 2026 at 08:44:02PM +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 the initial enumeration of a full-speed device,
> the USB core guesses 64 bytes (hub_port_init() with initial=true);
> after reading the descriptor, the real value (e.g. 8) is stored in
> udev->ep0.desc.wMaxPacketSize and propagated to the xHCI ep0 context
> via an Evaluate Context command (xhci_check_maxpacket()).
>
> After a port reset (usb_reset_and_verify_device() -> hub_port_init()
> with initial=false), the "if (initial)" guard added by commit
> ff33299ec8bb ("USB: core: Fix race by not overwriting udev->descriptor in hub_port_init()") skips the ep0 maxpacket
> guess block, so udev->ep0.desc.wMaxPacketSize retains the real value
> (e.g. 8). The xHCI Reset Device command preserves the ep0 context
> as well, so the xHC ep0 max_packet also stays at 8.
>
> When the subsequent re-enumeration then uses the "new scheme" and
> issues a 64-byte GET_DESCRIPTOR (get_bMaxPacketSize0 with size=64),
> xhci_check_maxpacket() sees hw==sw==8 and does not issue an Evaluate
> Context command. The xHC queues a Data TRB of length 64 but applies
> ep0 max_packet=8 for short-packet detection.
That's exactly what we want it to do.
> If the device's first
> data packet is exactly 8 bytes and it sends no ZLP, the xHC does not
> recognise a short packet, keeps issuing IN tokens for the remaining
> 56 bytes, and the control transfer hangs in the Data stage until URB
> timeout.
That is the correct behavior. Indeed, the same issue applies to _any_
64-byte transfer (not just control and not just Get-Descriptor) when the
maxpacket value is 8. If the device sends an 8-byte reply with no ZLP
following, the transfer will hang in the data stage. That's _supposed_
to happen.
> (A standard 18-byte descriptor returned as 8+8+2 is not
> affected because the final 2-byte packet is a short packet.)
Quite right. Why wouldn't the device reply with a standard 18-byte
descriptor in this situation?
Have you observed this problem in actual testing or is it purely
theoretical?
> Fix this by removing the "if (initial)" guard so that the ep0
> maxpacket size is re-guessed from udev->speed on every reset. For
> full-speed devices this re-establishes the 64-byte guess, which makes
> xhci_check_maxpacket() issue an Evaluate Context command that brings
> the xHC ep0 context back to max_packet=64 before the GET_DESCRIPTOR
> data stage; the data stage then terminates correctly via short-packet
> detection, and the ep0 maxpacket is subsequently corrected to the
> real value as usual.
>
> For high-speed, low-speed, and SuperSpeed-or-faster devices the
> re-guess is a no-op (guessed value equals the fixed real value).
> The descriptor-immutability checks (!initial && maxp0 !=
> udev->descriptor.bMaxPacketSize0) remain unchanged.
As far as I can see, there is no need to do this.
Alan Stern
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH] usb: core: hub: Re-guess ep0 maxpacket size on reset
2026-11-04 14:59 ` Alan Stern
@ 2026-11-04 17:36 ` Michal Pecio
0 siblings, 0 replies; 4+ messages in thread
From: Michal Pecio @ 2026-11-04 17:36 UTC (permalink / raw)
To: Alan Stern
Cc: Pengfei Lv, gregkh, linux-usb, linux-kernel, stable, xuhaiyang5,
xuetao09, caiyadong, suzhuangluan
On Wed, 4 Nov 2026 09:59:24 -0500, Alan Stern wrote:
> On Wed, Nov 04, 2026 at 08:44:02PM +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 the initial enumeration of a full-speed device,
> > the USB core guesses 64 bytes (hub_port_init() with initial=true);
> > after reading the descriptor, the real value (e.g. 8) is stored in
> > udev->ep0.desc.wMaxPacketSize and propagated to the xHCI ep0 context
> > via an Evaluate Context command (xhci_check_maxpacket()).
> >
> > After a port reset (usb_reset_and_verify_device() -> hub_port_init()
> > with initial=false), the "if (initial)" guard added by commit
> > ff33299ec8bb ("USB: core: Fix race by not overwriting udev->descriptor in hub_port_init()") skips the ep0 maxpacket
> > guess block, so udev->ep0.desc.wMaxPacketSize retains the real value
> > (e.g. 8). The xHCI Reset Device command preserves the ep0 context
> > as well, so the xHC ep0 max_packet also stays at 8.
> >
> > When the subsequent re-enumeration then uses the "new scheme" and
> > issues a 64-byte GET_DESCRIPTOR (get_bMaxPacketSize0 with size=64),
> > xhci_check_maxpacket() sees hw==sw==8 and does not issue an Evaluate
> > Context command. The xHC queues a Data TRB of length 64 but applies
> > ep0 max_packet=8 for short-packet detection.
>
> That's exactly what we want it to do.
Should we want it, though?
Were there known devices with bMaxPacketSize0 <= 16 which required the
whole transfer to complete before they are reset?
That's not what happens in the initial iteration, so it seems any such
devices would always take two iterations to begin working.
Is this what the only correct Proprietary Operating System does?
> > If the device's first
> > data packet is exactly 8 bytes and it sends no ZLP, the xHC does not
> > recognise a short packet, keeps issuing IN tokens for the remaining
> > 56 bytes, and the control transfer hangs in the Data stage until URB
> > timeout.
>
> That is the correct behavior. Indeed, the same issue applies to _any_
> 64-byte transfer (not just control and not just Get-Descriptor) when the
> maxpacket value is 8. If the device sends an 8-byte reply with no ZLP
> following, the transfer will hang in the data stage. That's _supposed_
> to happen.
Right, all those details about HC behavior and xHCI internals aren't
important here. The device is out of spec, apparently expecting the
host to always use MPS=64 for this particular request. It should send
the full descriptor in response to further polling.
More useful would be information about impact: what this abomination
is, does it always fail to enumerate on the first try and trigger this
unsuccessful retry, or only in exceptional cases? And of course,
> Have you observed this problem in actual testing or is it purely
> theoretical?
Regards,
Michal
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-11-04 17:36 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-04 12:44 [PATCH] usb: core: hub: Re-guess ep0 maxpacket size on reset Pengfei Lv
2026-11-04 12:57 ` sashiko-bot
2026-11-04 14:59 ` Alan Stern
2026-11-04 17:36 ` Michal Pecio
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox