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内核中的位置与核心组件关系:

dmaengine框架核心组件关系图 dmaengine框架核心组件关系图 上层驱动(Consumer Drivers) 音频驱动 (ASoC) | 网络驱动 (Ethernet) | 存储驱动 (MMC/NAND) | SPI/I2C/UART dmaengine 核心API层 (drivers/dma/dmaengine.c) 通道管理:dma_request_channel / dma_release_channel 传输控制:dmaengine_prep_slave_sg / dmaengine_submit / dma_async_issue_pending DMA控制器驱动层(Platform Drivers) 实现 struct dma_device 中的回调函数 如:ti-edma.c | imx-sdma.c | dw-axi-dmac.c | stm32-dma.c 硬件层:DMA控制器(DMA Engine Hardware)

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)的初始化流程如下:

  1. 平台设备探测:在 probe() 函数中,获取硬件寄存器基址、中断号、时钟等资源。
  2. 初始化硬件:复位DMA控制器,配置时钟,初始化通道寄存器。
  3. 分配并填充 struct dma_device:设置 dev 指针、chancnt、cap_mask,并实现所有必要的回调函数。
  4. 注册到dmaengine核心:调用 dma_async_device_register(),将设备加入全局链表。
  5. 创建虚拟通道:对于每个硬件通道,创建一个 struct dma_chan 并关联到 dma_device。
关键点: dmaengine框架不直接操作硬件寄存器。所有硬件访问都封装在平台驱动实现的回调函数中。框架通过 dma_device 中的函数指针调用底层代码,实现了“框架调用驱动,驱动操作硬件”的分层模式。

下图展示了dmaengine与平台DMA控制器之间的具体交互流程:

dmaengine与平台DMA控制器交互流程 dmaengine与平台DMA控制器交互流程 上层驱动 dma_request_channel() dmaengine_prep_slave_sg() dmaengine_submit() dmaengine核心 查找空闲通道 调用 device_prep_slave_sg 将描述符加入队列 平台DMA驱动 alloc_chan_resources() 构建硬件描述符 写寄存器启动传输 DMA控制器硬件 执行内存/外设传输 中断回调:dma_cookie_complete() → 通知上层

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进行数据传输的完整流程如下:

  1. 申请通道:chan = dma_request_channel(mask, filter_fn, filter_param);
  2. 配置从设备参数:dmaengine_slave_config(chan, &config); 设置地址、宽度、突发大小等。
  3. 准备描述符:desc = dmaengine_prep_slave_sg(chan, sgl, sg_len, dir, flags);
  4. 设置回调:desc->callback = my_callback; desc->callback_param = my_data;
  5. 提交传输:cookie = dmaengine_submit(desc);
  6. 启动传输:dma_async_issue_pending(chan);
  7. 等待完成:dma_async_is_tx_complete(chan, cookie, &state); 或在回调中处理。
  8. 终止与释放: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的三层结构及其调用关系:

dmaengine API分层结构图 dmaengine API分层结构图 消费者API层(Consumer API) dma_request_channel | dmaengine_prep_slave_sg | dmaengine_submit | dma_async_issue_pending 头文件:<linux/dmaengine.h> 核心API层(Core API) 通道管理 | 描述符队列 | Cookie分配 | 回调分发 实现文件:drivers/dma/dmaengine.c 驱动API层(Driver API) 实现 struct dma_device 回调函数 device_alloc_chan_resources | device_prep_slave_sg | device_issue_pending | device_tx_status DMA控制器硬件寄存器

2.4 本章小结

  • dmaengine框架通过 三层架构(消费者API、核心API、驱动API)实现了DMA控制器的硬件无关性。
  • 平台DMA控制器驱动通过填充 struct dma_device 并注册到核心,将硬件能力暴露给上层。
  • 消费者驱动只需调用标准API即可完成通道申请、描述符准备、提交和启动,无需关心底层寄存器细节。
  • 理解API分层结构是后续实现高效环形缓冲区设计的基础,尤其是在描述符复用和中断处理方面。