Files
RedBear-OS/local/scripts/test-ohci-runtime-qemu.sh
T
vasilito ee1da17617 tests: add USB UHCI/OHCI/EHCI-autospawn/error-recovery QEMU proofs
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.
2026-07-20 10:04:56 +09:00

204 lines
6.7 KiB
Bash
Executable File

#!/usr/bin/env bash
# Launch or validate OHCI controller enumeration (P1-B) in QEMU.
#
# Topology under test:
# -machine pc → -device pci-ohci,id=ohci
# └─ bus=ohci.0 → usb-kbd
#
# Validates: pcid auto-spawns ohcid for the QEMU OHCI PCI device (class 0c0310,
# see local/config/drivers.d/20-usb.toml), ohcid maps the MMIO registers,
# initializes the controller, and detects the attached usb-kbd (port connect +
# enumeration descriptor read). This is the P1-B OHCI runtime-enumeration proof
# that closes the P8-B gap for OHCI.
#
# Evidence is read from the serial log (no guest-side checker exists that
# validates OHCI enumeration specifically), mirroring the test-xhci-irq-qemu.sh
# proof style. Proof markers come straight from
# local/recipes/drivers/ohcid/source/src/main.rs:
# "OHCI USB 1.1 at <pci_path>" (main.rs:305 — controller bound)
# "ohcid: controller initialized, polling ports" (main.rs:341 — init done)
# "ohcid: port <n> connect detected" (main.rs:348 — port event)
# "ohcid: port <n> device <vid>:<pid> class <c>" (main.rs:352 — enumerated)
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-ohci-runtime-qemu.sh [--check] [config]
Boot or validate OHCI (USB 1.1) controller enumeration on a Red Bear image in
QEMU. `pci-ohci` is a standalone PCI OHCI controller; this proof uses
`-machine pc` to match the runbook's "legacy QEMU machine" framing for both
P1-B companion-controller proofs. 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/ohci-runtime-check.log"
rm -f "$log_file"
set +e
# 180s mirrors the xHCI interrupt proof budget. OHCI enumeration on QEMU's
# emulated pci-ohci is quick once ohcid spawns, 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 pc \
-device pci-ohci,id=ohci \
-device usb-kbd,bus=ohci.0 \
-smp 4 \
-m 2048 \
-bios "$firmware" \
-chardev stdio,id=debug,signal=off,mux=on \
-serial chardev:debug \
-mon chardev=debug \
-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. ohcid bound the controller and logged its PCI path. This proves pcid
# matched the OHCI PCI class (0c0310) and spawned ohcid.
if ! grep -q "OHCI USB 1.1 at " "$log_file"; then
echo "ERROR: ohcid did not bind the OHCI controller" >&2
echo " Expected: 'OHCI USB 1.1 at <pci_path>'" >&2
grep -i "ohcid\|OHCI" "$log_file" | tail -10 >&2 || true
echo " See $log_file" >&2
exit 1
fi
# 2. MMIO mapped and the polling loop started.
if ! grep -q "ohcid: controller initialized, polling ports" "$log_file"; then
echo "ERROR: ohcid did not finish controller initialization" >&2
echo " Expected: 'ohcid: controller initialized, polling ports'" >&2
echo " See $log_file" >&2
exit 1
fi
# 3. Port connect detected for the attached usb-kbd. This is the
# enumeration-proof marker: the controller saw a device and ran the
# descriptor-read path.
if ! grep -Eq "ohcid: port [0-9]+ connect detected" "$log_file"; then
echo "ERROR: ohcid did not detect a port connect event" >&2
echo " Expected: 'ohcid: port <n> connect detected'" >&2
echo " See $log_file" >&2
exit 1
fi
# 4. 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 "OHCI_DRIVER=ohcid"
echo "OHCI_CONTROLLER=pci-ohci"
echo "OHCI_MACHINE=pc"
echo "OHCI_LOG=$log_file"
echo "OHCI controller enumeration confirmed in $log_file"
exit 0
fi
exec qemu-system-x86_64 \
-name "Red Bear OS x86_64" \
-machine pc \
-device pci-ohci,id=ohci \
-device usb-kbd,bus=ohci.0 \
-smp 4 \
-m 2048 \
-bios "$firmware" \
-chardev stdio,id=debug,signal=off,mux=on \
-serial chardev:debug \
-mon chardev=debug \
-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