在此记录一下如何在 Linux 下启用 PCIe Passthrough 给虚拟机提供宿主 PCIe 设备。
我用的是 Ubuntu 22.04,现在新的系统内核一般内置了 vfio-pci 模块,可以直接使用。以 Intel Xeon E5-2670 为例子。
通过内核启动参数使用
需要修改 /etc/defaults/grub
,给 GRUB_CMDLINE_LINUX_DEFAULT
增加如下参数:
1
| iommu=pt intel_iommu=on vfio-pci.ids={DEVICE_ID_1},{DEVICE_ID_N} vfio-pci.disable_idle_d3=1 kvm_intel.nested=1 kvm_intel.emulate_invalid_guest_state=0
|
intel
相关的参数是 Intel 专用,amd 的需要参阅参考资料对应修改。
vfio-pci.ids=
后填设备的 ID,可以通过 lspci -nnv
看到,例如我的 GTX560:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| 04:00.0 VGA compatible controller [0300]: NVIDIA Corporation GF114 [GeForce GTX 560] [10de:1201] (rev a1) (prog-if 00 [VGA controller]) Subsystem: NVIDIA Corporation GF114 [GeForce GTX 560] [10de:0895] Physical Slot: 5 Flags: fast devsel, IRQ 11, IOMMU group 29 Memory at cc000000 (32-bit, non-prefetchable) [disabled] [size=32M] Memory at d0000000 (64-bit, prefetchable) [disabled] [size=128M] Memory at d8000000 (64-bit, prefetchable) [disabled] [size=64M] I/O ports at c000 [disabled] [size=128] Expansion ROM at ce000000 [disabled] [size=512K] Capabilities: <access denied> Kernel driver in use: nouveau Kernel modules: nvidiafb, nouveau
04:00.1 Audio device [0403]: NVIDIA Corporation GF114 HDMI Audio Controller [10de:0e0c] (rev a1) Subsystem: NVIDIA Corporation GF114 HDMI Audio Controller [10de:0895] Physical Slot: 5 Flags: fast devsel, IRQ 7, IOMMU group 29 Memory at ce080000 (32-bit, non-prefetchable) [disabled] [size=16K] Capabilities: <access denied> Kernel driver in use: snd_hda_intel Kernel modules: snd_hda_intel
|
04:00.0
是设备物理位置,也就是插槽的位置,只要不换插槽就不会变。设备名称后面的 10de:1201
就是设备 ID。如果显卡带音频控制器,需要一同 passthrough。这里我就应该填 vfio-pci.ids=10de:1201,10de:0e0c
。
修改完成后执行 sudo update-grub
更新 GRUB 配置。重启之后通过 lspci -nnv
查,就能看到对应设备的 Kernel driver in use
变成了 vfio-pci
了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| 04:00.0 VGA compatible controller [0300]: NVIDIA Corporation GF114 [GeForce GTX 560] [10de:1201] (rev a1) (prog-if 00 [VGA controller]) Subsystem: NVIDIA Corporation GF114 [GeForce GTX 560] [10de:0895] Physical Slot: 5 Flags: fast devsel, IRQ 11, IOMMU group 29 Memory at cc000000 (32-bit, non-prefetchable) [disabled] [size=32M] Memory at d0000000 (64-bit, prefetchable) [disabled] [size=128M] Memory at d8000000 (64-bit, prefetchable) [disabled] [size=64M] I/O ports at c000 [disabled] [size=128] Expansion ROM at ce000000 [disabled] [size=512K] Capabilities: <access denied> Kernel driver in use: vfio-pci Kernel modules: nvidiafb, nouveau
04:00.1 Audio device [0403]: NVIDIA Corporation GF114 HDMI Audio Controller [10de:0e0c] (rev a1) Subsystem: NVIDIA Corporation GF114 HDMI Audio Controller [10de:0895] Physical Slot: 5 Flags: fast devsel, IRQ 7, IOMMU group 29 Memory at ce080000 (32-bit, non-prefetchable) [disabled] [size=16K] Capabilities: <access denied> Kernel driver in use: vfio-pci Kernel modules: snd_hda_intel
|
当即启用
当即启用其实就是更改设备当前的驱动。更改之前需要关闭所有用到目的显卡的软件例如 xserver 以及 nVidia 相关的服务。先进入到其他的控制台例如 Ctrl + Alt + F2
进入 tty2,之后关闭对应的服务,更换驱动。
或许还需要卸载掉 nVidia 相关的驱动,可以参考这个链接的问题:VGA passthrough with QEMU and KVM - Nothing on screen
更换驱动需要知道当前设备在使用什么驱动。可以通过 ls -nnv
查看,例如我的 NVS 450:
1 2 3 4 5 6 7 8 9 10 11
| 07:00.0 3D controller [0302]: NVIDIA Corporation G98 [Quadro NVS 450] [10de:06fa] (rev a1) Subsystem: NVIDIA Corporation G98 [Quadro NVS 450] [10de:0619] ... Kernel driver in use: nouveau Kernel modules: nvidiafb, nouveau
08:00.0 VGA compatible controller [0300]: NVIDIA Corporation G98 [Quadro NVS 450] [10de:06fa] (rev a1) (prog-if 00 [VGA controller]) Subsystem: NVIDIA Corporation G98 [Quadro NVS 450] [10de:0619] ... Kernel driver in use: nouveau <<< 这个 Kernel modules: nvidiafb, nouveau
|
它正在使用 nouveau,且需要记下显卡的设备物理地址(也就是 bus id),这里同时带了 3D controller,所以是 07:00.0
和 08:00.0
。
然后到 /sys/bus/pci/drivers
下,进入驱动名称的文件夹。我这里是 nueveau 在用显卡,所以完整路径为 /sys/bus/pci/drivers/nueveau
。可以看到以上 id 都在这个文件夹下:
1 2
| cattenlinger@Z420:/sys/bus/pci/drivers/nouveau$ ls 0000:07:00.0 0000:08:00.0 bind module new_id remove_id uevent unbind
|
需要做的就是解绑驱动,给 unbind
这个文件喂对应的 ID:
1 2 3
| cd /sys/bus/pci/drivers/nueveau echo 0000:07:00.0 > ./unbind echo 0000:08:00.0 > ./unbind
|
这样就能卸载驱动了。然后得挂上 vfio-pci 的驱动。进入 vfio-pci 的驱动文件夹,记下显卡的设备 ID,并喂给 new_id
:
1 2 3
| cd /sys/bus/pci/drivers/vfio-pci # 第一个是 vendor id,第二个是 product id。 echo 10de 06fa > ./new_id
|
这样就挂上了。
把设备分配给 VM
我使用的是 QEMU,virt-manager 一样,只是换成了图形化界面。
给 QEMU 命令添加上这些个设备:
1 2
| -device vfio-pci,host=04:00.0,multifunction=on,romfile="$FIRMWARE_DIR/vbios.bin" -device vfio-pci,host=04:00.1
|
小插曲
如果显卡比较老(10XX 系列以下,amd 的我不熟不知道),还需要 vbios 文件,就是上面第一条命令的 romfile 参数。这个操作比较麻烦,可以参考这个连接:Preparation and placing of the ROM file。如果的确不方便用 Windows,也没法通过对应的 firmware upgrader 获取 vbios,可以尝试去 TECHPOWERUP - VGA VIOS 下载,但其实不建议,因为每台机子的 vbios 可能都不太相同。
如果不方便用 GPU-Z ,也可以用 CPU-Z。CPU-Z 出来的文件是 ASCII 的 dump,还需要手动转换为 binary。这里建议使用这个命令:
1
| xxd -r -p vbios.txt vbios.bin
|
我的显卡就比较老,所以需要一个 vbios 来让 OVMF 去驱使显卡。
听说 N 卡比较难伺候,即便 Passthrough 进去了也很大可能用不了,我就一时能用一时不能,老显卡的话建议 AMD。