Linux 下 DMA 使用的详细指南

一、DMA 使用基础流程

  1. 分配 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);
  1. 配置 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 详解

  1. 映射类型选择
    | 类型 | 适用场景 | 生命周期 | 缓存管理 |
    |-----------------|----------------------------|--------------|---------------|
    | 一致性映射 | 控制寄存器、长期共享数据 | 长期 | 自动维护 |
    | 流式映射 | 大数据传输、短期操作 | 短期 | 需手动同步 |

  2. 复杂传输场景处理
    分散/聚集(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]);
}

三、完整驱动示例

  1. 字符设备 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);
}

四、高级优化技巧

  1. 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);
  1. 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);

五、调试与问题排查

  1. 常见错误检查
// 检查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访问");
}
  1. 性能监控
# 查看DMA统计信息
$ cat /proc/interrupts | grep dma
$ perf stat -e dma_engine/cycles/,dma_engine/transactions/

六、关键注意事项

  1. 地址宽度限制

    // 检查设备DMA能力
    if (dev->dma_mask && *dev->dma_mask != DMA_BIT_MASK(64)) {
        // 处理32位DMA限制
    }
    
  2. 缓存行对齐

    // 确保缓冲区按缓存行对齐
    buf = kmalloc(size, GFP_KERNEL | __GFP_ZERO | __GFP_DMA);
    buf = (void *)ALIGN((unsigned long)buf, cache_line_size());
    
  3. 中断处理

    // 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)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值