ひねり出したメモ

主にコンピューター関連の話を書きます。

【Debian】【QEMU/KVM】Ryzen2のPCでGPUパススルーしてWindows8.1のゲストを動かす

Ryzen2を搭載したPCでGPUとUSBコントローラーをPCIパススルーして、ゲストのWindows8.1を快適に使用できる環境を作ります。

目的

自宅で使用しているメインPCがWindows7を使用しており、サポート期限が迫っているので移行先の環境を作ります。
Winodws8.1はRyzenをサポートしておらずWindows Updateがエラーになりますが、QEMU/KVMの設定でゲストマシンのCPUを古いモデルに設定してこれを回避します。

ゲストのWindows8.1は通常のデスクトップ機と同様に使えるようにします。
そのためGPUをゲストマシンへパススルーして、物理マシンと同程度のグラフィック性能を確保します。
さらにUSBコントローラーをパススルーすることで、仮想環境であるとこを意識せずにUSBデバイスを使用できるようにします。

ゲストのOSにWindows8.1を使用していますが、同様の手順でWindows10のゲストを動かすことも可能なはずです。 (Windows8.1なのは、Windows10があまり好きではないからです)

構成

  • ハードウェア
    • MB: Asrock Fatal1ty X370 Professional Gaming
    • CPU : AMD Ryzen7 2700X
    • RAM : 32GB (16GB 2枚)
    • グラフィック(1) : MSI GeForce GTX 1050 Ti 4GT LP ※ゲストにパススルーする用
    • グラフィック(2) : 玄人志向 GF-GT710-E1GB/LP ※ホストの操作用
    • サウンドASUS XONAR U7 MK II ※音の良いPCにするためパススルーしたUSBコントローラーの先に接続する。
    • そのほか、USB接続のキーボード・マウス(ゲストの操作用)、十分な容量HDD
  • ソフトウェア
    • Debian9 x64 →動作に問題があったのでテスト版のDebian10にアップグレードしています。OSインストールから始める場合は始めからDebian10使用したほうが良いと思います(できれば正式版を待ってから)。理由は最後に記載。

環境構築

Debian9のインストール

Debian9のインストールはウィザードに沿ってすすめるだけです。
ソフトウェアの選択では「Debianデスクトップ環境」と「MATE」を選びました。後ほどvirt-manageをインストールしてGUI仮想マシンの設定を可能にします。

OSのインストール後、/etc/apt/sources.listを編集してcontribとnon-freeのセクションを有効にしています。 そして、ホスト操作に使用するGPUのドライバをインストールしています。

# nano /etc/apt/sources.list
    各リポジトリにcontribとnon-freeのセクションを追加する
# apt update
# apt install nvidia-driver

必要なパッケージのインストール

QEMU/KVM関連のパッケージをインストールします。 なお、当方の環境ではすでにブリッジインターフェースの作成に必要なbridge-utilsはインストール済みでした。
Debinaインストール時のソフトウェアの選択次第ではbridge-utilsのインストールも必要になると思われます。

apt install qemu-kvm libvirt-clients qemu-utils libvirt-daemon-system ovmf virt-manager

ブリッジの設定

ホストとゲストで物理NICを共有できるようにブリッジインターフェースを作成します。
ブリッジの作成はNetworkManagerで作成する方法と設定ファイルを編集する方法があります。
私ははじめNetworkManagerで作成したのですが、ゲストをシャットダウン→起動すると、ゲストがネットワークにつながらなくなる問題が発生しました。
そのため、NetworkManagerを停止して設定ファイルを編集してブリッジを作成しています。
なお、IPv4は固定アドレス、IPv6は自動構成です。

# systemctl disable NetworkManager
  ※NetworkManagerを無効化した後DNSの名前解決ができなくなって何か設定変更したはず。
# nano /etc/network/interfaces

auto enp42s0
iface enp42s0 inet manual

auto br0
iface br0 inet static
        address 192.168.0.2
        netmask 255.255.255.0
        network 192.168.0.0
        broadcast 192.168.0.255
        gateway 192.168.0.1
        dns-nameservers 192.168.0.1
        bridge_ports enp42s0
        bridge_stp off

