Windows 8.1 als Linux KVM Guest mit VGA Passthrough (DirectX11) unter Ubuntu

Wer unter Linux spielen will, hat meist das Problem dass Windows Spiele nur bedingt mit Wine lauffähig sind und selten gibt es für neue Spieletitel eine Linux-Version. Aus diesem Grund hier ein Howto, wie Ihr mit halbwegs aktueller Hardware und KVM Virtualisierung eine DirectX11 taugliche Grafikkarte an den KVM Gast weitergeben könnt. Wichtig ist dass das Board eine IOMMU hat, sonst klappt das Ganze nicht.

Ich habe dieses Setup mit Ubuntu 12.04.5 LTS ausprobiert, es wird sicherlich auch mit neueren Versionen funktionieren. Also Ubuntu 12.04 installiert, und anschliessend einen aktuelleren Kernel kompilieren und installieren (3.16.1 funktioniert stabil damit).

Mein Testsystem besteht aus einem Gigabyte GA-990FXA-UD5 Motherboard, einer AMD Phenom(tm) II X4 965 CPU sowie 4×4 GB ECC DDR3 RAM (unbuffered). Darauf stecken eine passiv gekühlte GeForce 210 für Ubuntu, und eine ebenfalls passiv gekühlte Radeon HD 7750. Beide Grafikkarten sind an ein Samsung 55″ LCD via HDMI angeschlossen. Man kann also auch umschalten zwischen Ubuntu und Windows, da ja beides parallel läuft.

Funktionsfähig getestet habe ich mit QEMU emulator version 1.7.93 (commit f2c85a2f36f57f155cda7bc9f7c42b44f1a2439e – master), mit neurem QEMU habe ich es noch nicht stabil zum laufen bekommen.

Um die Radeon an den Gast durchzureichen empfiehlt es sich das radeon kernel Modul zu blocken, in dem man in /etc/modprobe.d/blacklist.conf den Eintrag “baclklist radeon” am Ende der Datei hinzufügt und rebootet.

Anschliessend mit lspci nachsehen welchen PCI Slot die Geräte erhalten haben, bei mir:
03:00.0 VGA compatible controller: Advanced Micro Devices, Inc. [AMD/ATI] Cape Verde PRO [Radeon HD 7750]
03:00.1 Audio device: Advanced Micro Devices, Inc. [AMD/ATI] Cape Verde/Pitcairn HDMI Audio [Radeon HD 7700/7800 Series]

Wir merken uns die Slots und sehen mit lspci -n genauer nach, denn wir benötigen zusätzlich PCI Vendor und Device ID:
03:00.0 0300: 1002:683f
03:00.1 0403: 1002:aab0

1002 ist die Vendor ID, 683f die Device ID für den VGA controller, aab0 HDMI audio.

Nun packen wir folgende Zeilen in /etc/rc.local:
echo 1 > /sys/module/kvm/parameters/allow_unsafe_assigned_interrupts
echo 1002 683f > /sys/bus/pci/drivers/vfio-pci/new_id
echo 1002 aab0 > /sys/bus/pci/drivers/vfio-pci/new_id

