Linux Kernel DMA Engine 框架深度实战:从 dmaengine API 到 Async TX 与零拷贝内存传输

引言

直接内存访问(DMA, Direct Memory Access)是现代计算机系统中实现高吞吐量外设数据传输的核心机制。在 Linux 内核中,DMA Engine 框架为各种 DMA 控制器提供了统一的驱动模型和面向客户端的 API。无论是 SoC 中的全通用 DMA 引擎(如 PL330、STM32-DMA、BCM2835 DMA),还是 PCIe 端的 peer-to-peer DMA,都需要通过 dmaengine 框架向客户端驱动暴露能力。本文将深入剖析 dmaengine 内核子系统,涵盖 dma_slave_config、Async TX API、DMA_MEMCPY 内存到内存复制、Device Tree 集成以及生产环境调优实战。

1. DMA Engine 整体架构

┌─────────────────────────────────────────────────────┐
│                    客户端驱动                         │
│  (SPI/I2S/NET/MMC/SCSI/Crypto 等)                   │
├─────────────────────────────────────────────────────┤
│                   dmaengine 核心层                    │
│  dmaengine.c / virt-dma.c / slave.c                 │
├─────────────────────────────────────────────────────┤
│                  DMA 控制器驱动                        │
│  pl330.c / stm32-dma.c / edma.c / imx-sdma.c        │
├─────────────────────────────────────────────────────┤
│                   硬件寄存器                           │
│               DMA Controller IP                      │
└─────────────────────────────────────────────────────�code>

DMA Engine 框架的核心设计思想是将 DMA 控制器的物理通道管理与传输请求描述分离:

  • dma_device:代表一个 DMA 控制器,管理通道池(channel list)和Capabilities(支持的最大burst长度、地址宽度、传输方向等)
  • dma_chan:代表一个可编程的 DMA 通道,绑定一个传输方向(mem-to-dev、dev-to-mem、mem-to-mem),支持独占或共享模式
  • dma_async_tx_descriptor:一份异步传输描述符,包含传输参数、回调函数和依赖关系链

2. 核心数据结构剖析

2.1 dma_slave_config

struct dma_slave_config {
    enum dma_transfer_direction direction;
    dma_addr_t src_addr;          // 外设 FIFO 物理地址(src 对于 TX 方向)
    dma_addr_t dst_addr;          // 内存物理地址(dst 对于 TX 方向)
    enum dma_slave_buswidth src_addr_width;  // DMA_SLAVE_BUSWIDTH_1/2/4/8_BYTES
    enum dma_slave_buswidth dst_addr_width;
    u32 src_maxburst;             // 源端最大 burst(如 16 beats = 128 bytes @ 8-byte beat)
    u32 dst_maxburst;
    u32 slave_id;                 // 某些 SoC 用于标识外设流控线(如 TI EDMA)
    bool device_fc;               // 设备流控(Device Flow Control):外设触发 burst 边界
    struct dma_slave_config *peripheral_config;  // 外设特定配置(DT 解析填充)
    unsigned int peripheral_size;
};

2.2 dma_async_tx_descriptor

struct dma_async_tx_descriptor {
    dma_cookie_t cookie;          // 提交后的全局唯一序列号,用于同步等待
    enum dma_ctrl_flags flags;    // DMA_CTRL_ACK | DMA_CTRL_REUSE | DMA_PREP_INTERRUPT | DMA_PREP_CONTINUE | DMA_PREP_FENCE
    dma_addr_t phys;              // 硬件描述符的物理地址(用于 bus-mastering descriptor)
    struct dma_chan *chan;        // 绑定的通道
    dmaengine_tx_callback callback;        // 传输完成回调
    void *callback_param;
    struct dma_async_tx_descriptor *next;  // 链式依赖链表(cyclic DMA 用)
    struct virt_dma_desc *vd;     // 内部虚拟描述符
};

2.3 virt_dma_desc(虚拟 DMA 描述符)

virt-dma 子系统将不同硬件 DMA 控制器的门描述符(Doorbell Descriptor、Ring Descriptor 等)抽象为统一的虚拟描述符循环链表:

struct virt_dma_desc {
    struct dma_async_tx_descriptor tx;
    struct list_head node;
    struct list_head virt_dma_desc_callbacks;
};

struct virt_dma_chan {
    struct virt_dma_desc *cyclic;         // cyclic 模式的回转指针
    struct list_head desc_allocated;      // 已分配但未提交的描述符
    struct list_head desc_submitted;      // 正在硬件中执行的描述符
    struct list_head desc_issued;         // 已发往硬件的描述符
    struct virt_dma_desc *vd_terminated;  // 已终止的描述符(错误/停止)
};

3. DMA Slave API:外设 DMA 传输

对于 SPI、I2S、MMC 等需要与外设 FIFO 进行批量数据传输的场景,使用 DMA Slave API。

3.1 申请通道与配置

struct dma_chan *dma_request_chan(struct device *dev, const char *name);

/* Device Tree 获取通道(推荐方式) */
struct dma_chan *dma_request_chan_by_dt(struct device *dev) {
    struct dma_chan *chan;
    chan = dma_request_chan(dev, "rx");  /* 通过 name 查找 dmas = <&dma0 1> */
    if (IS_ERR(chan)) {
        chan = dma_request_chan(dev, "tx");
    }
    return chan;
}

/* 配置 Slave 参数 */
int dmaengine_slave_config(struct dma_chan *chan, struct dma_slave_config *config);

3.2 准备传输描述符

/* 单次传输(Single Transfer) */
struct dma_async_tx_descriptor *dmaengine_prep_slave_sg(
    struct dma_chan *chan,
    struct scatterlist *sgl,           // 散集链表(分散/聚集传输)
    unsigned int sg_len,               // 散集入口数量
    enum dma_transfer_direction dir,
    unsigned long flags
);

/* 单区域传输(DMA_MEMCPY 内部使用) */
struct dma_async_tx_descriptor *dmaengine_prep_dma_memcpy(
    struct dma_chan *chan,
    dma_addr_t dst, dma_addr_t src,
    size_t len,
    unsigned long flags
);

/* 循环传输(Cyclic):用于音频等无终止条件的流式外设 */
struct dma_async_tx_descriptor *dmaengine_prep_dma_cyclic(
    struct dma_chan *chan,
    dma_addr_t buf_addr, size_t buf_len,
    size_t period_len,
    enum dma_transfer_direction dir,
    unsigned long flags
);

/* 交错传输(Interleaved):用于 2D 图像/视频操作(行列 tiling) */
struct dma_async_tx_descriptor *dmaengine_prep_interleaved_dma(
    struct dma_chan *chan,
    struct dma_interleaved_template *xt,
    unsigned long flags
);

3.3 提交通知与同步

/* 提交描述符到硬件队列 */
dma_cookie_t dmaengine_submit(struct dma_async_tx_descriptor *desc);

/* 触发等待(同步模式) */
void dma_async_issue_pending(struct dma_chan *chan);

/* 等待特定 cookie 完成 */
enum dma_status dma_sync_wait(struct dma_chan *chan, dma_cookie_t cookie);

/* 查询传输完成状态 */
enum dma_status dma_tx_state(struct dma_chan *chan, dma_cookie_t cookie,
                             struct dma_tx_state *state);
// state->residue: 剩余未传输的字节数(用于音频场景进度跟踪)

4. Async TX API:分散/聚集链式传输

Async TX API 是 dmaengine 最强大的功能,支持将多个分散的内存区域串联为一次 DMA 操作,每个段传输完成后可选择触发中断。

4.1 依赖链(DMA Chain)构建

/* 创建 async_tx 描述符 */
struct dma_async_tx_descriptor *async_tx_ack;
struct page *src_page = alloc_page(GFP_DMA);
struct page *dst_page = alloc_page(GFP_DMA);

/* 1. 准备 memcpy 描述符 */
struct dma_async_tx_descriptor *tx = dmaengine_prep_dma_memcpy(
    chan, page_to_phys(dst_page), page_to_phys(src_page), PAGE_SIZE,
    DMA_PREP_INTERRUPT | DMA_CTRL_ACK
);

/* 2. 设置完成回调 */
tx->callback = my_callback;
tx->callback_param = my_data;

/* 3. 提交 */
dma_cookie_t cookie = dmaengine_submit(tx);
dma_async_issue_pending(chan);

4.2 自定义操作描述符(XOR / PQ)

部分高端 DMA 控制器(如Intel I/OAT、AMD/amdpdsa)支持硬件级 XOR/P+Q RAID 计算,通过 Async TX API 暴露:

