因为仅仅使用PCIE_ACS_OVERRIDE依旧无法成功达成所需的IOMMU分组,故而选择直接编译PVE的内核,暂时解决IOMMU的分组问题。

WARNING

注意本文提到的方法只供实验学习使用。方法本身是存在安全隐患的。生产环境中切勿使用。

背景 | Context

手上有一个闲置的ASUS B460M Prime主板,另外手上有两个NVME,还有空闲的GPU以及SATA Expansion card。于是想着组一个新的家用服务器,开始了新一轮的折腾。

硬件 | Hardware

ASUS B460M Prime是一个低端MATX主板,4内存插槽,1 PCIEx16,2 PCIEx1,两个M.2接口,单1GbE Realtek网卡,没有无线网卡(WIFI对于拿来做服务器反而不是任何缺点,有也基本上是浪费,没有的话,network接口还稍微简单清晰一点),6个板载SATA。

SATA Expansion card主要是当初图方便,没有从Ebay上面淘HBA卡,况且也算是契合PCIEx1的接口。PCIEx1的接口除了干这个,目前估计也没有什么更合适的用处了,前提是SATA Expansion card只接HDD。

麻烦就处在这两个SATA Expansion card所在的PCIEx1上面。

软件|Software

PVE 8.0,Linux内核版本6.2,这个内核实际上是有PVE自己的补丁的。本来是寄希望于新版本的Linux也许可以对PCIE_ACS_OVERRIDE有更好的支持,可惜没有如愿。

问题|Problem

在使用了 PCIE_ACS_OVERRIDE argument的情况下,IOMMU分组里面,SATA Expansion card所在的两个PCIEx1接口,和板载的Ethernet接口在同一个分组。

现在大多数Promox启动应该都是用的systemd了,所以argument是加在 /etc/kernel/cmdline里面:

root=ZFS=rpool/ROOT/pve-1 boot=zfs quiet intel_iommu=on iommu=pt pcie_acs_override=downstream,multifunction initcall_blacklist=sysfb_init

注意以上内容必须是一行。 pcie_acs_override=downstream,multifunction 这里应该会把能拆开的设备都拆开。而且理论上multifunction可能也是不需要的,这个flag其实会把GPU里面的声卡拆分出来。

对于之前的Gigabyte Z270P D3主板,单独加这个 downstream,multifunction 已经足够了。板上的所有PCIE插槽都会分配到各自的IOMMU Group里面。

  • 通常来讲这样就足够了,而且对于CPU直连的设备,基本上可以确保能满足分组的要求了。对于GPU passthrough的情况,应该只需要这两个 flags。

对于GPU Passthrough,可以详见这篇:[proxmox-74-gpu-passthrough]

但是对于ASUS B460M,使用如下script查看分组:

#!/bin/bash
for d in $(find /sys/kernel/iommu_groups/ -type l | sort -n -k5 -t/); do
        n=${d#*/iommu_groups/*}
        n=$(echo "$n" | cut -d'/' -f1)
        printf 'IOMMU Group %s ' "$n"
        lspci -nns "${d##*/}"
done;

NOTE

Updated version (the old one also works):

#!/bin/bash
shopt -s nullglob
for g in /sys/kernel/iommu_groups/*; do
    echo "IOMMU Group ${g##*/}:"
    for d in $g/devices/*; do
        echo -e "\t$(lspci -nns ${d##*/})"
    done;
done;

结果发现 Ethernet controller和两个PCIEx1插槽上面的SATA Expansion card依旧在同一个分组(IOMMU Group 9)里面。

当设备处于同一个分组的时候,如果将分组中的一个设备pass through到一个VM里面,实际会导致整个分组的设备都被pass through。结果就是,分配SATA Expansion card到TrueNAS,会导致PVE Host断网。

实际上 PCIE_ACS_OVERRIDE 也是发挥作用了。并不是所有芯片组上面的PCI设备都没有分组成功,比如第二个M.2设备实际上了分组成功了的。只是这个Ethernet controller和SATA Expansion cards,还有一个PCI Bridge设备,无法分组。

之所以说PCIE_ACS_OVERRIDE设定成功,可以验证如下

root@pve:~# dmesg | grep -e IOMMU
[    0.000000] Warning: PCIe ACS overrides enabled; This may allow non-IOMMU protected peer-to-peer DMA
[    0.179824] DMAR: IOMMU enabled

这个Warning基本就是ACS overrides已经在使用中的证据。

失败的尝试

尝试了如下方法,试图省略编译kernel需要,结果还是免不了需要修改kernel。

  • 更新BIOS版本

原先的BIOS是1603,后来更新到最新的1620。更新之后确实需要重新在BIOS里面打开左右关于虚拟化的设置(比如vt-d之类的),IOMMU分组也要打开。但是最后系统内还是没有把三个设备分开。

使用 pcie_acs_override=id:nnnn:nnnn ,给定两个SATA Expansion Cards的vendor ID 和 product ID。 pcie_acs_override=downstream,multifunction,id:1b4b:9128,id:1b21:1064 结果是一样的。

解决|Solution

这个时候就只能重新编译Kernel了。因为ACS Overrides Patch虽然已经起作用了,但是其中的某些条件没有达成,所以依旧没有强制分组。

Checkout code

代码需要是PVE的Kernel代码:

git clone git://git.proxmox.com/git/pve-kernel.git --depth 1

检查一下Patch,在当下(2023-08-20),需要修改的文件是 pve-kernel/patches/kernel/0004-pci-Enable-overrides-for-missing-ACS-capabilities-4..patch 。这里头的有一行 if(!pci_is_pcie(dev) ||pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ACS)) 这个if statement 里面的 pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ACS) 大体就是问题所在。直接把这个 pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ACS) 改成 false 就成了。

diff --git a/patches/kernel/0004-pci-Enable-overrides-for-missing-ACS-capabilities-4..patch b/patches/kernel/0004-pci-Enable-overrides-for-missing-ACS-capabilities-4..patch
index f69c4e3..e456b13 100644
--- a/patches/kernel/0004-pci-Enable-overrides-for-missing-ACS-capabilities-4..patch
+++ b/patches/kernel/0004-pci-Enable-overrides-for-missing-ACS-capabilities-4..patch
@@ -157,7 +157,7 @@ index e84378684643..0bfa7199c974 100644
 +
 +      /* 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))
++               false)
 +                      return -ENOTTY;
 +
 +      for (i = 0; i < max_acs_id; i++)

Build

主要就是build dependencies有点麻烦,跟之前Proxmox 6时不一样了。我是直接在PVE host上面build的。这期间可能有些dependencies需要有 PVE no-subscription的repository才行。

# Probably not a comprehensive list of build dependencies though
apt install -y \
	dh dh-make dh-python build-essential ncurses-dev \
    xz-utils libssl-dev libelf-dev bison config-package-dev \
    sphinx asciidoc-base dwarves flex libdw-dev libiberty-dev \
    libnuma-dev libslang2-dev lz4 xmlto lintian automake \
    autoconf libtool
 
# Takes more than 1 hour on my machine
make all
 
# If *.deb files are not built, build deb files
make deb

Install New Kernel

确认一下deb文件都build出来了。

root@pve:~/pve-kernel# ls *.deb
linux-tools-6.2_6.2.16-10_amd64.deb
proxmox-headers-6.2.16-10-pve_6.2.16-10_amd64.deb
proxmox-kernel-6.2.16-10-pve_6.2.16-10_amd64.deb
proxmox-kernel-libc-dev_6.2.16-10_amd64.deb
linux-tools-6.2-dbgsym_6.2.16-10_amd64.deb
proxmox-headers-6.2_6.2.16-10_all.deb
proxmox-kernel-6.2_6.2.16-10_all.deb

Then

# Install all package (include kernel)
dpkg -i *.deb

Choose new kernel

pve-efiboot-tool 列出来都安装了哪些kernel。

root@pve:~/pve-kernel# pve-efiboot-tool kernel list
Manually selected kernels:
None.
 
Automatically selected kernels:
5.15.108-1-pve
6.2.16-10-pve
6.2.16-8-pve
 
Pinned kernel:
6.2.16-10-pve

其中 6.2.16-10-pve 是新安装的 Kernel。用 pve-efiboot-tool kernel pin 6.2.16-10-pve 锁定默认启动的Kernel。

pve-efiboot-tool kernel pin 5.11.22-7-pve

重启之后,再次运行如下 script 列出IOMMU groups。

#!/bin/bash
for d in $(find /sys/kernel/iommu_groups/ -type l | sort -n -k5 -t/); do
        n=${d#*/iommu_groups/*}; n=${n%%/*}
        printf 'IOMMU Group %s ' "$n"
        lspci -nns "${d##*/}"
done;

Example output:

IOMMU Group 0 00:02.0 VGA compatible controller [0300]: Intel Corporation CometLake-S GT2 [UHD Graphics 630] [8086:9bc8] (rev 03)
IOMMU Group 1 00:00.0 Host bridge [0600]: Intel Corporation 10th Gen Core Processor Host Bridge/DRAM Registers [8086:9b63] (rev 03)
IOMMU Group 2 00:01.0 PCI bridge [0604]: Intel Corporation 6th-10th Gen Core Processor PCIe Controller (x16) [8086:1901] (rev 03)
IOMMU Group 3 00:14.0 USB controller [0c03]: Intel Corporation Comet Lake PCH-V USB Controller [8086:a3af]
IOMMU Group 4 00:16.0 Communication controller [0780]: Intel Corporation Comet Lake PCH-V HECI Controller [8086:a3ba]
IOMMU Group 5 00:17.0 SATA controller [0106]: Intel Corporation 400 Series Chipset Family SATA AHCI Controller [8086:a382]
IOMMU Group 6 00:1b.0 PCI bridge [0604]: Intel Corporation Device [8086:a3e9] (rev f0)
IOMMU Group 7 00:1b.4 PCI bridge [0604]: Intel Corporation Comet Lake PCI Express Root Port #21 [8086:a3eb] (rev f0)
IOMMU Group 8 00:1c.0 PCI bridge [0604]: Intel Corporation Device [8086:a392] (rev f0)
IOMMU Group 9 00:1c.3 PCI bridge [0604]: Intel Corporation Device [8086:a393] (rev f0)
IOMMU Group 10 00:1c.4 PCI bridge [0604]: Intel Corporation Comet Lake PCI Express Root Port #05 [8086:a394] (rev f0)
IOMMU Group 11 00:1c.5 PCI bridge [0604]: Intel Corporation Device [8086:a395] (rev f0)
IOMMU Group 12 00:1d.0 PCI bridge [0604]: Intel Corporation Comet Lake PCI Express Root Port 9 [8086:a398] (rev f0)
IOMMU Group 13 00:1f.0 ISA bridge [0601]: Intel Corporation B460 Chipset LPC/eSPI Controller [8086:a3c8]
IOMMU Group 13 00:1f.2 Memory controller [0580]: Intel Corporation Cannon Lake PCH Power Management Controller [8086:a3a1]
IOMMU Group 13 00:1f.3 Audio device [0403]: Intel Corporation Comet Lake PCH-V cAVS [8086:a3f0]
IOMMU Group 13 00:1f.4 SMBus [0c05]: Intel Corporation Comet Lake PCH-V SMBus Host Controller [8086:a3a3]
IOMMU Group 14 01:00.0 VGA compatible controller [0300]: NVIDIA Corporation GA104 [GeForce RTX 3070] [10de:2484] (rev a1)
IOMMU Group 15 01:00.1 Audio device [0403]: NVIDIA Corporation GA104 High Definition Audio Controller [10de:228b] (rev a1)
IOMMU Group 16 03:00.0 Non-Volatile memory controller [0108]
IOMMU Group 17 05:00.0 Ethernet controller [0200]: Realtek Semiconductor Co., Ltd. RTL8111/8168/8411 PCI Express Gigabit Ethernet Controller [10ec:8168] (rev 15)
IOMMU Group 18 06:00.0 SATA controller [0106]: Marvell Technology Group Ltd. 88SE9128 PCIe SATA 6 Gb/s RAID controller [1b4b:9128] (rev 20)
IOMMU Group 19 07:00.0 SATA controller [0106]: ASMedia Technology Inc. Device [1b21:1064] (rev 02)
IOMMU Group 20 08:00.0 Non-Volatile memory controller [0108]

可以看到 Ethernet controller 和两个 SATA Controller 已经属于不同的 IOMMU Group了。(之前,三个设备都在Group 9)

后记|Appendix

这个目前只能说是暂时的解决方案。终归用 Consumer grade 的主板做虚拟机平台总还是有点不方便。这个ACS Override的方法(哪怕不重新编译 kernel ),实际上也是有安全隐患的。

Why is pcie_acs_override=downstream,multifunction discouraged

参考|References

Tutorial Building the PVE Kernel on Proxmox VE 6.x