The GitHub runner's Azure kernel has CONFIG_USB_HID disabled, so it cannot
enumerate the simulated HID keyboard over USB/IP. Install and boot the Ubuntu
generic kernel instead (which ships usbhid/hid-generic as modules and
usbip/cdc-acm in linux-modules-extra), bundle + load the full module chain
(hid, hid-generic, usbhid, usbip-core, vhci-hcd, cdc-acm) in the guest, and
restore the keyboard check as a gating assertion. Now both the CDC ACM serial
device and the HID keyboard are verified in CI.
- Allow overriding KERNEL / VMLINUZ / MODTREE so the test can run against a
downloaded kernel (e.g. to reproduce a CI kernel locally).
- Place decompressed kernel modules under /lib/modules/$KERNEL regardless of
where they are found.
- Make the HID-keyboard check best-effort: it is reported but does not gate the
result, because some kernels (Ubuntu azure/generic) do not complete HID
enumeration over USB/IP. The CDC ACM serial device remains the always-verified
device.
- Share the initramfs builder's MODTREE setting with run-qemu-test.sh.
On Ubuntu, linux-tools-common installs /usr/bin/usbip as a tiny shell script
that execs a versioned binary, which is why the guest reported 'usbip: not
found'. Locate a real ELF usbip binary (e.g. /usr/sbin/usbip from the usbip
package, or /usr/lib/linux-tools/*/usbip) and bundle that instead.
The Azure kernel does not load zstd/xz/gzip-compressed modules via insmod, so
decompress them to plain .ko when bundling the initramfs. Also copy the usbip
client to both /usr/sbin and /usr/bin (and its real path), and log its presence
in the guest for diagnostics.
On GitHub runners /boot/vmlinuz-* is mode 0600 (root-only), so a plain cp by
the non-root runner user fails. Fall back to sudo cp + chmod so QEMU can read
the kernel.
On GitHub runners the kernel image (/boot/vmlinuz-*) is root-only and
/dev/kvm is not writable by the runner user. Copy the kernel to a readable
temp path, and when /dev/kvm needs root, run QEMU under passwordless sudo to
get KVM acceleration (falling back to TCG). Ensure the serial log stays
readable for the artifact upload.
ldd can return non-zero / empty for statically-linked or symlinked binaries
(e.g. the distro usbip tool), which under set -o pipefail aborted the build.
Copy the dynamic loader plus a comprehensive set of libc/usbip libraries
explicitly, and drive the ldd-based copy via process substitution so it
never aborts. Also follow the usbip symlink and preserve its real path.
Under set -o pipefail, 'find ... | head -1' aborts with SIGPIPE once head
reads one line, which happened after installing linux-modules-extra (a large
tree). Use find -print -quit instead of piping to head, and add an ERR trap
for clearer diagnostics.
The GitHub runner uses an Azure kernel that does not ship the usbip /
cdc-acm modules by default; on Ubuntu those live in the separate
linux-modules-extra package. Install it and make the initramfs builder /
guest loader accept compressed kernel modules (.ko.zst, .ko.xz, .ko.gz).
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.