https://www.cnblogs.com/lifexy/p/7880737.html

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)dma_alloc_writecombine()函数:

/* 该函数只禁止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)dma_alloc_coherent()函数:

/*该函数禁止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()函数:

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()函数:

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_coherent(),只不过是用了#define重命名而已。)

而我们之前用的内存分配kmalloc()函数,是不能用在DMA上,因为分配出来的内存可能在物理地址上不连续的,因为DMA没有那么智能,不知道一会跑去那里,一会跑去那里。

1.2 那么2440开发板如何来启动DMA,先来看一下2440的DMA寄存器

(PS:实际这些DMA相关的寄存器,在linux内核中三星公司已封装好了,可以直接调用,不过非常麻烦,还不如直接设置寄存器,可以参考:https://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寄存器的[31]来设置)

查询模式:

当DMA请求XnXDREQ为低电平时,则DMA会一直传输数据,直到DMA请求拉高,才停止

握手模式:

当DMA请求XnXDREQ有下降沿触发时,则DMA会传输一次数据

1.2.4 DMA有两种传输模式(通过DCONn寄存器的[28]来设置)

单元传输:

指传输过程中,每执行一次,则读1次,写1次(如上图所示)

突发4传输:

指传输过程中,每执行一次,则读4次,然后写4次(如下图所示)

1.2.5 2440中的DMA寄存器如下图所示:

共有4个通道的寄存器,且每个通道的寄存器内容都一致,所以我们以DMA通道0为例:

1)DISR0 初始源寄存器(DMA Initial Source Register)

bit[30:0]:存放DMA源的基地址(物理地址)

2)DISRCC0 初始源控制寄存器(DMA Initial Source Control Register)

bit[1]:源位置选择,            0:源在系统总线上                    1:源在外设总线上

bit[0]:源地址增加的选择,0:传输时,源地址自动增加    1:源地址固定

3)DIDST0 初始目标寄存器(DMA Initial Destination Register)

bit[30:0]:设置DMA目的的基地址(物理地址)

4)DIDSTC0 初始目标控制寄存器(DMA Initial Destination Control Register)

bit[2]:中断时间选择,    0:当DMA传输计数=0,立即发生中断   1:执行完自动加载后再发送中断(也就是计数为0,然后重新加载计数值)

bit[1]:目的位置选择,    0:目的在系统总线上                                 1:目的在外设总线上

bit[0]:目的地址选择,    0:传输时目的地址自动增加                     1:目的地址固定

5)DCON0 控制寄存器(DMA Control Register)

bit[31]:工作模式选择,0:查询模式    1:握手模式(当源出于外设时,尽量选择握手模式)

bit[30]:中断请求(DREQ)/中断回应(DACK)的同步时钟选择,0:PCLK同步   1:HCLK同步

(PS:如果有设备在HCLK上,该为应当设为1,比如:(SDRAM)内存数组,反之当这些设备在PCLK上,应当设为0,比如:ADC,IIS,I2C,UART)

bit[29]:DMA传输计数中断使能/禁止    0:禁止中断   1:当传输完成后,产生中断

bit[28]:传输模式选择,                          0:单元传输   1:突发4传输

bit[27]:传输服务模式

0:单服务模式,比如:有2个DMA请求,它们会被顺序执行一次(单元传输/突发4传输)后停止,然后知道下一次DMA请求,再重新开始另一次循环。

1:全服务模式,指DMA若有请求,则会占用DMA总线,一直传输,期间若有其他DMA请求,只有等待传输计数TC为0,才会执行其他DMA请求

bit[26:24]:DMA外设请求源选择(我们是用软件触发,没有用到)

bit[23]:软件/硬件请求源选择,    0:软件请求     1:硬件请求(还需要设置[26:24]来选择外设源)

bit[22]:重新加载开关选项,不需要,为0即可

bit[21:20]:传输数据大小,为了简单,一个字节(8位)即可

bit[19:0]:设置DMA传输的计数TC(BUF_SIZE 2k大小)

6)DSTAT0 状态寄存器(DMA Status Register)

bit[21:20]:DMA状态,00:空闲,01:忙

bit[19:0]:传输计数当前值CURR_TC   为0表示传输结束

7)DCSRC0 当前源寄存器(DMA Current Source Register)

bit[30:0]:存放DMA当前的源基地址

8)DCDST0 当前目标寄存器(Current Destination Register)

bit[30:0]:存放DMA当前的目的基地址

9)DMASKTRIG0 触发屏蔽寄存器

bit[2]:停止STOP   该为写1,立刻停止DMA当前的传输

bit[1]:DMA通道使能    0:关闭DMA的通道0(禁止DMA请求) 1:开启DMA的通道0(开启DMA请求)

bit[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", 1);// 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 所以,驱动代码如下所示:dma.c

#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/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
#include <linux/poll.h>
#include <linux/dma-mapping.h>#define MEM_CPY_NO_DMA 0    //复制内存不使用DMA
#define MEM_CPY_DMA    1    //复制内存使用DMA#define BUF_SIZE (512*1024)  //大小512k//DMA各通道的起始地址
#define DMA0_BASE_ADDR  0x4B000000
#define DMA1_BASE_ADDR  0x4B000040
#define DMA2_BASE_ADDR  0x4B000080
#define DMA3_BASE_ADDR  0x4B0000C0//DMA通道寄存器
struct s3c_dma_regs {unsigned long disrc;unsigned long disrcc;unsigned long didst;unsigned long didstc;unsigned long dcon;unsigned long dstat;unsigned long dcsrc;unsigned long dcdst;unsigned long dmasktrig;
};static int major = 0;static char *src;       //源
static u32 src_phys;    //源的物理地址static char *dst;       //目的
static u32 dst_phys;    //目的的物理地址//为了自动创建设备节点,创建一个类
static struct class *cls;static volatile struct s3c_dma_regs *dma_regs;//结构体指针,加上volatile,免得被优化//定义一个等待队列
static DECLARE_WAIT_QUEUE_HEAD(dma_waitq);
/* 中断时间标志,中断服务程序将它置1,ioctl将它清0 */
static volatile int ev_dma = 0;static int s3c_dma_ioctl (struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{int i;//修改源和目的的地址memset(src, 0xAA, BUF_SIZE);memset(dst, 0x55, BUF_SIZE);switch (cmd){case MEM_CPY_NO_DMA ://复制内存不使用DMA{for(i = 0; i < BUF_SIZE; i++)dst[i] = src[i];//把源的数据拷到目的//拷贝完后,进行比较,等于0表示相等if (memcmp(src, dst, BUF_SIZE) == 0){printk("MEM_CPY_NO_DMA OK\n");}else{printk("MEM_CPY_NO_DMA ERROR\n");}break;}case MEM_CPY_DMA ://复制内存使用DMA{ev_dma = 0;/* 把源,目的,长度 告诉DMA */dma_regs->disrc    = src_phys;        /* 源的物理地址 */dma_regs->disrcc   = (0<<1) | (0<<0); /* 源位于AHB总线,源地址递增 */dma_regs->didst    = dst_phys;        /* 目的的物理地址 */dma_regs->didstc   = (0<<2) | (0<<1) | (0<<0); /* 中断,目的位 于AHB总线,目的地址递增 */dma_regs->dcon     = (1<<30) | (1<<29) | (0<<28) | (1<<27) | (0<<23) | (0<<20) | (BUF_SIZE<<0);        /* 同步信号,使能中断,单个传输,软件触发.数据传输大小为1字节, *//* 启动DMA */dma_regs->dmasktrig = (1<<1) | (1<<0);/* 如何知道DMA什么时候完成?dma返回一个中断给cpu(现在执行的程序在cpu里) *//* 启动DMA后,如果ev_dma=0,就休眠, */wait_event_interruptible(dma_waitq, ev_dma);//拷贝完后,进行比较,等于0表示相等if (memcmp(src, dst, BUF_SIZE) == 0){printk("MEM_CPY_DMA OK\n");}else{printk("MEM_CPY_DMA ERROR\n");}break;}}return 0;
}static struct file_operations dma_fops = {.owner = THIS_MODULE,.ioctl = s3c_dma_ioctl,
};//中断处理函数,唤醒
static irqreturn_t s3c_dma_irq(int irq, void *devid)
{/* 唤醒 */ev_dma = 1;wake_up_interruptible(&dma_waitq); //唤醒应用程序return IRQ_HANDLED;
}static int s3c_dma_init(void)
{//注册一个中断,用来dma完成以后,返回一个中断给cpuif (request_irq(IRQ_DMA3, s3c_dma_irq, 0, "s3c_dma", 1)){printk("can't request_irq for DMA\n");return -EBUSY;}/* 分配SRC源, DST目的对应的缓冲区           ,不能用kmalloc,分配的物理地址是不连续的 */src = dma_alloc_writecombine(NULL, BUF_SIZE, &src_phys, GFP_KERNEL);//返回虚拟地址,第三个参数返回物理地址if (NULL == src){printk("can't alloc buffer for src\n");free_irq(IRQ_DMA3, 1);return -ENOMEM;}dst = dma_alloc_writecombine(NULL, BUF_SIZE, &dst_phys, GFP_KERNEL);//返回虚拟地址,第三个参数返回物理地址if (NULL == dst){       free_irq(IRQ_DMA3, 1);//释放掉,免得内存泄漏(内存泄漏,就是内存分配了,没有释放,又没有人用它)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);/* 为了自动创建设备节点,创建类class */cls = class_create(THIS_MODULE, "s3c_dma");/* 在这个类下面再创建一个设备,mdev就会根据信息来创建/dev/dma设备节点 */class_device_create(cls, NULL, MKDEV(major, 0), NULL, "dma");dma_regs = ioremap(DMA3_BASE_ADDR, sizeof(struct s3c_dma_regs));return 0;
}static void s3c_dma_exit(void)
{iounmap(dma_regs);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);free_irq(IRQ_DMA3, 1);
}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 nodma* ./dma_test dma*/
#define MEM_CPY_NO_DMA 0    //复制内存不使用DMA
#define MEM_CPY_DMA    1    //复制内存使用DMAvoid 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;
}

3、测试运行

测试时,如果用默认的uboot启动,使用不了dma。要使用上一节的uboot,或其他uboot启动,才能使用dma。

输入./dma_test nodma &,使用CPU正常拷贝,可以发现占用了大部分资源,输入ls命令,要等好久才有反应:

输入./dma_test dma &,使用DMA拷贝,输入ls命令立马有反应,从而释放了CPU的压力:

S3C2440 DMA驱动程序编写及测试(三十二)相关推荐

  1. axi dma 寄存器配置_FPGA Xilinx Zynq 系列(三十二)AXI 接口

    大侠好,欢迎来到FPGA技术江湖,江湖偌大,相见即是缘分.大侠可以关注FPGA技术江湖,在"闯荡江湖"."行侠仗义"栏里获取其他感兴趣的资源,或者一起煮酒言欢. ...

  2. 【Visual C++】游戏开发笔记三十二 浅墨DirectX提高班之一 DirectX大局观认知篇

    本系列文章由zhmxy555(毛星云)编写,转载请注明出处. 文章链接:  http://blog.csdn.net/zhmxy555/article/details/8172615 作者:毛星云(浅 ...

  3. 静态树表查找算法及C语言实现,数据结构算法C语言实现(三十二)--- 9.1静态查找表...

    一.简述 静态查找表又分为顺序表.有序表.静态树表和索引表.以下只是算法的简单实现及测试,不涉及性能分析. 二.头文件 /** author:zhaoyu date:2016-7-12 */ #inc ...

  4. 程序员编程艺术第三十二~三十三章:最小操作数,木块砌墙问题

    第三十二~三十三章:最小操作数,木块砌墙问题 作者:July.caopengcs.红色标记.致谢:fuwutu.demo. 时间:二零一三年八月十二日 题记 再过一两月,便又到了每年的九月十月校招高峰 ...

  5. Kali Linux Web 渗透测试— 第十二课-websploit

    Kali Linux Web 渗透测试- 第十二课-websploit 文/玄魂 目录 Kali Linux Web 渗透测试- 第十二课-websploit..................... ...

  6. ASP 三十二条精华代码

    整理收藏: ASP 三十二条精华代码 1. οncοntextmenu="window.event.returnvalue=false" 将彻底屏蔽鼠标右键 <table b ...

  7. tensorflow学习笔记(三十二):conv2d_transpose (解卷积)

    tensorflow学习笔记(三十二):conv2d_transpose ("解卷积") deconv解卷积,实际是叫做conv_transpose, conv_transpose ...

  8. OpenCV学习笔记(三十一)——让demo在他人电脑跑起来 OpenCV学习笔记(三十二)——制作静态库的demo,没有dll也能hold住 OpenCV学习笔记(三十三)——用haar特征训练自己

    OpenCV学习笔记(三十一)--让demo在他人电脑跑起来 这一节的内容感觉比较土鳖.这从来就是一个老生常谈的问题.学MFC的时候就知道这个事情了,那时候记得老师强调多次,如果写的demo想在人家那 ...

  9. python建站部署_SpringBoot入门建站全系列(三十二)接入xxl-job分布式任务调度平台...

    SpringBoot入门建站全系列(三十二)接入xxl-job分布式任务调度平台 一.概述 XXL-JOB是一个轻量级分布式任务调度平台,其核心设计目标是开发迅速.学习简单.轻量级.易扩展.现已开放源 ...

