Go to file
2024-11-29 16:38:35 +01:00
libvirt_guest_example_with_annotations.xml initial commit, full description of my setup (hopefully) 2024-11-29 16:33:17 +01:00
README.md libvirt hook: CPUs isolieren 2024-11-29 16:38:35 +01:00

PFS libvirt/qemu GPU Passthrough für Windows Gaming VM

Linux kernel command line args

  • vfio-pci.ids=10de:2684,10de:22ba: Guest GPU PCI IDs "rausnehmen", dass der kernel damit nichts macht (GPU und HDMI Audio im Beispiel). Find ich bei mir via lspci -nn | grep NVIDIA.
  • obsolet: isolcpus=0-7,16-23: CPUs für Guest vom System isolieren. Kann man mittlerweile zur Laufzeit via cgroups machen. Siehe CPUs isolieren bei VM Start
  • even obsoleter: nohz_full=0-7,16-23: Siehe isolcpus. Bei Interesse docs auf kernel.org lesen. Aber total obsolet.
  • video=efifb:off: Ich hatte Probleme, weil selbst mit vfio der efifb noch irgendwie die NVIDIA "gegrabt" hat, deshalb efifb:off.
  • module_blacklist=nvidia: Ich hab bei mir den NVIDIA Treiber UND den AMD Treiber auf dem Host installiert (AMD = Linux, NVIDIA = Win Guest), weil ich LLMs mit ollama lokal laufen lassen will und dafür unter Linux auch die NVIDIA Treiber brauche. Wenn ich aber hybrid boote, also VM nutzen will, dann blackliste ich noch zusätzlich das NVIDIA Modul, macht insgesamt dann weniger Probleme.
  • systemd.unit=pfs-winvm-hybrid.target: Custom boot target, siehe "rEFInd Config" und "Custom systemd targets".

rEfInd Config

$ cat /boot/refind_linux.conf
"Arch Linux vfio NVIDIA 4090 hybrid WinVM off"     "[...] amd_iommu=on iommu=pt vfio-pci.ids=10de:2684,10de:22ba video=efifb:off module_blacklist=nvidia"
"Arch Linux vfio NVIDIA 4090 hybrid WinVM on"      "[...] amd_iommu=on iommu=pt vfio-pci.ids=10de:2684,10de:22ba video=efifb:off module_blacklist=nvidia systemd.unit=pfs-winvm-hybrid.target"
"Arch Linux vfio NVIDIA 4090 WinVM only"           "[...] amd_iommu=on iommu=pt vfio-pci.ids=10de:2684,10de:22ba video=efifb:off module_blacklist=nvidia systemd.unit=pfs-winvm.target"
"Arch Linux vfio NVIDIA 4090 Linux only NO NVIDIA" "[...] amd_iommu=on iommu=pt                                                  module_blacklist=nvidia"
"Arch Linux vfio NVIDIA 4090 Linux only"           "[...] amd_iommu=on iommu=pt"

Custom systemd targets

Hybrid

$ systemctl cat pfs-winvm-hybrid.target
# /etc/systemd/system/pfs-winvm-hybrid.target
[Unit]
Description=PFS WinVM hybrid Target
Requires=graphical.target
Conflicts=rescue.service rescue.target
After=graphical.target rescue.service rescue.target
AllowIsolate=yes

$ systemctl cat pfs-winvm-hybrid.service
# /etc/systemd/system/pfs-winvm-hybrid.service
[Unit]
Description=PFS WinVM hybrid
After=libvirtd.service

[Service]
ExecStart=virsh start win11new
Restart=no
Type=oneshot

[Install]
WantedBy=pfs-winvm-hybrid.target

Nur VM

$ systemctl cat pfs-winvm.target
# /etc/systemd/system/pfs-winvm.target
[Unit]
Description=PFS WinVM Target
Requires=multi-user.target
Conflicts=rescue.service rescue.target
After=multi-user.target rescue.service rescue.target
AllowIsolate=yes

$ systemctl cat pfs-winvm.service
# /etc/systemd/system/pfs-winvm.service
[Unit]
Description=PFS WinVM
After=pfs-winvm.target

[Service]
ExecStart=virsh start win11new
Restart=no
Type=oneshot

