32.Linux-2440下的DMA驅動(詳解)


DMA(Direct Memory Access)

即直接存儲器訪問, DMA 傳輸方式無需 CPU 直接控制傳輸,通過硬件為 RAM 、I/O 設備開辟一條直接傳送數據的通路,能使 CPU 的效率大為提高。


 

學了這么多驅動,不難推出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緩存以及禁止寫入緩沖區,從而使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上,因為分配出來的內存可能在物理地址上是不連續的.

 

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的壓力:

 

 

 

 

 


免責聲明!

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



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