hathach/openocd) Implementation PlanFor agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: One OpenOCD fork at hathach/openocd (default branch tinyusb) that flashes, debugs and RTT-captures every TinyUSB rig target — RP2040, RP2350 (arm + riscv), all WCH CH32/CH5xx, Analog Devices MAX32, and Espressif — replacing the four separate OpenOCD trees on ci.
Architecture: Fork openocd-org/openocd master (mainline is 1610 commits ahead of the RPi fork base and now the sole home of RISC-V support). Layer on top: 4 RP2350 TCL configs from the RPi fork, 1 ported max32665 TCL config from the ADI fork, the wlinke adapter + sdi transport + WCH flash drivers from hathach/riscv-openocd-wch (driving CH32 with mainline's riscv target if the DTM hypothesis holds), and ESP32-P4 TCL configs adapted from espressif/openocd-esp32 onto mainline's generic-riscv ESP pattern. ESP32/S2/S3/C3/C6/H2 debug is already in mainline; ESP flash stays with esptool.
Tech Stack: OpenOCD (autotools, C), TCL configs, GitHub CLI, TinyUSB HIL rig (hil_test.py, board_lock.py).
ci); no SSH hop needed.hathach/openocd, default branch tinyusb, source clone at ~/app/openocd, install prefix $HOME/app/openocd_tinyusb.tinyusb branch: one for raspberrypi/openocd, one for analogdevicesinc/openocd, one for riscv-openocd-wch, one for espressif/openocd-esp32 (plus the initial README commit). Iterate with git commit --amend / squash before declaring a task done.Co-Authored-By: Claude / Claude-Session: trailers in any commit.~/.local/bin/openocd_wch (symlink) and ~/app/openocd_wch_new stay untouched until Task 8's 4/4 WCH boards pass — it is the rig's only CH32 flasher. Backup exists at ~/.local/bin/openocd_wch.bak-20260727.python3 test/hil/board_lock.py hold <board> --reason "openocd-unified verify"; release after. Never stop the actions-runner.rtt polling_interval 1; never reset run inside an SDI session (target does not come back).pkill -x openocd — never pkill -f (pattern matches your own shell).libjim-dev is required to configure mainline; all build deps are already installed on ci (mainline was built here 2026-07-27)./usr/local/bin/openocd; the current binary is the RPi-fork build (byte-identical to ~/app/openocd_rpi/src/openocd).~/code/tinyusb except where a task explicitly says so (hil_test.py WCH cfg template, on a claude/-prefixed branch). Never git stash -u in a TinyUSB worktree.OPENOCD_UNIFIED_FORK_HANDOFF.md)| Tree on ci | Repo @ commit | Role |
|---|---|---|
~/app/openocd_rpi | raspberrypi/openocd @ ebec9504d (sdk-2.0.0) | rig default (/usr/local/bin/openocd) |
~/app/openocd_adi | analogdevicesinc/openocd @ 5fc33af | max32666fthr (~/app/openocd_adi/src/openocd) |
~/app/riscv-openocd-wch | hathach/riscv-openocd-wch @ ccb04d7 | CH32 flash+RTT (~/.local/bin/openocd_wch) |
~/app/openocd-mainline | openocd-org/openocd @ 43441cd83 | candidate build, verified on pico/pico2/max32666fthr |
Rig flasher entries (test/hil/tinyusb.json): openocd (pico ×3, fruit_jam, stm32h743nucleo, stm32g0b1nucleo), openocd_adi (max32666fthr), openocd_wch (nanoch32v203, ch32v103r_r1_1v0, ch32v307v_r1_1v0, ch582m_evt), esptool (espressif_s3_devkitm, espressif_p4_function_ev).
hathach/openocd, tinyusb branch, READMEFiles:
~/app/openocd/ (clone), ~/app/openocd/README.mdInterfaces:
Produces: GitHub repo hathach/openocd with default branch tinyusb; local clone ~/app/openocd with remotes origin (hathach) and upstream (openocd-org). All later tasks commit to this clone's tinyusb branch.
[ ] Step 1: Fork and clone
gh repo fork openocd-org/openocd --clone=false git clone --recursive https://github.com/hathach/openocd.git ~/app/openocd cd ~/app/openocd git remote add upstream https://github.com/openocd-org/openocd.git git checkout -b tinyusb origin/master
Run: cd ~/app/openocd && git log --oneline -1 Expected: 43441cd83 server: add 'services' command to list service information or newer.
README.md (new file — GitHub renders it instead of mainline's plain-text README, and leaving README untouched keeps future rebases conflict-free)# OpenOCD for the TinyUSB test rig One OpenOCD build that flashes, debugs and RTT-captures every board family on the [TinyUSB](https://github.com/hathach/tinyusb) hardware-in-the-loop rig, so the rig does not need four different OpenOCD trees. This is the `tinyusb` branch, tracking [openocd-org/openocd](https://github.com/openocd-org/openocd) `master`. Everything not listed below is unmodified mainline. ## Cherry-picked / ported from | Source repo | What we took | | --- | --- | | [raspberrypi/openocd](https://github.com/raspberrypi/openocd) (`sdk-2.0.0`) | `tcl/target/rp2350-riscv.cfg`, `rp2350-rescue.cfg`, `rp2350-dbgkey-secure.cfg`, `rp2350-dbgkey-nonsecure.cfg`. The RP2040/RP2350 C flash driver is already better in mainline (`rp2xxx.c`). | | [analogdevicesinc/openocd](https://github.com/analogdevicesinc/openocd) (`release`) | `tcl/target/max32665.cfg` (MAX32665/MAX32666), re-ported onto mainline's `max32xxx_common.cfg`. The fork's QSPI block is dropped — it is guarded by `QSPI_ENABLE`, which this part sets to 0. | | [hathach/riscv-openocd-wch](https://github.com/hathach/riscv-openocd-wch) (originally [dragonlock2/miscboards](https://github.com/dragonlock2/miscboards) WCH SDK) | `wlinke` adapter driver, `sdi` single-wire transport, and the WCH flash drivers (`wch_riscv`, `wch_arm`) for CH32V/CH32F/CH5xx over WCH-Link/LinkE. | | [espressif/openocd-esp32](https://github.com/espressif/openocd-esp32) | `tcl/target/esp32p4.cfg` + `tcl/board/esp32p4-builtin.cfg`, adapted to mainline's generic RISC-V ESP pattern. ESP32/S2/S3/C3/C6/H2 debug is already in mainline; ESP flash programming stays with `esptool`. | ## Build ./bootstrap ./configure --enable-jlink --enable-cmsis-dap --enable-stlink \ --enable-wlinke --disable-werror make -j$(nproc) `libjim-dev` is required — mainline no longer builds the bundled jimtcl by default and configure hard-fails without it.
cd ~/app/openocd git add README.md git commit -m "README: purpose of the tinyusb branch and its downstream sources" git push -u origin tinyusb gh repo edit hathach/openocd --default-branch tinyusb \ --description "OpenOCD for the TinyUSB test rig - one build for RP2040/RP2350, WCH CH32, MAX32 and ESP32 targets"
Run: gh repo view hathach/openocd --json defaultBranchRef -q .defaultBranchRef.name Expected: tinyusb
Files:
~/app/openocd_tinyusb/ (install prefix)Interfaces:
Consumes: ~/app/openocd clone from Task 1.
Produces: ~/app/openocd_tinyusb/bin/openocd (installed binary + scripts at ~/app/openocd_tinyusb/share/openocd/scripts/). Every later flash/verify step uses this path.
[ ] Step 1: Configure and build (same recipe that already worked for mainline on this box)
cd ~/app/openocd ./bootstrap ./configure --prefix=$HOME/app/openocd_tinyusb \ --enable-jlink --enable-cmsis-dap --enable-stlink --disable-werror make -j$(nproc) && make install
Run: ~/app/openocd_tinyusb/bin/openocd --version 2>&1 | head -1 Expected: Open On-Chip Debugger 0.12.0+dev-... with a -g<sha> matching git -C ~/app/openocd rev-parse --short HEAD.
Run: ~/app/openocd_tinyusb/bin/openocd -c 'adapter list; shutdown' 2>&1 | grep -E 'cmsis-dap|jlink|stlink' Expected: all three listed.
(No commit — build products only.)
Files:
~/app/openocd/tcl/target/rp2350-riscv.cfg, rp2350-rescue.cfg, rp2350-dbgkey-secure.cfg, rp2350-dbgkey-nonsecure.cfg, max32665.cfg~/code/tinyusb/openocd-unified-configs/ (the copies already hardware-verified this week; the max32665 port is already written there)Interfaces:
Consumes: ~/app/openocd + install prefix from Task 2.
Produces: target/rp2350-riscv.cfg and target/max32665.cfg resolvable via find in the installed scripts dir — Task 4 flashes with them.
[ ] Step 1: Copy the RPi configs and commit (downstream commit #1)
cd ~/app/openocd cp ~/code/tinyusb/openocd-unified-configs/rp2350-riscv.cfg \ ~/code/tinyusb/openocd-unified-configs/rp2350-rescue.cfg \ ~/code/tinyusb/openocd-unified-configs/rp2350-dbgkey-secure.cfg \ ~/code/tinyusb/openocd-unified-configs/rp2350-dbgkey-nonsecure.cfg \ tcl/target/ git add tcl/target/rp2350-*.cfg git commit -m "tcl/target: add RP2350 riscv/rescue/dbgkey configs from raspberrypi/openocd Taken from raspberrypi/openocd branch sdk-2.0.0 @ ebec9504d. These four configs are the only things that fork has which mainline lacks - the rp2040/rp2350 C driver was consolidated upstream as rp2xxx.c. All four use only mainline-present commands (swj_newdap, dap create -adiv6, target create riscv -ap-num, riscv set_enable_virt2phys). rp2350-riscv.cfg is what hw/bsp/rp2040/family.cmake requests when PICO_PLATFORM=rp2350-riscv."
cd ~/app/openocd cp ~/code/tinyusb/openocd-unified-configs/max32665.cfg tcl/target/ git add tcl/target/max32665.cfg git commit -m "tcl/target: add max32665 config ported from analogdevicesinc/openocd Ported from analogdevicesinc/openocd @ 5fc33af onto mainline's max32xxx_common.cfg (the ADI fork calls the same file max32xxx.cfg). The fork's QSPI block is dropped: it is guarded by QSPI_ENABLE, which this part sets to 0, and it needs the ADI-only max32xxx_qspi driver. Covers MAX32665/MAX32666 (both flash banks). Hardware-verified on max32666fthr 2026-07-27."
cd ~/app/openocd && make install ~/app/openocd_tinyusb/bin/openocd -c 'puts [find target/max32665.cfg]; puts [find target/rp2350-riscv.cfg]; shutdown'
Expected: both paths under ~/app/openocd_tinyusb/share/openocd/scripts/target/ printed; exit without “Can't find”.
cd ~/app/openocd && git push
Files:
~/code/tinyusb builds + test/hil/hil_test.py.Interfaces:
~/app/openocd_tinyusb/bin/openocd with Task 3 configs installed./usr/local/bin/openocd (Task 5's gate). PATH shim dir ~/app/openocd_tinyusb/shim/ reused by later tasks.Boards (every openocd/openocd_adi flasher entry in tinyusb.json): raspberry_pi_pico, raspberry_pi_pico_w, raspberry_pi_pico2, adafruit_fruit_jam, stm32h743nucleo, stm32g0b1nucleo, max32666fthr. Already verified on plain mainline 2026-07-27: pico, pico2, max32666fthr (re-run anyway — the binary changed).
examples/cmake-build-<board>; cmake-build-raspberry_pi_pico, -stm32g0b1nucleo, -max32666fthr already exist)cd ~/code/tinyusb/examples cmake -B cmake-build-raspberry_pi_pico2 -DBOARD=raspberry_pi_pico2 -G Ninja -DCMAKE_BUILD_TYPE=MinSizeRel . \ && cmake --build cmake-build-raspberry_pi_pico2
(Same pattern for raspberry_pi_pico_w, adafruit_fruit_jam, stm32h743nucleo. If a board fails get_deps, run python3 tools/get_deps.py -b <board> first.)
hil_test.py's hardcoded openocd resolve to the fork; symlink keeps scripts-dir resolution working because OpenOCD follows the realpath)mkdir -p ~/app/openocd_tinyusb/shim ln -sf ~/app/openocd_tinyusb/bin/openocd ~/app/openocd_tinyusb/shim/openocd
python3 ~/code/tinyusb/test/hil/board_lock.py hold raspberry_pi_pico --reason "openocd-unified verify" ~/app/openocd_tinyusb/bin/openocd -c "adapter serial E6614103E72C1D2F" \ -f interface/cmsis-dap.cfg -f target/rp2040.cfg -c "adapter speed 5000" \ -c "program /home/hathach/code/tinyusb/examples/cmake-build-raspberry_pi_pico/device/cdc_msc/cdc_msc.elf verify reset exit"
Expected: ** Verified OK ** then ** Resetting Target **. Release the lock after (board_lock.py release raspberry_pi_pico).
cd ~/code/tinyusb PATH=~/app/openocd_tinyusb/shim:$PATH \ python3 test/hil/hil_test.py test/hil/tinyusb.json \ -b raspberry_pi_pico -b raspberry_pi_pico_w -b raspberry_pi_pico2 \ -b adafruit_fruit_jam -b stm32h743nucleo -b stm32g0b1nucleo
Notes for the executor:
hil_test.py takes the config as a positional arg and -b per board; it holds board locks itself (that is the board-lock protocol in CI — do not also hold manual locks around hil_test.py runs).
max32666fthr is not in this run: its flash_openocd_adi() path uses the hardcoded OPENCOD_ADI_PATH = ~/app/openocd_adi (hil_test.py:408), which the shim can't intercept. Handle it in Step 4b instead. Do not edit hil_test.py for this — the adi path disappears at cutover (Task 10 flips tinyusb.json's flasher entry to plain openocd with -f interface/cmsis-dap.cfg -f target/max32665.cfg).
Expected: every board PASS in the report. Any failure: stop, diagnose (consult the hil skill), do not proceed to Task 5.
[ ] Step 4b: max32666fthr — manual flash with the fork, then tests with --skip-flash
python3 ~/code/tinyusb/test/hil/board_lock.py hold max32666fthr --reason "openocd-unified verify" ~/app/openocd_tinyusb/bin/openocd -c "adapter serial E6614C311B597D32" \ -f interface/cmsis-dap.cfg -f target/max32665.cfg \ -c "program /home/hathach/code/tinyusb/examples/cmake-build-max32666fthr/device/cdc_msc/cdc_msc.elf verify reset exit" python3 ~/code/tinyusb/test/hil/board_lock.py release max32666fthr cd ~/code/tinyusb && python3 test/hil/hil_test.py test/hil/tinyusb.json -b max32666fthr -sf
Expected: ** Verified OK ** on the flash, then PASS with -sf (tests run against the firmware just flashed).
target-debug skill has the full flow)Expected: RTT control block found, events stream, overflow 0.
openocdFiles:
/usr/local/bin/openocd (→ symlink), remove Debian openocd packageInterfaces:
Consumes: Task 4 all-green.
Produces: which openocd → fork for every rig user (hil_test.py, skills, CI). Rollback: restore /usr/local/bin/openocd.rpi-backup-20260727.
[ ] Step 1: Back up and repoint
sudo cp -a /usr/local/bin/openocd /usr/local/bin/openocd.rpi-backup-20260727 sudo ln -sf $HOME/app/openocd_tinyusb/bin/openocd /usr/local/bin/openocd openocd --version 2>&1 | head -1
Expected: fork version string (matches Task 2 Step 2).
--enable-jlink)sudo apt-get remove -y openocd which -a openocd
Expected: only /usr/local/bin/openocd remains.
cd ~/code/tinyusb python3 test/hil/hil_test.py test/hil/tinyusb.json -b raspberry_pi_pico -b stm32g0b1nucleo -b raspberry_pi_pico2
Expected: 3/3 PASS. If CI kicks a workflow mid-way, board locks arbitrate — just wait.
wlinke adapter + sdi transport (compiles, detects probe)Files (all in ~/app/openocd, sources from ~/app/riscv-openocd-wch @ ccb04d7 — this copy already carries the GCC-14 fixes):
src/jtag/drivers/wlinke.c (2041 lines, copy), src/jtag/sdi.c (~130 lines, port), src/jtag/sdi.h (if the fork has one — check ls ~/app/riscv-openocd-wch/src/jtag/sdi*)src/transport/transport.h (new transport id), src/jtag/interface.h (add sdi_ops to struct adapter_driver + struct sdi_driver decl), src/jtag/interfaces.c (register driver), src/jtag/drivers/Makefile.am, src/jtag/Makefile.am, configure.ac (--enable-wlinke)Interfaces:
openocd -c "adapter driver wlinke" works; wlink_* C exports (wlink_erase, wlink_write, wlink_getromram, wlink_reset, wlink_chip_reset, wlink_clean, wlink_flash_protect, …) available for Task 8's flash driver; sdi transport selectable. Commit stays amend-in-progress — Tasks 6–8 squash into downstream commit #3.Port notes gathered up front (verified against both trees 2026-07-27):
Fork wiring to replicate: configure.ac:117 (adapter list entry [[wlinke],[WLINKE Programmer],[WLINKE]]), :284-286 (AC_ARG_ENABLE), :537, :737 (AM_CONDITIONAL); src/jtag/drivers/Makefile.am:189 (DRIVERFILES += %D%/wlinke.c); src/jtag/interfaces.c:154,274 (extern + table entry).
Mainline transports are now a fixed bitmask enum (src/transport/transport.h:19-25: TRANSPORT_JTAG BIT(0) … TRANSPORT_SWIM BIT(6), plus TRANSPORT_VALID_MASK), and struct transport selects by unsigned int id, not name. Add #define TRANSPORT_SDI BIT(7), extend TRANSPORT_VALID_MASK, and port sdi.c's transport_register to the id-based struct.
SWIM is the exact precedent — ST‘s proprietary single-wire transport, wired upstream the same way this needs: swim_ops field at src/jtag/interface.h:363, its own transport bit, own command namespace. Mirror how grep -rn swim src/transport/ src/jtag/interface.h src/jtag/swim.c is structured wherever the fork’s 0.11-era pattern no longer matches mainline.
The fork's sdi op is a raw RISC-V DMI transfer: adapter_driver->sdi_ops->transfer(iIndex, iAddr, iData, iOP, oAddr, oData, oOP) (src/jtag/sdi.c:20-22) — keep that signature; Task 7 builds on it.
wlinke.c includes "cmsis_dap.h", "hidapi.h", "libusb_helper.h" and (spuriously) <windows.h> — drop/guard the windows include; hidapi + libusb helpers exist in mainline's drivers dir.
[ ] Step 1: Copy wlinke.c and sdi.c in; make the wiring edits above
[ ] Step 2: Reconfigure with wlinke and build
cd ~/app/openocd ./configure --prefix=$HOME/app/openocd_tinyusb \ --enable-jlink --enable-cmsis-dap --enable-stlink --enable-wlinke --disable-werror make -j$(nproc) && make install
Expected: clean build (--disable-werror tolerates the fork's warning-dirty code; do fix outright errors).
EBCA8F0670AF)python3 ~/code/tinyusb/test/hil/board_lock.py hold nanoch32v203 --reason "wlinke port bring-up" ~/app/openocd_tinyusb/bin/openocd -c "adapter driver wlinke" \ -c "adapter serial EBCA8F0670AF" -c "transport select sdi" \ -c "init" -c "shutdown"
Expected: log lines identifying the WCH-Link probe (firmware version print from wlink_init), no crash. init may complain about missing target — probe identification is the pass signal. Keep the lock held into Task 7 (same board).
cd ~/app/openocd && git add -A && git commit -m "WIP: wch port (squash into single downstream commit before push)"
Do not push until Task 8 squashes.
riscv over wlink DMIThe hypothesis (from the handoff, sharpened by code reading): WCH-LinkE‘s sdi op is a raw DMI transfer, and mainline’s riscv-013 target is just a DMI client. If mainline's riscv target can be fed by wlink DMI transfers, we skip porting wch_riscv.c/wch_riscv-013.c (~3.5k lines that #include <target/riscv/...> 0.11-era internals — the worst possible port surface).
Files:
src/jtag/drivers/wlinke.c (add the DTM bridge), possibly src/target/riscv/riscv-013.c shim hooks — decided by Step 1's reading.Interfaces:
Consumes: Task 6's working adapter (lock on nanoch32v203 still held).
Produces: a target create ... riscv (or, on fallback, wch_riscv) config shape that Task 8's flash/RTT/HIL work builds on. Records the decision in the WIP commit message.
[ ] Step 1: Read mainline's DMI plumbing before writing anything
Read src/target/riscv/riscv-013.c (the dmi_op/riscv_batch layer) and src/target/riscv/riscv.c‘s riscv dmi_read/dmi_write command handlers (they exist — mainline’s tcl/target/esp32c6.cfg calls them). Determine the narrowest insertion point, in order of preference:
wlinke.c: expose jtag_ops whose queue executor decodes IR=DTMCS/DMI DR scans into sdi transfers (the esp_usb_jtag-style approach, one level up);Same build command as Task 6 Step 2.
# wch-mainline-riscv-test.cfg adapter driver wlinke adapter speed 6000 transport select sdi ;# or jtag, if Step 1 chose the jtag-DTM emulation wlink_set_address 0x00000000 sdi newtap ch32 cpu -irlen 5 -expected-id 0x00001 target create ch32.cpu riscv -chain-position ch32.cpu ch32.cpu configure -work-area-phys 0x20000000 -work-area-size 0x2800 -work-area-backup 1 init
Evidence criteria — all four must hold to call the hypothesis confirmed:
halt → "Target halted" with a sane pc riscv dmi_read 0x11 → plausible dmstatus (nonzero, version field = 2 or 3) mdw 0x20000000 4 → reads SRAM without error resume → target runs again (LED blink / CDC re-enumerates)
If any criterion fails for reasons that look architectural (wlink protocol can‘t express raw DMI reads, QingKe deviates from the RISC-V debug spec in ways mainline won’t tolerate), stop and report to the user with the evidence. The two fallback options, costed:
src/target/wch_riscv.c (3033 ln) + wch_riscv-013.c + wch_riscv.h, plus the fork’s core patches (all findable via grep -rn 'riscvchip\|wlink_' src/ in the fork: src/flash/nor/tcl.c 5 hits, src/target/target.c 5, src/server/gdb_server.c 2). Hard: these files include 0.11-era target/riscv/* headers that clash with mainline's current riscv internals.openocd_wch as the rig's CH32 flasher indefinitely. Do not silently pick (a).Files:
src/flash/nor/wchriscv.c (324 ln, copy), src/flash/nor/wcharm.c (897 ln, copy — CH32F ARM parts; self-contained memory-mapped driver, zero wlink deps), src/jtag/drivers/wlinke.h (new — prototypes for the wlink_* exports; the fork relied on implicit declarations)src/flash/nor/drivers.c (extern + table entries, fork pattern at its lines 93-94/170-171), src/flash/nor/Makefile.am (fork pattern at lines 78-79)test/hil/hil_test.py WCH cfg template (~line 381) — only if Task 7 landed on the mainline-riscv target shapeInterfaces:
Consumes: Task 7's confirmed target shape + wlink_* exports from Task 6.
Produces: downstream commit #3 (single squashed commit, pushed); ~/.local/bin/openocd_wch repointed at the fork; hil_test.py template branch claude/hil-openocd-unified in the TinyUSB repo (unpushed — user pushes; “hold pushes” applies to the TinyUSB repo).
[ ] Step 1: Copy the flash drivers, add wlinke.h, wire drivers.c/Makefile.am; build
Keep the flash driver‘s registered name wch_riscv — the rig’s generated per-probe cfg does flash bank ... wch_riscv ... and Task 8 Step 4's template keeps working. Fork quirk to not copy: the fork patched src/flash/nor/tcl.c (handle_flash_protect_check_command, its line ~414) to call wlink_softreset()/wlnik_protect_check() for WCH banks. Implement that inside wchriscv.c‘s own protect_check op instead — no core-file patch. Check the fork’s src/server/gdb_server.c 2 wlink_ hits (grep -n 'riscvchip\|wlink_' ~/app/riscv-openocd-wch/src/server/gdb_server.c) — port the behavior into the driver/target layer if it matters for our flow (flash + RTT, no gdb needed on the rig for WCH), else document-and-skip in the commit message.
set _FLASHNAME ch32.flash flash bank $_FLASHNAME wch_riscv 0x00000000 0 0 0 ch32.cpu
~/app/openocd_tinyusb/bin/openocd -c "adapter serial EBCA8F0670AF" \ -f wch-mainline-riscv-test.cfg \ -c "program /home/hathach/code/tinyusb/examples/cmake-build-nanoch32v203-usbfs/device/cdc_msc/cdc_msc.elf verify reset exit"
Expected: ** Verified OK **; board re-enumerates as CDC (lsusb | grep -i cafe or dmesg).
rtt polling_interval 1, never reset run)RTT server start → capture a few seconds → nonzero events. The target-debug skill documents the WCH RTT route.
If Task 7 confirmed the mainline-riscv shape, the generated cfg template in test/hil/hil_test.py (~line 381: adapter driver wlinke … target create $_TARGETNAME.0 wch_riscv …) must switch to the Task 7 cfg shape. Do this on a TinyUSB branch:
cd ~/code/tinyusb && git worktree add .worktrees/claude/hil-openocd-unified -b claude/hil-openocd-unified # edit test/hil/hil_test.py template in the worktree; commit there; DO NOT push
Then repoint the rig's WCH binary (symlink, so scripts resolve):
ln -sf ~/app/openocd_tinyusb/bin/openocd ~/.local/bin/openocd_wch
(Old target ~/app/openocd_wch_new/bin/… and ~/.local/bin/openocd_wch.bak-20260727 stay as rollback.)
cd ~/code/tinyusb/.worktrees/claude/hil-openocd-unified python3 test/hil/hil_test.py test/hil/tinyusb.json \ -b nanoch32v203 -b ch32v103r_r1_1v0 -b ch32v307v_r1_1v0 -b ch582m_evt
Expected: 4/4 PASS. Firmware for missing cmake-build-<board> sets: build first (nanoch32v203 sets exist; ch32v103/307/ch582m may need tools/get_deps.py -b <board> + the examples build). Known flake: ch32v103r throughput is ~40% flaky historically — retry before blaming the port. If ch582m misbehaves specifically, note it and check wlinke.c's riscvchip dispatch for CH58x.
cd ~/app/openocd git reset --soft $(git log --grep='WIP: wch port' --format=%H | tail -1)^ git commit -m "jtag, flash: add WCH-LinkE adapter, sdi transport and CH32 flash drivers Ported from hathach/riscv-openocd-wch @ ccb04d7 (originally dragonlock2/miscboards WCH SDK, base openocd 0.11.0): - src/jtag/drivers/wlinke.c: WCH-Link/LinkE USB adapter (GCC-14 fixes included) - src/jtag/sdi.c: WCH single-wire debug transport, re-worked onto mainline's id-based transport API (TRANSPORT_SDI) - src/flash/nor/wchriscv.c, wcharm.c: CH32V/CH5xx (wlink protocol) and CH32F (memory-mapped) flash drivers CH32 cores are driven by mainline's riscv target over wlink DMI transfers; the fork's wch_riscv target stack is not needed. The fork's core patches (flash/nor/tcl.c protect-check hack) moved into the wch_riscv flash driver's protect_check op. Verified on ci rig: nanoch32v203, ch32v103r_r1_1v0, ch32v307v_r1_1v0, ch582m_evt - flash + verify + HIL suite + RTT (nanoch32v203)." git push
(Amend the target-stack paragraph if the fallback path was taken instead.) Release the nanoch32v203 lock if still held.
Mainline already has: src/target/espressif/ (esp32/s2/s3 xtensa targets + apptrace/semihosting), the esp_usb_jtag adapter driver, and builtin cfgs for c2/c3/c6/h2/s3. Missing vs the rig: anything ESP32-P4. Flash stays esptool (rig flashes ESP via idf.py/esptool; the espressif fork's flash-stub stack is explicitly out of scope).
Files:
~/app/openocd/tcl/target/esp32p4.cfg, ~/app/openocd/tcl/board/esp32p4-builtin.cfgInterfaces:
Consumes: install prefix; espressif fork cfgs fetched from GitHub.
Produces: downstream commit #4; P4 + S3 debug-attach evidence.
[ ] Step 1: Verify S3 attach with pure mainline inheritance (no new files; proves the “espressif support” baseline)
python3 ~/code/tinyusb/test/hil/board_lock.py hold espressif_s3_devkitm --reason "openocd-unified esp verify" ~/app/openocd_tinyusb/bin/openocd -f board/esp32s3-builtin.cfg -c "init; halt"
Expected: both xtensa cores detected over USB-Serial-JTAG (303a:1001), Target halted. Then resume; shutdown, release lock. Gotchas live in the esp-target-debug skill (S3's debug port can be occupied when TinyUSB firmware owns the USB peripheral — use the same recovery steps as that skill).
curl -fsSL https://raw.githubusercontent.com/espressif/openocd-esp32/master/tcl/target/esp32p4.cfg -o /tmp/claude-1000/-home-hathach-code-tinyusb/7dee5f9e-874b-4680-bb09-01a5d13fbd37/scratchpad/esp32p4-espressif.cfg curl -fsSL https://raw.githubusercontent.com/espressif/openocd-esp32/master/tcl/board/esp32p4-builtin.cfg -o /tmp/claude-1000/-home-hathach-code-tinyusb/7dee5f9e-874b-4680-bb09-01a5d13fbd37/scratchpad/esp32p4-builtin-espressif.cfg
Espressif‘s cfg creates an esp32p4-type target (their esp_riscv C stack — not in mainline). Rewrite tcl/target/esp32p4.cfg following **mainline’s own ESP RISC-V pattern** — tcl/target/esp32c6.cfg + esp_common.cfg (generic riscv target create, chip quirks via riscv dmi_write with the _RISCV_* register constants from esp_common.cfg) — carrying over from Espressif‘s file: _CPUTAPID, memory map/workarea, the dual-core SMP topology (P4 is 2× RV32 — model on how mainline handles SMP, and on Espressif’s _ESP_SMP_TARGET), and the _ESP_EFUSE_MAC_ADDR_REG value. tcl/board/esp32p4-builtin.cfg = esp_usb_jtag adapter + transport select jtag + source the target cfg (mirror board/esp32c6-builtin.cfg, adjusting ESP_USB_JTAG_* ids to Espressif's P4 values). Also check src/jtag/drivers/esp_usb_jtag.c accepts the P4 (VID/PID 303a:1001 is shared; verify any chip-id gating).
python3 ~/code/tinyusb/test/hil/board_lock.py hold espressif_p4_function_ev --reason "openocd-unified esp verify" cd ~/app/openocd && make install ~/app/openocd_tinyusb/bin/openocd -f board/esp32p4-builtin.cfg -c "init; halt"
Evidence criteria: both HP cores halt, mdw 0x4ff00000 4 (P4 HP TCM/SRAM — cross-check the address against Espressif‘s cfg memory map before running) reads, resume works. Known nuance from prior sessions: P4 attach can need the reset-into-attach dance — the esp-target-debug skill documents it; an attach that only works with that dance still counts as pass (note it in the commit). Decision checkpoint: if the generic-riscv shape cannot attach P4 for architectural reasons (needs Espressif’s C-level esp_riscv assist), stop and report — options are cherry-picking their esp_riscv stack (large) vs shipping P4 as esptool-flash-only with debug via ESP-IDF's openocd as today. Do not silently pick either.
cd ~/app/openocd git add tcl/target/esp32p4.cfg tcl/board/esp32p4-builtin.cfg git commit -m "tcl: add ESP32-P4 target/board configs adapted from espressif/openocd-esp32 Adapted from espressif/openocd-esp32 master onto mainline's generic RISC-V ESP pattern (tcl/target/esp32c6.cfg + esp_common.cfg): generic riscv targets over esp_usb_jtag instead of the fork's esp_riscv C stack. Flash programming stays with esptool, matching how the rig flashes all Espressif boards. ESP32/S2/S3/C3/C6/H2 were already supported by mainline. Verified on ci rig: espressif_p4_function_ev and espressif_s3_devkitm attach/halt/resume over built-in USB-Serial-JTAG." git push
Release both ESP board locks.
Files:
~/app/openocd/README.md (only if scope shifted in Tasks 7–9)test/hil/tinyusb.json — max32666fthr flasher openocd_adi → openocd with args -f interface/cmsis-dap.cfg -f target/max32665.cfg (plain openocd now serves it)Interfaces:
Consumes: everything green from Tasks 4–9.
Produces: the finished fork; TinyUSB branch claude/hil-openocd-unified with hil_test.py + tinyusb.json changes, committed, unpushed (user pushes per standing instruction).
[ ] Step 1: Full HIL regression across every openocd-family board
cd ~/code/tinyusb/.worktrees/claude/hil-openocd-unified python3 test/hil/hil_test.py test/hil/tinyusb.json \ -b raspberry_pi_pico -b raspberry_pi_pico_w -b raspberry_pi_pico2 \ -b adafruit_fruit_jam -b stm32h743nucleo -b stm32g0b1nucleo -b max32666fthr \ -b nanoch32v203 -b ch32v103r_r1_1v0 -b ch32v307v_r1_1v0 -b ch582m_evt
Expected: 11/11 PASS (ch32v103r throughput may need its usual retries).
Re-read README.md against what actually landed (WCH target route, P4 outcome). Fix any row that no longer matches; amend into the README commit or add git commit -m "README: reflect verified scope". Push.
Run: git -C ~/app/openocd log --oneline upstream/master..tinyusb Expected: exactly 5 commits (or 6 with a README truth-up): README, RPi configs, ADI config, WCH port, ESP32-P4 configs. If not, interactive-free cleanup: git rebase --onto / reset --soft re-squash, then git push --force-with-lease (fork branch, ours alone — safe).
cd ~/code/tinyusb/.worktrees/claude/hil-openocd-unified git add test/hil/hil_test.py test/hil/tinyusb.json git commit -m "test(hil): drive WCH boards and max32666fthr through the unified openocd"
Leave for the user to push/PR.
Write a short status into OPENOCD_UNIFIED_FORK_HANDOFF.md (append a “2026-07-XX outcome” section): what was repointed, rollback paths (/usr/local/bin/openocd.rpi-backup-20260727, ~/.local/bin/openocd_wch.bak-20260727), and that ~/app/openocd_rpi, ~/app/openocd_adi, ~/app/openocd-mainline, ~/app/openocd_mainline, ~/app/openocd_wch_new, ~/app/riscv-openocd-wch can be retired after a week of green CI — not now.