Repository navigation
EPIC-09: device-side visibility and recovery - #9
Merged
diegoparrilla merged 6 commits intoSep 15, 2026
Merged
Conversation
The statistics were keyed on NDEBUG, which both build types define, so MEM_STATS, MEMP_STATS and stats_display were compiled out everywhere. Port Booster's block: a debug build with DEVOPS_LWIP_STATS=1 in the environment turns them on. Release is unchanged; its lwIP objects are byte-identical to before.
…ORY-05) commemul_poll derived the write index from the DMA transfer count with no lap detection, so a main loop stalled for more than 4,096 samples silently mixed old and new data into the protocol parser. Count a lap when that many samples arrived since the last poll, and drop the unread ones. The channel stopped for good after 2^32 samples; re-arm it at 2^31 from its live write address. A corrupted frame could also carry a payload size larger than the payload buffer, and the parser wrote past it. Reject such a frame.
…(EPIC-09 STORY-01 STORY-04 STORY-06) A panic or HardFault used to freeze the RP silently: the SDK's panic stops at a breakpoint and fatfs-sdk's HardFault handler does the same, which locks up an RP2040 with no debugger. A hang had no recovery at all. health.c adds: - panic() through PICO_PANIC_FUNCTION and a HardFault handler installed in the RAM vector table at boot. Both write the reason, PC, LR and SP to watchdog scratch registers 0-3 and reboot after 100 ms. - An 8 s watchdog, paused under a debug probe, fed from the main loop, emul_pollTick, the Wi-Fi connect loop, the HTTP spin-waits and after the two slow boot steps. A phase ID in scratch[0] says where a hang happened. - Boot decoding into power-on, reset, panic, HardFault, hang or reboot, shown on row 2 of the setup menu and on the debug console. - A crash-loop guard: 3 crash reboots within 60 s stop the countdown until a SELECT reset or a power cycle. - A painted core-0 stack for a high-water mark, heap free and minimum free from mallinfo and sbrk, and code copied to RAM. - Debug-only fault injection for hardware verification. sd_timeouts.c overrides fatfs-sdk's weak timeout table so a failing card gives up well within the watchdog. The jump to Booster stops the watchdog first, since it does not reset the chip.
A release build has no console, so nothing on the device could be read without a debugger. GET /api/v1/system/health returns uptime, heap total, free, minimum free and sbrk high-water, stack high-water and reservation, code in RAM, the last reset reason with its phase or PC, the crash count and crash-loop state, ROM3 overruns and debug-byte drops. A debug build with DEVOPS_LWIP_STATS adds lwIP's pool counters. It is a new endpoint rather than more ping fields, so ping stays a minimal liveness check. `sidecart.py health` prints it, with tests. Debug builds also expose POST /api/v1/debug/test/<fault> to trigger each recovery path on hardware. Both are documented in docs/api.md.
…d (EPIC-09 STORY-06) With a mounted SD card pulled, sd_read_blocks returned early from its error path without releasing the card and SPI mutexes. Those mutexes block without a timeout, so the next disk call waited forever: the first GEMDRIVE or HTTP request after the pull failed, and the second froze the RP until the watchdog rebooted it (reported as a hang in disk_read by a diagnostic build). Upstream fixed it in f5f31b3. 6c644cf is that fix plus a README change, and is the commit Booster already pins. Verified on hardware: after an idle pull, listings and downloads fail in under a second and the RP stays up.
Flashing or resetting through a debug probe uses SYSRESETREQ, which leaves the watchdog reason and scratch registers exactly as a real watchdog expiry does, so every probe flash showed "Recovered: hang" and counted as a crash. Measured on hardware: reason, watchdog control, chip reset and the SDK's marker all match, and DHCSR reads as zero from the firmware. A once-a-second timer interrupt now writes a stall mark into scratch[1] after 5 s without a watchdog feed, and clears it when feeding resumes. A watchdog reset counts as a hang only if the mark is there. A hang with interrupts disabled cannot set the mark and is reported as a reboot. Verified on hardware: main-loop hang and HTTP handler hang still report their phase; a probe reset and a probe flash now report a reboot with crash count 0.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Second epic of v1.1.0: the device reports its own health, and a crash or hang reboots it with the reason kept instead of freezing silently.
What changes
panic()goes tohealth_panic(PICO_PANIC_FUNCTION), and a HardFault handler is installed in the RAM vector table at boot. fatfs-sdk's own handler stopped at a breakpoint, which locks up an RP2040 with no debugger. Both write the reason, PC, LR and SP to watchdog scratch registers 0-3 and reboot after 100 ms.emul_pollTick, the Wi-Fi connect loop, the HTTP spin-waits and two slow boot steps. A phase ID says where a hang happened.Recovered: panic @…,Recovered: fault @…orRecovered: hang in <phase>. After 3 crash reboots within 60 s the boot countdown stays stopped until a SELECT reset or a power cycle.GET /api/v1/system/healthandsidecart.py healthreport heap free and minimum, sbrk and stack high-water marks, code in RAM, the last reset with its phase or PC, crash count, ROM3 overruns and debug-byte drops. Debug builds print a summary every 30 s.NDEBUG, which both build types define. A debug build withDEVOPS_LWIP_STATS=1now adds the pool counters to the health report. Release lwIP objects are unchanged.6c644cf, the commit Booster uses. After a card pull,sd_read_blocksreturned without releasing its locks, so the next SD call deadlocked the RP.6c644cfis the upstream fix plus a README change.sd_timeouts.coverrides fatfs-sdk's weak timeout table. Debug-only fault-injection routes underPOST /api/v1/debug/test/.docs/api.md, README and CLAUDE.md updated.Verified on hardware
On
releaseanddebug, with the ST in the setup menu,[G]and[U]:health; the PC resolves to the faulting linehang in main_loop/hang in http_requestx3; a SELECT tap clears itreboot, crash count 0[G]or[U]cli/test_sidecart.pyFound and filed, not fixed here
gemdrive mkdirpanics inmallocon a 29 GB FAT32 card (EPIC-11).The 4 MB upload and 10 s
runner loadfalse-reboot checks wait for the idle-sweeper fix.