6446d440bf
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.
209 lines
6.7 KiB
Bash
Executable File
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
|