3. Linux DMA子系统概述:dmaengine框架、dma_slave_config、dma_async_tx_descriptor
在嵌入式Linux系统中,DMA(Direct Memory Access)是提升外设数据吞吐量、降低CPU负载的核心硬件机制。Linux内核通过一套统一的dmaengine框架,将不同SoC厂商的DMA控制器差异抽象化,为驱动开发者提供标准化的API接口。本章将深入剖析dmaengine框架的三层架构、关键数据结构dma_slave_config与dma_async_tx_descriptor,为后续实战优化打下坚实基础。
学习目标:理解dmaengine框架的软件分层与核心对象;掌握如何配置DMA传输参数;学会使用异步传输描述符管理DMA事务。
3.1 dmaengine框架三层架构
dmaengine框架采用经典的分层设计,从上到下依次为:DMA Engine API层(驱动调用)、DMA Engine Core层(框架核心)、DMA Controller Driver层(硬件适配)。这种分层隔离了业务逻辑与硬件细节,使得外设驱动无需关心底层DMA控制器的寄存器操作。
各层职责清晰,核心交互流程如下:
- API层:提供
dma_request_channel()、dmaengine_prep_slave_sg()、dmaengine_submit()、dma_async_issue_pending()等标准接口,供SPI、I2S、UART等外设驱动调用。 - Core层:维护通道状态机、管理描述符池、调度传输队列、触发回调通知。核心数据结构包括
dma_chan、dma_device、dma_async_tx_descriptor。 - Controller Driver层:由SoC厂商实现,填充
dma_device中的device_prep_slave_sg、device_issue_pending、device_tx_status等回调函数,直接操作DMA控制器寄存器。
3.2 dma_slave_config:传输参数配置
dma_slave_config是连接外设与DMA控制器的桥梁,用于描述外设侧的传输参数。外设驱动在启动DMA传输前,必须通过dmaengine_slave_config()将配置下发到DMA控制器驱动。
| 字段名 | 类型 | 描述 | 典型值 |
|---|---|---|---|
direction |
enum dma_transfer_direction | 数据传输方向 | DMA_MEM_TO_DEV / DMA_DEV_TO_MEM / DMA_MEM_TO_MEM |
src_addr |
dma_addr_t | 源端物理地址(外设FIFO地址) | 0x01c02000(UART TX FIFO) |
dst_addr |
dma_addr_t | 目的端物理地址 | 0x01c02004(UART RX FIFO) |
src_addr_width |
enum dma_slave_buswidth | 源端总线宽度(字节) | DMA_SLAVE_BUSWIDTH_1_BYTE / _4_BYTES |
dst_addr_width |
enum dma_slave_buswidth | 目的端总线宽度 | 同上 |
src_maxburst |
u32 | 源端单次突发传输最大数据量(单位:src_addr_width) |
4 / 8 / 16 |
dst_maxburst |
u32 | 目的端单次突发传输最大数据量 | 同上 |
device_fc |
bool | 是否由外设硬件控制流控 | false(通常由DMA控制器控制) |
配置示例:SPI外设接收DMA配置(DMA_DEV_TO_MEM)
struct dma_slave_config config = {
.direction = DMA_DEV_TO_MEM,
.src_addr = spi->phys_base + SPI_RX_FIFO,
.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES,
.src_maxburst = 4,
.dst_addr = 0, // 内存地址由描述符指定
.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES,
.dst_maxburst = 0,
};
ret = dmaengine_slave_config(chan, &config);
if (ret)
dev_err(dev, "DMA slave config failed: %d\n", ret);
常见错误:未正确设置
src_addr_width与src_maxburst的匹配关系。例如,外设FIFO宽度为4字节,但src_addr_width设为1字节,会导致DMA控制器每次只搬运1字节,严重降低传输效率。务必参考SoC手册确认外设总线宽度。
3.3 dma_async_tx_descriptor:异步传输描述符
dma_async_tx_descriptor是dmaengine框架中描述一次DMA传输事务的核心对象。它封装了传输的源/目的地址、长度、回调函数、标志位等信息。外设驱动通过dmaengine_prep_slave_sg()等准备函数获取该描述符,然后提交到传输队列。
dma_async_tx_descriptor的关键成员与方法:
- 回调机制:通过
desc->callback和desc->callback_param设置传输完成回调函数,在DMA中断上下文中执行。 - 传输标志:
desc->flags支持DMA_PREP_INTERRUPT(使能完成中断)、DMA_CTRL_ACK(描述符可重用)等。 - 链式传输:通过
desc->tx_submit将描述符加入通道队列,支持dmaengine_submit()批量提交多个描述符实现DMA链表传输。 - 状态查询:调用
dma_async_is_tx_complete()或dmaengine_tx_status()查询传输进度,返回已传输的字节数。
典型传输流程代码片段:
// 1. 准备描述符(scatterlist方式)
struct dma_async_tx_descriptor *desc;
desc = dmaengine_prep_slave_sg(chan, sgl, sg_len, direction,
DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
if (!desc) {
dev_err(dev, "Failed to prepare DMA descriptor\n");
return -ENOMEM;
}
// 2. 设置回调
desc->callback = dma_callback;
desc->callback_param = priv_data;
// 3. 提交到队列
dma_cookie_t cookie = dmaengine_submit(desc);
if (dma_submit_error(cookie)) {
dev_err(dev, "Failed to submit DMA descriptor\n");
return -EIO;
}
// 4. 启动传输
dma_async_issue_pending(chan);
3.4 关键数据结构关系总览
为了帮助读者建立整体认知,下表总结了dmaengine框架中核心数据结构之间的关系:
| 数据结构 | 所属层次 | 主要作用 | 关联对象 |
|---|---|---|---|
dma_device |
Controller Driver | 描述DMA控制器的能力与操作函数集 | 包含多个dma_chan |
dma_chan |
Core | 抽象DMA通道,管理传输队列与状态 | 关联dma_device和dma_async_tx_descriptor |
dma_slave_config |
API / Core | 配置外设侧传输参数(地址、宽度、突发) | 通过dmaengine_slave_config()传递给dma_chan |
dma_async_tx_descriptor |
Core | 描述一次DMA传输事务(地址、长度、回调) | 由dma_chan管理生命周期 |
scatterlist |
API | 描述物理非连续内存块,用于SG-DMA传输 | 作为参数传入dmaengine_prep_slave_sg() |
性能提示:在高速外设(如Gigabit Ethernet、高速ADC)中,建议使用SG-DMA(Scatter-Gather DMA)配合
dmaengine_prep_slave_sg(),避免大块连续物理内存分配失败。同时,合理设置src_maxburst和dst_maxburst为硬件支持的最大值,可显著提升DMA总线利用率。