Dies ist ein kleines Tutorial wie man ein bereits "bare metal" installiertes Windows 10 in KVM auf OpenSUSE Leap 15.3 virtualisiert (inkl. PCI passthrough) und zwar ohne dass man es jedes Mal aktivieren muss.
Ich habe es hauptsächlich für mich selbst geschrieben damit ich beim nächsten mal eine Orientierung habe was alles getan werden muss.

Ich habe hier auch nur aufgeschrieben was für mich von Relevanz ist.
Falls ihr das selbst noch nie gemacht habt, dann lest euch die verlinkten Artikel ebenfalls genau durch.

Voraussetzungen und Vorbemerkungen

  • Das allerwichtigste ist ein gutes Motherboard mit guter IOMMU Gruppierung und eine CPU mit Unterstüzung für Virtualisierung!!

    Ich habe es zuerst mit einem Motherboard versucht welches die PCI Geräte sehr schlecht gruppiert hat.
    Ich habe dann mit Hilfe eines Kernel-Patches (genannt ACS-Patch) die Geräte in ihre eigene Gruppe "gezwungen", was zwar grundsätzlich funktionierte aber das System ziemlich instabil machte.
    Könnte aber auch daran gelegen haben, dass ich damals OpenSUSE Tumbleweed (bleeding edge rolling release) benutzt habe und zweitens das VM-Image auf einer NTFS-Partition speicherte. Bin dem aber nicht weiter nachgegangen weil ich keinen Bock mehr hatte und lieber wieder nativ mit Windows 10 gearbeitet habe.

  • Sowohl Linux wie auch Windows müssen ihren eigenen Bootloader auf ihrer jeweiligen eigenen Festplatte haben!

    Es gibt aber HIER einen Ansatz wie man dieses Problem lösen kann wenn sich beide Systeme einen Bootloader teilen. Ich werde aber in diesem Tutorial nicht darauf eingehen.

  • Ich gehe in diesem Tutorial davon aus dass beide Betriebssysteme komplett installiert und konfiguriert sind und dass KVM in OpenSUSE installiert ist.

    KVM installiert man am besten mit YAST -> Virtualisierung -> Hypervisor und Tools installieren und dann bei KVM beide Optionen auswählt.

Bevor ich zum eigentlichen Aufsetzen der virtuellen Maschine komme, muss noch einiges vorbereitet werden.

PCI Passthrough

Als erstes konfiguriere ich das System für den PCI passthrough.
Ich habe dafür eigentlich hauptsächlich dieses Tutorial benutzt.

Zuerst aktiviert man im BIOS die Optionen für ACS, IOMMU und SR-IOV.
Wie das geht entnimmt man am besten dem Manual des entsprechenden Motherboards und BIOS.

Dann erstellt man ein Script um sowohl die IOMMU Gruppen wie auch die PCI Geräte und deren Adresse übersichtlich anzeigen zu lassen.

cd
nano iommu.sh

mit folgendem Inhalt

!/bin/bash
shopt -s nullglob
for iommu_group in $(find /sys/kernel/iommu_groups/ -maxdepth 1 -mindepth 1 -type d);do 
	echo "IOMMU group $(basename "$iommu_group")"
 	for device in $(\ls -1 "$iommu_group"/devices/); do 
		if [[ -e "$iommu_group"/devices/"$device"/reset ]]; then 
			echo -n "[RESET]"
		 fi
	echo -n $'\t'
	lspci -nns "$device"
	done
done

Dann macht man das Script ausführbar und startet es.
Für die Befehle innerhalb des Scripts benötigt man root-Rechte weshalb es mit sudo ausgeführt wird.

chmod +x iommu.sh
sudo ./iommu.sh

Nun sollte eine Liste mit den Geräten und deren Gruppen ausgegeben werden.
Man sucht nun nach den Geräten die man durchreichen möchte und überprüft ob sie alleine in ihrer Gruppe sind.
Grafikkarten sind immer mit ihrer HDMI-Audioausgabe in einer Gruppe und man sollte auch beiden Geräte an den Gast durchreichen damit die Grafikkarte einwandfrei funktioniert.

Damit die Geräte an den Gast durchgereicht werden können, müssen sie durch den vfio-pci Kernel-Treiber geladen werden.

