2. Linux DMA子系统概述

在嵌入式Linux系统中,DMA(Direct Memory Access)子系统是连接外设与内存之间高速数据通道的核心基础设施。本章将深入剖析Linux内核DMA API的架构层次、dmaengine框架的设计哲学以及关键数据结构,帮助开发者建立从硬件寄存器到内核抽象层的完整认知。

2.1 Linux内核DMA API架构

Linux内核的DMA API采用分层设计,从上到下依次为:设备驱动层、通用DMA API层、DMA引擎层和硬件抽象层。这种分层结构使得上层驱动无需关心底层DMA控制器的具体实现细节。

架构要点: Linux DMA API的核心设计目标是提供统一的接口,让设备驱动开发者能够以标准方式申请、使用和释放DMA通道,同时保持对不同硬件平台的可移植性。
Linux DMA API 分层架构 设备驱动层 (Device Driver) dma_request_channel() / dmaengine_prep_slave_sg() / dma_async_issue_pending() 通用DMA API层 (Generic DMA API) dma_map_single() / dma_alloc_coherent() / dma_sync_single_for_device() DMA引擎层 (dmaengine Core) dma_device / dma_chan / dma_async_tx_descriptor / tx_submit() 硬件抽象层 (HAL / DMA Controller Driver) pl330_dma_probe() / edma_alloc_chan_resources() / dma_controller_ops 数据流方向:设备驱动 → 通用API → dmaengine核心 → 硬件驱动 → DMA控制器寄存器

2.2 dmaengine框架介绍

dmaengine是Linux内核中专门为DMA控制器设计的通用框架,位于drivers/dma/目录下。它提供了一套标准化的接口,使得不同厂商的DMA控制器(如TI的EDMA、ARM的PL330、Intel的IDMA等)能够以统一的方式被上层驱动使用。

2.2.1 dmaengine核心组件

组件 角色 关键函数/回调
DMA设备 代表一个DMA控制器硬件实例 dma_async_device_register()
DMA通道 代表一个独立的DMA传输通道 dma_request_channel()
传输描述符 描述一次DMA传输的所有参数 dmaengine_prep_slave_sg()
传输操作 控制传输的执行与状态 dma_async_issue_pending()
设计思路: dmaengine框架借鉴了Linux网络子系统的设计模式——将传输请求抽象为描述符(descriptor),支持异步提交和回调通知,从而实现CPU与DMA控制器的解耦并行工作。

2.2.2 dmaengine工作流程

典型的dmaengine使用流程包含以下步骤:

  1. 通道申请: 调用 dma_request_channel() 获取一个满足需求的DMA通道。
  2. 配置参数: 通过 dmaengine_slave_config() 设置源/目的地址、数据宽度、突发长度等。
  3. 准备描述符: 使用 dmaengine_prep_slave_sg() 或 dmaengine_prep_dma_memcpy() 创建传输描述符。
  4. 提交传输: 调用 dmaengine_submit() 将描述符加入通道的待处理队列。
  5. 启动传输: 调用 dma_async_issue_pending() 通知DMA控制器开始执行。
  6. 等待完成: 通过回调函数或 dma_wait_for_async_tx() 等待传输结束。
dmaengine 典型工作流程 申请通道 配置参数 准备描述符 提交传输 启动传输 等待完成 关键点:描述符提交(submit)后并未立即执行,必须调用 issue_pending() 才会真正触发DMA硬件传输 异步特性:CPU在提交传输后可以继续执行其他任务,传输完成时通过回调函数获得通知

2.3 主要数据结构

dmaengine框架的核心数据结构定义了DMA设备、通道、传输描述符等关键实体的属性和操作接口。理解这些数据结构是进行DMA驱动开发的基础。

2.3.1 struct dma_device

struct dma_device 代表一个DMA控制器设备,包含设备能力描述、通道列表和操作函数集。

struct dma_device {
    unsigned int    chancnt;        /* 通道数量 */
    unsigned int    max_burst;      /* 最大突发长度 */
    u32             src_addr_widths; /* 支持的源地址宽度位图 */
    u32             dst_addr_widths; /* 支持的目的地址宽度位图 */
    u32             directions;     /* 支持的传输方向位图 */
    u32             cap_mask;       /* 设备能力掩码 */
    
    struct list_head channels;      /* 通道链表头 */
    struct list_head global_node;   /* 全局设备链表节点 */
    
