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、文件操作等复杂机制。
Linux DMA引擎分层架构 第3层:用户/内核API层 dmaengine_prep_slave_sg() | dma_async_issue_pending() | async_tx API 第2层:dmaengine核心层 通道分配/释放 | 描述符管理 | 事务提交 | 回调分发 第1层:硬件驱动层 device_alloc_chan_resources | device_prep_slave_sg | device_issue_pending DMA控制器硬件 (DMAC) 分层解耦 上层不感知寄存器细节

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事务从申请到完成经历以下阶段:

  1. 通道申请: 调用 dma_request_channel() 获取一个满足过滤条件的DMA通道。
  2. 描述符准备: 调用 dmaengine_prep_slave_sg() 或 dmaengine_prep_dma_memcpy() 创建传输描述符。
  3. 提交事务: 调用 dmaengine_submit() 将描述符放入硬件待处理队列。
  4. 启动传输: 调用 dma_async_issue_pending() 通知硬件开始搬运数据。
  5. 等待完成: 通过回调函数或 dma_wait_for_async_tx() 同步等待传输结束。
  6. 释放通道: 使用完毕后调用 dma_release_channel() 归还通道。
dmaengine事务生命周期 ① 申请通道 ② 准备描述符 ③ 提交事务 ④ 启动传输 ⑤ 等待完成 ⑥ 释放通道

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 数据结构关系图

dmaengine核心数据结构关系 struct dma_device chancnt: 通道数量 channels: 通道链表 cap_mask: 能力位图 包含 struct dma_chan device: 所属控制器 cookie: 传输标识 chan_id: 通道ID 提交 dma_async_tx_descriptor tx_submit: 提交函数 callback: 完成回调 chan: 所属通道 dma_slave_config direction src_addr/dst_addr src_addr_width src_maxburst 配置

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地址空间
注意事项:
  1. 在调用 dmaengine_prep_slave_sg() 之前,必须先通过 dma_slave_config() 配置好通道参数。
  2. 描述符一旦通过 dmaengine_submit() 提交,不能再修改其内容。
  3. 中断回调函数中不能调用可能导致睡眠的函数(如 dma_alloc_coherent())。

2.5 本章小结

本章建立了Linux内核DMA子系统的整体认知:

  • 三层架构(硬件驱动层 → dmaengine核心层 → API层)实现了硬件无关性。
  • dmaengine框架通过通道、描述符、回调三大抽象,屏蔽了不同DMA控制器的差异。
  • 核心数据结构 dma_device、dma_chan、dma_async_tx_descriptor、dma_slave_config 构成了驱动开发的基础。
  • 掌握API调用流程(申请→配置→准备→提交→启动→完成→释放)是编写正确驱动的关键。