历史上的今天
返回首页

历史上的今天

今天是:2025年07月21日(星期一)

正在发生

2021年07月21日 | 24.Linux-2440下的DMA驱动

2021-07-21 来源:eefocus

1.DMA(Direct Memory Access)

  即直接存储器访问, DMA 传输方式无需 CPU 直接控制传输,通过硬件为 RAM 、I/O 设备开辟一条直接传送数据的通路,能使 CPU 的效率大为提高。


  学了这么多驱动,不难推出DMA的编写套路:


  1)注册DMA中断,分配缓冲区

  2)注册字符设备,并提供文件操作集合fops

   -> 2.1)file_operations里设置DMA硬件相关操作,来启动DMA

 由于我们是用字符设备的测试方法测试的,而本例子只是用两个地址之间的拷贝来演示DMA的作用,所以采用字符设备方式编写


2.驱动编写之前,先来讲如何分配释放缓冲区、DMA相关寄存器介绍、使用DMA中断

2.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:分配出来的内存参数,标志定义在,常用标志如下:

        //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上,因为分配出来的内存在物理地址上是不连续的(在虚拟地址上连续).


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

(PS:实际这些DMA相关的寄存器,在linux内核中三星已封装好了,可以直接调用,不过非常麻烦,还不如直接设置寄存器,可以参考: http://blog.csdn.net/mirkerson/article/details/6632273)


2.2.1 2440支持4个通道的DMA控制器

 其中4个通道的DMA外设请求源,如下图所示(通过DCONn寄存器的[26:24]来设置)

在这里插入图片描述

(PS:如果请求源是系统总线上的,就只需要设置DCONn寄存器的[23]=0即可)


2.2.2 且每个通道都可以处理以下4种情况:

 1) 源和目标都在系统总线上(比如:两个物理内存地址)

 2) 当目标在外设总线上时,源在系统总线上(外设指:串口,定时器,I2C,I2S等)

 3) 当目标在系统总线上时,源在外设总线上

 4) 源和目标都在外设总线上


2.2.3 DMA有两种工作模式(通过DCONn寄存器的[28]来设置)

  查询模式:


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


  握手模式:


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

在这里插入图片描述

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

  单元传输:


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


  突发4传输:


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

在这里插入图片描述

2.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


2.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,否则注册失败


3.接下来,我们便来写一个DMA的字符设备驱动

步骤如下:


  1) 注册DMA中断,分配两个DMA缓冲区(源、目的)

  2) 注册字符设备,并提供文件操作集合fops

  -> 2.1) 通过ioctl的cmd来判断是使用DMA启动两个地址之间的拷贝,还是直接两个地址之间的拷贝

  -> 2.2)若是DMA启动,则设置DMA的相关硬件,并启动DMA传输


#include

#include

#include

#include

#include

#include    

#include

#include

#include

#include

#include

#include


#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                 dest_virt[i] =  source_virt[i];


             if(memcmp(dest_virt, source_virt, S3C_DMA_SIZE)==0)

           {

         printk("NORMAL_COPY OKn");

                return 0;

         }

         else

        {

         printk("NORMAL_COPY ERRORn");

               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 OKn");

推荐阅读

史海拾趣

GETEDZ ( HVGT)公司的发展小趣事
根据电路的需要选择合适的接触器、继电器等电器元件,确保元件的性能和质量满足要求。
G-Mag Usa公司的发展小趣事

进入21世纪后,G-Mag意识到单一产品线难以满足市场多元化需求,于是开始实施并购扩张战略。2005年,G-Mag成功收购了国内一家领先的电子元器件制造商,这次收购不仅增强了G-Mag在供应链上的控制力,还为其带来了丰富的产品线和技术储备。随后几年,G-Mag又陆续完成了对多家在传感器、无线通信等领域具有优势企业的并购,逐步构建起了一个覆盖电子产业链上下游的庞大帝国。通过并购,G-Mag不仅实现了业务的快速增长,还巩固了其在电子行业中的领先地位。

FERYSTER公司的发展小趣事

在全球环保意识日益增强的背景下,FERYSTER公司积极响应环保号召,推动企业的绿色转型。公司采用环保材料替代传统材料,优化生产工艺减少能源消耗和废弃物排放。此外,公司还积极推广绿色电子产品,引导消费者关注环保问题。这些努力不仅提升了公司的社会形象,也为公司带来了更多的商业机会。

ADPOW公司的发展小趣事

ADPOW公司成立于XXXX年,初期是一个小型电子技术研发团队。创始人凭借对电子技术的深刻理解和市场需求的敏锐洞察,带领团队专注于电源管理技术的研发。经过数年的不懈努力,团队成功开发出一款高效、稳定的电源管理芯片,这一技术突破为ADPOW公司后续的发展奠定了坚实的基础。

Fagor Electrónica公司的发展小趣事

如今,Fagor Electrónica已经成为电子和数字领域的领军企业之一。展望未来,公司将继续秉承创新驱动的发展理念,加大在人工智能、物联网等新兴领域的投入。同时,Fagor Electrónica还将积极参与全球市场竞争,拓展更广阔的市场空间。相信在不久的将来,Fagor Electrónica将会创造更加辉煌的业绩。

Genesys Logic公司的发展小趣事

90年代初期,“General Microcircuits”敏锐地捕捉到市场对定制化半导体解决方案的需求增长。公司迅速调整战略,成立专门的定制化服务部门,为客户提供从需求分析、设计开发到生产测试的全流程定制化服务。这种以客户为中心的服务模式赢得了众多客户的青睐,尤其是在通信、汽车电子等高端应用领域,公司凭借定制化的高性能芯片解决方案占据了重要的市场份额。

问答坊 | AI 解惑

LabVIEW数据库连接工具包(Database Connectivity Toolkit)版本:1.0.132.0

LabVIEW数据库连接工具包(Database Connectivity Toolkit)简介: * 完整的SQL功能 * 与本地或远程数据库可直接交互式操作 * 高级而易用的功能适用于常见的数据库操作 * 使用Microsoft ADO技术能与大多数常用数据库连接 NI LabVIEW数据库连接(D ...…

查看全部问答>

请大家都来分析一下这个经典的电路(7805扩流)

分析一下这个经典的电源电路(7805扩流) 下图为在非常流行的经典电路上做小许改动的电路图. 电路目的: 1)+24V 转换为+5V +/-5% 2)可提供+2A以上的电流. 主要元件: TIP32C (ST)           L7805CV (ST) 图中 ...…

查看全部问答>

ARM 中断指令

为什么在 Thumb 状态下调用 SVC 时 指令的地址在 lr–2,而不在 lr–4。 lr是不是随着PC的改变而改变啊~~ …

查看全部问答>

平模式下的异常可恢复性归类?

一道凹凸科技的笔试题,完全不懂题目的意思…

查看全部问答>

msp430x24x系列单片机

我用2417,以及149.对于他们的时钟选择以及使用,感觉很模糊,没有清晰的认识,那位能给我一个比较详细的讲解,先谢过了…

查看全部问答>

关于MPXY8300

见有些文章用到MPXY8300用于无线数据发射,但对着这个片子不是特别了解,想各位给点建议?它和哪个单片机组合好些呢、、、在此谢过了…

查看全部问答>

【玩转C2000 Launchpad】TFT LCD

为搞这个LCD,耽误了不少时间,密密麻麻的接线,焊了四次。第一个屏背光不亮,扔了;第二、第三个屏搞了一阵子点不亮,怀疑DATASHEET对不上号,拆了;第四个屏是现在的这个,开始时也是点不亮,由于疏忽使编程有误,但没没有怀疑资料问题,只是多次 ...…

查看全部问答>

关于BB-Black烧写问题,求救!!

BB-BLACK 板子想通过串口“烧写”SPL和UBOOT到emmc中,启动u-boot之后希望通过TFTP将kernel“烧写”到板子的emmc中,而不是加载到RAM中,最终是要用nor flash的,但开发板上是emmc,所以想先调通emmc,另外也不知道此方法是否适用于nor flash,请有 ...…

查看全部问答>

官方Altium Designer 封装库 大家可以收藏一下

官方Altium Designer 封装库 大家可以收藏一下 特别是pcb文件夹,包含几乎全部可能使用到封装库,快去下载吧!!!这是官方的http://wiki.altium.com/display/ADOH/Download+Libraries …

查看全部问答>