struct dma_async_tx_descriptor *async_xor(
    struct page *dest, struct page **src_list,
    unsigned int src_cnt, unsigned int offset,
    unsigned int len, enum async_tx_flags flags,
    struct dma_async_tx_descriptor *depend_tx,
    dma_callback_t callback, void *callback_param
);

5. DMA_MEMCPY:零拷贝内存引擎

对于纯内存到内存的复制(如帧缓冲区拷贝、大内存页合并),DMA Engine 提供了 dmaengine_prep_dma_memcpy 接口,相比 CPU 指令复制具有以下优势:

  • 不占用 CPU 周期(CPU 可以执行其他任务)
  • 对于大内存区域(>4KB),可利用 DMA burst 效率优势
  • 具备硬件级 ECC 校验(部分 SoC DMA)
  • 支持异步提交 + 回调通知模式

5.1 性能基准分析

┌──────────────┬──────────────┬──────────────┬──────────────┐
│ 复制大小     │ CPU memcpy   │ DMA memcpy   │ DMA 优势      │
├──────────────┼──────────────┼──────────────┼──────────────┤
│ 64 B         │ ~5 ns       │ ~500 ns     │ 不适用(启动 │
│              │              │              │ 开销过大)   │
├──────────────┼──────────────┼──────────────┼──────────────┤
│ 1 KB         │ ~80 ns      │ ~800 ns     │ 劣势         │
├──────────────┼──────────────┼──────────────┼──────────────┤
│ 4 KB         │ ~300 ns     │ ~1.2 μs     │ 接近持平     │
├──────────────┼──────────────┼──────────────┼──────────────┤
│ 64 KB        │ ~3 μs       │ ~4 μs       │ 接近持平     │
├──────────────┼──────────────┼──────────────┼──────────────┤
│ 1 MB         │ ~100 μs     │ ~40 μs      │ DMA 胜出 2.5x│
├──────────────┼──────────────┼──────────────┼──────────────┤
│ 16 MB        │ ~2 ms       │ ~0.6 ms     │ DMA 胜出 3.3x│
└──────────────┴──────────────┴──────────────┴──────────────┘
* 注:以上数据基于 ARM Cortex-A53 + PL330 DMA 实测,具体数值因平台而异

5.2 生产场景应用

