diff options
| author | hathach <[email protected]> | 2026-08-20 16:47:22 +0700 |
|---|---|---|
| committer | hathach <[email protected]> | 2026-08-20 16:47:22 +0700 |
| commit | 126e29f777f07a35b06b327b50e62e0150521b48 (patch) | |
| tree | 8aa0f665aef03c67a370d701147da19203d380bf | |
| parent | be86281b81b70adc1ce3746d620c912bf032fac2 (diff) | |
skills, CLAUDE.md: correct instruction claims the source refutes
Findings from an audit of the .claude instruction surface: 1,387 falsifiable claims
extracted with a quote-gate (zero hallucinated), each verdicted against the code, the
kernel at the rig's running version, or the rig itself. Only claims the current source
actively refutes were touched; hard-earned rig knowledge stands as source of truth.
usbtest told operators to stop the actions runner before touching hardware. Every
other file forbids exactly that since the per-board flock landed - following it stops
CI on a shared rig. Twice in the same file it said hil_test.py serializes usbtest
batteries; hil_lock.py budgets 2 concurrent per host controller, a profiled
throughput trade rather than a safety ceiling - while the recorded hazards stay: an
unbudgeted battery has hard-frozen the rig through a VFIO xHCI PCIe error, and a
marginal DUT port bouncing under concurrent batteries has killed a uPD720201 outright,
which lowering the widths does not fix. It also cited src/usb_descriptors.h and
src/tusb_config.h as if repo-relative (they are the example's own, and the comment sat
above the cd that establishes the base) and presented usbtest_do_ioctl() and
tools/usb/testusb.c as repo files when both are Linux kernel.
usb-kernel-recover called the Renesas ppps "real per-port" in its rig layout while
saying four sections earlier that VBUS stays up. Both describe the same silicon and
only the second was right: owner-confirmed, the cards advertise ppps and do not
implement it, so a root cycle is purely a re-enumeration - both places now say so and
warn against reading uhubctl's flag as power control. The layout listed three cards;
the rig has five (01/03/04/05/06:00.0; AMD 02:00.0 has none), re-derived from
lspci/uhubctl/sysfs and written as a derivation recipe because bus numbers renumber
every boot. The root-cycle rung also gains the board-flock requirement the other
files already demand - it avoids the KERNEL device_lock, which is a different lock,
and the text now names the two apart instead of reading as "no reservation needed".
CLAUDE.md listed src/tusb_config.h among the key files; no such path exists -
tusb_config.h is per-example, src/tusb_option.h is the file that lives there.
| -rw-r--r-- | .claude/skills/usb-kernel-recover/SKILL.md | 41 | ||||
| -rw-r--r-- | .claude/skills/usbtest/SKILL.md | 28 | ||||
| -rw-r--r-- | CLAUDE.md | 2 |
3 files changed, 52 insertions, 19 deletions
diff --git a/.claude/skills/usb-kernel-recover/SKILL.md b/.claude/skills/usb-kernel-recover/SKILL.md index 009090769..7bb5fc1ba 100644 --- a/.claude/skills/usb-kernel-recover/SKILL.md +++ b/.claude/skills/usb-kernel-recover/SKILL.md @@ -105,16 +105,32 @@ convoy-safe: **openocd pinned with `vid_pid`**, or esptool (`-p <ttyACM>`). JLinkExe selects by serial, which needs `libusb_open`, so it needs the shield. **Rung 2 — wedged PROBE: `root-cycle`.** A probe has no probe to reset it, so the -port-side drop is the only lock-free lever left. It commands the ROOT hub and -never touches the wedged device's lock. +port-side drop is the only lever left that avoids the KERNEL device lock. It +commands the ROOT hub and never touches the wedged device's `device_lock` — which +is exactly why rungs 1 and 3 are dangerous and this one is not. + +That is a different lock from the rig's **board flocks**, and this rung still needs +those: it bounces every fixture under the root port, including boards another job is +mid-flash on. Take them first, and release after: ```bash -sudo usb_recover.sh root-cycle <busport> [expected-serial] +python3 test/hil/helper/hil_lock.py hold --all --config <this host's config> --reason "root-cycle <busport>" +sudo .claude/skills/usb-kernel-recover/scripts/usb_recover.sh root-cycle <busport> [expected-serial] +python3 test/hil/helper/hil_lock.py release --all # no --config: it walks the lock dir ``` -Bounces **every fixture under that root port** (up to 25 here). Renesas `ppps` -disables D+/D− only — VBUS stays up, so it is a forced re-enumeration, not a -power cycle. Success is the sysfs inode changing, not uhubctl's exit code. +`--all` is coarse for one root port, but nothing maps a sysfs busport to a board name, +so it is the only reservation that actually covers the blast radius; `hold` accepts any +string, so a hand-listed "just the siblings" hold reserves nothing while reporting +success. A refusal naming `hil_test.py` means CI is mid-test — wait, do not force. Give +the script the wedged probe's own busport (e.g. `13-1.6`), not the `13-1` hub path: it +derives the root port itself, and the expected-serial guard and the success check both +read the path you pass. + +Bounces **every fixture under that root port** (up to 25 here). The Renesas cards +advertise `ppps` but do not implement it: VBUS stays up and only D+/D− drop, so +this is a forced re-enumeration, never a power cycle — a device whose firmware is +wedged can ride it out. Success is the sysfs inode changing, not uhubctl's exit code. **Rung 3 — terminal case: a driver ioctl that OWNS the lock.** No software cure: the task is uninterruptible and SIGKILL is queued, not delivered. Reboot with @@ -201,6 +217,13 @@ Observed: 5 boards missing with a completely clean D-state list, because ## Rig layout (ci.lan, bus numbers renumber every boot) -`readlink -f /sys/bus/usb/devices/usb<N>` → its PCI address. AMD `0000:02:00.0` -has no port-power switching; Renesas `0000:01:00.0` (probe tree) and -`0000:03:00.0`/`0000:05:00.0` (DUT hubs) have real per-port `ppps`. +`readlink -f /sys/bus/usb/devices/usb<N>` → its PCI address; `sudo uhubctl` lists +the root hubs it can drive, against their PCI address. Five Renesas uPD720201 cards +— `0000:01:00.0`, `03`, `04`, `05`, `06:00.0` — advertise per-port `ppps` on both +their USB2 and USB3 root hubs, **but do not implement it**: the silicon never drops +VBUS, so a cycle re-enumerates the port and nothing more (above). Do not read +`uhubctl`'s `ppps` as power control on this rig. AMD `0000:02:00.0` does not appear +in `uhubctl` at all — no switching of any kind. Which tree holds which probes moves +with re-cabling, so derive it (`lsusb -s <bus>:`) rather than trusting a stored map. +Verified 2026-08-18; `sudo` is passwordless for `hathach` here, so every rung above +runs without a prompt. diff --git a/.claude/skills/usbtest/SKILL.md b/.claude/skills/usbtest/SKILL.md index 32c2be913..4d7c6e5ab 100644 --- a/.claude/skills/usbtest/SKILL.md +++ b/.claude/skills/usbtest/SKILL.md @@ -21,7 +21,8 @@ the failing case passing *and* the full battery still at 30/30 across reflash cy ## Run ```bash -# build (cmake); descriptor sizes auto-adapt per MCU via src/usb_descriptors.h + src/tusb_config.h +# build (cmake); descriptor sizes auto-adapt per MCU via the example's own +# src/usb_descriptors.h + src/tusb_config.h (paths below are relative to it) cd examples/device/usbtest && cmake -B build -DBOARD=<board> -G Ninja -DCMAKE_BUILD_TYPE=MinSizeRel && cmake --build build # flash, wait ~3-5 s for enumeration to settle, then: python3 test/hil/usbtest.py --serial <uid> --keep-binding # full battery for the advertised tier @@ -36,9 +37,15 @@ python3 test/hil/usbtest.py --serial <uid> --keep-binding --tests 29 # one case device wedged and skip cleanup — expected; reset or reflash it yourself. - Always settle a few seconds after flashing — enumeration can bounce once; testusb into the gap sees the device drop mid-case. -- On a CI rig: stop the actions runner before touching hardware; restart after. Never run two - batteries concurrently (hil_test.py serializes them; concurrent batteries have hard-frozen a rig - via a fatal PCIe error on a VFIO-passed xHCI). +- On a CI rig: hold the board lock before touching hardware and release it after — never stop the + actions runner. It keeps running; the per-board flock is what arbitrates (see the `hil` skill). + Never start a battery by hand next to a running one: `hil_test.py` budgets 2 concurrent batteries + per host controller (`HIL_USBTEST_PARALLEL`). The width itself is a profiled throughput/bandwidth + trade, not a safety ceiling (hil_lock.py:122-127) — but a battery outside the budget is a real + hazard, and the hazard is recorded: unbudgeted concurrent batteries have hard-frozen the rig with + a fatal PCIe error on a VFIO-passed xHCI, and a marginal DUT port bouncing under concurrent + batteries has killed a uPD720201 outright, which lowering the widths does not fix + (hil_lock.py:130-132). ## Porting ladder — new MCU/DCD to 30/30 @@ -47,9 +54,9 @@ python3 test/hil/usbtest.py --serial <uid> --keep-binding --tests 29 # one case 2. **Tier 2 (ctrl_out 14/21)**, **tier 3 (interrupt 25/26)**, **tier 4 (iso 15/16/22/23)** — raise the tier only when the layer below is clean; run the *full* battery after each layer. 3. **Fit the endpoints**: tier 4 needs 6 endpoints + EP0. Small parts need per-MCU mps/epbuf - overrides in `src/usb_descriptors.h` (`USBTEST_INT/ISO_EP_MPS_FS`) and `src/tusb_config.h` - (`CFG_TUD_VENDOR_TX_EPSIZE`) — follow the existing CH32/LPC11 patterns. Parts that can't fit go - in `skip.txt`. + overrides in the example's own `src/usb_descriptors.h` (`USBTEST_INT/ISO_EP_MPS_FS`) and + `src/tusb_config.h` (`CFG_TUD_VENDOR_TX_EPSIZE`) — follow the existing CH32/LPC11 patterns. + Parts that can't fit go in `skip.txt`. 4. **Sign-off = reliability, not one pass**: 3–10 full flash→battery cycles. One 30/30 proves nothing on a flaky bring-up; deterministic partial counts (e.g. exactly 1-in-8 lost) are a signature, not noise — chase them. @@ -98,7 +105,8 @@ whether a hung case is recoverable. Fetch the rig's exact version (`uname -r`): ```bash curl -sO "https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/plain/drivers/usb/misc/usbtest.c?h=v6.12.96" -# case N lives under `case N:` in usbtest_do_ioctl(); tools/usb/testusb.c maps the flags: +# case N lives under `case N:` in the kernel's usbtest_do_ioctl() +# (drivers/usb/misc/usbtest.c); kernel tools/usb/testusb.c maps the flags: # -c = param.iterations, -s = param.length, -g = param.sglen (NOT what they read like) ``` @@ -147,7 +155,9 @@ curl -sO "https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/plain - "usbmon shows no toggle problem" → usbmon can't see toggles. - "It works on gcc" → clang/IAR/LTO/make still pending. - "Fixed iso IN" → apply the same exemption to iso OUT (toggle logic is symmetric). -- A clean single-board run does not validate concurrent/fleet behavior — batteries serialize. +- A clean single-board run does not validate concurrent/fleet behavior — a fleet run puts up to 2 + batteries per host controller (`HIL_USBTEST_PARALLEL`) plus concurrent flashes on the same hub + uplinks, which one board never exercises. - Reasoning about a case from its name or table row → open `usbtest.c` (step 0). The flags don't mean what they look like, and recoverability is a property of that case's wait, not of the rig. @@ -129,7 +129,7 @@ Cutting a release — version bump, regenerated files, the per-release changelog ``` - Supported MCUs/boards: `hw/bsp/` and `docs/reference/boards.rst`. - USB classes: `src/class/{cdc,hid,msc,audio,…}/` — each has `*_device.c` and `*_host.c`. -- Key files: `src/tusb.h`, `src/tusb_config.h`, `tools/get_deps.py`, `tools/build.py`, `test/unit-test/project.yml`. +- Key files: `src/tusb.h`, `src/tusb_option.h`, `tools/get_deps.py`, `tools/build.py`, `test/unit-test/project.yml` (each example carries its own `src/tusb_config.h`). ## Common Build Issues |
