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传输事务的描述符。
DMA引擎框架三层架构 DMA引擎框架三层架构 客户端驱动层(Client Driver) SPI、I2C、UART、Audio、Video等外设驱动 dmaengine核心层(Core) 通道管理 · 事务队列 · 回调机制 · dma_device / dma_chan DMA控制器驱动层(Controller Driver) PL330 · EDMA · SDMA · 各平台DMA硬件寄存器操作 API调用方向:客户端 → 核心 → 控制器驱动

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传输遵循申请→配置→准备→提交→触发→完成回调的步骤:

  1. 申请通道:chan = dma_request_chan(dev, "rx");
  2. 配置从设备:dmaengine_slave_config(chan, &config);
  3. 准备描述符:desc = dmaengine_prep_slave_sg(chan, sgl, nents, dir, flags);
  4. 设置回调:desc->callback = my_dma_callback;
  5. 提交事务:cookie = dmaengine_submit(desc);
  6. 触发传输:dma_async_issue_pending(chan);
  7. 等待完成:在回调函数中处理数据或使用 dma_sync_wait() 同步等待。
常见错误:
  • 未调用 dmaengine_slave_config() 直接准备描述符,导致传输参数错误。
  • 提交描述符后忘记调用 dma_async_issue_pending(),传输永远不会启动。
  • 在中断上下文中调用可能睡眠的API(如 dma_request_chan())。
DMA传输API调用流程 DMA传输API调用流程 dma_request_chan() dmaengine_slave_config() dmaengine_prep_slave_sg() dmaengine_submit() dma_async_issue_pending() 回调函数 / dma_sync_wait() 每个步骤都必须检查返回值,失败时需回滚已分配的资源

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 描述符生命周期

  1. 创建:通过 dmaengine_prep_*() 系列函数创建,此时描述符处于“空闲”状态。
  2. 提交:dmaengine_submit() 将描述符加入通道的事务队列,返回cookie。
  3. 执行:dma_async_issue_pending() 触发DMA控制器开始处理队列中的描述符。
  4. 完成:传输完成后,DMA控制器触发中断,核心层调用描述符的 callback 函数。
  5. 释放:回调执行后,描述符被核心层自动回收(或由控制器驱动回收)。
重要规则:
  • 描述符在提交后,禁止修改其内容(包括回调函数指针)。
  • 回调函数中不能调用 dmaengine_submit() 或 dma_async_issue_pending() 等可能引起重入的函数。
  • 如果需要在回调中再次发起DMA传输,应使用工作队列(workqueue)延后执行。
DMA描述符生命周期 DMA描述符生命周期 空闲(Idle) prep_*() 已提交(Submitted) issue_pending() 执行中(In Progress) 传输完成中断 已完成(Completed) 回调执行 回收(Reclaimed) 内核自动回收 描述符一旦提交,驱动不应再访问其内存,回收由dmaengine核心层自动完成

2.5 本章小结

本章系统梳理了Linux DMA子系统的核心框架与API:

  • dmaengine框架采用三层架构,实现了硬件无关的DMA编程模型。
  • 核心API遵循“申请→配置→准备→提交→触发”的固定流程,每个步骤都有明确的错误处理要求。
  • dma_slave_config是连接外设与DMA控制器的关键配置结构,方向、地址、宽度、突发长度必须与外设规格严格匹配。
  • dma_async_tx_descriptor管理一次DMA传输的完整生命周期,驱动开发者必须遵守描述符的使用规则,避免在回调中直接发起新传输。
下一阶段准备:在掌握DMA子系统基础后,第3章将深入讲解DMA内存映射的两种模式——一致性映射(coherent)和流式映射(streaming),并分析它们与Cache一致性的关系。建议读者在阅读下一章前,先回顾本章的API流程和描述符生命周期。