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驱动(详解)的更多相关文章

  1. linux环境下/etc/hosts文件详解

    linux环境下/etc/hosts文件详解 就没一个昵称能用关注 0.0632017.09.12 17:04:28字数 623阅读 27,096 介绍 hosts文件是linux系统中负责ip地址与 ...

  2. Linux下usb设备驱动详解

    USB驱动分为两块,一块是USB的bus驱动,这个东西,Linux内核已经做好了,我们可以不管,我们只需要了解它的功能.形象的说,USB的bus驱动相当于铺出一条路来,让所有的信息都可以通过这条USB ...

  3. 【夯实Mysql基础】MySQL在Linux系统下配置文件及日志详解

    本文地址 分享提纲: 1. 概述 2. 详解配置文件 3. 详解日志 1.概述 MySQL配置文件在Windows下叫my.ini,在MySQL的安装根目录下:在Linux下叫my.cnf,该文件位于 ...

  4. Linux操作系统下IPTables配置方法详解

    如果你的IPTABLES基础知识还不了解,建议先去看看. 们来配置一个filter表的防火墙 1.查看本机关于IPTABLES的设置情况 [root@tp ~]# iptables -L -n Cha ...

  5. Linux系统下DNS主从配置详解

    一.DNS概述DNS(Domain Name System),即域名系统.因特网上作为域名和IP地址相互映射的一个分布式数据库,能够使用户更方便的访问互联网,而不用去记住能够被机器直接读取的IP数串. ...

  6. linux系统下top命令参数详解

    简介 top命令是Linux下常用的性能分析工具,能够实时显示系统中各个进程的资源占用状况,类似于Windows的任务管理器. top显示系统当前的进程和其他状况,是一个动态显示过程,即可以通过用户按 ...

  7. Linux系统下chkconfig命令使用详解

    chkconfig命令可以用来检查.设置系统的各种服务 使用语法:chkconfig [--add][--del][--list][系统服务] 或 chkconfig [--level <等级代 ...

  8. linux根目录下文件夹概览详解

    / 根目录 /bin 存放必要的命令 /boot 存放内核以及启动所需的文件等 /dev 存放设备文件 /etc 存放系统的配置文件 /home 用户文件的主目录,用户数据存放在其主目录中 /lib ...

  9. Linux环境下部署svn服务详解

    说明 环境: 操作系统:centos 8.0 IP:39.100.228.13 安装 用ROOT账号登录,在控制台执行以下命令,一直默认安装就好可以了. [root@localhost ~]#yum ...

随机推荐

  1. 使用BigQuery分析GitHub上的C#代码

    一年多以前,Google 在GitHub中提供了BigQuery用于查询的GitHub上的开源代码(open source code on GitHub available for querying) ...

  2. c语言入门

    c 语言现在是一门很流行的语言,它介于汇编语言和高级语言之间,我认为 它属于中级语言,如c语言 的指针 ,位操作符,等,因为接近于汇编语言,c语言的执行代码效率很高 现在大多数的系统 如unix,和l ...

  3. JavaFX引入资源问题

    描述 - 使用javafx 引入资源的时候 抛出异常 在swing引入资源 采取相对路径即可,而javafx不是 ImageView imageNode = (ImageView) root.look ...

  4. 各大公司Java后端开发面试题总结

    ThreadLocal(线程变量副本)Synchronized实现内存共享,ThreadLocal为每个线程维护一个本地变量.采用空间换时间,它用于线程间的数据隔离,为每一个使用该变量的线程提供一个副 ...

  5. java分页算法,传入当前pageIndex,pageSise,dataTotal可计算出页面上显示的页码,和是否启动上一页下一页

    public class CalculationPage { private Boolean showStartPagerDot; private Boolean showEndPagerDot; p ...

  6. LeetCode 598. Range Addition II (范围加法之二)

    Given an m * n matrix M initialized with all 0's and several update operations. Operations are repre ...

  7. Model Representation and Cost Function

    Model Representation To establish notation for future use, we’ll use x(i) to denote the “input” vari ...

  8. JS框架设计读书笔记之-动画

    基础概念 CSS样式可分为两种,一种值接近无限的集合(color,width),一种值只有几种(display),可以进行计算的样式,产生了动画效果.\ 1. 动画的第一步是获得元素的精确样式值. 2 ...

  9. 学习UML --用例图

    用例图用于描述系统提供的系列功能.使用用例图的主要目的是帮助开发团队以一种可视化的方式理解系统的功能需求.用例图对系统的实现不做任何说明,仅仅是系统功能的描述. 用例图主要在需求分析阶段使用,用于描述 ...

  10. Exclusive-OR(带权并查集)

    Exclusive-OR Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Total ...