2. Linux DMA子系统概述
DMA(Direct Memory Access)是嵌入式Linux系统中实现高性能数据传输的核心硬件机制。本章深入剖析Linux内核DMA子系统的软件架构,重点讲解dmaengine框架、核心API、从设备配置以及异步传输描述符四个关键层次,为后续DMA内存映射与Cache一致性调试奠定基础。
学习提示:DMA子系统是连接CPU、内存和外设的数据高速公路。理解dmaengine框架的分层设计,是掌握DMA驱动调试的前提。建议结合内核源码
drivers/dma/ 目录下的控制器驱动进行对照学习。
2.1 DMA引擎框架(dmaengine)
Linux内核的DMA引擎框架(dmaengine)是一个通用、抽象的DMA控制器驱动框架,位于 drivers/dma/ 目录下。它将不同硬件平台的DMA控制器差异封装在底层,向上层(客户端驱动)提供统一的API接口。
框架采用三层架构:
- DMA控制器驱动层:直接操作硬件寄存器,实现通道分配、传输控制、中断处理等底层功能。
- dmaengine核心层:提供通道管理、事务队列、回调机制等通用逻辑,维护
dma_device和dma_chan数据结构。 - 客户端驱动层:通过标准API发起DMA传输请求,无需关心底层硬件细节。
核心数据结构:
struct dma_device:描述一个DMA控制器的能力(通道数、传输类型、地址宽度等)。struct dma_chan:代表一个已分配的DMA通道,包含通道状态、回调函数、客户端私有数据。struct dma_slave_config:从设备(如SPI、I2C、UART)的DMA传输参数配置。struct dma_async_tx_descriptor:描述一次异步DMA传输事务的描述符。
2.2 dmaengine API详解
dmaengine API是客户端驱动与DMA子系统交互的标准接口。掌握这些API的调用顺序和参数含义,是编写正确DMA驱动的第一步。
2.2.1 核心API函数
| API函数 | 功能描述 | 关键参数 |
|---|---|---|
dma_request_chan() |
请求分配一个DMA通道 | 设备指针、通道名称 |
dmaengine_slave_config() |
配置从设备DMA传输参数 | 通道指针、dma_slave_config结构体 |
dmaengine_prep_slave_sg() |
准备SG(Scatter-Gather)传输描述符 | 通道、SG列表、方向、标志 |
dmaengine_submit() |
提交描述符到传输队列 | 描述符指针 |
dma_async_issue_pending() |
触发DMA控制器开始处理队列 | 通道指针 |
dmaengine_terminate_sync() |
同步终止当前DMA传输 | 通道指针 |
dma_release_channel() |
释放DMA通道 | 通道指针 |
2.2.2 典型DMA传输流程
一次完整的DMA传输遵循申请→配置→准备→提交→触发→完成回调的步骤:
- 申请通道:
chan = dma_request_chan(dev, "rx"); - 配置从设备:
dmaengine_slave_config(chan, &config); - 准备描述符:
desc = dmaengine_prep_slave_sg(chan, sgl, nents, dir, flags); - 设置回调:
desc->callback = my_dma_callback; - 提交事务:
cookie = dmaengine_submit(desc); - 触发传输:
dma_async_issue_pending(chan); - 等待完成:在回调函数中处理数据或使用
dma_sync_wait()同步等待。
常见错误:
- 未调用
dmaengine_slave_config()直接准备描述符,导致传输参数错误。 - 提交描述符后忘记调用
dma_async_issue_pending(),传输永远不会启动。 - 在中断上下文中调用可能睡眠的API(如
dma_request_chan())。
2.3 dma_slave_config配置
struct dma_slave_config 是连接DMA控制器与具体外设(从设备)的桥梁。它告诉DMA控制器:数据从哪里来、到哪里去、数据宽度是多少、是否使用流控等关键信息。
2.3.1 结构体成员详解
| 成员 | 类型 | 说明 |
|---|---|---|
direction |
enum dma_transfer_direction | 传输方向:DMA_MEM_TO_DEV、DMA_DEV_TO_MEM、DMA_MEM_TO_MEM |
src_addr |
phys_addr_t | 源物理地址(外设数据寄存器地址) |
dst_addr |
phys_addr_t | 目的物理地址 |
src_addr_width |
enum dma_slave_buswidth | 源数据宽度:1、2、4、8字节 |
dst_addr_width |
enum dma_slave_buswidth | 目的数据宽度 |
src_maxburst |
u32 | 源端最大突发传输长度(单位:数据宽度) |
dst_maxburst |
u32 | 目的端最大突发传输长度 |
device_fc |
bool | 是否由外设硬件控制流控(true=硬件流控) |
2.3.2 配置示例:SPI RX DMA
struct dma_slave_config config;
struct dma_chan *chan;
int ret;
/* 请求DMA通道 */
chan = dma_request_chan(&spi_dev->dev, "rx");
if (IS_ERR(chan))
return PTR_ERR(chan);
/* 配置从设备参数 */
memset(&config, 0, sizeof(config));
config.direction = DMA_DEV_TO_MEM; /* 外设→内存 */
config.src_addr = (phys_addr_t)spi_base + SPI_DR; /* SPI数据寄存器地址 */
config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; /* 32位数据宽度 */
config.src_maxburst = 4; /* 每次最多传输4个数据单元 */
config.device_fc = false; /* 软件流控 */
/* 应用配置 */
ret = dmaengine_slave_config(chan, &config);
if (ret) {
dev_err(dev, "DMA slave config failed: %d\n", ret);
dma_release_channel(chan);
return ret;
}
调试要点:配置
src_addr 和 dst_addr 时,务必确认使用的是物理地址而非虚拟地址。外设寄存器地址通常通过 platform_get_resource() 获取后转换为物理地址。
2.4 dma_async_tx_descriptor
struct dma_async_tx_descriptor 是DMA传输的事务描述符,它封装了一次DMA传输的所有信息:源地址、目的地址、传输长度、回调函数、标志位等。理解描述符的生命周期管理,是避免内存泄漏和DMA传输异常的关键。
2.4.1 描述符关键字段
| 字段 | 类型 | 说明 |
|---|---|---|
txd.cookie |
dma_cookie_t | 传输事务的唯一标识符,由 dmaengine_submit() 返回 |
callback |
dma_async_tx_callback | 传输完成后的回调函数指针 |
callback_param |
void * | 回调函数的私有参数 |
tx_submit |
函数指针 | 底层提交函数,由 dmaengine_submit() 内部调用 |
phys |
dma_addr_t | 描述符本身的物理地址(用于硬件链表) |
2.4.2 描述符生命周期
- 创建:通过
dmaengine_prep_*()系列函数创建,此时描述符处于“空闲”状态。 - 提交:
dmaengine_submit()将描述符加入通道的事务队列,返回cookie。 - 执行:
dma_async_issue_pending()触发DMA控制器开始处理队列中的描述符。 - 完成:传输完成后,DMA控制器触发中断,核心层调用描述符的
callback函数。 - 释放:回调执行后,描述符被核心层自动回收(或由控制器驱动回收)。
重要规则:
- 描述符在提交后,禁止修改其内容(包括回调函数指针)。
- 回调函数中不能调用
dmaengine_submit()或dma_async_issue_pending()等可能引起重入的函数。 - 如果需要在回调中再次发起DMA传输,应使用工作队列(workqueue)延后执行。
2.5 本章小结
本章系统梳理了Linux DMA子系统的核心框架与API:
- dmaengine框架采用三层架构,实现了硬件无关的DMA编程模型。
- 核心API遵循“申请→配置→准备→提交→触发”的固定流程,每个步骤都有明确的错误处理要求。
- dma_slave_config是连接外设与DMA控制器的关键配置结构,方向、地址、宽度、突发长度必须与外设规格严格匹配。
- dma_async_tx_descriptor管理一次DMA传输的完整生命周期,驱动开发者必须遵守描述符的使用规则,避免在回调中直接发起新传输。
下一阶段准备:在掌握DMA子系统基础后,第3章将深入讲解DMA内存映射的两种模式——一致性映射(coherent)和流式映射(streaming),并分析它们与Cache一致性的关系。建议读者在阅读下一章前,先回顾本章的API流程和描述符生命周期。