diff options
| author | salmonik <salmonik@tuta.io> | 2026-11-01 16:33:09 +0100 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-11-01 16:33:09 +0100 |
| commit | 363e24bcd18a28948f63fa78ac8a4eaa8784fe59 (patch) | |
| tree | 6b74ab993ff42e3bf15b01f4cfd09bcb3f0d5ea6 | |
| parent | usb: real-hardware rework — hubs, both EHCI controllers, keyboard, xHCI har... (diff) | |
| download | rs-dos-363e24bcd18a28948f63fa78ac8a4eaa8784fe59.tar.gz rs-dos-363e24bcd18a28948f63fa78ac8a4eaa8784fe59.zip | |
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`.
| -rw-r--r-- | DEBUG_NOTES.md | 90 | ||||
| -rw-r--r-- | Makefile | 8 | ||||
| -rw-r--r-- | scripts/lib.sh | 6 | ||||
| -rwxr-xr-x | scripts/tests/test-usb.sh | 77 |
4 files changed, 178 insertions, 3 deletions
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) | |||
| 541 | ``` | 541 | ``` |
| 542 | 542 | ||
| 543 | Periodic schedule: 2200+ EXECUTEs/frame with 0 qtd halts; idle mouse NAKs. | 543 | Periodic schedule: 2200+ EXECUTEs/frame with 0 qtd halts; idle mouse NAKs. |
| 544 | |||
| 545 | --- | ||
| 546 | |||
| 547 | ## 🔌 USB real-hardware status (OptiPlex 7010 / Q77) — v0.6.x rework | ||
| 548 | |||
| 549 | ### Why USB "straight out did not work" | ||
| 550 | |||
| 551 | 1. **EHCI was disabled in the shipped build.** `ehci.rs` had | ||
| 552 | `static DEBUG_MATRIX: bool = true` (since `be868a4`): every boot ran the | ||
| 553 | one-shot transfer-variant bisection matrix and then `return false` before | ||
| 554 | enumerating. No committed build had ever brought an EHCI device up. | ||
| 555 | 2. **No hub support.** On 7-series PCH the EHCI root port presents the | ||
| 556 | **integrated Rate Matching Hub** (8087:0024, device class 9); FS/LS mice | ||
| 557 | and keyboards sit *behind* it. The driver read the RMH's descriptors, | ||
| 558 | found no HID endpoint and gave up. Split transactions were hardcoded to | ||
| 559 | hub address 0 + root port (the QEMU topology). | ||
| 560 | 3. **Only the first EHCI was initialized** (`find_ehci()` returned the first | ||
| 561 | match). The 7010 has two: `00:1a.0` and `00:1d.0`. | ||
| 562 | 4. **No `SET_PROTOCOL(boot)`** on EHCI/xHCI; UHCI asked for report protocol | ||
| 563 | (`wValue=1`). Report protocol layouts are device-defined. | ||
| 564 | 5. **EHCI DMA had no <4 GiB bound** (32-bit DMA pointers), while the page | ||
| 565 | bitmap spans all RAM. A buffer above 4 GiB silently breaks transfers. | ||
| 566 | 6. **xHCI**: no scratchpad buffers (HCSPARAMS2), `MaxSlotsEn=1`, and DMA | ||
| 567 | from the heap allocator rather than physical pages. | ||
| 568 | |||
| 569 | ### What the drivers do now | ||
| 570 | |||
| 571 | - `ehci.rs`: both controllers; hub class requests (power / reset / status); | ||
| 572 | TT splits with the hub address + child port; HID mouse **and** keyboard; | ||
| 573 | boot protocol; per-device periodic QH (chained into the frame list); | ||
| 574 | DMA from `alloc_pages_range(16 MiB, 4 GiB)`; 2 ms recovery after | ||
| 575 | `SET_ADDRESS`. | ||
| 576 | - `usb.rs` (xHCI): scratchpad buffers, `MaxSlotsEn = min(MaxSlots, 4)`, | ||
| 577 | per-slot EP0 + interrupt rings, page-allocator DMA, boot protocol, | ||
| 578 | mouse + keyboard. | ||
| 579 | - `hid.rs`: shared HID boot-keyboard decoding (one scancode FIFO feeding | ||
| 580 | `ps2::feed_scancode`), used by UHCI, EHCI and xHCI. | ||
| 581 | - First-report lines are logged (`ehci: mouse report …`, `usb: kbd report …`) | ||
| 582 | and the `usb` shell command lists controller(s) and device(s) with | ||
| 583 | armed/fails/reports counters. | ||
| 584 | |||
| 585 | ### OptiPlex 7010 — expected serial log (success path) | ||
| 586 | |||
| 587 | ``` | ||
| 588 | ehci: 00:1a.0 mmio=… ports=… 64-bit-caps=… | ||
| 589 | ehci: USB legacy handed over by BIOS | ||
| 590 | ehci: controller up (… ports) | ||
| 591 | ehci: port N connected (high-speed) | ||
| 592 | ehci: dev 8087:0024 class=0x9 … addr=1 ← rate matching hub | ||
| 593 | ehci: hub addr=1 ports=8 | ||
| 594 | ehci: hub port K: full-speed device (split) | ||
| 595 | ehci: dev …:… class=0x0 … addr=2 sp=0 (split) | ||
| 596 | ehci: HID mouse armed (addr 2 ep0x81 mps … ) | ||
| 597 | ehci: periodic schedule enabled (1 device(s) on ctrl 0) | ||
| 598 | ehci: mouse report dx=… dy=… b=0x… | ||
| 599 | ``` | ||
| 600 | |||
| 601 | Then the same on `00:1d.0` for the keyboard, and `> ` followed by typed keys. | ||
| 602 | Failure signatures to report: | ||
| 603 | |||
| 604 | - `ehci: no EHCI controller` — PCI probe/class mismatch (should not happen). | ||
| 605 | - `ehci: USB legacy claim timed out` — BIOS still owns the controller. | ||
| 606 | - `ehci: hub descriptor failed` / `hub port K reset timeout` — hub class | ||
| 607 | request stage; note the code. | ||
| 608 | - `ehci: get-dev-desc failed (addr 0 sp 0 tt Some(…))` — split transaction | ||
| 609 | did not reach the child; note the TT tuple. | ||
| 610 | - `ehci: no HID mouse/keyboard endpoint in config` — descriptor walk. | ||
| 611 | - `ehci: xfer fail stage=…` — transfer engine; the QR band has the state. | ||
| 612 | |||
| 613 | ### QEMU coverage (make test-usb) | ||
| 614 | |||
| 615 | - EHCI root-port FS mouse/keyboard (split path), two devices chained. | ||
| 616 | - xHCI mouse/keyboard on two slots. | ||
| 617 | - Keyboard report → scancode → WM → shell echo is asserted in both cases. | ||
| 618 | - **Not coverable in QEMU**: an EHCI high-speed hub (`usb-hub` is FS-only and | ||
| 619 | refuses a HS EHCI root port), so the RMH code path is hardware-only. | ||
| 620 | - **QEMU EHCI split limit**: for full/low-speed devices behind root-port | ||
| 621 | split transactions QEMU delivers only the *first* report (125 NAKs after | ||
| 622 | it, even though the qTD is re-armed). The re-arm shape is the standard one | ||
| 623 | (`qh->next_qtd = qtd`, qTD re-activated; the HC owns the status/toggle | ||
| 624 | overlay), so `test-usb` asserts one key end-to-end on EHCI and a full | ||
| 625 | command on xHCI. | ||
| 626 | |||
| 627 | ### Known gaps | ||
| 628 | |||
| 629 | - EHCI: hub support is one level deep; no hotplug once a device of that kind | ||
| 630 | is armed; no isochronous/other transfer types. | ||
| 631 | - xHCI: no hub support; no USB3 warm-reset subtleties beyond the current | ||
| 632 | port-reset sequence. | ||
| 633 | - AHCI/PS2 behaviour unchanged (PS/2 remains the fallback input path). | ||
| @@ -6,7 +6,7 @@ | |||
| 6 | SYSTEM_IMG ?= /tmp/system.img | 6 | SYSTEM_IMG ?= /tmp/system.img |
| 7 | 7 | ||
| 8 | .PHONY: all build run run-headless debug system system-fresh inspect \ | 8 | .PHONY: all build run run-headless debug system system-fresh inspect \ |
| 9 | test test-disk test-fault test-gfx test-mouse test-msg fmt clean help | 9 | test test-disk test-fault test-gfx test-mouse test-msg test-usb fmt clean help |
| 10 | 10 | ||
| 11 | # ---- build ---------------------------------------------------------------- | 11 | # ---- build ---------------------------------------------------------------- |
| 12 | 12 | ||
| @@ -45,7 +45,7 @@ inspect: | |||
| 45 | 45 | ||
| 46 | # ---- tests ---------------------------------------------------------------- | 46 | # ---- tests ---------------------------------------------------------------- |
| 47 | 47 | ||
| 48 | test: test-disk test-gfx test-mouse test-msg test-fault | 48 | test: test-disk test-gfx test-mouse test-msg test-fault test-usb |
| 49 | 49 | ||
| 50 | test-disk: | 50 | test-disk: |
| 51 | ./scripts/tests/test-disk.sh | 51 | ./scripts/tests/test-disk.sh |
| @@ -62,6 +62,9 @@ test-mouse: | |||
| 62 | test-msg: | 62 | test-msg: |
| 63 | ./scripts/tests/test-msg.sh | 63 | ./scripts/tests/test-msg.sh |
| 64 | 64 | ||
| 65 | test-usb: | ||
| 66 | ./scripts/tests/test-usb.sh | ||
| 67 | |||
| 65 | # ---- utilities ------------------------------------------------------------ | 68 | # ---- utilities ------------------------------------------------------------ |
| 66 | 69 | ||
| 67 | fmt: | 70 | fmt: |
| @@ -86,6 +89,7 @@ help: | |||
| 86 | @echo " make test-mouse PS/2 mouse + cursor tracking" | 89 | @echo " make test-mouse PS/2 mouse + cursor tracking" |
| 87 | @echo " make test-msg scheduler message queues" | 90 | @echo " make test-msg scheduler message queues" |
| 88 | @echo " make test-fault fault dump (screen + serial)" | 91 | @echo " make test-fault fault dump (screen + serial)" |
| 92 | @echo " make test-usb USB HID enumeration (EHCI + xHCI)" | ||
| 89 | @echo " make fmt format Rust source" | 93 | @echo " make fmt format Rust source" |
| 90 | @echo " make clean remove build artifacts" | 94 | @echo " make clean remove build artifacts" |
| 91 | @echo "" | 95 | @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() { | |||
| 157 | cp "$OVMF" "$nv" | 157 | cp "$OVMF" "$nv" |
| 158 | local -a qargs | 158 | local -a qargs |
| 159 | mapfile -t qargs < <(qemu_args) | 159 | mapfile -t qargs < <(qemu_args) |
| 160 | "$QEMU" -bios "$nv" \ | 160 | # qemu_args() already carries -bios $OVMF; put the fresh varstore copy |
| 161 | # last so it wins (QEMU uses the last -bios) without duplicating the | ||
| 162 | # whole argument set. | ||
| 163 | "$QEMU" \ | ||
| 161 | "${qargs[@]}" \ | 164 | "${qargs[@]}" \ |
| 165 | -bios "$nv" \ | ||
| 162 | -serial "file:$LOG" \ | 166 | -serial "file:$LOG" \ |
| 163 | -monitor "unix:$SOCK,server,nowait" \ | 167 | -monitor "unix:$SOCK,server,nowait" \ |
| 164 | -display none \ | 168 | -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 @@ | |||
| 1 | #!/usr/bin/env bash | ||
| 2 | # | ||
| 3 | # scripts/tests/test-usb.sh — USB host-controller + HID regression. | ||
| 4 | # | ||
| 5 | # Boots twice, once with an EHCI (USB2) controller and once with an xHCI | ||
| 6 | # (USB3) controller, each with a HID mouse and keyboard: | ||
| 7 | # | ||
| 8 | # • both devices must enumerate and arm their interrupt-IN endpoints | ||
| 9 | # • a keyboard report must be decoded (scancode path) | ||
| 10 | # • the key must reach the shell (WM input routing) | ||
| 11 | # | ||
| 12 | # QEMU limitation: its EHCI model delivers only the *first* report for a | ||
| 13 | # full/low-speed device serviced by root-port split transactions, so the EHCI | ||
| 14 | # case asserts one key end-to-end (a regression in enumeration/arming fails | ||
| 15 | # earlier). The xHCI case has no splits and asserts a whole typed command. | ||
| 16 | # Mouse report delivery cannot be injected into usb-mouse headless; the | ||
| 17 | # hardware run (OptiPlex) is the arbiter for both. | ||
| 18 | # | ||
| 19 | # Usage: ./scripts/tests/test-usb.sh | ||
| 20 | # Env: SYSTEM_IMG, OVMF, QEMU (see scripts/lib.sh) | ||
| 21 | # | ||
| 22 | set -euo pipefail | ||
| 23 | source "$(dirname "$0")/../lib.sh" | ||
| 24 | |||
| 25 | echo "==> 1/3 build (kernel + bins)" | ||
| 26 | build_all | ||
| 27 | |||
| 28 | echo "==> 2/3 stage system image" | ||
| 29 | stage_image --force | ||
| 30 | |||
| 31 | # Per-controller log/socket files; test_boot() reads these globals. | ||
| 32 | case_boot() { | ||
| 33 | local name="$1" | ||
| 34 | shift | ||
| 35 | LOG="/tmp/rsdos-usb-$name-serial.log" | ||
| 36 | SOCK="/tmp/rsdos-usb-$name-qmon.sock" | ||
| 37 | QPID="/tmp/rsdos-usb-$name-qpid" | ||
| 38 | QLOG="/tmp/rsdos-usb-$name-qemu.log" | ||
| 39 | test_boot "$@" | ||
| 40 | } | ||
| 41 | |||
| 42 | case_arms() { | ||
| 43 | wait_log "$LOG" "HID mouse armed" 20 || fail "$1: mouse not enumerated" | ||
| 44 | wait_log "$LOG" "HID keyboard armed" 20 || fail "$1: keyboard not enumerated" | ||
| 45 | sleep 1 | ||
| 46 | } | ||
| 47 | |||
| 48 | case_done() { | ||
| 49 | kill "$(cat "$QPID" 2>/dev/null)" 2>/dev/null || true | ||
| 50 | sleep 0.5 | ||
| 51 | echo " $1: mouse + keyboard OK" | ||
| 52 | } | ||
| 53 | |||
| 54 | echo "==> 3/3 EHCI (USB2) enumeration" | ||
| 55 | case_boot ehci \ | ||
| 56 | -device usb-ehci,id=ehci \ | ||
| 57 | -device usb-mouse,bus=ehci.0,port=1 \ | ||
| 58 | -device usb-kbd,bus=ehci.0,port=2 | ||
| 59 | case_arms ehci | ||
| 60 | sendkeys "h" | ||
| 61 | wait_log "$LOG" "kbd report" 10 || fail "ehci: no keyboard report decoded" | ||
| 62 | tr -d '\r' < "$LOG" | grep -qx "h" || fail "ehci: typed key did not reach the shell" | ||
| 63 | case_done ehci | ||
| 64 | |||
| 65 | echo "==> 3/3 xHCI (USB3) enumeration" | ||
| 66 | case_boot xhci \ | ||
| 67 | -device qemu-xhci,id=xhci \ | ||
| 68 | -device usb-mouse,bus=xhci.0 \ | ||
| 69 | -device usb-kbd,bus=xhci.0 | ||
| 70 | case_arms xhci | ||
| 71 | sendkeys "he" | ||
| 72 | wait_log "$LOG" "kbd report" 10 || fail "xhci: no keyboard report decoded" | ||
| 73 | wait_log "$LOG" "unknown command: he" 15 || fail "xhci: typed text did not reach the shell" | ||
| 74 | case_done xhci | ||
| 75 | |||
| 76 | echo "" | ||
| 77 | echo "PASS — USB enumeration + HID input (EHCI and xHCI)" | ||
