赞
踩
直接存储器存取(DMADirect Memory Access)方式是用硬件实现存储器与存储器之间或存储器与I\O设备之间直接进行高速数据传送,不需要CPU的干预。这种方式通常用来传送数据块。可以认为DMA是一个半智能化的控制器,专门负责数据在存储设备间的传输工作。
注意:这里DMA的外部启动有一个例子:音频接口I2S的数据传输
当外部的MIC接收到数据后,会将数据首先存储在其内部的FIFO缓冲区,之后向DMA递交一个数据拷贝请求(源:就是I2S的FIFO,目的:就是目标存储器),最后由DMA控制器完成数据的拷贝工作,期间CPU不参与。
思路:一个简单的例子:在内存中完成数据从 “源” 到 “目的” 的拷贝。
用两种方式实现:使用DMA和不使用DMA。最后比较差别。
方式:以字符设备驱动程序为模板来实现,用字符设备驱动程序中的ioctl函数来实现内存数据的复制功能。
按照字符设备驱动的常规5步来进行编写代码。
我们要做的事情是先分配一块可操作的缓冲区,然后进行数据拷贝操作;
这里需要注意的是:分配缓冲区的时候不能用kmalloc()函数!!
因为kmalloc()函数分配的内存虽然在虚拟地址上连续,但是其物理地址并不连续。
如果想用DMA操作内存,就必须得到连续的物理内存!!
分配方法:
用什么函数分配连续的物理内存?
函数的原型:
创建类:
之后在类下创建设备:
到这里整个框架就已经写好了。
重点在于如何实现ioctl()函数的内存拷贝功能。
1)首先使用常规操作(非DMA)
这里的ioctl()函数根据cmd命令参数来进行不同的操作。
注意:这里首先需要对之前分配好的内存进行设置:
2)使用DMA操作进行数据拷贝
使用DMA的方法就是看懂2440中的DMA控制器寄存器含义,然后通过寄存器的设置将“源 目的 和长度”告诉DMA控制器,然后启动即可。
2440DMA的控制寄存器一共有4个通道,每个通道都有一组寄存器进行控制:
DMA传输一启动,就会从 源 中得到字节数据,之后便立即将数据写入 目的 中。
下面分析DMA寄存器:重要的几个寄存器
1)DMA源相关的控制寄存器
2)DMA目的 相关的寄存器
由于DMA的寄存器数量太多(1个通道由9个寄存器控制),一个一个ioremap太麻烦。因此这里采用先定义一个结构体,然后直接映射的方法:
定义寄存器相关的结构体:
之后定义DMA各通道寄存器的基地址:
最后在入口函数中进行寄存器地址映射:
现在假设调用ioctl()函数后使用DMA操作:
把 源,目的,长度 告诉DMA控制器
前几个寄存器相对设置简单,DMA控制寄存器DCON参数较多,做以下解释:
选择Demand 模式,置位1;
由于实验在内存中完成,因此选择同步至HCLK时钟,设置为1;
置为1;
这里我们将DSZ设置为0(1字节)。
接下来设置DMASKTRIG寄存器启动DMA;
如何知道DMA什么时候完成??
DMA传输完成后会产生一个中断,因此需要注册一个中断来接收DMA发来的中断信号。
这里的中断号是在irqs.h中找到的:
但是需要注意的是:如果内核中已经使用了dma中断的话,我们是不能再次注册的,进入内核查看所有中断:
再次查看中断号后发现只能使用DMA3。
这里的请求中断和端口映射都需要改:
之后在入口函数中申请irq中断:
这里在中间如果没有申请成功目的内存,也要释放该irq中断:
最后在出口函数中释放之前申请的所有资源:
注意:这里可以引入“休眠-唤醒”机制:在入口函数中启动了DMA之后休眠;之后在DMA中断处理函数s3c_dma_irq()函数中唤醒:
①首先声明一个队列和事件标志:
②之后在启动DMA后休眠:
③在DMA的中断处理函数中唤醒
注意:“休眠-唤醒”的工作流程:
休眠后程序在休眠处停止执行,直到产生中断后在中断处理程序中将程序再次唤醒,唤醒后程序又会在之前休眠的地方继续往下执行。
编译后出现警告:
显示dma_free_writecombine函数未定义,参考LCD的程序发现是有关dma的一个头文件没有添加的缘故,
添加头文件后编译生成.ko文件—>/work/nfs_root/first_fs
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/ioctl.h> #include <string.h> /* ./dma_test nodma * ./dma_test dma */ #define MEM_CPY_NO_DMA 0 #define MEM_CPY_DMA 1 void print_usage(char *name) { printf("Usage:\n"); printf("%s <nodma | dma>\n", name); } int main(int argc, char **argv) { int fd; if (argc != 2) { print_usage(argv[0]); return -1; } fd = open("/dev/dma", O_RDWR); if (fd < 0) { printf("can't open /dev/dma\n"); return -1; } if (strcmp(argv[1], "nodma") == 0) { while (1) { ioctl(fd, MEM_CPY_NO_DMA); } } else if (strcmp(argv[1], "dma") == 0) { while (1) { ioctl(fd, MEM_CPY_DMA); } } else { print_usage(argv[0]); return -1; } return 0; }
这里有一个小技巧:如果在写程序的时候忘记了调用了某个函数需要包含的头文件,则可以在Linux系统中输入 “man + 函数名” 的方式查询:
这里的2指的是函数的参数个数。
之后上传至服务器中编译:
1)首先测试不使用DMA的情况:
应用程序在后台调用ioctl 进行数据的拷贝操作,在此过程中CPU全程都在参与数据的拷贝过程,一直消耗CPU资源,因此此时其他应用程序是不能够被有效执行的,或者说会出现很卡顿的情况。
2)测试使用DMA的情况:
传入相应的参数后,进入内核态执行ioctl函数中的dma分支,dma控制寄存器参数被设置好以后启动dma,启动之后CPU就不再介入数据的传输过程,期间CPU可以去执行其他程序,直到dma将数据拷贝完成产生中断信号才会通知CPU去处理。
输入了相应命令后并没有执行dma操作,没有打印出内存数据拷贝的结果,也就是说一直没有中断唤醒,应该是代码中有关dma寄存器设置的代码出现了问题:
目前编写的操作DMA的程序是通过设置相应的DMA控制寄存器来进行数据的读写。但是实际上在内核中已经提供了完整的操作DMA的函数。
以声卡驱动的例子来看: s3c2410-uda1341.c
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。