Files
usbip/scripts/qemu/run-qemu-test.sh
Jiajie Chen 411e63f8e8 Add QEMU end-to-end test verifying simulated USB devices
Boot a real Linux inside QEMU and confirm that simulated USB/IP devices
work against a real kernel (vhci-hcd + cdc_acm):

- Add examples/demo.rs: a single server exporting a HID keyboard and a
  CDC ACM serial device on port 3240.
- Add scripts/qemu/: build a minimal initramfs (host kernel + busybox +
  usbip tool + demo server), boot it in QEMU, attach the simulated devices
  over 127.0.0.1, and verify:
    * the serial port emits 'a' (0x61)  -> SERIAL_TEST
    * the keyboard generates a KEY_1 input event -> KEYBOARD_TEST
- Add .github/workflows/qemu.yml to run this in CI (KVM when available,
  TCG fallback otherwise), uploading the serial console log as an artefact.
2026-09-04 00:16:40 +08:00

110 lines
3.5 KiB
Bash
Executable File

#!/usr/bin/env bash
# Run the QEMU-based usbip end-to-end test.
#
# This builds the `demo` example, assembles an initramfs from the running
# kernel + modules + busybox + usbip tool, boots it in QEMU, and verifies that
# the simulated serial port and keyboard actually work inside a real Linux.
#
# Usage: run-qemu-test.sh [--build] [--dump-log]
# --build build the demo example first (default: assume prebuilt)
# --dump-log print the full serial console log on stdout (for debugging)
#
# Exit: 0 if the test passed, non-zero otherwise.
set -uo pipefail
REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
cd "$REPO_ROOT"
BUILD=0
DUMP=0
for arg in "$@"; do
case "$arg" in
--build) BUILD=1 ;;
--dump-log) DUMP=1 ;;
esac
done
if [ "$BUILD" = "1" ]; then
echo "=== building demo example ==="
cargo build --example demo --release --all-features || exit 1
fi
DEMO_BIN="$REPO_ROOT/target/release/examples/demo"
[ -e "$DEMO_BIN" ] || { echo "ERROR: $DEMO_BIN not found; run with --build"; exit 1; }
KERNEL=$(uname -r)
VMLINUZ=/boot/vmlinuz-$KERNEL
[ -e "$VMLINUZ" ] || { echo "ERROR: no vmlinuz for kernel $KERNEL at $VMLINUZ"; exit 1; }
WORK=$(mktemp -d)
trap 'rm -rf "$WORK"' EXIT
# Persist the serial console log for CI artefact upload / debugging.
SERIAL_LOG="${REPO_ROOT}/.qemu-serial.log"
rm -f "$SERIAL_LOG"
echo "=== building initramfs ==="
DEMO_BIN="$DEMO_BIN" KERNEL="$KERNEL" "$SCRIPT_DIR/build-initramfs.sh" "$WORK/initramfs.cpio.gz" || exit 1
QEMU_CMD=(qemu-system-x86_64 -m 512 -smp 2)
# Use hardware acceleration if /dev/kvm is present, otherwise fall back to TCG.
# FORCE_TCG=1 forces software emulation (useful for debugging / CI runners w/o KVM).
if [ "${FORCE_TCG:-0}" != "1" ] && [ -e /dev/kvm ] && [ -w /dev/kvm ]; then
QEMU_CMD+=(-enable-kvm -cpu host)
echo "=== using KVM acceleration ==="
else
QEMU_CMD+=(-cpu max)
echo "=== /dev/kvm not available, falling back to TCG (slow) ==="
fi
QEMU_CMD+=(
-kernel "$VMLINUZ"
-initrd "$WORK/initramfs.cpio.gz"
-append "console=ttyS0 panic=-1"
-display none
-serial "file:$SERIAL_LOG"
-monitor none
-no-reboot
)
KVM_FAIL=0
echo "=== launching QEMU ==="
if [[ " ${QEMU_CMD[*]} " == *" -enable-kvm "* ]]; then
timeout 300 "${QEMU_CMD[@]}" 2>"$WORK/qemu.err" || { KVM_FAIL=$?; }
# If KVM failed (e.g. permission), retry with TCG once.
if [ "$KVM_FAIL" != "0" ]; then
echo "=== KVM run returned $KVM_FAIL, retrying with TCG ==="
rm -f "$SERIAL_LOG"
timeout 600 qemu-system-x86_64 -m 512 -smp 2 -cpu max \
-kernel "$VMLINUZ" \
-initrd "$WORK/initramfs.cpio.gz" \
-append "console=ttyS0 panic=-1" \
-display none -serial "file:$SERIAL_LOG" -monitor none -no-reboot \
2>"$WORK/qemu.err" || true
fi
else
timeout 600 "${QEMU_CMD[@]}" 2>"$WORK/qemu.err" || true
fi
echo "=== serial log (tail) ==="
tail -80 "$SERIAL_LOG" || true
if [ "$DUMP" = "1" ]; then
echo "=== full serial log ==="
cat "$SERIAL_LOG" 2>/dev/null || true
echo "=== qemu stderr ==="
cat "$WORK/qemu.err" 2>/dev/null || true
fi
if grep -q "TEST_RESULT: PASS" "$SERIAL_LOG"; then
echo "SUCCESS: QEMU usbip test passed"
echo "VERIFY_SERIAL=$(grep -c 'SERIAL_TEST: PASS' "$SERIAL_LOG")"
echo "VERIFY_KEYBOARD=$(grep -c 'KEYBOARD_TEST: PASS' "$SERIAL_LOG")"
exit 0
else
echo "FAILURE: TEST_RESULT: PASS not found in serial log"
exit 1
fi