/* 网络设备 ring buffer 零拷贝填充 */
static int nic_fill_rx_ring(struct nic_device *nic)
{
    struct dma_chan *chan = nic->memcpy_chan;
    struct dma_async_tx_descriptor *tx;
    int i;

    for (i = 0; i < RX_RING_SIZE; i++) {
        tx = dmaengine_prep_dma_memcpy(chan,
            nic->rx_ring[i].phys_dst,       // 目的:DMA-coherent 缓冲区
            nic->prealloc_buf[i].phys_src,  // 来源:预分配的 contiguous 块
            nic->rx_buf_size,
            DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
        if (!tx) return -ENOMEM;

        tx->callback = nic_rx_dma_complete;
        tx->callback_param = &nic->rx_ring[i];

        dmaengine_submit(tx);
    }
    dma_async_issue_pending(chan);
    return 0;
}

6. Cyclic DMA:无终止流式传输

Cyclic DMA 模式用于音频编解码器、ADC 采集、显示扫描输出等无限循环的流式场景。描述符会

  • 传输一个 period后触发中断回调
  • 自动回到缓冲区起点继续下一轮传输
  • 回调函数执行期间新 period 已开始传输(双缓冲无缝切换)

6.1 ALSA SoC 集成模式

Cyclic DMA 波形示意:
IRQ → |period0| ← CPU 填充 period1
IRQ → |period1| ← CPU 填充 period0(乒乓切换)
IRQ → |period0| ← CPU 填充 period1
...

/* ALSA PCM 驱动中配置 cyclic DMA */
static const struct snd_pcm_hardware my_dma_hw = {
    .formats = SNDRV_FMTBIT_LINEAR,
    .rate_min = 8000, .rate_max = 192000,
    .channels_min = 1, .channels_max = 8,
    .buffer_bytes_max = 256 * 1024,      // 256KB 环形缓冲区
    .period_bytes_min = 4 * 1024,        // 最小 period = 4KB(一个chunk)
    .period_bytes_max = 64 * 1024,
    .periods_min = 2,
    .periods_max = 256,
};

/* DMA 通道配置 */
static int my_dma_open(struct snd_soc_component *component,
                       struct snd_pcm_substream *substream)
{
    struct dma_chan *chan = ...;
    struct dma_slave_config cfg = {
        .direction = DMA_MEM_TO_DEV,
        .dst_addr = fifo_phys_addr,
        .dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES,
        .dst_maxburst = 16,              // 16-beat AXI burst = 64 bytes
        .device_fc = true,               // I2S 外设流控
    };
    dmaengine_slave_config(chan, &cfg);

    tx = dmaengine_prep_dma_cyclic(chan,
        substream->dma_buf.addr,         // 环形缓冲区物理地址
        buffer_bytes,                     // 总缓冲区大小
        period_bytes,                     // 一个 period 大小
        DMA_MEM_TO_DEV,                   // TX 方向
        DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
}

7. Device Tree 集成

现代 SoC 中 DMA 通道的分配通过 Device Tree 静态配置,实现驱动与硬件描述分离。

7.1 DMA 控制器节点定义

/* arch/arm64/boot/dts/myvendor/my-soc.dtsi */
dma0: dma-controller@ff200000 {
    compatible = "myvendor,my-dma";
    reg = <0x0 0xff200000 0x0 0x10000>;
    interrupts = <GIC_SPI 30 IRQ_TYPE_LEVEL_HIGH>;
    #dma-cells = <1>;                      // 单 cell = 通道号
    clocks = <&clk_dma>;
    clock-names = "dma";
    dma-channels = <16>;                  // 总通道数
    dma-requests = <64>;                  // 可配置的外设请求线数量
};

7.2 客户端 DMA 通道引用

/* SPI 控制器使用 DMA 通道 3 和 4 */
spi0: spi@ff800000 {
    compatible = "myvendor,spi";
    dmas = <&dma0 3>,                 // RX - 通道 3
          <&dma0 4>;                 // TX - 通道 4
    dma-names = "rx", "tx";
    // ...
};

/* SPI Flash 设备使用这些 DMA 通道进行高速数据传输 */
flash@0 {
    compatible = "jedec,spi-nor";
    // dmas 和 dma-names 在父节点定义
};

7.3 高级流控配置(DMA router)

/* 早期 SoC(如 TI AM335x)需要使用 dma-router 映射外设到 DMA 请求线 */
dma-router {
    compatible = "ti,dma-router";
    #dma-cells = <1>;
    dma-requests = <128>;

    /* SPI0 RX 映射到通道 17 的请求线 0 */
    spi0_rx = <&edma 17 0>;
};

8. DMA Engine 驱动开发实战

8.1 注册 DMA 控制器驱动

static int my_dma_probe(struct platform_device *pdev)
{
    struct my_dma_dev *mdev;
    struct dma_device *ddev;
    int ret;

    mdev = devm_kzalloc(&pdev->dev, sizeof(*mdev), GFP_KERNEL);
    ddev = &mdev->ddev;

    /* 1. 定义 Capabilities */
    dma_cap_set(DMA_MEMCPY, ddev->cap_mask);        // 支持内存拷贝
    dma_cap_set(DMA_SLAVE, ddev->cap_mask);          // 支持外设 Slave
    dma_cap_set(DMA_CYCLIC, ddev->cap_mask);         // 支持 Cyclic 模式
    dma_cap_set(DMA_INTERLEAVE, ddev->cap_mask);     // 支持 2D 交错

    /* 2. 初始化 dma_device 回调 */
    ddev->dev = &pdev->dev;
    ddev->device_alloc_chan_resources  = my_dma_alloc_chan;
    ddev->device_free_chan_resources   = my_dma_free_chan;
    ddev->device_prep_dma_memcpy       = my_dma_prep_memcpy;
    ddev->device_prep_slave_sg         = my_dma_prep_slave_sg;
    ddev->device_prep_dma_cyclic       = my_dma_prep_cyclic;
    ddev->device_prep_interleaved_dma  = my_dma_prep_interleaved;
    ddev->device_config               = my_dma_config;
    ddev->device_pause                 = my_dma_pause;
    ddev->device_resume                = my_dma_resume;
    ddev->device_terminate_all        = my_dma_terminate;
    ddev->device_tx_status            = my_dma_tx_status;
    ddev->device_issue_pending        = my_dma_issue_pending;

    /* 3. 硬件通道数与地址宽度 */
    ddev->channels = MY_DMA_MAX_CHANNELS;
    ddev->src_addr_widths = BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) |
                           BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) |
                           BIT(DMA_SLAVE_BUSWIDTH_4_BYTES) |
                           BIT(DMA_SLAVE_BUSWIDTH_8_BYTES);
    ddev->dst_addr_widths = ddev->src_addr_widths;
    ddev->directions = BIT(DMA_MEM_TO_MEM) | BIT(DMA_MEM_TO_DEV) | BIT(DMA_DEV_TO_MEM);
    ddev->residue_granularity = DMA_RESIDUE_GRANULARITY_BURST;
    ddev->copy_align = ilog2(dma_get_min_align_mask(&pdev->dev));

    /* 4. 注册 DMA 设备 */
    ret = dmaenginem_async_device_register(ddev);
    if (ret) return ret;

    platform_set_drvdata(pdev, mdev);
    return 0;
}

