You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
usbh: 10 ms reset recovery + hcd fail-fast + no enumeration retry leaves devices behind a hub permanently absent #3876
With three devices attached at power-up, only the first enumerates; the others are absent until physically replugged. No panic, no assert, host stack idle and healthy afterwards.
A timing-neutral in-RAM event log of the hcd (transfer starts, buffer completions, interrupts — added because any CFG_TUSB_DEBUG logging masks the fault completely by changing timing) shows the mechanism:
the hub finishes the second device's PORT_RESET; ENUM_RESET_RECOVERY_DELAY_MS = 10 later, the first SETUP goes to address 0;
the device is not answering yet (10 ms is the USB 2.0 minimum reset recovery; these devices want more);
the controller's automatic retries raise ERROR_RX_TIMEOUT every ~17 µs;
the rp2 hcd stops and fails the transfer on the first timeout;
usbh abandons the enumeration — and there is no retry path for a failed enumeration, so the device stays absent;
the next device's enumeration then met it half-configured and drew a STALL on the 8-byte descriptor read — also abandoned.
Under 0.20 the same devices enumerate: its RX-timeout handler did nothing and the controller retried until the device answered. That also explains why every debug build "fixes" this — the logging supplies the recovery time by accident, which is worth knowing when triaging similar reports.
and/or retry a failed address-0 GET_DESCRIPTOR with backoff (Linux does);
and/or tolerate RX timeouts in the rp2 hcd for a bounded window before failing. Caution from our attempts: naively not stopping a timed-out transfer on RP2350 and letting timeouts accumulate hung the controller — the stop-and-fail path needs to remain as the backstop.
Hardware is available for testing candidate fixes; the full investigation and measurements are in ports/rp2/boards/PICO_COMPUTER_3/DEVELOPMENT_NOTES.md (§73) on the branch above.
#3862 (430fcd2) adds the missing enumeration retry: a device that fails an enumeration control transfer is torn down and re-enumerated from address 0 after a fresh port reset (CFG_TUH_ENUM_ATTEMPT_MAX, default 2), and a control transfer that never completes now times out after CFG_TUH_CONTROL_TIMEOUT_MS (5 s). The 10 ms reset recovery delay is unchanged.
Could you test current master on your RP2350 + hub setup and report whether all devices mount?
Thanks. Tested master at e42fa93 (which includes #3862) on the same board: a Pico Computer 3 (RP2350B, native controller in host mode) with a keyboard, mouse, gamepad and USB flash drive behind its on-board hub, built into our PicoMite (MMBasic) firmware rather than MicroPython.
The retry replaces all of our usbh.c workarounds, but on its own it is not enough: we still need a bounded RX-timeout hold-off in hcd_rp2040.c.
Build
Result
master + our other RP2 driver fixes (including #3874 and #3875), stock usbh.c, no hold-off
the gamepad and flash drive mounted; the keyboard and mouse did not, although the hub had powered all four ports
the same + the hold-off below
all four mounted every time: 5 software restarts, 4 reset-button resets, 4 power-ons and hot-plugging on one firmware variant, and 4 of each reset type on three more variants
With the hold-off and master's retry, the three usbh.c changes we carried on 0.21 are no longer needed, and we have dropped them: the 100 ms reset recovery, disabling the hub port of an abandoned enumeration, and holding application control transfers back while a device enumerates.
The hold-off restores 0.20's behaviour for endpoint 0 only, for a bounded time. For 1 s from the first EP0 RX_TIMEOUT, the timeout is cleared and the transfer is left armed, so the SIE keeps retrying. After that, the existing STOP_TRANS + XFER_RESULT_FAILED path runs as before; it has to stay as the backstop (see the caution in the original report). Only an endpoint-0 completion closes the window: closing it on any completion let hub status and HID traffic re-arm it for ever. Bulk and interrupt endpoints keep failing fast, since a slow device NAKs there rather than timing out.
#defineCTRL_RX_TIMEOUT_GRACE_US (1000u * 1000u)
staticboolctrl_rx_timeout_timing= false;
staticuint32_tctrl_rx_timeout_start_us;
// xfer_complete_isr(): an endpoint-0 completion closes the windowif (tu_edpt_number(ep->ep_addr) ==0) ctrl_rx_timeout_timing= false;
// hcd_rp2040_irq()if (status&USB_INTS_ERROR_RX_TIMEOUT_BITS) {
usb_hw_clear->sie_status=USB_SIE_STATUS_RX_TIMEOUT_BITS;
// within the window, leave the transfer armed and let the SIE retry (0.20 behaviour);// no STOP_TRANS, which would cancel the retryboolin_grace= false;
if (epx->state==EPSTATE_ACTIVE&&tu_edpt_number(epx->ep_addr) ==0) {
constuint32_tnow=time_us_32();
if (!ctrl_rx_timeout_timing) {
ctrl_rx_timeout_timing= true;
ctrl_rx_timeout_start_us=now;
}
in_grace= (uint32_t)(now-ctrl_rx_timeout_start_us) <CTRL_RX_TIMEOUT_GRACE_US;
}
if (!in_grace) {
// existing STOP_TRANS + XFER_RESULT_FAILED path, unchanged// (with usbh_dpram->epx_buf_ctrl = 0 before failing, #3874)
}
}
We haven't instrumented master to see why the retry alone misses the keyboard and mouse. A likely reason is that the retry's fresh port reset is followed by the same 10 ms recovery, so a device that is slow after a reset is just as mute on the second attempt. A longer recovery delay on the retry might do the same job as the hold-off; we haven't tried that.
#3874 and #3875 are still needed on master; we carry fixes for both.
A follow-up to the hold-off above, now that #4166 (e204823) implements hcd_edpt_abort_xfer() and retires EPX in hcd_device_close(). Those end a control transfer without going through xfer_complete_isr(), so the window has to close there too. Otherwise a window opened by the aborted transfer stays open with its old start time, and the next endpoint-0 transfer, usually the enumeration retry after the teardown, finds it already expired and fails on its first timeout. In epx_retire(), inside the ep == epx && ep->state == EPSTATE_ACTIVE branch:
if (tu_edpt_number(ep->ep_addr) ==0) ctrl_rx_timeout_timing= false; // an aborted/closed control transfer ends the window
We have this on e204823 with the rest of our RP2 fixes rebased onto it. On the same Pico Computer 3 (four devices behind the hub) all four enumerate on every one of 5 CPU restarts, 4 reset-button resets and 4 power-ons, and flash-drive hot-plug is clean 5/5. We have not seen the abort path fire in those tests; the change follows from the code.
Activity
hathach commentedon Oct 5, 2026
#3862 (430fcd2) adds the missing enumeration retry: a device that fails an enumeration control transfer is torn down and re-enumerated from address 0 after a fresh port reset (
CFG_TUH_ENUM_ATTEMPT_MAX, default 2), and a control transfer that never completes now times out afterCFG_TUH_CONTROL_TIMEOUT_MS(5 s). The 10 ms reset recovery delay is unchanged.Could you test current master on your RP2350 + hub setup and report whether all devices mount?
UKTailwind commentedon Oct 7, 2026
Thanks. Tested master at e42fa93 (which includes #3862) on the same board: a Pico Computer 3 (RP2350B, native controller in host mode) with a keyboard, mouse, gamepad and USB flash drive behind its on-board hub, built into our PicoMite (MMBasic) firmware rather than MicroPython.
The retry replaces all of our
usbh.cworkarounds, but on its own it is not enough: we still need a bounded RX-timeout hold-off inhcd_rp2040.c.usbh.c, no hold-offWith the hold-off and master's retry, the three
usbh.cchanges we carried on 0.21 are no longer needed, and we have dropped them: the 100 ms reset recovery, disabling the hub port of an abandoned enumeration, and holding application control transfers back while a device enumerates.The hold-off restores 0.20's behaviour for endpoint 0 only, for a bounded time. For 1 s from the first EP0
RX_TIMEOUT, the timeout is cleared and the transfer is left armed, so the SIE keeps retrying. After that, the existingSTOP_TRANS+XFER_RESULT_FAILEDpath runs as before; it has to stay as the backstop (see the caution in the original report). Only an endpoint-0 completion closes the window: closing it on any completion let hub status and HID traffic re-arm it for ever. Bulk and interrupt endpoints keep failing fast, since a slow device NAKs there rather than timing out.We haven't instrumented master to see why the retry alone misses the keyboard and mouse. A likely reason is that the retry's fresh port reset is followed by the same 10 ms recovery, so a device that is slow after a reset is just as mute on the second attempt. A longer recovery delay on the retry might do the same job as the hold-off; we haven't tried that.
#3874 and #3875 are still needed on master; we carry fixes for both.
UKTailwind commentedon Oct 8, 2026
A follow-up to the hold-off above, now that #4166 (e204823) implements
hcd_edpt_abort_xfer()and retires EPX inhcd_device_close(). Those end a control transfer without going throughxfer_complete_isr(), so the window has to close there too. Otherwise a window opened by the aborted transfer stays open with its old start time, and the next endpoint-0 transfer, usually the enumeration retry after the teardown, finds it already expired and fails on its first timeout. Inepx_retire(), inside theep == epx && ep->state == EPSTATE_ACTIVEbranch:We have this on e204823 with the rest of our RP2 fixes rebased onto it. On the same Pico Computer 3 (four devices behind the hub) all four enumerate on every one of 5 CPU restarts, 4 reset-button resets and 4 power-ons, and flash-drive hot-plug is clean 5/5. We have not seen the abort path fire in those tests; the change follows from the code.
usbh: resend a failed enumeration request before resetting the port
HiFiPhile commentedon Nov 12, 2026
@UKTailwind could you try #4484 ?