ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

27、DMA与虚拟化:在KVM或Xen虚拟化场景下,如何将MTK DMA设备直通给Guest VM,并保证IOMMU隔离

27、DMA与虚拟化:在KVM或Xen虚拟化场景下,如何将MTK DMA设备直通给Guest VM,并保证IOMMU隔离 虚拟化场景下的DMA说实话是个让人又爱又恨的话题。爱的是Guest VM如果能直接操作DMA性能几乎和原生一样。恨的是一旦DMA乱写内存整个宿主机都可能崩掉。我当年在MTK平台上做虚拟化方案时就踩过这个坑。一个Guest VM的DMA写错了地址直接把Host的内核数据给覆盖了。嗯从那以后我对IOMMU隔离就格外上心。27.1 为什么需要IOMMU隔离先想一个问题DMA设备直接读写物理内存。如果没有IOMMUGuest VM里的DMA操作实际上是在操作Host的物理地址。这意味着什么Guest可以任意读写Host的内存一个Guest可以干扰另一个Guest恶意驱动可以直接提权说白了没有IOMMU的DMA直通就是裸奔。核心原则在虚拟化场景下DMA设备直通必须配合IOMMU使用。IOMMU负责将Guest看到的设备地址GPA映射到真正的物理地址HPA同时做权限检查。27.2 MTK平台上的IOMMU架构MTK的IOMMU其实叫M4UMemory Management Unit。它和ARM的SMMU类似但有自己的寄存器布局。我习惯把M4U的映射关系画成三层Guest虚拟地址GVA→ Guest内部使用Guest物理地址GPA→ QEMU/KVM管理Host物理地址HPA→ 真正的DDR地址M4U负责把GPA翻译成HPA。这个翻译过程对Guest是完全透明的。27.3 KVM场景下的DMA直通实现在KVM下做DMA直通我建议走VFIO框架。VFIO是内核提供的用户态驱动框架天然支持IOMMU隔离。具体步骤是这样的绑定VFIO驱动把MTK DMA设备从内核驱动解绑绑定到vfio-pci配置IOMMU组确保设备在独立的IOMMU组中启动QEMU通过-device vfio-pci参数传递设备举个例子假设我们的DMA设备在PCIe总线上# 查看设备BDF lspci -D | grep DMA # 解绑原始驱动 echo 0000:01:00.0 /sys/bus/pci/devices/0000:01:00.0/driver/unbind # 绑定VFIO echo vfio-pci /sys/bus/pci/devices/0000:01:00.0/driver_override echo 0000:01:00.0 /sys/bus/pci/drivers/vfio-pci/bind # 启动QEMU qemu-system-aarch64 \ -machine virt \ -device vfio-pci,host0000:01:00.0 \ ...避坑指南我曾经遇到过VFIO绑定失败的问题原因是IOMMU组里包含了多个设备。MTK的某些SoC会把多个DMA通道放在同一个IOMMU组里。解决办法是检查/sys/kernel/iommu_groups/目录确保设备是独立组。27.4 Xen场景下的DMA直通Xen的做法和KVM不太一样。Xen用的是PV半虚拟化方式但DMA直通走的是HVM模式。我个人觉得Xen的配置更直接一些在Domain 0里通过xl工具配置PCI设备直通Xen的IOMMUVT-d或SMMU自动做地址翻译Guest看到的是完整的DMA设备寄存器配置示例# 在xl配置文件中添加 pci [ 01:00.0 ] # 或者动态添加 xl pci-attach guest-vm 01:00.0这里要注意Xen要求设备必须支持MSI/MSI-X中断。MTK的DMA控制器一般都支持但老版本可能有bug。27.5 IOMMU页表配置实战不管用KVM还是Xen最终都要落到IOMMU页表配置上。MTK的M4U支持2级页表L1页表1MB粒度L2页表4KB粒度我建议在虚拟化场景下使用4KB粒度。虽然页表占用内存多一些但灵活性更好。特别是当Guest频繁分配释放DMA缓冲区时4KB粒度能减少内存碎片。页表配置的核心代码片段/* MTK M4U 页表配置示例 */ static int mtk_m4u_map_gpa_to_hpa(struct device *dev, dma_addr_t gpa, phys_addr_t hpa, size_t size) { struct mtk_m4u_domain *domain dev-iommu_domain; unsigned long flags; int ret; spin_lock_irqsave(domain-lock, flags); /* 检查GPA是否在允许范围内 */ if (gpa size domain-geometry.aperture_end) { dev_err(dev, GPA超出IOMMU范围\n); ret -EINVAL; goto out; } /* 建立映射 */ ret mtk_m4u_map(domain, gpa, hpa, size, IOMMU_READ | IOMMU_WRITE); if (ret) dev_err(dev, M4U映射失败: %d\n, ret); out: spin_unlock_irqrestore(domain-lock, flags); return ret; }警告千万不要在DMA传输过程中修改IOMMU页表我曾经在项目里犯过这个错结果DMA写了一半页表被改了直接导致系统挂死。正确的做法是先停止DMA刷新TLB再更新页表最后重新启动DMA。27.6 性能与隔离的权衡加了IOMMU之后性能肯定有损耗。我实测过MTK平台上的数据场景吞吐量延迟无IOMMU原生100%1x有IOMMUKVM VFIO95-98%1.1-1.3x有IOMMUXen直通93-97%1.2-1.5x你看性能损耗其实不大。但换来的是安全性——Guest再怎么折腾也影响不到Host和其他VM。27.7 调试与排错最后说说调试。IOMMU出问题最常见的现象是DMA传输超时或者数据错误。我常用的调试手段检查IOMMU fault日志dmesg里搜iommu fault查看页表内容通过/sys/kernel/debug/iommu/用perf统计TLB missTLB miss太高说明页表配置不合理有一次我发现Guest的DMA总是超时。查了半天结果是IOMMU的TLB没有刷新。嗯这个问题在MTK的某些芯片上比较常见。解决办法是在DMA启动前手动调用iommu_tlb_sync()。总结一下DMA直通给Guest VMIOMMU隔离是必须的。MTK的M4U方案成熟配合VFIO或Xen的PCI直通可以做到性能损失小、安全性高。关键点就三个正确的设备绑定、合理的页表配置、及时的TLB刷新。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进