1. Linux DMA开发基础概念
DMA(Direct Memory Access)是现代计算机系统中一种高效的数据传输机制,它允许外设与内存之间直接进行数据传输,而无需CPU的持续干预。在Linux开发中,理解并掌握DMA技术对于开发高性能驱动程序至关重要。
1.1 DMA工作原理与优势
DMA控制器作为独立的硬件模块,可以接管CPU的数据搬运工作。当外设需要与内存交换数据时,DMA控制器会按照预先配置的参数自动完成数据传输,整个过程只需要CPU在开始和结束时进行干预。
这种机制带来的主要优势包括:
- 显著降低CPU负载,提高系统整体性能
- 实现高速数据传输,特别适合大块数据搬运
- 减少数据传输延迟,提高实时性
在嵌入式Linux系统中,DMA常用于以下场景:
- 高速ADC数据采集
- 视频/音频流处理
- 网络数据包传输
- 存储设备读写
1.2 Linux DMA子系统架构
Linux内核提供了完整的DMA子系统,主要包括以下几个组件:
- DMA引擎框架:提供统一的API接口
- DMA设备驱动:针对特定DMA控制器的实现
- DMA映射机制:处理物理地址与虚拟地址的转换
- 分散/聚集(scatter-gather)支持:处理非连续内存传输
内核中的DMA相关代码主要分布在:
- drivers/dma/:DMA引擎框架和控制器驱动
- include/linux/dmaengine.h:主要头文件
- Documentation/dmaengine/:官方文档
2. DMA开发环境准备
2.1 硬件需求
进行Linux DMA开发需要:
- 支持DMA的处理器/开发板(如STM32、i.MX系列)
- 外设接口(如SPI、I2C、UART等)
- 调试工具(逻辑分析仪、示波器等)
2.2 软件工具链
开发环境配置:
bash复制# 安装交叉编译工具链
sudo apt-get install gcc-arm-linux-gnueabihf
# 获取内核源码
git clone https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
# 配置内核选项
make menuconfig
确保以下内核选项已启用:
- CONFIG_DMA_ENGINE=y
- CONFIG_DMA_VIRTUAL_CHANNELS=y
- 对应平台的DMA控制器驱动
3. DMA API详解
3.1 基本API函数
Linux内核提供了丰富的DMA API,主要函数包括:
- 分配DMA缓冲区:
c复制dma_alloc_coherent(dev, size, handle, gfp)
- 释放DMA缓冲区:
c复制dma_free_coherent(dev, size, cpu_addr, handle)
- 建立DMA映射:
c复制dma_map_single(dev, ptr, size, direction)
- 取消DMA映射:
c复制dma_unmap_single(dev, handle, size, direction)
3.2 DMA传输方向
DMA传输方向由以下枚举定义:
c复制enum dma_data_direction {
DMA_BIDIRECTIONAL = 0,
DMA_TO_DEVICE,
DMA_FROM_DEVICE,
DMA_NONE,
};
3.3 分散/聚集(scatter-gather)操作
对于非连续内存传输,使用sg相关API:
c复制dma_map_sg(dev, sg, nents, direction)
dma_unmap_sg(dev, sg, nents, direction)
4. DMA驱动开发实战
4.1 简单DMA传输示例
以下是一个基本的DMA传输代码框架:
c复制#include <linux/dmaengine.h>
#include <linux/dma-mapping.h>
struct dma_chan *chan;
dma_cookie_t cookie;
struct dma_device *dma_dev;
struct dma_async_tx_descriptor *txdesc;
/* 1. 获取DMA通道 */
chan = dma_request_channel(mask, filter_fn, filter_data);
/* 2. 准备DMA缓冲区 */
src_buf = dma_alloc_coherent(dev, BUF_SIZE, &src_dma, GFP_KERNEL);
dst_buf = dma_alloc_coherent(dev, BUF_SIZE, &dst_dma, GFP_KERNEL);
/* 3. 配置DMA传输 */
txdesc = chan->device->device_prep_dma_memcpy(chan, dst_dma, src_dma,
BUF_SIZE, flags);
/* 4. 提交传输 */
cookie = dmaengine_submit(txdesc);
/* 5. 启动传输 */
dma_async_issue_pending(chan);
/* 6. 等待传输完成 */
dma_async_is_tx_complete(chan, cookie, NULL, NULL);
4.2 中断处理
DMA传输完成通常通过中断通知:
c复制static irqreturn_t dma_callback(int irq, void *data)
{
/* 处理传输完成 */
return IRQ_HANDLED;
}
/* 注册中断处理函数 */
request_irq(irq, dma_callback, IRQF_SHARED, "dma_irq", dev);
5. 常见问题与调试技巧
5.1 DMA传输失败排查
- 检查DMA通道是否成功申请
- 验证缓冲区物理地址是否有效
- 确认传输方向设置正确
- 检查DMA控制器时钟是否使能
5.2 性能优化建议
- 使用分散/聚集传输减少内存拷贝
- 合理设置DMA burst size
- 对齐DMA缓冲区地址(通常需要32/64字节对齐)
- 考虑使用双缓冲技术
5.3 调试工具
- DMA传输状态监控:
bash复制cat /proc/dma
- 使用ftrace跟踪DMA事件:
bash复制echo 1 > /sys/kernel/debug/tracing/events/dma/enable
cat /sys/kernel/debug/tracing/trace_pipe
- 内核日志分析:
bash复制dmesg | grep dma
6. 高级DMA应用
6.1 循环DMA模式
对于连续数据流,可以配置DMA循环模式:
c复制struct dma_slave_config config = {
.direction = DMA_MEM_TO_DEV,
.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES,
.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES,
.src_maxburst = 8,
.dst_maxburst = 8,
.device_fc = false,
};
dmaengine_slave_config(chan, &config);
6.2 DMA与用户空间交互
通过mmap实现用户空间访问DMA缓冲区:
c复制int dma_mmap(struct file *filp, struct vm_area_struct *vma)
{
return dma_mmap_coherent(dev, vma, buf, dma_handle, size);
}
6.3 多通道DMA同步
对于需要同步的多个DMA通道:
c复制struct dma_async_tx_descriptor *tx1, *tx2;
struct dma_async_tx_descriptor *tx_array[] = {tx1, tx2};
dma_async_issue_pending(chan1);
dma_async_issue_pending(chan2);
/* 等待所有传输完成 */
dma_sync_wait(chan, cookie);
7. 实际案例分析:SPI DMA驱动
7.1 SPI DMA初始化
c复制static int spi_dma_init(struct spi_controller *ctlr)
{
ctlr->dma_tx = dma_request_chan(dev, "tx");
ctlr->dma_rx = dma_request_chan(dev, "rx");
if (!ctlr->dma_tx || !ctlr->dma_rx) {
dev_warn(dev, "DMA not available\n");
return -ENODEV;
}
return 0;
}
7.2 SPI DMA传输配置
c复制static int spi_dma_transfer(struct spi_device *spi, struct spi_transfer *xfer)
{
struct dma_async_tx_descriptor *tx_desc, *rx_desc;
/* 配置TX传输 */
tx_desc = dmaengine_prep_slave_single(ctlr->dma_tx,
xfer->tx_dma,
xfer->len,
DMA_MEM_TO_DEV,
DMA_PREP_INTERRUPT);
/* 配置RX传输 */
rx_desc = dmaengine_prep_slave_single(ctlr->dma_rx,
xfer->rx_dma,
xfer->len,
DMA_DEV_TO_MEM,
DMA_PREP_INTERRUPT);
/* 提交传输 */
dmaengine_submit(tx_desc);
dmaengine_submit(rx_desc);
/* 启动传输 */
dma_async_issue_pending(ctlr->dma_tx);
dma_async_issue_pending(ctlr->dma_rx);
return 0;
}
8. DMA安全注意事项
-
内存一致性:确保CPU和DMA看到的内存内容一致
- 使用dma_sync_single_for_device/cpu()同步缓存
-
地址转换:正确处理物理地址与虚拟地址
- 使用dma_map/unmap系列函数
-
并发控制:多线程访问DMA资源时加锁
- 使用spin_lock或mutex保护共享资源
-
错误处理:检查所有DMA API返回值
- 特别是dma_request_channel()可能失败
-
资源释放:确保释放所有申请的DMA资源
- 包括通道、缓冲区、中断等
重要提示:DMA操作直接访问物理内存,错误的DMA配置可能导致系统崩溃或数据损坏,开发时应特别注意安全性检查。
