一、DMA 使用基础流程
- 分配 DMA 缓冲区
一致性 DMA 映射(Coherent DMA)
#include <linux/dma-mapping.h>
// 分配长期存在的缓存一致内存
void *vaddr;
dma_addr_t dma_handle;
vaddr = dma_alloc_coherent(dev, size, &dma_handle, GFP_KERNEL);
if (!vaddr) {
dev_err(dev, "DMA 分配失败");
return -ENOMEM;
}
// 使用示例:
memcpy(vaddr, src_data, data_size); // CPU 写操作
device_start_dma(dma_handle); // 设备读取DMA地址
流式 DMA 映射(Streaming DMA)
void *buf = kmalloc(size, GFP_KERNEL);
dma_addr_t dma_handle;
// 建立映射(CPU→设备)
dma_handle = dma_map_single(dev, buf, size, DMA_TO_DEVICE);
if (dma_mapping_error(dev, dma_handle)) {
kfree(buf);
return -EFAULT;
}
// 启动传输后需要同步缓存
dma_sync_single_for_device(dev, dma_handle, size, DMA_TO_DEVICE);
- 配置 DMA 传输
// 典型设备寄存器配置
void setup_dma_transfer(struct device *dev, dma_addr_t dma_addr, size_t size)
{
// 设置DMA目标地址
iowrite32(dma_addr, dev->reg_base + DMA_ADDR_REG);
// 设置传输长度
iowrite32(size, dev->reg_base + DMA_LEN_REG);
// 启动DMA传输
iowrite32(DMA_START_BIT | DMA_IRQ_EN, dev->reg_base + DMA_CTRL_REG);
}
二、关键 API 详解
-
映射类型选择
| 类型 | 适用场景 | 生命周期 | 缓存管理 |
|-----------------|----------------------------|--------------|---------------|
| 一致性映射 | 控制寄存器、长期共享数据 | 长期 | 自动维护 |
| 流式映射 | 大数据传输、短期操作 | 短期 | 需手动同步 | -
复杂传输场景处理
分散/聚集(Scatter-Gather)DMA
struct scatterlist sg[SG_ENTRIES];
dma_addr_t sg_dma_addr[SG_ENTRIES];
// 初始化分散列表
sg_init_table(sg, SG_ENTRIES);
for (i = 0; i < SG_ENTRIES; i++) {
sg_set_page(&sg[i], virt_to_page(buf[i]), buf_len[i], offset);
}
// 映射分散列表
nents = dma_map_sg(dev, sg, SG_ENTRIES, DMA_TO_DEVICE);
for (i = 0; i < nents; i++) {
sg_dma_addr[i] = sg_dma_address(&sg[i]);
}
三、完整驱动示例
- 字符设备 DMA 驱动框架
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/dma-mapping.h>
#define DEVICE_NAME "dma_demo"
#define BUF_SIZE (4 * 1024) // 4KB DMA缓冲区
struct dma_device {
struct cdev cdev;
dma_addr_t dma_handle;
void *vaddr;
struct device *dev;
};
static int dma_open(struct inode *inode, struct file *filp)
{
struct dma_device *dma_dev = container_of(inode->i_cdev, struct dma_device, cdev);
filp->private_data = dma_dev;
return 0;
}
static ssize_t dma_write(struct file *filp, const char __user *buf,
size_t count, loff_t *pos)
{
struct dma_device *dma_dev = filp->private_data;
// 从用户空间拷贝数据到DMA缓冲区
if (copy_from_user(dma_dev->vaddr, buf, min(count, BUF_SIZE)))
return -EFAULT;
// 启动DMA传输
dma_sync_single_for_device(dma_dev->dev, dma_dev->dma_handle,
BUF_SIZE, DMA_TO_DEVICE);
start_hardware_transfer(dma_dev);
return count;
}
static struct file_operations dma_fops = {
.owner = THIS_MODULE,
.open = dma_open,
.write = dma_write,
};
static int __init dma_init(void)
{
struct dma_device *dma_dev;
dev_t devno;
// 分配设备号
alloc_chrdev_region(&devno, 0, 1, DEVICE_NAME);
// 初始化设备结构
dma_dev = kzalloc(sizeof(*dma_dev), GFP_KERNEL);
dma_dev->dev = &pdev->dev;
// 分配DMA缓冲区
dma_dev->vaddr = dma_alloc_coherent(dma_dev->dev, BUF_SIZE,
&dma_dev->dma_handle, GFP_KERNEL);
// 初始化字符设备
cdev_init(&dma_dev->cdev, &dma_fops);
cdev_add(&dma_dev->cdev, devno, 1);
return 0;
}
static void __exit dma_exit(void)
{
// 释放资源
dma_free_coherent(dma_dev->dev, BUF_SIZE,
dma_dev->vaddr, dma_dev->dma_handle);
unregister_chrdev_region(devno, 1);
kfree(dma_dev);
}
四、高级优化技巧
- DMA 池管理
#include <linux/dmapool.h>
// 创建DMA池(适合小对象)
struct dma_pool *pool = dma_pool_create("my_pool", dev,
obj_size, obj_align, 0);
// 从池中分配
void *obj = dma_pool_alloc(pool, GFP_KERNEL, &dma_handle);
// 释放到池
dma_pool_free(pool, obj, dma_handle);
// 销毁池
dma_pool_destroy(pool);
- IOMMU 支持处理
// 检查IOMMU支持
if (device_iommu_mapped(dev)) {
// 需要处理IOVA到PA的转换
dma_addr_t iova = dma_map_single_attrs(dev, buf, size,
dir, DMA_ATTR_SKIP_CPU_SYNC);
}
// 使用DMA属性
DEFINE_DMA_ATTRS(attrs);
dma_set_attr(DMA_ATTR_WEAK_ORDERING, &attrs);
dma_map_sg_attrs(dev, sg, nents, dir, &attrs);
五、调试与问题排查
- 常见错误检查
// 检查DMA地址是否有效
if (dma_mapping_error(dev, dma_handle)) {
dev_err(dev, "DMA映射失败 (地址: 0x%llx)", (u64)dma_handle);
}
// 调试缓存问题
if (!dma_is_consistent(dev, dma_handle)) {
dev_warn(dev, "检测到非一致性DMA访问");
}
- 性能监控
# 查看DMA统计信息
$ cat /proc/interrupts | grep dma
$ perf stat -e dma_engine/cycles/,dma_engine/transactions/
六、关键注意事项
-
地址宽度限制:
// 检查设备DMA能力 if (dev->dma_mask && *dev->dma_mask != DMA_BIT_MASK(64)) { // 处理32位DMA限制 }
-
缓存行对齐:
// 确保缓冲区按缓存行对齐 buf = kmalloc(size, GFP_KERNEL | __GFP_ZERO | __GFP_DMA); buf = (void *)ALIGN((unsigned long)buf, cache_line_size());
-
中断处理:
// DMA完成中断处理 irqreturn_t dma_irq_handler(int irq, void *dev_id) { struct dma_device *dev = dev_id; // 确认传输完成 if (ioread32(dev->reg_base + STATUS_REG) & DMA_DONE) { dma_unmap_single(dev->dev, dev->dma_handle, dev->buf_size, DMA_FROM_DEVICE); wake_up(&dev->waitq); } return IRQ_HANDLED; }
通过以上方法,可以安全高效地在 Linux 内核中实现 DMA 数据传输。实际开发中需特别注意:及时释放映射资源、正确处理缓存一致性、完善的错误检查机制。对于特定硬件平台,建议参考对应架构的 DMA 实现细节(如 ARM 的 arch/arm/mm/dma-mapping.c
)。