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 笔记

  • 相关阅读:
    AtCoder Grand Contest 015 题解
    AtCoder Grand Contest 014 题解
    AtCoder Grand Contest 013 题解
    AtCoder Grand Contest 012 题解
    AtCoder Grand Contest 011 题解
    AtCoder Grand Contest 010 题解
    AtCoder Grand Contest 009 题解
    NOIP2017 Day2 题解
    博客园主题备份
    多项式全家桶
  • 原文地址:https://www.cnblogs.com/zongzi10010/p/10225384.html
Copyright © 2011-2022 走看看