4. Linux DMA子系统概述

Linux内核提供了一套统一的DMA引擎框架——dmaengine,用于抽象不同硬件平台的DMA控制器差异,为驱动开发者提供标准化的API接口。本章深入剖析dmaengine的分层架构、核心数据结构及其协作关系。

4.1 Linux内核DMA引擎框架

dmaengine框架位于内核源码 drivers/dma/ 目录下,采用三层次架构:

  • 硬件适配层:各厂商DMA控制器驱动(如Intel IDMA、TI EDMA、PL330等)实现底层寄存器操作。
  • 核心管理层:提供通道分配、描述符管理、传输调度等通用逻辑。
  • 用户接口层:向其他内核子系统(如音频、网络、存储)暴露统一的dmaengine API。
设计目标: 解耦DMA控制器硬件差异,使驱动开发者无需关心具体寄存器细节,仅通过标准API即可完成DMA传输配置与启动。
dmaengine 三层架构 用户接口层 dmaengine API (dma_async_* 系列函数) 核心管理层 通道管理 · 描述符队列 · 传输调度 · 回调机制 硬件适配层 Intel IDMA · TI EDMA · PL330 · 各厂商DMA驱动 上层调用标准API,下层屏蔽硬件差异,中间层提供通用服务

4.2 dmaengine API分层结构

dmaengine API按照使用场景分为设备注册层、通道管理层和传输控制层三层:

层次 核心函数 功能描述
设备注册层 dma_async_device_register() DMA控制器驱动向内核注册自身能力
通道管理层 dma_request_channel() 申请一个满足特定能力的DMA通道
通道管理层 dma_release_channel() 释放不再使用的DMA通道
传输控制层 dmaengine_prep_slave_sg() 准备分散/聚集传输描述符
传输控制层 dmaengine_prep_dma_memcpy() 准备内存拷贝传输描述符
传输控制层 dmaengine_submit() 将描述符提交到硬件执行队列
传输控制层 dma_async_issue_pending() 触发硬件开始执行已提交的传输
典型调用序列:
dma_request_channel() → dmaengine_prep_slave_sg() → dmaengine_submit() → dma_async_issue_pending()
dmaengine API 典型调用流程 dma_request_channel dmaengine_prep_slave_sg dmaengine_submit dma_async_issue_pending 每个步骤返回的句柄/描述符需进行错误检查,确保传输可靠性

4.3 核心数据结构

dmaengine框架围绕三个核心数据结构构建:dma_device、dma_chan 和 dma_async_tx_descriptor。它们分别代表DMA控制器、DMA通道和一次传输事务。

4.3.1 dma_device — DMA控制器抽象

dma_device 描述一个DMA控制器的能力和操作函数集,是驱动注册时的核心对象:

struct dma_device {
    unsigned int    chancnt;          /* 通道数量 */
    unsigned int    copy_align;       /* 内存拷贝对齐要求 */
    enum dma_device_state  state;     /* 设备状态 */

    /* 能力位图 */
    u32             src_addr_widths;  /* 支持的源地址宽度 */
    u32             dst_addr_widths;  /* 支持的目的地址宽度 */
    u32             directions;       /* 支持的传输方向 */
    u32             max_burst;        /* 最大突发传输长度 */

    /* 操作函数集 */
    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_issue_pending)(struct dma_chan *chan);
    int (*device_tx_status)(struct dma_chan *chan, dma_cookie_t cookie,
                            struct dma_tx_state *txstate);
};
关键约束: copy_align 字段定义了内存拷贝时地址和长度的对齐要求(以2的幂次表示)。例如 copy_align = 3 表示必须8字节对齐。违反对齐会导致硬件异常或数据损坏。

4.3.2 dma_chan — DMA通道抽象

dma_chan 代表一个已分配的DMA通道,是驱动与DMA引擎交互的句柄:

struct dma_chan {
    struct dma_device *device;        /* 所属DMA设备 */
    dma_cookie_t       cookie;        /* 当前传输cookie */
    dma_cookie_t       completed_cookie; /* 已完成传输cookie */

    /* 通道属性 */
    enum dma_transfer_direction  direction;
    dma_addr_t         slave_addr;    /* 外设地址 */
    enum dma_slave_buswidth  addr_widths;
    u32                burst_size;

    /* 回调机制 */
    dma_async_tx_callback  callback;
    void                *callback_param;

    /* 内部状态 */
    struct list_head    free_nodes;   /* 空闲描述符链表 */
    struct list_head    submitted;    /* 已提交队列 */
    struct list_head    issued;       /* 已下发队列 */
    struct list_head    completed;    /* 已完成队列 */
};

4.3.3 dma_async_tx_descriptor — 传输描述符

每次DMA传输操作被封装为一个 dma_async_tx_descriptor,它描述了传输的所有参数和状态:

struct dma_async_tx_descriptor {
    dma_cookie_t        cookie;       /* 传输标识符 */
    enum dma_ctrl_flags flags;        /* 控制标志 */
    dma_addr_t          src;          /* 源地址 */
    dma_addr_t          dst;          /* 目的地址 */
    size_t              len;          /* 传输长度 */

    /* 链表支持(用于分散收集) */
    struct list_head    tx_list;      /* 描述符链表节点 */
    unsigned int        sg_count;     /* 散列表项数量 */

    /* 回调 */
    dma_async_tx_callback  callback;  /* 传输完成回调 */
    void                *callback_param;

    /* 依赖管理 */
    struct dma_async_tx_descriptor *next;
    struct dma_async_tx_descriptor *prev;

    /* 硬件操作 */
    enum dma_status (*tx_status)(struct dma_chan *chan,
                                 dma_cookie_t cookie,
                                 struct dma_tx_state *txstate);
    void (*tx_submit)(struct dma_async_tx_descriptor *tx);
};
三大核心数据结构关系 dma_device chancnt: 4 copy_align: 3 device_prep_slave_sg() device_issue_pending() 包含 dma_chan device: dma_device* cookie: 42 slave_addr: 0xFE00 submitted/issued/completed 提交 描述符 cookie: 42 src/dst/len callback tx_list 数据结构协作流程 1. dma_device 注册到内核,声明能力(通道数、对齐、方向) 2. 驱动通过 dma_request_channel() 从 dma_device 获取 dma_chan 3. 调用 prep 系列函数创建 dma_async_tx_descriptor 4. 描述符通过 submit/issue_pending 进入硬件执行队列 5. 传输完成后通过回调通知上层驱动

4.4 数据结构协作流程

三个核心数据结构在DMA传输生命周期中的协作关系如下:

  1. 注册阶段:DMA控制器驱动填充 dma_device 结构,调用 dma_async_device_register() 注册到内核。
  2. 通道申请:客户端驱动通过 dma_request_channel() 获取一个 dma_chan 实例,该实例指向所属的 dma_device。
  3. 描述符创建:调用 dmaengine_prep_slave_sg() 或 dmaengine_prep_dma_memcpy() 创建 dma_async_tx_descriptor,填充源地址、目的地址、长度等参数。
  4. 提交与执行:dmaengine_submit() 将描述符加入通道的提交队列,dma_async_issue_pending() 触发硬件开始传输。
  5. 完成通知:硬件传输完成后,通过描述符中注册的 callback 函数通知上层驱动。
性能关键点: 描述符的 tx_list 链表支持将多个描述符串联成一次硬件操作,这是实现分散收集(Scatter-Gather)传输的基础。合理利用链表可以减少硬件中断次数,提升批量数据传输效率。

4.5 本章小结