docs(skills): formatting feedback — agent skill table, probe bullets, aligned columns

- target-debugger: skill list is now a table referencing skills by name only
  (path pattern stated once).
- target-debug: probe-mapping run-on paragraph split into bullets; drop the
  GDB Ninth-Edition caveat (calibre now holds the Tenth-Edition PDF, id 2264).
- Align markdown table columns across target-debug, usb-sniffer, usbmon, hil,
  usbtest and the agent (7 tables); tables with paragraph-length cells left
  unpadded (usbmon symptom map, usbtest case map).
This commit is contained in:
hathach
2026-07-23 13:38:13 +07:00
parent 21bbcb5bbf
commit 9c6c0390a0
6 changed files with 65 additions and 70 deletions

View File

@ -8,26 +8,18 @@ You debug one failing USB behavior on one physical board until you can name the
mechanism — or report exactly what you ruled out. The target may run the device
stack, the host stack, or both; its link peer may be the Linux PC, another
TinyUSB board, or a Linux gadget (e.g. a Raspberry Pi) — pick capture channels
by which end runs Linux, not by habit. These repo skills are your source of
truth; read the relevant SKILL.md BEFORE acting:
by which end runs Linux, not by habit. These repo skills (each at
`.claude/skills/<name>/SKILL.md`) are your source of truth; read the relevant
one BEFORE acting:
- `.claude/skills/target-debug/SKILL.md` — your primary playbook: technique
choice by intrusiveness, channel choice by link topology, capture recipes,
breakpoint/watchpoint budget and cost model, vector catch + fault autopsy,
SWO trace, GDB autopsy, all rig warnings.
- `.claude/skills/hil/SKILL.md` — host/config selection, board lock protocol,
`hil_test.py` invocation.
- `.claude/skills/usbmon/SKILL.md` — Linux-host URB capture; exists only when a
Linux PC is the link's host (the default posture is dual-side: both ends
simultaneously).
- `.claude/skills/usb-sniffer/SKILL.md` — wire-level capture with the hardware
tap: when the host can't see the bus (device never enumerates, pre-URB
failures), when usbmon and target logs disagree — the wire arbitrates — or
when TinyUSB is the host and no end has usbmon.
- `.claude/skills/usb-kernel-debug/SKILL.md` — why the Linux kernel acted
(dmesg/dynamic debug); the PC host, or a Linux gadget peer's device side.
- `.claude/skills/usb-kernel-recover/SKILL.md` — only when the DUT or fixture
wedges the rig PC's Linux host stack.
| Skill | Use for |
|--------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| target-debug | primary playbook — technique choice by intrusiveness, channel choice by link topology, capture recipes, bp/wp budget + cost model, vector catch + fault autopsy, SWO trace, GDB autopsy, rig warnings |
| hil | host/config selection, board lock protocol, `hil_test.py` invocation |
| usbmon | Linux-host URB capture; only when a Linux PC is the link's host (default posture: dual-side, both ends simultaneously) |
| usb-sniffer | wire-level capture (hardware tap): host can't see the bus, usbmon vs target logs disagree, or TinyUSB is the host (no usbmon anywhere) |
| usb-kernel-debug | why the Linux kernel acted (dmesg/dynamic debug); PC host or a Linux gadget peer's device side |
| usb-kernel-recover | only when the DUT or fixture wedges the rig PC's Linux host stack |
## The loop (deliberately serial — no fan-out)

View File

@ -7,11 +7,11 @@ description: Use when running TinyUSB Hardware-in-the-Loop (HIL) tests on physic
Run TinyUSB HIL tests on real boards. **Run `hostname` first** — it tells you which host you are on, which determines the default config and whether remote mode is possible.
| Host | Local config | Remote (SSH → ci.lan)? |
|------|--------------|------------------------|
| `htpc` (dev PC) | `test/hil/local.json` | yes (large pool, `test/hil/tinyusb.json`) |
| `ci` (the rig) | `test/hil/tinyusb.json` (large pool) | no — can't SSH to htpc, and boards are already local |
| `hifiphile` (external rig) | `test/hil/hfp.json` | no outbound SSH to htpc/ci; SSH-reachable FROM both |
| Host | Local config | Remote (SSH → ci.lan)? |
|----------------------------|--------------------------------------|------------------------------------------------------|
| `htpc` (dev PC) | `test/hil/local.json` | yes (large pool, `test/hil/tinyusb.json`) |
| `ci` (the rig) | `test/hil/tinyusb.json` (large pool) | no — can't SSH to htpc, and boards are already local |
| `hifiphile` (external rig) | `test/hil/hfp.json` | no outbound SSH to htpc/ci; SSH-reachable FROM both |
Default to **local**. Use **remote** only when on `htpc` and the user says `remote`/`ci.lan`. Never attempt remote on `ci`.

View File

