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)运行用户程序:

浙公网安备 33010602011771号