sudo nano /etc/modprobe.d/gpu-passthrough.conf

Folgende Zeile einfügen

install vfio-pci /sbin/vfio-pci-override.sh

Nun muss man das vfio-pci-override.sh Script erstellen

sudo nano /sbin/vfio-pci-override.sh

Mit folgendem Inhalt

#!/bin/sh

DEVS="0000:0a:00.0 0000:0a:00.1"

for DEV in $DEVS; do
    echo "vfio-pci" > /sys/bus/pci/devices/$DEV/driver_override
done

modprobe -i vfio-pci

Bei DEVS trägt man die IDs der Geräte ein die man durchreichen möchte.
Anmerkung: mit dem iommu.sh Script werden die Geräte ohne 0000: angegeben, man muss das also immer noch selbst anfügen!

Danach macht man das Script ausführbar

sudo chmod +x /sbin/vfio-pci-override.sh

Jetzt muss dracut nur noch all diese Scripte und Dateien laden

sudo nano /etc/dracut.conf.d/gpu-passthrough.conf

und fügt folgendes ein

force_drivers+=" vfio vfio-pci vfio_iommu_type1 "
install_items=" /sbin/vfio-pci-override.sh "

Anmerkung: Die Spaces werden benötigt falls andere Scripte noch andere Dinge in die initramfs laden.

Damit die Geräte vom Kernel mit dem vfio-pci Treiber geladen werden, muss nun die initramfs neu generiert werden.
Mit folgendem Befehl macht man dies nur für den aktuell geladenen Kernel (dies hat aber bei mir nie wirklich funktioniert weshalb ich die initramfs einfach nur mit mkinitrd für alle Kernel neu generiert habe)

sudo mkinitrd -k $(uname -r)

Mit

sudo lsinitrd | grep vfio

kann man nun prüfen ob vfio geladen wird.

Zum Schluss muss noch der Bootloader angepasst werden indem man Folgendes an das Startkommando anfügt.
Dies kann man über Yast -> Bootloader machen.

amd_iommu=force_isolation iommu=pt rd.driver.pre=vfio-pci

Anschliessend muss der PC neu gestartet werden!
Dabei kann man zuerst direkt mal in Windows booten um die...

Windows 10 UUID

...auszulesen!

Damit Windows 10 nicht wegen geänderter Hardware meckert und neu aktiviert werden muss benötigt man dessen System-UUID!

Diese erhält man indem man in der Eingabeaufforderung (cmd) folgenden Befehl eingibt:

wmic csproduct get UUID

Dann bekommt man folgende Ausgabe:

(out)UUID
(out)b0a80e8d-6890-460f-89f3-66638ce42433

Diese UUID speichert man irgendwo wo man dann auch von Linux aus Zugriff hat.
(natürlich habe ich hier nicht meine richtige UUID benutzt sondern eine random generieren lassen)

Anschliessend geht es per Neustart wieder zurück in das Linux System!

Festplatte

Nun benötigt man noch eine eindeutige ID der Windows-Platte.
Da /dev/sda keine eindeutige Identifikation zulässt werde ich die Platte über /dev/disk/by-id einbinden.
Selbst wenn man die Festplatten nicht umsteckt kann es vorkommen, dass diese Identifikation ändert, je nachdem wie der Kernel beim Aufstarten den SATA-Controller lädt bzw. ausliest.

Ich verwende für meine VM deshalb die wwn-Identifikation von /dev/disk/by-id.

Um die Platten zu listen führt man folgenden Befehl aus:

ls -la /dev/disk/by-id/wwn*

Und notiert sich den gesamten Pfad der Platte auf der Windows 10 installiert ist, z.b.

(out)/dev/disk/by-id/wwn-0x5000c50032e8e25d

Virtuelle Maschine erstellen

Das Aufsetzen einer virtuellen Maschine mit der korrekten UUID ist relativ einfach.

Als erstes erstellt man mit KVM wie folgt eine virtuelle Maschine:

Manuelle installation wählen

Betriebssystem wählen

Arbeitsspeicher und CPU Anzahl einstellen

Die Festplatte (siehe Oben) eintragen

