69 lines
2.7 KiB
Bash
Executable File
69 lines
2.7 KiB
Bash
Executable File
#!/bin/bash
|
|
# libvirt qemu hook: drive the kvm_amd cpuid_passthrough switch around a guest's life.
|
|
#
|
|
# The switch must be OFF while a guest cold-boots - raw CPUID changes shape mid
|
|
# enumeration and Windows hangs - and ON once the guest is up, where it clears the
|
|
# TIMER detection. This applies only to the domains you opt in with
|
|
# `vm-native-cpuid enable <domain>`; it does nothing to any other guest.
|
|
#
|
|
# On start it sets the brand string from the guest's declared SKU and forces N,
|
|
# then a detached watcher flips Y once the guest's network is up; on stop it sets N.
|
|
# cpuid_passthrough and brand_string are one global kvm_amd parameter, so a single
|
|
# hardened guest is served at a time; a second is logged and left alone.
|
|
#
|
|
# Exits 0 on every path. The hook never calls virsh (that deadlocks libvirtd); the
|
|
# watcher that does is a transient systemd unit and runs after this returns.
|
|
|
|
CONF=/etc/vfio-native/cpuid-passthrough.conf
|
|
ENABLED=yes
|
|
DOMAINS=""
|
|
[ -r "$CONF" ] && . "$CONF"
|
|
[ "$ENABLED" = yes ] || exit 0
|
|
|
|
PARAM=/sys/module/kvm_amd/parameters
|
|
[ -w "$PARAM/cpuid_passthrough" ] || exit 0 # stock module, or not an AMD host
|
|
|
|
DOMAIN=$1
|
|
OPERATION=$2
|
|
case " $DOMAINS " in *" $DOMAIN "*) ;; *) exit 0 ;; esac # not an opted-in domain
|
|
|
|
RUN=/run/vfio-native
|
|
ACTIVE=$RUN/cpuid-active
|
|
WATCH=/usr/lib/vfio-native/cpuid-passthrough-watch
|
|
|
|
case "$OPERATION" in
|
|
prepare)
|
|
# the guest's declared SKU, straight from the -cpu model-id in the XML on stdin
|
|
BRAND=$(grep -oE "model-id=[^'\"]+" | head -1 | sed 's/^model-id=//')
|
|
[ -n "$BRAND" ] || exit 0 # not a full-fidelity guest
|
|
|
|
mkdir -p "$RUN"
|
|
held=$( [ -e "$ACTIVE" ] && cut -d: -f1 "$ACTIVE" )
|
|
if [ -n "$held" ] && [ "$held" != "$DOMAIN" ]; then
|
|
logger -t vfio-cpuid "cpuid passthrough held by $held; $DOMAIN left unhardened (one guest at a time)"
|
|
exit 0
|
|
fi
|
|
|
|
printf '%s' "$BRAND" > "$PARAM/brand_string"
|
|
echo N > "$PARAM/cpuid_passthrough" # safe for the cold boot
|
|
printf '%s:%s' "$DOMAIN" "$BRAND" > "$ACTIVE"
|
|
logger -t vfio-cpuid "$DOMAIN start: brand='$BRAND' cpuid_passthrough=N, watcher armed"
|
|
|
|
# transient systemd unit, not a bare background job: libvirt reaps anything left
|
|
# in the hook's own process tree; systemd-run escapes it
|
|
[ -x "$WATCH" ] && systemd-run --collect --quiet \
|
|
--unit="vfio-cpuid-watch-${DOMAIN}" "$WATCH" "$DOMAIN" "$BRAND"
|
|
;;
|
|
|
|
release|stopped)
|
|
held=$( [ -e "$ACTIVE" ] && cut -d: -f1 "$ACTIVE" )
|
|
[ "$held" = "$DOMAIN" ] || exit 0
|
|
systemctl stop "vfio-cpuid-watch-${DOMAIN}.service" 2>/dev/null
|
|
echo N > "$PARAM/cpuid_passthrough"
|
|
rm -f "$ACTIVE"
|
|
logger -t vfio-cpuid "$DOMAIN $OPERATION: cpuid_passthrough=N"
|
|
;;
|
|
esac
|
|
|
|
exit 0
|