Skip to content

zephyr-cp Pico W: closing CDC console can stall MSC reads until reopen #42

Open

Description

@tyeth-ai-assisted

Closing Pico W's USB serial console can stall a read-only MSC command until the console is reopened. This is physically reproduced on the older CircuitPython 9e153ad624 integration and is also observed during attempted rollout of 6839890534 / Zephyr c6200e80ea. It is not established as a regression introduced by the two new USB reconfiguration fixes.

Both builds are based on official 11.0.0-alpha.2. Device: raspberrypi_rpi_pico_w_zephyr, RP2040, direct Linux xHCI USB connection. This reproduction does not use the S31/USB-IP bridge. Pico 2 and other UDC implementations have not been qualified for this trigger.

Matched physical controls on 9e153ad

All reads are the same uncached 4 KiB range, with identical hashes and no device filesystem writes or firmware changes.

Host console state Read time
Open about 31 ms
Handle open, DTR deasserted about 31 ms; also 60 ms in the repeat
Closed, reopen scheduled after 2 s 2.068 s
Closed, reopen scheduled after 4 s 4.066 s
Open after reconnect about 30–53 ms
Closed, reopen scheduled after 1 s 30 ms; this attempt did not stall

The trigger is intermittent; do not claim every close stalls. A separate sleep / CPU-busy / sleep control completed the same reads in 31 / 32 / 60 ms while Python executed an eight-second workload. That control does not support changing thread priority as a fix.

Candidate rollout and USB trace

Two actual 6839890534 attempts answered CDC but timed out after 10 seconds in blockdev --getsize64 before any repeated SET_CONFIGURATION test. The second attempt captured 15,762 USBmon events with zero reported drops or packet truncation. The candidate runtime's delayed TEST UNIT READY command completed successfully after 10.126 seconds, as recovery opened the console. All captured MSC command wrappers have matching status wrappers; this is host-request evidence, not an electrical bus trace.

The first automatic restore was blocked before writing by a timed-out usb-storage unbind. A separate guarded recovery requested ROM through CDC without waiting for that unbind and restored the original code with whole-image verification and three radio checks. The traced attempt used that recovery path and also restored successfully. Both production Picos remain on 9e153ad624; the new USB build is not retained in production.

Minimal reproduction

  1. On a Pico W running the revision above, use a verified, unmounted CIRCUITPY block device and its matching serial device. Keep HID/USB configuration unchanged.
  2. Open the console, enter/leave raw REPL and confirm the firmware banner. With the serial handle open, time dd if=/dev/<DUT-disk> bs=4096 count=1 iflag=direct status=none and retain the hash.
  3. Keep the handle open but deassert DTR. Repeat the same read, then restore DTR. This is the negative control.
  4. Close the serial handle. Schedule a fresh open at the same baud (115200, not the 1200-baud bootloader trigger) after two or four seconds. Immediately issue the identical uncached read with a bounded host timeout. Compare its completion time with the scheduled reopen.
  5. Repeat open-console controls and compare exact read hashes. Preserve the non-stalling close attempts too. Restore the original host bindings.

Do not infer a flash/storage data-corruption defect from this delay. Root cause and whether the owner is CircuitPython, Zephyr UDC/class code, or host interaction remain open. The existing Zephyr SET_CONFIGURATION and cancelled-OUT reports (#121843 and #122768) and CircuitPython adafruit#11531 are related USB work, but equivalence to this trigger is not established. See fork integration PR #32 for the existing USB fixes; this report is an additional rollout blocker.

Activity

  1. tyeth-ai-assisted commented on Oct 23, 2026

    @tyeth-ai-assisted
    CollaboratorAuthor

    The sanitized report and machine-readable timings are now committed in Collector draft #37: report and minimal reproduction, timings and capture hash.

    All flash backups and the full USBmon capture remain private. Production firmware and host bindings are restored. Source review has not yet isolated the wait; the UDC's lower thread priority alone is not a demonstrated cause, and no speculative priority change has been made.

  2. tyeth-ai-assisted commented on Oct 23, 2026

    @tyeth-ai-assisted
    CollaboratorAuthor

    Further matched controls narrow this to USB progress while Python continues running; root cause remains open.

    • Runtime autosuspend does not explain these reproductions: the Pico and parent hubs already had power policy on, all supported sampled ancestors remained active, and suspended time stayed zero. Three closed-console reads still waited about 3.074 seconds for reopening.
    • Device GET_STATUS succeeds with the console open but times out in two reproduced closed-console stalls; a third attempt passes. The corresponding disk reads wait 4.066 seconds. Direct interface line-state requests were rejected by Linux with EBUSY, so that comparison is inconclusive.
    • Two closed-console busy-loop runs have disk stalls of 3.133/3.067 seconds while the largest gaps between Python iterations are only 8.06/7.81 milliseconds. A sleeping-loop reproduction has the same separation. This does not support a whole-MCU freeze or a REPL-only fault.
    • After leaving the console closed, Linux also logged USB reset/descriptor timeouts and subsequently performed its own port power cycle/re-enumeration. Failed probes and recovery evidence are retained.

    All successful reads have the same hash. No firmware or filesystem writes were made; completed comparisons restored bindings/power settings and left the bridge unchanged. Diagnostic completion is not a firmware-fix pass. Both production Picos remain on the prior image; rollout stays blocked.

    Exact sanitized comparisons and limits, timing data.

  3. tyeth-ai-assisted commented on Oct 23, 2026

    @tyeth-ai-assisted
    CollaboratorAuthor

    A temporary passive RAM diagnostic reproduces two closed-console disk stalls (3.126 and 3.067 seconds); open controls take 0.031/0.060 seconds and one closed round is fast. Twenty snapshots per round were captured about 300 ms apart and retrieved only after reopening.

    At sampled points during both stalls, the UDC mutex is unowned, driver event/completion/interrupt bits are zero, and UDC/USBD/MSC workers wait on their normal queues. The USB SOF counter continues advancing. MSC OUT 0x02 has an available receive buffer, no halt and no received bytes. Exact-ELF symbol resolution distinguishes the separate HID buffer. Actual runtime priorities are UDC/USBD -8, MSC -1 and Python main 14; configured UDC value 8 is cooperative, not preemptive priority 8.

    This does not show a UDC mutex deadlock, stranded completion or whole-MCU freeze. It does not establish host/hub versus device protocol responsibility: snapshots can miss transitions, and USBmon is not an electrical packet capture. No fix or erratum attribution is claimed.

    The diagnostic used CP 683989 + Zephyr c6200e80; original 9e153 code was restored with complete expected-flash verification and three successful Wi-Fi/BLE checks. The full restored image differs from the pretest backup in runtime version metadata. Source edits and USB bindings were restored; the bridge was untouched. Production rollout remains blocked.

    Report and limits, sanitized measurements, instrumentation and reproducer.

    Follow-up on original 9e153: unbinding only the host HID driver (CDC/MSC still bound) does not prevent the stall. Closed reads before unbinding: 3.128/0.034/3.068 s; while unbound: 3.068/0.058/3.140 s; after rebinding: 0.031/0.035/0.060 s. All eleven reads, including open controls, match; Python iteration gaps stay below 18 ms. Original bindings were restored, with no firmware/filesystem writes or bridge changes. A bound HID driver is not necessary, but three attempts per phase cannot establish rates or a rebinding fix. Full matched control and limits.

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