Einen Namen wählen und (wichtig!) "Konfiguration bearbeiten vor der Installation" anklicken
Bei der Netzwerk Auswahl wähle ich eine Netzwerkbrücke (Bridge) da ich Scream (siehe Unten) und die Windows Freigabe mit NAT nicht sauber hinbekommen habe.
Ein kleines Tutorial wie ich eine Bridge mit NetworkManager erstellt habe findet sich im Artikel Netzwerkbrücke mit nmcli in OpenSUSE 15.3 erstellen
Ansonsten kann man da auch den Standard beibehalten und die Netzwerkkonfiguration später noch bearbeiten.

Auf der nun angezeigten Übersichtsseite wählt man bei BIOS ein EFI-Bios aus.
Im Fall von OpenSUSE ist ovmf-x86_64-code.bin am neutralsten.

Nun wechselt man in die XML-Ansicht um die Konfiguration direkt zu ändern.
Hier trägt man als erstes die Windows 10 UUID (siehe Oben) ein

Nun fügt man das IVSHMEM Gerät für Looking Glass, welches man später noch benötigt und konfiguriert, ein.
Am besten fügt man es vor </devices> ein!

    <shmem name='looking-glass'>
      <model type='ivshmem-plain'/>
      <size unit='M'>32</size>
    </shmem>

Anmerkung: Der Wert bei <size unit='M'> wird wie folgt berechnet:
Auflösung Breite x Auflösung Höhe x 4 x 2 / 1024 / 1024 + 10
das Ergebnis wird anschliessend zur nächsten Binären Potenz aufgerundet

Da Windows höchstens 2 CPUs (Sockets) erkennen kann und auch sonst die Standardeinstellungen Probleme machen können, muss man noch den Prozessor konfigurieren.
Dazu geht man auf den Abschnitt "Anzahl der CPUS" und wechselt wieder auf die Registerkarte "Details".
Nun deaktiviert man "Die CPU-Konfiguration vom Wirt kopieren" und wählt stattdessen das Model "host-passthrough".
Bei der Netzstruktur wählt man "Manuell die CPU-Netzstruktur einstellen" und gibt ein:
- 1 Sockets
- 12 Kerne (oder wieviel man halt zuweisen möchten)
- 1 Threads
Man kann auch 6 Kerne mit 2 Threads nehmen, wichtig ist nur, dass die Anzahl Sockets zwei nicht übersteigen.

Jetzt kann man mit "+ Gerät hinzufügen" die Grafikkarte (und andere PCI- oder USB-Geräte die man an den Gast übergeben möchte) hinzufügen.
Anschliessend klickt man auf "Maschine installieren" und die VM sollte nun in Windows 10 booten! YAY!

Anmerkung: Die Maschine startet in einer Art VNC-Verbindung, die GPU kann auf diese Weise nicht benutzt werden.
Eine Möglichkeit wäre einen zweiten Bildschirm an die Gast-GPU anzuschliessen und ein zweites Set Maus und Tastatur an den Gast durchzureichen, was aber nicht sehr praktisch ist.
Aber das wird im nächsten Kapitel gelöst.
Wichtig ist momentan nur dass Windows läuft und Internet Zugang hat, weil nun noch vier Tools und Treiber heruntergeladen werden müssen.

Virtio, IVSHMEM Looking Glass und Scream installieren

Gast

Da nun Windows eh gerade läuft installiere ich gleich noch die IVSHMEM Treiber, Virtio Treiber, Looking Glass und Scream.
Wichtig! Sowohl auf dem Gast wie auch dem Host müssen die gleichen Versionen von Looking Glass und Scream installiert werden, sonst funktioniert es nicht!

Zuerst lädt man die Virtio Treiber herunter (man kann entweder ein .iso oder die .exe benutzen) und installiert sie als Administrator.
Alternativ kann man auch das ISO-Abbild herunterladen, als virtuelles CD-ROM Laufwerk einbinden und für alle Geräte die im Geräte-Manager nicht erkannt werden den Treiber auf der CD suchen lassen.

Anschliessend lädt man die IVSHMEM Treiber für Looking Glass herunter und installiert sie als Administrator.

Looking Glass ist einfach; man lädt einfach die Windows Host Binary Datei von der Webseite herunter (Passwort aufschreiben!) und führt es als Administrator aus.

