-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
usbtest: device-side peer for the Linux kernel usbtest battery + DCD fixes across 11 ports #3758
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
hathach
wants to merge
24
commits into
master
Choose a base branch
from
usbtest
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
24 commits
Select commit
Hold shift + click to select a range
33cabfe
class/vendor: add interrupt/iso endpoint pairs and alt-setting support
hathach fd63ad6
usbd: forward vendor EP0 requests; clear ep state on iso activate
hathach dfb53cb
dcd: route samd/rusb2/ip3511/nrf5x through iso alloc/activate
hathach 8ec71dc
dcd(samd): implement iso alloc/activate
hathach 7b1eb4f
dcd(rusb2): iso alloc/activate; bound the FIFO-ready wait
hathach 93b5719
dcd(ip3511): iso alloc/activate; retire armed buffers via EPSKIP
hathach 4bbb235
dcd(nrf5x): errata 199 DMA workaround + iso alloc/activate
hathach ad7acc8
dcd(rp2040): re-issue in-flight transfer on clear-halt toggle reset
hathach 0464636
dcd(fsdev): don't disarm an armed endpoint on clear-halt toggle reset
hathach 3fc60ea
dcd(musb): flush TX FIFO on halt; don't load a disarmed pipe
hathach 9904489
dcd(ch32-usbhs): re-queue the pending OUT read on clear-halt
hathach c97c0a1
dcd(ch32-usbfs): isochronous support
hathach 90d48c2
bsp(ch32): naked fsdev ISRs so nested USBD IRQs return safely
hathach 5dbc153
example(usbtest): device-side peer for the Linux kernel usbtest battery
hathach bab21a2
test/hil: usbtest.py runner + HIL integration
hathach 411fc41
audio: drop stale EP-busy note in audiod_set_interface
hathach 79f7bda
skills: usbtest/usb-debug/usb-recover docs, hil runner notes
hathach 628e0e2
dcd(ip3511): clear Active directly on stall/iso-activate, keep EPSKIP…
hathach 23242ac
dcd(ch32_usbfs): reset stale transfer state in dcd_edpt_iso_activate
hathach 24f8bce
rusb2: EP0 OUT reliability, HS UTMI PHY power-up, FS-only build support
hathach e02f931
test/hil: usbtest fleet enablement, shuffled scheduling, unique PIDs
hathach a686c70
Merge remote-tracking branch 'origin/master' into usbtest
hathach ca402a0
dcd(ci_hs): stale overlay fix; run usbtest on lpcxpresso43s67
hathach 59f02a1
dwc2: fix EP0 OUT dcache invalidate range; run usbtest on espressif s…
hathach File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| --- | ||
| 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). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| #!/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. | ||
| # | ||
| # Usage: | ||
| # sudo usb_dyndbg.sh on <module>... # enable +p (e.g. usbcore xhci_hcd) | ||
| # sudo usb_dyndbg.sh off <module>... # disable -p | ||
| # sudo usb_dyndbg.sh status [module] # show enabled sites (or one module's sites) | ||
| 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' | ||
|
|
||
| die() { echo "usb_dyndbg: $*" >&2; exit 1; } | ||
| usage() { | ||
| echo "usage: usb_dyndbg.sh {on|off} <module>... modules: $ALLOW" >&2 | ||
| echo " usb_dyndbg.sh status [module]" >&2 | ||
| exit 2 | ||
| } | ||
| allowed() { local m; for m in $ALLOW; do [ "$m" = "$1" ] && return 0; done; return 1; } | ||
|
|
||
| [ -e "$CTL" ] || die "dynamic_debug unavailable (need CONFIG_DYNAMIC_DEBUG + debugfs mounted)" | ||
|
|
||
| action=${1:-}; shift || true | ||
| case "$action" in | ||
| on|off) | ||
| [ "$#" -ge 1 ] || usage | ||
| flag='+p'; [ "$action" = off ] && flag='-p' | ||
| for m in "$@"; do allowed "$m" || die "module not allowlisted: $m"; done | ||
| for m in "$@"; do echo "module $m $flag" > "$CTL"; echo "dynamic debug $action: $m"; done | ||
| ;; | ||
| status) | ||
| m=${1:-} | ||
| if [ -n "$m" ]; then | ||
| allowed "$m" || die "module not allowlisted: $m" | ||
| grep -E "\[$m\]" "$CTL" || echo "(no sites for $m)" | ||
| else | ||
| grep -E '=p( |$)' "$CTL" || echo "(no print sites enabled)" | ||
| fi | ||
| ;; | ||
| *) | ||
| usage | ||
| ;; | ||
| esac |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,93 @@ | ||
| --- | ||
| 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. | ||
| --- | ||
|
|
||
| # USB Recovery on the HIL Rig | ||
|
|
||
| Run this skill's `scripts/usb_recover.sh` with `sudo` (abbreviated to | ||
| `usb_recover.sh` in the examples below). It wraps four sysfs reset actions plus | ||
| a resolver: | ||
|
|
||
| ```bash | ||
| sudo usb_recover.sh resolve /dev/ttyACM3 # /dev node -> busport (e.g. 3-4.7); also ttyUSB*, sg* | ||
| sudo usb_recover.sh authorized <busport> # deauthorize+reauthorize: re-enumerate, no VBUS cut | ||
| sudo usb_recover.sh rebind <busport> # usb driver unbind+bind: re-probe | ||
| sudo usb_recover.sh pci-rebind <pciaddr> # whole HCD controller unbind+bind, e.g. 0000:02:00.0 | ||
| sudo usb_recover.sh pci-reset <pciaddr> # PCI function-level reset: kills URBs at HW level, no device lock | ||
| sudo usb_recover.sh pci-bind <pciaddr> [drv] # re-bind a DRIVERLESS controller (auto-tries xHCI drivers) | ||
| ``` | ||
|
|
||
| ## Decide first: is anything stuck in D state? | ||
|
|
||
| ```bash | ||
| ps -eo pid,stat,wchan:30,cmd | awk '$2 ~ /D/' | ||
| ``` | ||
|
|
||
| **If yes** (uninterruptible sleep, typically a usbfs ioctl — e.g. testusb inside | ||
| `usb_sg_wait`): run `pci-reset` and NOTHING ELSE first: | ||
|
|
||
| ```bash | ||
| sudo usb_recover.sh pci-reset <pciaddr> | ||
| ``` | ||
|
|
||
| FLR kills the URBs at the hardware level without taking the per-device lock; | ||
| the ioctl then returns and the convoy unwinds on its own. | ||
|
|
||
| **Not every controller supports FLR.** The Renesas uPD720201 (`0000:01:00.0`) | ||
| has no reset method — `pci-reset` fails with `Inappropriate ioctl for device` | ||
| (ENOTTY). On those, there is no clean software D-state cure — a VM reboot is NOT | ||
| reliable (the MosChip downstream hubs latch up across the PCIe reset and need a | ||
| physical replug); ask the operator for a full PVE host power cycle instead. Do NOT | ||
| fall through to `pci-rebind` (see next). | ||
|
|
||
| **`pci-rebind` can strand the controller driverless.** Its unbind succeeds but, | ||
| with a D-state process still holding a URB, the *re-bind* hangs — leaving the | ||
| PCI device with **no driver** (`/sys/bus/pci/devices/<addr>/driver` gone) and the | ||
| whole controller's fixtures offline. A second `pci-rebind` then dies with "no | ||
| driver bound". Recover with `pci-bind <addr>` (re-attaches the xHCI driver); | ||
| if that also hangs because the D-state URB is unkillable, only a full PVE host | ||
| power cycle (operator action) recovers. The Renesas binds via `xhci-pci-renesas` (firmware loader), others via | ||
| `xhci_hcd` — `pci-bind` auto-tries both, or pass the driver explicitly. | ||
|
|
||
| **Ordering is critical.** `authorized`/`rebind`/`pci-rebind` all take the | ||
| per-device lock the stuck ioctl holds — they block and join the convoy, and | ||
| soon every libusb tool (uhubctl, JLinkExe) hangs too. Worse, a blocked | ||
| `pci-rebind` grabs the PCI device lock on its way in, which `pci-reset` also | ||
| needs: once a rebind has been attempted and is stuck, even FLR deadlocks and | ||
| **only a full PVE host power cycle recovers**. pci-reset first (if supported), and never | ||
| `pci-rebind` a D-state wedge. | ||
|
|
||
| **If no** (device merely dead or silent), escalate gently: | ||
|
|
||
| 1. `authorized <busport>` — re-enumerates just that device | ||
| 2. `rebind <busport>` — re-probe; also worth trying on the parent hub's busport | ||
| 3. `pci-rebind <pciaddr>` — last resort: bounces every fixture on that controller | ||
|
|
||
| ## Finding targets | ||
|
|
||
| ```bash | ||
| grep -l <SERIAL> /sys/bus/usb/devices/*/serial # serial -> busport (dir name) | ||
| readlink -f /sys/bus/usb/devices/usb<N> # bus N -> its PCI addr in the path | ||
| ``` | ||
|
|
||
| Rig layout: buses 3+4 = `0000:02:00.0` (main fixture tree: J-Links, ST-Links, | ||
| WCH-Links, DUTs); buses 9+12 = `0000:01:00.0`, the only ones with uhubctl port | ||
| power (ganged VBUS: `sudo uhubctl -l 9 -a cycle`). Hubs on buses 1-4 have no | ||
| port power switching — uhubctl reports "No compatible devices" there. | ||
|
|
||
| ## Common mistakes | ||
|
|
||
| - `resolve` takes a **/dev node**, not a busport or serial ("no such device node"). | ||
| - `authorized`/`rebind` take a **busport** (`3-4.7`); `pci-rebind`/`pci-reset` | ||
| take a **PCI addr**. | ||
| - Command produces no output and doesn't return → it is blocked on the device | ||
| lock: a D-state holder exists; see above. | ||
| - Trying `pci-rebind` on a D-state hang — its re-bind hangs and strands the | ||
| controller **driverless**; recover with `pci-bind <addr>`, or a PVE host power | ||
| cycle if the D-state URB is unkillable. Use `pci-reset` (if supported) for D-state, never | ||
| `pci-rebind`. | ||
| - Running `pci-reset` on a controller without FLR support (Renesas) → ENOTTY; | ||
| no software recovery — needs a PVE host power cycle. | ||
| - A J-Link reset (`r; go`) does not disconnect a wedged DUT from the host: the | ||
| DWC2 soft-connect pullup stays up through a core halt, so stuck URBs stay stuck. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,111 @@ | ||
| #!/usr/bin/env bash | ||
| # usb_recover.sh — USB recovery helper for the HIL rig; run with sudo. Writes only | ||
| # to the specific sysfs control files below; arg regexes block path traversal. | ||
| # | ||
| # Usage: | ||
| # sudo usb_recover.sh authorized <busport> # e.g. 3-2 -> deauthorize+reauthorize (re-enumerate, NO VBUS cut) | ||
| # sudo usb_recover.sh rebind <busport> # e.g. 3-2 -> usb driver unbind+bind (re-probe) | ||
| # sudo usb_recover.sh pci-rebind <pciaddr> # e.g. 0000:01:00.0 -> HCD unbind+bind (WHOLE controller) | ||
| # sudo usb_recover.sh pci-reset <pciaddr> # e.g. 0000:01:00.0 -> PCI function-level reset: kills URBs at | ||
| # # HW level WITHOUT the device lock; the only cure when a process | ||
| # # is stuck in D state (usbfs ioctl) and unbind paths would convoy | ||
| # sudo usb_recover.sh pci-bind <pciaddr> [driver] # bind a DRIVERLESS controller (e.g. after a pci-rebind | ||
| # # whose re-bind hung and left it unbound). Auto-tries the xHCI | ||
| # # drivers (xhci-pci-renesas, xhci_hcd) unless one is named. | ||
| # sudo usb_recover.sh resolve <devnode> # e.g. /dev/ttyACM3 -> print its <busport> (no privilege needed) | ||
| set -euo pipefail | ||
|
|
||
| USBPATH_RE='^[0-9]+-[0-9]+(\.[0-9]+)*$' | ||
| PCI_RE='^[0-9a-fA-F]{4}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}\.[0-9]$' | ||
| DRIVER_RE='^[A-Za-z0-9_-]+$' | ||
|
|
||
| die() { echo "usb_recover: $*" >&2; exit 1; } | ||
| usage() { grep -E '^# sudo usb_recover' "$0" >&2; exit 2; } | ||
|
|
||
| # Refuse to touch a PCI function that is not a USB controller (class 0x0c03xx), so a stray or | ||
| # mistyped BDF can't unbind/reset an unrelated device (storage, NIC) on a shared HIL host. | ||
| require_usb_controller() { | ||
| local addr=$1 cls | ||
| cls=$(cat "/sys/bus/pci/devices/$addr/class" 2>/dev/null) || die "no such pci device: $addr" | ||
| [[ "$cls" =~ ^0x0c03 ]] || die "$addr is not a USB controller (class $cls); refusing" | ||
| } | ||
|
|
||
| # Resolve a /dev node (ttyACMx, ttyUSBx, sgN, ...) up to its USB device busport. | ||
| resolve() { | ||
| local node=$1 syspath dev | ||
| [ -e "$node" ] || die "no such device node: $node" | ||
| syspath=$(udevadm info -q path -n "$node" 2>/dev/null) || die "udevadm failed for $node" | ||
| dev="/sys$syspath" | ||
| while [ "$dev" != "/sys" ] && [ -n "$dev" ]; do | ||
| if [ -e "$dev/busnum" ] && [ -e "$dev/devnum" ] && [ -e "$dev/authorized" ]; then | ||
| basename "$dev"; return 0 | ||
| fi | ||
| dev=$(dirname "$dev") | ||
| done | ||
| die "could not find parent USB device for $node" | ||
| } | ||
|
|
||
| action=${1:-}; target=${2:-} | ||
| [ -n "$action" ] && [ -n "$target" ] || usage | ||
|
|
||
| case "$action" in | ||
| resolve) | ||
| resolve "$target" | ||
| ;; | ||
| authorized) | ||
| [[ "$target" =~ $USBPATH_RE ]] || die "bad usb path: $target" | ||
| d="/sys/bus/usb/devices/$target" | ||
| [ -e "$d/authorized" ] || die "no such usb device: $target" | ||
| echo 0 > "$d/authorized"; sleep 1; echo 1 > "$d/authorized" | ||
| echo "re-authorized $target" | ||
| ;; | ||
| rebind) | ||
| [[ "$target" =~ $USBPATH_RE ]] || die "bad usb path: $target" | ||
| [ -e "/sys/bus/usb/devices/$target" ] || die "no such usb device: $target" | ||
| echo "$target" > /sys/bus/usb/drivers/usb/unbind; sleep 1 | ||
| echo "$target" > /sys/bus/usb/drivers/usb/bind | ||
| echo "rebound $target" | ||
| ;; | ||
| pci-rebind) | ||
| [[ "$target" =~ $PCI_RE ]] || die "bad pci addr: $target" | ||
| require_usb_controller "$target" | ||
| [ -e "/sys/bus/pci/devices/$target/driver" ] || die "no driver bound to $target" | ||
| drv=$(basename "$(readlink -f "/sys/bus/pci/devices/$target/driver")") | ||
| echo "$target" > "/sys/bus/pci/drivers/$drv/unbind"; sleep 1 | ||
| echo "$target" > "/sys/bus/pci/drivers/$drv/bind" | ||
| echo "rebound pci $target ($drv)" | ||
| ;; | ||
| pci-bind) | ||
| # Re-attach a driver to a controller left DRIVERLESS (e.g. a pci-rebind whose re-bind hung). | ||
| [[ "$target" =~ $PCI_RE ]] || die "bad pci addr: $target" | ||
| require_usb_controller "$target" | ||
| [ -e "/sys/bus/pci/devices/$target" ] || die "no such pci device: $target" | ||
| [ -e "/sys/bus/pci/devices/$target/driver" ] && die "$target already has a driver bound" | ||
| drv=${3:-} | ||
| if [ -n "$drv" ]; then | ||
| [[ "$drv" =~ $DRIVER_RE ]] || die "bad driver name: $drv" | ||
| [ -e "/sys/bus/pci/drivers/$drv/bind" ] || die "no such pci driver: $drv" | ||
| echo "$target" > "/sys/bus/pci/drivers/$drv/bind" | ||
| echo "bound pci $target ($drv)" | ||
| else | ||
| # Auto-try the xHCI drivers (Renesas uPD720201 uses xhci-pci-renesas; others xhci_hcd). | ||
| for cand in xhci-pci-renesas xhci_hcd; do | ||
| [ -e "/sys/bus/pci/drivers/$cand/bind" ] || continue | ||
| if echo "$target" > "/sys/bus/pci/drivers/$cand/bind" 2>/dev/null; then | ||
| echo "bound pci $target ($cand)"; exit 0 | ||
| fi | ||
| done | ||
| die "could not bind $target with a known xHCI driver; pass the driver explicitly" | ||
| fi | ||
| ;; | ||
| pci-reset) | ||
| [[ "$target" =~ $PCI_RE ]] || die "bad pci addr: $target" | ||
| require_usb_controller "$target" | ||
| [ -e "/sys/bus/pci/devices/$target/reset" ] || die "no reset support on $target" | ||
| echo 1 > "/sys/bus/pci/devices/$target/reset" | ||
| echo "flr-reset pci $target" | ||
| ;; | ||
| *) | ||
| usage | ||
| ;; | ||
| esac | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.