Description
On RP2350 (Cortex-M33), the USB device driver (arch/arm/src/rp23xx/rp23xx_usbdev.c)
re-arms an endpoint by writing the buffer-control AVAILABLE bit without a memory
barrier ordering it after the preceding BUFF_STATUS clear. These are two writes to two
different peripheral regions:
- the
BUFF_STATUS clear (done in rp23xx_usbintr_buffstat when handling a completion)
targets the USB controller register block at 0x50110000 (BUFF_STATUS @ +0x58);
- setting AVAILABLE in the endpoint buffer-control word targets USB DPSRAM at
0x50100000.
On the Cortex-M33 bus fabric, writes to different Device regions may be observed out of
order unless separated by a DMB. In a non-SMP build (CONFIG_SMP unset)
spin_lock_irqsave compiles to a barrier-free up_irq_save, so nothing orders these two
stores.
The race (lost IN completion)
When re-arming the next IN buffer directly from a completion handler under back-to-back TX:
- AVAILABLE (DPSRAM) is observed by the controller before the
BUFF_STATUS clear
(register block) lands.
- The controller transmits the next IN packet and latches its completion by setting the
BUFF_STATUS bit.
- The late-arriving clear then wipes that just-set bit.
The completion edge is lost: the IN endpoint never raises another buffer interrupt, the
class wrcomplete callback never runs, and (for CDC-NCM) wrreq_idle is never reposted, so
transmit wedges permanently. The controller-register note in the header even warns:
"clearing the buffer status bit may instantly re-set it on the next clock cycle."
Steps to reproduce
- RP2350 (Raspberry Pi Pico 2 W), CDC-NCM composite acting as a USB-NIC.
- Enable
CONFIG_NET_TCP_WRITE_BUFFERS=y so TCP transmit is bursty (a single txavail
poll drains many segments back-to-back), keeping the IN pipe continuously full.
- Drive sustained/concurrent HTTP downloads.
Actual: transmit wedges intermittently (worse with denser TX). The core is idle
(up_idle), no fault, wrreq_idle never reposted. Over SWD in the wedged state:
BUFF_STATUS = 0, USB INTS = 0, the bulk-IN buffer-control word shows the buffer
prepared but AVAILABLE clear (e.g. 0x0000_2024 = PID + length, no AVAILABLE), i.e. a
lost completion edge — hardware quiescent while the driver waits forever.
Expected: sustained TX never wedges.
Why RP2040 is unaffected
The identical logic runs on RP2040 (Cortex-M0+), whose far stricter/simpler store ordering
does not exhibit this reorder — so this is RP2350/Cortex-M33-specific.
Proposed fix
Insert a UP_DMB() (and #include <arch/barriers.h>) immediately before setting the
AVAILABLE bit on the re-arm path, so every preceding controller-register write (the
BUFF_STATUS clear) is ordered ahead of the DPSRAM AVAILABLE store.
This was root-caused and fixed on an RP2350 (Pico 2 W) CDC-NCM USB-NIC: with the barrier,
a soak of 144 dense/concurrent HTTP downloads at ~486 KB/s ran with zero wedges (previously
transmit hung within a handful of requests). A patch is available; I can open a PR against
whichever revision of the RP2350 IN-endpoint re-arm path is preferred (the exact call site
depends on recent driver refactors — refs the USB endpoint work in #19378/#19450/#19451 and
the CDC-NCM TX fixes in #19469/#19470).
Environment
- Chip: RP2350 (Cortex-M33), non-SMP.
- Board: Raspberry Pi Pico 2 W.
- NuttX: current
master.
Disclosure: this root-cause analysis was performed with the assistance of an AI agent
(Anthropic's Claude, via Claude Code), operated and directed by the submitter, and validated
on-hardware before filing.
Description
On RP2350 (Cortex-M33), the USB device driver (
arch/arm/src/rp23xx/rp23xx_usbdev.c)re-arms an endpoint by writing the buffer-control AVAILABLE bit without a memory
barrier ordering it after the preceding
BUFF_STATUSclear. These are two writes to twodifferent peripheral regions:
BUFF_STATUSclear (done inrp23xx_usbintr_buffstatwhen handling a completion)targets the USB controller register block at
0x50110000(BUFF_STATUS@+0x58);0x50100000.On the Cortex-M33 bus fabric, writes to different Device regions may be observed out of
order unless separated by a
DMB. In a non-SMP build (CONFIG_SMPunset)spin_lock_irqsavecompiles to a barrier-freeup_irq_save, so nothing orders these twostores.
The race (lost IN completion)
When re-arming the next IN buffer directly from a completion handler under back-to-back TX:
BUFF_STATUSclear(register block) lands.
BUFF_STATUSbit.The completion edge is lost: the IN endpoint never raises another buffer interrupt, the
class
wrcompletecallback never runs, and (for CDC-NCM)wrreq_idleis never reposted, sotransmit wedges permanently. The controller-register note in the header even warns:
"clearing the buffer status bit may instantly re-set it on the next clock cycle."
Steps to reproduce
CONFIG_NET_TCP_WRITE_BUFFERS=yso TCP transmit is bursty (a singletxavailpoll drains many segments back-to-back), keeping the IN pipe continuously full.
Actual: transmit wedges intermittently (worse with denser TX). The core is idle
(
up_idle), no fault,wrreq_idlenever reposted. Over SWD in the wedged state:BUFF_STATUS = 0, USBINTS = 0, the bulk-IN buffer-control word shows the bufferprepared but AVAILABLE clear (e.g.
0x0000_2024= PID + length, no AVAILABLE), i.e. alost completion edge — hardware quiescent while the driver waits forever.
Expected: sustained TX never wedges.
Why RP2040 is unaffected
The identical logic runs on RP2040 (Cortex-M0+), whose far stricter/simpler store ordering
does not exhibit this reorder — so this is RP2350/Cortex-M33-specific.
Proposed fix
Insert a
UP_DMB()(and#include <arch/barriers.h>) immediately before setting theAVAILABLE bit on the re-arm path, so every preceding controller-register write (the
BUFF_STATUSclear) is ordered ahead of the DPSRAM AVAILABLE store.This was root-caused and fixed on an RP2350 (Pico 2 W) CDC-NCM USB-NIC: with the barrier,
a soak of 144 dense/concurrent HTTP downloads at ~486 KB/s ran with zero wedges (previously
transmit hung within a handful of requests). A patch is available; I can open a PR against
whichever revision of the RP2350 IN-endpoint re-arm path is preferred (the exact call site
depends on recent driver refactors — refs the USB endpoint work in #19378/#19450/#19451 and
the CDC-NCM TX fixes in #19469/#19470).
Environment
master.Disclosure: this root-cause analysis was performed with the assistance of an AI agent
(Anthropic's Claude, via Claude Code), operated and directed by the submitter, and validated
on-hardware before filing.