OVMF による PCI パススルー
Open Virtual Machine Firmware (OVMF) は仮想マシンで UEFI を使えるようにするプロジェクトです。Linux 3.9 以上と新しいバージョンの QEMU では、グラフィックカードをパススルーすることが可能で、仮想マシンでネイティブと同じグラフィック性能を発揮することができます。
デスクトップコンピュータに使用していない GPU が接続されている場合 (内蔵 GPU や古い OEM カードでもかまいません、ブランドが一致している必要はありません)、ハードウェアがサポートしていれば (#要件を参照)、あらゆる OS の仮想マシンで専用 GPU として(ほぼ)最大限の性能を活用できます。技術的な詳細は こちらのプレゼンテーション (pdf) を見てください。
目次
要件
VGA パススルーでは最先端の技術を使っているため、あなたのハードウェアでは使用できない可能性があります。パススルーを行うには以下の要件が満たされていなければなりません:
- CPU がハードウェア仮想化 (KVM) と IOMMU (パススルー) をサポートしていること。
- マザーボードが IOMMU をサポートしていること。
- チップセットと BIOS の両方が対応している必要があります。対応しているかどうかすぐに判断することはできませんが、Xen wiki の対応ハードウェアリスト や Wikipedia の対応リストが存在します。
- ゲスト GPU ROM が UEFI をサポートしていること。
- このリストに載っている ROM に使用している GPU が存在し UEFI をサポートしていると書かれていれば、問題ありません。2012年以降の GPU はサポートされているはずです。Windows 8 との互換性があると売り出すには UEFI のサポートが要件だと Microsoft が決めたためです。
使用していないモニターやマウス、キーボードがあれば、それも仮想マシンに割り当てることができます (GPU はディスプレイが接続されていないと何も出力することができず Spice 接続では性能が上がりません)。何か問題が発生した場合でも、スペアの機材があればホストマシンは制御できます。
IOMMU のセットアップ
IOMMU はシステム固有の IO マッピング機構でほとんどのデバイスで使用することができます。IOMMU は Intel の Intel VT-x と 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 上書きパッチをインストールする方法もありますが、こちらは欠点があります。詳しくは #IOMMU グループのバイパス (ACS 上書きパッチ) を参照してください。
GPU の分離
GPU ドライバーは巨大で複雑なため、動的な再バインドはあまりサポートされておらず、ホストの GPU を透過的に仮想マシンにパススルーすることは通常できません。代わりのドライバーに GPU をバインドすることを推奨します。他のドライバーが GPU を使用できないようにして、仮想マシンが動作していないときは GPU は強制的に活動を停止します。2つの方法が存在しますが、使用しているカーネルがサポートしている場合は vfio-pci を使用することが推奨されます。
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:13c2
と 10de: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)}}
ベンダーとデバイス ID を vfio-pci に渡されるデフォルトパラメータに追加します:
/etc/modprobe.d/vfio.conf
options vfio-pci ids=10de:13c2,10de:0fbb
上記の設定だけでは vfio-pci が他のグラフィックドライバーよりも前にロードされるとは限りません。必ずロードされるようにするには、カーネルイメージの中で静的にバインドされるようにする必要があります。vfio
, vfio_iommu_type1
, vfio_pci
, vfio_virqfd
を mkinitcpio にこの順番で追加してください:
/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
再起動して 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:13c2
と 10de: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)}}
(Arch Linux を含む) ほとんどの Linux ディストリはカーネルイメージの中に静的に pci-stub を組み込んでいます。何らかの理由でモジュールとしてロードしなければならない場合、ディストリが提供しているツールを使用してバインドする必要があります。Arch の場合は mkinitpcio
です:
/etc/mkinitcpio.conf
MODULES="... pci-stub ..."
手動でカーネルイメージにモジュールを追加する必要がある場合、initramfs を再生成してください:
# mkinitcpio -p linux
pci-stubs.ids
カーネルパラメータにパススルーする PCI デバイスの ID を追加してください (例: pci-stub.ids=10de:13c2,10de:0fbb
)。
デバイスが 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-install
や virt-manager
が認識できるようにしてください:
/etc/libvirt/qemu.conf
nvram = [ "/usr/share/ovmf/ovmf_code_x64.bin:/usr/share/ovmf/ovmf_vars_x64.bin" ]
そして libvirtd
とログ出力コンポーネントを起動・有効化します:
# systemctl enable --now libvirtd # systemctl enable virtlogd.socket
ゲスト OS のセットアップ
virt-manager
による仮想マシンの設定は画面上の指示に従うだけで完了します。ただし、以下のステップでは特別な注意を払う必要があります:
- 仮想マシンの作成ウィザードで VM の名前を付けるとき、"Customize before install" チェックボックスにチェックを入れてください。
- "Overview" セクションで、ファームウェアを "UEFI" に設定してください。オプションがグレーになっている場合、
/etc/libvirt/qemu.conf
でファームウェアの場所が正しく指定されているか確認してください。修正した後libvirtd.service
を再起動してください。 - "CPUs" セクションで、CPU モデルを "host-passthrough" に変更してください。リストに存在しない場合、手動で入力してください。これで libvirt が CPU の機能を実際の CPU と同じように反映するようになって CPU が正しく認識されるようになります。変更しないと、一部のアプリケーションで CPU のモデルが不明だとエラーが発生します。
- IO のオーバーヘッドを抑えたい場合、"Add Hardware" から "VirtIO SCSI" モデルの SCSI ドライブのコントローラを追加してください。それからデフォルトの IDE ディスクを SCSI ディスクに変更して作成したコントローラーにバインドしてください。
- Windows の仮想マシンはデフォルトでは SCSI ドライブを認識しないため、こちら からドライバーが含まれている ISO をダウンロードして IDE (あるいは Windows 8.1 以上の場合は SATA) の CD-ROM ストレージデバイスを作成してダウンロードした ISO にリンクしてください。この設定を行わないとインストール時に Windows がディスクを認識できません。Windows をインストールするディスクを選択するときは、vioscsi 下の CD-ROM に含まれているドライバーをロードしてください。
他のインストールは通常と同じです。標準の QXL ビデオアダプタをウィンドウで実行します。現時点では、仮想デバイスのためのドライバーをインストールする必要はありません。ほとんどが後で削除されるためです。ゲスト OS のインストールが完了したら、仮想マシンをオフにしてください。
PCI デバイスの接続
インストールが完了したら、libvirt でハードウェアの詳細情報を編集して spice チャンネルや仮想ディスプレイ、QXL ビデオアダプタ、マウスやキーボード、USB タブレットデバイスのエミュレートなどの仮想デバイスを削除できます。入力デバイスがなくなるので、仮想マシンに USB ホストデバイスをバインドする場合、ゲストに何かあったときは最低でもひとつのマウスやキーボードをホストに割り当ててください。ここで、先に分離しておいた PCI デバイスを接続することができます。"Add Hardware" をクリックしてパススルーしたい PCI Host Device を選択してください。問題がなければ、GPU に接続されたディスプレイが OVMF のスプラッシュ画面を表示して通常通りに VM が起動します。そこから VM のドライバーの設定をおこなってください。
注意事項
OVMF ベースの VM で非 EFI イメージを使う
OVMF ファームウェアは EFI 以外のメディアの起動をサポートしていません。起動後に UEFI シェルが開かれてしまう場合、EFI ブートメディアに問題がある可能性があります。他の linux/windows イメージを使ってみてイメージに問題がないか確認してください。
パフォーマンスチューニング
PCI パススルーを使うのはビデオゲームや GPU を使用する作業など大抵パフォーマンスが重要な場合です。PCI パススルーによってネイティブの性能に近づきますが、仮想マシンを最大限活用するにはホストとゲスト両方で設定が必要です。
CPU ピニング
KVM ゲストではデフォルトでゲストから要求された操作を仮想プロセッサを表すスレッドとして実行します。スレッドは Linux のスケジューラによって他のスレッドと同じように管理され、nice 値や優先度にあわせて手隙の CPU コアに割り当てられます。スレッド切り替えにはオーバーヘッドが存在するため (コンテキストスイッチによってコアのキャッシュが強制的に変更されるため)、ゲストのパフォーマンスに悪い影響を与えます。CPU ピニングはこの問題を解決してプロセスのスケジューリングを上書きして VM のスレッドは特定のコアでのみ動作するようになります。例えば、ゲストのコア 0, 1, 2, 3 をホストの 4, 5, 6, 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> ...
ハイパースレッディングの場合
CPU がハードウェアによるマルチタスクをサポートしている場合 (Intel のチップではハイパースレッディングと呼ばれます)、CPU ピニングをする方法は2つ存在します。ハイパースレッディングは1つの CPU で2つのスレッドを効率的に動作させる手法であるため、クアッドコア CPU ならば8つの論理コアが使えます。物理コアの負担率が高い場合、論理コアは使われません。VM のスレッドを2つの物理コアに割り当てても、2つのコアが対応できる負担を超える作業では2つの論理コアの補助を得ることができません。4つのコアのうち2つのコアをパススルーしただけだからです。
以下はクアッドコアのマシンでハイパースレッディングが有効になっている場合の /proc/cpuinfo
の要約です:
$ 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
仮想マシンを使っているときにホスト側で負担が重い計算をしない場合 (あるいはホストを全く使わない場合)、仮想マシンのスレッドを全ての論理コアに固定化して、仮想マシンがコアを活用できるようにすると良いでしょう。
クアッドコアのマシンの場合、以下のようになります:
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> ...
ホストとゲストで同時に何か処理を行う場合、一部の物理コアとゲストのスレッドを固定して、後はホストでも使えるようにすると良いでしょう。
クアッドコアのマシンの場合、以下のようになります:
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> ...
静的ヒュージページ
大量のメモリを必要するアプリケーションでは、メモリの遅延が問題になることがあります。使用するメモリページ (メモリ割り当ての基本単位) が増えれば増えるほど、複数のメモリページにまたがる情報にアプリケーションがアクセスするようになる確立が高まります。メモリページの実際のアドレスを解決するには複数のステップを踏まないとならないため、大抵の場合 CPU は最近使用されたメモリページの情報をキャッシュすることで同一ページの使用を高速化します。しかしながらアプリケーションが大量のメモリを使うとすると問題です。例えば仮想マシンが使用する 4GB のメモリが (メモリページのデフォルトサイズである) 4kB に分割されるような場合、頻繁にキャッシュミスが発生することになりメモリの遅延を増大させてしまいます。このような問題を緩和するためにヒュージページが存在します。大きなサイズのページをアプリケーションに割り当てることで、同一ページが使用される可能性を高めます。通常は必要に応じてヒュージページを動的に管理する、透過的ヒュージページが使用されます。
しかしながら仮想マシンで PCI パススルーを使う場合は透過ヒュージページは意味がありません。IOMMU がゲストのメモリ割り当てを必要とし仮想マシンが起動するとすぐに固定化されるためです。したがってヒュージページの効果を得るには静的に割り当てる必要があります。
起動時にヒュージページを割り当てるには、カーネルコマンドラインで hugepages=x
を使って適切な量を指定します。例えば hugepages=1024
として1024ページを予約すると、ヒュージページあたりデフォルトで 2048kB のサイズが割り当てられるため、仮想マシンが使用するための 2GB 分のメモリが作成されます。
また、静的ヒュージページは要求を行ったアプリケーションだけが使用できるため、libvirt のドメイン設定に kvm が割り当てたヒュージページを活用するように設定を追加する必要があります:
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.
スクリプトを作成して vfio-pci をブート GPU 以外の全ての GPU にバインドすることができます。/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
を編集:
MODULES からビデオドライバーを全て削除して vfio-pci
と vfio_iommu_type1
を追加してください:
MODULES="ext4 vfat vfio-pci vfio_iommu_type1"
/etc/modprobe.d/vfio.conf
と /sbin/vfio-pci-override.sh
を FILES に追加してください:
FILES="/etc/modprobe.d/vfio.conf /sbin/vfio-pci-override.sh"
initramfs を再生成して再起動してください:
# 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=
オプションが有効になった状態で新しいカーネルをロードするようにしてください。
QEMU で libvirtd を使わない (再起動せずに GPU を切り替え可能)
[1] は Samba と Synergy を起動して仮想マシンを実行し、仮想マシンがシャットダウンしたら全て終了します。この方法では libvirtd を実行・設定する必要がありません。
新しいスクリプトでは、再起動しなくても GPU を切り替えることができ、X セッションの再起動だけで使うことができます。tty で動作する小さなシェルスクリプトで全て管理されます。シェルスクリプトを自動起動すれば tty にログインしたときにどちらのカードを使用するのか聞いてくれます。
vfio-users : Full set of (runtime) scripts for VFIO + Qemu CLI
vfio-users : Example configuration with CLI Qemu (working VM => host audio)
他のデバイスのパススルー
USB コントローラ
マザーボードに接続された複数の USB コントローラが複数のグループにマッピングされている場合、USB デバイスの代わりに USB コントローラをパススルーすることができます。個別の USB デバイスではなくコントローラをパススルーすることには以下の利点があります:
- 特定の操作 (スマートフォンのアップデートなど) でデバイスが切断されたり ID が変わったりしても、仮想マシンから突然認識されなくなることはありません。
- コントローラによって管理されている USB 端子を VM が直接扱うため、デバイスを接続・切断してもハイパーバイザーに通知する必要がありません。
- VM を起動したときにゲストにパススルーする USB デバイスがなくなってしまっていても Libvirt はエラーを出力しません。
GPU と違って、大抵の USB コントローラのドライバーでは VM で使用するのに特殊な設定を必要としません。副作用を起こさずにホストとゲストの間で制御を受け渡すことができます。
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.
PulseAudio で仮想マシンの音声出力をホストにパススルー
libvirt を使うことで仮想マシンの音声出力をアプリケーションとしてホストに転送することが可能です。複数の音声ストリームをホストの出力に転送でき、パススルーをサポートしていない音声出力デバイスで使うことができます。転送するには PulseAudio が必要です。
まず、#user = ""
行のコメントを削除してください。それからクォートの中にユーザー名を入力してください。これで QEMU はユーザーの PulseAudio ストリームを転送するようになります。
/etc/libvirt/qemu.conf
user = "example"
次に、libvirt の設定を変更してください。
以下の行を:
virsh edit [vmname]
<domain type='kvm'>
以下のように変更してください:
virsh edit [vmname]
<domain type='kvm' xmlns:qemu='http://libvirt.org/schemas/domain/qemu/1.0'>
そして libvirt の xml ファイルの末尾に QEMU PulseAudio 環境変数を設定してください。
以下の行を:
virsh edit [vmname]
</devices> </domain>
以下のように変更してください:
virsh edit [vmname]
</devices> <qemu:commandline> <qemu:env name='QEMU_AUDIO_DRV' value='pa'/> <qemu:env name='QEMU_PA_SERVER' value='/run/user/1000/pulse/native'/> </qemu:commandline> </domain>
user ディレクトリの 1000 はあなたが使用しているユーザーの uid に置き換えてください (id
コマンドを実行することで確認できます)。
設定したら libvirtd
と pulseaudio を再起動してください。
これで仮想マシンの音声出力はアプリケーションとしてホストに転送されるようになります。pavucontrol アプリケーションを使うことで出力デバイスを制御できます。Windows ゲストの場合、メッセージシグナル割り込みを使用しないとノイズが発生するので注意してください。
注意事項
リセットに対応していないデバイスのパススルー
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.
トラブルシューティング
Windows の仮想マシンに NVIDIA の GPU をパススルーした場合に "Error 43 : Driver failed to load"
バージョン 337.88 から、Windows の Nvidia ドライバーはハイパーバイザーが動作しているかどうかを確認して、動作していることを認識すると Windows のデバイスマネージャに Error 43 を吐くようになりました。QEMU 2.5.0 と libvirt 1.3.3 以上では、ハイパーバイザーの vendor_id を偽装することができ、Nvidia ドライバーを騙してロードさせることができます。QEMU コマンドの cpu パラメータに hv_vendor_id=whatever
を追加するか、libvirt のドメイン設定に以下の行を追加するだけです。ID は12文字ちょうどの英数字 (例: '123456789ab') に設定してください。
EDITOR=nano virsh edit myPciPassthroughVm
... <features> <hyperv> ... <vendor_id state='on' value='whatever'/> ... </hyperv> ... <kvm> <hidden state='on'/> </kvm> </features> ...
古いバージョンの QEMU や libvirt を使用している場合は、ハイパーバイザーの拡張を無効化する必要があり、かなり性能が落ちてしまいます。libvirt のドメイン設定ファイルに以下を記述してください:
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 ...
Windows の仮想マシンを起動したときに "System Thread Exception Not Handled"
Windows 8 や Windows 10 ゲストが起動時に "System Thread Exception Not Handled" という例外を発生させることがあります。実機で古いドライバーの動作がおかしいことが原因です。KVM では CPU のモデルを 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).
MSI がサポートされているか・有効になっているか確認するには、以下のコマンドを root で実行してください:
# lspci -vs $device | grep 'MSI:'
`$device` はカードのアドレスに置き換えてください (例: `01:00.0`)。
出力は以下のようになります:
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.
有効にする手順は非常に複雑です。こちら に設定の手順と概要が載っています。
Other hints can be found on the lime-technology's wiki, or on this article on VFIO tips and tricks.
インターネット上には MSI_util
という名前のツールが存在しますが、64ビットの Windows 10 では動作しないことがあります。
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.
intel_iommu を有効にしたときにホスト側で HDMI から音声が出力されない
intel_iommu
を有効にしたときにホストの Intel GPU の HDMI 出力デバイスが使えなくなった場合、igfx_off
(i.e. intel_iommu=on,igfx_off
) オプションを設定することで音声が出力できるようになることがあります。igfx_off
の設定について詳しくは Intel-IOMMU.txt の Graphics Problems?
を読んでください。
vfio_pci を有効化したあとに X が起動しない
ホスト GPU がセカンダリ GPU として認識されている場合、ゲスト GPU のドライバーをロードしようとしたときに X がエラーを起こします。Xorg の設定でホスト GPU の BusID を指定することで解決します。BusID は lspci や Xorg のログで確認できます。
10-radeon.conf
Section "Device" Identifier "Radeon GPU" Driver "radeon" BusID "PCI:3:0:0" EndSection
詳しくは [2] を参照してください。