4、IOMMU/SMMU驱动框架:Linux内核中的IOMMU子系统、IOMMU驱动接口(API)、设备与IOMMU域的绑定、DMA-API与IOMMU-API

好,咱们今天来聊聊IOMMU的驱动框架。说实话,这部分内容在Linux内核里算是比较“绕”的一块。我当年第一次看IOMMU子系统的代码时,也被那一堆回调函数和抽象层搞得有点晕。但别怕,咱们一层层剥开来看。

4.1 Linux内核中的IOMMU子系统

Linux内核的IOMMU子系统,说白了就是一个“中间人”。它负责把上层的DMA API请求,翻译成底层硬件(比如Intel的VT-d、ARM的SMMU)能听懂的命令。

这个子系统的核心数据结构是struct iommu_ops。嗯,你可以把它理解成一张“能力清单”。每个IOMMU硬件驱动都要实现这张清单上的函数。比如:

  • attach_dev:把设备绑定到某个IOMMU域
  • map:建立IOVA到物理地址的映射
  • iova_to_phys:把IOVA翻译回物理地址

我习惯把IOMMU子系统想象成一个“调度中心”。它不直接干活,但它知道哪个驱动能干什么活。你想想看,如果没有这层抽象,每个驱动都得直接操作硬件寄存器,那代码得多乱?

核心要点:IOMMU子系统的设计哲学就是“统一接口,多样实现”。不管底层是SMMU还是VT-d,上层看到的API都是一样的。

4.2 IOMMU驱动接口(API)

咱们来看看几个最常用的API。我个人觉得,搞懂这几个,你就能应付80%的场景了。

4.2.1 核心API一览

API函数 作用 我在项目中踩过的坑
iommu_domain_alloc() 分配一个IOMMU域 记得检查返回值,分配失败是常有的事
iommu_attach_device() 把设备绑定到域 设备必须先处于“空闲”状态,否则会报错
iommu_map() 建立映射关系 注意对齐要求,不是随便什么地址都能映射
iommu_unmap() 解除映射 我曾经忘了unmap,结果内存泄漏查了两天

这里有个细节要注意:iommu_map()的第三个参数是IOVA(IO虚拟地址),第四个才是物理地址。很多新手容易搞反。为什么会这样?因为IOMMU的本质就是“地址重映射”,IOVA才是设备看到的地址空间。

我的小技巧:写驱动时,我习惯在iommu_map()前后加调试打印,把IOVA和物理地址都打出来。这样一旦出问题,能快速定位是映射没建立,还是硬件访问错了地址。

4.3 设备与IOMMU域的绑定

设备与IOMMU域的绑定,是IOMMU驱动里最核心的操作。说白了,就是告诉IOMMU:“这个设备归你管了,它的DMA操作你来翻译。”

绑定的流程大致是这样的:

  1. 分配一个IOMMU域(iommu_domain_alloc
  2. 设置域的属性(比如地址空间大小、页表格式)
  3. 把设备attach到这个域(iommu_attach_device
  4. 建立IOVA到物理地址的映射(iommu_map

我曾经在一个项目里遇到过一个问题:设备attach之后,DMA操作一直报错。查了半天,发现是域的类型设置错了。ARM SMMU有stage-1和stage-2两种转换模式,我选错了。嗯,这里要注意,不同的硬件平台,域的类型可能完全不同。

避坑指南:我曾经在绑定设备时,忘了检查设备是否已经被其他域绑定了。结果两个域同时操作同一个设备,导致系统崩溃。所以,绑定前一定要调用iommu_device_is_bound()检查一下。

4.4 DMA-API与IOMMU-API

这两个API经常让人混淆。我简单解释一下:

  • DMA-API:是给普通驱动开发者用的。你不需要知道底层有没有IOMMU,直接调用dma_alloc_coherent()就行。
  • IOMMU-API:是给IOMMU子系统内部用的,或者给那些需要精细控制映射关系的驱动用的。

你想想看,大部分驱动开发者其实不需要直接碰IOMMU-API。他们只需要用DMA-API,内核会自动判断是否需要经过IOMMU。但如果你在写虚拟化相关的代码,或者要搞用户态DMA,那就得跟IOMMU-API打交道了。

我个人的经验是:能用DMA-API就别用IOMMU-API。DMA-API封装得更好,出错概率低。只有当你需要控制IOVA的分配策略,或者要共享映射关系时,才去碰IOMMU-API。

4.5 知识体系图

下面这张图,是我画的一个IOMMU驱动框架的简化结构。你可以看到,从最上层的驱动,到最底层的硬件,中间经过了哪些层次。

IOMMU驱动框架层次结构 设备驱动 (Device Driver) 调用 dma_alloc_coherent / dma_map_single 等DMA-API DMA-API 映射层 判断是否启用IOMMU,调用底层IOMMU-API IOMMU子系统核心 (iommu_ops) attach_dev / map / unmap / iova_to_phys 硬件驱动 (SMMU/VT-d驱动) 操作硬件寄存器,管理页表 IOMMU/SMMU 硬件 调用链方向

从这张图你可以看到,整个调用链是自上而下的。设备驱动只跟DMA-API打交道,DMA-API再调用IOMMU子系统,最后落到具体的硬件驱动上。这种分层设计的好处是:更换硬件平台时,只需要替换最底层的硬件驱动,上层的代码完全不用动。

总结一下:IOMMU驱动框架的核心就是“分层”和“抽象”。你只要记住:设备驱动用DMA-API,IOMMU子系统用iommu_ops,硬件驱动实现具体操作。这三层各司其职,互不干扰。

好了,这一章的内容就到这里。如果你在实际项目中遇到IOMMU相关的问题,欢迎随时交流。我踩过的坑,说不定能帮你省下不少调试时间。


公众号:蓝海资料掘金营,微信deep3321