tools: add core1 flash-call checker for rp2 USB host builds#11115
tools: add core1 flash-call checker for rp2 USB host builds#11115mikeysklar wants to merge 6 commits into
Conversation
|
Let's wait to merge this until all the bugs this detects are fixed. Then you can add the CI check as part of this PR. |
|
Sounds good. I'll take a look at the mutex path |
|
Very cool thank you! I've vibed up changes to LLVM a bit ago for the compiler to handle this. The current mechanisms are clearly insufficient. Thanks for the reminder that we need this. |
When core1 posts a USB event while core0 holds the TinyUSB queue mutex, core1 falls into the pico-sdk blocking wait, which lived in flash. Core1 cannot execute from flash, so a contended moment hard faults core1 and USB host goes silent. Fixes adafruit#11116. On USB host builds only, --wrap best_effort_wfe_or_timeout and time_us_64 with RAM-resident implementations in usb_host/Port.c. The best_effort replacement mirrors the SDK's own documented PICO_TIME_DEFAULT_ALARM_POOL_DISABLED fallback (poll the clock, allow early return), so no alarm pool machinery is pulled into RAM. The time_us_64 replacement keeps the SDK's hi/lo/hi rollover re-read loop. Also RAM-place pico_int64_ops_aeabi.o (50 bytes) next to divider.o in the linker scripts: mutex_enter_timeout_ms multiplies ms to us through __aeabi_lmul, and the SDK already wraps that symbol so it cannot be wrapped again per-feature. Cost: +64 bytes RAM on USB host builds, 50 bytes on builds with CIRCUITPY_USB_HOST=0 (Pico baseline 70320 -> 70384). The previous approach in this branch cost 1216 bytes on every build. With this, the core1 flash-call checker (adafruit#11115) passes on Feather RP2040 USB Host, Fruit Jam, and Raspberry Pi Pico builds. Smoke tested on the Feather: 20 SCSI read/write cycles to a flash drive. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
On raspberrypi boards with USB host, core1 runs the PIO-USB frame loop behind an MPU region that makes flash inaccessible, so everything core1 reaches must be RAM-resident. The linker script places the entry points in RAM, but nothing verified their callees — which is how adafruit#10243 happened: an upstream TinyUSB change added a flash-resident call inside RAM-placed tuh_task_event_ready() and core1 died at first device attach. check_core1_flash_calls.py disassembles the linked ELF, walks the static call graph from core1_main (following linker veneers through their literal-pool targets), and fails if any reachable function lives in flash, printing the offending call chain. Run against current builds it catches the adafruit#10243 regression, the Pico-PIO-USB calc_usb_crc16 flash placement (hard-locks the board on any multi-packet OUT transfer, e.g. writing to a USB drive), and a latent flash call chain in the TinyUSB queue mutex contention path. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fails the build with the offending call chain if any function reachable from core1 lives in flash. Adds about 2 seconds per board build. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
f7e823d to
b03cc85
Compare
|
All bugs the checker detects are now fixed on main (#11114, #11117, #11120). Added the CI check as requested: the rp2 Makefile now runs the checker after linking any CIRCUITPY_USB_HOST build and fails the build with the offending call chain (about 2 seconds per board). Verified locally: fails correctly on pre-fix trees, passes on current main. |
The picodvi Framebuffer_RP2040 core1_main calls dvi_register_irqs_this_core and dvi_start before it enables the MPU that blocks flash access, like the usb_host core1_main does with common_hal_mcu_processor_get_frequency. Fixes the 10 RP2040 DVI board build failures. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
tannewt
left a comment
There was a problem hiding this comment.
One suggestion on when to run this test. It'd be nice to cover all uses of core1.
picodvi on RP2040 also locks flash out via the MPU and runs core1 RAM-only. Check those builds too, rooting the walk at core1_main plus the two entry points reached only through registered pointers: dvi_dma1_irq and core1_scanline_callback. The checker now skips roots absent from a given build instead of skipping the whole check. This immediately flagged libdvi's flash-resident panic() on a "can't happen" queue overflow in the IRQ handler; allow it explicitly since core1 halts either way and the code is vendored. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Core1 with flash MPU-locked (usb_host, picodvi RP2040) could reach the SDK's flash-resident panic(), e.g. libdvi's TMDS queue overflow path, which would hard fault before printing. Wrap panic at link time with a RAM-resident implementation: core0 keeps the SDK behavior (print and exit), core1 halts in RAM via breakpoint + spin since stdio is not usable there. The core1 flash-call checker verifies the RAM path and allows the core0-only print half by name. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Follow-up to #10243 / #11114, answering "could we have caught this at build time?" Yes.
On RP2 USB host boards, core1 may only run code that lives in RAM. If it ever steps into flash, it crashes and USB host goes silent. We keep core1's functions in RAM, but nothing ever checked what those functions call. That's how #10243 slipped in.
This script checks the finished firmware: it follows every call core1 can make and fails the build with the guilty call chain if any of them lead into flash. Runs in ~2 seconds using arm-none-eabi-objdump.
Against current builds it catches three real cases:
calc_usb_crc16lives in flash. Verified on hardware: writing to a USB drive hard-locks the board. One-word upstream fix coming in a separate PR.Not wired into CI yet, since 2 and 3 would fail the build until they're fixed.