Files
MeshCore/docs/nrf52-usb-ready-fix.md
T

47 lines
2.8 KiB
Markdown

# nRF52 USB READY hang
The RAK3401 on Mercer was found stuck in the framework's TinyUSB power handler:
USBD was enabled and attached, but its write-one-to-clear READY event was
already consumed. The duplicate-event guard also required HFCLK to be running.
If that guard missed the completed initialization, the following unbounded
READY wait could stop application startup. A bootloader update alone cannot
replace this USB driver in an already installed MeshCore application.
The wait and guard were inherited through the pinned Adafruit nRF52 Arduino
framework, not introduced by the recent OTAFIX upstream merge. TinyUSB's wait
dates back at least to 2019; the guard was added in
[upstream commit 7d9efd0697](https://github.com/hathach/tinyusb/commit/7d9efd06979f6cdde2a4093f0c26e8100312c92c).
OTAFIX's August 24 HFCLK backport addressed a later clock wait, leaving this
earlier READY wait uncovered. The exact live interrupt ordering is not known;
the stalled program counter and consumed event were read directly over SWD.
All nRF52 environments inherit `pre:scripts/nrf52_usb_power_fix.py`. It compiles
a build-local replacement for the matching framework driver, without changing
PlatformIO's shared SDK or affecting ESP32 builds. A changed/unrecognized
framework implementation stops the build for review. The backport:
- requests HFCLK before checking the consumed READY event;
- rechecks attachment while waiting, including completion by another callback;
- bounds both waits with one 100,000-poll budget, independent of OS ticks;
- ignores stale READY on a disabled peripheral and exits if it is removed.
- replays an already asserted regulator READY state when the application USB
task starts with VBUS present, as the bootloader does. The framework's
original startup replayed DETECTED only, so a missing later power edge could
leave the new application's USB transport unattached after a UF2 handoff.
The poll budget is an iteration limit, not a promised elapsed-time timeout.
If the clock/peripheral never becomes ready, the caller returns instead of
freezing the application. A later power event can retry; permanent electrical
failure can still require reconnecting USB. This does not establish or fix the
separate Pi hub/controller fault.
Run `python3 -B test/test_nrf52_usb_power.py -v`. The tests compile the actual
patched handler from the pinned-framework fixture, model W1C event semantics,
and cover duplicate/nested callbacks, delayed/missing clocks and READY, removal,
retry, and detached USB. Restoring the inherited READY prefix must reproduce
the infinite wait under a subprocess deadline. Additional tests check SDK
isolation, idempotence, fail-closed patching, the startup DETECTED/READY order,
and all nRF52 environment hooks.
The harness is shared with OTAFIX's TinyUSB fork at
`test/otafix/nrf5x_power_test.py`; keep the two copies in sync when extending it.