Skip to content

Instantly share code, notes, and snippets.

@zgock999
Last active February 26, 2021 07:52
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save zgock999/1c991ef2c74e6119db01fbc4faf7d25e to your computer and use it in GitHub Desktop.
Save zgock999/1c991ef2c74e6119db01fbc4faf7d25e to your computer and use it in GitHub Desktop.

KVM/XenにおけるGPUパススルーメモ

KVM/Xenであれこれ遊んだ記録を書いていきます

KVM実験記録

現在のターゲット環境

ハードウェア

CPU:Intel Xeon-E3 1245v2 (core i7 3770相当)

GPU:Intel HD 4000P / AMD RADEON HD7750 / nVidia Geforce 750Tiの3GPU構成

M/B:Asrock Z77 extreme4

ソフトウェア

openSUSE tumbleweed x86-64

kernel 4.11.0

*(Kernel 4.8以上じゃないとigpu passthroughが出来ないのでLeap 42.3がkernel 4.9LTSベースになったらLeapに戻すかも)

qemu-kvm 2.8

*Leap 42.2はまだqemuが2.7

libvirt 3.3.0

環境構築

OSインストール

普通にtumbleweedをインストール。一旦はxfceデスクトップで(ssh -Xで外からX端末でssh接続してvirt-manager動かしたいからX11関係を一旦一式入れてしまう)

最初のインストール時点ではdGPUを入れずにiGPUだけでインストールした方が無難。特にnouveauドライバはマルチGPUだと色々悪さする

KVMインストールとbridge設定

YASTから一発でlibvirt関連とブリッジ設定まで済んでしまうので割愛

ここで一度普通にパススルーしないゲストがちゃんと動くか確認

ランレベルの変更

YAST>サービスマネージャからランレベル3(マルチユーザー)に変更

YAST>ブートローダからカーネルパラメータにnomodeset追加、ついでにintel_iommu=onも追加(AMDの場合はそのように)

必ずランレベル変更を先にやること(nomodesetしつつX立ち上げるとハマる)

vfio-pciドライバの先読み設定

/etc/sysconfig/kernelにvfio関連のドライバを列挙してinitrd中にvfio関連ドライバを入れておく

このへんディストリ的に行儀の良いやり方は変わるが、SUSEとRHではドライバ先読み設定はここに書くのが行儀良いとされる

他のディストリの方法で行ってもたまたま大丈夫な場合もあるが、バージョン変化に追従できないことがある

逆もしかりなのでubuntuやdebian系で試す人は公式doc読むなりでディストリの流儀に従うことを推奨

# echo "INITRD_MODULES=\"pci_stub vfio vfio_iommu_type1 vfio_pci vfio_virqfd kvm kvm_intel\"" >> /etc/sysconfig/kernel

パススルー対象デバイスの設定

/etc/modprobe.d/以下に適当な俺々confを作ってパススルー対象のPCI device IDを列挙する IDはlspci -nで取る(例に列挙してるIDは当然うちの環境なので実環境に合わせること)

# echo "options vfio-pci ids=8086:016a,8086:1e20,1002:683f,1002:aab0,10de:1380,10de:0fbc" >> /etc/modprobe.d/99-mygpu.conf

パススルー対象を掴むドライバ類をblacklistへ

本来nomodeset入れてランレベル3で起動すればカーネルはGPUもサウンドも掴まないはずではあるけど一応

# echo -e "\nblacklist i915\nblacklist snd_hda_intel\nblacklist radeon\nblacklist amdgpu\nblacklist nouveau" >> /etc/modprobe.d/50-blacklist.conf

initrd作成

   # mkinitrd

カスタムカーネル適用

LGA115x系core i/Xeon-E3とかRyzen7なんかのようにgpu用のPCIレーンが16本しかないM/Bでdgpuを二つ以上パススルーしたい時にはpached kernelが必要

(いわゆるacs override patch)

kernel 4.1~4.10であればarchのAURからパッチを拾ってくれば使えるはず

kernel 4.11用は自分で作ったのを貼っておく

openSUSE tumbleweed用で良ければビルド済カーネル作ったのでどうぞ

https://build.opensuse.org/project/show/home:zgock:kvm-vfio

GPU別傾向と対策

intel HD

kernel 4.8&qemu 2.7でやっとこigpuパススルーがどうにか動き始めた感じ

Seabios/i440fxでないと起動しないのでOVMF/Q35は使わないこと

Linux Gusetならわりと問題なく動く

Windows Guestは素の状態のkernelではドライバ入れた時点でBSOD。(intel driverがunsafe割り込みを吐いたり、qemuが対応してない拡張命令を吐いたりする) 対策としてKVMにオプションを追加する

# echo "options kvm allow_unsafe_assigned_interrupts=1 ignore_msrs=1 halt_poll_ns=0" >> /etc/modprobe.d/99-kvm.conf

ただまだゲームベンチでフルスクリーンにすると落ちたりとかが発生したりまだまだ未熟性

AMD Radeon

UEFI未対応の世代(RADEON R7系以前のHDxxxx系列)でもSeabiosでパススルーできるが、libvirtで使う場合ちょっと注意が必要

virt-managerで単純にPCI Deviceとして追加しただけではBIOS時点で固まる

virsh editでvgaのhostdevに対してx-vgaオプションを与える必要があるが、デフォルトのlibvirtのnamespaceでは付与できないため、domainのnamespaceを明示的に与えてqemu:commandlineを有効にする

<domain type='kvm' xmlns:qemu='http://libvirt.org/schemas/domain/qemu/1.0'>

xml末尾にqemu:commandlineタグを入れてvgaに対応するhostdevにx-vgaオプションを付ける

  <qemu:commandline>
    <qemu:arg value='-set'/>
    <qemu:arg value='device.hostdev0.x-vga=on'/>
  </qemu:commandline>
</domain>

OVMF対応世代以降ならOVMFで良いが、x-vgaオプションは付けておいた方が無難

ただし、RADEONは特定の世代のチップ(R9-3xx系あたりに多い)のUEFIファームにバグがあってシャットダウン時に問題が出るとかあるので、特に理由がない限りはseabiosでゲスト起動するのを推奨

Windows7はsecure boot未対応なのでOVMFで起動する場合は必ずOVMFのセキュアブートを切っておくこと

Nvidia geforce

Seabiosでは基本的に動かない(geforce系のファームにガードがかかっている)。OVMF環境では普通に動くが、Windowsゲスト及びLinuxでもnvidiaドライバでは仮想化カーネルであることを検知するとドライバが落ちる問題(Code 43)問題があるため設定にひと工夫が必要

(要するにUEFI時代になってファームでガードかけるのが難しくなったのでドライバーでガードかけるようになったと(ここに中指画像が入ります))

具体的にはlibvirtのfeatures句でkvmをドライバに検知させないようにする

    <kvm>
      <hidden state='on'/>
    </kvm>
  </features>

Xen実験記録

現在のターゲット環境

openSUSE Leap 42.1 x86-64 kernel 4.1.xx-xen xen 4.5

--- a/drivers/pci/quirks.c
+++ b/drivers/pci/quirks.c
@@ -3630,6 +3630,107 @@
fs_initcall_sync(pci_apply_final_quirks);
+static bool acs_on_downstream;
+static bool acs_on_multifunction;
+
+#define NUM_ACS_IDS 16
+struct acs_on_id {
+ unsigned short vendor;
+ unsigned short device;
+};
+static struct acs_on_id acs_on_ids[NUM_ACS_IDS];
+static u8 max_acs_id;
+
+static __init int pcie_acs_override_setup(char *p)
+{
+ if (!p)
+ return -EINVAL;
+
+ while (*p) {
+ if (!strncmp(p, "downstream", 10))
+ acs_on_downstream = true;
+ if (!strncmp(p, "multifunction", 13))
+ acs_on_multifunction = true;
+ if (!strncmp(p, "id:", 3)) {
+ char opt[5];
+ int ret;
+ long val;
+
+ if (max_acs_id >= NUM_ACS_IDS - 1) {
+ pr_warn("Out of PCIe ACS override slots (%d)\n",
+ NUM_ACS_IDS);
+ goto next;
+ }
+
+ p += 3;
+ snprintf(opt, 5, "%s", p);
+ ret = kstrtol(opt, 16, &val);
+ if (ret) {
+ pr_warn("PCIe ACS ID parse error %d\n", ret);
+ goto next;
+ }
+ acs_on_ids[max_acs_id].vendor = val;
+
+ p += strcspn(p, ":");
+ if (*p != ';') {
+ pr_warn("PCIe ACS invalid ID\n");
+ goto next;
+ }
+
+ p++;
+ snprintf(opt, 5, "%s", p);
+ ret = kstrtol(opt, 16, &val);
+ if (ret) {
+ pr_warn("PCIe ACS ID parse error %d\n", ret);
+ goto next;
+ }
+ acs_on_ids[max_acs_id].device = val;
+ max_acs_id++;
+ }
+next:
+ p += strcspn(p, ",");
+ if (*p == ',')
+ p++;
+ }
+
+ if (acs_on_downstream || acs_on_multifunction || max_acs_id)
+ pr_warn("Warning: PCIe ACS overrides enabled; This may allow non-IOMMU protected peer-to-peer DMA\n");
+
+ return 0;
+}
+early_param("pcie_acs_override", pcie_acs_override_setup);
+
+static int pcie_acs_overrides(struct pci_dev *dev, u16 acs_flags)
+{
+ int i;
+
+ /* Never override ACS for legacy devices or devices with ACS caps */
+ if (!pci_is_pcie(dev) ||
+ pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ACS))
+ return -ENOTTY;
+
+ for (i = 0; i < max_acs_id; i++)
+ if (acs_on_ids[i].vendor == dev->vendor &&
+ acs_on_ids[i].device == dev->device)
+ return 1;
+
+ switch (pci_pcie_type(dev)) {
+ case PCI_EXP_TYPE_DOWNSTREAM:
+ case PCI_EXP_TYPE_ROOT_PORT:
+ if (acs_on_downstream)
+ return 1;
+ break;
+ case PCI_EXP_TYPE_ENDPOINT:
+ case PCI_EXP_TYPE_UPSTREAM:
+ case PCI_EXP_TYPE_LEG_END:
+ case PCI_EXP_TYPE_RC_END:
+ if (acs_on_multifunction && dev->multifunction)
+ return 1;
+ }
+
+ return -ENOTTY;
+}
+
/*
* Following are device-specific reset methods which can be used to
* reset a single function if other methods (e.g. FLR, PM D0->D3) are
@@ -4346,6 +4447,7 @@
{ 0x10df, 0x720, pci_quirk_mf_endpoint_acs }, /* Emulex Skyhawk-R */
/* Cavium ThunderX */
{ PCI_VENDOR_ID_CAVIUM, PCI_ANY_ID, pci_quirk_cavium_acs },
+ { PCI_ANY_ID, PCI_ANY_ID, pcie_acs_overrides },
{ 0 }
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment