32.Linux-2440下的DMA驱动(详解)

来源:互联网 发布:欧莱雅数据库营销案例 编辑:程序博客网 时间:2024/06/15 07:25

学了这么多驱动,不难推出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缓存以及禁止写入缓冲区*/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", 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 所以,驱动代码如下所示:

复制代码
#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=0;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=1;                             //退出等待队列    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=0;i<S3C_DMA_SIZE;i++)                 dest_virt[i] =  source_virt[i];             if(memcmp(dest_virt, source_virt, S3C_DMA_SIZE)==0)           {         printk("NORMAL_COPY OK\n");                return 0;         }         else        {         printk("NORMAL_COPY ERROR\n");               return -EAGAIN;        }                             case DMA_COPY:                               //DMA拷贝        s3c_dma_even=0;     //进入等待队列                /*设置DMA寄存器,启动一次DMA传输 */        /* 源的物理地址 */        s3c_dma3_regs->disrc3      = source_phys;              /* 源位于AHB总线, 源地址递增 */          s3c_dma3_regs->disrcc3     = (0<<1) | (0<<0);        /* 目的的物理地址 */        s3c_dma3_regs->didst3      = dest_phys;              /* 目的位于AHB总线, 目的地址递增 */        s3c_dma3_regs->didstc3     = (0<<2) | (0<<1) | (0<<0);             /* 使能中断,单个传输,软件触发, */        s3c_dma3_regs->dcon3=(1<<30)|(1<<29)|(0<<28)|(1<<27)|(0<<23)|(0<<20)|(S3C_DMA_SIZE<<0);          //启动一次DMA传输        s3c_dma3_regs->dmasktrig3  = (1<<1) | (1<<0);                     wait_event_interruptible(s3c_dma_queue, s3c_dma_even);    //进入睡眠,等待DMA传输中断到来才退出                if(memcmp(dest_virt, source_virt, S3C_DMA_SIZE)==0)        {         printk("DMA_COPY OK\n");                return 0;         }        else        {       printk("DMA_COPY ERROR\n");             return -EAGAIN;           }              break;    }    return 0;}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",1))     {        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(0, "s3c_dma",&s3c_dma_fops);    cls= class_create(THIS_MODULE, "s3c_dma");    class_device_create(cls, NULL,MKDEV(major,0), NULL, "s3c_dma");    s3c_dma3_regs=ioremap(0x4b0000c0, sizeof(struct S3c_dma3_regs));        return 0;  }static void s3c_dma_exit(void){    iounmap(s3c_dma3_regs);        class_device_destroy(cls, MKDEV(major,0));    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, 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 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=30;         if (argc != 2)    {        print_usage(argv[0]);        return -1;    }    fd = open("/dev/s3c_dma", O_RDWR);    if (fd < 0)    {        printf("can't open /dev/s3c_dma\n");        return -1;    }    if (strcmp(argv[1], "NORMAL") == 0)    {        while (i--)                //调用驱动的ioctl(),30次        {            ioctl(fd, NORMAL_COPY);        }    }    else if (strcmp(argv[1], "DMA") == 0)    {        while (i--)                //调用驱动的ioctl(),30次                {            ioctl(fd, DMA_COPY);        }    }    else    {        print_usage(argv[0]);        return -1;    }    return 0;     }
复制代码

3.测试运行

输入 ./dma_test NORMAL & ,使用CPU正常拷贝,可以发现占用了大部分资源,输入 ls 无反应:

 

 

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

 

 

 

 

 


版权声明:本文为博主原创文章,未经博主允许不得转载。

原创粉丝点击