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 区分不同设备。
关键认知:MMU 服务 CPU,SMMU/IOMMU 服务设备。二者共享"页表 + 地址转换 + 权限"的思想,但页表格式、寄存器接口、驱动模型完全不同。DMA 映射 API 则是内核为驱动开发者提供的统一抽象层,屏蔽底层是否存在 SMMU。

二、知识框架与信号链

下图展示了从设备发起 DMA 请求,到数据最终落入物理内存的完整信号链,以及 CPU 侧 MMU 路径的对照关系。请重点观察 Stream ID 如何标识设备、页表如何被查询、以及 DMA API 在软件层所处的位置。

DMA / MMU / IOMMU / SMMU 信号链与软件分层 CPU 核心 虚拟地址 VA MMU VA → PA + 权限 外设设备 发起 DMA 请求 SMMU / IOMMU IOVA → PA Stream ID 标识设备 页表 / 转换表 多级查表 · 权限位 物理内存 PA DDR / SRAM Linux 内核软件层 dma_alloc_coherent / dma_map_single / dma_map_sg → DMA-IOMMU 子系统 → SMMU 驱动

从图中可以看到: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
常见误区一:认为"启用 SMMU 后 DMA 会自动变慢"。实际上性能取决于映射粒度与 TLB 命中率,合理使用 dma_map_sg() 与 IOMMU 大页可显著降低开销。
常见误区二:混淆 dma_alloc_coherent() 与 dma_map_single()。前者返回 CPU 与设备均可访问的一致性内存,后者只做流式映射,需配对 dma_unmap_single()。
常见误区三:忽略 Stream ID 与设备树的对应关系,导致 SMMU 无法识别设备而走 bypass 或直接 fault。