2 Commits

Author SHA1 Message Date
0c3425e5af chore: release 1.3.2 2026-09-06 10:07:57 +02:00
a956ececfd fix: keep cpuid passthrough correct across in-guest reboots 2026-09-06 10:07:57 +02:00
2 changed files with 52 additions and 27 deletions

View File

@@ -8,7 +8,7 @@
# vfio-native-qemu QEMU 11.1.1 with the platform-identity patches, in /opt # vfio-native-qemu QEMU 11.1.1 with the platform-identity patches, in /opt
pkgname=vfio-native pkgname=vfio-native
pkgver=1.3.1 pkgver=1.3.2
pkgrel=1 pkgrel=1
pkgdesc="Present a libvirt guest as a self-consistent physical machine, and tune it" pkgdesc="Present a libvirt guest as a self-consistent physical machine, and tune it"
arch=('any') arch=('any')

View File

@@ -1,12 +1,18 @@
#!/bin/bash #!/bin/bash
# Waits for a just-started guest's network to actually come up, then turns # Keeps the kvm_amd cpuid_passthrough switch correct across a guest's whole life,
# kvm_amd cpuid_passthrough on. # in-guest reboots included.
# #
# The readiness signal is the guest's own tap RX counter: it is fresh every boot, # The switch must be OFF (N) while the guest enumerates CPUID at boot or Windows
# it needs nothing enabled inside the guest (no SSH, no RDP, no agent), and it only # hangs, and ON (Y) once it is up, where it clears the timer detection. The libvirt
# moves once the guest's NIC driver has really loaded - which is well past the CPU # hook only fires at VM start and stop, so a guest-initiated reboot would otherwise
# enumeration that the switch must not change under. A DHCP lease left over from a # re-enumerate with the switch still Y and hang. This watcher drops it to N on every
# previous boot cannot trip it early. # QEMU RESET and raises it again once the guest's NIC is back up.
#
# Readiness signal: the guest tap's rx_packets counter growing past a baseline. It
# only moves once the guest NIC driver has loaded, well past CPU enumeration, and it
# needs nothing enabled inside the guest. The counter is cumulative and does NOT
# reset on an in-guest reboot, so readiness is growth past the value captured at the
# reset, not an absolute threshold.
# #
# Launched as a transient systemd unit by the cpuid-passthrough hook, so it is free # Launched as a transient systemd unit by the cpuid-passthrough hook, so it is free
# to call virsh (the hook itself must not - that deadlocks libvirtd). # to call virsh (the hook itself must not - that deadlocks libvirtd).
@@ -18,24 +24,43 @@ V="virsh -c qemu:///system"
running() { [ "$($V domstate "$DOMAIN" 2>/dev/null)" = running ]; } running() { [ "$($V domstate "$DOMAIN" 2>/dev/null)" = running ]; }
tap="" tap=$($V domiflist "$DOMAIN" 2>/dev/null | awk '$1 ~ /^(vnet|tap|macvtap)/ {print $1; exit}')
i=0 RX="/sys/class/net/$tap/statistics/rx_packets"
for _ in $(seq 1 65); do # ~195 s cap, then flip anyway if still up rx() { cat "$RX" 2>/dev/null || echo 0; }
set_N() { echo N > "$PARAM/cpuid_passthrough"; }
set_Y() { printf '%s' "$BRAND" > "$PARAM/brand_string"; echo Y > "$PARAM/cpuid_passthrough"; }
# Wait until the tap rx counter grows at least 4 past $1 (guest NIC driver up again).
# The iteration floor keeps a stray pre-OS packet (a UEFI netboot attempt) from
# tripping the flip before the guest is even past its interrupt and timer setup.
wait_net_up() {
local base=$1 i=0
for _ in $(seq 1 100); do # ~300 s cap, then raise anyway if still up
i=$((i + 1)) i=$((i + 1))
running || { sleep 3; continue; } # not "running" yet at prepare time - wait running || return 1
[ -z "$tap" ] && tap=$($V domiflist "$DOMAIN" 2>/dev/null | [ "$i" -ge 4 ] && [ -n "$tap" ] && [ -r "$RX" ] &&
awk '$1 ~ /^(vnet|tap|macvtap)/ {print $1; exit}') [ "$(rx)" -ge "$((base + 4))" ] && return 0
rx="/sys/class/net/$tap/statistics/rx_packets"
# the iteration floor keeps a stray pre-OS packet (a UEFI netboot attempt) from
# tripping the flip before the guest is even past its interrupt and timer setup
if [ "$i" -ge 4 ] && [ -n "$tap" ] && [ -r "$rx" ] &&
[ "$(cat "$rx" 2>/dev/null || echo 0)" -ge 4 ]; then
break
fi
sleep 3 sleep 3
done done
return 0
}
running || exit 0 # guest went away before it came up # initial cold boot: wait for the network, then harden
printf '%s' "$BRAND" > "$PARAM/brand_string" wait_net_up 0
echo Y > "$PARAM/cpuid_passthrough" running || exit 0
set_Y
logger -t vfio-cpuid "$DOMAIN network up: cpuid_passthrough=Y" logger -t vfio-cpuid "$DOMAIN network up: cpuid_passthrough=Y"
# every in-guest reboot fires a QEMU RESET: drop to N for the re-enumeration, then
# raise it again once the guest's NIC is back. --loop streams one line per reset.
$V qemu-monitor-event --domain "$DOMAIN" --event RESET --loop 2>/dev/null | while read -r _; do
running || continue
base=$(rx)
set_N
logger -t vfio-cpuid "$DOMAIN reset: cpuid_passthrough=N for re-enumeration"
wait_net_up "$base" || continue
running || continue
set_Y
logger -t vfio-cpuid "$DOMAIN back up: cpuid_passthrough=Y"
done