次に、ブリッジを通過する通信に対するNetfilterを無効化します。 ArckWikiによるとセキュリティとパフォーマンス上の理由から推奨とのこと。

# nano /etc/sysctl.conf

net.bridge.bridge-nf-call-ip6tables=0
net.bridge.bridge-nf-call-iptables=0
net.bridge.bridge-nf-call-arptables=0

PCIパススルー関連の設定

事前にUEFIの設定でAMD-V(SVM)とIOMMUを有効化しておきます。

次に/etc/default/grubを編集してLinux側でもIOMMUを有効化します。
kvm_amd.npt=1はNPTの有効化。なくても問題ないかも?)

# nano /etc/default/grub

GRUB_CMDLINE_LINUX_DEFAULT="quiet amd_iommu=on iommu=pt kvm_amd.npt=1"

# update-grub
# reboot

再起動したらIOMMUが有効になっていることを確認します。

# dmesg | grep -e DMAR -e IOMMU

[    0.420123] AMD-Vi: IOMMU performance counters supported
[    0.421728] AMD-Vi: Found IOMMU at 0000:00:00.2 cap 0x40
[    0.422049] perf/amd_iommu: Detected AMD IOMMU #0 (2 banks, 4 counters/bank).
[    0.537226] AMD IOMMUv2 driver by Joerg Roedel <jroedel@suse.de>

次にIOMMUグループを確認します。
PCIパススルーはIOMMUグループ単位で行われるため、1つのIOMMUグループに複数のデバイスが存在する場合は注意が必要です。
ArchWikiに記載されているスクリプトを使用します。

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

# chmod 755 iommu-group
# ./iommu-group

以下は出力の抜粋です。
IOMMU Group 13 2d:00.0 VGA compatible controller [0300]: NVIDIA Corporation GP107 [GeForce GTX 1050 Ti] [10de:1c82] (rev a1)
IOMMU Group 13 2d:00.1 Audio device [0403]: NVIDIA Corporation GP107GL High Definition Audio Controller [10de:0fb9] (rev a1)
IOMMU Group 16 2e:00.3 USB controller [0c03]: Advanced Micro Devices, Inc. [AMD] USB 3.0 Host controller [1022:145f]

私のPCでは、IOMMU Group 13にゲストに割り当てるGPUGPU内蔵のオーディオが接続されているようです。
また、オンボードUSBコントローラーの1つがIOMMU Group 16に単独で接続されていたため、これもパススルーします。
各行の最後のほうにあるPCI ID([10de:1c82]等)は後で使うので控えておきます。

次にPCIパススルーするデバイスにVFIOドライバを割り当てます。
ArchWikiによると、VFIOドライバの割り当ては仮想マシンの起動時に動的に行われるがGPUドライバは巨大で複雑だから先にやっておけ、とのことです。
この設定方法を調べてみると、/etc/modulesや/etc/modprobe.d/あたりをいじる方法が出てくるのですが、私の環境ではこちらを参考に/etc/initramfs-tools/modulesを編集する方法でVFIOドライバの割り当てに成功しています。

# nano /etc/initramfs-tools/modules
以下を追記。idsにはPCI IDをカンマ区切りで記載します。
vfio_pci ids=10de:1c82,10de:0fb9,1022:145f

# update-initramfs -u
# reboot

再起動後にVFIOドライバが割り当てられていることを確認します。

$ lspci -nnk -d 10de:1c82
2d:00.0 VGA compatible controller [0300]: NVIDIA Corporation GP107 [GeForce GTX 1050 Ti] [10de:1c82] (rev a1)
        Subsystem: Micro-Star International Co., Ltd. [MSI] GP107 [GeForce GTX 1050 Ti] [1462:8c96]
        Kernel driver in use: vfio-pci
        Kernel modules: nvidia

$ lspci -nnk -d 10de:0fb9
2d:00.1 Audio device [0403]: NVIDIA Corporation GP107GL High Definition Audio Controller [10de:0fb9] (rev a1)
        Subsystem: Micro-Star International Co., Ltd. [MSI] GP107GL High Definition Audio Controller [1462:8c96]
        Kernel driver in use: vfio-pci
        Kernel modules: snd_hda_intel

$ lspci -nnk -d 1022:145f
2e:00.3 USB controller [0c03]: Advanced Micro Devices, Inc. [AMD] USB 3.0 Host controller [1022:145f]
        Subsystem: ASRock Incorporation USB 3.0 Host controller [1849:7914]
        Kernel driver in use: vfio-pci
        Kernel modules: xhci_pci

ゲストマシンを作成する

ゲストマシンの作成はvirt-managerを使用してGUIで行いますが、仮想マシンを作成するウィザードの最後で「インストール前に設定をカスタマイズする」にチェックを入れて設定の編集を行います。
編集する箇所は

  • 「ハードウェアを追加」から「ホストPCIバイス」を選択してパススルーするデバイスをゲストに追加する。
  • 仮想のビデオとディスプレイは不要なので削除する。
  • Windows8.1を動作させるためCPUの設定でWindows8.1がサポートしているCPUを選択する。私の環境ではOpteron G3を選択しました。G5とG4ではホストのCPUがサポートしていない命令があるためゲストの起動ができませんでした。
  • 「概要」のファームウェアUEFIを選択する。
  • 「概要」のチップセットで「Q35」を選択する。Q35にしないとUSBオーディオGPU内蔵オーディオの音が途切れたり変になったりします。私はi440FXにしてトラブりました。詳細は最後に記載。

以上の設定が完了したらゲストのインストールを開始します。

最後にNvidiaのドライバをインストール可能にするため、こちらに従ってゲストの設定を編集します。

はじめはトラブった

記事の途中に書いていますが、当初Debian9を使用したこととゲストのチップセットをi440FXにしたことが原因で動作に問題がありました。 参考のため記載しておきます。

  1. 1コアで2スレッドにならない
    RyzenはSMT(インテルの表現ではハイパースレッディング)に対応していますが、Debian9に搭載のカーネルRyzenのSMTに未対応です。対応はkernel 4.10からのようですWindows8.1ゲストのCPUモデルOpteron G3はそもそもSMT未対応なのですが、Windows10ゲスト用にCPU設定をいじっているとエラーが出るなどして問題に気が付きました。

  2. NPT(Nested Page Table)のバグがありPCIパススルーの性能が落ちる
    Debian9に搭載のカーネルはNPTのバグがあり、AMDのCPUでPCIパススルーをした際に20%程度性能が低下するそう。一方、NPTを無効にするとゲストの性能が低下するとのこと。(参考) このバグはKernel 4.15rc1で修正されているらしいです。

  3. USBオーディオが止まる、音かプツプツ途切れる
    パススルーしているUSBコントローラーの先にUSBオーディオ(XONAR U7 MK II)を接続して音を出すと止まることが判明しました。

    • Firefox+ニコニコ動画だと数秒再生したところで停止。
    • Windows Media Playerだと1秒も再生できない。
    • XONAR U7 MK II付属の設定ツールを起動しても画面が表示されない。
    • USBオーディオ以外にも、DisplayPortからの音もおかしい(遅れてエコーがかかったような状態)

上記のNTPバグが原因と疑ってkernel 4.19搭載のDebian10(テスト版)にアップグレードしたところ、音かプツプツ途切れるが停止することはなくなりました。

さらに原因を調べると、ArchWikiにゲストが使用する割り込み方法の問題との記載がありました。

ArchWikiに記載の解決策はゲストのレジストリを編集するとのことでしたが、ふと「ゲストのチップセットをi440FXより新しいQ35にしたら解決するのでは?」と思ってゲストを再作成して試したところ無事解決。
ゲストが使用する割り込みの方法がLine-Based InterruptsではなくMSI (Message Signaled-Based Interrupts)になっており、音が正常に再生されるようになりました。