2. Linux DMA子系统概述:DMA Engine框架、dmaengine核心层、dmaengine驱动层、dmaengine API概览
DMA(Direct Memory Access)是嵌入式系统中实现高性能数据搬移的关键技术。Linux内核自2.6.x版本起引入了统一的DMA Engine框架,旨在解决以往DMA控制器驱动实现各异、API不统一的问题。本章将系统性地剖析DMA Engine框架的分层架构、核心数据结构以及对外提供的API接口,为后续的设备树配置与驱动开发奠定理论基础。
核心观点: Linux DMA Engine框架通过分层设计,将DMA控制器硬件差异封装在驱动层,向上层(如音频、视频、存储子系统)提供一套通用、异步、基于描述符的数据传输接口。
2.1 DMA Engine框架整体架构
DMA Engine框架采用经典的三层架构:用户层/内核子系统层、dmaengine核心层和DMA控制器驱动层。核心层作为枢纽,向下注册驱动,向上提供API。
架构要点: 核心层不直接操作硬件寄存器,而是通过驱动层注册的
dma_device 结构体中的函数指针来间接控制硬件。这种设计使得上层子系统无需关心底层是PL330还是EDMA。
2.2 dmaengine核心层详解
核心层是DMA Engine框架的“大脑”,负责管理通道、描述符和事务生命周期。其核心数据结构包括:
| 数据结构 | 文件位置 | 核心作用 | 关键字段 |
|---|---|---|---|
struct dma_device |
include/linux/dmaengine.h | 描述一个DMA控制器硬件实例,包含其能力、通道列表和操作函数集 | channels, cap_mask, device_alloc_chan_resources, device_prep_slave_sg |
struct dma_chan |
include/linux/dmaengine.h | 代表一个已分配的DMA通道,是上层与硬件交互的句柄 | device, chan_id, slave, private |
struct dma_async_tx_descriptor |
include/linux/dmaengine.h | 描述一次DMA传输事务,包含源/目的地址、长度、回调函数等 | tx_submit, callback, callback_param, cookie |
struct dma_slave_config |
include/linux/dmaengine.h | 配置从设备(如SPI、I2C)与DMA控制器之间的接口参数 | direction, src_addr, dst_addr, src_maxburst, dst_maxburst |
关键机制: 核心层维护一个全局的
dma_list 链表,所有注册的 dma_device 都会加入该链表。当上层调用 dma_request_channel() 时,核心层遍历该链表,根据 cap_mask(能力掩码)匹配最合适的通道。
2.3 dmaengine驱动层职责
驱动层是具体DMA控制器硬件的“翻译官”,负责将核心层的通用请求转化为硬件操作。一个典型的DMA控制器驱动需要实现以下核心回调函数:
- 通道管理:
device_alloc_chan_resources()和device_free_chan_resources(),负责分配/释放硬件通道相关的资源(如描述符池、中断)。 - 传输准备:
device_prep_slave_sg()(从设备SG传输)、device_prep_dma_memcpy()(内存拷贝)、device_prep_dma_cyclic()(循环传输,常用于音频)。 - 事务控制:
device_issue_pending()(触发硬件开始处理已提交的描述符)、device_tx_status()(查询传输状态)。 - 配置:
device_config(),用于接收来自上层的dma_slave_config配置。
驱动开发注意: 驱动层必须正确处理描述符的生命周期管理。描述符在
device_prep_* 中创建,在 tx_submit 后进入提交队列,传输完成后由中断处理函数调用回调,最后释放。内存泄漏或野指针是常见问题。
2.4 dmaengine API概览
dmaengine API是上层子系统与DMA Engine交互的唯一途径。根据使用场景,API可分为以下几类:
| API类别 | 函数原型 | 功能描述 | 使用阶段 |
|---|---|---|---|
| 通道管理 | dma_request_channel(mask, filter_fn, filter_param) |
根据能力掩码和过滤函数请求一个DMA通道 | 驱动初始化 |
dma_release_channel(chan) |
释放已分配的DMA通道 | 驱动卸载 | |
| 传输配置 | dmaengine_slave_config(chan, config) |
配置从设备接口参数(地址、宽度、突发长度) | 传输前配置 |
dmaengine_prep_slave_sg(chan, sgl, sg_len, dir, flags) |
准备一个从设备SG(分散/聚集)传输描述符 | 传输前准备 | |
dmaengine_prep_dma_cyclic(chan, buf_addr, buf_len, period_len, dir, flags) |
准备一个循环传输描述符(常用于音频流) | 传输前准备 | |
| 事务提交与控制 | dmaengine_submit(desc) |
将描述符提交到硬件待处理队列,返回cookie | 准备完成后 |
dma_async_issue_pending(chan) |
触发DMA控制器开始处理已提交的描述符 | 提交之后 | |
| 状态查询与同步 | dma_async_is_tx_complete(chan, cookie, state) |
查询指定cookie对应的传输是否完成 | 轮询或中断中 |
dma_sync_wait(chan, cookie) |
同步等待指定传输完成(阻塞调用) | 需要同步时 |
典型使用模式: 请求通道 → 配置从设备参数 → 准备描述符 → 提交描述符 → 触发传输 → (中断回调或轮询)等待完成 → 释放描述符。这个模式在音频、SPI、UART等驱动中反复出现。
2.5 典型传输流程示例
以下是一个典型的从设备SG传输代码片段,展示了dmaengine API的完整调用序列:
/* 1. 请求DMA通道 */
struct dma_chan *chan = dma_request_channel(mask, filter_fn, NULL);
if (!chan) {
dev_err(dev, "Failed to request DMA channel\n");
return -ENODEV;
}
/* 2. 配置从设备参数 */
struct dma_slave_config config = {
.direction = DMA_DEV_TO_MEM,
.src_addr = (dma_addr_t)phy_fifo_addr,
.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES,
.src_maxburst = 4,
};
dmaengine_slave_config(chan, &config);
/* 3. 准备SG传输描述符 */
struct scatterlist sg;
sg_init_one(&sg, buf, BUFFER_SIZE);
struct dma_async_tx_descriptor *desc;
desc = dmaengine_prep_slave_sg(chan, &sg, 1, DMA_DEV_TO_MEM,
DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
if (!desc) {
dev_err(dev, "Failed to prepare slave sg\n");
goto err;
}
/* 4. 设置回调 */
desc->callback = my_dma_callback;
desc->callback_param = my_data;
/* 5. 提交描述符 */
dma_cookie_t cookie = dmaengine_submit(desc);
/* 6. 触发传输 */
dma_async_issue_pending(chan);
/* 7. 等待完成(或在回调中处理) */
dma_sync_wait(chan, cookie);
关键点:
dmaengine_submit() 只是将描述符加入队列,真正的硬件启动由 dma_async_issue_pending() 触发。两者必须成对调用。回调函数在中断上下文中执行,应尽量简短。
2.6 本章小结
本章从宏观到微观,系统梳理了Linux DMA Engine框架的完整脉络:
- 三层架构明确了核心层与驱动层的职责边界,核心层提供通用API,驱动层负责硬件适配。
- 核心数据结构(
dma_device、dma_chan、dma_async_tx_descriptor)是理解框架的钥匙。 - 驱动层回调是硬件操作的“最后一公里”,开发者需要根据硬件手册实现这些回调。
- API概览提供了从通道请求到传输完成的完整工具链,是后续实战编码的基础。