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 进一步降低延迟。

发表评论 取消回复