Skip to content

zephyr-cp: release builds lose USB during BLE activity — the UDC thread stack fix in #5 is debug-only #18

Description

@tyeth

Symptom

On a release build, the board disappears from USB partway through a _bleio scan: the CDC port (COM51) and the CIRCUITPY mass-storage volume both vanish mid-run. On reset Windows reports a USB device/stack error.

Recovered with an SWD reset run — no power cycle needed (unlike the flash-XIP hang in #15).

Observed 2026-09-09 on a Pico 2 W, raspberrypi_rpi_pico2_w_zephyr, 10.3.0-51-g8031983fc9, running a 15 s adapter.start_scan().

Cause

CONFIG_UDC_RPI_PICO_STACK_SIZE is 512 in release builds — Zephyr's driver default (drivers/usb/udc/Kconfig.rpi_pico).

The 2048-byte fix from #5 was added to debug.conf only — that PR's own title scopes it to debug builds — so every release image still ran the 512-byte stack. The stack overflow we originally diagnosed as usbd@50110000 was therefore never fixed for the images we actually flash, which also retroactively explains the "USB came up badly" resets during the original Pico 2 W bring-up.

Fix

Move it out of debug.conf and into prj.conf so all builds get it:

CONFIG_UDC_RPI_PICO_STACK_SIZE=2048

Cost: +1,536 B RAM (with CONFIG_BT_MAX_CONN=4 alongside it, 231,112 → 236,864 B, 43.40% → 44.48%).

Verified on hardware: with the fix, a full 12 s active scan producing 925 advertisement reports from 20 distinct devices completed with USB still enumerated — CDC and the CIRCUITPY volume both intact. The same workload took the board off the bus before.

Residual, separate, cause not established

With USB surviving, a second symptom remains: execution stalls on the first statement after the scan window closes, for minutes, until Ctrl-C.

  • run 1 — interrupted at a.stop_scan()
  • run 2 — interrupted at the print() immediately after the scan loop; its output never arrived

Both times Ctrl-C was delivered and honoured, and the REPL was immediately healthy afterwards (print('ALIVE', 6*7) → ALIVE 42). So the core is not faulted and BLE RX works throughout — only the post-scan transition stalls.

Candidates not ruled out: CDC writes blocking when the host stops draining the port, or scan teardown blocking in the HCI driver. Filing separately rather than guessing, since it does not share the stack-size cause and is not fixed by it.

🤖 Generated with Claude Code

Activity

  1. tyeth commented on Sep 9, 2026

    @tyeth
    OwnerAuthor

    What gdb shows about the "post-scan stall": nothing is stalled — the scan timeout is ignored

    Reproduced on 10.3.0-51-g8031983fc9 (ELF verified against flash with compare-sections), then halted the core 9 s into a start_scan(timeout=3) loop and dumped every thread:

    main thread common_hal_bleio_scanresults_next() at shared-module/_bleio/ScanResults.c:27 — the wait loop, done = false, ring buffer used = 0
    bt_dev.flags 0x35 = ENABLE | READY | HAS_PUB_KEY | SCANNING
    bt_dev.ncmd_sem count = 1; sent_cmd = 0x0 — no HCI command outstanding
    cyw43_bus_mutex owner = 0x0, lock_count = 0 — gSPI bus free
    bt_poll_thread state 0x04, PC in z_tick_sleep — its normal 4 ms poll
    all work_queue_main, usbd_thread, udc_rpi_pico_thread_0, tc_rx_handler, airoc_event_task, bt_tx_processor pended (0x02)

    Neither candidate holds: the CDC TX path is idle, and the HCI driver is idle. stop_scan() called explicitly takes 5–6 ms.

    Cause: Zephyr's start_le_scan_legacy() never reads bt_le_scan_param.timeout — only the extended-scan path passes a duration to the controller — and CONFIG_BT_EXT_ADV=n (required: the CYW43439 has no extended advertising) selects the legacy path. So timeout= was silently dropped; a timeout=3 scan was still yielding at 57 s / 1600 reports. scanresults_next() returns None when a Ctrl-C is pending, so the loop exits cleanly and the KeyboardInterrupt lands on the next statement — the stop_scan() / print() in the two runs here. The "12 s scan, 925 reports" was ~33 s at the observed ~28 reports/s.

    Fix: #20 enforces the deadline from the main thread's background task. Measured after: timeout=3 → 3.0 s, 1 → 1.0 s, 0.5 → 0.53 s, 2 → 2.05 s.

    Two other things seen while at it (not the stall)

    • Any SWD halt kills USB CDC on this board — even a ~0.5 s gdb attach + bt. Windows keeps a stale COM entry; the fix is monitor reset run. gdb evidence therefore has to be one scripted batch per reset.
    • udc_rpi_pico logs <err> Endpoint 0x83 busy in bursts of >1400 (dropped) lines whenever CDC transmits (the class enqueues while a transfer is in flight — the driver's XFER_NEW handler treats that as an error). Harmless with deferred logging; with LOG_MODE_IMMEDIATE it would block the UDC thread for seconds — plausibly the original debug-build "USB loss" amplifier.

    🤖 Generated with Claude Code

  2. tyeth commented on Sep 10, 2026

    @tyeth
    OwnerAuthor

    Resolved. Both halves of this issue are now accounted for.

    The USB loss is fixed by #19, which moves CONFIG_UDC_RPI_PICO_STACK_SIZE=2048 out of debug.conf and into prj.conf so release images stop running Zephyr's 512-byte default. Verified on hardware: a sustained active scan completes with CDC and the CIRCUITPY volume both intact, where the same workload previously took the board off the USB bus entirely.

    The "residual, cause not established" post-scan stall was not a stall. Zephyr's start_le_scan_legacy() never reads bt_le_scan_param.timeout, and CONFIG_BT_EXT_ADV=n (forced — the CYW43439 has no extended advertising) selects that path, so scans never ended. scanresults_next() returns None when a Ctrl-C is pending, so the loop exited cleanly and the KeyboardInterrupt landed on whatever statement followed — which is exactly the two tracebacks recorded here. Fixed by #20; the full gdb evidence is in #25.

    That also corrects the scan figures quoted in this thread: the run described as "12 s" was ~33 s at ~28 reports/s, because it was never being stopped by the timeout.

    Closing. The remaining udc_rpi_pico annoyance found alongside this — <err> Endpoint 0x83 busy bursts during normal CDC transmission — is tracked separately for upstreaming.

    🤖 Generated with Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions