4. AMD-Vi(IOMMU)与设备隔离
在 Ryzen Embedded 平台上,CPU 侧的 MMU 只负责把虚拟地址翻译成物理地址,而外设发起的 DMA 请求使用的是设备自己的地址空间。AMD-Vi 就是 AMD 平台上的 IOMMU 实现,它位于设备与内存之间,为每个设备提供独立的地址翻译与访问控制。理解 AMD-Vi 的关键在于:它把“设备可见的地址”与“真实物理内存”解耦,从而让直通、隔离和中断投递都变得可控。
IOMMU 的工作原理
当设备发起 DMA 时,请求中携带的是 IOVA(I/O Virtual Address)。AMD-Vi 根据请求来源的 BDF(Bus/Device/Function)号找到对应的设备表项,再沿多级页表把 IOVA 翻译成系统物理地址 SPA。翻译过程中会检查权限位(读、写、执行)以及页表项的有效性。若地址未映射或权限不符,硬件会阻止该访问并产生 IOMMU 故障事件,而不是让设备直接写坏内存。
AMD-Vi 的翻译结构可以概括为“设备表 → 页表”两级查找。设备表(Device Table)以 BDF 为索引,每个表项指向该设备的页表根;页表则采用与 x86 类似的多级结构,通常为 4 级或 5 级,具体取决于平台配置。设备表项还包含中断重映射表指针、域 ID 等字段,因此设备隔离与中断投递共享同一套设备描述结构。
设备表与页表
设备表是 AMD-Vi 的入口。系统启动时,BIOS 或内核会为每个可能发起 DMA 的 BDF 分配设备表项。表项中的“页表根指针”决定了该设备使用哪一棵页表树。若把两个设备指向同一棵页表树,它们就处于同一隔离域,可以互相访问对方映射的内存;若指向不同页表树,则天然隔离。因此,IOMMU 组的本质就是“共享同一页表树的设备集合”。
页表翻译的粒度通常是 4KB 页,也支持 2MB 或 1GB 大页以减少 TLB 压力。IOMMU 内部有 IOTLB 缓存翻译结果,设备频繁访问同一区域时命中率较高。当页表被修改后,需要通过命令队列发送 IOTLB 失效命令,否则设备可能继续使用旧映射。这一点在热插拔或动态重映射场景中尤其重要。
中断重映射
传统 MSI/MSI-X 中断由设备直接写目标 CPU 的 APIC 地址。在虚拟化环境中,如果让设备直接写物理 APIC,中断会绕过 VMM,无法正确投递给 Guest。AMD-Vi 的中断重映射功能把设备发出的 MSI 地址和数据进行重写:设备仍然写一个“中断地址”,但该地址被 IOMMU 截获,查中断重映射表后转换为目标 vCPU 的虚拟 APIC 投递。这样,直通设备的中断也能被正确注入 Guest,而不会误投到 Host 或其他 Guest。
DMA 隔离与 IOMMU 组
IOMMU 组是 Linux 内核根据 PCIe 拓扑和 IOMMU 能力划分的最小隔离单位。同一个 IOMMU 组内的设备共享页表,无法单独分配给不同 Guest;只有位于不同 IOMMU 组的设备才能分别直通给不同虚拟机。判断一个设备能否安全直通,第一步就是查看它所在的 IOMMU 组是否只包含目标设备。
在 Ryzen Embedded 上,常见的 IOMMU 组划分受 PCIe 根端口和桥的影响。例如,CPU 直连的 PCIe 插槽通常各自成组,而经过 PCIe Switch 扩展的多个设备可能落在同一组内。若一个组内混有必须留给 Host 的设备(如管理网卡),则该组内所有设备都无法直通,需要调整插槽或使用 ACS 覆盖等方案。
在 Ryzen Embedded 上配置 IOMMU 与直通
以下操作以 Linux 环境为例,示例参数属于教学假设,实际值需根据平台手册和硬件拓扑调整。
首先确认固件已开启 IOMMU。在 BIOS 中启用 “IOMMU” 或 “AMD-Vi” 选项,并确保 “ACS Enable” 按需开启。启动后检查内核是否识别:
dmesg | grep -i amd-vi
# 期望看到:AMD-Vi: Interrupt remapping enabled
# 以及:AMD-Vi: Virtual APIC enabled
ls /sys/kernel/iommu_groups/
# 列出所有 IOMMU 组编号
接着查看目标设备所在的组。假设我们要直通一块 NVMe 控制器,BDF 为 0000:03:00.0:
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
若输出显示 03:00.0 所在组只有它自己,则具备直通条件。若组内还有其它设备,需要评估是否可一并直通,或调整硬件插槽。
然后绑定 VFIO 驱动。以 PCI 设备为例,先获取 vendor:device ID:
lspci -nns 03:00.0
# 示例输出:03:00.0 0108: 1022:1234 (rev 01)
echo "1022 1234" > /sys/bus/pci/drivers/vfio-pci/new_id
echo "0000:03:00.0" > /sys/bus/pci/devices/0000:03:00.0/driver/unbind
echo "0000:03:00.0" > /sys/bus/pci/drivers/vfio-pci/bind
绑定后,该设备不再由 Host 原生驱动管理,而是交给 VFIO 框架,由用户态虚拟机监控器将其映射进 Guest。此时 Guest 内的驱动可以正常初始化设备,DMA 访问经过 AMD-Vi 翻译,中断经过 IRT 重映射投递。
lspci 确认设备可见,并观察 dmesg 中是否有 IOMMU 故障事件。若出现 “IOMMU fault” 或 “DMAR” 类报错,通常说明页表映射缺失或权限配置有误。常见问题与排查方向
直通失败时,优先检查三件事:一是设备是否真的位于独立 IOMMU 组;二是内核是否启用了 iommu=pt 或 amd_iommu=on 等参数;三是中断重映射是否开启。若设备能识别但 DMA 报错,多半是页表映射问题;若设备无法识别,则可能是 VFIO 绑定或 PCI 复位问题。对于 Ryzen Embedded,还需注意部分集成外设与 CPU 共享 IOMMU 组,这类设备通常不建议直通。
整体而言,AMD-Vi 把设备隔离从“物理插槽决定”提升为“页表与设备表决定”。掌握设备表、页表、中断重映射表三者的关系,再结合 IOMMU 组判断,就能在 Ryzen Embedded 上稳定地完成设备直通与隔离配置。