kernel/dma DMA 映射机制与原理详解
源码路径:rk3588/kernel-6.1/kernel/dma/
内核版本:Linux 6.1(RK3588 平台)
平台:RK3588(4×Cortex-A76 + 4×Cortex-A55 big.LITTLE,ARM64)
该目录实现 Linux 内核 DMA Mapping API 的通用框架代码。DMA 映射负责在 CPU 虚拟/物理地址与设备可见的 DMA 地址(bus address / IOVA) 之间建立对应关系,处理缓存一致性、IOMMU 翻译、SWIOTLB 回弹及连续内存分配等问题。所有设备驱动通过 dma_alloc_*、dma_map_* 等统一 API 访问 DMA 内存。
说明:架构相关缓存维护、IOMMU 挂接位于 arch/arm64/mm/dma-mapping.c;IOMMU/SMMU 驱动位于 drivers/iommu/;CMA 核心实现在 mm/cma.c。本目录提供架构无关的 DMA 框架层。
目录
一、源码目录结构
1.1 编译依赖(Makefile / Kconfig)
1 2 3 4 5 6 7 8 9
| obj-$(CONFIG_HAS_DMA) += mapping.o direct.o obj-$(CONFIG_DMA_OPS) += ops_helpers.o dummy.o obj-$(CONFIG_DMA_CMA) += contiguous.o obj-$(CONFIG_DMA_DECLARE_COHERENT) += coherent.o obj-$(CONFIG_DMA_API_DEBUG) += debug.o obj-$(CONFIG_SWIOTLB) += swiotlb.o obj-$(CONFIG_DMA_COHERENT_POOL) += pool.o obj-$(CONFIG_MMU) += remap.o obj-$(CONFIG_DMA_MAP_BENCHMARK) += map_benchmark.o
|
| 配置项 |
说明 |
CONFIG_HAS_DMA |
平台支持 DMA(ARM64 默认 y) |
CONFIG_DMA_OPS |
使用 struct dma_map_ops 抽象 |
CONFIG_DMA_CMA |
DMA 连续内存分配器(CMA) |
CONFIG_SWIOTLB |
软件 I/O TLB 回弹缓冲 |
CONFIG_DMA_COHERENT_POOL |
原子 coherent 内存池 |
CONFIG_DMA_API_DEBUG |
DMA-API 使用调试 |
CONFIG_DMA_PERNUMA_CMA |
每 NUMA 节点独立 CMA(ARM64 可选) |
1.2 编译单元
| 编译单元 |
源文件 |
规模 |
功能 |
| API 入口 |
mapping.c |
~831 行 |
dma_map_*/dma_alloc_* 统一分发 |
| Direct 映射 |
direct.c |
~657 行 |
无 IOMMU 时的直接物理地址映射 |
| SWIOTLB |
swiotlb.c |
~1,112 行 |
软件 bounce buffer |
| DMA 调试 |
debug.c |
~1,604 行 |
映射泄漏/重复释放检测 |
| CMA 集成 |
contiguous.c |
~443 行 |
DMA 侧 CMA 初始化与分配接口 |
| Coherent 池 |
coherent.c |
~403 行 |
设备预留 coherent 内存 |
| Atomic Pool |
pool.c |
~295 行 |
小块 coherent 原子分配池 |
| Remap 辅助 |
remap.c |
~70 行 |
vmap/remap 非连续 DMA 内存 |
| Ops 辅助 |
ops_helpers.c |
~93 行 |
mmap/sgtable 公共实现 |
| Dummy Ops |
dummy.c |
~38 行 |
无 DMA 能力设备的空 ops |
| Benchmark |
map_benchmark.c |
~378 行 |
dma_map 性能测试 |
| Direct 头文件 |
direct.h |
~126 行 |
direct 映射 inline 函数 |
1.3 相关代码分布(本目录外)
| 位置 |
功能 |
arch/arm64/mm/dma-mapping.c |
ARM64 缓存 flush/invalidate、arch_setup_dma_ops |
include/linux/dma-mapping.h |
驱动使用的公共 DMA API 头文件 |
include/linux/dma-map-ops.h |
struct dma_map_ops 定义 |
drivers/iommu/ |
ARM SMMU、Rockchip IOMMU 等 |
mm/cma.c |
CMA 区域管理与 page 迁移 |
kernel/dma/direct.h |
direct 路径 inline 实现 |
二、整体架构
2.1 分层模型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| ┌─────────────────────────────────────────────────────────────┐ │ 设备驱动 │ │ dma_alloc_coherent / dma_map_sg / dma_sync_* │ └──────────────────────────┬──────────────────────────────────┘ │ ┌──────────────────────────▼──────────────────────────────────┐ │ mapping.c (API 分发层) │ │ get_dma_ops(dev) → direct 快路径 或 ops->map_* │ └──────┬──────────┬──────────┬──────────┬─────────────────────┘ │ │ │ │ ┌──────▼──┐ ┌─────▼────┐ ┌──▼──────┐ ┌─▼──────────────┐ │ direct │ │ SWIOTLB │ │ CMA │ │ IOMMU ops │ │ direct.c│ │ swiotlb.c│ │contiguous│ │ (drivers/iommu)│ └──────┬──┘ └─────┬────┘ └──┬──────┘ └─┬──────────────┘ │ │ │ │ └──────────┴──────────┴──────────┘ │ ┌────────────▼────────────┐ │ 物理内存 / IOVA │ │ 设备 DMA 控制器 │ └─────────────────────────┘
|
2.2 三种映射路径
| 路径 |
条件 |
行为 |
| Direct |
无 IOMMU 或 bypass,DMA 地址在 mask 内 |
物理地址直接作为 DMA 地址 |
| IOMMU |
设备挂接 SMMU/IOMMU |
分配 IOVA,建立页表映射 |
| SWIOTLB |
物理地址超出 DMA mask 或无 IOMMU |
数据复制到 bounce buffer |
mapping.c 中 dma_map_direct() 判断是否走 direct 快路径:
1 2 3 4
| static inline bool dma_map_direct(struct device *dev, const struct dma_map_ops *ops) { return dma_go_direct(dev, *dev->dma_mask, ops); }
|
2.3 两类 DMA 内存
| 类型 |
API |
特点 |
| Coherent(一致性) |
dma_alloc_coherent |
CPU 与设备共享同一视图,无需 sync |
| Streaming(流式) |
dma_map_page/sg + dma_sync_* |
需显式缓存同步,适合网络/块 I/O |
三、核心 API 与数据结构
3.1 struct dma_map_ops — 驱动 ops 虚表
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| struct dma_map_ops { void *(*alloc)(...); void (*free)(...); dma_addr_t (*map_page)(...); void (*unmap_page)(...); int (*map_sg)(...); void (*unmap_sg)(...); void (*sync_single_for_cpu)(...); void (*sync_single_for_device)(...); void (*sync_sg_for_cpu/device)(...); int (*mmap)(...); };
|
每个 struct device 通过 dev->dma_ops 指向具体实现。IOMMU 驱动注册自己的 ops;无 IOMMU 时使用 dma_direct_ops。
3.2 主要公共 API(mapping.c)
| API |
功能 |
dma_alloc_coherent / dma_free_coherent |
分配/释放一致性 DMA 内存 |
dma_alloc_attrs / dma_free_attrs |
带属性(WC、non-coherent 等)的分配 |
dma_map_page / dma_unmap_page |
映射/解映射单个 page |
dma_map_sg / dma_unmap_sg |
映射/解映射 scatterlist |
dma_map_sgtable / dma_unmap_sgtable |
sg_table 版本(带错误码) |
dma_map_resource / dma_unmap_resource |
映射 MMIO 等资源地址 |
dma_sync_single_for_cpu/device |
单 buffer 缓存同步 |
dma_sync_sg_for_cpu/device |
SG 列表缓存同步 |
dma_set_mask / dma_set_coherent_mask |
设置 DMA 地址掩码 |
dma_mmap_attrs |
将 coherent 内存 mmap 到用户空间 |
dmam_alloc_* / dmam_free_* |
托管式分配(驱动 detach 时自动释放) |
3.3 DMA 数据方向
1 2 3 4 5 6
| enum dma_data_direction { DMA_BIDIRECTIONAL = 0, DMA_TO_DEVICE = 1, DMA_FROM_DEVICE = 2, DMA_NONE = 3, };
|
3.4 DMA 属性(attrs)
| 属性 |
含义 |
DMA_ATTR_WRITE_COMBINE |
写合并(WC)映射 |
DMA_ATTR_NON_CONSISTENT |
非一致性映射 |
DMA_ATTR_SKIP_CPU_SYNC |
跳过 CPU 侧 cache sync |
DMA_ATTR_FORCE_CONTIGUOUS |
强制物理连续 |
DMA_ATTR_NO_KERNEL_MAPPING |
不建立 CPU 线性映射 |
四、Direct Mapping 直接映射
4.1 概述
direct.c + direct.h 实现 不使用 IOMMU 时的 DMA 操作:DMA 地址 = 物理地址(经 phys_to_dma 转换)。
4.2 Coherent 分配流程
1 2 3 4 5
| dma_direct_alloc() ├── is_swiotlb_for_alloc()? → swiotlb_alloc() ├── dma_alloc_contiguous() // 优先 CMA 连续页 ├── alloc_pages_node() // 回退普通页分配 └── 检查 dma_coherent_ok()(地址在 mask 范围内)
|
4.3 Streaming 映射(direct.h inline)
1 2 3 4 5
| dma_direct_map_page(dev, page, offset, size, dir, attrs) ├── is_swiotlb_force_bounce()? → swiotlb_map() ├── !dma_capable()? → swiotlb_map() 或 ERROR └── 非 coherent 设备? → arch_sync_dma_for_device() return phys_to_dma(phys)
|
4.4 Zone 选择策略
1 2 3 4 5
| dma_direct_optimal_gfp_mask(dev, mask, &phys_limit) → mask ≤ 24bit: GFP_DMA → mask ≤ 32bit: GFP_DMA32 → 否则: 0(普通 ZONE_NORMAL)
|
RK3588 ARM64 通常 DMA mask 为 64-bit,默认从 ZONE_NORMAL 分配。
五、SWIOTLB 软件回弹
5.1 使用场景
swiotlb.c 在以下情况提供 bounce buffer:
- 设备 DMA mask 小于物理地址(如 32-bit 设备访问 >4GB 内存)
- 无 IOMMU 且物理地址不可达
- 强制 bounce 模式(
swiotlb=force)
5.2 工作原理
1 2 3 4 5 6 7 8 9 10
| dma_map_page(高地址物理页) → dma_direct_map_page 检测 !dma_capable → swiotlb_map() ├── 在 SWIOTLB 池中分配低地址 slot ├── DMA_TO_DEVICE: 复制数据到 bounce buffer └── 返回 bounce buffer 的 DMA 地址
dma_unmap_page / sync_for_cpu → DMA_FROM_DEVICE: 从 bounce buffer 复制回原始 buffer → 释放 SWIOTLB slot
|
5.3 核心结构
1 2 3 4 5 6 7 8 9 10
| struct io_tlb_mem { phys_addr_t start; unsigned long nslabs; };
struct io_tlb_slot { phys_addr_t orig_addr; size_t alloc_size; };
|
5.4 内核参数
| 参数 |
功能 |
swiotlb=force |
强制所有 DMA 使用 bounce |
swiotlb=noforce |
禁用强制 bounce |
swiotlb=<size> |
设置 bounce buffer 大小 |
默认大小 IO_TLB_DEFAULT_SIZE(通常 64MB)。
六、CMA 连续内存分配
6.1 概述
contiguous.c 是 DMA 子系统与 CMA(Contiguous Memory Allocator) 的桥梁。CMA 在 boot 时预留一块物理连续区域,平时供 pagecache 使用,需要时可迁移页面以提供大块连续物理内存。
6.2 为什么需要 CMA
| 场景 |
原因 |
| 视频编解码 |
硬件 IP 不支持 SG-DMA,需物理连续 buffer |
| 摄像头/V4L2 |
frame buffer 通常要求连续 |
| DMA 大 buffer |
减少 TLB miss 和 SG 列表复杂度 |
6.3 初始化
1 2 3 4
| struct cma *dma_contiguous_default_area;
early_param("cma", early_cma);
|
Kconfig 默认 ARM64 可设 CMA_SIZE_MBYTES=16 或按内存百分比。
6.4 分配接口
1 2 3
| struct page *dma_alloc_contiguous(struct device *dev, size_t size, gfp_t gfp); void dma_free_contiguous(struct device *dev, struct page *page, size_t size);
|
direct.c 中 coherent 分配优先调用 dma_alloc_contiguous()。
6.5 Pernuma CMA
启用 CONFIG_DMA_PERNUMA_CMA 时,每个 NUMA 节点有独立 CMA 区域,SMMU 可分配本地内存。RK3588 为 UMA 架构,通常使用单一全局 CMA。
七、Coherent 内存与 Pool
7.1 设备 Coherent 内存(coherent.c)
允许平台/驱动声明 预留物理内存 作为设备专用 coherent 池:
1 2 3 4 5 6 7
| struct dma_coherent_mem { void *virt_base; dma_addr_t device_base; unsigned long pfn_base; int size; unsigned long *bitmap; };
|
通过 dma_declare_coherent_memory() 注册,dev->dma_mem 指向该结构。
7.2 Atomic Coherent Pool(pool.c)
为 原子上下文(中断、持有 spinlock)中的小 buffer 分配提供 gen_pool:
1 2 3
| static struct gen_pool *atomic_pool_dma; static struct gen_pool *atomic_pool_dma32; static struct gen_pool *atomic_pool_kernel;
|
内核参数 coherent_pool=size 控制池大小。/sys/kernel/debug/dma_pools/ 可查看统计。
7.3 Remap 辅助(remap.c)
非连续物理页通过 vmap 映射为连续虚拟地址:
dma_common_pages_remap() — 页数组 remap
dma_common_contiguous_remap() — 连续页 remap
dma_common_free_remap() — 释放 remap
八、缓存一致性与同步
8.1 ARM64 缓存维护
1 2 3 4 5 6 7 8 9
| arch_sync_dma_for_device(paddr, size, dir) → dcache_clean_poc()
arch_sync_dma_for_cpu(paddr, size, dir) → dcache_inval_poc()
arch_dma_prep_coherent(page, size) → dcache_clean_inval_poc()
|
8.2 同步 API 调用时机
| 场景 |
调用 |
| DMA 发送前 |
dma_sync_single_for_device(DMA_TO_DEVICE) |
| DMA 完成后 CPU 读 |
dma_sync_single_for_cpu(DMA_FROM_DEVICE) |
| map 时(non-coherent) |
内部自动 arch_sync_dma_for_device |
| unmap 时 |
内部自动 arch_sync_dma_for_cpu |
8.3 Coherent vs Non-coherent
|
Coherent |
Non-coherent (Streaming) |
| CPU cache |
硬件或映射保证一致 |
需软件 sync |
| 分配 API |
dma_alloc_coherent |
普通内存 + dma_map_* |
| 性能 |
简单但可能慢 |
高吞吐,需正确 sync |
| RK3588 |
大部分外设为 non-coherent |
网络/存储驱动常用 |
九、DMA-API 调试
9.1 DMA_API_DEBUG(debug.c)
启用 CONFIG_DMA_API_DEBUG 后,跟踪每次 dma_map_* 和 dma_alloc_coherent:
1 2 3 4 5 6 7 8
| struct dma_debug_entry { struct device *dev; size_t size; int type; enum dma_data_direction direction; dma_addr_t dev_addr; };
|
检测常见问题:
- 双重 unmap
- 释放未 map 的地址
- map/unmap size 不匹配
- SG 映射 segment 边界违规
Debugfs 接口:/sys/kernel/debug/dma-api/。
9.2 Map Benchmark(map_benchmark.c)
启用 CONFIG_DMA_MAP_BENCHMARK 提供 /sys/kernel/debug/dma_map_benchmark,用于测试 dma_map_page 性能。
十、完整 DMA 映射时序
以 网络驱动 TX(streaming DMA) 为例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| [驱动] skb = alloc_skb(...) dma_addr = dma_map_single(dev, skb->data, len, DMA_TO_DEVICE)
[mapping.c] ops = get_dma_ops(dev) if (dma_map_direct(dev, ops)) addr = dma_direct_map_page(dev, page, offset, len, DMA_TO_DEVICE, 0) else addr = ops->map_page(dev, page, offset, len, DMA_TO_DEVICE, 0)
[direct.h — direct 路径] phys = page_to_phys(page) + offset if (!dma_capable(dev, dma_addr, len)) return swiotlb_map(dev, phys, len, ...) // 高地址 bounce arch_sync_dma_for_device(phys, len, DMA_TO_DEVICE) // cache clean return phys_to_dma(dev, phys)
[ARM64] dcache_clean_poc(vaddr, vaddr + size)
[驱动] 写入硬件 DMA 描述符: dma_addr, len 触发 TX
[TX 完成中断] dma_unmap_single(dev, dma_addr, len, DMA_TO_DEVICE) → dma_direct_sync_single_for_cpu() // 如需要 → swiotlb_unmap() 如使用了 bounce
|
Coherent 分配路径(如 V4L2 帧缓冲):
1 2 3 4 5
| dma_alloc_coherent(dev, size, &dma_handle, GFP_KERNEL) → ops->alloc() 或 dma_direct_alloc() → dma_alloc_contiguous() // CMA 连续页 → arch_dma_prep_coherent() // cache clean+invalidate → 返回 {cpu_addr, dma_handle}
|
十一、RK3588 平台应用
11.1 典型配置
1 2 3 4 5 6 7 8 9
| zcat /proc/config.gz | grep -E 'CMA|SWIOTLB|DMA|IOMMU'
|
11.2 IOMMU / SMMU
RK3588 集成 ARM SMMU,多数外设(VOP、RGA、VPU、ISP 等)通过 IOMMU 映射:
1 2 3
| arch_setup_dma_ops(dev, dma_base, size, iommu_ops, coherent) → iommu_setup_dma_ops() // 设置 dev->dma_ops = iommu_dma_ops → dev->dma_coherent = coherent
|
- 有 IOMMU:
dma_map_sg 分配 IOVA 并建立页表
- Bypass 模式:满足
dma_ops_bypass 条件时走 direct 快路径
11.3 CMA 与多媒体
RK3588 视频/图形驱动大量依赖 CMA:
1 2 3 4 5
| cma=256M
cat /proc/meminfo | grep -i cma
|
/dev/dma_heap/ 用户空间也可从 CMA 分配(dma-buf 框架)。
11.4 常见问题
| 问题 |
原因 |
排查 |
| DMA 映射失败 |
内存不足或 IOVA 耗尽 |
dmesg、/sys/kernel/debug/iommu/ |
| 花屏/数据错误 |
缺少 cache sync |
检查 dma_sync_* 调用 |
| 高地址设备失败 |
32-bit DMA mask |
SWIOTLB bounce 或 CMA 低地址分配 |
| CMA 分配失败 |
CMA 区域太小 |
增大 cma= 参数 |
11.5 驱动开发要点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64));
void *cpu = dma_alloc_coherent(dev, size, &dma_addr, GFP_KERNEL);
dma_addr = dma_map_single(dev, buf, len, DMA_TO_DEVICE);
dma_unmap_single(dev, dma_addr, len, DMA_TO_DEVICE);
nents = dma_map_sg(dev, sg, nents, DMA_TO_DEVICE);
dma_unmap_sg(dev, sg, nents, DMA_TO_DEVICE);
|
十二、总结
kernel/dma 是 Linux 设备 DMA 访问的统一框架:
- mapping.c — 公共 API 入口,根据
dev->dma_ops 和 direct 条件分发
- direct.c — 无 IOMMU 时的直接物理地址映射,含 CMA 优先分配
- swiotlb.c — 软件 bounce buffer,解决 DMA 地址受限问题
- contiguous.c — CMA 集成,为多媒体等提供大块连续物理内存
- coherent.c / pool.c — 设备预留内存和原子 coherent 池
- debug.c — DMA-API 使用错误检测
在 RK3588 平台上,DMA 子系统与 ARM SMMU、CMA、dma-buf 紧密配合,是 VOP/RGA/VPU/ISP、Ethernet、PCIe 等所有外设驱动的基础。
附录:源文件完整清单
| 文件 |
行数 |
分类 |
debug.c |
1,604 |
DMA-API 调试 |
swiotlb.c |
1,112 |
软件 I/O TLB |
mapping.c |
831 |
API 入口 |
direct.c |
657 |
Direct 映射 |
contiguous.c |
443 |
CMA 集成 |
coherent.c |
403 |
Coherent 内存 |
map_benchmark.c |
378 |
性能测试 |
pool.c |
295 |
Atomic pool |
direct.h |
126 |
Direct inline |
debug.h |
130 |
调试头文件 |
ops_helpers.c |
93 |
Ops 辅助 |
remap.c |
70 |
Remap 辅助 |
dummy.c |
38 |
Dummy ops |
正在加载留言…