1. 从CPU的MMU到设备的SMMU:为什么你的外设也需要“翻译官”?
大家好,我是老K,在ARM平台和Linux内核里摸爬滚打了十几年,今天想和大家聊聊一个听起来有点“硬核”,但实际上对系统稳定性和性能至关重要的家伙——ARM SMMU。如果你玩过嵌入式开发或者驱动编程,肯定对CPU的MMU(内存管理单元)不陌生,它负责把程序看到的虚拟地址转换成物理内存地址。那SMMU是干什么的呢?你可以把它理解为专门给外设DMA操作配的“翻译官”。
想象一个场景:你的显卡、网卡或者高速存储控制器(比如NVMe SSD)需要通过DMA(直接内存访问)高速读写内存。如果没有SMMU,这些设备看到的就是实实在在的物理地址。这会导致几个大问题:第一,驱动程序必须把真实的物理地址告诉设备,这既不安全也不灵活;第二,多个设备或者虚拟机可能访问到同一块物理内存,造成混乱甚至崩溃;第三,设备可能因为编程错误,写到不该写的内存区域,导致系统“死给你看”。
SMMU(System Memory Management Unit,系统内存管理单元)就是来解决这些问题的。它是ARM体系结构下的IOMMU实现。它的核心工作,是在设备发起的DMA请求到达系统总线之前,把设备使用的“IO虚拟地址(IOVA)”转换成真正的“物理地址(PA)”。这个过程对设备是透明的,设备以为自己操作的是连续的、从0开始的地址空间,实际上背后已经被SMMU映射到了物理内存中可能分散的、受保护的区域。
我刚开始接触SMMU时,也觉得概念一堆,寄存器眼花缭乱。但后来在几个实际项目中踩过坑、调过bug之后,发现只要抓住“地址转换”和“安全隔离”这两个核心,很多问题就迎刃而解了。这篇文章,我就结合自己在驱动开发和内核调试中的经验,带大家深入SMMU在Linux内核里的“五脏六腑”,从原理到配置,从数据结构到实战代码,争取让大家看完就能上手。
2. ARM SMMUv3 架构快速入门:核心概念与硬件视角
在深入代码之前,我们有必要从硬件和协议的角度,搞清楚SMMUv3(目前主流版本)的基本工作模型。这能帮助我们在看内核那些复杂数据结构时,知道它们到底对应硬件上的什么部件。
2.1 SMMU在系统中的位置与角色
你可以把SMMU想象成系统总线上的一个“关卡”。所有打算通过DMA访问内存的设备请求,都要先经过这个关卡的检查和翻译。下图是一个简化的逻辑视图:
[PCIe/USB/其他设备] ---(DMA请求,带StreamID)---> [SMMU] ---(转换后的物理地址)---> [内存控制器] ---> [物理内存]
这里引出了一个关键概念:StreamID。SMMU要管理那么多设备,怎么区分谁是谁呢?就是靠StreamID。每个发起DMA请求的设备,都必须在自己的请求中携带一个StreamID(通常由硬件设计固定,比如在设备树中定义)。SMMU用这个ID作为“索引”,去查找属于这个设备的地址转换配置。
一个常见的误解:SMMU只管理从设备到内存的事务(DMA读/写)。从CPU到设备的内存映射寄存器访问,是由CPU的MMU管理的,不经过SMMU。这一点一定要分清。
2.2 地址转换的“三级寻址”机制
这是SMMU最核心的原理,也是Linux内核驱动代码主要实现的部分。一次完整的地址转换,可以类比于MMU的多级页表查询,但SMMU有自己的“三件套”:
-
流表(Stream Table):这是第一级。SMMU根据设备请求中的StreamID,在流表中找到对应的STE(Stream Table Entry)。你可以把STE理解为这个设备的“通行证”或“档案袋”,里面记录了下一步该去哪里找更详细的配置。流表有两种组织形式:线性流表(所有STE连续存放)和2级流表(类似页表的多级结构),选择哪种取决于支持的StreamID数量和对内存占用的权衡。
-
上下文描述符表(Context Descriptor Table):这是第二级。STE里有一个指针,指向这个设备所属的CD(Context Descriptor) 或CD表。CD里存放了与第1阶段转换(Stage-1 Translation)相关的所有设置。什么是第1阶段?这涉及到ARM的虚拟化扩展。简单理解,Stage-1转换是将设备看到的IOVA转换成中间物理地址(IPA),这个过程可以支持类似CPU的虚拟内存特性,比如给设备驱动提供独立的地址空间。CD本身也可能是一个单级或2级的表结构,用以支持PASID(Process Address Space ID),让单个设备(如GPU)能同时为多个进程服务。
-
页表(Page Tables):这是第三级,也是最终执行地址转换的地方。CD里包含了指向ARMv8页表(TTBR0/TTBR1)的基址寄存器。SMMU的页表格式与CPU的MMU页表基本兼容(如4KB、16KB、64KB粒度),SMMU硬件会遍历这个页表,将输入的IOVA(或IPA)转换成输出的物理地址(PA)。
整个过程简化描述:StreamID -> STE -> CD -> 页表 -> 物理地址。Linux内核驱动的工作,就是在内存中创建并维护好这些表(STE, CD, 页表),然后把它们的物理地址正确地配置到SMMU的硬件寄存器中。
2.3 关键硬件特性探测
SMMUv3驱动在初始化时,第一件事就是“摸清家底”,通过读取一系列ID寄存器(IDR0-IDR5)来探测硬件能力。这直接影响后续数据结构的创建和功能使能。几个你需要关注的关键特性:
- SMMU_IDR0.S1P/S2P:是否支持Stage-1和Stage-2转换。Stage-2转换用于虚拟机场景,将IPA转换为PA。我们通常更关注S1P。
- SMMU_IDR0.COHACC:是否支持与CPU保持缓存一致性。这对于性能至关重要。
- SMMU_IDR1.SIDSIZE:StreamID的位宽,决定了流表的最大索引范围。
- SMMU_IDR5.OAS:输出地址大小(Output Address Size),比如是48位还是52位物理地址,这决定了SMMU能管理多大的物理内存。
驱动代码会根据这些特性位,设置一个featu


388

被折叠的 条评论
为什么被折叠?