8.2 实现 memcpy prep 回调

static struct dma_async_tx_descriptor *
my_dma_prep_memcpy(struct dma_chan *chan, dma_addr_t dst, dma_addr_t src,
                   size_t len, unsigned long flags)
{
    struct my_dma_chan *mchan = to_my_dma_chan(chan);
    struct my_dma_desc *desc;
    size_t xfer_len;
    u32 burst, ctrla, ctrlb;

    /* 边界对齐检查 */
    if (!IS_ALIGNED(dst, mchan->dst_addr_width) ||
        !IS_ALIGNED(src, mchan->src_addr_width))
        return NULL;

    desc = kzalloc(sizeof(*desc), GFP_NOWAIT);
    if (!desc) return NULL;

    /* 计算最优 burst 长度,最大化 AXI 总线效率 */
    burst = my_dma_optimal_burst(len);

    desc->len = len;
    desc->src = src;
    desc->dst = dst;
    desc->dir = DMA_MEM_TO_MEM;

    /* 配置硬件描述符寄存器:源/目的地址、传输长度、burst 模式 */
    ctrla = ...;
    ctrlb = LLI_DST_ENABLE | LLI_SRC_ENABLE | LLI_LAST_SGL;

    /* 拆分多 segment(当 len 超过单次传输上限时自动插入 LLI 链表) */
    my_dma_create_lli_chain(mchan, desc, src, dst, len, burst, ctrla, ctrlb);

    desc->tx.tx_submit = my_dma_tx_submit;
    desc->tx.phys = dma_map_single(chan->device->dev, desc->lli_hw,
                                     sizeof(*desc->lli_hw), DMA_TO_DEVICE);

    return &desc->tx;
}

9. 性能调优与生产实践

9.1 Burst 长度优化

AXI4 总线 burst 模式下每次传输可连续推送多 beat 数据,最大化 burst 长度直接决定 DMA 吞吐:

/* 计算最优 burst 长度 */
static inline u32 my_dma_optimal_burst(size_t len)
{
    /* PL330 单通道单次最大传输 255 beats × 数据宽度 bytes */
    #define PL330_MAX_XFER (32 * KB)

    if (len <= 256)
        return 2;         // 8 bytes -> 16 bytes(单次小块)
    if (len <= 4 * KB)
        return ilog2(len / 4);
    return 16;            // 16-beat burst(64 bytes @ 8-byte beat)
}

9.2 Cache Consistency 管理

DMA 传输前后必须正确管理 CPU cache,否则会导致数据不一致:

/* 方式一:一致性 DMA 缓冲区(Coherent,推荐用于长期 DMA 映射) */
dma_addr_t dma_handle;
void *cpu_addr = dma_alloc_coherent(dev, size, &dma_handle, GFP_KERNEL);
// cpu_addr 和 dma_handle 均有效,硬件自动 snoop cache

/* 方式二:流式 DMA 映射(Stream,适用于单次传输) */
dma_addr_t dma_handle = dma_map_single(dev, cpu_addr, size, DMA_TO_DEVICE);
dma_sync_single_for_device(dev, dma_handle, size, DMA_TO_DEVICE);  // 设备读前同步
dma_sync_single_for_cpu(dev, dma_handle, size, DMA_FROM_DEVICE);   // CPU 读前同步
dma_unmap_single(dev, dma_handle, size, DMA_FROM_DEVICE);

/* 方式三:散集映射(Scatter-Gather) */
struct scatterlist sg;
sg_init_one(&sg, cpu_addr, size);
int nents = dma_map_sg(dev, &sg, 1, DMA_TO_DEVICE);
// sg.dma_address 和 sg.dma_length 已填充
// 使用完毕后调用 dma_unmap_sg