[Install]
WantedBy=pfs-winvm.target

IOMMU (wichtig)

Damit man einzelne PCI devices zum Guest durchreichen kann, muss im BIOS IOMMU aktiv sein (SVM sowieso für Virtualisierung).

Es können immer nur ganze IOMMU Gruppen durchgereicht werden, nicht einzelne Geräte innerhalb einer Gruppe.

IOMMU Group 13:
        01:00.0 VGA compatible controller [0300]: NVIDIA Corporation AD102 [GeForce RTX 4090] [10de:2684] (rev a1)
        01:00.1 Audio device [0403]: NVIDIA Corporation AD102 High Definition Audio Controller [10de:22ba] (rev a1)
IOMMU Group 14:
        02:00.0 Non-Volatile memory controller [0108]: Seagate Technology PLC FireCuda 530 SSD [1bb1:5018] (rev 01)
IOMMU Group 15:
        03:00.0 PCI bridge [0604]: Advanced Micro Devices, Inc. [AMD] Device [1022:43f4] (rev 01)

Hier ist Group 13 die Gruppe in der mein NVIDIA VGA Controller und das NVIDIA Audio Device ist. Deshalb auch beide auf jeden Fall durchreichen!

USB Controller reiche ich auch durch, aber da musste ich schauen, welche überhaupt einzeln durchreichbar waren.

Skript um die IOMMU Gruppen schnell zu sehen:

 $ cat iommu_groups.sh
#!/bin/bash

shopt -s nullglob

for iommu_group in /sys/kernel/iommu_groups/*; do
    echo "IOMMU Group ${iommu_group##*/}:"
    for d in "${iommu_group}/devices"/*; do
        echo -e "\t$(lspci -nns "${d##*/}")"
    done
done

CPU Topologie und Cache

lstopo: Tool um schnell die Topologie der CPU zu sehen. Also um herauszufinden, welche Kerne auf welchem CCD liegen.

Bei meiner CPU haben die Cores 0-7 die virtuellen Cores 0-7 und 16-23 und die Cores 8-15 die virtuellen Cores 8-15 und 24-31. Außerdem ist mit lstopo erkennbar, dass die Cores 0-7 mit dem ersten L3 Cache verbunden sind, und die Cores 8-15 mit dem Zweiten. Es ist ratsam für die Performance, dass sich die VM Cores und die Host Cores keinen Cache teilen, also in meinem Fall alle virtuellen Cores der Cores 0-7 durchzureichen, sprich 0-7,16-23. Siehe XML File.

CPUs isolieren bei VM Start

$ cat /etc/libvirt/hooks/qemu
#!/usr/bin/bash

__systemctl_set_cpu_isolation_properties__() {
    local prop="$1"

    systemctl set-property --runtime -- "pfs-rust-compile.slice" "$prop"
    systemctl set-property --runtime -- "user.slice"             "$prop"
    systemctl set-property --runtime -- "system.slice"           "$prop"
    systemctl set-property --runtime -- "init.scope"             "$prop"
}

vmisolatecpus() {
    __systemctl_set_cpu_isolation_properties__ "AllowedCPUs=8-15,24-31"
}

vmfreecpus() {
    __systemctl_set_cpu_isolation_properties__ "AllowedCPUs=0-31"
}


VM_NAME="$1"
VM_ACTION="${2} ${3}"
LOG_FILE="/home/pfs/shm/libvirt_hook.log"

if [[ "$VM_NAME" == "win11new" ]]; then

    if [[ "$VM_ACTION" == "started begin" ]]; then
        echo "[$(date "+%Y-%m-%d %H:%M:%S")] hook triggered: $* -> isolating CPUs 8-15,24-31" >> "$LOG_FILE"
        vmisolatecpus

    elif [[ "$VM_ACTION" == "release end" ]]; then
        echo "[$(date "+%Y-%m-%d %H:%M:%S")] hook triggered: $* -> freeing all CPUs" >> "$LOG_FILE"
        vmfreecpus

    else
        echo "[$(date "+%Y-%m-%d %H:%M:%S")] hook ignored: $*" >> "$LOG_FILE"
    fi

fi