169 lines
6.3 KiB
Markdown
169 lines
6.3 KiB
Markdown
# PFS libvirt/qemu GPU Passthrough für Windows Gaming VM
|
|
|
|
## Links
|
|
- Arch Linux Wiki: https://wiki.archlinux.org/title/PCI_passthrough_via_OVMF
|
|
- VFIO Subreddit: https://www.reddit.com/r/VFIO/
|
|
- Weitere Links, die ich in Kommentaren der XML gepackt hab.
|
|
|
|
## 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
|
|
```ini
|
|
$ 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
|
|
```ini
|
|
$ 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.
|
|
|
|
```ini
|
|
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:
|
|
```bash
|
|
$ 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
|
|
```bash
|
|
$ 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
|
|
```
|