ricardgb opened a new issue, #19434:
URL: https://github.com/apache/nuttx/issues/19434

   ## Description
   
   On RP2040 and RP2350, a USB device (tested: composite CDC-ACM + CDC-NCM + 
USBMSC) frequently fails to enumerate when the board is **cold-plugged** 
(physical unplug → replug). The host sees *nothing* — no enumeration attempt in 
the host kernel log at all — while NuttX itself boots and runs normally 
underneath. Warm resets (reflash-reboot, `reboot` from NSH) enumerate reliably, 
which makes the failure look like an intermittent early-boot hang and sends 
debugging in the wrong direction (we chased flash/littlefs, WiFi cold-init, and 
image size for days).
   
   Root cause is in `usbdev_register()` in both 
`arch/arm/src/rp2040/rp2040_usbdev.c` and `arch/arm/src/rp23xx/rp23xx_usbdev.c`:
   
   1. `CLASS_BIND(driver, ...)` is called first. For composite (and cdcacm), 
the bind ends with `DEV_CONNECT(dev)` — i.e. `..._pullup(dev, true)`, which 
sets `SIE_CTRL.PULLUP_EN`.
   2. A few lines later, `usbdev_register()` does a **wholesale write** to the 
same register:
   
   ```c
     /* Enable interrupt */
   
     putreg32(RP2040_USBCTRL_REGS_SIE_CTRL_EP0_INT_1BUF,
              RP2040_USBCTRL_REGS_SIE_CTRL);
   ```
   
   (rp2040_usbdev.c:2117, rp23xx_usbdev.c:2108 on current master)
   
   This **clobbers `PULLUP_EN` microseconds after the class asserted it**.
   
   Enumeration still *usually* works, but only by accident: if the host port 
latched the microsecond pull-up blip, it issues a bus reset; the `BUS_RESET` 
handler calls `CLASS_DISCONNECT`, `composite_disconnect()` ends with 
`DEV_CONNECT`, and *that* pull-up sticks (nothing clobbers it any more). A 
warm, already-active host port catches the blip. A **cold-plugged port is still 
in attach/debounce and misses it — no bus reset ever arrives, `PULLUP_EN` stays 
0 forever**, and the device is invisible while the OS runs fine.
   
   ## Evidence (RP2350, Raspberry Pi Pico 2 W, silicon)
   
   Instrumented `rp23xx_pullup()`, the `BUS_RESET` handler, and the `SIE_CTRL` 
write with console breadcrumbs (console on UART0, captured through a second 
Pico). A **warm** boot shows the accidental rescue in action:
   
   ```
   [PULLUP=1]        <- composite_bind ends with DEV_CONNECT
   [REG:SIE_STRIP]   <- usbdev_register putreg32 clobbers PULLUP_EN
   [BUSRESET]        <- host caught the microsecond blip, resets the port
   [PULLUP=0]        <- busreset handler
   [PULLUP=1]        <- composite_disconnect -> DEV_CONNECT re-arms; sticks
   ...enumerates normally
   ```
   
   A **cold-plugged** failing boot was inspected live over the serial console: 
NSH running, all USB class drivers registered (`/dev/ttyACM0` present, network 
app had configured the CDC-NCM interface), and:
   
   - `SIE_CTRL = 0x20000000` (`EP0_INT_1BUF` only — `PULLUP_EN` = 0)
   - `SIE_STATUS = 0x00000001` (VBUS detected, line state SE0)
   - `INTR = 0` (quiescent — no bus reset ever arrived)
   
   i.e. software believes it is connected; the pull-up was never (re-)asserted.
   
   Reproduction statistics on one board, one port, one cable, same day (10 
cold-plug cycles each, success = host logs an enumeration):
   
   | image | cold-plug enumerations |
   |---|---|
   | 1.5 MB image (unmodified driver) | **0 / 10** |
   | 1.0 MB image (unmodified driver) | 10 / 10 |
   | the same 1.5 MB image, driver fix below | **10 / 10** (8/8 within the 
logged dmesg window) |
   
   The image-size dependence is only boot-timing: it deterministically shifts 
*when* the pull-up blip happens relative to the host port's attach-debounce 
window, which is why this bug can masquerade as almost anything.
   
   ## Suggested fix
   
   Preserve the pull-up state — set only the intended bit instead of 
overwriting the register:
   
   ```c
     setbits_reg32(RP2040_USBCTRL_REGS_SIE_CTRL_EP0_INT_1BUF,
                   RP2040_USBCTRL_REGS_SIE_CTRL);
   ```
   
   (and the same in `rp23xx_usbdev.c`). Validated on silicon (RP2350): the 0/10 
image above went to 10/10 cold-plug enumerations with only this change; 
instrumentation confirms the pull-up now survives registration and enumeration 
no longer depends on the bus-reset rescue.
   
   A more thorough variant would be to bring the controller fully up (`PHY_ISO` 
clear on RP2350, `CONTROLLER_EN`, `SIE_CTRL`, `INTE`) *before* `CLASS_BIND`, so 
the bind-time `DEV_CONNECT` lands on a live controller — at bind time the 
controller is currently still disabled (and the PHY still isolated on RP2350). 
The one-line `setbits` change is the minimal fix and is what was tested.
   
   Note this may also explain historical intermittent USB flakiness on these 
ports (e.g. devices that "sometimes don't appear" after plugging, RNDIS/NCM 
bring-up hangs), since every successful enumeration currently depends on the 
host catching a microsecond pull-up glitch.
   
   ## Environment
   
   - NuttX master (bug present at `rp2040_usbdev.c:2117` / 
`rp23xx_usbdev.c:2108` as of 2026-07-14); observed on a recent-master board 
branch
   - Board: Raspberry Pi Pico 2 W (RP2350), composite USBDEV (CDC-ACM console + 
CDC-NCM + USBMSC), bus-powered
   - Host: Linux 6.17 xhci
   
   ## Disclosure
   
   This investigation, root-cause analysis, and the tested fix were performed 
by an AI agent (Claude Code), operated and directed by the submitter, and the 
results were reviewed by the submitter before posting. Happy to provide the 
full breadcrumb traces or test any proposed alternative fix on hardware.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to