kernel/dma DMA 映射机制与原理详解

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.cdma_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
// include/linux/dma-map-ops.h
struct dma_map_ops {
void *(*alloc)(...); // coherent 分配
void (*free)(...);
dma_addr_t (*map_page)(...); // 单页 streaming 映射
void (*unmap_page)(...);
int (*map_sg)(...); // scatter-gather 映射
void (*unmap_sg)(...);
void (*sync_single_for_cpu)(...); // 缓存同步
void (*sync_single_for_device)(...);
void (*sync_sg_for_cpu/device)(...);
int (*mmap)(...); // 用户空间 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, // CPU → 设备
DMA_FROM_DEVICE = 2, // 设备 → CPU
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
// direct.c — 根据 DMA mask 选择 GFP 区域
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; // bounce buffer 物理起始
unsigned long nslabs; // slot 数量
// ...
};

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
// contiguous.c
struct cma *dma_contiguous_default_area;

early_param("cma", early_cma); // cma=64M@0x80000000

Kconfig 默认 ARM64 可设 CMA_SIZE_MBYTES=16 或按内存百分比。

6.4 分配接口

1
2
3
// 通过 dma-map-ops.h
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; // CPU 虚拟地址
dma_addr_t device_base; // 设备 DMA 地址
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;     // GFP_DMA 区域
static struct gen_pool *atomic_pool_dma32; // GFP_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/arm64/mm/dma-mapping.c
arch_sync_dma_for_device(paddr, size, dir)
→ dcache_clean_poc() // 写回 CPU cache 到 PoC

arch_sync_dma_for_cpu(paddr, size, dir)
→ dcache_inval_poc() // 使 CPU cache 失效(DMA_FROM_DEVICE)

arch_dma_prep_coherent(page, size)
→ dcache_clean_inval_poc() // coherent 分配时 clean+invalidate

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; // single/sg/coherent/resource
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'

# RK3588 典型项
# CONFIG_HAS_DMA=y
# CONFIG_DMA_CMA=y
# CONFIG_CMA_SIZE_MBYTES=16 (或更大)
# CONFIG_SWIOTLB=y
# CONFIG_IOMMU_DMA=y
# CONFIG_ARM_SMMU=y

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+)
cma=256M

# 查看 CMA 使用
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
// 1. 设置 DMA mask
dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64));

// 2. Coherent 分配(帧缓冲等)
void *cpu = dma_alloc_coherent(dev, size, &dma_addr, GFP_KERNEL);

// 3. Streaming 映射(网络/块 I/O)
dma_addr = dma_map_single(dev, buf, len, DMA_TO_DEVICE);
// ... 设备 DMA ...
dma_unmap_single(dev, dma_addr, len, DMA_TO_DEVICE);

// 4. SG 映射(scatter-gather)
nents = dma_map_sg(dev, sg, nents, DMA_TO_DEVICE);
// ...
dma_unmap_sg(dev, sg, nents, DMA_TO_DEVICE);

十二、总结

kernel/dma 是 Linux 设备 DMA 访问的统一框架:

  1. mapping.c — 公共 API 入口,根据 dev->dma_ops 和 direct 条件分发
  2. direct.c — 无 IOMMU 时的直接物理地址映射,含 CMA 优先分配
  3. swiotlb.c — 软件 bounce buffer,解决 DMA 地址受限问题
  4. contiguous.c — CMA 集成,为多媒体等提供大块连续物理内存
  5. coherent.c / pool.c — 设备预留内存和原子 coherent 池
  6. 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

文章互动

阅读 --

留言

0 条留言

正在加载留言…