mirror of
https://github.com/jiegec/usbip.git
synced 2026-09-30 09:57:06 -04:00
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.
This commit is contained in:
54
.github/workflows/qemu.yml
vendored
Normal file
54
.github/workflows/qemu.yml
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
name: QEMU usbip integration test
|
||||
|
||||
# Boot a real Linux inside QEMU and verify that the *simulated* USB devices
|
||||
# exported by this crate actually work (a CDC ACM serial port and a HID keyboard).
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
qemu-test:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: Install system packages
|
||||
run: |
|
||||
set -euo pipefail
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y --no-install-recommends \
|
||||
qemu-system-x86 busybox-static cpio usb.ids
|
||||
|
||||
# The usbip userspace tool ships in the distro 'usbip' package (universe),
|
||||
# or in linux-tools. Try both and make sure a binary is available.
|
||||
if ! sudo apt-get install -y usbip; then
|
||||
sudo apt-get install -y linux-tools-common linux-tools-generic
|
||||
fi
|
||||
|
||||
echo "--- installed binaries ---"
|
||||
ls -l /usr/bin/busybox /usr/bin/qemu-system-x86_64 2>/dev/null || true
|
||||
ls -l /usr/sbin/usbip /usr/bin/usbip 2>/dev/null || true
|
||||
|
||||
- name: Preflight checks
|
||||
run: |
|
||||
set -euo pipefail
|
||||
echo "kernel: $(uname -r)"
|
||||
ls -l /boot/vmlinuz-$(uname -r)
|
||||
ls -l /lib/modules/$(uname -r)/kernel/drivers/usb/usbip/ 2>/dev/null || true
|
||||
ls -l /lib/modules/$(uname -r)/kernel/drivers/usb/class/cdc-acm.ko 2>/dev/null || true
|
||||
command -v usbip >/dev/null 2>&1 || test -x /usr/sbin/usbip
|
||||
test -e /dev/kvm && echo "KVM: available" || echo "KVM: not available (will use TCG)"
|
||||
|
||||
- name: Run QEMU end-to-end test
|
||||
run: |
|
||||
./scripts/qemu/run-qemu-test.sh --build --dump-log
|
||||
|
||||
- name: Upload serial console log
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: qemu-serial-log
|
||||
path: .qemu-serial.log
|
||||
if-no-files-found: ignore
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
||||
/target
|
||||
Cargo.lock
|
||||
/.qemu-serial.log
|
||||
|
||||
22
README.md
22
README.md
@@ -31,11 +31,12 @@ cargo build --release
|
||||
|
||||
### Examples
|
||||
|
||||
The `examples/` directory contains three example programs:
|
||||
The `examples/` directory contains four example programs:
|
||||
|
||||
1. **hid_keyboard**: Simulate a HID keyboard that types something every second
|
||||
2. **cdc_acm_serial**: Simulate a CDC ACM serial device that receives a character every second
|
||||
3. **host**: Act as a USB/IP server, sharing physical devices from the host machine to remote clients
|
||||
4. **demo**: Simulate a HID keyboard *and* a CDC ACM serial device together (used by the QEMU test)
|
||||
|
||||
#### Running an example
|
||||
|
||||
@@ -55,6 +56,25 @@ usbip list -r $remote_ip
|
||||
usbip attach -r $remote_ip -b $bus_id
|
||||
```
|
||||
|
||||
### QEMU end-to-end test
|
||||
|
||||
The simulated devices can be verified against a real Linux kernel booted under
|
||||
QEMU. The test assembles a minimal initramfs from the running kernel, its USB/IP
|
||||
and cdc_acm modules, a static busybox and the `usbip` userspace client, then
|
||||
boots it and uses `vhci-hcd` to attach the simulated keyboard and serial device.
|
||||
Inside the guest it confirms the serial port emits `'a'` and that the keyboard
|
||||
generates a `KEY_1` input event.
|
||||
|
||||
Run it locally (requires `qemu-system-x86`, a static `busybox`, `cpio`, and the
|
||||
`usbip` tool):
|
||||
|
||||
```bash
|
||||
./scripts/qemu/run-qemu-test.sh --build --dump-log
|
||||
```
|
||||
|
||||
It runs under KVM when available and falls back to QEMU TCG otherwise. It is
|
||||
also wired into CI via `.github/workflows/qemu.yml`.
|
||||
|
||||
## License
|
||||
|
||||
MIT License - see [LICENSE](LICENSE) file for details.
|
||||
|
||||
94
examples/demo.rs
Normal file
94
examples/demo.rs
Normal file
@@ -0,0 +1,94 @@
|
||||
//! Simulate a HID keyboard **and** a CDC ACM serial device on a single USB/IP server.
|
||||
//!
|
||||
//! This example is intended for the QEMU end-to-end test: it exports two simulated
|
||||
//! devices on one server port (`0.0.0.0:3240`) so a real Linux (booted inside QEMU)
|
||||
//! can attach to both of them and verify that the simulated serial port and keyboard
|
||||
//! actually work.
|
||||
use std::net::*;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::Duration;
|
||||
|
||||
use log::*;
|
||||
use tokio::time;
|
||||
|
||||
/// Build a simulated HID keyboard device.
|
||||
fn keyboard_device() -> usbip::UsbDevice {
|
||||
let handler = Arc::new(Mutex::new(
|
||||
Box::new(usbip::hid::UsbHidKeyboardHandler::new_keyboard())
|
||||
as Box<dyn usbip::UsbInterfaceHandler + Send>,
|
||||
));
|
||||
let mut device = usbip::UsbDevice::new(1).with_interface(
|
||||
usbip::ClassCode::HID as u8,
|
||||
0x00,
|
||||
0x00,
|
||||
Some("Test HID"),
|
||||
vec![usbip::UsbEndpoint {
|
||||
address: 0x81, // IN
|
||||
attributes: 0x03, // Interrupt
|
||||
max_packet_size: 0x08, // 8 bytes
|
||||
interval: 10,
|
||||
}],
|
||||
handler.clone(),
|
||||
);
|
||||
device.bus_id = "1-1".to_string();
|
||||
device
|
||||
}
|
||||
|
||||
/// Build a simulated CDC ACM serial device.
|
||||
fn serial_device() -> usbip::UsbDevice {
|
||||
let handler =
|
||||
Arc::new(Mutex::new(Box::new(usbip::cdc::UsbCdcAcmHandler::new())
|
||||
as Box<dyn usbip::UsbInterfaceHandler + Send>));
|
||||
let mut device = usbip::UsbDevice::new(2).with_interface(
|
||||
usbip::ClassCode::CDC as u8,
|
||||
usbip::cdc::CDC_ACM_SUBCLASS,
|
||||
0x00,
|
||||
Some("Test CDC ACM"),
|
||||
usbip::cdc::UsbCdcAcmHandler::endpoints(),
|
||||
handler.clone(),
|
||||
);
|
||||
device.bus_id = "1-2".to_string();
|
||||
device
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
env_logger::init();
|
||||
let keyboard = keyboard_device();
|
||||
let handler = keyboard.interfaces.first().unwrap().handler.clone();
|
||||
|
||||
let serial = serial_device();
|
||||
let serial_handler = serial.interfaces.first().unwrap().handler.clone();
|
||||
|
||||
let server = Arc::new(usbip::UsbIpServer::new_simulated(vec![keyboard, serial]));
|
||||
let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 3240);
|
||||
tokio::spawn(usbip::server(addr, server));
|
||||
|
||||
loop {
|
||||
// Drive the simulated devices every second:
|
||||
// - the keyboard presses "1"
|
||||
// - the serial device emits a character 'a'
|
||||
time::sleep(Duration::new(1, 0)).await;
|
||||
|
||||
if let Some(hid) = handler
|
||||
.lock()
|
||||
.unwrap()
|
||||
.as_any()
|
||||
.downcast_mut::<usbip::hid::UsbHidKeyboardHandler>()
|
||||
{
|
||||
hid.pending_key_events
|
||||
.push_back(usbip::hid::UsbHidKeyboardReport::from_ascii(b'1'));
|
||||
info!("Simulate a key event");
|
||||
}
|
||||
|
||||
if let Some(acm) = serial_handler
|
||||
.lock()
|
||||
.unwrap()
|
||||
.as_any()
|
||||
.downcast_mut::<usbip::cdc::UsbCdcAcmHandler>()
|
||||
{
|
||||
acm.tx_buffer.push(b'a');
|
||||
info!("Simulate a char input");
|
||||
}
|
||||
}
|
||||
}
|
||||
91
scripts/qemu/build-initramfs.sh
Executable file
91
scripts/qemu/build-initramfs.sh
Executable file
@@ -0,0 +1,91 @@
|
||||
#!/usr/bin/env bash
|
||||
# Build a minimal initramfs for the QEMU usbip end-to-end test.
|
||||
#
|
||||
# The initramfs bundles everything the guest needs to:
|
||||
# 1. boot (via the host kernel, /boot/vmlinuz-$(uname -r))
|
||||
# 2. load the vhci-hcd + cdc_acm kernel modules
|
||||
# 3. run the compiled usbip demo server (our library under test)
|
||||
# 4. run the usbip userspace client and attach the simulated devices
|
||||
#
|
||||
# Usage: build-initramfs.sh <output.cpio.gz>
|
||||
#
|
||||
# Overridable via env:
|
||||
# KERNEL kernel version whose modules/kernel to use (default: uname -r)
|
||||
# BUSYBOX path to a *static* busybox binary (default: `which busybox`)
|
||||
# USBIP path to the usbip userspace binary (default: auto-detect)
|
||||
# DEMO_BIN path to the compiled demo example (default: target/release/examples/demo)
|
||||
set -euo pipefail
|
||||
|
||||
OUT="${1:?usage: build-initramfs.sh <output.cpio.gz>}"
|
||||
KERNEL="${KERNEL:-$(uname -r)}"
|
||||
BUSYBOX="${BUSYBOX:-$(command -v busybox)}"
|
||||
USBIP="${USBIP:-$(command -v usbip || echo /usr/sbin/usbip)}"
|
||||
DEMO_BIN="${DEMO_BIN:-target/release/examples/demo}"
|
||||
|
||||
[ -e "$BUSYBOX" ] || { echo "ERROR: busybox not found at $BUSYBOX"; exit 1; }
|
||||
[ -e "$USBIP" ] || { echo "ERROR: usbip not found at $USBIP"; exit 1; }
|
||||
[ -e "$DEMO_BIN" ] || { echo "ERROR: demo binary not found at $DEMO_BIN"; exit 1; }
|
||||
|
||||
ROOT=$(mktemp -d)
|
||||
trap 'rm -rf "$ROOT"' EXIT
|
||||
|
||||
echo "kernel=$KERNEL busybox=$BUSYBOX usbip=$USBIP demo=$DEMO_BIN"
|
||||
|
||||
# --- binaries ---
|
||||
mkdir -p "$ROOT/bin" "$ROOT/sbin" "$ROOT/usr/sbin" "$ROOT/usr/share/misc"
|
||||
cp "$BUSYBOX" "$ROOT/bin/busybox"
|
||||
|
||||
# busybox applet symlinks (see init.sh for the applets we use)
|
||||
for a in sh insmod modprobe ls cat dmesg grep sleep dd hexdump od mount mknod \
|
||||
poweroff reboot echo printf test head tail cp rm more seq timeout mkdir \
|
||||
sync umount true false mdev find ifconfig stty; do
|
||||
ln -sf busybox "$ROOT/bin/$a"
|
||||
done
|
||||
ln -sf busybox "$ROOT/sbin/modprobe"
|
||||
ln -sf busybox "$ROOT/sbin/insmod"
|
||||
ln -sf busybox "$ROOT/sbin/mdev"
|
||||
|
||||
cp "$USBIP" "$ROOT/usr/sbin/usbip"
|
||||
cp "$DEMO_BIN" "$ROOT/demo_server"
|
||||
|
||||
# --- kernel modules (preserve layout so the guest can `find` them) ---
|
||||
for mod in usbip-core vhci-hcd cdc-acm; do
|
||||
ko=$(find "/lib/modules/$KERNEL" -name "$mod.ko" 2>/dev/null | head -1)
|
||||
if [ -z "$ko" ]; then
|
||||
echo "ERROR: kernel module $mod.ko not found for kernel $KERNEL"
|
||||
exit 1
|
||||
fi
|
||||
mkdir -p "$ROOT$(dirname "$ko")"
|
||||
cp "$ko" "$ROOT$ko"
|
||||
done
|
||||
|
||||
# --- usb.ids database required by the usbip tool ---
|
||||
UI="$(find /usr/share -name usb.ids 2>/dev/null | head -1)"
|
||||
[ -n "$UI" ] && cp "$UI" "$ROOT/usr/share/misc/usb.ids" || echo "WARN: usb.ids not found"
|
||||
|
||||
# --- libraries needed by usbip + demo server (copy preserving absolute path) ---
|
||||
copy_libs() {
|
||||
local bin="$1"
|
||||
ldd "$bin" 2>/dev/null \
|
||||
| grep -oE '/[^ ]+\.so[^ ]*' \
|
||||
| sort -u \
|
||||
| while read -r lib; do
|
||||
[ -e "$lib" ] || continue
|
||||
mkdir -p "$ROOT$(dirname "$lib")"
|
||||
cp -L "$lib" "$ROOT$lib"
|
||||
done
|
||||
}
|
||||
copy_libs "$USBIP"
|
||||
copy_libs "$DEMO_BIN"
|
||||
|
||||
# --- device dirs (devtmpfs populates /dev at runtime) ---
|
||||
mkdir -p "$ROOT/dev" "$ROOT/proc" "$ROOT/sys" "$ROOT/tmp"
|
||||
mknod -m 666 "$ROOT/dev/console" c 5 1 2>/dev/null || true
|
||||
|
||||
# --- init script ---
|
||||
cp "$(dirname "$0")/init.sh" "$ROOT/init"
|
||||
chmod +x "$ROOT/init"
|
||||
|
||||
# --- cpio archive ---
|
||||
( cd "$ROOT" && find . -print0 | cpio --null -ov --format=newc 2>/dev/null | gzip -9 > "$OUT" )
|
||||
echo "wrote $OUT"
|
||||
142
scripts/qemu/init.sh
Executable file
142
scripts/qemu/init.sh
Executable file
@@ -0,0 +1,142 @@
|
||||
#!/bin/busybox sh
|
||||
# QEMU end-to-end usbip test init script.
|
||||
#
|
||||
# Runs *inside* the QEMU guest. It:
|
||||
# 1. mounts proc/sys/devtmpfs
|
||||
# 2. loads vhci-hcd + cdc_acm kernel modules
|
||||
# 3. starts the compiled usbip demo server (our library under test)
|
||||
# 4. uses the usbip client to attach the simulated HID keyboard + CDC ACM serial
|
||||
# 5. verifies the serial device emits 'a' and the keyboard emits KEY_1
|
||||
# 6. prints "TEST_RESULT: PASS" or "TEST_RESULT: FAIL" and powers off
|
||||
#
|
||||
# The workflow greps the serial console output for TEST_RESULT.
|
||||
set -x
|
||||
|
||||
PATH=/sbin:/bin:/usr/sbin:/usr/bin
|
||||
export PATH
|
||||
|
||||
mount -t proc proc /proc
|
||||
mount -t sysfs sysfs /sys
|
||||
mount -t devtmpfs devtmpfs /dev 2>/dev/null || true
|
||||
mkdir -p /dev/pts
|
||||
mount -t devpts devpts /dev/pts 2>/dev/null || true
|
||||
mount -t tmpfs tmpfs /tmp 2>/dev/null || true
|
||||
|
||||
# Bring up loopback so the usbip client can reach the local server on 127.0.0.1
|
||||
ifconfig lo 127.0.0.1 netmask 255.0.0.0 up 2>/dev/null || echo "WARN: could not bring up lo"
|
||||
|
||||
echo "=== init: mounting done ==="
|
||||
|
||||
K=$(uname -r)
|
||||
echo "=== kernel: $K ==="
|
||||
|
||||
echo "=== loading modules ==="
|
||||
for mod in usbip-core vhci-hcd cdc-acm; do
|
||||
ko=$(find /lib/modules -name "$mod.ko" 2>/dev/null | head -1)
|
||||
echo "- loading $ko"
|
||||
insmod "$ko" || echo "WARN: failed to load $ko"
|
||||
done
|
||||
sleep 1
|
||||
|
||||
echo "=== starting usbip demo server ==="
|
||||
RUST_LOG=info /demo_server > /tmp/server.log 2>&1 &
|
||||
SERVER_PID=$!
|
||||
sleep 2
|
||||
if ! kill -0 $SERVER_PID 2>/dev/null; then
|
||||
echo "!!! demo server died, log:"
|
||||
cat /tmp/server.log
|
||||
fi
|
||||
|
||||
echo "=== usbip list ==="
|
||||
usbip list -r 127.0.0.1
|
||||
|
||||
# Extract busids (e.g. 1-1, 1-2) from "usbip list -r" output
|
||||
BUSIDS=$(usbip list -r 127.0.0.1 2>/dev/null \
|
||||
| sed -n 's/^[[:space:]]*\([0-9][0-9-]*\):.*/\1/p')
|
||||
echo "=== bus ids: [$BUSIDS] ==="
|
||||
|
||||
PASS=1
|
||||
|
||||
if [ -z "$BUSIDS" ]; then
|
||||
echo "TEST_RESULT: FAIL (no exportable devices)"
|
||||
dmesg | tail -40
|
||||
poweroff -f
|
||||
sleep 1
|
||||
exit 1
|
||||
fi
|
||||
|
||||
for BUSID in $BUSIDS; do
|
||||
echo "=== attaching $BUSID ==="
|
||||
# Note: the usbip tool may report a spurious "record connection" error after a
|
||||
# successful attach; the kernel still enumerates the device, so we don't abort here.
|
||||
usbip attach -r 127.0.0.1 -b "$BUSID"
|
||||
echo "=== attach exit: $? ==="
|
||||
sleep 2
|
||||
done
|
||||
|
||||
echo "=== waiting for devices ==="
|
||||
for i in $(seq 1 30); do
|
||||
ACM=$(ls /dev/ttyACM* 2>/dev/null | head -1)
|
||||
EV=$(awk '
|
||||
/^S:.*vhci_hcd/ { f=1 }
|
||||
f && /^H:/ { if (match($0, /event[0-9]+/)) { print substr($0, RSTART, RLENGTH); exit } }
|
||||
' /proc/bus/input/devices)
|
||||
echo "iter $i: ACM=[$ACM] event=[$EV]"
|
||||
if [ -n "$ACM" ] && [ -n "$EV" ]; then
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
echo "=== /proc/bus/input/devices ==="
|
||||
cat /proc/bus/input/devices 2>/dev/null
|
||||
echo
|
||||
echo "=== /dev listing ==="
|
||||
ls -la /dev/ttyACM* /dev/input/ 2>/dev/null
|
||||
|
||||
# ---- Serial test: read one raw byte from /dev/ttyACM0, expect 'a' (0x61) ----
|
||||
if [ -n "$ACM" ]; then
|
||||
echo "=== serial test: reading $ACM (raw) ==="
|
||||
stty -F "$ACM" raw -echo 2>/dev/null || true
|
||||
DATA=$(timeout 8 dd if="$ACM" bs=1 count=1 2>/dev/null | od -An -tx1 -v | tr -d ' \n')
|
||||
echo "serial byte: [$DATA]"
|
||||
case "$DATA" in
|
||||
61) echo "SERIAL_TEST: PASS (got 'a' = 0x61)" ;;
|
||||
*) echo "SERIAL_TEST: FAIL ([$DATA])"; PASS=0 ;;
|
||||
esac
|
||||
else
|
||||
echo "SERIAL_TEST: FAIL (no /dev/ttyACM device)"; PASS=0
|
||||
fi
|
||||
|
||||
# ---- Keyboard test: read the vhci HID event device, look for KEY_1 (code=2) ----
|
||||
if [ -n "$EV" ]; then
|
||||
echo "=== keyboard test: reading /dev/input/$EV (timeout 6s) ==="
|
||||
timeout 6 cat "/dev/input/$EV" > /tmp/kbd.bin 2>/dev/null
|
||||
HEX=$(od -An -tx1 -v /tmp/kbd.bin | tr -d ' \n')
|
||||
echo "kbd bytes: $HEX"
|
||||
# EV_KEY(type=1) KEY_1(code=2) value=1(down) -> LE bytes "0100 0200 01000000"
|
||||
case "$HEX" in
|
||||
*0100020001000000*) echo "KEYBOARD_TEST: PASS (KEY_1 down event seen)" ;;
|
||||
*) echo "KEYBOARD_TEST: FAIL"; PASS=0 ;;
|
||||
esac
|
||||
else
|
||||
echo "KEYBOARD_TEST: FAIL (no vhci input event device)"; PASS=0
|
||||
fi
|
||||
|
||||
echo "=== server.log (tail) ==="
|
||||
tail -40 /tmp/server.log 2>/dev/null
|
||||
|
||||
if [ "$PASS" = "1" ]; then
|
||||
echo "TEST_RESULT: PASS"
|
||||
else
|
||||
echo "TEST_RESULT: FAIL"
|
||||
fi
|
||||
|
||||
echo "=== dmesg tail ==="
|
||||
dmesg | tail -20
|
||||
|
||||
sync
|
||||
sleep 1
|
||||
poweroff -f
|
||||
sleep 1
|
||||
exit 0
|
||||
109
scripts/qemu/run-qemu-test.sh
Executable file
109
scripts/qemu/run-qemu-test.sh
Executable file
@@ -0,0 +1,109 @@
|
||||
#!/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
|
||||
Reference in New Issue
Block a user