核完了。结论是:技术内核基本属实,但"目前已经可用"和"建议 6.10+"这两处判断与主线代码现状有明显偏差。下面逐条拆。
一、逐条核实结果
| 说法 | 判定 | 依据 |
|---|---|---|
| 2026 年 8 月 LWN 有这条报道 | ✅ 基本属实,但性质要说清 | LWN 条目 lwn.net/Articles/1090023,2026-08-21,作者 Fangyu Yu(fangyu.yu@linux.alibaba.com),标题 [RFC PATCH v3 00/10] iommu/riscv: Add hardware dirty tracking for second-stage domains |
| 二级域硬件脏页跟踪 | ✅ 属实 | AMO_HWAD(capabilities bit 24)+ DC.tc.GADE(bit 7) |
| 测试中 NVMe + e1000e 透传到 L2 guest | ✅ 属实(作者自述) | v3 cover letter 原文:"Test on QEMU RISC-V, a nvme and an e1000e device was passed through to an L2 guest via vfio-pci + iommufd" |
| 同时启用 KVM_VFIO 接口 | ✅ 属实,且是同一系列的补丁 | 该 RFC 系列直接打包了 Tomasz Jeznach 的 RISC-V: KVM: Enable KVM_VFIO interfaces on RISC-V arch |
| NVMe VFIO 透传"目前已经可用" | ⚠️ 仅限打过补丁的树,主线不可用 | 主线 v7.3-rc2 源码核对见下文 |
| 建议 6.10+ | ❌ 明显错误 | RISC-V IOMMU 驱动 v6.13 才合入 |
| 用 Ventana 内核树 | ✅ 属实,且有官方文档背书 | QEMU 官方文档明确推荐 |
| 需要支持 AIA 的中断控制器 | ✅ 属实 | IMSIC / guest interrupt file 相关 |
二、三个需要修正的点
1. "LWN 报道"实际是 LWN 的邮件归档页
lwn.net/Articles/1090023 的页面结构是 From / To / Subject / Date / Message-ID / Archive-link------这是 LWN 对 LKML 邮件的镜像归档,不是 LWN 编辑部撰写的专题文章。措辞上写成"LWN 收录了这封邮件"更准确。
2. 内核版本:不是 6.10+,是 6.13 起步,且 VFIO 那部分至今未合入
RISC-V IOMMU 驱动是 v6.13 合入的,Joerg Roedel 的 6.13 pull request 里明确列着 "New IOMMU driver for RISC-V"。6.10 上连 drivers/iommu/riscv/ 这个目录都不存在。
而我拉取的主线 v7.3-rc2 源码显示,VFIO 透传所需的东西一项都没进主线:
/* drivers/iommu/riscv/iommu.c ------ 主线 v7.3-rc2 */
static const struct iommu_ops riscv_iommu_ops = {
.of_xlate = riscv_iommu_of_xlate,
.identity_domain = &riscv_iommu_identity_domain,
.blocked_domain = &riscv_iommu_blocking_domain,
.release_domain = &riscv_iommu_blocking_domain,
.domain_alloc_paging = riscv_iommu_alloc_paging_domain, /* ← 老回调,无 flags 参数 */
.device_group = riscv_iommu_device_group,
.probe_device = riscv_iommu_probe_device,
.release_device = riscv_iommu_release_device,
};
注意这份 iommu_ops 里没有 .capable、没有 .domain_alloc_paging_flags、没有 dirty_ops。对照 v3 补丁要改成的样子:
/* 补丁新增 ------ 主线完全没有 */
static const struct iommu_dirty_ops riscv_iommu_dirty_ops = {
IOMMU_PT_DIRTY_OPS(riscv_64),
.set_dirty_tracking = riscv_iommu_set_dirty_tracking,
};
static bool riscv_iommu_capable(struct device *dev, enum iommu_cap cap)
{
case IOMMU_CAP_DIRTY_TRACKING:
return !!(iommu->caps & RISCV_IOMMU_CAPABILITIES_AMO_HWAD);
}
其余几处 grep 结果同样是否定的:
| 检查项 | 主线 v7.3-rc2 实际内容 | 结论 |
|---|---|---|
arch/riscv/kvm/Kconfig |
无任何 select KVM_VFIO |
KVM_VFIO 未开启 |
drivers/vfio/Kconfig |
select VFIO_IOMMU_TYPE1 if MMU && (X86 || S390 || ARM || ARM64) |
无 RISCV |
drivers/iommu/Kconfig |
config IOMMU_DMA / def_bool ARM64 || X86 || S390 |
无 RISCV |
drivers/iommu/riscv/ 目录 |
只有 iommu.c / iommu-bits.h / iommu-pci.c / iommu-platform.c / iommu.h |
无 iommu-ir.c,MSI 重映射未合入 |
drivers/iommu/generic_pt/fmt/riscv.h |
无 is_write_dirty / make_write_clean / make_write_dirty |
脏页 PTE ops 未合入 |
唯一的例外是 iommu-bits.h 里这两个位定义早就在主线了------但它们只是 spec 定义的寄存器位,不代表功能已接线:
/* drivers/iommu/riscv/iommu-bits.h ------ 主线已存在 */
#define RISCV_IOMMU_CAPABILITIES_AMO_HWAD BIT_ULL(24)
#define RISCV_IOMMU_DC_TC_GADE BIT_ULL(7)
#define RISCV_IOMMU_DC_TC_SADE BIT_ULL(8)
QEMU 官方文档的说法也印证了这点:"Linux kernel iommu support was merged in v6.13... As of v6.17, it does not have support for features like VFIO passthrough. There is a VFIO RFC series that is not yet merged."
所以准确表述是:NVMe VFIO 透传在打齐补丁栈的内核上可跑通,但在任何 mainline 内核上开箱即用是做不到的。
3. 依赖不止"AIA",还有两个硬约束
AIA 这条没错------QEMU 那边的标准命令行就是 -M virt,aia=aplic-imsic,aia-guests=5,MSI 重映射本身就是把 IMSIC 的 MSI 目标页预先映射到 IOVA。但要补两点:
- 仍需
vfio allow_unsafe_interrupts=1。Andrew Jones 原话:"The interrupt-remapping domain does not yet validate MSI data, so VFIO still requires allow_unsafe_interrupts=1"。RISC-V IOMMU 规范不校验 MSI data,这个限制是架构性的。 - irqbypass(MSI 直投 guest interrupt file)尚未支持,被明确推到后续系列。而 Ventana 树之所以被推荐,恰恰因为它额外带了 irqbypass。
三、"二级域脏页跟踪"到底在做什么
这是整件事里技术含量最高、也最容易被误读的部分,值得解释一下。
RISC-V IOMMU 有两级页表:一级 iosatp(VA→GPA,给 guest 自己管),二级 iohgatp(GPA→SPA,给 hypervisor 管)。设备透传给 VM 时,宿主机需要知道"这块内存被 DMA 写脏了没有"------这是虚拟机热迁移收敛的关键。没有硬件支持就只能靠写保护 + 缺页模拟,代价极高。
RISC-V 的做法是:硬件带 AMO_HWAD 能力时,只要把设备上下文的 DC.tc.GADE 打开,IOMMU 就会在设备首次写入某页时原子地把 PTE 的 D 位(bit 7)置上。软件只需读走并清掉:
/* drivers/iommu/generic_pt/fmt/riscv.h ------ 补丁新增的三个 PTE ops */
static inline bool riscvpt_entry_is_write_dirty(const struct pt_state *pts)
{
/* Svnapot 64K 时一个叶子跨 16 个 PTE,必须全查,否则丢脏标记 */
for (; tablep != end; tablep++)
if (READ_ONCE(*tablep) & RISCVPT_D)
return true;
return false;
}
static inline bool riscvpt_entry_make_write_dirty(struct pt_state *pts)
{
pt_riscv_entry_t new = pts->entry | RISCVPT_D;
return try_cmpxchg64(tablep, &pts->entry, new);
}
对外暴露的就是 iommufd 那两个标准 ioctl:IOMMU_HWPT_SET_DIRTY_TRACKING / IOMMU_HWPT_GET_DIRTY_BITMAP。
注意这个系列只做二级域 ,一级域(iosatp)完全没动------因为只有二级域才是 hypervisor 为 VM 分配 IOMMU_HWPT_ALLOC_NEST_PARENT 或 IOMMU_HWPT_ALLOC_DIRTY_TRACKING 时创建的。这也正好解释了为什么测试用的是 L2 guest:二级域服务于嵌套场景,L1 管一级、L0 管二级。
四、它还只是个 RFC,别当定论
系列从 v1(4 月)走到 v3(8 月),中间被 Jason Gunthorpe 改过一次设计:
- v1 想动态开关
DC.tc.GADE,Jason 说没必要------直接在上 S2 域时预设 GADE,靠 D 位预置规避硬件开销; - v1 想加
PT_FEAT_RISCV_DIRTY_TRACKING_ACTIVE特性位,Jason 说 D 位该由 iommufd core 管,用户态在首次清 D 之前本就该假设全脏。
作者两处都接受并改掉了,v3 里 GADE 改成静态预置。另外作者自己在 cover letter 里也承认端到端测试还没做:"Build a dedicated end-to-end test case that drives the full flow... so that the behaviour can be regression-tested beyond the KUnit PTE-level coverage" 。也就是说,NVMe 那条测试记录是手工验证 + KUnit 单元级验证,还没有自动化回归覆盖。
再补一个细节:v1/v2 的测试写的是 "a virtio-net and an e1000e" ,NVMe 是 v3 才出现的。如果你在别处看到 virtio-net 版本的引用,那不是笔误,是更早的版本。
五、修正后的说法
2026 年 8 月,阿里巴巴的 Fangyu Yu 向 LKML 提交了 RFC v3 补丁集(LWN 有邮件归档),为 RISC-V IOMMU 的二级(iohgatp)域接上硬件脏页跟踪,利用
AMO_HWAD能力 +DC.tc.GADE让 IOMMU 在 DMA 首次写入时自动置 PTE 的 D 位,并通过 iommufd 的SET_DIRTY_TRACKING/GET_DIRTY_BITMAP暴露给上层。作者在 QEMU RISC-V 上把 nvme 与 e1000e 通过 vfio-pci + iommufd 透传进 L2 guest 验证通过,系列本身也打包了 Tomasz Jeznach 的 KVM_VFIO 使能补丁。但截至主线 v7.3-rc2,这套东西尚未合入 :
arch/riscv/kvm/Kconfig无select KVM_VFIO,VFIO_IOMMU_TYPE1与IOMMU_DMA的架构白名单里都没有 RISCV,MSI 重映射(iommu-ir.c)也还在 review 中。RISC-V IOMMU 驱动本身是 v6.13 才进的主线,所谓"6.10+"站不住。想实际跑 NVMe 透传,需要 AIA/IMSIC 中断控制器、打齐 MSI 重映射 + IOMMU_DMA + VFIO 使能 + 本系列补丁的内核------QEMU 官方推荐的捷径是 Ventana 的dev-upstream树,另需vfio allow_unsafe_interrupts=1,且 irqbypass 暂不支持。