9.3 中断与 NAPI 优化

在大流量 DMA 场景中(如网络、存储),建议将 DMA 完成中断与 NAPI 或 tasklet 结合,避免硬中断过载:

/* 在 DMA 回调中调度 NAPI 处理 */
static void my_rx_dma_callback(void *param)
{
    struct my_ring *ring = param;
    napi_schedule(&ring->napi);
}

/* NAPI poll 函数中批量处理 */
static int my_napi_poll(struct napi_struct *napi, int budget)
{
    struct my_ring *ring = container_of(napi, struct my_ring, napi);
    int work_done = 0;

    while (work_done < budget) {
        /* 处理完成的 DMA 描述符 */
        /* ... */
        work_done++;
    }

    if (work_done < budget) {
        napi_complete_done(napi, work_done);  // 退出 polling 模式
        // 重新开启中断
        dma_slave_irq_enable(ring->chan);
    }
    return work_done;
}

9.4 错误处理与恢复

/* DMA 错误中断处理 */
static irqreturn_t my_dma_irq_handler(int irq, void *dev_id)
{
    struct my_dma_dev *mdev = dev_id;
    u32 status;

    status = readl(mdev->regs + DMA_INTR_STATUS);

    if (status & DMA_ERR_MASK) {
        struct dma_chan *chan = ...;
        /* 立即终止所有通道传输 */
        dmaengine_terminate_sync(chan);
        /* 清除错误标志 */
        writel(status & DMA_ERR_MASK, mdev->regs + DMA_INTR_CLEAR);
        /* 上报错误日志 */
        dev_err(mdev->dev, "DMA XFER ERROR: %#x\n", status);
        /* 恢复:重新初始化通道队列 */
        my_dma_chan_reset(chan);
    }

    if (status & DMA_TC_MASK) {
        /* 正常完成处理 */
        tasklet_schedule(&mdev->tc_tasklet);
    }

    return IRQ_HANDLED;
}

10. DMA Engine 调试与可观测性

10.1 DebugFS 接口

通过 debugfs 可以实时查看 DMA 控制器和通道的使用状态:

# mount -t debugfs none /sys/kernel/debug
# ls /sys/kernel/debug/dmaengine/
summary          # 全局 DMA 引擎摘要

# cat /sys/kernel/debug/dmaengine/summary
dma0 (MyVendor DMA Engine): 16 channels, 63 requests
  CH00: slave:SPI0-RX, active, xfer_count=1234
  CH01: slave:SPI0-TX, idle
  CH02: memcpy, active, xfer_count=5678

10.2 ftrace 跟踪

# 跟踪 DMA 引擎关键事件
# echo 1 > /sys/kernel/debug/tracing/events/dma/enable

# 查看触发记录
# cat /sys/kernel/debug/tracing/trace_pipe
          dmaengine_submit    chan=dma0.0 cookie=42
          dma_chan_terminate   chan=dma0.0 cookie=42 status=completed
          dma_alloc_coherent   size=65536 dma_handle=0x80000000

10.3 DMA 传输延迟分析

/* 基于 tracepoint 的 DMA 延迟测量 */
#include <trace/events/dma.h>

/* 在 prep 描述符时记录时间戳 */
struct timespec64 ts_start;
ktime_get_real_ts64(&ts_start);
desc->submit_ts = timespec64_to_ns(&ts_start);

/* 回调时计算耗时 */
static void measure_latency(struct my_desc *desc)
{
    u64 ns_delta = ktime_get_ns() - desc->submit_ts;
    trace_dma_latency(desc->len, ns_delta);
}

11. 实战案例:零拷贝帧缓冲区传输

场景描述

一个嵌入式 GPU(Mali/Adreno)在完成渲染后,需要通过 DMA 将帧缓冲区(frame buffer)从 GPU 输出缓冲区传输到显示控制器(Display Engine)的扫描输出缓冲区,要求 CPU 仅负责调度,不参与任何数据搬运。

解决方案架构

┌──────────┐    DMA Ch0     ┌──────────────┐    DMA Ch1     ┌────────────┐
│ GPU Frame │ ────────────→ │ Display Eng  │ ────────────→ │  Panel/LCD │
│ Buffer    │   MEMCPY      │ Scan-out BUF │   SLAVE      │            │
└──────────┘               └──────────────┘               └────────────┘
                                  │
                             DMA_LINK(LLI Chain)

实现代码

