2. Linux DMA子系统概述:dmaengine框架介绍、dmaengine与平台DMA控制器的关系、dmaengine API分层结构
在嵌入式Linux系统中,直接内存访问(DMA)是提升数据吞吐量、降低CPU负载的关键技术。为了统一管理不同厂商、不同平台的DMA控制器,Linux内核引入了 dmaengine 框架。本章将深入剖析dmaengine的架构设计、与底层硬件的关系以及API的分层结构,为后续的环形缓冲区设计与实战编程打下坚实基础。
2.1 dmaengine框架介绍
dmaengine是Linux内核中一个通用的、抽象的DMA控制器驱动框架。它位于内核的 drivers/dma/ 目录下,旨在解决以下核心问题:
- 硬件差异屏蔽:不同SoC的DMA控制器(如TI的EDMA、NXP的eDMA、Synopsys的DW DMA)寄存器接口、通道管理、描述符格式各不相同。dmaengine通过统一的抽象层,让上层驱动(如音频、网络、存储)无需关心具体硬件细节。
- 通道管理与复用:提供标准的通道申请、释放、控制接口,支持虚拟通道与硬件通道的映射。
- 描述符链与异步操作:支持基于描述符(Descriptor)的链式传输,允许批量提交传输请求,并通过回调机制异步通知完成状态。
核心思想: dmaengine将DMA控制器抽象为一个“DMA通道”资源池。外设驱动(如SPI、I2S、UART)通过标准的API申请一个通道,配置源/目的地址、传输宽度、突发长度等参数,然后提交传输请求。框架负责将请求转化为硬件描述符,并调度硬件执行。
下图展示了dmaengine在Linux内核中的位置与核心组件关系:
2.2 dmaengine与平台DMA控制器的关系
dmaengine框架与底层平台DMA控制器之间通过 设备模型(Device Model) 和 回调函数(Callbacks) 进行解耦。理解这种关系是编写正确DMA驱动的关键。
2.2.1 核心数据结构:struct dma_device
每个DMA控制器驱动在初始化时,需要分配并填充一个 struct dma_device 实例,然后通过 dma_async_device_register() 将其注册到dmaengine核心。这个结构体是框架与硬件驱动之间的契约。
struct dma_device {
// 设备标识与能力
unsigned int chancnt; // 硬件通道数量
unsigned int embedded_hw; // 是否内嵌硬件
enum dma_capability cap_mask; // 设备能力位图
// 通道操作回调
int (*device_alloc_chan_resources)(struct dma_chan *chan);
void (*device_free_chan_resources)(struct dma_chan *chan);
// 描述符准备回调(核心)
struct dma_async_tx_descriptor *(*device_prep_slave_sg)(
struct dma_chan *chan, struct scatterlist *sgl,
unsigned int sg_len, enum dma_transfer_direction direction,
unsigned long flags, void *context);
struct dma_async_tx_descriptor *(*device_prep_dma_cyclic)(
struct dma_chan *chan, dma_addr_t buf_addr, size_t buf_len,
size_t period_len, enum dma_transfer_direction direction,
unsigned long flags);
// 传输控制回调
void (*device_issue_pending)(struct dma_chan *chan);
enum dma_status (*device_tx_status)(struct dma_chan *chan,
dma_cookie_t cookie, struct dma_tx_state *txstate);
// 设备控制
int (*device_control)(struct dma_chan *chan, enum dma_ctrl_cmd cmd,
unsigned long arg);
// ... 其他成员
};
2.2.2 平台DMA控制器的注册流程
一个典型的平台DMA控制器驱动(如 drivers/dma/imx-sdma.c)的初始化流程如下:
- 平台设备探测:在
probe()函数中,获取硬件寄存器基址、中断号、时钟等资源。 - 初始化硬件:复位DMA控制器,配置时钟,初始化通道寄存器。
- 分配并填充
struct dma_device:设置dev指针、chancnt、cap_mask,并实现所有必要的回调函数。 - 注册到dmaengine核心:调用
dma_async_device_register(),将设备加入全局链表。 - 创建虚拟通道:对于每个硬件通道,创建一个
struct dma_chan并关联到dma_device。
关键点: dmaengine框架不直接操作硬件寄存器。所有硬件访问都封装在平台驱动实现的回调函数中。框架通过
dma_device 中的函数指针调用底层代码,实现了“框架调用驱动,驱动操作硬件”的分层模式。
下图展示了dmaengine与平台DMA控制器之间的具体交互流程:
2.3 dmaengine API分层结构
dmaengine的API设计遵循严格的分层原则,从上层到底层依次为:消费者API、核心API、驱动API。理解每一层的职责和调用关系,是正确使用dmaengine的前提。
2.3.1 三层API概览
| 层次 | 主要头文件 | 使用者 | 核心函数/接口 |
|---|---|---|---|
| 消费者API | #include <linux/dmaengine.h> |
音频、网络、存储、SPI等外设驱动 | dma_request_channel(), dmaengine_prep_slave_sg(), dmaengine_submit(), dma_async_issue_pending(), dmaengine_terminate_sync() |
| 核心API | drivers/dma/dmaengine.c |
框架内部实现,对消费者透明 | 通道分配/释放、描述符队列管理、cookie分配、回调分发、dma_async_device_register() |
| 驱动API | #include <linux/dmaengine.h> |
平台DMA控制器驱动开发者 | 填充 struct dma_device,实现 device_alloc_chan_resources, device_prep_slave_sg, device_issue_pending, device_tx_status 等回调 |
2.3.2 消费者API典型调用序列
一个典型的外设驱动使用dmaengine进行数据传输的完整流程如下:
- 申请通道:
chan = dma_request_channel(mask, filter_fn, filter_param); - 配置从设备参数:
dmaengine_slave_config(chan, &config);设置地址、宽度、突发大小等。 - 准备描述符:
desc = dmaengine_prep_slave_sg(chan, sgl, sg_len, dir, flags); - 设置回调:
desc->callback = my_callback;desc->callback_param = my_data; - 提交传输:
cookie = dmaengine_submit(desc); - 启动传输:
dma_async_issue_pending(chan); - 等待完成:
dma_async_is_tx_complete(chan, cookie, &state);或在回调中处理。 - 终止与释放:
dmaengine_terminate_sync(chan);dma_release_channel(chan);
常见陷阱:
- 必须在
dmaengine_submit()之前设置好回调,否则可能丢失完成通知。 dma_async_issue_pending()必须在dmaengine_submit()之后调用,否则描述符不会被硬件执行。- 在中断上下文中,避免调用可能睡眠的函数(如
dma_request_channel())。
2.3.3 驱动API核心回调实现要点
对于平台DMA控制器驱动的开发者,需要重点关注以下回调的实现:
| 回调函数 | 触发时机 | 典型实现内容 |
|---|---|---|
device_alloc_chan_resources |
消费者调用 dma_request_channel() 时 |
初始化通道寄存器、分配描述符内存池、使能通道时钟 |
device_prep_slave_sg |
消费者调用 dmaengine_prep_slave_sg() 时 |
解析scatterlist,构建硬件描述符链表,返回 struct dma_async_tx_descriptor |
device_issue_pending |
消费者调用 dma_async_issue_pending() 时 |
将待处理描述符队列写入硬件启动寄存器,触发DMA传输 |
device_tx_status |
消费者轮询传输状态时 | 读取硬件状态寄存器,计算已传输字节数,返回 DMA_COMPLETE / DMA_IN_PROGRESS / DMA_ERROR |
device_control |
消费者调用 dmaengine_terminate_all() 等控制命令时 |
停止通道、刷新描述符队列、复位状态机 |
下图展示了dmaengine API的三层结构及其调用关系:
2.4 本章小结
- dmaengine框架通过 三层架构(消费者API、核心API、驱动API)实现了DMA控制器的硬件无关性。
- 平台DMA控制器驱动通过填充
struct dma_device并注册到核心,将硬件能力暴露给上层。 - 消费者驱动只需调用标准API即可完成通道申请、描述符准备、提交和启动,无需关心底层寄存器细节。
- 理解API分层结构是后续实现高效环形缓冲区设计的基础,尤其是在描述符复用和中断处理方面。