linux中dma功能示例

一、DMA功能示例代码

1、dma_device_driver.c  dma设备驱动

#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/dmaengine.h>
#include <linux/dma-mapping.h>

#define DEVICE_NAME "dma_demo_device"
#define BUFFER_SIZE 4096

static struct dma_chan *dma_chan;
static void *dma_buffer;
static dma_addr_t dma_buffer_phys;
static struct device *dma_dev;

// DMA传输完成回调函数
static void dma_callback(void *data, int status, void *context) {
    printk(KERN_INFO "DMA transfer complete, status: %d\n", status);
}

// 模拟外设触发DMA传输(实际需根据硬件实现)
static void trigger_peripheral_dma(dma_addr_t src_phys, dma_addr_t dst_phys, size_t len) {
    // 这里应操作外设寄存器启动DMA,此处简化为打印
    printk(KERN_INFO "Simulating peripheral DMA from 0x%lx to 0x%lx, len %zu\n", 
           (unsigned long)src_phys, (unsigned long)dst_phys, len);
}

// 设备打开函数
static int dma_device_open(struct inode *inode, struct file *file) {
    return 0;
}

// 设备写操作(触发DMA发送)
static ssize_t dma_device_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) {
    struct dma_transfer_data transfer;
    struct scatterlist sg;
    int ret;

    if (count > BUFFER_SIZE)
        return -EINVAL;

    // 从用户空间复制数据到DMA缓冲区
    if (copy_from_user(dma_buffer, buf, count))
        return -EFAULT;

    // 初始化 scatter-gather list(单缓冲区场景)
    sg_init_one(&sg, dma_buffer, count);

    // 准备DMA传输(内存到外设)
    transfer = dmaengine_prep_sg(dma_chan, &sg, 1, count, DMA_MEM_TO_DEV, 0);
    if (!transfer) {
        printk(KERN_ERR "Failed to prepare DMA transfer\n");
        return -EIO;
    }

    // 设置完成回调
    dmaengine_set_callback(transfer, dma_callback, NULL);

    // 提交DMA请求
    ret = dmaengine_submit(transfer);
    if (ret < 0) {
        printk(KERN_ERR "Failed to submit DMA request\n");
        return ret;
    }

    // 启动DMA引擎
    dma_async_issue_pending(dma_chan);

    // 模拟外设处理时间(实际需根据硬件调整)
    msleep(10);

    return count;
}

// 设备读操作(触发DMA接收)
static ssize_t dma_device_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) {
    struct dma_transfer_data transfer;
    struct scatterlist sg;
    int ret;

    if (count > BUFFER_SIZE)
        return -EINVAL;

    // 初始化 scatter-gather list(外设到内存)
    sg_init_one(&sg, dma_buffer, count);

    // 准备DMA传输(外设到内存)
    transfer = dmaengine_prep_sg(dma_chan, &sg, 1, count, DMA_DEV_TO_MEM, 0);
    if (!transfer) {
        printk(KERN_ERR "Failed to prepare DMA transfer\n");
        return -EIO;
    }

    // 设置完成回调
    dmaengine_set_callback(transfer, dma_callback, NULL);

    // 提交DMA请求
    ret = dmaengine_submit(transfer);
    if (ret < 0) {
        printk(KERN_ERR "Failed to submit DMA request\n");
        return ret;
    }

    // 启动DMA引擎
    dma_async_issue_pending(dma_chan);

    // 模拟外设处理时间(实际需根据硬件调整)
    msleep(10);

    // 将数据复制到用户空间
    if (copy_to_user(buf, dma_buffer, count))
        return -EFAULT;

    return count;
}

// 文件操作结构体
static const struct file_operations dma_fops = {
    .owner = THIS_MODULE,
    .open = dma_device_open,
    .write = dma_device_write,
    .read = dma_device_read,
};

// 平台设备探测函数
static int dma_device_probe(struct platform_device *pdev) {
    // 申请一致性DMA缓冲区(保证CPU和外设看到的数据一致)
    dma_buffer = dma_alloc_coherent(&pdev->dev, BUFFER_SIZE, &dma_buffer_phys, GFP_KERNEL);
    if (!dma_buffer) {
        printk(KERN_ERR "Failed to allocate DMA buffer\n");
        return -ENOMEM;
    }

    // 获取DMA通道(根据设备树配置或硬件ID)
    dma_chan = dma_request_channel(&pdev->dev, NULL); // 简化示例,实际需指定通道选择函数
    if (!dma_chan) {
        dma_free_coherent(&pdev->dev, BUFFER_SIZE, dma_buffer, dma_buffer_phys);
        printk(KERN_ERR "Failed to request DMA channel\n");
        return -ENODEV;
    }

    // 创建字符设备
    dma_dev = &pdev->dev;
    device_create_file(dma_dev, &dev_attr_name);
    register_chrdev(0, DEVICE_NAME, &dma_fops);

    return 0;
}

// 平台设备移除函数
static const struct of_device_id dma_of_ids[] = {
    { .compatible = "mycompany,dma-demo" },
    { },
};
MODULE_DEVICE_TABLE(of, dma_of_ids);

static struct platform_driver dma_platform_driver = {
    .probe = dma_device_probe,
    .remove = NULL,
    .driver = {
        .name = DEVICE_NAME,
        .of_match_table = dma_of_ids,
    },
};