最新文章

  1. linux at自动挂化,linux的at定时任务的使用
  2. 工具SSHSecure连接远程服务器步骤
  3. java栈实现简易计算器算法
  4. 第三次学JAVA再学不好就吃翔(part13)--基础语法之while循环语句
  5. 2020年上半年小程序互联网发展报告
  6. 苹果cms v10模板 蓝色简洁大气手机端模板
  7. python os模块下载_python os模块
  8. leetcode刷题:1.无重复字符的最长字串
  9. ubuntu系统使用命令行播放MP3歌曲
  10. 由H264软编码可以看出,电脑的性能远远超过手机
  11. 电子科技大学研究生图论课程
  12. 关于我在《大话5G》这本书里学到了什么——5G和物联网不得不说的关系
  13. 计算机专业及课程设置,清华及各大高校公布计算机专业培养方案课程
  14. Oracle中nlssort()函数排序功能
  15. 扫描仪显示没有服务器,扫描仪安装好了,点击显示寻找扫描仪怎么显示未找出扫描仪...
  16. 专门查英语单词的软件_有什么软件可以查英语单词
  17. 喂,恶臭青年,你还想继续单身?今天特别福利来袭,出来挨打!
  18. Java的学习之路Day08
  19. 用X64 Native tools command promt for vs安装ROS
  20. 计算机专业要不要读研?

热门文章

  1. 获取access_token错误 40164
  2. resnet18实现猫狗图片的分类
  3. 适配iOS10.2 https双向认证
  4. 探索 PlanetIX:解读区块链游戏运营的奥秘
  5. Unity3D 阴影和深度纹理总结
  6. 拆装计算机主机,怎样快速拆卸电脑主机
  7. lombok插件介绍
  8. Java 两数相除
  9. RPA智能时代的采购流程革新
  10. Apifox vs Eolink,国内 Api 工具哪家强?