Bei Scream ist die Prozedur grundsätzlich die gleiche wie bei Looking Glas.
Zuerst die Nicht-Source Zip-Datei von der Scream Release Seite herunterladen und entpacken.
Dann die "install-x64.bat", wiederum als Administrator, ausführen.
Nun fährt man die Maschine herunter.

Nun muss bei der Virtuellen Maschine das Gerät "Tablett" entfernt werden und "Video" auf "none" gesetzt werden (kann man einfach in das Feld reinschreiben).

Host

Ich werde alles in einem Ordner "VMGaming" im Home-Ordner meines Benutzers installieren.

Als erstes braucht es einige headers und cmake für die Kompilierung

sudo zypper in cmake gnu-free-fonts Mesa-libEGL-devel Mesa-libGL-devel libnettle-devel binutils-devel libXfixes-devel libXi-devel libXinerama-devel libXss-devel wayland-protocols-devel spice-protocol-devel wayland-devel freetype-devel fontconfig-devel zlib-devel-static libpulse-devel

Nun lädt man die beiden Quellen herunter um sie zu kompilieren.

Anmerkung: Zur Zeit als dieses Tutorial geschrieben wurde, waren die aktuellsten Versionen von Looking Glass B4 und Scream 3.8

Die aktuellsten Versionen finden man sonst hier:

Ok, let's go!
Zuerst lade ich die beiden Source Archive herunter und entpacke sie:

cd
mkdir VMGaming
cd VMGaming
wget --content-disposition https://looking-glass.io/ci/host/source?id=stable
wget https://github.com/duncanthrax/scream/archive/refs/tags/3.8.zip
tar -xvf looking-glass-B4.tar.gz
unzip 3.8.zip
Looking Glass
cd
mkdir VMGaming/looking-glass-B4/client/build
cd VMGaming/looking-glass-B4/client/build
cmake ../
make
mv looking-glass-client ~/VMGaming/looking-glass-client
Scream
cd
mkdir VMGaming/scream-3.8/Receivers/unix/build
cd VMGaming/scream-3.8/Receivers/unix/build
cmake ../
make
mv scream ~/VMGaming/scream

Nun lösche ich die Source Ordner/Archive

cd ~/VMGaming
rm looking-glass-B4.tar.gz && rm -fr looking-glass-B4
rm 3.8.zip && rm -fr scream-3.8

Bevor ich Looking Glass starten kann, muss ich noch eine Shared-Memory-Datei mit Schreib-/Leserechten für meinen Benutzer für IVSHMEM erstellen.

sudo nano /etc/tmpfiles.d/10-looking-glass.conf

Mit folgendem Inhalt (den user durch den eigenen Benutzer ersetzen)

#Type Path               Mode UID  GID Age Argument

f /dev/shm/looking-glass  0660 user kvm -

Damit man den PC nicht zuerst neu starten muss kann man diese Datei manuell erstellen.

sudo touch /dev/shm/looking-glass && sudo chown user:kvm /dev/shm/looking-glass

Scream läuft standardmässig auf dem UDP-Port 4010, also muss ich diesen in der Firewall öffnen um Audio zu empfangen.
Ich benutze firewalld:

sudo firewall-cmd --permanent --add-port=4010/udp

Da Scream bei mir im standardmässigen Multicast-Modus immer wieder "abhängte" betreibe ich das Teil nun im Unicast-Modus auf Port 4011

Dazu muss in der Windows Registry der Schlüssel HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Scream\Options geändert bzw. hinzugefügt werden.
Dann fügt man eine neue Zeichenfolge mit dem Namen "UnicastIPv4" und der IP des Hosts auf dem Scream läuft als Wert ein.
Der Port wird mit einem DWORD mit dem Namen "UnicastPort" und dem Wert "4011" (Dezimal!) definiert.
Nach dieser Änderung muss die Maschine neu gestartet werden.

Ausserdem muss der Befehl für die Firewall entsprechend angepasst werden.

sudo firewall-cmd --permanent --add-port=4011/udp

