1. DMA引擎框架概述
DMA(Direct Memory Access)引擎是Linux内核中用于管理直接内存访问控制器的核心框架。它抽象了不同硬件DMA控制器的共性操作,为上层驱动提供统一的编程接口。在实际工作中,我发现很多嵌入式开发者对DMA引擎的理解停留在表面,本文将结合内核5.x版本的实现,深入解析DMA引擎的工作原理和最佳实践。
DMA引擎框架的核心价值在于:
- 统一不同厂商DMA控制器的编程模型
- 提供异步传输机制提升系统吞吐量
- 支持scatter-gather等高级传输特性
- 简化驱动开发者的工作负担
2. DMA硬件基础解析
2.1 DMA控制器工作原理
典型的DMA控制器包含以下关键组件:
- 通道(Channel):独立的传输通路,现代DMA控制器通常提供4-8个物理通道
- 请求线(Request Line):外设触发DMA传输的物理信号线
- FIFO:数据缓冲,用于匹配不同总线速度
- 配置寄存器组:设置传输参数的控制接口
传输过程分为三个阶段:
- 初始化:CPU配置源/目的地址、传输模式等参数
- 触发:外设通过DRQ信号线发起传输请求
- 传输:DMA控制器接管总线,直接完成数据搬运
2.2 关键传输参数
c复制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;
// ...
};
- 传输宽度(Width):单次操作的数据位宽(8/16/32位)
- 突发长度(Burst):连续传输的单元数
- 传输方向(Direction):内存到设备、设备到内存等
- 地址递增模式:固定地址或自动递增
3. Linux DMA引擎实现剖析
3.1 核心数据结构关系
mermaid复制graph TD
A[dma_device] --> B[dma_chan]
B --> C[dma_async_tx_descriptor]
C --> D[硬件描述符]
A --> E[dma_slave_config]
关键结构体说明:
dma_device:代表整个DMA控制器dma_chan:对应物理传输通道dma_async_tx_descriptor:描述单个传输任务virt_dma_chan:虚拟DMA通道的通用实现
3.2 驱动开发关键步骤
- 初始化dma_device结构体:
c复制static int foo_dma_probe(struct platform_device *pdev)
{
struct dma_device *dma_dev;
dma_dev = devm_kzalloc(&pdev->dev, sizeof(*dma_dev), GFP_KERNEL);
dma_cap_set(DMA_SLAVE, dma_dev->cap_mask);
dma_dev->directions = BIT(DMA_MEM_TO_DEV) | BIT(DMA_DEV_TO_MEM);
dma_dev->src_addr_widths = BIT(DMA_SLAVE_BUSWIDTH_4_BYTES);
dma_dev->dst_addr_widths = BIT(DMA_SLAVE_BUSWIDTH_4_BYTES);
// ...
}
- 实现必要的操作集:
c复制static const struct dma_device_ops foo_dma_ops = {
.device_alloc_chan_resources = foo_alloc_chan,
.device_free_chan_resources = foo_free_chan,
.device_prep_slave_sg = foo_prep_slave_sg,
.device_config = foo_dma_config,
.device_terminate_all = foo_terminate_all,
// ...
};
- 通道中断处理模板:
c复制static irqreturn_t foo_dma_irq(int irq, void *devid)
{
struct foo_chan *chan = devid;
struct foo_desc *desc;
u32 status;
status = readl(chan->regs + FOO_STAT_REG);
if (!(status & FOO_IRQ_FLAG))
return IRQ_NONE;
/* 处理完成描述符 */
desc = chan->running;
if (desc) {
vchan_cookie_complete(&desc->vd);
chan->running = NULL;
}
/* 启动下一个传输 */
if (!list_empty(&chan->pending))
foo_start_next_tx(chan);
return IRQ_HANDLED;
}
4. 高级特性实现技巧
4.1 循环传输优化
音频等场景需要循环缓冲区支持,DMA引擎通过DMA_CYCLIC标志实现:
c复制struct dma_async_tx_descriptor *desc;
desc = dmaengine_prep_dma_cyclic(chan, buf_addr, buf_len, period_len,
DMA_MEM_TO_DEV);
if (!desc) {
dev_err(dev, "Failed to prep cyclic DMA\n");
return -EINVAL;
}
desc->callback = audio_dma_complete;
desc->callback_param = stream;
dmaengine_submit(desc);
dma_async_issue_pending(chan);
关键点:
- 缓冲区必须物理连续(通常用dma_alloc_coherent分配)
- period_len需要对齐硬件限制(通常是burst size的整数倍)
- 完成回调在每个period结束时触发
4.2 分散-聚集传输
处理非连续内存区域时,scatter-gather比手动拼接更高效:
c复制struct scatterlist *sgl;
int nr_sgs;
/* 构建scatterlist */
nr_sgs = dma_map_sg(dev, sgl, nents, dir);
/* 准备传输描述符 */
desc = dmaengine_prep_slave_sg(chan, sgl, nr_sgs, dir, flags);
if (!desc) {
dma_unmap_sg(dev, sgl, nents, dir);
return -ENOMEM;
}
性能优化建议:
- 合并相邻的小块内存区域
- 确保每个sg项长度是burst size的整数倍
- 考虑使用dmaengine_get_slave_caps查询硬件能力
5. 常见问题排查指南
5.1 传输停滞诊断
症状:DMA启动后没有完成中断,数据未传输
排查步骤:
- 检查DRQ信号是否正常触发(逻辑分析仪抓取)
- 验证DMA配置寄存器值是否正确
- 确认外设DMA使能位已设置
- 检查DMA通道仲裁优先级
- 查看DMA控制器时钟是否使能
5.2 数据错位问题
症状:接收数据出现偏移或错位
可能原因:
- 源/目标地址宽度配置不匹配
- 突发长度超过FIFO容量
- 字节序设置错误
- 内存未正确对齐(应满足:addr % width == 0)
调试方法:
shell复制# 查看DMA寄存器状态
devmem2 0x48000000 w
# 检查内存内容
hexdump -C /sys/kernel/debug/dma_buf/foo
6. 性能优化实践
6.1 双缓冲技术
通过交替使用两个缓冲区减少等待时间:
c复制struct dma_async_tx_descriptor *desc[2];
/* 准备两个描述符 */
for (i = 0; i < 2; i++) {
desc[i] = dmaengine_prep_slave_sg(chan, &sgl[i], 1, dir, 0);
desc[i]->callback = dma_complete;
desc[i]->callback_param = &data[i];
dmaengine_submit(desc[i]);
}
/* 启动第一个传输 */
dma_async_issue_pending(chan);
/* 在回调中切换缓冲区 */
static void dma_complete(void *param)
{
struct dma_data *data = param;
/* 处理数据... */
/* 重新提交描述符 */
dmaengine_submit(data->desc);
}
6.2 通道优先级调整
多通道竞争时的优化策略:
- 查询硬件支持的最大通道数:
c复制struct dma_slave_caps caps;
dma_get_slave_caps(chan, &caps);
pr_info("Max channels: %d\n", caps.channel_count);
- 设置通道优先级(如有支持):
c复制/* 某些控制器支持动态优先级 */
dma_set_priority(chan, DMA_PRIORITY_HIGH);
- 使用专用通道处理关键路径
7. 最新内核特性适配
7.1 DMA虚拟通道
5.10+内核引入的改进:
c复制struct virt_dma_chan {
struct dma_chan chan;
struct list_head desc_allocated;
struct list_head desc_submitted;
struct list_head desc_issued;
struct list_head desc_completed;
// ...
};
优势:
- 统一了不同控制器的公共逻辑
- 提供标准的描述符管理机制
- 简化驱动开发工作量
适配要点:
- 继承virt_dma_chan而不是直接实现dma_chan
- 使用vchan_init初始化虚拟通道
- 实现硬件相关的issue_pending操作
7.2 流式DMA API
针对高性能场景的新接口:
c复制int dmaengine_submit_stream(struct dma_async_tx_descriptor *desc);
void dmaengine_terminate_stream(struct dma_chan *chan);
特点:
- 减少软件开销
- 支持描述符链式提交
- 提供更精确的完成状态报告
8. 开发调试技巧
8.1 调试信息收集
内核提供多种调试手段:
shell复制# 查看注册的DMA设备
cat /sys/kernel/debug/dmaengine/devices
# 监控DMA事件
echo 1 > /sys/kernel/debug/dmaengine/foo/chan0/trace_enable
# 性能统计
cat /sys/kernel/debug/dmaengine/foo/chan0/stats
8.2 用户空间测试工具
dmatest是内核自带的测试模块:
shell复制# 加载测试模块
modprobe dmatest
# 运行内存到内存测试
echo 1 > /sys/module/dmatest/parameters/run
# 查看测试结果
dmesg | grep dmatest
自定义测试用例模板:
c复制static int run_dma_test(struct dma_chan *chan)
{
struct dma_async_tx_descriptor *desc;
dma_cookie_t cookie;
enum dma_status status;
/* 准备测试数据 */
src = dma_alloc_coherent(dev, BUF_SIZE, &src_dma, GFP_KERNEL);
dst = dma_alloc_coherent(dev, BUF_SIZE, &dst_dma, GFP_KERNEL);
memset(src, 0xAA, BUF_SIZE);
/* 提交传输 */
desc = dmaengine_prep_dma_memcpy(chan, dst_dma, src_dma, BUF_SIZE, 0);
cookie = dmaengine_submit(desc);
dma_async_issue_pending(chan);
/* 等待完成 */
status = dma_async_is_tx_complete(chan, cookie, NULL, NULL);
if (status == DMA_COMPLETE) {
if (memcmp(src, dst, BUF_SIZE) == 0)
pr_info("Test passed\n");
else
pr_err("Data mismatch\n");
}
dma_free_coherent(dev, BUF_SIZE, src, src_dma);
dma_free_coherent(dev, BUF_SIZE, dst, dst_dma);
return 0;
}
9. 典型应用场景分析
9.1 音频传输实现
音频DMA配置要点:
c复制static int audio_dma_config(struct snd_pcm_substream *substream)
{
struct dma_slave_config config = {0};
config.direction = DMA_MEM_TO_DEV;
config.dst_addr = regs->FIFO_ADDR;
config.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
config.dst_maxburst = 8; /* 匹配FIFO深度 */
config.slave_id = AUDIO_DMA_REQ;
return dmaengine_slave_config(chan, &config);
}
常见问题处理:
- 时钟抖动导致的音频卡顿:调整DMA请求时机
- 缓冲区欠载:增加period数量或减小period大小
- 数据错位:检查字节序和采样率匹配
9.2 图像处理加速
2D DMA配置示例:
c复制struct dma_interleaved_template xt;
xt.src_start = fb_dma_addr;
xt.dst_start = lcd_dma_addr;
xt.dir = DMA_MEM_TO_DEV;
xt.src_inc = true;
xt.dst_inc = false;
xt.src_sgl = false;
xt.dst_sgl = false;
xt.numf = height;
xt.frame_size = 1;
xt.sgl[0].size = width * bpp;
xt.sgl[0].icg = stride - width * bpp;
desc = dmaengine_prep_interleaved_dma(chan, &xt, flags);
性能优化方向:
- 利用硬件缩放和色彩空间转换功能
- 采用行缓冲减少内存带宽占用
- 使用chroma-key等硬件加速特性
10. 跨平台开发注意事项
10.1 兼容性处理
不同平台的DMA控制器差异:
- 寄存器布局和位定义
- 中断触发方式(电平/边沿)
- 通道仲裁机制
- 最大传输长度限制
- 对齐要求(地址/长度)
推荐做法:
c复制static const struct foo_dma_platform_data {
u32 max_burst;
bool has_lli;
u32 data_widths;
} pdata[] = {
[PLAT_A] = { .max_burst = 16, .has_lli = true },
[PLAT_B] = { .max_burst = 8, .has_lli = false },
};
10.2 设备树配置
典型DMA节点定义:
dts复制dma: dma-controller@48000000 {
compatible = "foo,dma-controller";
reg = <0x48000000 0x1000>;
interrupts = <10 IRQ_TYPE_LEVEL_HIGH>;
#dma-cells = <2>;
dma-channels = <8>;
dma-requests = <32>;
clocks = <&dma_clk>;
};
客户端设备引用:
dts复制audio: audio-codec@0 {
compatible = "vendor,audio-codec";
dmas = <&dma 5 DMA_MEM_TO_DEV>,
<&dma 6 DMA_DEV_TO_MEM>;
dma-names = "tx", "rx";
};
调试技巧:
shell复制# 查看解析后的DMA映射
cat /proc/device-tree/dma-controller@48000000/dma-channels
