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

測試程序

#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;     
}

測試

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

參考鏈接

csdn DMA框架

cnblog DMA請求應答協議

cnblog 筆記


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM