一. DMA的引入
假设我们让2440来拷贝一段数据, 从内存的src拷贝到dst,拷贝的长度为size.
char *src = AAA;
char *dst = BBB;
int i;
for (i = 0; i <size; i++)
dst[i] = src[i];
这写出来的代码是让cpu来执行数据的拷贝工作, 显然,cpu在执行这个过程中,同一时间内只能做一件事情,如果要拷贝的数据很大的话,cpu在拷贝的过程中就不能做其他事情了,这样就会引起卡顿.
所以我们就引入了DMA (Direct Memory Access,直接内存存取), 它允许不同速度的硬件装置来沟通, 而不需要依赖于 CPU 的大量中断负载.
1. 把源告诉DMA;
2. 把目的告诉DMA;
3. 把size告诉DMA;
4. 设置DMA参数 a. 地址递增/递减/不变; b. 手工启动(设置某些寄存器让它自动的启动DMA去拷贝这些数据), 外部启动(麦克MIC得到数据存在I2S的数据缓冲区里,当这个缓冲区里有了数据之后就会去触发DMA产生一个DMA请求, 由DMA将数据拷贝到内存里, 这时的源就是I2S的缓冲区, 目的就是内存的某个地址处)
5.启动DMA.
启动DMA之后, DMA就会将数据拷从src拷贝到dst,拷贝完之后就会发出一个中断告诉cpu数据拷贝完成.
二. DMA驱动程序编写
(一). 怎么写字符设备驱动
应用程序APP要open, 要read, 要write等 ;应用程序就提供对映的drv_open, drv_read, drv_write等. 为了便于管理引入一个 file_operations 结构体, 把这些open, read等函数放入这个结构体. 构造好file_operations 结构体之后要用起来, 要注册进内核:register_chrdev(主设备号, 名字, file_operations);
0. 确定主设备号;
1. 构造file_operations 结构体
2. 注册:register_chrdev(主设备号, 名字, file_operations);
3. 入口
4. 出口
分配缓冲区不能用kmalloc来分配, 因为kmalloc分配出来的内存是虚拟地址上连续的, 物理地址上并非连续. 而想用DMA的话就必须用连续的物理内存, 这个DMA没那么高的智能, 它只能处理物理地址连续的内存.
(二). DMA驱动初步框架
dma.c
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
static int major = 0; #define MEM_CPY_NO_DMA 0 #define MEM_CPY_DMA 1 static char *src; /* 源 */ static u32 src_phys; /* 源的物理地址 */ static char *dst; /* 目的 */ static u32 dst_phys; /* 目的的物理地址 */ static struct class *cls; #define BUF_SIZE (512*1024) //512k static int s3c_dma_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) { switch (cmd) { case MEM_CPY_NO_DMA : { break; } case MEM_CPY_DMA : { break; } } return 0; } /* 构造file_operations结构体 */ static struct file_operations dma_fops = { .owner = THIS_MODULE, .ioctl = s3c_dma_ioctl, }; static int s3c_dma_init(void) { /* 分配SRC, DST对应的缓冲区 */ src = dma_alloc_writecombine(NULL, BUF_SIZE, &src_phys, GFP_KERNEL); if (NULL == src) { printk("can't alloc buffer for src\n"); return -ENOMEM; } dst = dma_alloc_writecombine(NULL, BUF_SIZE, &dst_phys, GFP_KERNEL); if (NULL == dst) { dma_free_writecombine(NULL, BUF_SIZE, src, src_phys); printk("can't alloc buffer for dst\n"); return -ENOMEM; } major = register_chrdev(0, "s3c_dma", &dma_fops); /* 为了自动创建设备节点 */ cls = class_create(THIS_MODULE, "s3c_dma"); class_device_create(cls, NULL, MKDEV(major, 0), NULL, "dma"); /* /dev/dma */ return 0; } static void s3c_dma_exit(void) { class_device_destroy(cls, MKDEV(major, 0)); class_destroy(cls); unregister_chrdev(major, "s3c_dma"); dma_free_writecombine(NULL, BUF_SIZE, src, src_phys); dma_free_writecombine(NULL, BUF_SIZE, dst, dst_phys); } module_init(s3c_dma_init); module_exit(s3c_dma_exit); MODULE_LICENSE("GPL");
注: 第66行里所说的内存泄漏是指, 这块内存被分配了, 却没有被调用,也没有被释放.
上面的代码里只分配了两块内存,我们把重点的东西放在s3c_dma_ioctl里, 下面再来写.
(三). DMA驱动填充框架
在开发板上执行# ls /dev/dma* 查看开发板上有没有这个设备节点, 没有继续, 有就去掉这个设备节点.
填充s3c_dma_ioctl, 不用DMA:
static int s3c_dma_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
int i;
memset(src, 0xAA, BUF_SIZE);/