zoukankan      html  css  js  c++  java
  • DMA设计


    title: DMA设计
    tags: linux
    date: 2019年1月5日 17:27:08
    toc: true

    DMA设计

    DMA框架

    一个简单的DMA框图如下DREQ→HOLD→HLDA→DACK

    mark

    DMAC的一些必备特性:

    • 能发出地址信息,对存储器寻址,并修改地址指针,DMAC内部必须有能自动加1或减1的地址寄存
    • 能决定传送的字节数,并能判断DMA传送是否结束。DMA内部必须有能自动减1的字计数寄存器,计数结束产生终止计数信号;
    • 能发出DMA结束信号,释放总线,使CPU恢复总线控制权;
    • 能发出读、写控制信号,包括存储器访问信号和I/O访问信号。DMAC内部必须有时序和读写控制逻辑。

    信号线如下

    • DRQ:DMA请求信号。是外设向DMA控制器提出要求DMA操作的申请信号。
    • DACK:DMA响应信号。是DMA控制器向提出DMA请求的外设表示已收到请求和正进行处理的信号。
    • HOLD:总线请求信号。是DMA控制器向CPU要求让出总线的请求信号。
    • HLDA:总线响应信号,是CPU向DMA控制器表示允许总线请求的应答信号。

    流程顺序

    1. 当外设有DMA需求,并且准备就绪,就向DMAC控制器发出DMA请求信号DREQ
    2. DMAC接到DMA请求信号后向CPU发出总线请求信号HRQ。该信号连接到CPU的HOLD信号。
    3. CPU接到总线请求信号以后,如果允许DMA传输,则会在当前总线周期结束后,发出DMA响应信号HLDA。一方面CPU将控制总线、数据总线和地址总线置高阻态,即放弃对总线的控制权;另一方面CPU将有效的HLDA信号送给DMAC,通知DMAC,CPU已经放弃了对总线的控制权。
    4. DMAC获得对总线的控制权,并且向外设送出DMAC的应答信号DACK,通知外设可以开始进行DMA传输了。
    5. DMAC向存储器发送地址信号和向存储器及外设发出读/写控制信号,控制数据按初始化设定的方向传送,实现外设与内存的数据传输。
    6. 数据全部传输结束后,DMAC向CPU发HOLD信号,要求撤销总线请求信号。CPU收到该信号以后,使HLDA无效,同时收回对总线的控制权。

    DMA控制器的基本组成

    • 内存地址计数器:用于存放内存中要交换的数据的地址。
    • 字计数器:用于记录传送数据块的长度(多少字数)。
    • 数据缓冲寄存器:用于暂存每次传送的数据(一个字)。
    • "DMA请求"标志:每当设备准备好一个数据字后给出一个控制信号,使"DMA请求"标志置"1"。该标志置位后向"控制/状态"逻辑发出DMA请求,后者又向CPU发出总线使用权的请求(HOLD),CPU响应此请求后发回响应信号HLDA,"控制/状态"逻辑接收此信号后发出DMA响应信号,使"DMA 请求"标志复位,为交换下一个字做好准备。
    • "控制/状态"逻辑:由控制和时序电路以及状态标志等组成,用于修改内存地址计数器和字计数器,指定传送类型(输入或输出),并对"DMA请求"信号和CPU响应信号进行协调和同步。
    • 中断机构:当字计数器溢出时,意味着一组数据交换完毕,由溢出信号触发中断机构,向CPU提出中断报告。

    手册请看英文手册

    芯片特性

    请求来源

    • 软件触发

    • 外设触发

    • 外部引脚触发,这个是STM32所没有的,这个是有具体的时序的,STM32应该是可以用中断引脚触发

      mark

    2440通道传输类型

    • 源和目标都在系统总线上(比如:两个物理内存地址)
    • 目标在外设总线上时,源在系统总线上(外设指:串口,定时器,I2C,I2S等)
    • 目标在系统总线上时,源在外设总线上
    • 源和目标都在外设总线上----------这个ST的也没有

    外部引脚的DMA协议

    这个貌似有点复杂,暂时也没用过,暂时不做深入分析了

    协议简述

    2440里面的DMA传输分为两个层次,一个是REQ/ACK协议,还一个是单模式和全模式,所谓单模式个全模式是指的在一次DMA请求中的传输数量

    mark

    基本时序

    mark

    时序参数

    • 信号的有效性: 高电平无效,低电平有效,这里称为assert

    • REQ有效

      REQ的只能在ACK释放(high)的时候才能被asserted(high),也就是说 请求信号只能在ACK为高的时候才能被MCU的DMA识别到

    • 信号生效识别

      nXDREQ请求生效并经过2CLK周期同步后,nXDACK响应并开始生效,但至少还要经过3CLK的周期延迟,DMA控制器才可获得总线的控制权,并开始数据传输。

    模式

    • Single service : 当没有原子传输(unit/burst)后,停止传输,等待下一次请求

    • Whole service : 重复原子传输,直到计数器到0.这个模式下,不需要另外的请求.这个是重点
      在全模式下,DMA也会在每个原子传输后释放总线然后去尝试获得总线,以防止总线被占据

    也就是说,全模式是我们一般使用的模式,使用计数器,一次请求会传输所有数据.单模式一次传输一个原子操作.

    ACK清零:

    • 单服务是完成一个原子操作
    • 全服务是完成所有传输

    中断发生:

    • 都在计数器为0的时候

    协议

    这里的协议,指的是请求应答协议,分为两种.

    • Demand Mode 请求/查询模式

      如果REQ信号有效,则一直保持传输,这个时候的ACK只是告诉你这一次传输完成

      这个模式会霸占总线的,不像全服务中完成一个原子操作释放一下总线

    • Handshake Mode 握手模式

      如果REQ信号释放,这个时候DMA控制器释放ACK两个周期,否则DMA会一直等到REQ的释放

      也就是启动下一次传输前,需要请求端先释放,然后MCU完成后会无效ACK两个周期告诉请求端,请求端再来请求,否则一直等待

    在Demond模式下,如果DMA完成一次请求后如果Request仍然有效,那么DMA就认为这是下一次DMA请求,并立即开始下一次的传输;

    在Handshake模式下,DMA完成一次请求后等待Request信号无效,如果Request无效,DMA会无效ACK两个时钟周期,再等待下一次Request。

    mark

    数据大小的描述

    数据传输的大小=数据传输次数 * 每次传输的读写次数 * 一次读或者写的大小

    • 每次传输的读写次数可以是1个或者4个 unit/burst
    • 一次读或者写的大小可以是1字节,2字节,4字节

    mark

    具体完整的实例时序

    单服务查询请求模式

    mark

    单服务握手模式

    mark

    全服务握手模式

    在这里其实无所谓hand了,因为在全模式下只需要一次请求就能完成后续的所有操作

    mark

    代码设计

    这里的代码就是驱动实现一个内存的拷贝,不涉及到上面长篇大论的时序分析,只是需要设置好相关的寄存器配置配置DMA的模式,然后启动DMA后进入休眠,完成后DMA中断唤醒后退出.

    测试程序调用字符驱动程序的接口ioctl来测试即可

    写程序前需要查看用到的DMA

    cat /proc/interrupts
    

    驱动程序

    #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
    ");
                    return 0;
             }
             else
            {
             printk("NORMAL_COPY ERROR
    ");
                   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
    ");
                    return 0;
             }
            else
            {
           printk("DMA_COPY ERROR
    ");
                 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"!!!
     ");
            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   
     ");
            return -ENOMEM;
       }
        
        dest_virt=dma_alloc_writecombine(NULL,S3C_DMA_SIZE, &dest_phys, GFP_KERNEL);
        if(dest_virt==NULL)       
       {
            printk("Can't  dma_alloc   
     ");
            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");
    

    测试程序

    #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:
    ");
        printf("%s <NORMAL | DMA>
    ", 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
    ");
            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;     
    }
    

    测试

    1. ./dma_test NORMAL & 卡住
    2. ./dma_test DMA &,输入命令有反应

    参考链接

    csdn DMA框架

    cnblog DMA请求应答协议

    cnblog 笔记

  • 相关阅读:
    Leetcode | Work Break I & II
    X-Japan
    Leetcode | Gas Station
    jstring, String, char* 变换函数
    动态链接库的创建
    C语言实现md5函数代码
    ARM汇编指令集
    2014年 各类黑客工具包
    ARM汇编指令的一些总结-转
    ARM指令集学习总结-转载
  • 原文地址:https://www.cnblogs.com/zongzi10010/p/10225384.html
Copyright © 2011-2022 走看看