initial commit, full description of my setup (hopefully)

This commit is contained in:
Paul Schulze 2024-11-29 16:33:17 +01:00
commit 83fc46f000
2 changed files with 334 additions and 0 deletions

124
README.md Normal file
View File

@ -0,0 +1,124 @@
# 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.
- 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.

View File

@ -0,0 +1,210 @@
<domain type='kvm'>
<name>win11new</name>
<uuid>1fdac169-a106-42f2-b43e-616601c53af1</uuid> <!-- schon geändert, damit du nicht meine hast ;) -->
<metadata>
<libosinfo:libosinfo xmlns:libosinfo="http://libosinfo.org/xmlns/libvirt/domain/1.0">
<libosinfo:os id="http://microsoft.com/win/11"/>
</libosinfo:libosinfo>
</metadata>
<memory unit='KiB'>50331648</memory> <!-- 48 GB -->
<currentMemory unit='KiB'>50331648</currentMemory> <!-- 48 GB -->
<memoryBacking>
<source type='memfd'/>
<access mode='shared'/>
</memoryBacking>
<vcpu placement='static'>16</vcpu> <!-- Anzahl der durchgereichten (virtuellen) CPU cores -->
<iothreads>1</iothreads> <!-- Ich glaube das hier bezieht sich nur unten auf die iothreadpin Zeile, also einfach alle CPUs, die NICHT zu Windows durchgereicht werden, können für qemu IO benutzt werden -->
<cputune>
<vcpupin vcpu='0' cpuset='0'/> <!-- Hier pinne ich jetzt die 16 Cores von EINEM CCD meines Ryzen auf die 16 vCores -->
<vcpupin vcpu='1' cpuset='16'/>
<vcpupin vcpu='2' cpuset='1'/>
<vcpupin vcpu='3' cpuset='17'/>
<vcpupin vcpu='4' cpuset='2'/>
<vcpupin vcpu='5' cpuset='18'/>
<vcpupin vcpu='6' cpuset='3'/>
<vcpupin vcpu='7' cpuset='19'/>
<vcpupin vcpu='8' cpuset='4'/>
<vcpupin vcpu='9' cpuset='20'/>
<vcpupin vcpu='10' cpuset='5'/>
<vcpupin vcpu='11' cpuset='21'/>
<vcpupin vcpu='12' cpuset='6'/>
<vcpupin vcpu='13' cpuset='22'/>
<vcpupin vcpu='14' cpuset='7'/>
<vcpupin vcpu='15' cpuset='23'/>
<emulatorpin cpuset='8,24'/> <!-- musst nochmal nachlesen was emulatorpin genau war, aber das war glaub ich einfach die CPUs die qemu für Eigenbedarf benutzt, deshalb auch NICHT die zu Windows durchgereichten Cores -->
<iothreadpin iothread='1' cpuset='8-15,24-31'/> <!-- genau die nicht durchgereichten Cores -->
</cputune>
<os>
<type arch='x86_64' machine='pc-q35-6.2'>hvm</type> <!-- Hier könnte man noch mal schauen, diese pc-q35 sieht man auch im Guest -->
<loader readonly='yes' secure='yes' type='pflash'>/usr/share/edk2-ovmf/x64/OVMF_CODE.secboot.4m.fd</loader> <!-- secure boot OVMF UEFI image (Arch Linux package: edk2-ovmf), man beachte, dass es jetzt die 4m Images nur noch gibt :) -->
<nvram template='/usr/share/edk2-ovmf/x64/OVMF_VARS.4m.fd'>/var/lib/libvirt/qemu/nvram/win11new_VARS.4m.fd</nvram> <!-- Platz für NVRAM Variablen -->
<bootmenu enable='no'/>
</os>
<features>
<acpi/>
<apic/>
<hyperv mode='custom'>
<relaxed state='on'/>
<vapic state='on'/>
<spinlocks state='on' retries='8191'/>
<vendor_id state='on' value='somevendorid'/> <!-- hier hab ich bei mir irgendwas reingeschrieben -->
</hyperv>
<kvm>
<hidden state='on'/> <!-- ich glaub das war wichtig, damit das Guest system das nicht so gut als VM identifiziert, wobei... man sieht ja eh das pc-q35... -->
</kvm>
<vmport state='off'/>
<smm state='on'/>
<ioapic driver='kvm'/>
</features>
<cpu mode='host-passthrough' check='none' migratable='on'> <!-- hier wichtig, host-passthrough, dass alle CPU Features available sind im Guest -->
<topology sockets='1' dies='1' clusters='1' cores='8' threads='2'/> <!-- 8 cores mit jeweils 2 threads = 16 vcores -->
<feature policy='require' name='invtsc'/> <!-- Hier meine Originalnotiz, die ich mir dazu gemacht hatte: "Cyberpunk slow in VM. Passthrough the native invariant TSC timer." -->
<feature policy='require' name='topoext'/> <!-- see https://wiki.archlinux.org/title/PCI_passthrough_via_OVMF#Improving_performance_on_AMD_CPUs -->
</cpu>
<clock offset='utc'> <!-- die ganzen Dinge hier drin waren auch alle wichtig soweit ich mich erinnern kann -->
<timer name='rtc' tickpolicy='catchup'/>
<timer name='pit' tickpolicy='delay'/>
<timer name='hpet' present='no'/>
<timer name='hypervclock' present='yes'/>
<timer name='tsc' present='yes' mode='native'/>
</clock>
<on_poweroff>destroy</on_poweroff>
<on_reboot>restart</on_reboot>
<on_crash>destroy</on_crash>
<pm>
<suspend-to-mem enabled='no'/> <!-- ich glaub das würde nicht gehen, wenn man es auf yes setzt, wegen des Passthrough der Graka -->
<suspend-to-disk enabled='no'/>
</pm>
<devices>
<emulator>/usr/bin/qemu-system-x86_64</emulator>
<disk type='file' device='cdrom'>
<driver name='qemu' type='raw'/>
<source file='/var/lib/libvirt/images/Win11_22H2_EnglishInternational_x64v1 15.10.2022.iso'/>
<target dev='sdc' bus='sata'/>
<readonly/>
<address type='drive' controller='0' bus='0' target='0' unit='2'/>
</disk>
<disk type='file' device='cdrom'>
<driver name='qemu' type='raw'/>
<source file='/var/lib/libvirt/images/virtio-win-0.1.240.iso'/>
<target dev='sdd' bus='sata'/>
<readonly/>
<address type='drive' controller='0' bus='0' target='0' unit='3'/>
</disk>
<disk type='file' device='disk'>
<driver name='qemu' type='raw' io='threads' discard='unmap' iothread='1'/> <!-- alle meine virtuellen "SSD" Images laufen auf iothread=1 was eben dieser oben definierte CPU Pool ist -->
<source file='/var/lib/libvirt/images/win11.raw'/> <!-- und sind fully encrypted, weil die btrfs Linux partition auf der das Image liegt mit dm-crypt encrypted ist -->
<target dev='vdb' bus='virtio'/> <!-- alternativ kann man auch einzelne PCIE M.2 NVME Controller per passthrough an den Guest durchreichen, aber ich wollte alles mit images haben -->
<boot order='1'/> <!-- deshalb nutze ich hier auch virtio für performance, auch wieder den Windows Treiber manuell installiert aus dem virtio-win ISO -->
<address type='pci' domain='0x0000' bus='0x08' slot='0x00' function='0x0'/> <!-- da musste ich die virtio Treiber bei der Windows Installation laden, einfach virtio-win ISO laden und als SATA CDROM mit in die VM hängen -->
</disk>
<disk type='file' device='disk'>
<driver name='qemu' type='raw' cache='none' io='threads' discard='unmap' iothread='1'/>
<source file='/pantheon/vm/win_games1.img'/> <!-- /pantheon ist bei mir ein btrfs RAID0 (encrypted via dm-crypt untendrunter) und da liegt ein 3TB image für die Windows Games -->
<target dev='vdc' bus='virtio'/>
<address type='pci' domain='0x0000' bus='0x07' slot='0x00' function='0x0'/>
</disk>
<controller type='usb' index='0' model='qemu-xhci' ports='15'> <!-- Standard Kram, hab ich nie angefasst -->
<address type='pci' domain='0x0000' bus='0x02' slot='0x00' function='0x0'/>
</controller>
<controller type='sata' index='0'>
<address type='pci' domain='0x0000' bus='0x00' slot='0x1f' function='0x2'/>
</controller>
<controller type='pci' index='0' model='pcie-root'/>
<controller type='pci' index='1' model='pcie-root-port'>
<model name='pcie-root-port'/>
<target chassis='1' port='0x10'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x0' multifunction='on'/>
</controller>
<controller type='pci' index='2' model='pcie-root-port'>
<model name='pcie-root-port'/>
<target chassis='2' port='0x11'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x1'/>
</controller>
<controller type='pci' index='3' model='pcie-root-port'>
<model name='pcie-root-port'/>
<target chassis='3' port='0x12'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x2'/>
</controller>
<controller type='pci' index='4' model='pcie-root-port'>
<model name='pcie-root-port'/>
<target chassis='4' port='0x13'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x3'/>
</controller>
<controller type='pci' index='5' model='pcie-root-port'>
<model name='pcie-root-port'/>
<target chassis='5' port='0x14'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x4'/>
</controller>
<controller type='pci' index='6' model='pcie-root-port'>
<model name='pcie-root-port'/>
<target chassis='6' port='0x8'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x0' multifunction='on'/>
</controller>
<controller type='pci' index='7' model='pcie-root-port'>
<model name='pcie-root-port'/>
<target chassis='7' port='0x9'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x1'/>
</controller>
<controller type='pci' index='8' model='pcie-root-port'>
<model name='pcie-root-port'/>
<target chassis='8' port='0xa'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x2'/>
</controller>
<controller type='pci' index='9' model='pcie-root-port'>
<model name='pcie-root-port'/>
<target chassis='9' port='0xb'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x3'/>
</controller>
<controller type='pci' index='10' model='pcie-root-port'>
<model name='pcie-root-port'/>
<target chassis='10' port='0xc'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x4'/>
</controller>
<controller type='pci' index='11' model='pcie-root-port'>
<model name='pcie-root-port'/>
<target chassis='11' port='0xd'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x5'/>
</controller>
<controller type='pci' index='12' model='pcie-root-port'>
<model name='pcie-root-port'/>
<target chassis='12' port='0xe'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x6'/>
</controller>
<controller type='pci' index='13' model='pcie-to-pci-bridge'>
<model name='pcie-pci-bridge'/>
<address type='pci' domain='0x0000' bus='0x0a' slot='0x00' function='0x0'/>
</controller>
<interface type='bridge'>
<mac address='1d:33:99:c1:23:93'/> <!-- changed for privacy ;) -->
<source bridge='pfsbridge'/> <!-- ich hab in meinem Linux ne Bridge, an der mein eigentliches netdev hängt, hier hängt auch die VM dran, so dass sie wie ein normaler LAN client im Netzwerk ist -->
<model type='virtio'/> <!-- virtio fürs Netzwerk, dafür muss man den Windows Treiber aus der virtio-win ISO manuell installieren. -->
<address type='pci' domain='0x0000' bus='0x01' slot='0x00' function='0x0'/> <!-- Ich hatte mir einfach die virtio-win ISO runtergeladen und als weiteres SATA CDROM Laufwerk in die VM gepackt. -->
</interface>
<input type='mouse' bus='ps2'/> <!-- nicht wundern, einiges ist einfach standardmäßig da, ich hatte das ursprünglich über die libvirt gui (virt-manager) erstellt -->
<input type='keyboard' bus='ps2'/> <!-- keine Ahnung ob man sowas auch rauslöschen könnte, aber es hat auch nie gestört :D -->
<tpm model='tpm-tis'>
<backend type='emulator' version='2.0'/> <!-- TPM 2.0 emulation, Arch Linux package: swtpm -->
</tpm>
<audio id='1' type='none'/>
<hostdev mode='subsystem' type='pci' managed='yes'> <!-- hier kommen jetzt die durchgereichten PCI Devices. Das habe ich aber auch immer nur via virt-manager geändert -->
<source>
<address domain='0x0000' bus='0x01' slot='0x00' function='0x0'/> <!-- NVIDIA GPU -->
</source>
<address type='pci' domain='0x0000' bus='0x03' slot='0x00' function='0x0'/>
</hostdev>
<hostdev mode='subsystem' type='pci' managed='yes'>
<source>
<address domain='0x0000' bus='0x01' slot='0x00' function='0x1'/> <!-- NVIDIA HDMI Audio -->
</source>
<address type='pci' domain='0x0000' bus='0x05' slot='0x00' function='0x0'/>
</hostdev>
<hostdev mode='subsystem' type='pci' managed='yes'>
<source>
<address domain='0x0000' bus='0x16' slot='0x00' function='0x3'/> <!-- USB Controller -->
</source>
<address type='pci' domain='0x0000' bus='0x06' slot='0x00' function='0x0'/>
</hostdev>
<watchdog model='itco' action='reset'/>
</devices>
</domain>