static int display_show_frame(struct display_dev *disp, struct frame_buf *fb)
{
    struct dma_chan *disp_chan = disp->chan;
    struct dma_async_tx_descriptor *tx;
    struct dma_slave_config *cfg = &disp->slave_cfg;
    dma_addr_t dst_phys = disp->scanout_buf.phys_addr;
    dma_addr_t src_phys = fb->gpu_buf.phys_addr;

    /* 配置 display 外设的 FIFO 地址和 burst */
    cfg->direction = DMA_MEM_TO_DEV;
    cfg->dst_addr = DISP_FIFO_PHYS;
    cfg->dst_addr_width = DMA_SLAVE_BUSWIDTH_8_BYTES;  // 8-byte beat
    cfg->dst_maxburst = 16;
    cfg->device_fc = true;

    dmaengine_slave_config(disp_chan, cfg);

    /* 准备 cyclic 扫描输出(vsync 中断回调中切换行) */
    tx = dmaengine_prep_dma_cyclic(disp_chan,
        dst_phys,                  // 帧缓冲区物理地址
        disp->total_line_bytes,    // 总行字节数(double buffer)
        disp->line_bytes,          // 每行字节数
        DMA_MEM_TO_DEV,
        DMA_PREP_INTERRUPT | DMA_PREP_REPEAT | DMA_CTRL_ACK);

    tx->callback = display_vsync_callback;
    tx->callback_param = disp;

    dmaengine_submit(tx);
    dma_async_issue_pending(disp_chan);

    return 0;
}

12. 常见坑位与解决方案

问题现象根因分析解决方案
传输数据部分为零 / 残留旧数据流式 DMA 映射后未调用 dma_sync_single_for_device 写入硬件前 CPU cache 未刷出必须在 dmaengine_submit 前调用 dma_sync_single_for_device
DMA 启动超时外设未正确配置 DMACK/DRQ 请求线,外设 FIFO 未使能 DMA 请求检查外设 DMA 使能位 + dma_request_chan 用 DT 引用
Cyclic 模式首周期数据异常Cyclic 启动时硬件尚未读取第一个 period,第一帧跳过(常见于 I2S)在 prep cyclic 后手动预填一个 period 零数据或使用双缓冲初始化
DMA memcpy 大内存(>1MB)时系统卡顿DMA 占用内存总线长时间阻塞 CPU 访问(尤其在 interleaved 总线架构中)改用 DMA_MEMCPY 异步模式 + 中断通知,或使用 multi-page scatterlist 分段传输
residue 计数始终为0驱动未实现 device_tx_status 或 residue_granularity 设置为 DMA_RESIDUE_GRANULARITY_DESCRIPTOR正确实现 residue 查询逻辑(如读取硬件剩余计数器),设置 granularity 为 BURST
DMA 通道 find 失败Device Tree dmas 中 #dma-cells 数量不匹配,或控制器驱动未匹配 compatible确认 #dma-cells 与客户端 dmas 属性 cells 数量一致(通常1个cell=通道号)

13. 总结

Linux DMA Engine 框架通过统一的数据结构(dma_device/dma_chan/dma_async_tx_descriptor)和 API(dmaengine_prep_slave_sg/dmaengine_prep_dma_memcpy/dmaengine_prep_dma_cyclic),为超过 50 种 SoC 的 DMA 控制器提供了标准化的驱动模型。理解和掌握以下要点是进行高效 DMA 开发的关键:

  • 通道分配策略:通过 Device Tree 静态绑定 + dma_request_chan 动态获取
  • burst 长度调优:根据 AXI 总线宽度和 FIFO 深度选择最优 burst 值
  • cache 一致性:区分 coherent(一致性)与 streaming(流式)映射的使用场景
  • 回调与异步通知:DMA 传输是异步的,回调函数中不可睡眠,需要用时配合工作队列
  • 错误恢复路径:dmaengine_terminate_sync + 通道重新初始化是标准错误恢复流程
  • Cyclic 模式专供流式外设:音频/显示/ADC 采集等无限数据流场景的首选方案

在实际工程中,DMA Engine 往往与 IOMMU、CMA(Contiguous Memory Allocator)等子系统协作,构建完整的零拷贝数据通路。对于追求极致吞吐量的场景(RDMA over Converged Ethernet、NVMe passthrough),还可以结合 VFIO 的用户态 DMA 或 io_uring 的 fixed buffer 进一步降低延迟。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部