32.Linux-2440下的DMA驱动(详解)
DMA(Direct Memory Access)
即直接存储器访问, DMA 传输方式无需 CPU 直接控制传输,通过硬件为 RAM 、I/O 设备开辟一条直接传送数据的通路,能使 CPU 的效率大为提高。
学了这么多驱动,不难推出DMA的编写套路:
- 1)注册DMA中断,分配缓冲区
- 2)注册字符设备,并提供文件操作集合fops
- -> 2.1)file_operations里设置DMA硬件相关操作,来启动DMA
由于我们是用字符设备的测试方法测试的,而本例子只是用两个地址之间的拷贝来演示DMA的作用,所以采用字符设备方式编写
1.驱动编写之前,先来讲如何分配释放缓冲区、DMA相关寄存器介绍、使用DMA中断
1.1在linux中,分配释放DMA缓冲区,常用以下几个函数
1)
- /*该函数只禁止cache缓冲,保持写缓冲区,也就是对注册的物理区写入数据,也会更新到对应的虚拟缓存区上*/
- void *dma_alloc_writecombine(struct device *dev, size_t size, dma_addr_t *handle, gfp_t gfp);
- //分配DMA缓存区
- //返回值为:申请到的DMA缓冲区的虚拟地址,若为NULL,表示分配失败,需要释放,避免内存泄漏
- //参数如下:
- //*dev:指针,这里填0,表示这个申请的缓冲区里没有内容
- //size:分配的地址大小(字节单位)
- //*handle:申请到的物理起始地址
- //gfp:分配出来的内存参数,标志定义在<linux/gfp.h>,常用标志如下:
- //GFP_ATOMIC 用来从中断处理和进程上下文之外的其他代码中分配内存. 从不睡眠.
- //GFP_KERNEL 内核内存的正常分配. 可能睡眠.
- //GFP_USER 用来为用户空间页来分配内存; 它可能睡眠.
2)
- /*该函数禁止cache缓存以及禁止写入缓冲区,从而使CPU读写的地址和DMA读写的地址内容一致*/
- void * dma_alloc_coherent(struct device *dev, size_t size, dma_addr_t *handle, gfp_t gfp);
- //分配DMA缓存区,返回值和参数和上面的函数一直
3)
- dma_free_writecombine(struct device *dev, size_t size, void *cpu_addr, dma_addr_t handle); //释放DMA缓存,与dma_alloc_writecombine()对应
- //size:释放长度
- //cpu_addr:虚拟地址,
- //handle:物理地址
4)
- dma_free_coherent(struct device *dev, size_t size, void *cpu_addr, dma_addr_t handle) //释放DMA缓存,与dma_alloc_coherent ()对应
- //size:释放长度
//cpu_addr:虚拟地址,- //handle:物理地址
(PS: dma_free_writecombine()其实就是dma_free_conherent(),只不过是用了#define重命名而已。)
而我们之前用的内存分配kmalloc()函数,是不能用在DMA上,因为分配出来的内存可能在物理地址上是不连续的.
1.2 那么2440开发板如何来启动DMA,先来看2440的DMA寄存器
(PS:实际这些DMA相关的寄存器,在linux内核中三星已封装好了,可以直接调用,不过非常麻烦,还不如直接设置寄存器,可以参考:http://blog.csdn.net/mirkerson/article/details/6632273)
1.2.1 2440支持4个通道的DMA控制器
其中4个通道的DMA外设请求源,如下图所示(通过DCONn寄存器的[26:24]来设置)
(PS:如果请求源是系统总线上的,就只需要设置DCONn寄存器的[23]=0即可)
1.2.2 且每个通道都可以处理以下4种情况:
1) 源和目标都在系统总线上(比如:两个物理内存地址)
2) 当目标在外设总线上时,源在系统总线上(外设指:串口,定时器,I2C,I2S等)
3) 当目标在系统总线上时,源在外设总线上
4) 源和目标都在外设总线上
1.2.3 DMA有两种工作模式(通过DCONn寄存器的[28]来设置)
查询模式:
当DMA请求XnXDREQ为低电平时,则DMA会一直传输数据,直到DMA请求拉高,才停止
握手模式:
当DMA请求XnXDREQ有下降沿触发时,则DMA会传输一次数据
1.2.4 DMA有两种传输模式(通过DCONn寄存器的[31]来设置)
单元传输:
指传输过程中,每执行一次,则读1次,写1次.(如上图所示)
突发4传输:
指传输过程中,每执行一次,则读4次,然后写4次(如下图所示)
1.2.5 2440中的DMA寄存器如下图所示:
共有4个通道的寄存器,且每个通道的寄存器内容都一致,所以我们以DMA通道0为例:
1)DISRC0初始源寄存器
[30:0] : 存放DMA源的基地址
2)DISRCC0初始源控制寄存器
[1] : 源位置选择,0:源在系统总线上, 1:源在外设总线上
[0] : 源地址选择,0:传输时源地址自动增加, 1:源地址固定
3)DIDST0初始目标寄存器
[30:0] : 设置DMA目的的基地址
4)DIDSTC0初始目标控制寄存器
[2] :
中断时间选择, 0:当DMA传输计数=0,立即发生中断 1:执行完自动加载后再发送中断(也就是计数为0,然后重新加载计数值)
[1] : 目的位置选择, 0:目的在系统总线上, 1:目的在外设总线上
[0] : 目的地址选择, 0:传输时目的地址自动增加, 1:目的地址固定
5)DCON0控制寄存器
[31] : 工作模式选择, 0:查询模式 1:握手模式
(当源处于外设时,尽量选择握手模式)
[30] : 中断请求(DREQ)/中断回应(DACK)的同步时钟选择, 0:PCLK同步 1:HCLK同步
(PS:如果有设备在HCLK上,该位应当设为1,比如:(SDRAM)内存数组, 反之当这些设备在PCLK上,应当设为0,比如:ADC,IIS,I2C,UART)
[29] : DMA传输计数中断使能/禁止 0:禁止中断 1:当传输完成后,产生中断
[28] : 传输模式选择, 0:单元传输 1:突发4传输
[27] : 传输服务模式
0:单服务模式,比如:有2个DMA请求,它们会被顺序执行一次(单元传输/突发4传输)后停止,然后直到有下一次DMA请求,再重新开始另一次循环。
1:全服务模式,指该DMA若有请求,则会占用DMA总线,一直传输,期间若有其它DMA请求,只有等待传输计数TC为0,才会执行其它DMA请求
[26:24] : DMA外设请求源选择
[23] :
软件/硬件请求源选择 0:软件请求 1:硬件请求(还需要设置[26:24]来选择外设源)
[22] : 重新加载开关选项 为0即可
[21:20] : 传输数据大小 为00(8位)即可
[19:0] :
设置DMA传输的计数TC
6)DSTAT0状态寄存器
[21:20] : DMA状态 00:空闲 01:忙
[19:0] :
传输计数当前值CURR_TC 为0表示传输结束
7)DCSRC0当前源寄存器
[30:0] : 存放DMA当前的源基地址
8)DCDST0当前目标寄存器
[30:0] : 存放DMA当前的目的基地址
9)DMASKTRIG0触发屏蔽寄存器
[2] :
停止STOP 该位写1,立刻停止DMA当前的传输
[1] : DMA通道使能 0:关闭DMA的通道0(禁止DMA请求) 1:开启DMA的通道0(开启DMA请求)
[0] : 软件请求触发 1:表示启动一次软件请求DMA,只有DCONn[23]=0和DMASKTRIGn[1]=1才有效,DMA传输时,该位自动清0
1.3接下来就开始讲linux注册DMA中断
首先,DMA的每个通道只能有一个源- >目的,所以输入命令 cat /proc/interrupts ,找到DMA3中断未被使用
所以在linux中使用:
- request_irq(IRQ_DMA3, s3c_dma_irq, NULL, "s3c_dma", );// s3c_dma_irq:中断服务函数,这里注册DMA3中断服务函数
- //NULL:中断产生类型, 不需要,所以填NULL
- //1:表示中断时,传入中断函数的参数,本节不需要所以填1,切记不能填0,否则注册失败
2.接下来,我们便来写一个DMA的字符设备驱动
步骤如下:
- 1) 注册DMA中断,分配两个DMA缓冲区(源、目的)
- 2) 注册字符设备,并提供文件操作集合fops
- -> 2.1) 通过ioctl的cmd来判断是使用DMA启动两个地址之间的拷贝,还是直接两个地址之间的拷贝
- -> 2.2)若是DMA启动,则设置DMA的相关硬件,并启动DMA传输
2.1 所以,驱动代码如下所示:
- #include <linux/module.h>
- #include <linux/kernel.h>
- #include <linux/fs.h>
- #include <linux/init.h>
- #include <linux/delay.h>
- #include <linux/irq.h>
- #include <asm/irq.h>
- #include <asm/arch/regs-gpio.h>
- #include <asm/hardware.h>
- #include <asm/uaccess.h>
- #include <asm/io.h>
- #include <linux/dma-mapping.h>
- #define S3C_DMA_SIZE 512*1024 //DMA传输长度 512KB
- #define NORMAL_COPY 0 //两个地址之间的正常拷贝
- #define DMA_COPY 1 //两个地址之间的DMA拷贝
- /*函数声明*/
- static DECLARE_WAIT_QUEUE_HEAD(s3c_dma_queue); //声明等待队列
- static int s3c_dma_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long flags);
- /*
- * 定义中断事件标志
- * 0:进入等待队列 1:退出等待队列
- */
- static int s3c_dma_even=;
- static unsigned char *source_virt; //源虚拟地址
- static unsigned int source_phys; //源物理地址
- static unsigned char *dest_virt; //目的虚拟地址
- static unsigned int dest_phys; //目的虚拟地址
- /*DMA3寄存器*/
- struct S3c_dma3_regs{
- unsigned int disrc3 ; //0x4b0000c0
- unsigned int disrcc3 ;
- unsigned int didst3 ;
- unsigned int didstc3 ;
- unsigned int dcon3 ;
- unsigned int dstat3 ;
- unsigned int dcsrc3 ;
- unsigned int dcdst3 ;
- unsigned int dmasktrig3; //0x4b0000e0
- };
- static volatile struct S3c_dma3_regs *s3c_dma3_regs;
- /*字符设备操作*/
- static struct file_operations s3c_dma_fops={
- .owner = THIS_MODULE,
- .ioctl = s3c_dma_ioctl,
- };
- /*中断服务函数*/
- static irqreturn_t s3c_dma_irq (int irq, void *dev_id)
- {
- s3c_dma_even=; //退出等待队列
- wake_up_interruptible(&s3c_dma_queue); //唤醒 中断
- return IRQ_HANDLED;
- }
- /*ioctl函数*/
- static int s3c_dma_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long flags)
- {
- int i;
- memset(source_virt, 0xAA, S3C_DMA_SIZE);
- memset(dest_virt, 0x55, S3C_DMA_SIZE);
- switch(cmd)
- {
- case NORMAL_COPY: //正常拷贝
- for(i=;i<S3C_DMA_SIZE;i++)
- dest_virt[i] = source_virt[i];
- if(memcmp(dest_virt, source_virt, S3C_DMA_SIZE)==)
- {
- printk("NORMAL_COPY OK\n");
- return ;
- }
- else
- {
- printk("NORMAL_COPY ERROR\n");
- return -EAGAIN;
- }
- case DMA_COPY: //DMA拷贝
- s3c_dma_even=; //进入等待队列
- /*设置DMA寄存器,启动一次DMA传输 */
- /* 源的物理地址 */
- s3c_dma3_regs->disrc3 = source_phys;
- /* 源位于AHB总线, 源地址递增 */
- s3c_dma3_regs->disrcc3 = (<<) | (<<);
- /* 目的的物理地址 */
- s3c_dma3_regs->didst3 = dest_phys;
- /* 目的位于AHB总线, 目的地址递增 */
- s3c_dma3_regs->didstc3 = (<<) | (<<) | (<<);
- /* 使能中断,单个传输,软件触发, */
- s3c_dma3_regs->dcon3=(<<)|(<<)|(<<)|(<<)|(<<)|(<<)|(S3C_DMA_SIZE<<);
- //启动一次DMA传输
- s3c_dma3_regs->dmasktrig3 = (<<) | (<<);
- wait_event_interruptible(s3c_dma_queue, s3c_dma_even); //进入睡眠,等待DMA传输中断到来才退出
- if(memcmp(dest_virt, source_virt, S3C_DMA_SIZE)==)
- {
- printk("DMA_COPY OK\n");
- return ;
- }
- else
- {
- printk("DMA_COPY ERROR\n");
- return -EAGAIN;
- }
- break;
- }
- return ;
- }
- static unsigned int major;
- static struct class *cls;
- static int s3c_dma_init(void)
- {
- /*1.1 注册DMA3 中断 */
- if(request_irq(IRQ_DMA3, s3c_dma_irq,NULL, "s3c_dma",))
- {
- printk("Can't request_irq \"IRQ_DMA3\"!!!\n ");
- return -EBUSY;
- }
- /*1.2 分配两个DMA缓冲区(源、目的)*/
- source_virt=dma_alloc_writecombine(NULL,S3C_DMA_SIZE, &source_phys, GFP_KERNEL);
- if(source_virt==NULL)
- {
- printk("Can't dma_alloc \n ");
- return -ENOMEM;
- }
- dest_virt=dma_alloc_writecombine(NULL,S3C_DMA_SIZE, &dest_phys, GFP_KERNEL);
- if(dest_virt==NULL)
- {
- printk("Can't dma_alloc \n ");
- return -ENOMEM;
- }
- /*2.注册字符设备,并提供文件操作集合fops*/
- major=register_chrdev(, "s3c_dma",&s3c_dma_fops);
- cls= class_create(THIS_MODULE, "s3c_dma");
- class_device_create(cls, NULL,MKDEV(major,), NULL, "s3c_dma");
- s3c_dma3_regs=ioremap(0x4b0000c0, sizeof(struct S3c_dma3_regs));
- return ;
- }
- static void s3c_dma_exit(void)
- {
- iounmap(s3c_dma3_regs);
- class_device_destroy(cls, MKDEV(major,));
- class_destroy(cls);
- dma_free_writecombine(NULL, S3C_DMA_SIZE, dest_virt, dest_phys);
- dma_free_writecombine(NULL, S3C_DMA_SIZE, source_virt, source_phys);
- free_irq(IRQ_DMA3, );
- }
- module_init(s3c_dma_init);
- module_exit(s3c_dma_exit);
- MODULE_LICENSE("GPL");
2.2 应用测试程序如下所示:
- #include <stdio.h>
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <fcntl.h>
- #include <sys/ioctl.h>
- #include <string.h>
- /* ./dma_test NORMAL
- * ./dma_test DMA
- */
- #define NORMAL_COPY 0 //两个地址之间的正常拷贝
- #define DMA_COPY 1 //两个地址之间的DMA拷贝
- void print_usage(char *name)
- {
- printf("Usage:\n");
- printf("%s <NORMAL | DMA>\n", name);
- }
- int main(int argc, char **argv)
- {
- int fd,i=;
- if (argc != )
- {
- print_usage(argv[]);
- return -;
- }
- fd = open("/dev/s3c_dma", O_RDWR);
- if (fd < )
- {
- printf("can't open /dev/s3c_dma\n");
- return -;
- }
- if (strcmp(argv[], "NORMAL") == )
- {
- while (i--) //调用驱动的ioctl(),30次
- {
- ioctl(fd, NORMAL_COPY);
- }
- }
- else if (strcmp(argv[], "DMA") == )
- {
- while (i--) //调用驱动的ioctl(),30次
- {
- ioctl(fd, DMA_COPY);
- }
- }
- else
- {
- print_usage(argv[]);
- return -;
- }
- return ;
- }
3.测试运行
输入 ./dma_test NORMAL & ,使用CPU正常拷贝,可以发现占用了大部分资源,输入 ls 无反应:
输入./dma_test DMA & ,使用DMA拷贝,输入 ls 立马有反应,从而释放了CPU的压力:
32.Linux-2440下的DMA驱动(详解)的更多相关文章
- linux环境下/etc/hosts文件详解
linux环境下/etc/hosts文件详解 就没一个昵称能用关注 0.0632017.09.12 17:04:28字数 623阅读 27,096 介绍 hosts文件是linux系统中负责ip地址与 ...
- Linux下usb设备驱动详解
USB驱动分为两块,一块是USB的bus驱动,这个东西,Linux内核已经做好了,我们可以不管,我们只需要了解它的功能.形象的说,USB的bus驱动相当于铺出一条路来,让所有的信息都可以通过这条USB ...
- 【夯实Mysql基础】MySQL在Linux系统下配置文件及日志详解
本文地址 分享提纲: 1. 概述 2. 详解配置文件 3. 详解日志 1.概述 MySQL配置文件在Windows下叫my.ini,在MySQL的安装根目录下:在Linux下叫my.cnf,该文件位于 ...
- Linux操作系统下IPTables配置方法详解
如果你的IPTABLES基础知识还不了解,建议先去看看. 们来配置一个filter表的防火墙 1.查看本机关于IPTABLES的设置情况 [root@tp ~]# iptables -L -n Cha ...
- Linux系统下DNS主从配置详解
一.DNS概述DNS(Domain Name System),即域名系统.因特网上作为域名和IP地址相互映射的一个分布式数据库,能够使用户更方便的访问互联网,而不用去记住能够被机器直接读取的IP数串. ...
- linux系统下top命令参数详解
简介 top命令是Linux下常用的性能分析工具,能够实时显示系统中各个进程的资源占用状况,类似于Windows的任务管理器. top显示系统当前的进程和其他状况,是一个动态显示过程,即可以通过用户按 ...
- Linux系统下chkconfig命令使用详解
chkconfig命令可以用来检查.设置系统的各种服务 使用语法:chkconfig [--add][--del][--list][系统服务] 或 chkconfig [--level <等级代 ...
- linux根目录下文件夹概览详解
/ 根目录 /bin 存放必要的命令 /boot 存放内核以及启动所需的文件等 /dev 存放设备文件 /etc 存放系统的配置文件 /home 用户文件的主目录,用户数据存放在其主目录中 /lib ...
- Linux环境下部署svn服务详解
说明 环境: 操作系统:centos 8.0 IP:39.100.228.13 安装 用ROOT账号登录,在控制台执行以下命令,一直默认安装就好可以了. [root@localhost ~]#yum ...
随机推荐
- 超全面!这可能是最全面的 jQuery 知识总结
个人建议:学习 jQuery 前先掌握基本的 JavaScrpit 语法,特别是对函数要掌握,jQuery 基本上是使用函数. jQuery 简介 jQuery 是一个轻量级 JavaScript 库 ...
- ios自定义数字键盘
因为项目又一个提现的功能,textfiled文本框输入需要弹出数字键盘,首先想到的就是设置textfiled的keyboardType为numberPad,此时你会看到如下的效果: 但是很遗憾这样 ...
- C# TextBlock 上标
我需要做一个函数,显示 ,但是看起来用 TextBlock 做的不好看. 我用 WPF 写的上标看起来不好看,但是最后有了一个简单方法让他好看. 本文告诉大家如何做一个好看的上标. 一开始做的方法: ...
- Linux 进程状态 概念 Process State Definition
From : http://www.linfo.org/process_state.html 进程状态是指在进程描述符中状态位的值. 进程,也可被称为任务,是指一个程序运行的实例. 一个进程描述符是一 ...
- php的序列化和反序列化有什么好处?
序列化是将变量转换为可保存或传输的字符串的过程:反序列化就是在适当的时候把这个字符串再转化成原来的变量使用.这两个过程结合起来,可以轻松地存储和传输数据,使程序更具维护性. PHP 中的序列化和反序列 ...
- FreeType in OpenCASCADE
FreeType in OpenCASCADE eryar@163.com Abstract. FreeType is required for text display in the 3D view ...
- Spring 组成
Spring 主要由 Bean Context Core 三个组件组成 Bean 组件是整个 Spring 核心,Spring 通过管理 Bean 组件提供服务 Context 是 Bean 组件容器 ...
- Shell中bash的特性小结
Shell: 用户与操作系统之间完成交互式操作的一个接口程序,为用户提供简化了的操作:上世纪的70年代中期在贝尔实验室,Bourne位Unix开发了一个shell程序Bourne Shell,简称sh ...
- gsoap入门实例
环境VS2008,gsoap_2.8,win7 实例场景:在客户端输入一个字符串,然后传递给服务端计算字符串长度并返回给客户端(附加一些加减乘除法的实现): 将..\gsoap-2.8\gsoap\b ...
- Android 开发笔记___AutoComplateTextView__自动完成文本框
原理:EdtText结合监听器TextWatcher与下拉框spinner,一旦监控到EditText的文本发生变化,就自动弹出适配好的文字下拉内容. 属性以及设置方法: XML中的属性 代码中 说明 ...