PCI passthrough avec OMVF

De ArchwikiFR

Open Virtual Machine Firmware (OVMF) est un projet pour permettre le support de l‘UEFI aux machines virtuelles. Depuis linux 3.9 et des recentes versions de QEMU, il est maintenant possible de passer une carte graphique, ce qui offre à la machine virtuelle des performances graphiques natives, ce qui est pratique pour les tâches intensives.

Si vous avez un ordinateur avec un GPU que vous pouvez dédier à l’hôte ( qu’il s’agisse d’un GPU intégré ou bien d’une vielle carte OEM) et que le matériel le supporte (voir les #Prérequis), il est possible d’avoir une VM avec un système d’exploitation qui possède sa propre carte dédiée et qui délivre des performances presque natives.

Prérequis

Le passage d’une carte graphique repose sur un certain nombre de technologies qui ne sont pas encore omniprésentes et qui peuvent très bien ne pas être compatible sur votre matériel. Vous pourrez le réaliser sur votre machine si ces exigences sont satisfaites : Votre CPU doit supporter la virtualisation matérielle

  • Intel VT-x et Intel VT-d
    • AMD-V et AMD-Vi
    • Votre carte mère doit supporter la technologie IOMMU
  • Le chipset comme le BIOS doivent le supporter
  • Votre carte graphique doit supporter l’UEFI

Vous pourrez avoir besoin d’un second moniteur ou d’un moniteur à plusieurs sorties de manière à avoir un contrôle sur la machine hôte en cas de problème.

Mise en place de l’IOMMU

IOMMU est un mécanisme d’adressage des entrées-sorties, aussi connu sous les noms commerciaux de Intel VT et AMD-V.

Activation de l'IOMMU

Assurez vous d’avoir activé l’option dans le BIOS. On le trouve sous différents nom (Intel VT, AMD-V, Virtualization technology). Vous pouvez vous référer au manuel. Il faudra aussi amorcer le système hôte à l’aide d’un paramètre de noyau. Selon votre processeur, choisissezintel_iommu=on ou amd_iommu=on. Après redémarrage, effectuez un dmesg pour vérifier que l’IOMMU s’est correctement activé :

dmesg|grep -e DMAR -e IOMMU
[    0.000000] ACPI: DMAR 0x00000000BDCB1CB0 0000B8 (v01 INTEL  BDW      00000001 INTL 00000001)
[    0.000000] Intel-IOMMU: enabled
[    0.028879] dmar: IOMMU 0: reg_base_addr fed90000 ver 1:0 cap c0000020660462 ecap f0101a
[    0.028883] dmar: IOMMU 1: reg_base_addr fed91000 ver 1:0 cap d2008c20660462 ecap f010da
[    0.028950] IOAPIC id 8 under DRHD base  0xfed91000 IOMMU 1
[    0.536212] DMAR: No ATSR found
[    0.536229] IOMMU 0 0xfed90000: using Queued invalidation
[    0.536230] IOMMU 1 0xfed91000: using Queued invalidation
[    0.536231] IOMMU: Setting RMRR:
[    0.536241] IOMMU: Setting identity map for device 0000:00:02.0 [0xbf000000 - 0xcf1fffff]
[    0.537490] IOMMU: Setting identity map for device 0000:00:14.0 [0xbdea8000 - 0xbdeb6fff]
[    0.537512] IOMMU: Setting identity map for device 0000:00:1a.0 [0xbdea8000 - 0xbdeb6fff]
[    0.537530] IOMMU: Setting identity map for device 0000:00:1d.0 [0xbdea8000 - 0xbdeb6fff]
[    0.537543] IOMMU: Prepare 0-16MiB unity mapping for LPC
[    0.537549] IOMMU: Setting identity map for device 0000:00:1f.0 [0x0 - 0xffffff]
[    2.182790] [drm] DMAR active, disabling use of stolen memory
Attention : Attention, la présence d’une seule ligne indiquant "Intel-IOMMU: enabled" n’est pas suffisante. Elle est écrite lorsque le paramètre de noyau est utilisé

S’assurer que les groupes sont valides

Le script suivant devrait vous montrer comment les différents périphériques PCI sont adressés dans les groupes IOMMU. Si il ne renvoit rien, vous avez soit pas activé correctement le support de l’IOMMU, ou bien votre matériel ne le supporte pas.

#!/bin/bash
shopt -s nullglob
for d in /sys/kernel/iommu_groups/*/devices/*; do 
    n=${d#*/iommu_groups/*}; n=${n%%/*}
    printf 'IOMMU Group %s ' "$n"
    lspci -nns "${d##*/}"
done;

Exemple de sortie :

IOMMU Group 0 00:00.0 Host bridge [0600]: Intel Corporation Device [8086:591f] (rev 05)
IOMMU Group 1 00:01.0 PCI bridge [0604]: Intel Corporation Skylake PCIe Controller (x16) [8086:1901] (rev 05)
IOMMU Group 1 01:00.0 VGA compatible controller [0300]: Advanced Micro Devices, Inc. [AMD/ATI] Ellesmere [Radeon RX 470/480] [1002:67df] (rev c7)
IOMMU Group 1 01:00.1 Audio device [0403]: Advanced Micro Devices, Inc. [AMD/ATI] Device [1002:aaf0]
IOMMU Group 2 00:02.0 VGA compatible controller [0300]: Intel Corporation Device [8086:5912] (rev 04)
…

Un groupe IOMMU est la plus petite quantité de périphériques qui peuvent être passés votre machine virtuelle. Par exemple, dans l’exemple ci-dessus, le GPU et son controlleur audio font partie du même groupe (Group 1) et ne peuvent être passés qu’ensemble. En revanche, deux controlleurs USB appartenant à différents groupes peuvent être passés séparéments.

Branchement votre GPU invité dans un port PCIe relié au CPU

Tous les ports PCI-E ne sont pas identiques. Ils peuvent être reliés à votre CPU ou à votre PCH. Selon votre matériel, il est possible que votre port PCIe relié au processeur ne supporte pas correctement l’isolation. Dans ce cas le port PCI apparaitera dans le même groupe que le dispositif qui est branché dessus.

IOMMU Group 1 00:01.0 PCI bridge: Intel Corporation Xeon E3-1200 v2/3rd Gen Core processor PCI Express Root Port (rev 09)
IOMMU Group 1 01:00.0 VGA compatible controller: NVIDIA Corporation GM107 [GeForce GTX 750] (rev a2)
IOMMU Group 1 01:00.1 Audio device: NVIDIA Corporation Device 0fbc (rev a1)

Si votre groupe se présente que ci-dessus, ce n’est pas un problème. Selon comment sont organisés vos slot, vous pourriez trouver des périphériques en plus dans le groupe, ce qui obligerait a tous les passer à la machine virtuelle.

Note : Si les PCI roots et les PCI bridges sont groupés à d’autres périphériques, ils ne doivent pas être cachés à l’amorçage du système, ni être ajoutés à une machine virtuelle..

Isolation de la carte graphique

De part leur complexité, les pilotes graphiques ne supportent bien pas les changements de système à chaud, donc vous ne pouvez pas passer un GPU que vous utilisez sur l’hôte à l’invité de manière transparente sans conséquences. Il est générallement préférable de les lier à un espace réservé. Celà aura pour effet d’empécher des pilotes de se charger et forcera le GPU à rester inactif tant que la machine virtuelle ne sera pas lancée. Si votre noyau le supporte, il est recommandé d’utiliser vfio-pci.

Attention : Quand vous redémarrez après cette procédure, les GPU que vous aurez configuré ne pourront plus être utilisés sur l'hôte à moins de faire la manipulation inverse. Faites attention à ce que le GPU utilisé sur l'hôte soit correctement configuré. Dans le doute, il peut être utile de désactiver certains services (ex : gdm) qui pourrait geler le système en cas de problème de pilote graphique.
Note : Une clé amorçable d'Arch Linux peut être utile en cas de problème.

En utilisant vfio-pci

Le module vfio-pci est inclus dans le noyau Linux depuis sa version 4.1 et est similaire à pci-stub. Vous pouvez voir s votre système le supporte en lançant la commande suivante. Si il renvoit une erreur, vous devrez utiliser pci-stub à la place.

$ modinfo vfio-pci
filename:       /lib/modules/4.4.5-1-ARCH/kernel/drivers/vfio/pci/vfio-pci.ko.gz
description:    VFIO PCI - User Level meta-driver
author:         Alex Williamson <alex.williamson@redhat.com>
...

Vfio-pci cible normalement les périphériques PCI grâce à leur ID, ce qui signifie que vous devrez inscrire tous les Ids de périphériques que vous souhaitez passer. Dans le cas ci-dessous, vous relierez viof-pci avec [1002:67df] et [1002:aaf0] :

 IOMMU Group 1 00:01.0 PCI bridge [0604]: Intel Corporation Skylake PCIe Controller (x16) [8086:1901] (rev 05)
 IOMMU Group 1 01:00.0 VGA compatible controller [0300]: Advanced Micro Devices, Inc. [AMD/ATI] Ellesmere [Radeon RX 470/480] [1002:67df] (rev c7)
 IOMMU Group 1 01:00.1 Audio device [0403]: Advanced Micro Devices, Inc. [AMD/ATI] Device [1002:aaf0]
Note : Si vous avez deux cartes graphiques identiques ou avec le même nom, vous ne devez pas essayer de les isoler avec cette méthode. La procédure est à adapter.

Vous pouvez donc ajouter les ID des appareils au paramètre chargés par défaut au lancement du module vfio-pci.

Note : Si, comme expliqué précédemment, le concentrateur PCI appartient au même groupe que la carte graphique à passer, alors vous ne devez surtout pas l’inclure avec les autres ID. Il doit rester rattaché au système hôte pour fonctionner correctement. En revanche, tous les autres périphériques rattachés à ce même groupe doit figurer parmi les IDs liés au vfio-pci.

Malgré cela, il n’est pas garanti que vfio-pci soit lancé avant un autre pilote graphique quelconque. Pour s’assurer de cela, on doit l’inscrire dans le noyau, avec ses dépendances. Pour cela, on ajoute dans cet ordre vfio, vfio_iommu_type1, vfio_pci et vfio_virqfd à mkinitcpio:

/etc/mkinitcpio.conf
MODULES="... vfio vfio_iommu_type1 vfio_pci vfio_virqfd ..."
Note : Si vous lancez un autre pilote graphique par KMS (tel que nouveau, amdgpu, i915, etc), il est important que tous les modules vfio figurent avant.

Assurez vous également que le hook modconf figure dans la liste de mkinitcpio.conf

/etc/mkinitcpio.conf
HOOKS="... modconf ..."

Maintenant que les modules ont été ajouté à la configuration de l’iniframfs, vous devez le régénérer. Vous devez également le refaire à chaque édition du fichier /etc/modprobe.d/vfio.conf.

# mkinitcpio -p linux
Note : Si vous utilisez un autre noyau, tel que linux-vfio, remplacer linux avec le noyau que vous souhaitez utiliser.

Redémarrez et vérifiez que vfio-pci c’est correctement chargé et qu’il pointe vers les bons appareils.

$ dmesg | grep -i vfio 
[    0.719819] VFIO - User Level meta-driver version: 0.3
[    0.736560] vfio_pci: add [1002:67df[ffff:ffff]] class 0x000000/00000000
[    0.753197] vfio_pci: add [1002:aaf0[ffff:ffff]] class 0x000000/00000000

Vous pouvez vérifier le driver utilisé pour les périphériques qui doivent utiliser vfio :

$ lspci -nnk -d 10de:13c2
$ lspci -nnk -d 1002:aaf0
01:00.1 Audio device [0403]: Advanced Micro Devices, Inc. [AMD/ATI] Device [1002:aaf0]
	Subsystem: PC Partner Limited / Sapphire Technology Device [174b:aaf0]
	Kernel driver in use: vfio-pci
	Kernel modules: snd_hda_intel

Préparer un système invité à l’aide OVMF

OVMF est le microprogramme UEFI open source qu’on utilise pour les machines vrtuelles QEMU et KVM. Il est préférable de l’utiliser plûtot que SeaBIOS, qui est le microprogramme par défaut.

Configurer libvirt

Libvirt est utilisé pour faciliter le déploiement des machines virtuelles. Installez qemu, libvirt, ovmf-git and virt-manager, Ensuite, ajouter le chemin vers le microprogramme OVMF à la configuration de libvirt pour que virt-install et virt-manager puisse les trouver lors de l’installation de la machine. Par exemple :

/etc/libvirt/qemu.conf
nvram = [
	"/usr/share/ovmf/x64/ovmf_x64.bin:/usr/share/ovmf/x64/ovmf_vars_x64.bin"
]

Vous devez maintenant activer et démarrer libvirtd et son module de journalisation

# systemctl enable --now libvirtd
# systemctl enable virtlogd.socket

Paramétrer le système invité

Le processus est guidé à l’aide de virt-manager. Quelques précautions importantes sont à prendre en considération :

  • N’oubliez pas de cocher l’option personnaliser la configuration avant l’installation
  • Dans l’onglet "Aperçu", réglez le microprogramme sur "UEFI". Si l’option n’est pas disponible, assurez vous d’avoir correctement modifié le fichier de dconfiguration de libvirt
  • Dans l’onglet "Processeur", cochez l’option "Copier la configuration du processeur de l’hôte".
  • Pour minimiser la surcharge des entrées/sorties, à l’aide de l’option "Ajouter un matériel", ajoutez un controlleur SCSI et reliez les disques à ce contrôleur en sélectionnant le type de bus
    • Les machines Windows ne reconnaiteront pas ces disques par défaut. Vous devez télécharger les pilotes à partir d’iciet ajouter un lecteur de CD-ROM avec cette image disque. L’installation de ces pilotes est réalisée lors de l’installation de Windows en installant un pilote à partir du disque.

Le reste de l'installation se déroule normalement. Elle utilise une adaptateur vidéo QXL dans une fenêtre.

Rattacher le périphérique PCI

Quand l’installation est terminée, il est possible d’ajouter le périphérique PCI à la machine virtuelle. Vous pouvez alors supprimer l’affichage virtuel, l’adaptateur QXL et les souris, claviers et tablette USB. Vous pouvez alors rattacher une souris USB et un clavier USB à l’invité. Attention à garder au moins un clavier et une souris sur le périphérique hôte au cas ou un problème survient sur l’invité. Cela permettera d’eteindre la machine virtuelle et de reprendre le contrôle du système hôte. Si tout s’est bien déroulé, vous pouvez alors relier un écran au GPU de l’invité, et installer tous les pilotes nécéssaires.

Amélioration des performances

Dans la plupart des cas, les PCI passthrough sont utilisés pour des applications qui utilisent les ressources de manière intensive, comme les jeux-vidéos ou les tâches demandant une accélération 3D GPU-accelerated tasks. Si cette manipulation est une grande étape pour se rapprocher des performances native d'un système sur la machine, quelques ajustements sont encore nécessaires pour améliorer l'efficacité de la machine.

CPU pinning

Par défaut, les invités KVM lance le calcul d'operations selon un certain nombre de fils (threads) représentant le nombre de processeurs virtuels. Les fils sont traités comme n'importe quel autre processus par le noyau Linux. Passé d'un thread à un autre ajoute de la surcharge au processeur (car passer cela force le coeur à changer son cahce entre les opérations) ce qui peut altérer de façon considérable les performances.. Le CPU pinning (signifiant "ancrage" ou "épinglage") permet de résoudre le problème car il contrôle l'ordonnaceur et s'assure que tous les threads sont toujours exécutés sur les mêmes coeurs. Par exemple ici, les coeurs de l'invité 0, 1, 2 et 3 sont ancrés respectivement sur les coeurs 4, 5, 6 et 7.

EDITOR=nano virsh edit myPciPassthroughVm
...
<vcpu placement='static'>4</vcpu>
<cputune>
    <vcpupin vcpu='0' cpuset='4'/>
    <vcpupin vcpu='1' cpuset='5'/>
    <vcpupin vcpu='2' cpuset='6'/>
    <vcpupin vcpu='3' cpuset='7'/>
</cputune>
...

Le cas de l'Hyper-threading

Si vous utilisez un processeur pouvant traiter plusieurs threads par coeur (par exemple les processeurs possédant l'Intel Hyperthreading), il peut être intéressant de décider comment les threads doivent être répartis sur le coeurs Voici le résultat de la commande /proc/cpuinfo sur une machine à 4 coeurs physiques avec hyperthreading.

$ cat /proc/cpuinfo | grep -e "processor" -e "core id" -e "^$"
processor	: 0
core id		: 0

processor	: 1
core id		: 1

processor	: 2
core id		: 2

processor	: 3
core id		: 3

processor	: 4
core id		: 0

processor	: 5
core id		: 1

processor	: 6
core id		: 2

processor	: 7
core id		: 3

Si vous ne comptez pas réaliser des tâches très lourdes sur le système hôte (ou même aucune tâche) en même temps que vous utilisez la machine virtuelle, il sera probablement mieux de répartir les threads sur tous les coeurs logiques. ainsi, la machine virtuelle pourra tirer profit de tous les coeurs physiques

Sur la machine à 4 cœurs mentionnée précédemment, celà donne :

EDITOR=nano virsh edit myPciPassthroughVm
...
<vcpu placement='static'>4</vcpu>
<cputune>
    <vcpupin vcpu='0' cpuset='4'/>
    <vcpupin vcpu='1' cpuset='5'/>
    <vcpupin vcpu='2' cpuset='6'/>
    <vcpupin vcpu='3' cpuset='7'/>
</cputune>
...
<cpu mode='custom' match='exact'>
    ...
    <topology sockets='1' cores='4' threads='1'/>
    ...
</cpu>
...

Si vous comptez executer des tâches intensives sur les deux systèmes invités et hôte, il peut être préférable de limiter une quantité limitée de coeurs à l'invité, avec leurs threads respectifs, et de laisser le reste à l'hôte.

Celà donnerait :

EDITOR=nano virsh edit myPciPassthroughVm
...
<vcpu placement='static'>4</vcpu>
<cputune>
    <vcpupin vcpu='0' cpuset='2'/>
    <vcpupin vcpu='1' cpuset='3'/>
    <vcpupin vcpu='2' cpuset='6'/>
    <vcpupin vcpu='3' cpuset='7'/>
</cputune>
...
<cpu mode='custom' match='exact'>
    ...
    <topology sockets='1' cores='2' threads='2'/>
    ...
</cpu>
...

Dépannage

Plus de son sur la sortie HDMI quand intel_iommu est activé

Si après l'activation de intel_iommu la sortie HDMI audio de la carte graphique intégrée Intel ne produit plus de son, le paramètre de noyau igfx_off (i.e. intel_iommu=on,igfx_off) devrait suffire à retrouver le son. Lire Graphics Problems? sur Intel-IOMMU.txt pour plus de détails sur igfx_off.

Voir également