    /* 操作函数集 */
    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_dma_memcpy)(
        struct dma_chan *chan, dma_addr_t dst, dma_addr_t src,
        size_t len, unsigned long flags);
    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);
    int (*device_config)(struct dma_chan *chan,
                         struct dma_slave_config *config);
    enum dma_status (*device_tx_status)(struct dma_chan *chan,
                                        dma_cookie_t cookie,
                                        struct dma_tx_state *txstate);
    void (*device_issue_pending)(struct dma_chan *chan);
};

2.3.2 struct dma_chan

struct dma_chan 代表一个已分配的DMA通道,是驱动与dmaengine交互的主要句柄。

struct dma_chan {
    struct dma_device *device;      /* 所属DMA设备 */
    dma_cookie_t cookie;            /* 当前传输的cookie */
    dma_cookie_t completed_cookie;  /* 已完成传输的cookie */
    
    void *private;                  /* 通道私有数据 */
    struct list_head device_node;   /* 设备通道链表节点 */
    struct list_head global_node;   /* 全局通道链表节点 */
    
    /* 回调相关 */
    dma_async_tx_callback callback;
    void *callback_param;
    
    /* 通道属性 */
    enum dma_transfer_direction dir;
    struct dma_slave_config slave_config;
};

2.3.3 struct dma_async_tx_descriptor

传输描述符是dmaengine框架中最核心的数据结构,它封装了一次DMA传输的所有信息,包括源/目的地址、传输长度、回调函数等。

struct dma_async_tx_descriptor {
    dma_cookie_t cookie;            /* 传输cookie,用于跟踪状态 */
    enum dma_ctrl_flags flags;      /* 控制标志位 */
    dma_addr_t src;                 /* 源物理地址 */
    dma_addr_t dst;                 /* 目的物理地址 */
    size_t len;                     /* 传输长度(字节) */
    
    struct dma_chan *chan;          /* 所属通道 */
    
    /* 回调函数 */
    dma_async_tx_callback callback;
    void *callback_param;
    
    /* 描述符操作 */
    struct dma_async_tx_descriptor *(*tx_submit)(
        struct dma_async_tx_descriptor *tx);
    int (*desc_free)(struct dma_async_tx_descriptor *tx);
    
    /* 链表节点,用于描述符队列 */
    struct list_head tx_list;
    struct list_head node;
};

2.3.4 struct dma_slave_config

该结构体用于配置从设备(外设)与DMA控制器之间的传输参数,通常在调用 device_config() 时使用。

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;               /* 目的最大突发长度 */
    u32 slave_id;                   /* 从设备ID(平台相关) */
};
常见陷阱: 在配置 dma_slave_config 时,src_addr 和 dst_addr 必须是物理地址(总线地址),而非内核虚拟地址。对于MMU使能的系统,需要使用 dma_map_single() 或 dma_alloc_coherent() 获取正确的DMA地址。

2.4 数据结构关系图

以下SVG图展示了dmaengine框架中主要数据结构之间的关联关系:

dmaengine 核心数据结构关系 struct dma_device chancnt: 通道数量 cap_mask: 能力掩码 channels: 通道链表 struct dma_chan device: 所属DMA设备 cookie: 传输跟踪 slave_config: 从设备配置 struct dma_async_tx_descriptor src/dst: 源/目的地址 len: 传输长度 callback: 完成回调 struct dma_slave_config src_addr/dst_addr src_maxburst/dst_maxburst direction: 传输方向 包含 提交 配置 关键关系:一个 dma_device 管理多个 dma_chan,每个 dma_chan 可以提交多个描述符 描述符通过 tx_submit() 加入通道队列,由 device_issue_pending() 触发硬件执行

2.5 DMA传输类型与能力描述

dmaengine框架支持多种DMA传输类型,通过 dma_cap_mask_t 位图来描述设备的能力:

能力标志 传输类型 典型应用场景
DMA_MEMCPY 内存到内存拷贝 数据搬移、缓冲区复制
DMA_SLAVE 外设到内存/内存到外设 SPI、I2C、UART等外设数据收发
DMA_CYCLIC 循环传输 音频流、连续数据采集
DMA_INTERLEAVE 交错传输 多通道数据交织处理
DMA_PRIVATE 私有通道 特定外设独占使用
实践建议: 在编写DMA驱动时,应优先使用 dma_get_any_slave_channel() 或 dma_request_chan() 来获取通道,而不是直接操作 dma_device 的通道链表。这样可以确保代码与未来内核版本的兼容性。