hil: convoy-safe openocd_seq recovery flasher for seven J-Link boards - #3832
hil: convoy-safe openocd_seq recovery flasher for seven J-Link boards#3832hathach wants to merge 4 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR completes the “convoy-safe recovery flasher” mechanism introduced in #3803 by adding an openocd_seq flasher backend plus flasher_recover roster entries for seven J-Link–based boards, enabling recovery flashing/reset over OpenOCD’s J-Link transport without triggering usbfs convoy hangs.
Changes:
- Added
openocd_seqflasher backend implementing an explicit OpenOCD command sequence (init/reset halt/flash write_image erase/verify_image/reset run/shutdown) and extendedconvoy_safe()+FLASHER_SUFFIXto recognize it. - Added
flasher_recoverentries totest/hil/tinyusb.jsonfor seven boards, pairing each board’s existing J-Link probe UID with OpenOCD-over-J-Link arguments. - Extended
test_hil_select.pyroster/dispatch linting and added regression coverage ensuringopenocd_seqdoes not use OpenOCD’sprogramcommand over the J-Link transport.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| test/hil/tinyusb.json | Adds flasher_recover entries for seven J-Link boards using openocd_seq with OpenOCD J-Link interface args. |
| test/hil/test/test_hil_select.py | Extends dispatch/suffix checks to include flasher_recover and adds focused tests for openocd_seq convoy-safety + command form. |
| test/hil/hil_flash.py | Implements flash_openocd_seq/reset_openocd_seq, updates missing-vid_pid warning behavior for J-Link interface, and allows openocd_seq in convoy_safe/FLASHER_SUFFIX. |
| docs/superpowers/followup/pr3803-flasher-recover.md | Removes the follow-up plan document now that the work is implemented. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Code reviewNo issues found. Checked for bugs and CLAUDE.md compliance. |
|
Code-size comparison skipped: PR selection built no families on this push. |
Hardware-in-the-loop (HIL) Test Reporthfp-iar✅ 56 passed · ❌ 0 failed · ⚪ 0 skipped · blank not run
hfp.json✅ 56 passed · ❌ 0 failed · ⚪ 0 skipped · blank not run
tinyusb-esp.json✅ 20 passed · ❌ 4 failed · ⚪ 0 skipped · blank not run
tinyusb.json✅ 333 passed · ❌ 48 failed · ⚪ 17 skipped · blank not run
|
|
fb195c8 to
4802ecd
Compare
Summary
Follow-up to #3803. That PR landed the wedge-containment machinery (
recover_flasher(), theconvoy_safe()gate,hil_testsubstituting the recovery flasher into--recover-board) but left it inert: no roster entry definedflasher_recover, and no flasher backend actually worked over openocd's jlink transport. This PR completes it:openocd_seqflasher backend (test/hil/hil_flash.py): explicitinit/reset halt/flash write_image erase/verify_image/reset run/shutdownsequence. The existingflash_openocd'sprogram <fw> verify reset exitform fails over the jlink transport (Examination failed→auto_probe failed, measured on stm32f4x and stm32f0x); the explicit sequence succeeded on all seven candidate boards.flasher_recoverroster entries (test/hil/tinyusb.json): stm32f407disco, stm32f072disco, stm32f723disco, stm32l476disco, feather_nrf52840_express, metro_m4_express, frdm_k64f — each the same J-Link probe as its primary flasher, driven through openocd's jlink driver instead of the SEGGER tools.test/hil/test/test_ci_select.py): roster lint for the recovery entries (name, jlink args, requiredadapter speed, uid must match the primary probe, convoy-safe), dispatch/suffix lints extended toflasher_recover, command-form regression test pinning "noprogramover jlink".interface/jlink.cfgargs: libjaylink's discovery gates onidVendor == 0x1366before ever callinglibusb_open, so a jlink-args entry never touches foreign usbfs nodes — which is exactly what makes it convoy-safe without a pin. The entries deliberately carry novid_pid: openocd's jlink driver never readsadapter usb vid_pid.Why this matters: when a DUT wedges its usbfs node mid-usbtest, the primary SEGGER-tool flashers (JLinkExe) block on libusb enumeration of the poisoned node — the convoy effect. The recovery flasher reaches the same probe without opening any non-SEGGER usbfs node, so the post-hang reflash/reset can run while the bus is still poisoned.
Bench validation (ci.lan, 2026-08-19, all seven boards, locks held ~8 min)
Each board was flashed and reset through its
flasher_recoverentry exactly asusbtest.pyreceives it (board['flasher']replaced by the recovery dict), timeout 90 s / 30 s:All flashes finish far inside
RECOVER_FLASH_TIMEOUT(90 s). openocd on the rig:0.12.0+dev-02620-g0ce743125.Known caveats (documented, deliberately not coded around)
usbtest.pyrecovery separates its reset and reflash steps by a ≥5 s settle plus a wedged-pids re-check plus flash time, so the chained case needs unusually fast fall-through; the residual worst case is visible (subsequent cases fail against a healthy, enumerable239a:0021bootloader — no bus poison), and the next primary flash restores the app. Accepted as a known limitation.target/nrf52.cfgis deprecated in favor oftarget/nordic/nrf52.cfg; the committed args are the bench-validated ones and the alias works on the rig's build.Out of scope
flash_openocdkeeps theprogramform — it's what the nine pinned CMSIS-DAP boards run in CI daily and was not re-validated here.rescue_openocd(the deeper vid_pid-pinned rescue) remains exact-openocd-gated;openocd_seqentries don't take that path.usbtest.pystill suggests only "pin vid_pid on an openocd flasher"; mentioning the jlink-argsflasher_recoveralternative there is a possible follow-up touch.Testing
test/hil/test/test_ci_select.py224/224,test/hil/test/test_hil_bounded.py108/108 (both offline, post-rebase on master's test_hil_select→test_ci_select move).pre-commit run --all-filesfully green (incl. unit-test, hil-test, ci-select-test hooks).🤖 Generated with Claude Code
https://claude.ai/code/session_01NWTa5C6ahUcWo7Z4kDjiju