Files
RedBear-OS/local/scripts/test-usb-hub-qemu.sh
T
vasilito 6446d440bf usb: hub-child enumeration proven end-to-end in QEMU + tighten hub test
The usb-kbd behind the QEMU usb-hub now enumerates fully: debounce ->
port reset -> enable -> second debounce -> pre-attach -> attach ->
ATTACH 5.2 -> slot 3 -> addressed -> descriptors read -> HID class match
-> 'Loading subdriver "USB HID" for port 5.2' (class 3.1 proto 1,
keyboard boot protocol).

Root cause of the final stall: an unconditional SetPortFeature
(PORT_INDICATOR) for every connected+enabled port NAK-hung on QEMU's
indicator-less hub (no control-transfer timeout), blocking the flow
before the post-enable debounce. Now gated on
wHubCharacteristics.HUB_CHAR_PORTIND like Linux has_indicators.

test-usb-hub-qemu.sh: check #5 now requires the hub-child subdriver
line ('Loading subdriver "USB HID" for port <root>.<child>') so a
root-port HID (tablet) can no longer satisfy it — closes a false-
positive hole. Timeout raised to 360s for slow TT-path driver bring-up.

Docs (plan P3-A runtime validation + matrix): full hub + hub-child
chain recorded as QEMU-proven; remaining items (intermittent reactor
startup race, slow TT-path Evaluate Context) noted.
2026-07-19 19:55:27 +09:00

209 lines
6.7 KiB
Bash
Executable File

#!/usr/bin/env bash
# Launch or validate USB hub enumeration (P3-A state machine) in QEMU.
#
# Topology under test:
# qemu-xhci root port 1 → usb-hub (USB 2.0, 8 ports)
# └─ hub port 2 → usb-kbd
# qemu-xhci root port 2 → usb-tablet (control case, direct attach)
#
# Validates: hub descriptor read, usbhubd spawn, EP1 change detection or
# polling fallback, power-on, debounce, reset-wait, attach delegation to
# xhcid, child enumeration behind the hub (usbhidd spawn), no panics.
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-usb-hub-qemu.sh [--check] [config]
Boot or validate USB hub enumeration (usbhubd P3-A state machine) on a
Red Bear image in QEMU. Defaults to redbear-mini.
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 artifact of build-redbear.sh). harddrive.img
# is only produced by older `make all`-style flows and can go stale — booting
# it tests the wrong tree (this happened in practice: a stale Jul-17 image
# with a pre-version-sync bootloader masked the current build).
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" >&2
echo "Build it first with: ./local/scripts/build-redbear.sh $config" >&2
exit 1
fi
pkill -f "qemu-system-x86_64.*$config" 2>/dev/null || true
sleep 1
# Assemble the boot-media args once so check and interactive modes share it.
if [[ "$boot_media" == "iso" ]]; then
media_args=(-cdrom "$iso")
else
if [[ ! -f "$extra" ]]; then
truncate -s 1g "$extra"
fi
media_args=(
-drive "file=$image,format=raw,if=none,id=drv0,snapshot=on"
-device nvme,drive=drv0,serial=NVME_SERIAL
-drive "file=$extra,format=raw,if=none,id=drv1,snapshot=on"
-device nvme,drive=drv1,serial=NVME_EXTRA
)
fi
if [[ "$check_mode" -eq 1 ]]; then
log_file="build/$arch/$config/usb-hub-check.log"
rm -f "$log_file"
set +e
# Timeout budget: hub enumeration itself is quick, but a hub-child's
# class driver then performs its own descriptor/control transfers on the
# (slow, emulated) TT path, and the guest clock runs several times
# slower than wall time under load. 180s was not enough for a hub-child
# HID driver to finish registering after being spawned.
timeout 360s qemu-system-x86_64 \
-name "Red Bear OS x86_64" \
-device qemu-xhci,id=xhci \
-device usb-hub,bus=xhci.0,port=1 \
-device usb-kbd,bus=xhci.0,port=1.2 \
-device usb-tablet,bus=xhci.0,port=2 \
-smp 4 \
-m 2048 \
-bios "$firmware" \
-chardev stdio,id=debug,signal=off,mux=on \
-serial chardev:debug \
-mon chardev=debug \
-machine q35 \
-device ich9-intel-hda -device hda-output \
-device virtio-net,netdev=net0 \
-netdev user,id=net0 \
-nographic -vga none \
"${media_args[@]}" \
-enable-kvm -cpu host \
> "$log_file" 2>&1
status=$?
set -e
fail=0
# 1. xhcid detected the controller.
if ! grep -q "^.*XHCI " "$log_file"; then
echo "ERROR: xHCI controller not detected in boot log; see $log_file" >&2
exit 1
fi
# 2. usbhubd spawned for the hub device. The "USB HUB driver spawned"
# log line is emitted before setup_logging and never reaches the
# serial console, so the reliable spawn proof is the hub-descriptor
# read. Serial output from many daemons interleaves mid-line, so
# match a short fragment rather than the full sentence.
if ! grep -q "port(s) detected" "$log_file"; then
echo "ERROR: usbhubd did not spawn or hub descriptor read failed" >&2
echo " Expected fragment: 'port(s) detected'" >&2
grep -i "usbhubd" "$log_file" | tail -10 >&2 || true
echo " See $log_file" >&2
exit 1
fi
# 4. Change detection is active (interrupt EP or polling fallback).
if ! grep -Eq "change detection|using polling|falling back to polling" "$log_file"; then
echo "ERROR: usbhubd did not report a change-detection mode" >&2
echo " See $log_file" >&2
exit 1
fi
# 5. Hub-child enumeration: the usb-kbd sits BEHIND the hub (QEMU port
# 1.2, a dotted PortId). Requiring the hub-child subdriver line proves
# the full chain (power-on → debounce → reset-wait → enable → attach →
# xhcid enumerate → class match). A root-port HID (the tablet on port 2)
# must NOT satisfy this — the match is on the hub-child "5.2" form.
if ! grep -Eq 'Loading subdriver "USB HID" for port [0-9]+\.[0-9]+' "$log_file"; then
echo "ERROR: hub-child (usb-kbd behind hub) did not enumerate to a HID driver" >&2
echo " Expected: 'Loading subdriver \"USB HID\" for port <root>.<child>'" >&2
echo " See $log_file" >&2
exit 1
fi
# 6. 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 "HUB_ENUMERATION=ok"
echo "HUB_LOG=$log_file"
echo "USB hub enumeration confirmed in $log_file"
exit 0
fi
exec qemu-system-x86_64 \
-name "Red Bear OS x86_64" \
-device qemu-xhci,id=xhci \
-device usb-hub,bus=xhci.0,port=1 \
-device usb-kbd,bus=xhci.0,port=1.2 \
-device usb-tablet,bus=xhci.0,port=2 \
-smp 4 \
-m 2048 \
-bios "$firmware" \
-chardev stdio,id=debug,signal=off,mux=on \
-serial chardev:debug \
-mon chardev=debug \
-machine q35 \
-device ich9-intel-hda -device hda-output \
-device virtio-net,netdev=net0 \
-netdev user,id=net0 \
-vga std \
"${media_args[@]}" \
-enable-kvm -cpu host