20150429 S3C实现DMA驱动程序编写
2015-04-29 Lover雪儿
在IMX257上只有SDMA,SDMA比DMA的功能更加强大,但是为了学习的目的,如果直接学习SDMA,可能会不能消化,
所以,此处,我们从简单到复杂,从S3C2440的DMA驱动程序开始学习,等学懂它之后,我们再进军IMX257的SDMA.
一.一个简单的程序框架
1.定义一些指针
1 static int major = 0; 2 3 #define MEM_CPY_NO_DMA 0 4 #define MEM_CPY_DMA 1 5 6 static char *src; 7 static u32 src_phys; 8 9 static char *dst; 10 static u32 dst_phys; 11 12 static struct class *cls; 13 14 #define BUF_SIZE (512*1024)
上面代码中主要定义了主设备号,目的地址,源地址等.
2.定义字符设备的file_operation结构体
1 static int s3c_dma_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) 2 { 3 switch (cmd) 4 { 5 case MEM_CPY_NO_DMA : 6 { 7 break; 8 } 9 case MEM_CPY_DMA : 10 { 11 break; 12 } 13 } 14 return 0; 15 } 16 17 static struct file_operations dma_fops = { 18 .owner = THIS_MODULE, 19 .ioctl = s3c_dma_ioctl, 20 };
在case中,分别定义CPU拷贝数据和DMA拷贝数据两种情况.
3.入口函数
1 static int s3c_dma_init(void) 2 { 3 /* 分配SRC, DST对应的缓冲区 */ 4 src = dma_alloc_writecombine(NULL, BUF_SIZE, &src_phys, GFP_KERNEL); 5 if (NULL == src) 6 { 7 printk("can't alloc buffer for src\n"); 8 return -ENOMEM; 9 } 10 11 dst = dma_alloc_writecombine(NULL, BUF_SIZE, &dst_phys, GFP_KERNEL); 12 if (NULL == dst) 13 { 14 dma_free_writecombine(NULL, BUF_SIZE, src, src_phys); 15 printk("can't alloc buffer for dst\n"); 16 return -ENOMEM; 17 } 18 19 major = register_chrdev(0, "s3c_dma", &dma_fops); 20 21 /* 为了自动创建设备节点 */ 22 cls = class_create(THIS_MODULE, "s3c_dma"); 23 class_device_create(cls, NULL, MKDEV(major, 0), NULL, "dma"); /* /dev/dma */ 24 25 return 0; 26 }
上面代码中:首先分配了源.目的地址的内存缓冲区,接着就是申请字符设备,自动创建字符设备节点.
4.出口函数
1 static void s3c_dma_exit(void) 2 { 3 class_device_destroy(cls, MKDEV(major, 0)); 4 class_destroy(cls); 5 unregister_chrdev(major, "s3c_dma"); 6 dma_free_writecombine(NULL, BUF_SIZE, src, src_phys); 7 dma_free_writecombine(NULL, BUF_SIZE, dst, dst_phys); 8 }
销毁字符设备的类,卸载字符设备,接着就是释放前面申请的源.目的地址的内存.
附上驱动代码1:
1 #include <linux/module.h> 2 #include <linux/kernel.h> 3 #include <linux/fs.h> 4 #include <linux/init.h> 5 #include <linux/delay.h> 6 #include <linux/irq.h> 7 #include <asm/uaccess.h> 8 #include <asm/irq.h> 9 #include <asm/io.h> 10 #include <asm/arch/regs-gpio.h> 11 #include <asm/hardware.h> 12 #include <linux/poll.h> 13 14 static int major = 0; 15 16 #define MEM_CPY_NO_DMA 0 17 #define MEM_CPY_DMA 1 18 19 static char *src; 20 static u32 src_phys; 21 22 static char *dst; 23 static u32 dst_phys; 24 25 static struct class *cls; 26 27 #define BUF_SIZE (512*1024) 28 29 static int s3c_dma_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) 30 { 31 switch (cmd) 32 { 33 case MEM_CPY_NO_DMA : 34 { 35 break; 36 } 37 38 case MEM_CPY_DMA : 39 { 40 break; 41 } 42 } 43 44 return 0; 45 } 46 47 static struct file_operations dma_fops = { 48 .owner = THIS_MODULE, 49 .ioctl = s3c_dma_ioctl, 50 }; 51 52 static int s3c_dma_init(void) 53 { 54 /* 分配SRC, DST对应的缓冲区 */ 55 src = dma_alloc_writecombine(NULL, BUF_SIZE, &src_phys, GFP_KERNEL); 56 if (NULL == src) 57 { 58 printk("can't alloc buffer for src\n"); 59 return -ENOMEM; 60 } 61 62 dst = dma_alloc_writecombine(NULL, BUF_SIZE, &dst_phys, GFP_KERNEL); 63 if (NULL == dst) 64 { 65 dma_free_writecombine(NULL, BUF_SIZE, src, src_phys); 66 printk("can't alloc buffer for dst\n"); 67 return -ENOMEM; 68 } 69 70 major = register_chrdev(0, "s3c_dma", &dma_fops); 71 72 /* 为了自动创建设备节点 */ 73 cls = class_create(THIS_MODULE, "s3c_dma"); 74 class_device_create(cls, NULL, MKDEV(major, 0), NULL, "dma"); /* /dev/dma */ 75 76 return 0; 77 } 78 79 static void s3c_dma_exit(void) 80 { 81 class_device_destroy(cls, MKDEV(major, 0)); 82 class_destroy(cls); 83 unregister_chrdev(major, "s3c_dma"); 84 dma_free_writecombine(NULL, BUF_SIZE, src, src_phys); 85 dma_free_writecombine(NULL, BUF_SIZE, dst, dst_phys); 86 } 87 88 module_init(s3c_dma_init); 89 module_exit(s3c_dma_exit); 90 91 MODULE_LICENSE("GPL");
二.增加不使用DMA的内存拷贝功能.
1 static int s3c_dma_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) 2 { 3 int i; 4 memset(src, 0xAA, BUF_SIZE); 5 memset(dst, 0x55, BUF_SIZE); 6 7 switch (cmd) 8 { 9 case MEM_CPY_NO_DMA : 10 { 11 for (i = 0; i < BUF_SIZE; i++) 12 dst[i] = src[i]; 13 if (memcmp(src, dst, BUF_SIZE) == 0) 14 { 15 printk("MEM_CPY_NO_DMA OK\n"); 16 } 17 else 18 { 19 printk("MEM_CPY_DMA ERROR\n"); 20 } 21 break; 22 } 23 case MEM_CPY_DMA : 24 { 25 break; 26 } 27 } 28 29 return 0; 30 }
如程序所示,再ioctl函数中不适用DMA的增加内存拷贝功能.
附上驱动程序2
1 #include <linux/module.h> 2 #include <linux/kernel.h> 3 #include <linux/fs.h> 4 #include <linux/init.h> 5 #include <linux/delay.h> 6 #include <linux/irq.h> 7 #include <asm/uaccess.h> 8 #include <asm/irq.h> 9 #include <asm/io.h> 10 #include <asm/arch/regs-gpio.h> 11 #include <asm/hardware.h> 12 #include <linux/poll.h> 13 14 static int major = 0; 15 16 #define MEM_CPY_NO_DMA 0 17 #define MEM_CPY_DMA 1 18 19 static char *src; 20 static u32 src_phys; 21 22 static char *dst; 23 static u32 dst_phys; 24 25 static struct class *cls; 26 27 #define BUF_SIZE (512*1024) 28 29 static int s3c_dma_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) 30 { 31 int i; 32 33 memset(src, 0xAA, BUF_SIZE); 34 memset(dst, 0x55, BUF_SIZE); 35 36 switch (cmd) 37 { 38 case MEM_CPY_NO_DMA : 39 { 40 for (i = 0; i < BUF_SIZE; i++) 41 dst[i] = src[i]; 42 if (memcmp(src, dst, BUF_SIZE) == 0) 43 { 44 printk("MEM_CPY_NO_DMA OK\n"); 45 } 46 else 47 { 48 printk("MEM_CPY_DMA ERROR\n"); 49 } 50 break; 51 } 52 53 case MEM_CPY_DMA : 54 { 55 break; 56 } 57 } 58 59 return 0; 60 } 61 62 static struct file_operations dma_fops = { 63 .owner = THIS_MODULE, 64 .ioctl = s3c_dma_ioctl, 65 }; 66 67 static int s3c_dma_init(void) 68 { 69 /* 分配SRC, DST对应的缓冲区 */ 70 src = dma_alloc_writecombine(NULL, BUF_SIZE, &src_phys, GFP_KERNEL); 71 if (NULL == src) 72 { 73 printk("can't alloc buffer for src\n"); 74 return -ENOMEM; 75 } 76 77 dst = dma_alloc_writecombine(NULL, BUF_SIZE, &dst_phys, GFP_KERNEL); 78 if (NULL == dst) 79 { 80 dma_free_writecombine(NULL, BUF_SIZE, src, src_phys); 81 printk("can't alloc buffer for dst\n"); 82 return -ENOMEM; 83 } 84 85 major = register_chrdev(0, "s3c_dma", &dma_fops); 86 87 /* 为了自动创建设备节点 */ 88 cls = class_create(THIS_MODULE, "s3c_dma"); 89 class_device_create(cls, NULL, MKDEV(major, 0), NULL, "dma"); /* /dev/dma */ 90 91 return 0; 92 } 93 94 static void s3c_dma_exit(void) 95 { 96 class_device_destroy(cls, MKDEV(major, 0)); 97 class_destroy(cls); 98 unregister_chrdev(major, "s3c_dma"); 99 dma_free_writecombine(NULL, BUF_SIZE, src, src_phys); 100 dma_free_writecombine(NULL, BUF_SIZE, dst, dst_phys); 101 } 102 103 module_init(s3c_dma_init); 104 module_exit(s3c_dma_exit); 105 106 MODULE_LICENSE("GPL");
附上测试程序2.
1 #include <stdio.h> 2 #include <sys/types.h> 3 #include <sys/stat.h> 4 #include <fcntl.h> 5 #include <sys/ioctl.h> 6 #include <string.h> 7 8 /* ./dma_test nodma 9 * ./dma_test dma 10 */ 11 #define MEM_CPY_NO_DMA 0 12 #define MEM_CPY_DMA 1 13 14 void print_usage(char *name) 15 { 16 printf("Usage:\n"); 17 printf("%s <nodma | dma>\n", name); 18 } 19 20 21 int main(int argc, char **argv) 22 { 23 int fd; 24 25 if (argc != 2) 26 { 27 print_usage(argv[0]); 28 return -1; 29 } 30 31 fd = open("/dev/dma", O_RDWR); 32 if (fd < 0) 33 { 34 printf("can't open /dev/dma\n"); 35 return -1; 36 } 37 38 if (strcmp(argv[1], "nodma") == 0) 39 { 40 while (1) 41 { 42 ioctl(fd, MEM_CPY_NO_DMA); 43 } 44 } 45 else if (strcmp(argv[1], "dma") == 0) 46 { 47 while (1) 48 { 49 ioctl(fd, MEM_CPY_DMA); 50 } 51 } 52 else 53 { 54 print_usage(argv[0]); 55 return -1; 56 } 57 return 0; 58 }
三.映射s3c的DMA内存地址
测试程序时,不知道大家有没有发现,当我们运行test测试程序时,我们的拷贝进程占用了全部的CPU,以至于我们想运行一条命令都要等好久才能实现,
为了解决它,我们就引入了DMA,接下来,我们就真正开始DMA的编程
1 #define DMA0_BASE_ADDR 0x4B000000 2 #define DMA1_BASE_ADDR 0x4B000040 3 #define DMA2_BASE_ADDR 0x4B000080 4 #define DMA3_BASE_ADDR 0x4B0000C0 5 6 struct s3c_dma_regs { 7 unsigned long disrc; 8 unsigned long disrcc; 9 unsigned long didst; 10 unsigned long didstc; 11 unsigned long dcon; 12 unsigned long dstat; 13 unsigned long dcsrc; 14 unsigned long dcdst; 15 unsigned long dmasktrig; 16 };
如程序中所示:首先定义DMA的四个通道的基地址,接着定义一个s3c的dma的寄存器结构体.
分别再入口函数中映射DMA的内存IO端口,此处我们使用DMA0,当然如果要使用其他的DMA,类似.
在入口函数中映射:
dma_regs = ioremap(DMA0_BASE_ADDR, sizeof(struct s3c_dma_regs));
在出口函数中解除DMA映射.
iounmap(dma_regs);
四.注册DMA中断
前面我们已经映好DMA的内存地址了.
接下来,要想DMA工作,还需要对齐进行注册DMA中断和DMA配置.
在入口函数中注册DMA中断,再出口函数中释放DMA中断
在入口函数中注册中断:
1 if (request_irq(IRQ_DMA3, s3c_dma_irq, 0, "s3c_dma", 1)) 2 { 3 printk("can't request_irq for DMA\n"); 4 return -EBUSY; 5 }
在出口函数中释放中断:
free_irq(IRQ_DMA3, 1);
当用户程序中定义为 MEM_CPY_DMA 使用DMA传输时,便会在ioctl函数中进入case MEM_CPY_DMA :中.
接着我们便在其中配置号DMA的源.目的参数,接着便启动传输,CPU则进入休眠状态,让出CPU供其他进程使用.当DMA拷贝完成时,便会触发中断,从而唤醒.
ioctl函数的部分程序如下面所示:
1 case MEM_CPY_DMA : 2 { 3 ev_dma = 0; 4 5 /* 把源,目的,长度告诉DMA */ 6 dma_regs->disrc = src_phys; /* 源的物理地址 */ 7 dma_regs->disrcc = (0<<1) | (0<<0); /* 源位于AHB总线, 源地址递增 */ 8 dma_regs->didst = dst_phys; /* 目的的物理地址 */ 9 dma_regs->didstc = (0<<2) | (0<<1) | (0<<0); /* 目的位于AHB总线, 目的地址递增 */ 10 dma_regs->dcon = (1<<30)|(1<<29)|(0<<28)|(1<<27)|(0<<23)|(0<<20)|(BUF_SIZE<<0); /* 使能中断,单个传输,软件触发, */ 11 12 /* 启动DMA */ 13 dma_regs->dmasktrig = (1<<1) | (1<<0); 14 15 /* 如何知道DMA什么时候完成? */ 16 /* 休眠 */ 17 wait_event_interruptible(dma_waitq, ev_dma); 18 19 if (memcmp(src, dst, BUF_SIZE) == 0) 20 { 21 printk("MEM_CPY_DMA OK\n"); 22 } 23 else 24 { 25 printk("MEM_CPY_DMA ERROR\n"); 26 } 27 28 break; 29 }
中断服务程序:
1 static irqreturn_t s3c_dma_irq(int irq, void *devid) 2 { 3 /* 唤醒 */ 4 ev_dma = 1; 5 wake_up_interruptible(&dma_waitq); /* 唤醒休眠的进程 */ 6 return IRQ_HANDLED; 7 }
附上驱动程序3:
1 #include <linux/module.h> 2 #include <linux/kernel.h> 3 #include <linux/fs.h> 4 #include <linux/init.h> 5 #include <linux/delay.h> 6 #include <linux/irq.h> 7 #include <asm/uaccess.h> 8 #include <asm/irq.h> 9 #include <asm/io.h> 10 #include <asm/arch/regs-gpio.h> 11 #include <asm/hardware.h> 12 #include <linux/poll.h> 13 #include <linux/dma-mapping.h> 14 15 #define MEM_CPY_NO_DMA 0 16 #define MEM_CPY_DMA 1 17 18 #define BUF_SIZE (512*1024) 19 20 #define DMA0_BASE_ADDR 0x4B000000 21 #define DMA1_BASE_ADDR 0x4B000040 22 #define DMA2_BASE_ADDR 0x4B000080 23 #define DMA3_BASE_ADDR 0x4B0000C0 24 25 struct s3c_dma_regs { 26 unsigned long disrc; 27 unsigned long disrcc; 28 unsigned long didst; 29 unsigned long didstc; 30 unsigned long dcon; 31 unsigned long dstat; 32 unsigned long dcsrc; 33 unsigned long dcdst; 34 unsigned long dmasktrig; 35 }; 36 37 38 static int major = 0; 39 40 static char *src; 41 static u32 src_phys; 42 43 static char *dst; 44 static u32 dst_phys; 45 46 static struct class *cls; 47 48 static volatile struct s3c_dma_regs *dma_regs; 49 50 static DECLARE_WAIT_QUEUE_HEAD(dma_waitq); 51 /* 中断事件标志, 中断服务程序将它置1,ioctl将它清0 */ 52 static volatile int ev_dma = 0; 53 54 static int s3c_dma_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) 55 { 56 int i; 57 58 memset(src, 0xAA, BUF_SIZE); 59 memset(dst, 0x55, BUF_SIZE); 60 61 switch (cmd) 62 { 63 case MEM_CPY_NO_DMA : 64 { 65 for (i = 0; i < BUF_SIZE; i++) 66 dst[i] = src[i]; 67 if (memcmp(src, dst, BUF_SIZE) == 0) 68 { 69 printk("MEM_CPY_NO_DMA OK\n"); 70 } 71 else 72 { 73 printk("MEM_CPY_DMA ERROR\n"); 74 } 75 break; 76 } 77 78 case MEM_CPY_DMA : 79 { 80 ev_dma = 0; 81 82 /* 把源,目的,长度告诉DMA */ 83 dma_regs->disrc = src_phys; /* 源的物理地址 */ 84 dma_regs->disrcc = (0<<1) | (0<<0); /* 源位于AHB总线, 源地址递增 */ 85 dma_regs->didst = dst_phys; /* 目的的物理地址 */ 86 dma_regs->didstc = (0<<2) | (0<<1) | (0<<0); /* 目的位于AHB总线, 目的地址递增 */ 87 dma_regs->dcon = (1<<30)|(1<<29)|(0<<28)|(1<<27)|(0<<23)|(0<<20)|(BUF_SIZE<<0); /* 使能中断,单个传输,软件触发, */ 88 89 /* 启动DMA */ 90 dma_regs->dmasktrig = (1<<1) | (1<<0); 91 92 /* 如何知道DMA什么时候完成? */ 93 /* 休眠 */ 94 wait_event_interruptible(dma_waitq, ev_dma); 95 96 if (memcmp(src, dst, BUF_SIZE) == 0) 97 { 98 printk("MEM_CPY_DMA OK\n"); 99 } 100 else 101 { 102 printk("MEM_CPY_DMA ERROR\n"); 103 } 104 105 break; 106 } 107 } 108 109 return 0; 110 } 111 112 static struct file_operations dma_fops = { 113 .owner = THIS_MODULE, 114 .ioctl = s3c_dma_ioctl, 115 }; 116 117 static irqreturn_t s3c_dma_irq(int irq, void *devid) 118 { 119 /* 唤醒 */ 120 ev_dma = 1; 121 wake_up_interruptible(&dma_waitq); /* 唤醒休眠的进程 */ 122 return IRQ_HANDLED; 123 } 124 125 static int s3c_dma_init(void) 126 { 127 if (request_irq(IRQ_DMA3, s3c_dma_irq, 0, "s3c_dma", 1)) 128 { 129 printk("can't request_irq for DMA\n"); 130 return -EBUSY; 131 } 132 133 /* 分配SRC, DST对应的缓冲区 */ 134 src = dma_alloc_writecombine(NULL, BUF_SIZE, &src_phys, GFP_KERNEL); 135 if (NULL == src) 136 { 137 printk("can't alloc buffer for src\n"); 138 free_irq(IRQ_DMA3, 1); 139 return -ENOMEM; 140 } 141 142 dst = dma_alloc_writecombine(NULL, BUF_SIZE, &dst_phys, GFP_KERNEL); 143 if (NULL == dst) 144 { 145 free_irq(IRQ_DMA3, 1); 146 dma_free_writecombine(NULL, BUF_SIZE, src, src_phys); 147 printk("can't alloc buffer for dst\n"); 148 return -ENOMEM; 149 } 150 151 major = register_chrdev(0, "s3c_dma", &dma_fops); 152 153 /* 为了自动创建设备节点 */ 154 cls = class_create(THIS_MODULE, "s3c_dma"); 155 class_device_create(cls, NULL, MKDEV(major, 0), NULL, "dma"); /* /dev/dma */ 156 157 dma_regs = ioremap(DMA3_BASE_ADDR, sizeof(struct s3c_dma_regs)); 158 159 return 0; 160 } 161 162 static void s3c_dma_exit(void) 163 { 164 iounmap(dma_regs); 165 class_device_destroy(cls, MKDEV(major, 0)); 166 class_destroy(cls); 167 unregister_chrdev(major, "s3c_dma"); 168 dma_free_writecombine(NULL, BUF_SIZE, src, src_phys); 169 dma_free_writecombine(NULL, BUF_SIZE, dst, dst_phys); 170 free_irq(IRQ_DMA3, 1); 171 } 172 173 module_init(s3c_dma_init); 174 module_exit(s3c_dma_exit); 175 176 MODULE_LICENSE("GPL");
附上测试程序3:
1 #include <stdio.h> 2 #include <sys/types.h> 3 #include <sys/stat.h> 4 #include <fcntl.h> 5 #include <sys/ioctl.h> 6 #include <string.h> 7 8 /* ./dma_test nodma 9 * ./dma_test dma 10 */ 11 #define MEM_CPY_NO_DMA 0 12 #define MEM_CPY_DMA 1 13 14 void print_usage(char *name) 15 { 16 printf("Usage:\n"); 17 printf("%s <nodma | dma>\n", name); 18 } 19 20 21 int main(int argc, char **argv) 22 { 23 int fd; 24 25 if (argc != 2) 26 { 27 print_usage(argv[0]); 28 return -1; 29 } 30 31 fd = open("/dev/dma", O_RDWR); 32 if (fd < 0) 33 { 34 printf("can't open /dev/dma\n"); 35 return -1; 36 } 37 38 if (strcmp(argv[1], "nodma") == 0) 39 { 40 while (1) 41 { 42 ioctl(fd, MEM_CPY_NO_DMA); 43 } 44 } 45 else if (strcmp(argv[1], "dma") == 0) 46 { 47 while (1) 48 { 49 ioctl(fd, MEM_CPY_DMA); 50 } 51 } 52 else 53 { 54 print_usage(argv[0]); 55 return -1; 56 } 57 return 0; 58 }
五.总结DMA工作流程.
简单来说,我觉得DMA编程总共分为配置和运行两个步骤.
配置:
①注册中断
②分配供模拟拷贝数据的源.目的地址的内存空间.
③注册字符设备
④映射ioremapDMA的IO空间
运行:
①应用程序调用ioctl(fd,MEM_CPY_DMA),进入case语句
②在case语句中配置号DMA传输数据所需要的源,目的,大小,便启动DMA传输.
③启动DMA传输后,程序进入可中断的休眠,让出CPU
④一旦DMA传输完毕,便会触发中断,再中断中唤醒,然后打印MEM_CPY_DMA OK告诉应用程序DMA传输成功了.