From 363e24bcd18a28948f63fa78ac8a4eaa8784fe59 Mon Sep 17 00:00:00 2001 From: salmonik Date: Sun, 1 Nov 2026 16:33:09 +0100 Subject: scripts: add test-usb; fix duplicate -bios in the test harness - test-usb boots an EHCI and an xHCI controller with a mouse + keyboard and asserts enumeration, report decoding, and input reaching the shell. The EHCI case asserts one key end-to-end (QEMU's EHCI model delivers only the first report through root-port split transactions); the xHCI case asserts a whole typed command. - lib.sh: qemu_args() already supplies -bios, so the fresh OVMF varstore copy was silently overridden by the system firmware. Pass the copy last (QEMU honours the last -bios) so tests really boot clean NVRAM. - DEBUG_NOTES: document why USB never worked on the OptiPlex (EHCI shipped with DEBUG_MATRIX enabled, no RMH/hub support, only the first controller, no boot protocol, unbounded DMA), the success/failure log to look for on hardware, and the QEMU coverage limits. - Makefile: add test-usb and include it in `make test`. --- DEBUG_NOTES.md | 90 +++++++++++++++++++++++++++++++++++++++++++++++ Makefile | 8 +++-- scripts/lib.sh | 6 +++- scripts/tests/test-usb.sh | 77 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 178 insertions(+), 3 deletions(-) create mode 100755 scripts/tests/test-usb.sh diff --git a/DEBUG_NOTES.md b/DEBUG_NOTES.md index 5730ca5..b7c0e08 100644 --- a/DEBUG_NOTES.md +++ b/DEBUG_NOTES.md @@ -541,3 +541,93 @@ ehci: HID boot mouse armed (device 1) ``` Periodic schedule: 2200+ EXECUTEs/frame with 0 qtd halts; idle mouse NAKs. + +--- + +## 🔌 USB real-hardware status (OptiPlex 7010 / Q77) — v0.6.x rework + +### Why USB "straight out did not work" + +1. **EHCI was disabled in the shipped build.** `ehci.rs` had + `static DEBUG_MATRIX: bool = true` (since `be868a4`): every boot ran the + one-shot transfer-variant bisection matrix and then `return false` before + enumerating. No committed build had ever brought an EHCI device up. +2. **No hub support.** On 7-series PCH the EHCI root port presents the + **integrated Rate Matching Hub** (8087:0024, device class 9); FS/LS mice + and keyboards sit *behind* it. The driver read the RMH's descriptors, + found no HID endpoint and gave up. Split transactions were hardcoded to + hub address 0 + root port (the QEMU topology). +3. **Only the first EHCI was initialized** (`find_ehci()` returned the first + match). The 7010 has two: `00:1a.0` and `00:1d.0`. +4. **No `SET_PROTOCOL(boot)`** on EHCI/xHCI; UHCI asked for report protocol + (`wValue=1`). Report protocol layouts are device-defined. +5. **EHCI DMA had no <4 GiB bound** (32-bit DMA pointers), while the page + bitmap spans all RAM. A buffer above 4 GiB silently breaks transfers. +6. **xHCI**: no scratchpad buffers (HCSPARAMS2), `MaxSlotsEn=1`, and DMA + from the heap allocator rather than physical pages. + +### What the drivers do now + +- `ehci.rs`: both controllers; hub class requests (power / reset / status); + TT splits with the hub address + child port; HID mouse **and** keyboard; + boot protocol; per-device periodic QH (chained into the frame list); + DMA from `alloc_pages_range(16 MiB, 4 GiB)`; 2 ms recovery after + `SET_ADDRESS`. +- `usb.rs` (xHCI): scratchpad buffers, `MaxSlotsEn = min(MaxSlots, 4)`, + per-slot EP0 + interrupt rings, page-allocator DMA, boot protocol, + mouse + keyboard. +- `hid.rs`: shared HID boot-keyboard decoding (one scancode FIFO feeding + `ps2::feed_scancode`), used by UHCI, EHCI and xHCI. +- First-report lines are logged (`ehci: mouse report …`, `usb: kbd report …`) + and the `usb` shell command lists controller(s) and device(s) with + armed/fails/reports counters. + +### OptiPlex 7010 — expected serial log (success path) + +``` +ehci: 00:1a.0 mmio=… ports=… 64-bit-caps=… +ehci: USB legacy handed over by BIOS +ehci: controller up (… ports) +ehci: port N connected (high-speed) +ehci: dev 8087:0024 class=0x9 … addr=1 ← rate matching hub +ehci: hub addr=1 ports=8 +ehci: hub port K: full-speed device (split) +ehci: dev …:… class=0x0 … addr=2 sp=0 (split) +ehci: HID mouse armed (addr 2 ep0x81 mps … ) +ehci: periodic schedule enabled (1 device(s) on ctrl 0) +ehci: mouse report dx=… dy=… b=0x… +``` + +Then the same on `00:1d.0` for the keyboard, and `> ` followed by typed keys. +Failure signatures to report: + +- `ehci: no EHCI controller` — PCI probe/class mismatch (should not happen). +- `ehci: USB legacy claim timed out` — BIOS still owns the controller. +- `ehci: hub descriptor failed` / `hub port K reset timeout` — hub class + request stage; note the code. +- `ehci: get-dev-desc failed (addr 0 sp 0 tt Some(…))` — split transaction + did not reach the child; note the TT tuple. +- `ehci: no HID mouse/keyboard endpoint in config` — descriptor walk. +- `ehci: xfer fail stage=…` — transfer engine; the QR band has the state. + +### QEMU coverage (make test-usb) + +- EHCI root-port FS mouse/keyboard (split path), two devices chained. +- xHCI mouse/keyboard on two slots. +- Keyboard report → scancode → WM → shell echo is asserted in both cases. +- **Not coverable in QEMU**: an EHCI high-speed hub (`usb-hub` is FS-only and + refuses a HS EHCI root port), so the RMH code path is hardware-only. +- **QEMU EHCI split limit**: for full/low-speed devices behind root-port + split transactions QEMU delivers only the *first* report (125 NAKs after + it, even though the qTD is re-armed). The re-arm shape is the standard one + (`qh->next_qtd = qtd`, qTD re-activated; the HC owns the status/toggle + overlay), so `test-usb` asserts one key end-to-end on EHCI and a full + command on xHCI. + +### Known gaps + +- EHCI: hub support is one level deep; no hotplug once a device of that kind + is armed; no isochronous/other transfer types. +- xHCI: no hub support; no USB3 warm-reset subtleties beyond the current + port-reset sequence. +- AHCI/PS2 behaviour unchanged (PS/2 remains the fallback input path). diff --git a/Makefile b/Makefile index ee9755f..ead8c20 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ SYSTEM_IMG ?= /tmp/system.img .PHONY: all build run run-headless debug system system-fresh inspect \ - test test-disk test-fault test-gfx test-mouse test-msg fmt clean help + test test-disk test-fault test-gfx test-mouse test-msg test-usb fmt clean help # ---- build ---------------------------------------------------------------- @@ -45,7 +45,7 @@ inspect: # ---- tests ---------------------------------------------------------------- -test: test-disk test-gfx test-mouse test-msg test-fault +test: test-disk test-gfx test-mouse test-msg test-fault test-usb test-disk: ./scripts/tests/test-disk.sh @@ -62,6 +62,9 @@ test-mouse: test-msg: ./scripts/tests/test-msg.sh +test-usb: + ./scripts/tests/test-usb.sh + # ---- utilities ------------------------------------------------------------ fmt: @@ -86,6 +89,7 @@ help: @echo " make test-mouse PS/2 mouse + cursor tracking" @echo " make test-msg scheduler message queues" @echo " make test-fault fault dump (screen + serial)" + @echo " make test-usb USB HID enumeration (EHCI + xHCI)" @echo " make fmt format Rust source" @echo " make clean remove build artifacts" @echo "" diff --git a/scripts/lib.sh b/scripts/lib.sh index d5aecac..6ca3e92 100644 --- a/scripts/lib.sh +++ b/scripts/lib.sh @@ -157,8 +157,12 @@ test_boot() { cp "$OVMF" "$nv" local -a qargs mapfile -t qargs < <(qemu_args) - "$QEMU" -bios "$nv" \ + # qemu_args() already carries -bios $OVMF; put the fresh varstore copy + # last so it wins (QEMU uses the last -bios) without duplicating the + # whole argument set. + "$QEMU" \ "${qargs[@]}" \ + -bios "$nv" \ -serial "file:$LOG" \ -monitor "unix:$SOCK,server,nowait" \ -display none \ diff --git a/scripts/tests/test-usb.sh b/scripts/tests/test-usb.sh new file mode 100755 index 0000000..55a3f2b --- /dev/null +++ b/scripts/tests/test-usb.sh @@ -0,0 +1,77 @@ +#!/usr/bin/env bash +# +# scripts/tests/test-usb.sh — USB host-controller + HID regression. +# +# Boots twice, once with an EHCI (USB2) controller and once with an xHCI +# (USB3) controller, each with a HID mouse and keyboard: +# +# • both devices must enumerate and arm their interrupt-IN endpoints +# • a keyboard report must be decoded (scancode path) +# • the key must reach the shell (WM input routing) +# +# QEMU limitation: its EHCI model delivers only the *first* report for a +# full/low-speed device serviced by root-port split transactions, so the EHCI +# case asserts one key end-to-end (a regression in enumeration/arming fails +# earlier). The xHCI case has no splits and asserts a whole typed command. +# Mouse report delivery cannot be injected into usb-mouse headless; the +# hardware run (OptiPlex) is the arbiter for both. +# +# Usage: ./scripts/tests/test-usb.sh +# Env: SYSTEM_IMG, OVMF, QEMU (see scripts/lib.sh) +# +set -euo pipefail +source "$(dirname "$0")/../lib.sh" + +echo "==> 1/3 build (kernel + bins)" +build_all + +echo "==> 2/3 stage system image" +stage_image --force + +# Per-controller log/socket files; test_boot() reads these globals. +case_boot() { + local name="$1" + shift + LOG="/tmp/rsdos-usb-$name-serial.log" + SOCK="/tmp/rsdos-usb-$name-qmon.sock" + QPID="/tmp/rsdos-usb-$name-qpid" + QLOG="/tmp/rsdos-usb-$name-qemu.log" + test_boot "$@" +} + +case_arms() { + wait_log "$LOG" "HID mouse armed" 20 || fail "$1: mouse not enumerated" + wait_log "$LOG" "HID keyboard armed" 20 || fail "$1: keyboard not enumerated" + sleep 1 +} + +case_done() { + kill "$(cat "$QPID" 2>/dev/null)" 2>/dev/null || true + sleep 0.5 + echo " $1: mouse + keyboard OK" +} + +echo "==> 3/3 EHCI (USB2) enumeration" +case_boot ehci \ + -device usb-ehci,id=ehci \ + -device usb-mouse,bus=ehci.0,port=1 \ + -device usb-kbd,bus=ehci.0,port=2 +case_arms ehci +sendkeys "h" +wait_log "$LOG" "kbd report" 10 || fail "ehci: no keyboard report decoded" +tr -d '\r' < "$LOG" | grep -qx "h" || fail "ehci: typed key did not reach the shell" +case_done ehci + +echo "==> 3/3 xHCI (USB3) enumeration" +case_boot xhci \ + -device qemu-xhci,id=xhci \ + -device usb-mouse,bus=xhci.0 \ + -device usb-kbd,bus=xhci.0 +case_arms xhci +sendkeys "he" +wait_log "$LOG" "kbd report" 10 || fail "xhci: no keyboard report decoded" +wait_log "$LOG" "unknown command: he" 15 || fail "xhci: typed text did not reach the shell" +case_done xhci + +echo "" +echo "PASS — USB enumeration + HID input (EHCI and xHCI)" -- cgit v1.2.3