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 指定通道)。
2、注意事项
在理想情况下(驱动正确加载、硬件模拟正常),上述程序的执行结果如下:
三、执行过程
[ 123.456] DMA:DMA buffer allocated at virtual 0xffff880012345000, physical 0x12345000 [ 123.457] DMA:Requested channel successfully
[ 123.458] Simulating peripheral DMA from 0x12345000 to 0x56789000, len 11 [ 123.468] DMA transfer complete, status: 0 # 状态0表示成功
[ 123.469] Simulating peripheral DMA from 0x56789000 to 0x12345000, len 11 [ 123.479] DMA transfer complete, status: 0
验证步骤
(1)加载驱动:
insmod dma_device_driver.ko mknod /dev/dma_demo_device c <主设备号> <次设备号> # 驱动自动分配主设备号时可省略
(2)运行用户程序: