- 虚拟化
- 硬件仿真
【免费下载链接】qemu
Official QEMU mirror. Please see https://www.qemu.org/contribute/ for how to submit changes to QEMU. Pull Requests are disabled. Please only use release tarballs from the QEMU website.
QEMU 依据 RISC-V IOMMU 规范 1.0 版本实现了完整的 IOMMU 模拟,包含 PCIe 参考设备(riscv-iommu-pci)和平台总线设备(riscv-iommu-sys)两种形态。本文以仓库文档 docs/specs/riscv-iommu.rst 为主体,结合 hw/riscv/riscv-iommu.c、hw/riscv/riscv-iommu-pci.c、hw/riscv/riscv-iommu-sys.c 等源码实现,讲解在 virt 机器上启用两种 IOMMU 设备的完整方法、全部可配置参数及其底层含义,帮助读者快速上手 RISC-V 平台的 DMA 重映射与直通实验。
背景:QEMU 中的 RISC-V IOMMU 支持
QEMU 实现了基于 RISC-V IOMMU 规范 1.0(riscv-iommu.pdf,即iommu1.0.0发布版本)的 IOMMU 模拟。该规范定义了设备如何通过内存驻留的数据结构(Device Directory Table、进程上下文、命令队列、故障队列等)完成从设备请求的 IOVA(I/O Virtual Address)到系统物理地址的翻译,并提供 MSI 重映射、ATS(Address Translation Services)等能力。
这套模拟在仓库中由以下几个文件共同构成:
- hw/riscv/riscv-iommu.c:IOMMU 核心状态机与翻译实现(约 3000 行),涵盖 DDT 解析、命令队列/故障队列处理、地址翻译缓存、HPM 硬件性能计数器等;
- hw/riscv/riscv-iommu.h:核心状态结构
RISCVIOMMUState与翻译上下文定义; - hw/riscv/riscv-iommu-bits.h:寄存器偏移、能力位等规范位域定义;
- hw/riscv/riscv-iommu-pci.c:PCIe 参考设备;
- hw/riscv/riscv-iommu-sys.c:平台总线设备;
- hw/riscv/riscv-iommu-hpm.c 与 hw/riscv/riscv-iommu-hpm.h:硬件性能计数器(HPM)实现;
- include/hw/riscv/iommu.h:对外接口头文件。
模拟分为两种设备形态,virtRISC-V 机器对两者都兼容。
riscv-iommu-pci 参考 PCIe 设备
设备形态与 PCI ID
riscv-iommu-pci按照规范中 "Integrating an IOMMU as a PCIe device"(将 IOMMU 集成进 PCIe 设备)一节的建议实现:PCI 基础类(base class)为 08h,子类(sub-class)为 06h,编程接口为 00h,即标准 "System peripheral / IOMMU" 类型。这一点在源码 hw/riscv/riscv-iommu-pci.c 中通过RISCV_PCI_CLASS_SYSTEM_IOMMU 0x0806常量体现。
作为参考设备,它不实现规范之外的任何扩展,因此使用 QEMU 提供的通用默认 PCI ID:1b36:0014(vendor 0x1b36 为 Red Hat 在 QEMU 中使用的通用厂商 ID,device ID 0014 对应 riscv-iommu)。源码中默认值由 hw/riscv/riscv-iommu-pci.c 的DEFINE_PROP_UINT16("vendor-id", ...PCI_VENDOR_ID_REDHAT)和DEFINE_PROP_UINT16("device-id", ...PCI_DEVICE_ID_REDHAT_RISCV_IOMMU)给出,并且支持通过属性覆盖。
设备实例化时(riscv_iommu_pci_realize),还会在 BAR0 中注册 IOMMU 寄存器内存区域,并初始化 5 个 MSI-X 向量(hw/riscv/riscv-iommu-pci.c),其中 4 个用于 ICVEC(命令队列/故障队列/性能监控/页请求四种中断向量),1 个用于 MRIF(内存驻留中断文件)的通知 MSI。若平台不支持 MSI-X,驱动可退化为基于定时器/轮询的通知处理方式,代码中通过msix_init返回-ENOTSUP时打印告警处理(hw/riscv/riscv-iommu-pci.c)。
在 virt 机器上启用
将设备加入 virt 机器非常简单:
$ qemu-system-riscv64 -M virt -device riscv-iommu-pci,[optional_pci_opts] (...)这会在板卡上添加一个 RISC-V IOMMU PCI 设备,并可附带额外的 PCI 参数(例如 PCI 总线地址addr=1.0)。IOMMU 的行为由规范定义,但其具体运作方式与操作系统实现相关(OS dependent)。
与 Linux 内核的配合现状
Linux 内核的 RISC-V IOMMU 支持在v6.13合入主线。QEMU 的 IOMMU 模拟可以与主线内核配合,用于简单的 IOMMU PCIe 支持场景。
截至 v6.17,主线内核尚不支持 VFIO 直通等特性;存在一个尚未合入的 VFIO RFC 系列补丁,Ventana Micro Systems 公开维护的 ventana-linux 内核仓库可用于测试 VFIO 相关功能。
需要特别说明的是:v6.13+ 的 Linux 内核驱动会使用 IOMMU 设备为系统中所有符合条件的网卡创建 IOMMU 组,与设备在命令行中添加的先后顺序无关。因此,下面两条命令行在 IOMMU 内核驱动看来是等价的——都会为两张 e1000e 网卡创建 IOMMU 组:
$ qemu-system-riscv64 \ -M virt,aia=aplic-imsic,aia-guests=5 \ -device riscv-iommu-pci,addr=1.0 \ -device e1000e,netdev=net1 -netdev user,id=net1,net=192.168.0.0/24 \ -device e1000e,netdev=net2 -netdev user,id=net2,net=192.168.200.0/24 \ (...) $ qemu-system-riscv64 \ -M virt,aia=aplic-imsic,aia-guests=5 \ -device e1000e,netdev=net1 -netdev user,id=net1,net=192.168.0.0/24 \ -device e1000e,netdev=net2 -netdev user,id=net2,net=192.168.200.0/24 \ -device riscv-iommu-pci,addr=3.0 \ (...)注意-M virt,aia=aplic-imsic,aia-guests=5中的 AIA 相关选项:IOMMU 的中断通知依赖 AIA(Advanced Interrupt Architecture)中断控制器,aia=aplic-imsic指定使用 APLIC + IMSIC 组合,aia-guests=5指定 IMSIC 的 guest MMIO 页数量。
另外,在 virt 机器上热插 riscv-iommu-pci 设备时(virt_machine_device_plug_cb),QEMU 会自动为设备生成 FDT 节点(create_fdt_iommu)并关闭平台 IOMMU 设备(s->iommu_sys = ON_OFF_AUTO_OFF),见 hw/riscv/virt.c,二者互斥、不会同时启用。
可配置属性一览
riscv-iommu-pci提供了下列属性来控制设备能力(属性由 hw/riscv/riscv-iommu.c 的核心属性表与 hw/riscv/riscv-iommu-pci.c 的 PCI 属性表共同定义,通过qdev_alias_all_properties暴露给 PCI 设备):
| 属性 | 含义 | 默认值 |
|---|---|---|
bus | IOMMU 设备所使用的 PCI 总线 | 0 |
ioatc-limit | 地址翻译缓存(I/O Address Translation Cache)大小 | 2MB(源码中LIMIT_CACHE_IOT,即 1 << 20 条目) |
intremap | 启用/禁用 MSI 中断重映射 | true |
ats | 启用 ATS(Address Translation Services)支持 | true |
off | 复位后(out-of-reset)的翻译模式:on表示 DMA 禁用(OFF 模式),off表示 BARE(直通/passthrough) | true |
s-stage | 启用 S/VS-Stage 地址翻译(单级 S 阶段) | true |
g-stage | 启用 G-Stage 地址翻译(guest 阶段) | true |
hpm-counters | 硬件性能计数器(HPM)数量,最大 31,默认 31;设为 0 关闭 HPM | 31 |
vendor-id/device-id | PCI 设备 ID | 1b36:0014(Red Hat) |
revision | PCI 修订号 | 0x01 |
每个布尔属性都直接映射到核心状态结构 hw/riscv/riscv-iommu.h 中的对应开关(enable_msi、enable_ats、enable_off、enable_s_stage、enable_g_stage),在设备复位时决定cap(能力寄存器)与fctl(功能控制寄存器)的初始状态。例如off属性对应enable_off,复位时直接决定 DDTP(Device Directory Table Pointer)寄存器翻译模式的初始值——为on时初始化为 OFF 模式(DMA 全部被拒绝),为off时初始化为 BARE 模式(直通,不翻译),相关逻辑见 hw/riscv/riscv-iommu.c。
使用示例:
$ qemu-system-riscv64 -M virt \ -device riscv-iommu-pci,addr=1.0,intremap=off,ats=off \ -device riscv-iommu-pci,off=off \ (...)其中第一条关闭 MSI 重映射与 ATS,第二条让 IOMMU 复位后直接处于 BARE 直通模式(相当于 passthrough 启动,便于先验证整机启动、再在 guest 中动态开启 IOMMU)。
riscv-iommu-sys 平台总线设备
设备形态
riscv-iommu-sys将同一套 RISC-V IOMMU 模拟实现为平台总线(platform bus / sysbus)设备,供 RISC-V 板卡使用。与 PCI 版本相比,它不通过 PCI 配置空间暴露,而是通过 MMIO 寄存器直接映射到系统内存空间。
从源码 hw/riscv/riscv-iommu-sys.c 可以看出,其实现要点包括:
- 继承
SysBusDevice,寄存器内存区域iommu.regs_mr通过sysbus_init_mmio暴露; - 支持两种中断通知方式(
riscv_iommu_sysdev_notify):当 FCTL 寄存器的WSI(Wired/软件中断)位被置位时,通过qemu_irq_pulse触发有线中断;否则通过 MMIO 模拟的 MSI-X 表(msix_table_mmio)向内存写入 MSI,二者由RISCV_IOMMU_CAP_IGS_BOTH能力位(IGS 模式为 both)支持; - 定义 4 个中断(
RISCV_IOMMU_INTR_COUNT),从base-irq开始接入irqchip(AIA 中断控制器); - 物理地址宽度按机器位数设置:riscv64 使用 56 位(44 位 PPN),riscv32 使用 34 位(22 位 PPN),见 hw/riscv/riscv-iommu-sys.c。
在 virt 机器上启用
virt 板卡上该设备默认禁用。使用iommu-sys机器选项启用:
$ qemu-system-riscv64 -M virt,iommu-sys=on (...)该选项在 hw/riscv/virt.c 中注册为OnOffAuto类型的机器属性(默认值为auto,见 hw/riscv/virt.c,virt_is_iommu_sys_enabled只在ON_OFF_AUTO_ON时返回 true,见 hw/riscv/virt.c)。启用时,virt 机器会调用riscv_create_iommu_sys创建设备,映射到 VIRT_IOMMU_SYS 固定地址(0x3010000,大小 0x1000,见 hw/riscv/virt.c),并接入IOMMU_SYS_IRQ(见 hw/riscv/virt.c),同时在 FDT 中生成对应节点。
关键互斥逻辑:在 virt 机器上,当插入 PCI 形态的 riscv-iommu-pci(或 virtio-iommu-pci)时,virt_machine_get_hotplug_handler会自动把iommu_sys置为 off(hw/riscv/virt.c),避免同一板卡上同时出现两个 IOMMU。
固定能力配置
virt 板卡上无法通过 QEMU 命令行配置该设备的能力,设备以固定的 riscv-iommu 选项组合初始化:
ioatc-limit:默认值(2MB)intremap:启用(MSI 重映射开启)ats:启用off:on(复位后 DMA 禁用,即 OFF 模式)s-stage:启用g-stage:启用
即平台设备默认启用全部能力(包括 S/G 两阶段翻译与 MSI 重映射),但复位后处于 OFF 模式(DMA 默认被拦截),需要软件(固件/内核驱动)在运行时显式配置并切换到 BARE 或翻译模式。
IOMMU 内部工作原理解读
理解了两种设备形态与参数,再结合核心实现 hw/riscv/riscv-iommu.c 可以更清楚地把握其运作方式。
核心状态结构
RISCVIOMMUState(hw/riscv/riscv-iommu.h)承载了 IOMMU 的全部状态:规范版本号、进程 ID 位宽、物理地址位宽、能力寄存器(cap)、已启用功能(fctl)、DDT 根指针(ddtp)、命令/故障/页请求三个队列的基地址与索引掩码(cq_addr/cq_mask、fq_addr/fq_mask、pq_addr/pq_mask),以及两张软件缓存——设备翻译上下文缓存ctx_cache与 I/O 地址翻译缓存iot_cache(容量由iot_limit即ioatc-limit属性控制)。
地址翻译缓存(IATC)与上下文缓存
规范要求的 I/O 地址翻译缓存上限为 2MB(LIMIT_CACHE_IOT即1U << 20),上下文缓存上限为 128 条(LIMIT_CACHE_CTX即1U << 7,见 hw/riscv/riscv-iommu.c)。翻译缓存条目RISCVIOMMUEntry(hw/riscv/riscv-iommu.c)记录翻译标签(bypass/单级/仅 G 级/嵌套)、IOVA 页号、进程软上下文 ID(pscid)、物理页号、guest 软上下文 ID(gscid)与读写权限。翻译模式通过RISCVIOMMUTransTag枚举区分(hw/riscv/riscv-iommu.c),其中嵌套翻译(Nested)即同时使用 S 级与 G 级两阶段页表。
中断与故障上报路径
IOMMU 的四类事件(命令队列 CQ、故障队列 FQ、性能监控 PM、页请求队列 PQ)通过icvec寄存器选择 MSI-X 向量(riscv_iommu_get_icvec_vector,hw/riscv/riscv-iommu.c),随后调用设备形态注册的通知回调notify——PCI 版本走msix_notify(hw/riscv/riscv-iommu-pci.c),平台版本按 WSI 位选择有线中断或内存写 MSI(hw/riscv/riscv-iommu-sys.c)。故障(如 MSI 写失败)会被记录进故障队列fq_record,事件原因(cause)包括 MSI 写故障(RISCV_IOMMU_FQ_CAUSE_MSI_WR_FAULT)等(hw/riscv/riscv-iommu-sys.c)。
设备 DMA 空间绑定
每个接入的设备都会在 IOMMU 下创建一个RISCVIOMMUSpace(hw/riscv/riscv-iommu.c),包含该设备的 IOVA 内存区域与地址空间,并记录其总线与devfn(即规范中的 requester/device_id)。设备 ID 由PCI_BUILD_BDF结合总线号构成(hw/riscv/riscv-iommu.c)。riscv_iommu_pci_setup_iommu(在两种设备 realize 时都会调用)将该 IOMMU 挂接到 PCI 总线的pci_setup_iommu回调上,使总线上的所有设备 DMA 都经过 IOMMU 翻译。
复位行为
两种设备的复位都委托给riscv_iommu_reset(分别见 hw/riscv/riscv-iommu-pci.c 与 hw/riscv/riscv-iommu-sys.c),复位时根据enable_off决定 DDTP 初始翻译模式(OFF 或 BARE),并清零队列基地址与缓存。这正是前面命令行示例中off参数影响启动行为的底层原因。
迁移限制与注意事项
当前riscv-iommu设备的 VMState 描述被标记为unmigratable = 1(hw/riscv/riscv-iommu-pci.c),即该设备暂不支持虚拟机迁移(live migration),在涉及迁移的场景中需要留意。此外,设备不支持热插拔(hotpluggable = false),但属于用户可创建设备(user_creatable = true,见 hw/riscv/riscv-iommu-pci.c),核心 IOMMU 内部设备则相反(user_creatable = false,只能由 PCI/sys 外壳创建,见 hw/riscv/riscv-iommu.c)。
实践建议小结
- 快速体验:使用
-M virt -device riscv-iommu-pci配合 v6.13+ 主线内核即可验证基础 IOMMU 组创建与 DMA 重映射;如需同时使用 AIA 中断,请加上-M virt,aia=aplic-imsic,aia-guests=5。 - 对比两种形态:PCI 版本灵活可控(可在命令行逐项开关能力、指定 PCI 地址与设备 ID),平台版本不可配置但固定启用全能力,适合固定板卡拓扑场景;两者互斥,不可同时启用。
- 排查启动问题:若 guest 启动后 DMA 异常,先检查
off参数——默认复位后为 OFF 模式(DMA 被拒),需要 guest 驱动完成初始化切换到翻译或 BARE 模式;需要直通启动时显式传off=off。 - 性能与监控:默认 31 个 HPM 计数器可观测 IOMMU 行为;需要关闭时设
hpm-counters=0。 - 生产部署:注意该设备当前不支持迁移,且主线内核(截至 v6.17)尚不支持 VFIO 直通,直通场景需使用 Ventana Micro Systems 的 ventana-linux 内核分支测试。
- 虚拟化
- 硬件仿真
【免费下载链接】qemu
Official QEMU mirror. Please see https://www.qemu.org/contribute/ for how to submit changes to QEMU. Pull Requests are disabled. Please only use release tarballs from the QEMU website.
相关推荐
QEMU iommu-testdev 深度解析:用于裸机 IOMMU 翻译验证的测试 PCI 设备
QEMU iommu testdev 深度解析:用于裸机 IOMMU 翻译验证的测试 PCI 设备 iommu testdev 是 QEMU 仓库中一个最小化、
虚拟化硬件仿真Cloud Hypervisor 虚拟 IOMMU 实战:基于 virtio-iommu 的 DMA 隔离、嵌套虚拟化与性能调优
Cloud Hypervisor 虚拟 IOMMU 实战:基于 virtio iommu 的 DMA 隔离、嵌套虚拟化与性能调优 虚拟 IOMMU(Virtua
Agent 沙箱虚拟化云原生人工智能后端容器运行时Cloud Hypervisor 虚拟 IOMMU(virtio-iommu)使用指南:从嵌套虚拟化保护到 VFIO 直通与巨页优化
Cloud Hypervisor 虚拟 IOMMU(virtio iommu)使用指南:从嵌套虚拟化保护到 VFIO 直通与巨页优化 导读 本文基于 Cloud
云原生
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考