4. Linux DMA子系统概述
在嵌入式Linux系统中,DMA(Direct Memory Access)是一种允许硬件设备直接读写系统内存而无需CPU干预的数据传输技术。为了统一管理不同硬件平台上的DMA控制器,Linux内核提供了一个标准化的DMA引擎框架——dmaengine。本章将深入剖析该框架的核心设计理念与关键数据结构。
4.1 Linux内核DMA引擎框架(dmaengine)
dmaengine框架位于内核源码的 drivers/dma/ 目录下,它抽象了不同DMA控制器的硬件差异,为驱动开发者提供了一套统一的API。该框架采用消费者-提供者模型:
- 提供者(Provider):DMA控制器驱动,负责注册硬件能力到框架。
- 消费者(Consumer):其他外设驱动(如SPI、I2C、音频),通过框架API发起DMA传输请求。
设计目标: 让消费者驱动无需关心底层DMA控制器的寄存器细节,只需描述“从哪里搬到哪里”即可。
下图展示了dmaengine框架在系统中的层次位置与核心交互流程:
4.2 dmaengine核心数据结构
dmaengine框架围绕三个核心数据结构构建:struct dma_device、struct dma_chan 和 struct dma_async_tx_descriptor。它们分别代表DMA控制器、DMA通道和一次传输事务的描述符。
4.2.1 struct dma_device —— DMA控制器抽象
struct dma_device 是DMA控制器的软件抽象,每个DMA控制器驱动在初始化时分配并注册一个该结构体实例。它包含了控制器的能力描述和操作函数指针。
| 字段 | 类型 | 说明 |
|---|---|---|
dev |
struct device * |
关联的Linux设备模型节点 |
cap_mask |
dma_cap_mask_t |
位掩码,标识支持的传输类型(如MEMCPY、SG、P2P) |
directions |
u32 |
支持的DMA方向位图(DEV_TO_MEM / MEM_TO_DEV / MEM_TO_MEM) |
src_addr_widths |
u32 |
源端支持的数据宽度(1/2/4/8字节) |
dst_addr_widths |
u32 |
目的端支持的数据宽度 |
device_alloc_chan_resources |
函数指针 | 分配并初始化一个DMA通道 |
device_free_chan_resources |
函数指针 | 释放DMA通道资源 |
device_prep_dma_memcpy |
函数指针 | 准备一次内存到内存的拷贝传输 |
device_prep_slave_sg |
函数指针 | 准备一次散列表(SG)方式的从设备传输 |
device_issue_pending |
函数指针 | 将已准备好的描述符提交给硬件执行 |
关键点:
cap_mask 是消费者驱动选择DMA通道时的首要过滤条件。例如,一个音频驱动只会选择设置了 DMA_SLAVE 能力的通道。
4.2.2 struct dma_chan —— DMA通道抽象
struct dma_chan 代表一个已分配的DMA通道。消费者驱动通过 dma_request_channel() 获得该结构体,之后所有传输操作都通过它进行。
| 字段 | 类型 | 说明 |
|---|---|---|
device |
struct dma_device * |
指向所属DMA控制器的反向指针 |
cookie |
dma_cookie_t |
通道当前传输的cookie值,用于状态跟踪 |
chan_id |
int |
通道在控制器内的索引ID |
slave_config |
struct dma_slave_config |
从设备配置(地址、宽度、突发大小等) |
private |
void * |
提供者驱动的私有数据指针 |
注意事项: DMA通道是稀缺资源。一个DMA控制器通常只有4~8个通道,消费者驱动在使用完毕后必须调用
dma_release_channel() 归还通道,否则其他驱动将无法使用。
4.2.3 struct dma_async_tx_descriptor —— 传输事务描述符
每次DMA传输请求被封装为一个 struct dma_async_tx_descriptor。它描述了一次完整的传输事务,包括源地址、目的地址、传输长度、回调函数等信息。
| 字段 | 类型 | 说明 |
|---|---|---|
tx_submit |
函数指针 | 将描述符提交到通道的待处理队列 |
callback |
dma_async_tx_callback |
传输完成后的回调函数 |
callback_param |
void * |
回调函数的参数 |
cookie |
dma_cookie_t |
该描述符的唯一标识,用于查询传输状态 |
phys |
dma_addr_t |
描述符对应的硬件链表物理地址(用于链式DMA) |
chan |
struct dma_chan * |
该描述符所属的DMA通道 |
下图展示了从消费者发起请求到描述符被硬件执行的完整生命周期:
4.3 数据结构关系总结
三个核心数据结构之间的关系可以概括为:
- struct dma_device 是工厂,负责创建和管理通道。
- struct dma_chan 是工人,代表一个可用的传输通道。
- struct dma_async_tx_descriptor 是任务单,描述一次具体的传输工作。
编程模型: 消费者驱动通过
dma_device 中的函数指针创建描述符,然后将描述符提交给 dma_chan,最后调用 issue_pending 让硬件开始工作。这种设计将传输准备与传输执行解耦,支持批量提交和异步完成。