2. Linux内核DMA子系统概述
DMA(Direct Memory Access,直接存储器访问)是嵌入式Linux系统中实现高性能数据搬运的核心硬件机制。本章从全局视角剖析Linux内核DMA子系统的分层架构、核心框架以及关键数据结构,为后续编写具体DMA控制器驱动奠定理论基础。
2.1 Linux DMA引擎架构
Linux内核的DMA子系统采用分层解耦的设计思想,将硬件相关的控制器操作与上层通用API彻底分离。整个架构自底向上分为三层:
| 层次 | 名称 | 职责 | 典型文件/模块 |
|---|---|---|---|
| 第1层 | 硬件驱动层 | 直接操作DMA控制器寄存器,处理通道配置、描述符链、中断 | drivers/dma/ 下的各平台驱动 |
| 第2层 | dmaengine核心层 | 提供统一的框架、通道管理、事务抽象、回调机制 | drivers/dma/dmaengine.c |
| 第3层 | 用户/内核API层 | 向其他内核子系统(音频、网络、存储)或用户空间暴露标准接口 | include/linux/dmaengine.h |
架构设计要点: 硬件驱动层只需实现dmaengine框架规定的回调函数集(如device_prep_slave_sg、device_issue_pending),即可自动获得上层API支持,无需关心VFS、文件操作等复杂机制。
2.2 dmaengine框架介绍
dmaengine是Linux内核自2.6.31版本引入的通用DMA框架,旨在替代各平台零散的私有DMA接口。框架的核心设计理念包括:
- 通道抽象: 每个DMA控制器包含多个通道,每个通道代表一条独立的数据搬运路径。
- 描述符链: 支持将多个DMA传输描述符链接成链,硬件自动逐个执行,减少CPU干预。
- 异步回调: 传输完成时通过回调通知上层,支持同步等待和异步通知两种模式。
- 事务类型: 支持memcpy、slave_sg(散列表)、cyclic(循环)、interleaved(交错)等多种传输模式。
框架优势: 驱动开发者只需实现约10个核心回调函数,即可让DMA控制器被音频、SPI、UART、网络等子系统复用。框架自动处理通道引用计数、电源管理、调试fs接口等通用逻辑。
2.2.1 核心工作流程
一个典型的dmaengine事务从申请到完成经历以下阶段:
- 通道申请: 调用
dma_request_channel()获取一个满足过滤条件的DMA通道。 - 描述符准备: 调用
dmaengine_prep_slave_sg()或dmaengine_prep_dma_memcpy()创建传输描述符。 - 提交事务: 调用
dmaengine_submit()将描述符放入硬件待处理队列。 - 启动传输: 调用
dma_async_issue_pending()通知硬件开始搬运数据。 - 等待完成: 通过回调函数或
dma_wait_for_async_tx()同步等待传输结束。 - 释放通道: 使用完毕后调用
dma_release_channel()归还通道。
2.3 主要内核头文件与数据结构
开发DMA控制器驱动需要熟悉以下核心头文件和数据结构,它们是框架与硬件驱动之间的契约。
2.3.1 关键头文件
| 头文件路径 | 提供内容 | 使用场景 |
|---|---|---|
include/linux/dmaengine.h |
核心API、数据结构、回调函数原型 | 所有DMA驱动和用户必须包含 |
include/linux/dma-mapping.h |
DMA地址映射、流式/一致性API | 分配DMA缓冲区、转换虚拟地址 |
include/linux/dma-direction.h |
DMA传输方向枚举 | 指定DMA_TO_DEVICE/DMA_FROM_DEVICE |
include/linux/interrupt.h |
中断处理函数注册 | DMA传输完成中断处理 |
2.3.2 核心数据结构
以下四个数据结构是dmaengine框架的基石,每个DMA控制器驱动都必须与之交互:
数据结构关系: struct dma_device 描述控制器整体能力;struct dma_chan 代表一个通道实例;struct dma_async_tx_descriptor 描述一次传输任务;struct dma_slave_config 配置从设备端参数。
// 文件:include/linux/dmaengine.h
/**
* struct dma_device - 代表一个DMA控制器
* @dev: 设备结构体指针
* @chancnt: 通道数量
* @channels: 通道链表头
* @cap_mask: 控制器支持的能力位图
* @device_alloc_chan_resources: 分配通道资源回调
* @device_free_chan_resources: 释放通道资源回调
* @device_prep_slave_sg: 准备散列表传输描述符
* @device_prep_dma_memcpy: 准备内存拷贝描述符
* @device_issue_pending: 启动待处理传输
* @device_tx_status: 查询传输状态
*/
struct dma_device {
struct device *dev;
unsigned int chancnt;
struct list_head channels;
dma_cap_mask_t 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_memcpy)(
struct dma_chan *chan, dma_addr_t dest, dma_addr_t src,
size_t len, 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);
};
/**
* struct dma_chan - DMA通道实例
* @device: 所属的DMA控制器
* @cookie: 当前传输的cookie标识
* @chan_id: 通道ID
* @slave: 指向从设备配置
*/
struct dma_chan {
struct dma_device *device;
dma_cookie_t cookie;
int chan_id;
struct dma_slave_config *slave;
};
/**
* struct dma_async_tx_descriptor - 异步传输描述符
* @tx_submit: 提交描述符到硬件队列
* @callback: 传输完成回调函数
* @callback_param: 回调参数
* @chan: 所属通道
*/
struct dma_async_tx_descriptor {
dma_cookie_t (*tx_submit)(struct dma_async_tx_descriptor *tx);
dma_async_tx_callback callback;
void *callback_param;
struct dma_chan *chan;
};
/**
* struct dma_slave_config - 从设备DMA配置
* @direction: 传输方向
* @src_addr: 源外设地址
* @dst_addr: 目的外设地址
* @src_addr_width: 源数据宽度(字节)
* @dst_addr_width: 目的数据宽度
* @src_maxburst: 源突发传输长度
* @dst_maxburst: 目的突发传输长度
*/
struct dma_slave_config {
enum dma_transfer_direction direction;
dma_addr_t src_addr;
dma_addr_t dst_addr;
enum dma_slave_buswidth src_addr_width;
enum dma_slave_buswidth dst_addr_width;
u32 src_maxburst;
u32 dst_maxburst;
};
2.3.3 数据结构关系图
2.4 关键API速查
下表汇总了DMA驱动开发中最常用的API函数及其用途:
| API函数 | 所属层次 | 功能描述 |
|---|---|---|
dma_request_channel() |
核心层 | 根据掩码和过滤函数申请DMA通道 |
dma_release_channel() |
核心层 | 释放已申请的DMA通道 |
dmaengine_prep_slave_sg() |
核心层 | 准备从设备散列表传输描述符 |
dmaengine_prep_dma_cyclic() |
核心层 | 准备循环传输描述符(音频常用) |
dmaengine_submit() |
核心层 | 提交描述符到硬件待处理队列 |
dma_async_issue_pending() |
核心层 | 触发硬件开始执行待处理传输 |
dma_async_is_tx_complete() |
核心层 | 查询传输是否完成 |
dma_slave_config() |
核心层 | 配置从设备DMA参数(地址、宽度、突发) |
dma_alloc_coherent() |
DMA映射层 | 分配一致性DMA缓冲区 |
dma_map_sg() |
DMA映射层 | 映射散列表到DMA地址空间 |
注意事项:
- 在调用
dmaengine_prep_slave_sg()之前,必须先通过dma_slave_config()配置好通道参数。 - 描述符一旦通过
dmaengine_submit()提交,不能再修改其内容。 - 中断回调函数中不能调用可能导致睡眠的函数(如
dma_alloc_coherent())。
2.5 本章小结
本章建立了Linux内核DMA子系统的整体认知:
- 三层架构(硬件驱动层 → dmaengine核心层 → API层)实现了硬件无关性。
- dmaengine框架通过通道、描述符、回调三大抽象,屏蔽了不同DMA控制器的差异。
- 核心数据结构
dma_device、dma_chan、dma_async_tx_descriptor、dma_slave_config构成了驱动开发的基础。 - 掌握API调用流程(申请→配置→准备→提交→启动→完成→释放)是编写正确驱动的关键。