1. 课程导入与学习地图
在嵌入式与服务器 SoC 中,CPU 访问内存要经过 MMU 完成虚拟地址到物理地址的转换,而外设通过 DMA 直接读写内存时,同样需要一套地址转换与保护机制。这套机制在 ARM 体系结构中称为 SMMU(System Memory Management Unit),在通用术语中称为 IOMMU(Input/Output Memory Management Unit)。本课程围绕 Linux 内核的 DMA 映射 API 与 SMMU 配置展开,帮助你把"设备如何安全、正确地访问内存"这条主线彻底打通。
一、核心概念:四个关键词的定位
理解本课程,首先要厘清四个容易混淆的概念。它们不是并列关系,而是分属"CPU 侧"与"设备侧"两条地址转换路径。
- DMA(Direct Memory Access):外设不经过 CPU 直接与内存交换数据的总线主控能力。它解决的是"谁来搬数据"的效率问题。
- MMU(Memory Management Unit):CPU 侧的地址转换单元,把进程虚拟地址 VA 转为物理地址 PA,并提供权限检查。
- IOMMU:设备侧的地址转换单元,把设备发出的 I/O 虚拟地址 IOVA 转为物理地址 PA,同时做隔离与保护。
- SMMU:ARM 架构对 IOMMU 的具体实现,遵循 SMMUv2/SMMUv3 规范,通过 Stream ID 区分不同设备。
二、知识框架与信号链
下图展示了从设备发起 DMA 请求,到数据最终落入物理内存的完整信号链,以及 CPU 侧 MMU 路径的对照关系。请重点观察 Stream ID 如何标识设备、页表如何被查询、以及 DMA API 在软件层所处的位置。
从图中可以看到:CPU 路径(蓝色)与设备路径(橙色)在物理内存处汇合,而软件层通过 DMA API 统一管理设备侧映射。当系统未启用 SMMU 时,IOVA 等于 PA,DMA API 退化为简单的地址转换与缓存一致性维护;当启用 SMMU 后,IOVA 与 PA 解耦,隔离能力显著增强。
三、课程目标与实验环境
本课程的目标是让你具备独立完成"设备 DMA 映射配置 + SMMU 调试排障"的工程能力。具体包括:掌握 DMA 一致性映射与流式映射的适用场景;理解 SMMUv2 与 SMMUv3 的差异;能够阅读设备树中的 iommus 与 smmu 节点;能够通过内核日志与 debugfs 定位映射失败问题。
| 类别 | 工具 / 组件 | 用途说明 |
|---|---|---|
| 内核源码 | Linux 5.15+ / 6.x | 包含 DMA-IOMMU 与 SMMU 驱动 |
| 编译工具 | aarch64-linux-gnu-gcc | 交叉编译 ARM64 内核与模块 |
| 调试接口 | debugfs / sysfs | 查看 iommu 组、映射表、Stream ID |
| 日志工具 | dmesg / ftrace | 跟踪 SMMU 缺页与映射异常 |
| 硬件平台 | ARM64 SoC 开发板 | 支持 SMMUv2 或 SMMUv3 |
| 验证设备 | PCIe 网卡 / 自定义 DMA 设备 | 产生真实 DMA 流量 |
四、典型场景与常见误区
考虑一个典型场景:PCIe 网卡通过 DMA 接收数据包。若未启用 SMMU,网卡驱动申请一段物理连续内存,把物理地址写入网卡描述符即可。若启用 SMMU,驱动必须调用 dma_map_single() 获得 IOVA,再把 IOVA 写入描述符。若驱动错误地写入了物理地址,SMMU 会因地址不在映射表中触发 fault。
# 查看 SMMU 是否启用及设备 Stream ID
dmesg | grep -i smmu
cat /sys/kernel/debug/iommu/arm-smmu-v3/streams
# 典型 SMMU fault 日志
arm-smmu-v3 0000:00:00.0: event 0x10 received
reason: translation fault
stream id: 0x0001
iova: 0xffff0000
dma_map_sg() 与 IOMMU 大页可显著降低开销。常见误区二:混淆
dma_alloc_coherent() 与 dma_map_single()。前者返回 CPU 与设备均可访问的一致性内存,后者只做流式映射,需配对 dma_unmap_single()。常见误区三:忽略 Stream ID 与设备树的对应关系,导致 SMMU 无法识别设备而走 bypass 或直接 fault。