2. Linux DMA 子系统架构:dmaengine 框架、dma_device 结构体、dma_chan 通道模型
Linux DMA 子系统通过 dmaengine 框架为内核驱动和用户空间提供统一的 DMA 操作接口。该框架屏蔽了不同 DMA 控制器的硬件差异,使开发者无需关心底层寄存器细节即可完成高效的数据搬运。本章深入剖析 dmaengine 的三层核心模型:框架层、设备层(dma_device)和通道层(dma_chan)。
核心概念速览: dmaengine 框架采用“控制器 → 设备 → 通道”三级抽象。一个 DMA 控制器(硬件 IP)对应一个
dma_device 结构体;每个 dma_device 管理多个 dma_chan 通道;上层用户通过通道提交传输描述符(dma_async_tx_descriptor)并获取完成回调。
2.1 dmaengine 框架整体架构
dmaengine 框架位于 drivers/dma/ 目录,向上对内核其他子系统(如 SPI、I2C、音频、网络)提供标准 API,向下通过 dma_device 操作集与具体硬件交互。其核心设计思想是异步传输 + 回调通知,避免 CPU 在数据搬运过程中被阻塞。
设计意图: 三层分离使得添加新 DMA 控制器只需实现
dma_device 中的回调函数,无需修改上层调用代码。这也是 Linux 内核“面向接口编程”的典型范例。
2.2 dma_device 结构体:控制器的软件抽象
dma_device 是 DMA 控制器在内核中的软件代表,定义在 include/linux/dmaengine.h。它包含了控制器的能力描述和操作函数指针。每个 DMA 控制器驱动在 probe 阶段分配并初始化一个 dma_device 实例,然后通过 dma_async_device_register() 注册到框架。
| 字段 | 类型 | 说明 |
|---|---|---|
dev |
struct device * |
指向硬件设备结构体,用于 DMA API 映射和电源管理 |
channels |
struct list_head |
该控制器下所有 dma_chan 通道的链表头 |
cap_mask |
dma_cap_mask_t |
位掩码,声明控制器支持的传输类型(如 MEMCPY、SLAVE、CYCLIC) |
directions |
u32 |
位掩码,表示支持的 DMA 方向(DEV_TO_MEM / MEM_TO_DEV / MEM_TO_MEM) |
src_addr_widths |
u32 |
支持的源端数据宽度(1/2/4/8 字节) |
dst_addr_widths |
u32 |
支持的目的端数据宽度 |
max_burst |
u32 |
单次突发传输最大长度(字节),直接影响带宽 |
device_alloc_chan_resources |
函数指针 | 分配通道资源(描述符池、中断等) |
device_prep_slave_sg |
函数指针 | 准备 SLAVE 模式的 SG 传输描述符 |
device_issue_pending |
函数指针 | 将待处理描述符推送到硬件队列 |
device_tx_status |
函数指针 | 查询传输状态(已完成字节数 / 错误码) |
性能关键点:
max_burst 和 src_addr_widths / dst_addr_widths 直接决定了单次 DMA 传输能搬运的数据量。在带宽测试中,若 burst 设置过小(如 1 字节),DMA 控制器将频繁仲裁总线,导致有效带宽急剧下降。通常建议将 burst 设为硬件支持的最大值(如 64 或 128 字节)。
2.3 dma_chan 通道模型:传输的执行单元
dma_chan 是 DMA 传输的逻辑执行单元。一个 DMA 控制器可能包含多个物理通道(如 8 通道 PL330),每个物理通道对应一个 dma_chan 实例。通道模型的核心职责包括:
- 通道申请与释放: 上层驱动通过
dma_request_channel()按能力掩码申请通道,使用完毕后通过dma_release_channel()归还。 - 描述符管理: 每个通道维护一个
desc_allocated/desc_submitted/desc_issued/desc_completed链表,管理传输描述符的完整生命周期。 - 回调机制: 传输完成时,框架调用
dma_async_tx_descriptor中的callback函数,通知上层数据已就绪。 - 虚拟通道支持: 对于硬件通道数较少的控制器,框架提供
virt-dma辅助层,用软件队列模拟更多逻辑通道。
2.4 关键数据结构关系总结
以下表格总结了 dmaengine 框架中三个核心数据结构的分工与协作关系:
| 结构体 | 层级 | 职责 | 生命周期 | 性能影响 |
|---|---|---|---|---|
dma_device |
控制器 | 硬件能力描述、操作函数集、通道链表 | 驱动加载 → 卸载 | 决定最大 burst、地址宽度、传输方向 |
dma_chan |
通道 | 传输执行单元、描述符队列、回调上下文 | 申请 → 释放 | 通道数影响并发度;虚拟通道增加调度开销 |
dma_async_tx_descriptor |
描述符 | 单次传输的配置(源/目的地址、长度、回调) | 准备 → 完成 → 释放 | 描述符复用减少分配开销;回调延迟影响响应 |
2.5 典型驱动注册流程(代码片段)
以下代码展示了一个典型 DMA 控制器驱动在 probe 函数中初始化 dma_device 并注册到框架的过程:
static int my_dma_probe(struct platform_device *pdev)
{
struct my_dma_dev *dma_dev;
struct dma_device *dd;
int ret;
dma_dev = devm_kzalloc(&pdev->dev, sizeof(*dma_dev), GFP_KERNEL);
if (!dma_dev)
return -ENOMEM;
dd = &dma_dev->dma_slave;
INIT_LIST_HEAD(&dd->channels);
/* 填充能力掩码:支持 SLAVE 和 MEMCPY */
dma_cap_set(DMA_SLAVE, dd->cap_mask);
dma_cap_set(DMA_MEMCPY, dd->cap_mask);
/* 设置硬件参数 */
dd->directions = BIT(DMA_DEV_TO_MEM) | BIT(DMA_MEM_TO_DEV);
dd->src_addr_widths = BIT(1) | BIT(2) | BIT(4); /* 1/2/4 字节 */
dd->dst_addr_widths = BIT(1) | BIT(2) | BIT(4);
dd->max_burst = 128; /* 最大突发 128 字节 */
/* 绑定操作函数 */
dd->device_alloc_chan_resources = my_dma_alloc_chan_resources;
dd->device_prep_slave_sg = my_dma_prep_slave_sg;
dd->device_issue_pending = my_dma_issue_pending;
dd->device_tx_status = my_dma_tx_status;
dd->dev = &pdev->dev;
/* 注册到 dmaengine 框架 */
ret = dma_async_device_register(dd);
if (ret) {
dev_err(&pdev->dev, "Failed to register DMA device: %d\n", ret);
return ret;
}
platform_set_drvdata(pdev, dma_dev);
dev_info(&pdev->dev, "DMA controller registered, max_burst=%d\n", dd->max_burst);
return 0;
}
调试技巧: 在
/sys/class/dma/ 目录下可以查看系统已注册的 DMA 通道信息。执行 ls /sys/class/dma/ 列出所有通道,cat /sys/class/dma/dma0chan0/device/max_burst 可读取该通道的最大突发长度。这是验证驱动配置是否正确的快速方法。
2.6 带宽与延迟测试中的架构映射
理解 dmaengine 架构对于后续章节的带宽和延迟测试至关重要。下表总结了架构各层与测试指标的对应关系:
| 架构层 | 对应测试指标 | 典型瓶颈 |
|---|---|---|
dma_device 能力 |
最大理论带宽(max_burst × 总线频率) | burst 设置过小、地址宽度受限 |
dma_chan 调度 |
多通道并发带宽、通道切换延迟 | 虚拟通道软件开销、中断共享冲突 |
dma_async_tx_descriptor |
单次传输延迟、描述符分配/释放开销 | 频繁 alloc/free、回调函数执行时间过长 |
| 硬件 FIFO / 总线仲裁 | 实际有效带宽、传输延迟抖动 | FIFO 深度不足、总线竞争(如 AXI 共享) |
常见误区: 很多开发者认为只要硬件支持高带宽,软件就能自动获得高性能。实际上,
dma_device 中 max_burst 的配置、device_issue_pending 的实现效率、描述符池的预分配策略,都会显著影响最终测得的带宽和延迟。后续章节将逐一通过实验数据验证这些因素的影响。