Created
May 19, 2024 21:43
-
-
Save AskAlice/a951fda0d96879e5f396d3b11583e8ab to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# Stop on error | |
# set -e | |
set -x | |
# Execution variables | |
OBJECT="${1}" # emu | |
OPERATION="${2}" # prepare / release | |
ACTION="${3}" # end / begin | |
XML="/etc/libvirt/qemu/${OBJECT}.xml" | |
# Log debug information to file | |
function debug { | |
echo "[HOOK] ${1}" >> "/var/log/libvirt/qemu/${OBJECT}.log" | |
} | |
# Check if file exists | |
# We check this to avoid qemu hooks on other objects that are not VMs | |
if [ ! -f "${XML}" ]; then | |
debug "VM object not detected, skipping" | |
exit 0 | |
fi | |
VMF="/tmp/vfio-${OBJECT}" | |
OPTIONS=$(grep -oPm1 "(?<=<description>)[^<]+" "${XML}") | |
################################# | |
### ==== Display Manager ==== ### | |
################################# | |
# Detect and stop the running display manager on host, when available | |
function stop_display_manager { | |
managers=("sddm" "gdm" "lightdm" "lxdm" "xdm" "mdm" "display-manager") | |
for manager in "${managers[@]}"; do | |
if [ -x "$(command -v systemctl)" ]; then | |
if systemctl is-active --quiet "${manager}.service"; then | |
debug "Stopping ${manager} display service" | |
echo "${manager}" >> "${VMF}-display-manager" | |
systemctl stop "${manager}.service" | |
fi | |
while systemctl is-active --quiet "${manager}.service"; do | |
debug "Waiting for ${manager} display service to stop" | |
sleep "5" | |
done | |
fi | |
done | |
} | |
# Restore the previously detected host display manager | |
function restore_display_manager { | |
if [ -f "${VMF}-display-manager" ]; then | |
while read -r manager; do | |
if [ -x "$(command -v systemctl)" ]; then | |
debug "Starting ${manager} display service again" | |
systemctl start "${manager}.service" | |
fi | |
done < "${VMF}-display-manager" | |
fi | |
} | |
################################# | |
### ======= VTConsole ======= ### | |
################################# | |
# Unbind consoles on host | |
function unbind_consoles { | |
for (( i = 0; i < 16; i++)); do | |
if [ -x "/sys/class/vtconsole/vtcon${i}" ]; then | |
value=$(cat "/sys/class/vtconsole/vtcon${i}/name") | |
if [ "$(echo "${value}" | grep -c "frame buffer")" = "1" ]; then | |
debug "Unbinding console number ${i}" | |
echo "0" > "/sys/class/vtconsole/vtcon${i}/bind" | |
echo "${i}" >> "${VMF}-bound-consoles" | |
fi | |
fi | |
done | |
} | |
# Rebind previously detected bound consoles on host | |
function rebind_consoles { | |
if [ -f "${VMF}-bound-consoles" ]; then | |
while read -r number; do | |
if [ -x "/sys/class/vtconsole/vtcon${number}" ]; then | |
value=$(cat "/sys/class/vtconsole/vtcon${number}/name") | |
if [ "$(echo "${value}" | grep -c "frame buffer")" = "1" ]; then | |
debug "Rebinding console number ${number}" | |
echo "1" > "/sys/class/vtconsole/vtcon${number}/bind" | |
fi | |
fi | |
done < "${VMF}-bound-consoles" | |
fi | |
} | |
################################# | |
### ====== Framebuffer ====== ### | |
################################# | |
# Unbind framebuffer on host | |
function unbind_framebuffer { | |
if [ -f /proc/fb ] && [ -n "$(cat /proc/fb)" ]; then | |
debug "Unbinding framebuffer" | |
echo "1" > "${VMF}-bound-framebuffer" | |
echo "efi-framebuffer.0" > /sys/bus/platform/drivers/efi-framebuffer/unbind | |
fi | |
} | |
# Rebind previouly detected framebuffer on host | |
function rebind_framebuffer { | |
if [ -f "${VMF}-bound-framebuffer" ]; then | |
debug "Rebinding framebuffer" | |
echo "efi-framebuffer.0" > /sys/bus/platform/drivers/efi-framebuffer/bind | |
fi | |
} | |
################################## | |
## ======= GPU Devices ======= ### | |
################################## | |
# Release GPU to be used by VFIO | |
function release_gpu { | |
# Special case for main GPU | |
[ "${1}" == "main" ] && stop_display_manager | |
[ "${1}" == "main" ] && unbind_consoles | |
[ "${1}" == "main" ] && unbind_framebuffer | |
# Extract data and set config info | |
vga="/sys/bus/pci/devices/0000:${2}" | |
audio="/sys/bus/pci/devices/0000:${3}" | |
driver=$(basename $(readlink "${vga}/driver")) | |
mixer=$(basename $(readlink "${audio}/driver")) | |
# Fix AMD GPU reset method | |
if [ "${driver}" == "amdgpu" ] && [ "${4}" == "reset" ]; then | |
debug "Applying device_specific reset method for AMD GPUs" | |
echo "device_specific" > "${vga}/reset_method" | |
echo "device_specific" > "${audio}/reset_method" | |
fi | |
# Unbind vga and audio from host | |
if [ -n "${driver}" ] && [ "${driver}" != "pcieport" ]; then | |
debug "Unbinding VGA ${2} from host driver: ${driver}" | |
echo "0000:${2}" > "/sys/bus/pci/drivers/${driver}/unbind" | |
echo "${driver}" > "${VMF}-${1}-gpu-driver" | |
fi | |
if [ -n "${mixer}" ] && [ "${mixer}" != "pcieport" ]; then | |
debug "Unbinding audio ${3} from host driver: ${mixer}" | |
echo "0000:${3}" > "/sys/bus/pci/drivers/${mixer}/unbind" | |
echo "${mixer}" > "${VMF}-${1}-gpu-mixer" | |
fi | |
# Bind to VFIO | |
debug "Binding GPU ${1} to VFIO" | |
virsh nodedev-detach "pci_0000_${2//[:.]/_}" | |
virsh nodedev-detach "pci_0000_${3//[:.]/_}" | |
} | |
# Restore GPU to the system | |
function restore_gpu { | |
# Unbind vga and audio from VFIO | |
debug "Reataching GPU ${1} to host" | |
(setsid virsh nodedev-reattach "pci_0000_${2//[:.]/_}" &) | |
(setsid virsh nodedev-reattach "pci_0000_${3//[:.]/_}" &) | |
# Check for drivers and bind again if necessary | |
vga="/sys/bus/pci/devices/0000:${2}" | |
audio="/sys/bus/pci/devices/0000:${3}" | |
if [ -f "${VMF}-${1}-gpu-driver" ] && [ ! -d "${vga}/driver" ]; then | |
driver=$(cat ${VMF}-${1}-gpu-driver) | |
debug "Binding VGA driver to ${2}: ${driver}" | |
echo "0000:${2}" > "/sys/bus/pci/drivers/${driver}/bind" | |
fi | |
if [ -f "${VMF}-${1}-gpu-mixer" ] && [ ! -d "${audio}/driver" ]; then | |
mixer=$(cat ${VMF}-${1}-gpu-mixer) | |
debug "Binding audio driver to ${3}: ${mixer}" | |
echo "0000:${3}" > "/sys/bus/pci/drivers/${mixer}/bind" | |
fi | |
# Special case for main GPU | |
[ "${1}" == "main" ] && restore_display_manager | |
[ "${1}" == "main" ] && rebind_consoles | |
[ "${1}" == "main" ] && rebind_framebuffer | |
} | |
################################## | |
### == CPU Scaling Governor == ### | |
################################## | |
# Set CPU scaling governor to user defined mode | |
function set_cpu_scaling_governor { | |
for index in `find /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor | sort -V`; do | |
debug "Scaling CPU governor to ${1} mode in core number ${index//[^0-9]/}" | |
cat "${index}" >> "${VMF}-scaling-governor" | |
echo "${1}" > "${index}" | |
done | |
} | |
# Restore previously detected CPU scaling governor | |
function restore_cpu_scaling_governor { | |
if [ -f "${VMF}-scaling-governor" ]; then | |
index=0 | |
while read -r scaling; do | |
debug "Restoring scaling CPU governor mode to ${scaling} in core number ${index}" | |
echo "${scaling}" > "/sys/devices/system/cpu/cpu${index}/cpufreq/scaling_governor" | |
((index=index+1)) | |
done < "${VMF}-scaling-governor" | |
fi | |
} | |
################################## | |
### === CPU Cores / Pinning === ## | |
################################## | |
# Preserve CPU cores by pinning the usage of some specific cores | |
function preserve_cpu_cores { | |
if [ -x "$(command -v systemctl)" ]; then | |
debug "Pinning CPU to use only the cores ${1}" | |
echo "1" > "${VMF}-cpu-pinning" | |
systemctl set-property --runtime -- user.slice AllowedCPUs="${1}" | |
systemctl set-property --runtime -- system.slice AllowedCPUs="${1}" | |
systemctl set-property --runtime -- init.scope AllowedCPUs="${1}" | |
fi | |
echo 0 > /sys/bus/workqueue/devices/writeback/numa | |
echo 3 > /proc/sys/vm/drop_caches | |
} | |
# Restore previously detected CPU pinning on host | |
function restore_cpu_cores { | |
if [ -s "${VMF}-cpu-pinning" ]; then | |
debug "Restoring CPU pinning to use all cores" | |
systemctl set-property --runtime -- user.slice AllowedCPUs="" | |
systemctl set-property --runtime -- system.slice AllowedCPUs="" | |
systemctl set-property --runtime -- init.scope AllowedCPUs="" | |
fi | |
echo FFFFFFFF > /sys/bus/workqueue/devices/writeback/cpumask | |
echo 1 > /sys/bus/workqueue/devices/writeback/numa | |
} | |
################################## | |
### ======= USB Devices ======= ## | |
################################## | |
# Watch and attach USB devices to VM | |
function attach_usb_device { | |
debug "Added USB device to live passthrough: ${1}" | |
echo "${1}" >> "${VMF}-usb-watch" | |
} | |
################################## | |
### ======= QEMU Hooks ======= ### | |
################################## | |
# On prepare begin qemu hook | |
function on_prepare_begin { | |
for i in "$@"; do | |
case $i in | |
# Enable live GPU passthrough | |
--gpu-passthrough=*) | |
info=($(echo "${i#*=}" | tr ',' ' ')) | |
release_gpu "${info[@]}" | |
shift | |
;; | |
# Set CPU scaling governor mode | |
--cpu-scaling-mode=*) | |
mode="${i#*=}" | |
set_cpu_scaling_governor "$mode" | |
shift | |
;; | |
# Set CPU pinning by allowing host use only specific cores | |
--preserve-cores=*) | |
cores="${i#*=}" | |
./pinning.sh $OBJECT $OPERATION $ACTION "$cores" | |
# preserve_cpu_cores "$cores" | |
shift | |
;; | |
# Enable live USB passthrough | |
--usb-passthrough=*) | |
device="${i#*=}" | |
attach_usb_device "$device" | |
shift | |
;; | |
# Unknown or ignored | |
*) | |
;; | |
esac | |
done | |
eval /etc/libvirt/hooks/qemu.d/emu/prepare/begin/alloc_hugepages.sh "$@" | |
} | |
# On release end qemu hook | |
function on_release_end { | |
for i in "$@"; do | |
case $i in | |
# Disable live GPU passthrough | |
--gpu-passthrough=*) | |
info=($(echo "${i#*=}" | tr ',' ' ')) | |
restore_gpu "${info[@]}" | |
shift | |
;; | |
# Restore CPU scaling governor | |
--cpu-scaling-mode=*) | |
restore_cpu_scaling_governor | |
shift | |
;; | |
# Restore CPU core pinning on system | |
--preserve-cores=*) | |
cores="${i#*=}" | |
./pinning.sh $OBJECT $OPERATION $ACTION "$cores" | |
#restore_cpu_cores "$cores" | |
shift | |
;; | |
# Unknown or ignored | |
*) | |
;; | |
esac | |
eval /etc/libvirt/hooks/qemu.d/emu/release/end/dealloc_hugepages.sh "$@" | |
done | |
# Config clean up | |
debug "Cleaning config cache" | |
rm -f "${VMF}"-* | |
} | |
################################## | |
### ======== Execution ======= ### | |
################################## | |
debug "Hook called: ${OPERATION} ${ACTION}" | |
if [ -n "$(command -v "on_${OPERATION}_${ACTION}")" ]; then | |
debug "Hook options: ${OPTIONS}" | |
eval "on_${OPERATION}_${ACTION}" "${OPTIONS}" | |
fi | |
set +x |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment