OVMF による PCI パススルー

提供: ArchWiki
2016年11月28日 (月) 21:11時点におけるKusakata (トーク | 投稿記録)による版 (同期)
ナビゲーションに移動 検索に移動

Open Virtual Machine Firmware (OVMF) は仮想マシンで UEFI を使えるようにするプロジェクトです。Linux 3.9 と新しいバージョンの QEMU では、グラフィックカードをパススルーすることが可能で、仮想マシンでネイティブと同じグラフィック性能を発揮することができます。

デスクトップコンピュータに使用していない GPU が接続されている場合 (内蔵 GPU や古い OEM カードでもかまいません、ブランドが一致している必要はありません)、ハードウェアがサポートしていれば (#要件を参照)、あらゆる OS の仮想マシンで専用 GPU として(ほぼ)最大限の性能を活用できます。技術的な詳細は こちらのプレゼンテーション (pdf) を見てください。

目次

要件

VGA パススルーでは最先端の技術を使っているため、あなたのハードウェアでは使用できない可能性があります。パススルーを行うには以下の要件が満たされていなければなりません:

使用していないモニターやマウス、キーボードがあれば、それも仮想マシンに割り当てることができます (GPU はディスプレイが接続されていないと何も出力することができず Spice 接続では性能が上がりません)。何か問題が発生した場合でも、スペアの機材があればホストマシンは制御できます。

IOMMU のセットアップ

IOMMU はシステム固有の IO マッピング機構でほとんどのデバイスで使用することができます。IOMMU は Intel の VT-x/Intel と AMD の AMD-V/AMD-Vi で共通して使われる名前です。

IOMMU の有効化

BIOS の設定で AMD-VI/VT-d を有効化してください。通常は他の CPU 機能と一緒に設定が並んでいるはずです (オーバークロック関連のメニューに存在することもあります)。設定における名前は技術的な名前 ("Vt-d" あるいは "AMD-VI") だったり、旧式の名前 (Vt-x の場合は "Vanderpool"、AMD-V の場合は "Pacifica")、あるいは "Virtualization technology" などの曖昧な単語だったりします。マニュアルに載っていない場合もあります。

また、ブートローダーのカーネルオプションでカーネル内の IOMMU のサポートも有効にする必要があります。使用している CPU のタイプにあわせて、Intel 製の CPU (VT-d) であれば intel_iommu=on を、AMD 製の CPU (AMD-Vi) であれば amd_iommu=on を使用してください。

再起動して、dmesg で IOMMU が有効になっていることを確認してください:

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

グループが正しいことを確認

以下のスクリプトを使うことで PCI デバイスが IOMMU グループにどのようにマッピングされたか確認できます。何も出力が返ってこない場合、IOMMU のサポートが有効になっていないかハードウェアが IOMMU をサポートしていないかのどちらかです。

#!/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;

出力の例:

IOMMU Group 0 00:00.0 Host bridge [0600]: Intel Corporation 2nd Generation Core Processor Family DRAM Controller [8086:0104] (rev 09)
IOMMU Group 1 00:16.0 Communication controller [0780]: Intel Corporation 6 Series/C200 Series Chipset Family MEI Controller #1 [8086:1c3a] (rev 04)
IOMMU Group 2 00:19.0 Ethernet controller [0200]: Intel Corporation 82579LM Gigabit Network Connection [8086:1502] (rev 04)
IOMMU Group 3 00:1a.0 USB controller [0c03]: Intel Corporation 6 Series/C200 Series Chipset Family USB Enhanced Host Controller #2 [8086:1c2d] (rev  
...

IOMMU グループは仮想マシンにパススルーすることができる一番小さい単位の物理デバイスのセットです。例えば、上記の例の場合、06:00.0 の GPU と 6:00.1 のオーディオコントローラは IOMMU グループ13に属しており、両方一緒にしかパススルーすることができません。フロントの USB コントローラは USB 拡張コントローラ (グループ10) やリアの USB コントローラ (グループ4) と分かれているグループ (グループ2) なので、他のデバイスに影響を与えないで仮想マシンにパススルーすることができます

注意事項

独立していない CPU ベースの PCIe スロットにゲスト GPU を接続した場合

全ての PCI-E スロットは同じではありません。ほとんどのマザーボードでは PCIe スロットには CPU 由来のものと PCH 由来のものがあります。CPU によっては、プロセッサ由来の PCIe スロットは隔離することができず、その場合 PCI スロットは接続されているデバイスと一緒にグループ化されてしまいます。

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)

上記のようにゲスト GPU しか含まれていない場合は問題ありません。他の PCIe スロットに接続した場合や CPU や PCH の配置によって、同じグループに他のデバイスが含まれる場合、そのデバイスも一緒にパススルーすることになります。仮想マシンにデバイスをパススルーしても問題ない場合は次に進んでください。そうでない場合、他の PCIe スロットに GPU を接続してみて他のデバイスと分離できないか試してみてください。もしくは ACS 上書きパッチをインストールする方法もありますが、こちらは欠点があります。

ノート: If they are grouped with other devices in this manner, pci root ports and bridges should neither be bound to vfio at boot, nor be added to the VM.

GPU の分離

GPU ドライバーは巨大で複雑なため、動的な再バインドはあまりサポートされておらず、ホストの GPU を透過的に仮想マシンにパススルーすることは通常できません。代わりのドライバーに GPU をバインドすることを推奨します。他のドライバーが GPU を使用できないようにして、仮想マシンが動作していないときは GPU は強制的に活動を停止します。2つの方法が存在しますが、使用しているカーネルがサポートしている場合は vfio-pci を使用することが推奨されます。

警告: Once you reboot after this procedure, whatever GPU you have configured will no longer be usable on the host until you reverse the manipulation. Make sure the GPU you intend to use on the host is properly configured before doing this.

vfio-pci を使う

Linux 4.1 から、カーネルには vfio-pci が含まれており、pci-stub と同じような機能を持ちながら、使用していないときはデバイスを D3 状態にするなどの機能が追加されています。あなたのシステムが vfio-pci をサポートしている場合、以下のコマンドを実行してみてください。エラーが返ってくる場合、代わりに pci-stub を使用する方法を使ってください。

$ 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 は基本的に PCI デバイスを ID で指定するため、パススルーしたいデバイスの ID を指定する必要があります。以下の IOMMU グループの場合、vfio-pci を 10de:13c210de:0fbb にバインドします。

IOMMU Group 13 06:00.0 VGA compatible controller: NVIDIA Corporation GM204 [GeForce GTX 970] [10de:13c2] (rev a1)
IOMMU Group 13 06:00.1 Audio device: NVIDIA Corporation GM204 High Definition Audio Controller [10de:0fbb] (rev a1)}}
ノート: ホスト GPU とゲスト GPU のベンダーとデバイス ID が同じ場合 (同じ型番の GPU を使っている場合)、ベンダーとデバイス ID を使ってデバイスを分離させることはできません。そのような場合はゲストとホストで同じ GPU を使うのセクションを読んでください。

ベンダーとデバイス ID を vfio-pci に渡されるデフォルトパラメータに追加します:

/etc/modprobe.d/vfio.conf
options vfio-pci ids=10de:13c2,10de:0fbb
ノート: こちらにあるように、PCI のルートポートが IOMMU グループに属している場合、ID を vfio-pci に指定してはいけません。ルートポートを機能させるにはホスト側に割り当てたままにする必要があります。グループ内の他のデバイスも vfio-pci にバインドされてしまいます。

上記の設定だけでは vfio-pci が他のグラフィックドライバーよりも前にロードされるとは限りません。必ずロードされるようにするには、mkinitpcio.conf の MODULES リストに追加してカーネルイメージの中で静的にバインドされるようにする必要があります:

ノート: 初期モードセッティングのために他のドライバー ("nouveau", "radeon", "amdgpu", "i915" など) をロードしている場合、以下の VFIO モジュールが先にロードされるようにしてください。
/etc/mkinitcpio.conf
MODULES="... vfio vfio_iommu_type1 vfio_pci vfio_virqfd ..."

さらに、mkinitcpio.conf の HOOKS リストに modconf フックを追加してください:

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

新しいモジュールを initramfs に追加したら、initramfs を再生成する必要があります。/etc/modprobe.d/vfio.conf でデバイスの ID を変更した場合も、initramfs を再生成してください。パラメータは起動の初期段階で initramfs で指定される必要があります。

# mkinitcpio -p linux
ノート: linux-vfio など非標準のカーネルを使っている場合、linux を適当なカーネルに置き換えてください。

再起動して vfio-pci が正しくロードされ適切なデバイスがバインドされていることを確認:

$ dmesg | grep -i vfio 
[    0.329224] VFIO - User Level meta-driver version: 0.3
[    0.341372] vfio_pci: add [10de:13c2[ffff:ffff]] class 0x000000/00000000
[    0.354704] vfio_pci: add [10de:0fbb[ffff:ffff]] class 0x000000/00000000
[    2.061326] vfio-pci 0000:06:00.0: enabling device (0100 -> 0103)

vfio.conf の全てのデバイスが dmesg に出力される必要はありません。起動時にデバイスが出力に現れなくてもゲスト VM から問題なく使うことができます。

$ lspci -nnk -d 10de:13c2
06:00.0 VGA compatible controller: NVIDIA Corporation GM204 [GeForce GTX 970] [10de:13c2] (rev a1)
	Kernel driver in use: vfio-pci
	Kernel modules: nouveau nvidia
$ lspci -nnk -d 10de:0fbb
06:00.1 Audio device: NVIDIA Corporation GM204 High Definition Audio Controller [10de:0fbb] (rev a1)
	Kernel driver in use: vfio-pci
	Kernel modules: snd_hda_intel

pci-stub を使う (古い方法, 4.1 カーネル以前)

使用しているカーネルが vfio-pci をサポートしていない場合、代わりに pci-stub モジュールを使います。

pci-stub は基本的に PCI デバイスを ID で指定するため、パススルーしたいデバイスの ID を指定する必要があります。以下の IOMMU グループの場合、pci-stub を 10de:13c210de:0fbb にバインドします:

IOMMU group 13 06:00.0 VGA compatible controller: NVIDIA Corporation GM204 [GeForce GTX 970] [10de:13c2] (rev a1)
IOMMU group 13 06:00.1 Audio device: NVIDIA Corporation GM204 High Definition Audio Controller [10de:0fbb] (rev a1)}}
ノート: ホスト GPU とゲスト GPU のベンダーとデバイス ID が同じ場合 (同じ型番の GPU を使っている場合)、ベンダーとデバイス ID を使ってデバイスを分離させることはできません。そのような場合はゲストとホストで同じ GPU を使うのセクションを読んでください。

(Arch Linux を含む) ほとんどの Linux ディストリはカーネルイメージの中に静的に pci-stub を組み込んでいます。何らかの理由でモジュールとしてロードしなければならない場合、ディストリが提供しているツールを使用してバインドする必要があります。Arch の場合は mkinitpcio です:

/etc/mkinitcpio.conf
MODULES="... pci-stub ..."

手動でカーネルイメージにモジュールを追加する必要がある場合、initramfs を再生成してください:

# mkinitcpio -p linux
ノート: linux-vfio など非標準のカーネルを使っている場合、linux を使用しているカーネルに置き換えてください。

カーネルコマンドラインにパススルーする PCI デバイスの ID を追加:

/etc/mkinitcpio.conf
...
GRUB_CMDLINE_LINUX_DEFAULT="... pci-stub.ids=10de:13c2,10de:0fbb ..."
...
ノート: こちらにあるように、PCI のルートポートが IOMMU グループに属している場合、ID を pci-stub に指定してはいけません。ルートポートを機能させるにはホスト側に割り当てたままにする必要があります。グループ内の他のデバイスも pci-stub にバインドされてしまいます。

GRUB の設定をリロード:

# grub-mkconfig -o /boot/grub/grub.cfg

デバイスが pci-stub に割り当てられたことを dmesg の出力で確認:

dmesg | grep pci-stub
[    2.390128] pci-stub: add 10DE:13C2 sub=FFFFFFFF:FFFFFFFF cls=00000000/00000000
[    2.390143] pci-stub 0000:06:00.0: claimed by stub
[    2.390150] pci-stub: add 10DE:0FBB sub=FFFFFFFF:FFFFFFFF cls=00000000/00000000
[    2.390159] pci-stub 0000:06:00.1: claimed by stub

OVMF によるゲスト VM のセットアップ

OVMF は QEMU 仮想マシン用のオープンソース UEFI ファームウェアです。SeaBIOS を使うことでも PCI パススルーと同じような結果を得ることはできますが、セットアップ手順が異なります。一般的にはハードウェアがサポートしているのであれば EFI を使用する方法を推奨します。

libvirt の設定

libvirt は様々な仮想化ユーティリティのラッパーであり、仮想マシンの設定とデプロイを簡単にします。KVM と QEMU の場合、フロントエンドを使用することで QEMU 用にパーミッションを設定する必要がなくなり簡単に様々なデバイスを仮想マシンに追加・削除できます。ラッパーと名乗ってはいますが、QEMU の最新機能全てをサポートしているわけではありません。QEMU の引数を追加するためにラッパースクリプトを使用する必要がある場合もあります。

qemu, libvirt, ovmf-gitAUR, virt-manager をインストールしてから、OVMF ファームウェアイメージとランタイム変数テンプレートのパスを libvirt の設定に追加して、virt-installvirt-manager が認識できるようにしてください:

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

そして libvirtd とログ出力コンポーネントを起動・有効化します:

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

ゲスト OS のセットアップ

The process of setting up a VM using virt-manager is mostly self explainatory, as most of the process comes with fairly comprehensive on-screen instructions. However, you should pay special attention to the following steps :

  • When the VM creation wizard asks you to name your VM, check the "Customize before install" checkbox.
  • In the "Overview" section, set your firmware to "UEFI". If the option is grayed out, make sure that you have correctly specified the location of your firmware in /etc/libvirt/qemu.conf and restart libvirtd.service.
  • In the "CPUs" section, change your CPU model to "host-passthrough". If it is not in the list, you will have to type it by hand. This will ensure that your CPU is detected properly, since it causes libvirt to expose your CPU capabilities exactly as they are instead of only those it recognizes (which is the preferred default behavior to make CPU behavior easier to reproduce). Without it, some applications may complain about your CPU being of an unknown model.
  • If you want to minimize IO overhead, go into "Add Hardware" and add a Controller for SCSI drives of the "VirtIO SCSI" model. You can then change the default IDE disk for a SCSI disk, which will bind to said controller.
    • Windows VMs will not recognize those drives by default, so you need to download the ISO containing the drivers from here and add an IDE (or SATA for Windows 8.1 and newer) CD-ROM storage device linking to said ISO, otherwise you will not be able to get Windows to recognize it during the installation process. When prompted to select a disk to install windows on, load the drivers contained on the CD-ROM under vioscsi.

The rest of the installation process will take place as normal using a standard QXL video adapter running in a window. At this point, there is no need to install additional drivers for the rest of the virtual devices, since most of them will be removed later on. Once the guest OS is done installing, simply turn off the virtual machine.

PCI デバイスの接続

With the installation done, it's now possible to edit the hardware details in libvirt and remove virtual integration devices, such as the spice channel and virtual display, the QXL video adapter, the emulated mouse and keyboard and the USB tablet device. Since that leaves you with no input devices, you may want to bind a few USB host devices to your VM as well, but remember to leave at least one mouse and/or keyboard assigned to your host in case something goes wrong with the guest. At this point, it also becomes possible to attach the PCI device that was isolated earlier; simply click on "Add Hardware" and select the PCI Host Devices you want to passthrough. If everything went well, the screen plugged into your GPU should show the OVMF splash screen and your VM should start up normally. From there, you can setup the drivers for the rest of your VM.

注意事項

OVMF ベースの VM で非 EFI イメージを使う

The OVMF firmware does not support booting off non-EFI mediums. If the installation process drops you in a UEFI shell right after booting, you may have an invalid EFI boot media. Try using an alternate linux/windows image to determine if you have an invalid media.

パフォーマンスチューニング

Most use cases for PCI passthroughs relate to performance-intensive domains such as video games and GPU-accelerated tasks. While a PCI passthrough on its own is a step towards reaching native performance, there are still a few ajustments on the host and guest to get the most out of your VM.

CPU ピニング

The default behavior for KVM guests is to run operations coming from the guest as a number of threads representing virtual processors. Those threads are managed by the Linux scheduler like any other thread and are dispatched to any available CPU cores based on niceness and priority queues. Since switching between threads adds a bit of overhead (because context switching forces the core to change its cache between operations), this can noticeably harm performance on the guest. CPU pinning aims to resolve this as it overrides process scheduling and ensures that the VM threads will always run and only run on those specific cores. Here, for instance, the guest cores 0, 1, 2 and 3 are mapped to the host cores 5, 6, 7 and 8 respectively.

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>
...

ハイパースレッディングの場合

If your CPU supports hardware multitasking, also known as Hyper-threading on Intel chips, there are two ways you can go with your CPU pinning. That is, Hyper-threading is simply a very efficient way of running two threads on one CPU at any given time, so while it may give you 8 logical cores on what would otherwise be a quad-core CPU, if the physical core is overloaded, the logical core won't be of any use. One could pin their VM threads on 2 physical cores and their 2 respective threads, but any task overloading those two cores won't be helped by the extra two logical cores, since in the end you're only passing through two cores out of four, not four out of eight. What you should do knowing this depends on what you intend to do with your host while your VM is running.

This is the abridged content of /proc/cpuinfo on a quad-core machine with hyper-threading.

$ 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

If you don't intend to be doing any computation-heavy work on the host (or even anything at all) at the same time as you would on the VM, it would probably be better to pin your VM threads across all of your logical cores, so that the VM can fully take advantage of the spare CPU time on all your cores.

On the quad-core machine mentioned above, it would look like this :

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>
...

If you would instead prefer to have the host and guest running intensive tasks at the same time, it would then be preferable to pin a limited amount of physical cores and their respective threads on the guest and leave the rest to the host to avoid the two competing for CPU time.

On the quad-core machine mentioned above, it would look like this :

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>
...

静的ヒュージページ

When dealing with applications that require large amounts of memory, memory latency can become a problem since the more memory pages are being used, the more likely it is that this application will attempt to access information accross multiple memory "pages", which is the base unit for memory allocation. Resolving the actual address of the memory page takes multiple steps, and so CPUs normally cache information on recently used memory pages to make subsequent uses on the same pages faster. Applications using large amounts of memory run into a problem where, for instance, a virtual machine uses 4GB of memory divided into 4kB pages (which is the default size for normal pages), meaning that such cache misses can become extremely frequent and greatly increase memory latency. Huge pages exist to mitigate this issue by giving larger individual pages to those applications, increasing the odds that multiple operations will target the same page in succession. This is normally handeled with transparent huge pages, which dynamically manages hugepages to keep up with the demand.

On a VM with a PCI passthrough, however, it is not possible to benefit from transparent huge pages, as IOMMU requires that the guest's memory be allocated and pinned as soon as the VM starts. It is therefore required to allocate huge pages statically in order to benefit from them.

警告: Do note that static huge pages lock down the allocated amount of memory, making it unavailable for applications that are not configured to use them. Allocating 4GBs worth of huge pages on a machine with 8GBs of memory will only leave you with 4GBs of available memory on the host even when the VM is not running.

To allocate huge pages at boot, one must simply specify the desired amount on their kernel comand line with hugepages=x. For instance, reserving 1024 pages with hugepages=1024 and the default size of 2048kB per huge page creates 2GBs worth of memory for the virtual machine to use.

Also, since static huge pages can only be used by applications that specifically request it, you must add this section in your libvirt domain configuration to allow kvm to benefit from them :

EDITOR=nano virsh edit myPciPassthroughVm
...
<memoryBacking>
	<hugepages/>
</memoryBacking>
...

CPU 周波数ガバナー

CPU ガバナーの設定によっては、仮想マシンのスレッドによって周波数が引き上がる閾値まで CPU の負担が達しないことがあります。KVM が自力で CPU の周波数を変更することはできないため、CPU の使用率が思うように上がらないとパフォーマンスが出ないという問題になる可能性があります。ゲスト側で CPU 負担が重い作業を実行している間に watch lscpu によって報告される周波数に変化があるかどうか確認してみてください。周波数が最大値まで上がらない場合、CPU スケーリングがホスト OS によって制御されている ことが原因かもしれません。その場合、全てのコアを最大周波数に設定してみてパフォーマンスが改善しないか確認してください。最新の Intel 製チップをデフォルトの P-State ドライバーで使用している場合、cpupower コマンドは効果がないため、/proc/cpuinfo を監視して CPU が最大周波数になっていることを確認してください。

特殊な構成

Certain setups require specific configuration tweaks in order to work properly. If you're having problems getting your host or your VM to work properly, see if your system matches one of the cases below and try adjusting your configuration accordingly.

ゲストとホストで同じ GPU を使う

Due to how both pci-stub and vfio-pci use your vendor and device id pair to identify which device they need to bind to at boot, if you have two GPUs sharing such an ID pair you won't be able to get your passthough driver to bind with just one of them. This sort of setup makes it necessary to use a script, so that whichever driver you're using is instead assigned by pci bus address using the driver_override mechanism.

Here, we will make a script to bind vfio-pci to all GPUs but the boot gpu. Create the script "/sbin/vfio-pci-override.sh":

#!/bin/sh

