ee1da17617
Create the four USB validation scripts that local/docs/USB-VALIDATION-RUNBOOK.md references but which did not exist on disk, closing the P8-B runtime-proof gap for the legacy host controllers and the P8-C error-injection gap for xHCI: - test-uhci-runtime-qemu.sh (P1-B): -machine pc + piix3-usb-uhci, serial-log proof that uhcid binds the controller and detects the attached usb-kbd. - test-ohci-runtime-qemu.sh (P1-B): -machine pc + pci-ohci, serial-log proof that ohcid binds the controller and detects the attached usb-kbd. - test-ehci-class-autospawn-qemu.sh (P1-A): -machine q35 + usb-ehci + usb-kbd, serial-log proof that ehcid enumerates the keyboard and usbhidd auto-spawns via the unified UsbHostController trait. The existing test-ehci-qemu.sh is a coarse usb-tablet smoke test and does NOT cover this path, so this is a full script rather than an alias. - test-usb-error-recovery-qemu.sh (P8-C): hot-unplug usb-storage mid-transfer via the QEMU monitor device_del (chardev stdio mux, Ctrl-A c toggle — the same pattern as test-xhci-device-lifecycle-qemu.sh, since qemu-login-expect.py drives only serial and cannot reach the monitor). Proves graceful detach + usbscsid IO-error handling with no panic. test-xhci-device-lifecycle-qemu.sh already existed and is unchanged. test-usb-uas-qemu.sh is intentionally NOT created: UAS is in flight in a separate workstream and its test belongs to that change. Proof style and harness fidelity: - UHCI/OHCI/EHCI proofs follow the test-xhci-irq-qemu.sh serial-log grep pattern (dual --check + interactive mode, ISO-preferred boot_args, 180s timeout, no panic fail-marker). No guest-side checker exists that validates legacy-controller enumeration specifically, so per the runbook's serial-log-evidence rule they grep driver log lines instead of inventing a guest binary. - The error-recovery proof follows the test-xhci-device-lifecycle-qemu.sh expect/Tcl monitor-mux pattern and reuses the existing redbear-usb-storage-check guest binary (redbear-hwutils) to drive an active transfer — no new guest binary is invented. All proof markers are sourced directly from the real driver trees: uhcid main.rs:487/535/545 (UHCI USB 1.1 at / controller initialized / connect) ohcid main.rs:305/341/348 (OHCI USB 1.1 at / controller initialized / connect) ehcid main.rs:154/294/560 (EHCI USB 2.0 at / controller initialized / port device) usbhidd main.rs:221 (USB HID driver spawned with scheme) usbscsid main.rs:190/200/163/156 (READ/WRITE IO ERROR / scheme tick / event error) No existing scripts, qemu-login-expect.py, config/*.toml, recipes, or the runbook were modified. The runbook's script names and --check invocations already matched exactly, so no runbook edits were required. Validation: bash -n passes on all four scripts. shellcheck is not installed on this host. NO QEMU runs were performed — the host is contended by another workload that kills QEMU processes, so validation is syntax check + pattern fidelity review only. Runtime pass/fail is unverified.
237 lines
8.6 KiB
Bash
Executable File
237 lines
8.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Launch or validate the EHCI class-driver autospawn path (P1-A) in QEMU.
|
|
#
|
|
# Topology under test:
|
|
# -machine q35 → -device usb-ehci,id=ehci
|
|
# └─ bus=ehci.0 → usb-kbd
|
|
#
|
|
# Validates the P1-A claim that a USB keyboard on the EHCI route reaches the
|
|
# input stack via the unified `UsbHostController` trait:
|
|
# 1. pcid auto-spawns ehcid for the EHCI PCI device (class 0c0320, see
|
|
# local/config/drivers.d/20-usb.toml).
|
|
# 2. ehcid maps MMIO, initializes ports, and enumerates the usb-kbd.
|
|
# 3. ehcid calls usb_core::spawn::spawn_class_driver_for_port() (ehcid
|
|
# main.rs:576), which selects the HID driver for class 0x03 (usb-core
|
|
# spawn.rs:18) and spawns `/usr/bin/usbhidd`.
|
|
# 4. usbhidd starts and registers itself as an `inputd::ProducerHandle`
|
|
# holder (usbhidd main.rs:4), wiring the keyboard into the input stack.
|
|
#
|
|
# This is the autospawn half of the P1-A proof ("all four controllers
|
|
# auto-spawn via the unified trait; class daemons can connect to all four via
|
|
# the same scheme"). The existing local/scripts/test-ehci-qemu.sh is a coarse
|
|
# 17-line smoke test (usb-tablet, grep "ehci|EHCI") that does NOT exercise the
|
|
# keyboard-autospawn path; this script is the rigorous P1-A proof and does not
|
|
# duplicate that smoke test's logic.
|
|
#
|
|
# Evidence is read from the serial log. Proof markers come straight from the
|
|
# driver sources:
|
|
# "EHCI USB 2.0 at <pci_path>" (ehcid main.rs:154)
|
|
# "ehcid: controller initialized, <n> ports, ..." (ehcid main.rs:294)
|
|
# "ehcid: port <n> device <vid>:<pid> address <a>" (ehcid main.rs:560)
|
|
# "USB HID driver spawned with scheme `...`..." (usbhidd main.rs:221)
|
|
#
|
|
# Note on "reaches inputd": usbhidd is an inputd producer by construction
|
|
# (`use inputd::ProducerHandle`). There is no guest-side checker that validates
|
|
# an inputd keypress event for a USB keyboard, so the autospawn-to-usbhidd
|
|
# marker is the strongest serial-log evidence available; a dedicated
|
|
# keypress-to-inputd proof would require an inputd guest checker that does not
|
|
# exist yet (serial-log-evidence rule per the USB validation runbook).
|
|
|
|
set -euo pipefail
|
|
|
|
find_uefi_firmware() {
|
|
local candidates=(
|
|
"/usr/share/ovmf/x64/OVMF.4m.fd"
|
|
"/usr/share/OVMF/x64/OVMF.4m.fd"
|
|
"/usr/share/ovmf/x64/OVMF_CODE.4m.fd"
|
|
"/usr/share/OVMF/x64/OVMF_CODE.4m.fd"
|
|
"/usr/share/ovmf/OVMF.fd"
|
|
"/usr/share/OVMF/OVMF_CODE.fd"
|
|
"/usr/share/qemu/edk2-x86_64-code.fd"
|
|
)
|
|
local path
|
|
for path in "${candidates[@]}"; do
|
|
if [[ -f "$path" ]]; then
|
|
printf '%s\n' "$path"
|
|
return 0
|
|
fi
|
|
done
|
|
return 1
|
|
}
|
|
|
|
usage() {
|
|
cat <<'USAGE'
|
|
Usage: test-ehci-class-autospawn-qemu.sh [--check] [config]
|
|
|
|
Boot or validate the EHCI USB-keyboard class-driver autospawn path (P1-A) on a
|
|
Red Bear image in QEMU. Uses `-machine q35` + `usb-ehci` + `usb-kbd`, matching
|
|
the controller selection of the existing test-ehci-qemu.sh smoke test but
|
|
adding the keyboard and the autospawn evidence checks. Defaults to
|
|
redbear-mini (mapped to the in-tree redbear-mini image).
|
|
USAGE
|
|
}
|
|
|
|
check_mode=0
|
|
config="redbear-mini"
|
|
for arg in "$@"; do
|
|
case "$arg" in
|
|
--help|-h|help)
|
|
usage
|
|
exit 0
|
|
;;
|
|
--check)
|
|
check_mode=1
|
|
;;
|
|
redbear-*)
|
|
config="$arg"
|
|
;;
|
|
esac
|
|
done
|
|
|
|
firmware="$(find_uefi_firmware)" || {
|
|
echo "ERROR: no usable x86_64 UEFI firmware found" >&2
|
|
exit 1
|
|
}
|
|
|
|
arch="${ARCH:-$(uname -m)}"
|
|
# Prefer the live ISO (canonical build-redbear.sh artifact); harddrive.img is
|
|
# only produced by older make-all flows and goes stale, which would test the
|
|
# wrong tree. extra.img stays as a data drive in both modes.
|
|
iso="build/$arch/$config.iso"
|
|
image="build/$arch/$config/harddrive.img"
|
|
extra="build/$arch/$config/extra.img"
|
|
|
|
boot_media=""
|
|
if [[ -f "$iso" ]]; then
|
|
boot_media="iso"
|
|
elif [[ -f "$image" ]]; then
|
|
boot_media="harddrive"
|
|
else
|
|
echo "ERROR: no bootable image found ($iso or $image)" >&2
|
|
echo "Build it first with: ./local/scripts/build-redbear.sh $config" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [[ ! -f "$extra" ]]; then
|
|
truncate -s 1g "$extra"
|
|
fi
|
|
|
|
if [[ "$boot_media" == "iso" ]]; then
|
|
boot_args=(-cdrom "$iso")
|
|
else
|
|
boot_args=(-drive file="$image",format=raw,if=none,id=drv0,snapshot=on -device nvme,drive=drv0,serial=NVME_SERIAL)
|
|
fi
|
|
|
|
pkill -f "qemu-system-x86_64.*$config" 2>/dev/null || true
|
|
sleep 1
|
|
|
|
if [[ "$check_mode" -eq 1 ]]; then
|
|
log_file="build/$arch/$config/ehci-autospawn-check.log"
|
|
rm -f "$log_file"
|
|
set +e
|
|
# 180s mirrors the xHCI interrupt proof budget. The autospawn chain
|
|
# (ehcid enumerate → usb-core spawn → usbhidd init) is bounded but the
|
|
# guest clock runs several times slower than wall under boot load.
|
|
timeout 180s qemu-system-x86_64 \
|
|
-name "Red Bear OS x86_64" \
|
|
-machine q35 \
|
|
-device usb-ehci,id=ehci \
|
|
-device usb-kbd,bus=ehci.0 \
|
|
-smp 4 \
|
|
-m 2048 \
|
|
-bios "$firmware" \
|
|
-chardev stdio,id=debug,signal=off,mux=on \
|
|
-serial chardev:debug \
|
|
-mon chardev=debug \
|
|
-device ich9-intel-hda -device hda-output \
|
|
-device virtio-net,netdev=net0 \
|
|
-netdev user,id=net0 \
|
|
-object filter-dump,id=f1,netdev=net0,file="build/$arch/$config/network.pcap" \
|
|
-nographic -vga none \
|
|
"${boot_args[@]}" \
|
|
-drive file="$extra",format=raw,if=none,id=drv1,snapshot=on \
|
|
-device nvme,drive=drv1,serial=NVME_EXTRA \
|
|
-enable-kvm -cpu host \
|
|
> "$log_file" 2>&1
|
|
status=$?
|
|
set -e
|
|
|
|
# 1. ehcid bound the controller. This proves pcid matched the EHCI PCI
|
|
# class (0c0320) and spawned ehcid.
|
|
if ! grep -q "EHCI USB 2.0 at " "$log_file"; then
|
|
echo "ERROR: ehcid did not bind the EHCI controller" >&2
|
|
echo " Expected: 'EHCI USB 2.0 at <pci_path>'" >&2
|
|
grep -i "ehcid\|EHCI" "$log_file" | tail -10 >&2 || true
|
|
echo " See $log_file" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# 2. Controller initialized (ports detected, async queue armed).
|
|
if ! grep -q "ehcid: controller initialized," "$log_file"; then
|
|
echo "ERROR: ehcid did not finish controller initialization" >&2
|
|
echo " Expected: 'ehcid: controller initialized, <n> ports, ...'" >&2
|
|
echo " See $log_file" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# 3. usb-kbd enumerated on the EHCI port. This proves the EHCI transfer
|
|
# path (descriptor fetch + SET_ADDRESS + config) reached a fully
|
|
# attached device.
|
|
if ! grep -Eq "ehcid: port [0-9]+ device [0-9a-fA-F]{4}:[0-9a-fA-F]{4} address [0-9]+" "$log_file"; then
|
|
echo "ERROR: ehcid did not enumerate the usb-kbd" >&2
|
|
echo " Expected: 'ehcid: port <n> device <vid>:<pid> address <a>'" >&2
|
|
echo " See $log_file" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# 4. usbhidd auto-spawned via the unified trait. This is the P1-A core
|
|
# claim: spawn_class_driver_for_port selected the HID driver for the
|
|
# enumerated keyboard and launched usbhidd, which is the inputd
|
|
# producer for the keyboard. Without this marker the autospawn path
|
|
# is not proven even if enumeration succeeded.
|
|
if ! grep -q "USB HID driver spawned with scheme" "$log_file"; then
|
|
echo "ERROR: usbhidd did not auto-spawn for the EHCI keyboard" >&2
|
|
echo " Expected: 'USB HID driver spawned with scheme \`...\` ...'" >&2
|
|
grep -i "usbhidd\|HID driver" "$log_file" | tail -10 >&2 || true
|
|
echo " See $log_file" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# 5. No panics anywhere in the boot.
|
|
if grep -qiE "panic|RUST_BACKTRACE" "$log_file"; then
|
|
echo "ERROR: panic detected in boot log" >&2
|
|
grep -iE "panic|RUST_BACKTRACE" "$log_file" | head -5 >&2
|
|
echo " See $log_file" >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "EHCI_DRIVER=ehcid"
|
|
echo "EHCI_CONTROLLER=usb-ehci"
|
|
echo "EHCI_MACHINE=q35"
|
|
echo "EHCI_AUTOSPAWN=usbhidd"
|
|
echo "EHCI_LOG=$log_file"
|
|
echo "EHCI keyboard class-driver autospawn confirmed in $log_file"
|
|
exit 0
|
|
fi
|
|
|
|
exec qemu-system-x86_64 \
|
|
-name "Red Bear OS x86_64" \
|
|
-machine q35 \
|
|
-device usb-ehci,id=ehci \
|
|
-device usb-kbd,bus=ehci.0 \
|
|
-smp 4 \
|
|
-m 2048 \
|
|
-bios "$firmware" \
|
|
-chardev stdio,id=debug,signal=off,mux=on \
|
|
-serial chardev:debug \
|
|
-mon chardev=debug \
|
|
-device ich9-intel-hda -device hda-output \
|
|
-device virtio-net,netdev=net0 \
|
|
-netdev user,id=net0 \
|
|
-object filter-dump,id=f1,netdev=net0,file="build/$arch/$config/network.pcap" \
|
|
-vga std \
|
|
"${boot_args[@]}" \
|
|
-drive file="$extra",format=raw,if=none,id=drv1,snapshot=on \
|
|
-device nvme,drive=drv1,serial=NVME_EXTRA \
|
|
-enable-kvm -cpu host
|