diff options
| author | hathach <[email protected]> | 2026-07-23 10:18:11 +0700 |
|---|---|---|
| committer | hathach <[email protected]> | 2026-07-24 14:55:59 +0700 |
| commit | 8918c4fec4a99b174d480c043000fd4405678282 (patch) | |
| tree | ea49141343aedb97a9dabaac256915c945a27828 | |
| parent | 1b5c26b76e7194d05b82b0cccf76684716d3374d (diff) | |
docs(skills): rename debug skills, drop the PC-host/TinyUSB-device assumption
Rename usb-target-debug -> target-debug, usb-debug -> usb-kernel-debug,
usb-recover -> usb-kernel-recover (script filenames unchanged), and make all
debug skills/agents decide tool applicability by which end of the link runs
Linux: TinyUSB may run the device or host stack, and its peer may be a Linux
PC, another TinyUSB board, or a Linux gadget (e.g. Raspberry Pi UDC).
- usbmon: exists only when a Linux PC is the link's host
- usb-kernel-debug: either Linux end; allowlist gains dwc3/libcomposite/udc_core
for the gadget side of a Linux peer
- usb-sniffer: the only full-visibility capture when TinyUSB is the host
- target-debug: covers dcd_* and hcd_*/tuh_ debugging; channel choice by topology
- update target-debugger/hil-operator agents, pre-pr, hil-validate.js, and the
USB_RECOVER path constant in test/hil/usbtest.py
- CLAUDE.md: fold the dcd/hcd datasheet cross-check rule into the read-doc line
| -rw-r--r-- | .claude/agents/hil-operator.md | 6 | ||||
| -rw-r--r-- | .claude/agents/target-debugger.md | 31 | ||||
| -rw-r--r-- | .claude/skills/pre-pr/SKILL.md | 2 | ||||
| -rw-r--r-- | .claude/skills/target-debug/SKILL.md (renamed from .claude/skills/usb-target-debug/SKILL.md) | 42 | ||||
| -rw-r--r-- | .claude/skills/usb-debug/SKILL.md | 36 | ||||
| -rw-r--r-- | .claude/skills/usb-kernel-debug/SKILL.md | 47 | ||||
| -rwxr-xr-x | .claude/skills/usb-kernel-debug/scripts/usb_dyndbg.sh (renamed from .claude/skills/usb-debug/scripts/usb_dyndbg.sh) | 11 | ||||
| -rw-r--r-- | .claude/skills/usb-kernel-recover/SKILL.md (renamed from .claude/skills/usb-recover/SKILL.md) | 6 | ||||
| -rwxr-xr-x | .claude/skills/usb-kernel-recover/scripts/usb_recover.sh (renamed from .claude/skills/usb-recover/scripts/usb_recover.sh) | 0 | ||||
| -rw-r--r-- | .claude/skills/usb-sniffer/SKILL.md | 14 | ||||
| -rw-r--r-- | .claude/skills/usbmon/SKILL.md | 4 | ||||
| -rw-r--r-- | .claude/skills/usbtest/SKILL.md | 2 | ||||
| -rw-r--r-- | .claude/workflows/hil-validate.js | 2 | ||||
| -rw-r--r-- | CLAUDE.md | 2 | ||||
| -rwxr-xr-x | test/hil/usbtest.py | 6 |
15 files changed, 120 insertions, 91 deletions
diff --git a/.claude/agents/hil-operator.md b/.claude/agents/hil-operator.md index c48ceb8bd..d19eca047 100644 --- a/.claude/agents/hil-operator.md +++ b/.claude/agents/hil-operator.md @@ -8,8 +8,8 @@ model: sonnet You operate physical USB test hardware. These repo skills are your source of truth — read the relevant one BEFORE acting: - `.claude/skills/hil/SKILL.md` — run `hostname` first (host `ci` = local mode with `test/hil/tinyusb.json`; host `htpc` = local `local.json` or remote via `test/hil/hil_ci.sh`); the board lock protocol; exact `hil_test.py` invocations. -- `.claude/skills/usb-recover/SKILL.md` — only when a device/fixture is wedged or processes hang in D state. -- `.claude/skills/usb-debug/SKILL.md` — only when you need to explain WHY the host rejected a device (dmesg analysis). +- `.claude/skills/usb-kernel-recover/SKILL.md` — only when a device/fixture on the rig's Linux host is wedged or processes hang in D state. +- `.claude/skills/usb-kernel-debug/SKILL.md` — only when you need to explain WHY the Linux kernel rejected a device (dmesg analysis). ## Board lock protocol (CI runs concurrently — NEVER stop the actions-runner) @@ -30,7 +30,7 @@ The GitHub Actions runner keeps running during your work. Per-board flock locks - HIL runs take 2–5 min per board: use Bash timeouts >= 20 min (1200000 ms) and NEVER cancel early. - One hardware action at a time. You are never run concurrently with another hil-operator. -- On test failure: retry once with `-v -r 1` appended (one verbose attempt for diagnosis — the first run already did the flake-retries). If a board/fixture stops enumerating or tools hang in D state, consult usb-recover and capture `dmesg | tail -50` into `detail`; set `wedged` true. +- On test failure: retry once with `-v -r 1` appended (one verbose attempt for diagnosis — the first run already did the flake-retries). If a board/fixture stops enumerating or tools hang in D state, consult usb-kernel-recover and capture `dmesg | tail -50` into `detail`; set `wedged` true. ## Output contract diff --git a/.claude/agents/target-debugger.md b/.claude/agents/target-debugger.md index c1df47cb2..861600428 100644 --- a/.claude/agents/target-debugger.md +++ b/.claude/agents/target-debugger.md @@ -1,25 +1,32 @@ --- name: target-debugger -description: Root-cause one USB misbehavior on real HIL hardware by instrumenting the TinyUSB device side — TU_LOG/RTT, RAM ring-buffer trace, GDB autopsy, J-Link PC-sampling — correlated with host-side and wire-level capture. Long serial debug loop under one held board lock; strictly one instance. Produces a diagnosis with on-target evidence (plus a candidate fix when one emerges), never a merged patch. +description: Root-cause one USB misbehavior on real HIL hardware by instrumenting the TinyUSB target — device or host stack — with TU_LOG/RTT, RAM ring-buffer trace, GDB autopsy, J-Link PC-sampling, correlated with capture from the link's other end (Linux PC host, another TinyUSB board, or a Linux gadget peer) and the wire. Long serial debug loop under one held board lock; strictly one instance. Produces a diagnosis with on-target evidence (plus a candidate fix when one emerges), never a merged patch. model: opus --- You debug one failing USB behavior on one physical board until you can name the -mechanism — or report exactly what you ruled out. These repo skills are your -source of truth; read the relevant SKILL.md BEFORE acting: +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: -- `.claude/skills/usb-target-debug/SKILL.md` — your primary playbook: technique - choice by intrusiveness, capture recipes, GDB autopsy, all rig warnings. +- `.claude/skills/target-debug/SKILL.md` — your primary playbook: technique + choice by intrusiveness, channel choice by link topology, capture recipes, + 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` — host-side URB capture (the default posture - is dual-side: host + target simultaneously). +- `.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) or when usbmon and device logs disagree — the wire arbitrates. -- `.claude/skills/usb-debug/SKILL.md` — why the host acted (dmesg/dynamic debug). -- `.claude/skills/usb-recover/SKILL.md` — only when the DUT or fixture wedges - the host stack. + 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. ## The loop (deliberately serial — no fan-out) diff --git a/.claude/skills/pre-pr/SKILL.md b/.claude/skills/pre-pr/SKILL.md index d4c35f7e5..3f062db78 100644 --- a/.claude/skills/pre-pr/SKILL.md +++ b/.claude/skills/pre-pr/SKILL.md @@ -38,5 +38,5 @@ Invoke the Workflow tool: - Per-stage table: unit / build:<board> / size / pvs, then HIL per board — pass/fail with the first error for each failure. - If the hardware result has non-empty `locked` (a CI job held those boards): ask the user with AskUserQuestion — **Force now** (re-invoke `hil-validate` with `force: true` for those boards; user accepts the risk of colliding with a mid-test CI job), **Keep waiting** (re-invoke `hil-validate` for them after a few minutes; ask again if still locked), or **Accept** the partial verdict. Never force without the user's answer. -- Wedged boards: point at `.claude/skills/usb-recover/SKILL.md`. +- Wedged boards: point at `.claude/skills/usb-kernel-recover/SKILL.md`. - End with a clear ship / no-ship verdict and what to fix first. diff --git a/.claude/skills/usb-target-debug/SKILL.md b/.claude/skills/target-debug/SKILL.md index 71fac98f2..c82237ea3 100644 --- a/.claude/skills/usb-target-debug/SKILL.md +++ b/.claude/skills/target-debug/SKILL.md @@ -1,23 +1,28 @@ --- -name: usb-target-debug -description: Use when a TinyUSB device misbehaves on real hardware and host-side capture can't explain it — a HIL test fails but usbmon shows only Submits with no Completes, the device silently NAKs, wedges, STALLs, babbles, or drops data, EP0 starves, an ISR or DCD/HCD state bug is suspected — and you need device-side evidence: TU_LOG/RTT logs, GDB state dumps, a RAM ring-buffer event trace, or PC-sampling of where the core spins. +name: target-debug +description: Use when TinyUSB firmware — device or host stack — misbehaves on real hardware and capture from the other end can't explain it: a HIL test fails but usbmon shows only Submits with no Completes, the device silently NAKs, wedges, STALLs, babbles, or drops data, EP0 starves, tuh_ enumeration of an attached device fails, an ISR or DCD/HCD state bug is suspected — and you need target-side evidence: TU_LOG/RTT logs, GDB state dumps, a RAM ring-buffer event trace, or PC-sampling of where the core spins. --- -# usb-target-debug — device-side capture & debugging on the HIL rig +# target-debug — target-side capture & debugging on the HIL rig -Completes the debugging trio (the `usb-sniffer` skill adds a fourth, -wire-level view when hardware tapping is available): +The **target** is whichever MCU runs TinyUSB — device stack (`dcd_*`), host +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 | -|---|---| -| `usbmon` | what the host actually exchanged (URBs) | -| `usb-debug` | why the host acted (dmesg / dynamic debug) | -| **`usb-target-debug`** | **what the device did** (logs, driver state, PC) | -| `usb-sniffer` | what crossed the wire (PIDs, handshakes, resets — hardware tap) | +| 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 | For enumeration/transfer bugs the default posture is **dual-side capture** — -usbmon on the host *and* a target-side channel, simultaneously — not -host-first-then-escalate. +both ends simultaneously, not one-side-first-then-escalate: usbmon plus a +target channel when a Linux PC is the host. When TinyUSB is the host there is +no usbmon on either end — pair the target channel with the wire +(`usb-sniffer`) and, if the peer is a Linux gadget, `usb-kernel-debug` on the +peer. ## Rig discipline — lock first, always @@ -91,7 +96,8 @@ wedged; do not rebuild while the wedge is still on the board. The debug-loop specifics: ```gdb -p/x _usbd_dev.ep_status # usbd core [epnum][dir] (1=IN): busy/stalled/claimed +p/x _usbd_dev.ep_status # device stack: usbd [epnum][dir] (1=IN): busy/stalled/claimed +p _usbh_devices[0] # host stack: usbh per-device state (addr, enum/config) p/x <port's private state> # per-port names — read the board's dcd_*.c first x/32wx <USB peripheral base> # raw EP/FIFO regs; base = the macro the dcd uses watch xfer_status[2][1].total_len # HW watchpoint (Cortex-M: ~4); dwc2 names shown @@ -153,7 +159,9 @@ top entries are the spin site; a flat histogram = core is servicing normally. ## Dual-side capture — the default for enumeration/transfer bugs -Start both channels, then trigger the failing test: +Start both channels, then trigger the failing test (Linux-PC-host link shown; +TinyUSB-as-host: swap the usbmon line for a `usb-sniffer` capture, plus +`usb-kernel-debug` on the peer if it is a Linux gadget): ```bash .claude/skills/usbmon/scripts/usbcap.sh cafe: 30 /tmp/host.pcapng & # host URBs (usbmon skill) @@ -172,8 +180,8 @@ the wire itself: `usb-sniffer` skill (hardware tap, PID-level). - **Halting/resetting via the probe does NOT disconnect the device**: a DWC2 soft-connect pullup stays up through core halt *and* reset, so the host's - stuck URBs stay stuck and a wedged DUT stays wedged — recover the host side - with the `usb-recover` skill. + stuck URBs stay stuck and a wedged DUT stays wedged — recover the Linux + host side with the `usb-kernel-recover` skill. - **A bug that vanishes under LOG=2 is a timing bug**, not fixed: switch to the ring buffer; if it vanishes under GDB too, PC-sampling only. - **UART TU_LOG blocks in the write path** (worst perturbation, including diff --git a/.claude/skills/usb-debug/SKILL.md b/.claude/skills/usb-debug/SKILL.md deleted file mode 100644 index 20ab7d764..000000000 --- a/.claude/skills/usb-debug/SKILL.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -name: usb-debug -description: Use when USB enumeration fails or misbehaves and usbmon alone can't explain WHY the host acted — port reset storms, repeated re-enumeration, address errors, xHCI ring/command errors, "device descriptor read error", babble, or when you need the host driver's own reasoning from dmesg on the ci HIL rig. ---- - -# usb-debug — host-side kernel dynamic debug for USB - -usbmon shows the URBs; kernel **dynamic debug** shows the host driver's -*reasoning* usbmon can't: port resets and their causes, enumeration retries, -address (re)assignment, EP halts, xHCI ring/command errors. - -Run this skill's `scripts/usb_dyndbg.sh` with `sudo` (abbreviated to -`usb_dyndbg.sh` in the examples below). It flips the dynamic-debug print flag -for an allowlisted set of USB host modules only: - -```bash -sudo usb_dyndbg.sh on usbcore xhci_hcd # enable +p; pick modules from `lsusb -t` Driver= -sudo usb_dyndbg.sh status [module] # list enabled print sites -sudo usb_dyndbg.sh off usbcore xhci_hcd # ALWAYS turn off when done — very noisy -``` - -Allowlisted modules: `usbcore xhci_hcd xhci_pci xhci_pci_renesas ehci_hcd -ehci_pci ohci_hcd ohci_pci uhci_hcd dwc2 cdc_acm usb_storage uas`. - -## Workflow - -1. `sudo usb_dyndbg.sh on usbcore <hcd-module>` — `usbcore` for enumeration/hub - logic, plus the controller module (`lsusb -t` shows the driver per bus). -2. Reproduce (replug / re-enumerate / rerun the failing test) while following - `sudo dmesg -w` (or grab `sudo dmesg | tail` afterwards). -3. `sudo usb_dyndbg.sh off ...` — leaving it on floods the log and skews timing. - -Pair with the `usbmon` skill: usbmon for what crossed the bus, dynamic debug for -why the host reacted. For a wedged device/bus use the `usb-recover` skill. - -Requires `CONFIG_DYNAMIC_DEBUG` and mounted debugfs (standard on distro kernels). diff --git a/.claude/skills/usb-kernel-debug/SKILL.md b/.claude/skills/usb-kernel-debug/SKILL.md new file mode 100644 index 000000000..e4169b049 --- /dev/null +++ b/.claude/skills/usb-kernel-debug/SKILL.md @@ -0,0 +1,47 @@ +--- +name: usb-kernel-debug +description: Use when USB enumeration fails or misbehaves and packet/URB capture can't explain WHY the Linux kernel acted — port reset storms, repeated re-enumeration, address errors, xHCI ring/command errors, "device descriptor read error", babble — on whichever end of the link runs Linux: the PC host when testing a TinyUSB device, or a Linux gadget peer (e.g. Raspberry Pi) when testing the TinyUSB host stack. +--- + +# usb-kernel-debug — Linux kernel dynamic debug for USB + +Kernel **dynamic debug** shows the Linux side's *reasoning* that packet +capture can't: port resets and their causes, enumeration retries, address +(re)assignment, EP halts, xHCI ring/command errors. It applies wherever Linux +sits in the link — the rig PC when it is the host, or a Linux gadget peer +(dwc2/UDC + gadget modules) when TinyUSB is the host. It cannot see inside +the TinyUSB MCU — that is the `target-debug` skill. + +Run this skill's `scripts/usb_dyndbg.sh` with `sudo` (abbreviated to +`usb_dyndbg.sh` in the examples below). It flips the dynamic-debug print flag +for an allowlisted set of USB modules only: + +```bash +sudo usb_dyndbg.sh on usbcore xhci_hcd # enable +p; pick modules from `lsusb -t` Driver= +sudo usb_dyndbg.sh status [module] # list enabled print sites +sudo usb_dyndbg.sh off usbcore xhci_hcd # ALWAYS turn off when done — very noisy +``` + +Allowlisted modules: `usbcore xhci_hcd xhci_pci xhci_pci_renesas ehci_hcd +ehci_pci ohci_hcd ohci_pci uhci_hcd dwc2 dwc3 cdc_acm usb_storage uas +libcomposite udc_core` (`dwc2`/`dwc3` + the last two cover a Linux gadget +peer's device side). + +## Workflow + +1. `sudo usb_dyndbg.sh on usbcore <hcd-module>` — `usbcore` for enumeration/hub + logic, plus the controller module (`lsusb -t` shows the driver per bus). + On a gadget peer: `dwc2` (or `dwc3`) + `udc_core` + `libcomposite` instead — + run on the peer itself (its SSH/serial console); the script is self-contained, + copy it over or use the raw `dynamic_debug/control` writes from the `usbmon` + skill. +2. Reproduce (replug / re-enumerate / rerun the failing test) while following + `sudo dmesg -w` (or grab `sudo dmesg | tail` afterwards). +3. `sudo usb_dyndbg.sh off ...` — leaving it on floods the log and skews timing. + +On a Linux-PC-host link, pair with the `usbmon` skill: usbmon for what crossed +the bus, dynamic debug for why the kernel reacted. A gadget peer's UDC has no +usbmon — pair with `usb-sniffer` on the wire instead. For a wedged device/bus +on the rig PC use the `usb-kernel-recover` skill. + +Requires `CONFIG_DYNAMIC_DEBUG` and mounted debugfs (standard on distro kernels). diff --git a/.claude/skills/usb-debug/scripts/usb_dyndbg.sh b/.claude/skills/usb-kernel-debug/scripts/usb_dyndbg.sh index 0dc880469..3923cdc6d 100755 --- a/.claude/skills/usb-debug/scripts/usb_dyndbg.sh +++ b/.claude/skills/usb-kernel-debug/scripts/usb_dyndbg.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash -# usb_dyndbg.sh — toggle kernel dynamic-debug on USB host drivers; run with sudo. -# Flips +p/-p only on an allowlisted set of USB modules, so it can't reach -# arbitrary kernel debug or unrelated subsystems. +# usb_dyndbg.sh — toggle kernel dynamic-debug on USB drivers (host or gadget +# side); run with sudo. Flips +p/-p only on an allowlisted set of USB modules, +# so it can't reach arbitrary kernel debug or unrelated subsystems. # # Usage: # sudo usb_dyndbg.sh on <module>... # enable +p (e.g. usbcore xhci_hcd) @@ -10,8 +10,9 @@ set -euo pipefail CTL=/sys/kernel/debug/dynamic_debug/control -# Allowlist: USB host-controller + core + common host class drivers. -ALLOW='usbcore xhci_hcd xhci_pci xhci_pci_renesas ehci_hcd ehci_pci ohci_hcd ohci_pci uhci_hcd dwc2 cdc_acm usb_storage uas' +# Allowlist: USB core + host-controller + common class drivers, plus the +# gadget/UDC side of a Linux peer (dwc2/dwc3, udc_core, libcomposite). +ALLOW='usbcore xhci_hcd xhci_pci xhci_pci_renesas ehci_hcd ehci_pci ohci_hcd ohci_pci uhci_hcd dwc2 dwc3 cdc_acm usb_storage uas libcomposite udc_core' die() { echo "usb_dyndbg: $*" >&2; exit 1; } usage() { diff --git a/.claude/skills/usb-recover/SKILL.md b/.claude/skills/usb-kernel-recover/SKILL.md index beb6fd862..3f03722fe 100644 --- a/.claude/skills/usb-recover/SKILL.md +++ b/.claude/skills/usb-kernel-recover/SKILL.md @@ -1,9 +1,9 @@ --- -name: usb-recover -description: Use when a USB device or fixture on the ci HIL rig is stuck, hung, not enumerating, or wedged after a failed flash or test, or when processes touching USB (testusb, JLinkExe, uhubctl, libusb tools) start hanging in D state. +name: usb-kernel-recover +description: Use when a USB device or fixture attached to the ci HIL rig's Linux host is stuck, hung, not enumerating, or wedged after a failed flash or test, or when processes touching USB (testusb, JLinkExe, uhubctl, libusb tools) start hanging in D state. Linux-kernel-side only — a bus owned by a TinyUSB host is out of reach (reset the target / cycle its VBUS instead); the rig's probes and serial fixtures always remain in scope. --- -# USB Recovery on the HIL Rig +# USB Recovery on the HIL Rig (Linux kernel side) Run this skill's `scripts/usb_recover.sh` with `sudo` (abbreviated to `usb_recover.sh` in the examples below). It wraps the sysfs reset actions, a diff --git a/.claude/skills/usb-recover/scripts/usb_recover.sh b/.claude/skills/usb-kernel-recover/scripts/usb_recover.sh index 7652253fa..7652253fa 100755 --- a/.claude/skills/usb-recover/scripts/usb_recover.sh +++ b/.claude/skills/usb-kernel-recover/scripts/usb_recover.sh diff --git a/.claude/skills/usb-sniffer/SKILL.md b/.claude/skills/usb-sniffer/SKILL.md index 7c2cd2644..a20aecf05 100644 --- a/.claude/skills/usb-sniffer/SKILL.md +++ b/.claude/skills/usb-sniffer/SKILL.md @@ -1,6 +1,6 @@ --- name: usb-sniffer -description: Use when you need wire-level USB evidence that host-side capture can't provide — a device that never enumerates (usbmon shows nothing or only Submits), suspected NAK storms/STALL/babble/bad handshakes, bus-reset or enumeration timing, split-transaction issues, or a usbmon-vs-device-log disagreement the wire must arbitrate. Captures LS/FS/HS packets (PIDs, tokens, handshakes, SE0/line states) with the ataradov usb-sniffer hardware into Wireshark pcapng. +description: Use when you need wire-level USB evidence that host-side capture can't provide — a device that never enumerates (usbmon shows nothing or only Submits), suspected NAK storms/STALL/babble/bad handshakes, bus-reset or enumeration timing, split-transaction issues, a usbmon-vs-device-log disagreement the wire must arbitrate, or any link where TinyUSB is the host (no Linux PC host to run usbmon on). Captures LS/FS/HS packets (PIDs, tokens, handshakes, SE0/line states) with the ataradov usb-sniffer hardware into Wireshark pcapng. --- # usb-sniffer — wire-level capture with the ataradov hardware analyzer @@ -9,14 +9,16 @@ Extends the debugging trio with the layer below URBs: | Skill | Answers | |---|---| -| `usbmon` | what the host software exchanged (URBs) | -| `usb-debug` | why the host acted (dmesg / dynamic debug) | -| `usb-target-debug` | what the device firmware did | +| `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) or can't be trusted (URB completed but did the wire really ACK?). -For everything visible in URBs, usbmon is cheaper — no hardware, no locks. +failures), can't be trusted (URB completed but did the wire really ACK?), or +doesn't exist — a link where TinyUSB is the host has no usbmon on either end +(an MCU host runs no kernel; a Linux gadget peer's UDC bypasses usbmon). +Where a Linux PC is the host, usbmon is cheaper — no hardware, no locks. ## Rig inventory — find the sniffer and what it taps diff --git a/.claude/skills/usbmon/SKILL.md b/.claude/skills/usbmon/SKILL.md index 85ec33248..a2e7c1ac1 100644 --- a/.claude/skills/usbmon/SKILL.md +++ b/.claude/skills/usbmon/SKILL.md @@ -1,11 +1,11 @@ --- name: usbmon -description: Use when capturing, analyzing, or debugging USB bus traffic for TinyUSB device development on Linux — enumeration failures, STALLed control transfers, missing/short bulk or interrupt transfers, isochronous/audio dropouts, or descriptor problems. Captures host-side URBs with usbmon + tshark into a Wireshark pcapng and decodes them. Use whenever you need to see what the host actually exchanged with a device on real hardware, even if the user just says "sniff USB", "capture the enumeration", or "why won't my device enumerate". +description: Use when capturing, analyzing, or debugging USB bus traffic on a link where a Linux PC is the host (TinyUSB in device role) — enumeration failures, STALLed control transfers, missing/short bulk or interrupt transfers, isochronous/audio dropouts, or descriptor problems. Captures host-side URBs with usbmon + tshark into a Wireshark pcapng and decodes them. Not applicable when TinyUSB is the host — no URBs traverse the PC (use usb-sniffer / target-debug). Use whenever you need to see what the Linux host actually exchanged with a device on real hardware, even if the user just says "sniff USB", "capture the enumeration", or "why won't my device enumerate". --- # usbmon — capture & debug USB traffic -`usbmon` records host-side **URBs** — control / bulk / interrupt / isochronous transfers, descriptors, class requests, STALLs, short packets — i.e. exactly what the host exchanged with a device. Use it to debug a TinyUSB device on real hardware. (It's host/URB-level, not wire-level; for SOF/ACK/electrical use a hardware analyzer.) +`usbmon` records host-side **URBs** — control / bulk / interrupt / isochronous transfers, descriptors, class requests, STALLs, short packets — i.e. exactly what the host exchanged with a device. Use it to debug a TinyUSB device on real hardware. (It's host/URB-level, not wire-level; for SOF/ACK/electrical use a hardware analyzer.) It exists only on the Linux host side of a link: when TinyUSB runs the *host* stack (peer = another TinyUSB board or a Linux gadget, e.g. a Raspberry Pi), neither end has usbmon — capture the wire (`usb-sniffer` skill) or instrument the target (`target-debug` skill). **Setup (assumed in place):** `usbmon` loaded and a udev rule `SUBSYSTEM=="usbmon", GROUP="wireshark", MODE="0640"` with your user in the `wireshark` group — so `tshark` captures with no `sudo`. Freshly added to the group? The running shell doesn't have it yet (group adds need a new login) — wrap captures in `sg wireshark -c 'tshark -i usbmon3 -s 128 -a duration:30 -w /tmp/cap.pcapng'`; reading a finished `.pcapng` (`tshark -r`) needs no group. `-s 128` (snaplen) keeps only URB headers/status, not payloads — use it for long/high-throughput captures. diff --git a/.claude/skills/usbtest/SKILL.md b/.claude/skills/usbtest/SKILL.md index 8146d94e4..850781d6e 100644 --- a/.claude/skills/usbtest/SKILL.md +++ b/.claude/skills/usbtest/SKILL.md @@ -1,6 +1,6 @@ --- name: usbtest -description: Use when running, debugging, or porting the Linux usbtest/testusb battery (examples/device/usbtest, cafe:4010) — device "did not bind", SET_CONFIGURATION fails, a case fails with errno 110/32/5/71, toggle-clear/halt/unlink/iso failures, iso packets dropped, or a new MCU/DCD needs the full 30/30 sign-off. +description: Use when running, debugging, or porting the Linux usbtest/testusb battery (examples/device/usbtest, cafe:4010) — device "did not bind", SET_CONFIGURATION fails, a case fails with errno 110/32/5/71, toggle-clear/halt/unlink/iso failures, iso packets dropped, or a new MCU/DCD needs the full 30/30 sign-off. Needs a Linux PC as the link's host driving TinyUSB in device role — it exercises the DCD, not the TinyUSB host stack. --- # usbtest — porting & debugging the Linux kernel USB battery diff --git a/.claude/workflows/hil-validate.js b/.claude/workflows/hil-validate.js index aa0556abc..50559135f 100644 --- a/.claude/workflows/hil-validate.js +++ b/.claude/workflows/hil-validate.js @@ -52,7 +52,7 @@ if (!args.force) { } const wedged = results.filter(r => r.wedged).map(r => r.board) -if (wedged.length) log(`WEDGED boards needing usb-recover: ${wedged.join(', ')}`) +if (wedged.length) log(`WEDGED boards needing usb-kernel-recover: ${wedged.join(', ')}`) // Workers cannot prompt the user — surface still-locked boards for the main // session to ask: force (re-invoke with force: true), wait, or accept. const locked = args.force ? [] : results.filter(r => !r.pass && r.detail.startsWith('board locked')).map(r => r.board) @@ -116,7 +116,7 @@ Cutting a release — version bump, regenerated files, the per-release changelog ## References -- MCU reference manuals, datasheets, schematics: before answering register/bitfield/pinout/errata/timing questions from memory or the web, use the `read-doc` skill (`.claude/skills/read-doc/SKILL.md`) to search and read them from `$HOME/Documents/calibre-library` (skill no-ops if the library is absent). +- MCU reference manuals, datasheets, schematics: before answering register/bitfield/pinout/errata/timing questions from memory or the web — or changing a specific dcd/hcd driver — use the `read-doc` skill (`.claude/skills/read-doc/SKILL.md`) to cross-check against docs in `$HOME/Documents/calibre-library`; tell the user if the needed document is missing (skill no-ops if the library is absent). - 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`. diff --git a/test/hil/usbtest.py b/test/hil/usbtest.py index a2841f0b6..e17705a48 100755 --- a/test/hil/usbtest.py +++ b/test/hil/usbtest.py @@ -38,7 +38,7 @@ PID = '4010' GZ_REF = '0525 a4a0' # copy Gadget Zero's capability profile (ctrl_out+iso+intr) SYS_USB = Path('/sys/bus/usb/devices') DRIVER = Path('/sys/bus/usb/drivers/usbtest') -USB_RECOVER = Path(__file__).resolve().parents[2] / '.claude/skills/usb-recover/scripts/usb_recover.sh' +USB_RECOVER = Path(__file__).resolve().parents[2] / '.claude/skills/usb-kernel-recover/scripts/usb_recover.sh' PATTERN_PARAM = Path('/sys/module/usbtest/parameters/pattern') # Battery per tier, in run order: control sanity first, then simple bulk, @@ -391,7 +391,7 @@ def main(): if pci: print(f'aborting battery: kernel-side hang, device wedged mid-transfer.\n' f'auto-recovering: sudo {USB_RECOVER} pci-reset {pci} ' - f'(see .claude/skills/usb-recover)', file=sys.stderr) + f'(see .claude/skills/usb-kernel-recover)', file=sys.stderr) # FLR frees the D-state ioctl without the device lock; must run BEFORE # any unbind/remove_id, which would deadlock the bus otherwise if sudo([str(USB_RECOVER), 'pci-reset', pci]).returncode != 0: @@ -418,7 +418,7 @@ def main(): try: if unrecovered_hang: # testusb is still stuck in a usbfs ioctl holding the device lock; remove_id/unbind - # would join the convoy and deadlock the bus (see usb-recover skill) — leave it be + # would join the convoy and deadlock the bus (see usb-kernel-recover skill) — leave it be print('skipping cleanup after unrecovered hang: reboot required to release the bus', file=sys.stderr) elif not args.keep_binding: |