for i in /sys/devices/pci*/*/boot_vga; do
        if [ $(cat "$i") -eq 0 ]; then
                GPU="${i%/boot_vga}"
                AUDIO="$(echo "$GPU" | sed -e "s/0$/1/")"
                echo "vfio-pci" > "$GPU/driver_override"
                if [ -d "$AUDIO" ]; then
                        echo "vfio-pci" > "$AUDIO/driver_override"
                fi
        fi
done

modprobe -i vfio-pci

/etc/modprobe.d/vfio.conf を以下の内容で作成:

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

/etc/mkinitcpio.conf を編集:

Remove any video drivers from MODULES, and add vfio-pci, and vfio_iommu_type1

MODULES="ext4 vfat vfio-pci vfio_iommu_type1"

Add "/etc/modprobe.d/vfio.conf" and "/sbin/vfio-pci-override.sh" to FILES:

FILES="/etc/modprobe.d/vfio.conf /sbin/vfio-pci-override.sh"

Regenerate your initramfs, and reboot:

# mkinitcpio -p linux

ブート GPU をゲストにパススルー

The GPU marked as boot_vga is a special case when it comes to doing PCI passthroughs, since the BIOS needs to use it in order to display things like boot messages or the BIOS configuration menu. To do that, it makes a copy of the VGA boot ROM which can then be freely modified. This modified copy is the version the system gets to see, which the passthrough driver may reject as invalid. As such, it is generally reccomanded to change the boot GPU in the BIOS configuration so the host GPU is used instead or, if that's not possible, to swap the host and guest cards in the machine itself.

IOMMU グループのバイパス (ACS 上書きパッチ)

パススルーしたくない PCI デバイスもグループに入ってしまっている場合、Alex Williamson の ACS override パッチを使うことでデバイスを分離できます。その場合は 危険性 を承知してください。

パッチが適用されたカーネルが必要になります。linux-vfioAUR パッケージでカーネルをインストールするのが一番簡単です。

さらに、ACS override パッチはカーネルのコマンドラインオプションで有効にしなければなりません。パッチファイルは以下のドキュメントを追加します:

       pcie_acs_override =
               [PCIE] Override missing PCIe ACS support for:
           downstream
               All downstream ports - full ACS capabilties
           multifunction
               All multifunction devices - multifunction ACS subset
           id:nnnn:nnnn
               Specfic device - full ACS capabilities
               Specified as vid:did (vendor/device ID) in hex

通常は pcie_acs_override=downstream オプションで上手くいきます。

インストールと設定が終わったら、ブートローダーのカーネルパラメータを再設定して pcie_acs_override= オプションが有効になった状態で新しいカーネルをロードするようにしてください。

libvirtd を使わない (CLI ベースの) QEMU の例 (再起動せずに GPU を切り替え可能)

This script starts Samba and Synergy, runs the VM and closes everything after the VM is shut down. Note that this method does not require libvirtd to be running or configured.

Since this was posted, the author continued working on scripts to ease the workflow of switching GPUs. All of said scripts can be found on the author's GitLab instance: https://git.mel.vin/melvin/scripts/tree/master/qemu.

With these new scripts, is it possible to switch GPUs without rebooting, only a restart of the X session is needed. This is all handled by a tiny shell script that runs in the tty. When you log in the tty, it will ask which card you would like to use if you autolaunch the shell script.

vfio-users : Full set of (runtime) scripts for VFIO + Qemu CLI

vfio-users : Example configuration with CLI Qemu (working VM => host audio)

The script below is the main QEMU launcher as of 2016-05-16, all other scripts can be found in the repo.

slightly edited from "windows.sh" 2016-05-16 : https://git.mel.vin/melvin/scripts/tree/master/qemu
#!/bin/bash

if [[ $EUID -ne 0 ]]
then
	echo "This script must be run as root"
	exit 1
fi

echo "Starting Samba"
systemctl start smbd.service
systemctl start nmbd.service

echo "Starting VM"
export QEMU_AUDIO_DRV="pa"
qemu-system-x86_64 \
	-serial none \
	-parallel none \
	-nodefaults \
	-nodefconfig \
	-no-user-config \
	-enable-kvm \
	-name Windows \
	-cpu host,kvm=off,hv_vapic,hv_time,hv_relaxed,hv_spinlocks=0x1fff,hv_vendor_id=sugoidesu \
	-smp sockets=1,cores=4,threads=1 \
	-m 8192 \
	-mem-path /dev/hugepages \
	-mem-prealloc \
	-soundhw hda \
	-device ich9-usb-uhci3,id=uhci \
	-device usb-ehci,id=ehci \
	-device nec-usb-xhci,id=xhci \
	-machine pc,accel=kvm,kernel_irqchip=on,mem-merge=off \
	-drive if=pflash,format=raw,file=./Windows_ovmf_x64.bin \
	-rtc base=localtime,clock=host,driftfix=none \
	-boot order=c \
	-net nic,vlan=0,macaddr=52:54:00:00:00:01,model=virtio,name=net0 \
	-net bridge,vlan=0,name=bridge0,br=br0 \
	-drive if=virtio,id=drive0,file=./Windows.img,format=raw,cache=none,aio=native \
	-nographic \
	-device vfio-pci,host=04:00.0,addr=09.0,multifunction=on \
	-device vfio-pci,host=04:00.1,addr=09.1 \
	-usbdevice host:046d:c29b `# Logitech G27` &

#	-usbdevice host:054c:05c4 `# Sony DualShock 4` \
#	-usbdevice host:28de:1142 `# Steam Controller` \

sleep 5

while [[ $(pgrep -x -u root qemu-system-x86) ]]
do
	if [[ ! $(pgrep -x -u REGULAR_USER synergys) ]]
	then
		echo "Starting Synergy server"
		sudo -u REGULAR_USER /usr/bin/synergys --debug ERROR --no-daemon --enable-crypto --config /etc/synergy.conf &
	fi

	sleep 5
done

echo "VM stopped"

echo "Stopping Synergy server"
pkill -u REGULAR_USER synergys

echo "Stopping Samba"
systemctl stop smbd.service
systemctl stop nmbd.service

exit 0

QEMU と libvirtd を使用する例

<domain type='kvm' xmlns:qemu='http://libvirt.org/schemas/domain/qemu/1.0'>
  <name>win7</name>
  <uuid>a3bf6450-d26b-4815-b564-b1c9b098a740</uuid>
  <memory unit='KiB'>8388608</memory>
  <currentMemory unit='KiB'>8388608</currentMemory>
  <vcpu placement='static'>8</vcpu>
  <os>
    <type arch='x86_64' machine='pc-i440fx-2.4'>hvm</type>
    <boot dev='hd'/>
    <bootmenu enable='yes'/>
  </os>
  <features>
    <acpi/>
    <kvm>
      <hidden state='on'/>
    </kvm>
  </features>
  <cpu mode='host-passthrough'>
    <topology sockets='1' cores='8' threads='1'/>
  </cpu>
  <clock offset='utc'/>
  <on_poweroff>destroy</on_poweroff>
  <on_reboot>restart</on_reboot>
  <on_crash>destroy</on_crash>
  <devices>
    <emulator>/usr/sbin/qemu-system-x86_64</emulator>
    <disk type='block' device='disk'>
      <driver name='qemu' type='raw' cache='none' io='native'/>
      <source dev='/dev/rootvg/win7'/>
      <target dev='vda' bus='virtio'/>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x04' function='0x0'/>
    </disk>
    <disk type='block' device='disk'>
      <driver name='qemu' type='raw' cache='none' io='native'/>
      <source dev='/dev/rootvg/windane'/>
      <target dev='vdb' bus='virtio'/>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x07' function='0x0'/>
    </disk>
    <disk type='block' device='cdrom'>
      <driver name='qemu' type='raw' cache='none' io='native'/>
      <target dev='hdb' bus='ide'/>
      <readonly/>
      <address type='drive' controller='0' bus='0' target='0' unit='1'/>
    </disk>
    <controller type='usb' index='0'>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x2'/>
    </controller>
    <controller type='pci' index='0' model='pci-root'/>
    <controller type='ide' index='0'>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x1'/>
    </controller>
    <controller type='sata' index='0'>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/>
    </controller>
    <interface type='network'>
      <mac address='52:54:00:fa:59:92'/>
      <source network='default'/>
      <model type='rtl8139'/>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x05' function='0x0'/>
    </interface>
    <input type='mouse' bus='ps2'/>
    <input type='keyboard' bus='ps2'/>
    <sound model='ac97'>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x0'/>
    </sound>
    <memballoon model='virtio'>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x06' function='0x0'/>
    </memballoon>
  </devices>
  <qemu:commandline>
    <qemu:arg value='-device'/>
    <qemu:arg value='vfio-pci,host=02:00.0,multifunction=on,x-vga=on'/>
    <qemu:arg value='-device'/>
    <qemu:arg value='vfio-pci,host=02:00.1'/>
    <qemu:env name='QEMU_PA_SAMPLES' value='1024'/>
    <qemu:env name='QEMU_AUDIO_DRV' value='pa'/>
    <qemu:env name='QEMU_PA_SERVER' value='/run/user/1000/pulse/native'/>
  </qemu:commandline>
</domain>

トラブルシューティング

Windows の仮想マシンに NVIDIA の GPU をパススルーした場合に "Error 43 : Driver failed to load"

ノート: This may also fix SYSTEM_THREAD_EXCEPTION_NOT_HANDLED boot crashes related to Nvidia drivers

Since version 337.88, Nvidia drivers on Windows check if an hypervisor is running and fail if it detects one, which results in an Error 43 in the Windows device manager. Starting with QEMU 2.5.0 and libvirt 1.3.3, the vendor_id for the hypervisor can be spoofed, which is enough to fool the Nvidia drivers into loading anyway. All one must do is add hv_vendor_id=whatever to the cpu parameters in their QEMU command line, or by adding the following line to their libvirt domain configuration. It may help for the ID to be set to a 12-character alphanumeric (e.g. '123456789ab') as opposed to longer or shorter strings.

EDITOR=nano virsh edit myPciPassthroughVm
...

<features>
	<hyperv>
		...
		<vendor_id state='on' value='whatever'/>
		...
	</hyperv>
	...
	<kvm>
	<hidden state='on'/>
	</kvm>
</features>
...

Users with older versions of QEMU and/or libvirt will instead have to disable a few hypervisor extensions, which can degrade performance substentially. If this is what you want to do, do the following replacement in your libvirt domain config file.

EDITOR=nano virsh edit myPciPassthroughVm
...
<features>
	<hyperv>
		<relaxed state='on'/>
		<vapic state='on'/>
		<spinlocks state='on' retries='8191'/>
	</hyperv>
	...
</features>
...
<clock offset='localtime'>
	<timer name='hypervclock' present='yes'/>
</clock>
...
...

<clock offset='localtime'>
	<timer name='hypervclock' present='no'/>
</clock>
...
<features>
	<kvm>
	<hidden state='on'/>
	</kvm>
	...
	<hyperv>
		<relaxed state='off'/>
		<vapic state='off'/>
		<spinlocks state='off'/>
	</hyperv>
	...
</features>
...

CPU 例外によってクラッシュが発生する

GeForce Experience からサポートされていない CPU が存在するとエラーが吐かれて、ゲームの最適化などの機能が機能しない場合、KVM モジュールに ignore_msrs=1 オプションを指定して実装されていない MSR へのアクセスを無視することで問題は解決します:

/etc/modprobe.d/kvm.conf
...
options kvm ignore_msrs=1
...
警告: 未知の MSR のアクセスを無視すると、VM 内の他のソフトウェアや他の VM が動作しなくなる可能性があります。

Windows の仮想マシンを起動したときに "System Thread Exception Not Handled"

Windows 8 or Windows 10 guests may raise a generic compatibility exception at boot, namely "System Thread Exception Not Handled", which tends to be caused by legacy drivers acting strangely on real machines. On KVM machines this issue can generally be solved by setting the CPU model to core2duo.

ビデオカードの HDMI 出力からの音声がおかしい

For some users VM's audio slows down/starts stuttering/becomes demonic after a while when it's pumped through HDMI on the video card. This usually also slows down graphics. A possible solution consists of enabling MSI (Message Signaled-Based Interrupts) instead of the default (Line-Based Interrupts).

In order to check whether MSI is supported or enabled, run the following command as root:

# lspci -vs $device | grep 'MSI:'

where `$device` is the card's address (e.g. `01:00.0`).

The output should be similar to:

Capabilities: [60] MSI: Enable- Count=1/1 Maskable- 64bit+

A - after Enabled means MSI is supported, but not used by the VM, while a + says that the VM is using it.

The procedure to enable it is quite complex, instructions and an overview of the setting can be found here.

Other hints can be found on the lime-technology's wiki, or on this article on VFIO tips and tricks.

Some tools named MSI_util or similar are available on the Internet, but they didn't work for me on Windows 10 64bit.

In order to fix the issues enabling MSI on the 0 function of my nVidia card (01:00.0 VGA compatible controller: NVIDIA Corporation GM206 [GeForce GTX 960] (rev a1) (prog-if 00 [VGA controller])) was not enough; I also enabled it on the other function (01:00.1 Audio device: NVIDIA Corporation Device 0fba (rev a1)) and that seems to have fixed the issue.

他のデバイスのパススルー

USB コントローラ

If your motherboard has multiple USB controllers mapped to multiple groups, it is possible to pass those instead of USB devices. Passing an actual controller over an individual USB device provides the following advantages :

  • If a device disconnects or changes ID over the course of an given operation (such as a phone undergoing an update), the VM will not suddenly stop seeing it.
  • Any USB port managed by this controller is directly handled by the VM and can have its devices unplugged, replugged and changed without having to notify the hypervisor.
  • Libvirt will not complain if one of the USB devices you usually pass to the guest is missing when starting the VM.

Unlike with GPUs, drivers for most USB controllers do not require any specific configuration to work on a VM and control can normally be passed back and forth between the host and guest systems with no side effects.

警告: Make sure your USB controller supports resetting :#Passing through a device that does not support resetting

You can find out which PCI devices correspond to which controller and how various ports and devices are assigned to each one of them using this command :

$ for usb_ctrl in $(find /sys/bus/usb/devices/usb* -maxdepth 0 -type l); do pci_path="$(dirname "$(realpath "${usb_ctrl}")")"; echo "Bus $(cat "${usb_ctrl}/busnum") --> $(basename $pci_path) (IOMMU group $(basename $(realpath $pci_path/iommu_group)))"; lsusb -s "$(cat "${usb_ctrl}/busnum"):"; echo; done
Bus 1 --> 0000:00:1a.0 (IOMMU group 4)
Bus 001 Device 004: ID 04f2:b217 Chicony Electronics Co., Ltd Lenovo Integrated Camera (0.3MP)
Bus 001 Device 007: ID 0a5c:21e6 Broadcom Corp. BCM20702 Bluetooth 4.0 [ThinkPad]
Bus 001 Device 008: ID 0781:5530 SanDisk Corp. Cruzer
Bus 001 Device 002: ID 8087:0024 Intel Corp. Integrated Rate Matching Hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

Bus 2 --> 0000:00:1d.0 (IOMMU group 9)
Bus 002 Device 006: ID 0451:e012 Texas Instruments, Inc. TI-Nspire Calculator
Bus 002 Device 002: ID 8087:0024 Intel Corp. Integrated Rate Matching Hub
Bus 002 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

This laptop has 3 USB ports managed by 2 USB controllers, each with their own IOMMU group. In this example, Bus 001 manages a single USB port (with a SanDisk USB pendrive plugged into it so it appears on the list), but also a number of internal devices, such as the internal webcam and the bluetooth card. Bus 002, on the other hand, does not apprear to manage anything except for the calculator that is plugged into it. The third port is empty, which is why it does not show up on the list, but is actually managed by Bus 002.

Once you have identified which controller manages which ports by plugging various devices into them and decided which one you want to passthrough, simply add it to the list of PCI host devices controlled by the VM in your guest configuration. No other configuration should be needed.

注意事項

リセットに対応していないデバイスのパススルー

When the VM shuts down, all devices used by the guest are deinitialized by its OS in preparation for shutdown. In this state, those devices are no longer functionnal and must then be power-cycled before they can resume normal operation. Linux can handle this power-cycling on its own, but when a device has no known reset methods, it remains in this disabled state and becomes unavailable. Since Libvirt and Qemu both expect all host PCI devices to be ready to reattach to the host before completely stopping the VM, when encountering a device that won't reset, they will hang in a "Shutting down" state where they will not be able to be restarted until the host system has been rebooted. It is therefore reccomanded to only pass through PCI devices which the kernel is able to reset, as evidenced by the presence of a reset file in the PCI device sysfs node, such as /sys/bus/pci/devices/0000:00:1a.0/reset.

The following bash command shows which devices can and cannot be reset.

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
IOMMU group 0
	00:00.0 Host bridge [0600]: Intel Corporation Xeon E3-1200 v2/Ivy Bridge DRAM Controller [8086:0158] (rev 09)
IOMMU group 1
	00:01.0 PCI bridge [0604]: Intel Corporation Xeon E3-1200 v2/3rd Gen Core processor PCI Express Root Port [8086:0151] (rev 09)
	01:00.0 VGA compatible controller [0300]: NVIDIA Corporation GK208 [GeForce GT 720] [10de:1288] (rev a1)
	01:00.1 Audio device [0403]: NVIDIA Corporation GK208 HDMI/DP Audio Controller [10de:0e0f] (rev a1)
IOMMU group 2
	00:14.0 USB controller [0c03]: Intel Corporation 7 Series/C210 Series Chipset Family USB xHCI Host Controller [8086:1e31] (rev 04)
IOMMU group 4
[RESET]	00:1a.0 USB controller [0c03]: Intel Corporation 7 Series/C210 Series Chipset Family USB Enhanced Host Controller #2 [8086:1e2d] (rev 04)
IOMMU group 5
[RESET]	00:1b.0 Audio device [0403]: Intel Corporation 7 Series/C210 Series Chipset Family High Definition Audio Controller [8086:1e20] (rev 04)
IOMMU group 10
[RESET]	00:1d.0 USB controller [0c03]: Intel Corporation 7 Series/C210 Series Chipset Family USB Enhanced Host Controller #1 [8086:1e26] (rev 04)
IOMMU group 13
	06:00.0 VGA compatible controller [0300]: NVIDIA Corporation GM204 [GeForce GTX 970] [10de:13c2] (rev a1)
	06:00.1 Audio device [0403]: NVIDIA Corporation GM204 High Definition Audio Controller [10de:0fbb] (rev a1)

This signals that the xHCI USB controller in 00:14.0 cannot be reset and will therefore stop the VM from shutting down properly, while the integrated sound card in 00:1b.0 and the other two controllers in 00:1a.0 and 00:1d.0 do not share this problem and can be passed without issue.

参照