@ -10,12 +10,12 @@ stack (`hcd_*`/`tuh_*`), or both. Its link peer is not always a Linux PC: a
TinyUSB host may face another TinyUSB board or a Linux gadget (e.g. a
Raspberry Pi). Pick capture channels by which end runs Linux, not by habit:
| Skill | Answers | Exists when |
|---|---|---|
| `usbmon` | what the Linux host exchanged (URBs) | a Linux PC is the link's host |
| Skill | Answers | Exists when |
|--------------------|----------------------------------------------------|---------------------------------------------------|
| `usbmon` | what the Linux host exchanged (URBs) | a Linux PC is the link's host |
| `usb-kernel-debug` | why the Linux kernel acted (dmesg / dynamic debug) | Linux on either end: PC host or Linux gadget peer |
| **`target-debug`** | **what the target did** (logs, driver state, PC) | always — either role, needs a debug probe |
| `usb-sniffer` | what crossed the wire (PIDs, handshakes, resets) | hardware tap cabled in — role-agnostic |
| **`target-debug`** | **what the target did** (logs, driver state, PC) | always — either role, needs a debug probe |
| `usb-sniffer` | what crossed the wire (PIDs, handshakes, resets) | hardware tap cabled in — role-agnostic |
For enumeration/transfer bugs the default posture is **dual-side capture**
both ends simultaneously: usbmon + a target
@ -35,12 +35,15 @@ python3 test/hil/board_lock.py release <board>
```
Board → probe mapping: `test/hil/tinyusb.json``flasher.name` is the probe
family, `flasher.uid` the **probe serial** (many identical probes on the rig:
J-Link needs `-SelectEmuBySN <uid>` / GDB server `-select usb=<uid>`; OpenOCD
`-c 'adapter serial <uid>'`). `JLINK_DEVICE` / `OPENOCD_OPTION` come from
`hw/bsp/<family>/boards/<board>/board.cmake` (or `board.mk`); find the family
with `ls -d hw/bsp/*/boards/<board>`. Run on the host that owns the probe —
config is `test/hil/tinyusb.json` on ci, `local.json` on htpc (`hil` skill).
family, `flasher.uid` the **probe serial** (many identical probes on the rig):
- Select the probe by serial: J-Link `-SelectEmuBySN <uid>`, its GDB server
`-select usb=<uid>`, OpenOCD `-c 'adapter serial <uid>'`.
- `JLINK_DEVICE` / `OPENOCD_OPTION`: from
`hw/bsp/<family>/boards/<board>/board.cmake` (or `board.mk`); family via
`ls -d hw/bsp/*/boards/<board>`.
- Run on the host that owns the probe — config `test/hil/tinyusb.json` on ci,
`local.json` on htpc (`hil` skill).
## Pick the least intrusive technique that can answer the question
@ -48,16 +51,16 @@ Observation can mask the bug — the ch32v307 Heisenbug changed behavior under
logging *and* under the debugger. If the bug disappears when instrumented,
that IS a finding (timing-sensitive): move down in intrusiveness, not up.
| Technique | Intrusiveness | Reach for it when |
|---|---|---|
| PC-sampling | none — no halt, no code change | core wedged/spinning somewhere unknown (rusb2 FRDY) |
| SWO exception trace / hw PC-sample | none — needs SWO pin wired | ISR ordering/timing with zero code change |
| Vector catch | none until a fault fires | crash-shaped wedges — autopsy AT the faulting pc |
| RAM ring-buffer | ~tens of cycles per event | ISR ordering/timing bugs (musb babble) |
| TU_LOG (RTT) | µs per line | logic bugs that survive logging (J-Link or OpenOCD rtt) |
| TU_LOG (UART) | ms per line — blocking write | same, when no debug-probe RTT path |
| dprintf / conditional breakpoint | halt+resume per hit (~ms) | low-rate probes post-wedge; never ISR-rate events |
| GDB halt / breakpoints | stops USB service entirely | post-mortem state autopsy once wedged |
| Technique | Intrusiveness | Reach for it when |
|------------------------------------|--------------------------------|---------------------------------------------------------|
| PC-sampling | none — no halt, no code change | core wedged/spinning somewhere unknown (rusb2 FRDY) |
| SWO exception trace / hw PC-sample | none — needs SWO pin wired | ISR ordering/timing with zero code change |
| Vector catch | none until a fault fires | crash-shaped wedges — autopsy AT the faulting pc |
| RAM ring-buffer | ~tens of cycles per event | ISR ordering/timing bugs (musb babble) |
| TU_LOG (RTT) | µs per line | logic bugs that survive logging (J-Link or OpenOCD rtt) |
| TU_LOG (UART) | ms per line — blocking write | same, when no debug-probe RTT path |
| dprintf / conditional breakpoint | halt+resume per hit (~ms) | low-rate probes post-wedge; never ISR-rate events |
| GDB halt / breakpoints | stops USB service entirely | post-mortem state autopsy once wedged |
## TU_LOG capture
@ -293,7 +296,7 @@ the wire itself: `usb-sniffer` skill (hardware tap, PID-level).
- J-Link (UM08001): <https://kb.segger.com/UM08001_J-Link_/_J-Trace_User_Guide> — flash breakpoints, RTT, SWO, monitor mode, Commander.
- OpenOCD: <https://openocd.org/doc/html/index.html> — `rtt`, `bp`/`wp`, `cortex_m vector_catch`/`maskisr`, `itm`/`tpiu`.
- "Debugging with GDB" (§5.1 = break/watch/dprintf): Tenth Edition (GDB 18)
via calibre/`read-doc` — NOT the 2002 Ninth-Edition txt also there — or
via calibre/`read-doc`, or
`curl -sL -o /tmp/gdb.pdf https://sourceware.org/gdb/current/onlinedocs/gdb.pdf`
(the HTML mirror blocks fetchers). Installed `arm-none-eabi-gdb`
`help <cmd>` is authoritative here.