und rebooten oder führen dies auf der Console aus. Anschliesend definieren wir einen Gast in XML für libvirt, die wichtigen Parameter finden sich am Ende unter qemu:commandline. Man braucht eine Maschine bzw Vitrtuelles Board/Chipset vom Typ q35. Außerdem muss man die zwei Devices unterhalb eines PCIe root Ports einbinden (ioh3420).
<domain type='kvm' xmlns:qemu='http://libvirt.org/schemas/domain/qemu/1.0'>
<name>windows8.1</name>
<uuid>1e84aab9-affe-affe-f000-af312493ef00</uuid>
<memory unit='KiB'>8388608</memory>
<currentMemory unit='KiB'>8388608</currentMemory>
<vcpu placement='static' cpuset='0-3'>4</vcpu>
<os>
<type arch='x86_64' machine='pc-q35-2.0'>hvm</type>
<boot dev='cdrom'/>
</os>
<features>
<acpi/>
<apic/>
<pae/>
</features>
<cpu mode='host-model'>
<model fallback='allow'/>
</cpu>
<clock offset='localtime'/>
<on_poweroff>destroy</on_poweroff>
<on_reboot>restart</on_reboot>
<on_crash>restart</on_crash>
<devices>
<emulator>/usr/bin/qemu-system-x86_64</emulator>
<disk type='file' device='disk'>
<driver name='qemu' type='raw' cache='writeback'/>
<source file='/data/windows-8.1.img'/>
<target dev='vda' bus='virtio'/>
<address type='pci' domain='0x0000' bus='0x1c' slot='0x01' function='0x0'/>
</disk>
<disk type='file' device='cdrom'>
<driver name='qemu' type='raw'/>
<source file='/install/OS/windowsimage.iso'/>
<target dev='hdc' bus='ide'/>
<readonly/>
<address type='drive' controller='0' bus='1' target='0' unit='0'/>
</disk>
<disk type='file' device='cdrom'>
<driver name='qemu' type='raw'/>
<source file='/install/OS/virtio-win-0.1-30.iso'/>
<target dev='hdd' bus='ide'/>
<readonly/>
<address type='drive' controller='0' bus='2' target='0' unit='0'/>
</disk>
<controller type='pci' index='0' model='pcie-root'/>
<controller type='pci' index='1' model='dmi-to-pci-bridge'>
<address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x0'/>
</controller>
<controller type='pci' index='2' model='pci-bridge'>
<address type='pci' domain='0x0000' bus='0x01' slot='0x01' function='0x0'/>
</controller>
<controller type='pci' index='3' model='pci-bridge'>
<address type='pci' domain='0x0000' bus='0x01' slot='0x02' function='0x0'/>
</controller>
<controller type='pci' index='4' model='pci-bridge'>
<address type='pci' domain='0x0000' bus='0x01' slot='0x03' function='0x0'/>
</controller>
<controller type='pci' index='5' model='pci-bridge'>
<address type='pci' domain='0x0000' bus='0x01' slot='0x04' function='0x0'/>
</controller>
<controller type='pci' index='6' model='pci-bridge'>
<address type='pci' domain='0x0000' bus='0x01' slot='0x05' function='0x0'/>
</controller>
<controller type='pci' index='7' model='pci-bridge'>
<address type='pci' domain='0x0000' bus='0x01' slot='0x06' function='0x0'/>
</controller>
<controller type='pci' index='8' model='pci-bridge'>
<address type='pci' domain='0x0000' bus='0x01' slot='0x07' function='0x0'/>
</controller>
<controller type='pci' index='9' model='pci-bridge'>
<address type='pci' domain='0x0000' bus='0x01' slot='0x08' function='0x0'/>
</controller>
<controller type='pci' index='10' model='pci-bridge'>
<address type='pci' domain='0x0000' bus='0x01' slot='0x09' function='0x0'/>
</controller>
<controller type='pci' index='11' model='pci-bridge'>
<address type='pci' domain='0x0000' bus='0x01' slot='0x0a' function='0x0'/>
</controller>
<controller type='pci' index='12' model='pci-bridge'>
<address type='pci' domain='0x0000' bus='0x01' slot='0x0b' function='0x0'/>
</controller>
<controller type='pci' index='13' model='pci-bridge'>
<address type='pci' domain='0x0000' bus='0x01' slot='0x0c' function='0x0'/>
</controller>
<controller type='pci' index='14' model='pci-bridge'>
<address type='pci' domain='0x0000' bus='0x01' slot='0x0d' function='0x0'/>
</controller>
<controller type='pci' index='15' model='pci-bridge'>
<address type='pci' domain='0x0000' bus='0x01' slot='0x0e' function='0x0'/>
</controller>
<controller type='pci' index='16' model='pci-bridge'>
<address type='pci' domain='0x0000' bus='0x01' slot='0x0f' function='0x0'/>
</controller>
<controller type='pci' index='17' model='pci-bridge'>
<address type='pci' domain='0x0000' bus='0x01' slot='0x10' function='0x0'/>
</controller>
<controller type='pci' index='18' model='pci-bridge'>
<address type='pci' domain='0x0000' bus='0x01' slot='0x11' function='0x0'/>
</controller>
<controller type='pci' index='19' model='pci-bridge'>
<address type='pci' domain='0x0000' bus='0x01' slot='0x12' function='0x0'/>
</controller>
<controller type='pci' index='20' model='pci-bridge'>
<address type='pci' domain='0x0000' bus='0x01' slot='0x13' function='0x0'/>
</controller>
<controller type='pci' index='21' model='pci-bridge'>
<address type='pci' domain='0x0000' bus='0x01' slot='0x14' function='0x0'/>
</controller>
<controller type='pci' index='22' model='pci-bridge'>
<address type='pci' domain='0x0000' bus='0x01' slot='0x15' function='0x0'/>
</controller>
<controller type='pci' index='23' model='pci-bridge'>
<address type='pci' domain='0x0000' bus='0x01' slot='0x16' function='0x0'/>
</controller>
<controller type='pci' index='24' model='pci-bridge'>
<address type='pci' domain='0x0000' bus='0x01' slot='0x17' function='0x0'/>
</controller>
<controller type='pci' index='25' model='pci-bridge'>
<address type='pci' domain='0x0000' bus='0x01' slot='0x18' function='0x0'/>
</controller>
<controller type='pci' index='26' model='pci-bridge'>
<address type='pci' domain='0x0000' bus='0x01' slot='0x19' function='0x0'/>
</controller>
<controller type='pci' index='27' model='pci-bridge'>
<address type='pci' domain='0x0000' bus='0x01' slot='0x1a' function='0x0'/>
</controller>
<controller type='pci' index='28' model='pci-bridge'>
<address type='pci' domain='0x0000' bus='0x01' slot='0x1b' function='0x0'/>
</controller>
<controller type='usb' index='0'>
<address type='pci' domain='0x0000' bus='0x02' slot='0x01' function='0x0'/>
</controller>
<controller type='sata' index='0'>
<address type='pci' domain='0x0000' bus='0x00' slot='0x1f' function='0x2'/>
</controller>
<controller type='ide' index='0'/>
<interface type='bridge'>
<mac address='54:52:00:5d:f0:f0'/>
<source bridge='br0'/>
<model type='virtio'/>
<address type='pci' domain='0x0000' bus='0x1c' slot='0x04' function='0x0'/>
</interface>
<serial type='pty'>
<target port='0'/>
</serial>
<console type='pty'>
<target type='serial' port='0'/>
</console>
<input type='tablet' bus='usb'/>
<memballoon model='virtio'>
<address type='pci' domain='0x0000' bus='0x02' slot='0x02' function='0x0'/>
</memballoon>
</devices>
<qemu:commandline>
<qemu:arg value='-vnc'/>
<qemu:arg value=':1'/>
<qemu:arg value='-device'/>
<qemu:arg value='ioh3420,bus=pcie.0,addr=1c.0,multifunction=on,port=1,chassis=1,id=root.1'/>
<qemu:arg value='-device'/>
<qemu:arg value='vfio-pci,host=03:00.0,bus=root.1,addr=00.0,multifunction=on,x-vga=on'/>
<qemu:arg value='-device'/>
<qemu:arg value='vfio-pci,host=03:00.1,bus=root.1,addr=00.1'/>
<qemu:arg value='-device'/>
<qemu:arg value='pci-assign,host=00:14.2'/>
<qemu:arg value='-usb'/>
<qemu:arg value='-device'/>
<qemu:arg value='usb-host,hostbus=4,hostaddr=2'/>
<qemu:arg value='-device'/>
<qemu:arg value='usb-host,hostbus=4,hostaddr=3'/>
<qemu:arg value='-device'/>
<qemu:arg value='usb-host,hostbus=5,hostaddr=2'/>
<qemu:env name='QEMU_AUDIO_DRV' value='alsa'/>
</qemu:commandline>
</domain>

Ich habe zusätzlich noch einige UBS Ports für die XBOX 360 Controller und Tastatur sowie Maus durchgereicht. Audio verwende ich über ALSA direkt, d.h. den SPDIF Ausgang vom Mainboard und nicht über HDMI, es geht aber auch direkt Audio über HDMI sofern man dafür Reciever etc hat.

Anschliessend kann man den Gast mit
virsh define xmlfile.xml ; virsh start windows8.1
starten und sieht auf der Radeon das BIOS der Grafikkarte booten, und dann geht der normale Bootprozess weiter. Die Performance ist überragend, selbst aktuelle Spiele wie z.B. WatchDogs laufen darauf problemlos ohne Ruckler. Ich habe das Board in ein 4 HE Servergehäuse geschraubt und mit SiltenWings Lüftern versehen, dadurch ist das System im Wohnzimmer praktisch unhörbar und passt durch das 4HE 19 Zoll Format exakt in mein Lowboard 🙂

Wer will, kann auch im Speicher des Gasts “cheaten” ähnlich wie damals beim Amiga Action Replay – vorrausgesetzt, man weiss was man tut!

Have fun… 🙂