Anmerkung: Ich habe Scream bis jetzt nur über Pulseaudio mit einer Netzwerkbrücke betrieben.
Ich weiss nicht ob es mit ALSA oder Jack besser ist oder wie und ob es in einem virtuellen Netzwerk funktioniert Audio vom Gast im Host wiederzugeben. Für mich funktioniert das im Moment gut so.
Anmerkung 2: Wenn man die VM über NAT in einem Subnetz betreibt, dann muss man nebst dem Öffnen des Ports auch die IP des Gastes zur trusted-zone hinzufügen.

Ein kleines Script um beide Services gleichzeitig zu starten und zu beenden bzw. um Scream zu beenden sobald Looking Glass geschlossen wird habe ich in diesem Tutorial gefunden.

cd ~/VMGaming
touch startup.sh && chmod +x startup.sh
nano startup.sh

Und füge folgenden Inhalt ein.
Anmerkung: mein Netzwerk-Interface hat die Bezeichnung br0.

#!/bin/bash

~/VMGaming/looking-glass-client >/dev/null 2>&1 & # Starts Looking Glass, and ignores all output (We aren't watching anyways)
#~/VMGaming/scream -o pulse -i br0 & # Starts Scream in default multicast mode
~/VMGaming/scream -o pulse -u -p 4011 -i br0 & # Starts Scream in unicast mode on port 4011

wait -n # We wait for any of these processes to exit. (Like closing the Looking Glass window, in our case)
pkill -P $$ # We kill the remaining processes (In our case, scream)

Das Script kann man nun entweder über einen Dateiexplorer starten oder in der Konsole mit

~/VMGaming/startup.sh &

Die Basics sollten nun funktionieren.
Ich habe mein System genau so aufgesetzt und es funktioniert.
Bei Problemen ist die Suchmaschine der Wahl oder das Forum von LevelOneTechs zu konsultieren.
Nun kann man selber noch ein bisschen experimentieren.

Happy computing!

Noch eine kleine Anmerkung zu...

MSI Interrupt

Wenn es in Windows zu Crackles oder ähnlichen Audio oder gar visuellen Phänomenen kommt, kann das daran liegen, dass Windows die Grafikkarte oder andere durchgereichte Geräte mit dem Legacy Lane-based Interrupt betreibt.
Nativ scheint das kein Problem zu sein aber in der Virtualisierung können anscheinend Konflikte entstehen, meistens mit der Grafikkarte und deren Audioport.

Um zu überprüfen ob ein Geräte in MSI oder Legacy Modus läuft, öffnet man den Geräte-Manager, ändert die Ansicht auf "Ressourcen nach Typ" und übeprüft die durchgereichten bzw. Virtio gesteuerten Geräte unter "Interruptanforderungen (IRQ)".
Hat dort ein durchgereichtes oder Virtio Gerät (man sollte das Augenmerk zuerst auf die Grafikkarte und deren Audioport richten) einen positiven Wert in der Klammer, dann wird das Gerät im Legacy Modus betrieben und sollte geändert werden.
Bei einem negativen Wert wird das Gerät bereits im MSI Modus betrieben und das Problem muss irgendwo anders liegen.

Einen ausführlicheren Artikel über die Thematik findet sich in diesem guru3d Forum Beitrag, welchen ich hier gekürzt übernommen habe.

Um das entsprechende Gerät zu ändern macht man einen Doppelklick darauf um dessen Eigenschaften anzuzeigen.
Achtung: Bevor man hier irgend etwas ändert, sollte man zuerst einen Wiederherstellungspunkt erstellen!!
Dort geht man auf Details und wählt im Dropdown der Eigenschaften "Geräteinstanzpfad" und kopiert diesen.
Nun öffnet man den Registry Editor (regedit.exe) und fügt dem Pfad HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\ den vorhin kopierten Pfad des Gerätes hinzu.

Dort fügt man nun den Schlüssel Device Parameters\Interrupt Management\MessageSignaledInterruptProperties mit einen DWORD-Eintrag "MSISupported" mit dem Wert 1 (Dezimal) ein.
Um ein Gerät wieder in Legacy-Modus zu versetzen gibt man 0 anstatt 1 ein.
Die Maschine muss danach neu gestartet werden.

Anmerkung: Man sollte das Gerät nach jedem Treiber Update überprüfen.
Meine Grafikkarte hat den Schlüssel gelöscht und sich wieder in den Legacy Modus gesetzt.
Der Audioport blieb aber erhalten.