View File

@ -7,12 +7,12 @@ description: Use when you need wire-level USB evidence that host-side capture ca
Extends the debugging trio with the layer below URBs:
| Skill | Answers |
|---|---|
| `usbmon` | what a Linux PC host exchanged (URBs) |
| `usb-kernel-debug` | why the Linux kernel acted (dmesg / dynamic debug) |
| `target-debug` | what the TinyUSB target did (device or host role) |
| **`usb-sniffer`** | **what actually crossed D+/D-** (PIDs, handshakes, resets, timing) |
| Skill | Answers |
|--------------------|--------------------------------------------------------------------|
| `usbmon` | what a Linux PC host exchanged (URBs) |
| `usb-kernel-debug` | why the Linux kernel acted (dmesg / dynamic debug) |
| `target-debug` | what the TinyUSB target did (device or host role) |
| **`usb-sniffer`** | **what actually crossed D+/D-** (PIDs, handshakes, resets, timing) |
Reach for it when usbmon can't see (device never binds, pre-enumeration
failures), can't be trusted (URB completed but did the wire really ACK?), or

View File

@ -29,18 +29,18 @@ tshark -r cap.pcapng -Y 'usb.device_address==26' # filter to one device
## Filter (`-Y '<expr>'`)
| Goal | Expression |
|---|---|
| One device / endpoint | `usb.device_address==26` / `usb.endpoint_address==0x81` |
| IN (to host) / OUT (from host) | `usb.endpoint_address.direction==1` / `==0` |
| Submit / Complete event | `usb.urb_type=='S'` / `=='C'` (char literal: single quotes) |
| Control / bulk / interrupt / iso | `usb.transfer_type==2` / `3` / `1` / `0` |
| Only transfers carrying data | `usb.data_len>0` |
| GET_DESCRIPTOR / SET_ADDRESS / SET_CONFIGURATION | `usb.setup.bRequest==6` / `5` / `9` |
| SET_INTERFACE / CLEAR_FEATURE (clear-halt) | `usb.setup.bRequest==11` / `1` |
| Descriptor type DEVICE/CONFIG/STRING/HID-report | `usb.bDescriptorType==1` / `2` / `3` / `0x22` |
| Class / vendor requests | `usb.bmRequestType.type!=0` |
| STALLs / errors | `usb.urb_status!=0 && usb.urb_status!=-115` |
| Goal | Expression |
|--------------------------------------------------|-------------------------------------------------------------|
| One device / endpoint | `usb.device_address==26` / `usb.endpoint_address==0x81` |
| IN (to host) / OUT (from host) | `usb.endpoint_address.direction==1` / `==0` |
| Submit / Complete event | `usb.urb_type=='S'` / `=='C'` (char literal: single quotes) |
| Control / bulk / interrupt / iso | `usb.transfer_type==2` / `3` / `1` / `0` |
| Only transfers carrying data | `usb.data_len>0` |
| GET_DESCRIPTOR / SET_ADDRESS / SET_CONFIGURATION | `usb.setup.bRequest==6` / `5` / `9` |
| SET_INTERFACE / CLEAR_FEATURE (clear-halt) | `usb.setup.bRequest==11` / `1` |
| Descriptor type DEVICE/CONFIG/STRING/HID-report | `usb.bDescriptorType==1` / `2` / `3` / `0x22` |
| Class / vendor requests | `usb.bmRequestType.type!=0` |
| STALLs / errors | `usb.urb_status!=0 && usb.urb_status!=-115` |
Combine with `&&` — e.g. one endpoint's data: `usb.endpoint_address==0x02 && usb.data_len>0`.

View File

@ -80,12 +80,12 @@ python3 test/hil/usbtest.py --serial <uid> --keep-binding --tests 29 # one case
## Debug ladder (escalate in order)
| errno | Meaning |
|---|---|
| 110 | timeout — endpoint NAKing forever / device wedged |
| 32 | EPIPE — unexpected STALL |
| 5 | EIO — iso packet errors (check `dmesg`: "N errors out of M") |
| 71 | EPROTO — device answered wrong / too slow (after HC retries) |
| errno | Meaning |
|-------|--------------------------------------------------------------|
| 110 | timeout — endpoint NAKing forever / device wedged |
| 32 | EPIPE — unexpected STALL |
| 5 | EIO — iso packet errors (check `dmesg`: "N errors out of M") |
| 71 | EPROTO — device answered wrong / too slow (after HC retries) |
1. `usbtest.py` per-case output + its captured `dmesg` (`TEST n` markers bracket each case).
2. **usbmon** (`usbmon` skill): URB-level ground truth. **It cannot show data toggles or NAKs**