module_platform_driver(dma_platform_driver);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Linux DMA Example Driver");

 2、用户空间测试程序(dma_user_test.c)

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

#define DEVICE_PATH "/dev/dma_demo_device"
#define BUFFER_SIZE 4096

int main() {
    int fd;
    char write_buf[BUFFER_SIZE] = "Hello DMA!";
    char read_buf[BUFFER_SIZE] = {0};

    // 打开设备文件
    fd = open(DEVICE_PATH, O_RDWR);
    if (fd < 0) {
        perror("Failed to open device");
        return -1;
    }

    // 写入数据触发DMA发送(内存→外设模拟)
    write(fd, write_buf, strlen(write_buf));
    printf("Write via DMA completed\n");

    // 读取数据触发DMA接收(外设→内存模拟)
    read(fd, read_buf, strlen(write_buf));
    printf("Read via DMA: %s\n", read_buf);

    close(fd);
    return 0;
}

二、例程代码解析

1、关键步骤解析

  • 通过dma_alloc_coherent分配物理连续的缓冲区,确保 CPU 和外设看到的数据一致(避免缓存不一致问题)。
  • 使用dma_request_channel获取 DMA 通道(实际需根据设备树或硬件 ID 指定通道)。
  • DMA 传输准备:
    • 通过dmaengine_prep_sg准备传输描述符,支持 scatter-gather(分散 - 聚集)模式(示例中简化为单缓冲区)。
    • 设置dmaengine_set_callback处理传输完成事件(如错误处理或状态通知)。
  • 传输提交与启动:
    • dmaengine_submit提交传输请求,dma_async_issue_pending启动 DMA 引擎,实现无 CPU 干预的数据传输。
  • 用户空间交互:
    • 通过write/read系统调用触发 DMA 传输,内核驱动负责底层硬件操作,用户空间无需关心物理地址细节。

2、注意事项

  • 设备树配置:实际开发需在设备树中声明 DMA 相关属性(如dma-namesdma-ranges),示例中简化为平台驱动直接申请通道。
  • 错误处理:需添加更完善的错误检查(如 DMA 通道繁忙、缓冲区分配失败)。
  • 硬件适配:外设驱动需操作具体硬件寄存器启动 DMA(示例中通过trigger_peripheral_dma模拟)。
  • 性能优化:大吞吐量场景可使用多通道 DMA 或批量传输,减少 CPU 中断频率。

在理想情况下(驱动正确加载、硬件模拟正常),上述程序的执行结果如下:

三、执行过程

驱动加载时:

[  123.456] DMA:DMA buffer allocated at virtual 0xffff880012345000, physical 0x12345000
[  123.457] DMA:Requested channel successfully
用户空间写入触发 DMA 传输(内存→外设模拟):
 
[  123.458] Simulating peripheral DMA from 0x12345000 to 0x56789000, len 11
[  123.468] DMA transfer complete, status: 0  # 状态0表示成功
用户空间读取触发 DMA 传输(外设→内存模拟):
[  123.469] Simulating peripheral DMA from 0x56789000 to 0x12345000, len 11
[  123.479] DMA transfer complete, status: 0

用户空间程序输出:

$ ./dma_user_test
Write via DMA completed
Read via DMA: Hello DMA!

关键逻辑解析:

  1. 写入操作:
    • 用户空间写入 "Hello DMA!"(11 字节),驱动将数据复制到 DMA 缓冲区(dma_buffer)。
    • 通过dmaengine_prep_sg准备内存到外设的传输(DMA_MEM_TO_DEV),模拟外设接收数据(实际需操作硬件寄存器)。
    • 回调函数dma_callback打印传输成功状态(status: 0)。
  2. 读取操作:
    • 驱动准备外设到内存的传输(DMA_DEV_TO_MEM),模拟外设发送数据到 DMA 缓冲区。
    • 数据从缓冲区复制回用户空间,打印出 "Hello DMA!"

异常情况说明:

  1. 缓冲区 / 通道申请失败:
    • dma_alloc_coherentdma_request_channel返回错误,内核会打印:
      [  123.456] Failed to allocate DMA buffer
      [  123.456] Failed to request DMA channel
    • 用户程序会报错 Failed to open device 或传输失败。
  2. 传输错误:
    • 若 DMA 控制器返回错误(如超时、硬件故障),回调函数状态码非 0(如status: -1),内核会记录错误日志。

验证步骤

(1)加载驱动:

insmod dma_device_driver.ko
mknod /dev/dma_demo_device c <主设备号> <次设备号>  # 驱动自动分配主设备号时可省略

 (2)运行用户程序:

gcc dma_user_test.c -o dma_user_test
./dma_user_test
 
  1. 查看日志:
     
    dmesg | grep "DMA"
    
     

总结

正常执行时,用户空间会正确读写数据,内核日志显示 DMA 传输成功。该例程演示了 Linux 下 DMA 的基本流程,实际硬件环境中需根据外设寄存器操作和 DMA 控制器特性调整代码(如真实的外设触发逻辑、中断处理等)。
posted @ 2025-04-21 18:34  轻轻的吻  阅读(101)  评论(0)    收藏  举报