DMA驅動程序編寫與測試

一、驅動程序編寫

在本驅動程序中,咱們打算在內存中開闢兩個空間,分別做爲源和目的。咱們用兩個方法將源中的數據寫到目的中,一種方法是讓cpu去作,另一種發放是讓DMA去作!好的,閒話少說,直接分析代碼:
   
   
   
   
   
#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/uaccess.h>#include <asm/irq.h>#include <asm/io.h>#include <asm/arch/regs-gpio.h>#include <asm/hardware.h>#include <linux/poll.h>#include <linux/dma-mapping.h>#define MEM_CPY_NO_DMA 0#define MEM_CPY_DMA 1#define BUF_SIZE (512*1024)#define DMA0_BASE_ADDR 0x4B000000#define DMA1_BASE_ADDR 0x4B000040#define DMA2_BASE_ADDR 0x4B000080#define DMA3_BASE_ADDR 0x4B0000C0struct s3c_dma_regs { unsigned long disrc; unsigned long disrcc; unsigned long didst; unsigned long didstc; unsigned long dcon; unsigned long dstat; unsigned long dcsrc; unsigned long dcdst; unsigned long dmasktrig;};static int major = 0;static char *src;static u32 src_phys;static char *dst;static u32 dst_phys;static struct class *cls;static volatile struct s3c_dma_regs *dma_regs;static DECLARE_WAIT_QUEUE_HEAD(dma_waitq);/* 中斷事件標誌, 中斷服務程序將它置1,ioctl將它清0 */static volatile int ev_dma = 0;static int s3c_dma_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg){ int i; memset(src, 0xAA, BUF_SIZE); memset(dst, 0x55, BUF_SIZE); switch (cmd) { //這是非DMA模式 case MEM_CPY_NO_DMA : { for (i = 0; i < BUF_SIZE; i++) dst[i] = src[i]; //CPU直接將源拷貝到目的 if (memcmp(src, dst, BUF_SIZE) == 0)//這個函數見註釋2 { printk("MEM_CPY_NO_DMA OK\n"); } else { printk("MEM_CPY_DMA ERROR\n"); } break; } //這是DMA模式 case MEM_CPY_DMA : { ev_dma = 0; /* 把源,目的,長度告訴DMA */ /* 關於下面寄存器的具體狀況,咱們在註釋3裏面來詳細講一下 */ dma_regs->disrc = src_phys; /* 源的物理地址 */ dma_regs->disrcc = (0<<1) | (0<<0); /* 源位於AHB總線, 源地址遞增 */ dma_regs->didst = dst_phys; /* 目的的物理地址 */ dma_regs->didstc = (0<<2) | (0<<1) | (0<<0); /* 目的位於AHB總線, 目的地址遞增 */ dma_regs->dcon = (1<<30)|(1<<29)|(0<<28)|(1<<27)|(0<<23)|(0<<20)|(BUF_SIZE<<0); /* 使能中斷,單個傳輸,軟件觸發, */ /* 啓動DMA */ dma_regs->dmasktrig = (1<<1) | (1<<0); /* 如何知道DMA何時完成? */ /* 休眠 */ wait_event_interruptible(dma_waitq, ev_dma); if (memcmp(src, dst, BUF_SIZE) == 0) { printk("MEM_CPY_DMA OK\n"); } else { printk("MEM_CPY_DMA ERROR\n"); } break; } } return 0;}static struct file_operations dma_fops = { .owner = THIS_MODULE, .ioctl = s3c_dma_ioctl,};static irqreturn_t s3c_dma_irq(int irq, void *devid){ /* 喚醒 */ ev_dma = 1; wake_up_interruptible(&dma_waitq); /* 喚醒休眠的進程 */ return IRQ_HANDLED;}static int s3c_dma_init(void){ /* 這裏註冊一箇中斷,當DMA數據傳輸完畢以後會發生此中斷 */ if (request_irq(IRQ_DMA3, s3c_dma_irq, 0, "s3c_dma", 1)) { printk("can't request_irq for DMA\n"); return -EBUSY; } /* 分配SRC, DST對應的緩衝區,關於此函數詳見註釋1 */ src = dma_alloc_writecombine(NULL, BUF_SIZE, &src_phys, GFP_KERNEL);//源 if (NULL == src) { printk("can't alloc buffer for src\n"); free_irq(IRQ_DMA3, 1); return -ENOMEM; } dst = dma_alloc_writecombine(NULL, BUF_SIZE, &dst_phys, GFP_KERNEL);//目的 if (NULL == dst) { free_irq(IRQ_DMA3, 1); dma_free_writecombine(NULL, BUF_SIZE, src, src_phys); printk("can't alloc buffer for dst\n"); return -ENOMEM; } major = register_chrdev(0, "s3c_dma", &dma_fops);//註冊字符設備 /* 爲了自動建立設備節點 */ cls = class_create(THIS_MODULE, "s3c_dma"); class_device_create(cls, NULL, MKDEV(major, 0), NULL, "dma"); /* /dev/dma */ dma_regs = ioremap(DMA3_BASE_ADDR, sizeof(struct s3c_dma_regs));//這邊是將DMA控制寄存器映射到內核空間 return 0;}static void s3c_dma_exit(void){ iounmap(dma_regs); class_device_destroy(cls, MKDEV(major, 0)); class_destroy(cls); unregister_chrdev(major, "s3c_dma"); dma_free_writecombine(NULL, BUF_SIZE, src, src_phys); dma_free_writecombine(NULL, BUF_SIZE, dst, dst_phys); free_irq(IRQ_DMA3, 1);}module_init(s3c_dma_init);module_exit(s3c_dma_exit);MODULE_LICENSE("GPL");
註釋1:
以前咱們知道在內核中開闢空間能夠用kmalloc函數,這裏卻用了dma_alloc_writecombine,這是爲何呢?這是由於kmalloc開闢的空間其邏輯地址雖然是連續的,可是其實際的物理地址可能不是連續的。而DMA傳輸數據時,要求物理地址是連續的,dma_alloc_writecombine就知足這一點,這個函數的原型是:
dma_alloc_writecombine(struct device *dev, size_t size, dma_addr_t *handle, gfp_t gfp)
其中size表明開闢的空間的大小,handle表明開闢的空間的物理地址,返回值是開闢的空間的邏輯地址。
     
     
     
     
     
int memcmp(const void *cs, const void *ct, size_t count){ const unsigned char *su1, *su2; int res = 0; for (su1 = cs, su2 = ct; 0 < count; ++su1, ++su2, count--) if ((res = *su1 - *su2) != 0) break; return res;}
咱們看到這個函數的做用就是將第一個參數和第二個參數一位一位地比較,一旦不相等就返回,此時返回值爲非零。比較的位數爲第三個參數,若是前兩個參數的前count爲都是相等的,那麼就會返回0

註釋3:
咱們先來解析一下上面幾個寄存器:
 
 DISRCn  bit  Description   Initial State 
 S_ADDR  [30:0]  源起始地址  0x00000000 
      
 DISRCCn  bit  Description  Initial State 
 LOC   [1]  用於選擇源的位置
 0:源在系統總線上
 1:源在外設總線上
 0
 INC  [0]  用於選擇地址是否自動增長
 0:地址自動增長
 1:地址固定不變(此時即使是burst 模式下,傳輸過程當中地址自動增長,  可是一旦傳輸完這一次數據,地址又變爲初值)
 0
   
 DIDSTn  bit  Description   Initial State 
 D_ADDR  [30:0]  目的起始地址  0x00000000 
      
 DIDSTCn  Bit  Description  Initial State 
 CHK_INT   [2]  當設置爲自動加載時,用來選擇中斷髮生的時間
 0:TC爲0是產生中斷
 1:自動加載完成的時候產生中斷
 0
 LOC  [1]  用於選擇目的設備的位置
 0:目的設備在系統總線上
 1:目的設備在外設總線上
 0
 INC  [0]  用於選擇地址是否自動增長
 0:地址自動增長
 1:地址固定不變(此時即使是burst模式下,傳輸過程當中地址自動增長,可是一旦傳輸完這一次數據,地址又從新變爲初值)
 0
   
 DCONn  Bit  Description  Initial State 
 DMD_HS   [31]   選擇爲Demand模式或者是握手模式
 0:選擇爲Demand模式
 1:選擇爲握手模式
 這兩種模式下都是當發生請求時,DMA控制器開始傳輸數據而且發出  應  答信號,不一樣點是握手模式下,當DMA控制器收到請求撤銷信號,而且自  身發出應答撤銷信號以後才能接收下一次請求。而在Demand模式下,並  不須要等待請求撤銷信號,他只須要撤銷自身的應答信號,而後等待下一  次的請求。
 0
 SYNC  [30]  選擇DREQ/DACK的同步
 0:DREQ and DACK 與PCLK同步
 1:DREQ and DACK 與HCLK同步
 所以當設備在AHB系統總線時,這一位必須爲1,而當設備在APB系統  時,它應該被設爲0。當設備位於外部系統時,應根據具體狀況而定。
 0
 INT  [29]  是否使能中斷
 0:禁止中斷,用戶須要查看狀態寄存器來判斷傳輸是否完成
 1:使能中斷,全部的傳輸完成以後產生中斷信號
 0
 TSZ  [28]  選擇一個原子傳輸的大小
 0:單元傳輸(一次傳輸一個單元)
 1:突發傳輸(一次傳輸四個單元)
 0
 SERVMODE  [27]  選擇是單服務模式仍是總體服務模式
 0:單服務模式,當一次原子傳輸完成後須要等待下一個DMA請求
 1:總體服務模式,進行屢次原子傳輸,知道傳輸計數值到達0
 0
 HWSRCSEL  [26:24]  爲每個DMA選擇DMA請求源
 具體參見芯片手冊
 000
 SWHW_SEL  [23]  選擇DMA源爲軟件請求模式仍是硬件請求模式
 0:軟件請求模式,須要將寄存器DMASKTRIG的SW_TRIG置位
 1:硬件請求模式
 0
 RELOAD  [22]  是否自動從新裝載
 0:自動重裝,當目前的傳輸計數值變爲0時,自動重裝
 1:不自動重裝
 RELOAD[1]被設置爲0以防無心識地進行新的DMA傳輸
 0
 DSZ  [21:20]   要被傳輸的數據的大小
 00 = Byte    01 = Half word 
 10 = Word   11 = reserved 
 00
 TC  [19:0]  初始化傳輸計數 0000
   
這裏咱們須要注意了,裏面有三個東東要分清楚:
DSZ   :表明數據的大小
TSZ   :一次傳輸多少個數據
TC     :一共傳輸多少次
因此實際傳輸的數據的大小爲:DSZ * TSZ * TC   
咱們本程序裏面因爲設置爲數據大小爲1個字節,一次傳輸1個數據,因此傳輸次數直接就是實際數據的大小了。

 DSTATn  Bit  Description  Initial State 
 STAT  [21:20]   DMA控制器的狀態
 00:DMA控制器已經準備好接收下一個DMA請求
 01:DMA控制器正在處理DMA請求
 00
 CURR_TC    [19:0]  傳輸計數的當前值
 每一個原子傳輸減1
 
   
 DCSRCn  Bit  Description  Initial State 
 CURR_SRC  [30:0]   當前的源地址  0x00000000
   
 DCDSTn  Bit  Description  Initial State 
 CURR_DST  [30:0]   當前的目的地址  0x00000000
   
 DMASKTRIGn  Bit  Description  Initial State 
 STOP  [2]  中止DMA操做
 1:當前的原子傳輸完成以後,就中止DMA操做。若是當前沒有原子  傳輸正在進行,就當即結束。
 
 ON_OFF  [1]  DMA通道的開/閉
 0:DMA通道關閉
 1:DMA通道打開,而且處理DMA請求
 
 SW_TRIG   [0]  1:在軟件請求模式時觸發DMA通道  
  
OK!寄存器分析完畢,具體設置就不在寫出來了!
在此咱們在來總結一下DMA的操做流程:
咱們首先設置DMA的工做方式,而後打開DMA通道,緊接着咱們使CPU休眠,中止執行代碼。與此同時,在DMA控制器的做用下,從源向目的拷貝數據。一旦數據拷貝完成,就會觸發中斷,在中斷函數裏面,喚醒進程,從而程序繼續運行,打印相關信息。

二、應用程序編寫
      
      
      
      
      
#include <stdio.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <sys/ioctl.h>#include <string.h>/* ./dma_test nodma * ./dma_test dma */#define MEM_CPY_NO_DMA 0#define MEM_CPY_DMA 1void print_usage(char *name){ printf("Usage:\n"); printf("%s <nodma | dma>\n", name);}int main(int argc, char **argv){ int fd; if (argc != 2) { print_usage(argv[0]); return -1; } fd = open("/dev/dma", O_RDWR); if (fd < 0) { printf("can't open /dev/dma\n"); return -1; } if (strcmp(argv[1], "nodma") == 0) { while (1) { ioctl(fd, MEM_CPY_NO_DMA); } } else if (strcmp(argv[1], "dma") == 0) { while (1) { ioctl(fd, MEM_CPY_DMA); } } else { print_usage(argv[0]); return -1; } return 0; }
應用程序過於簡單,不在分析!

三、測試
# insmod dma.ko       //加載驅動
# cat /proc/interrupts  //查看中斷
           CPU0
 30:      52318         s3c  S3C2410 Timer Tick
 33:          0         s3c  s3c-mci
 34:          0         s3c  I2SSDI
 35:          0         s3c  I2SSDO
 36:          0         s3c  s3c_dma
 37:         12         s3c  s3c-mci
 42:          0         s3c  ohci_hcd:usb1
 43:          0         s3c  s3c2440-i2c
 51:       2725     s3c-ext  eth0
 60:          0     s3c-ext  s3c-mci
 70:         97   s3c-uart0  s3c2440-uart
 71:        100   s3c-uart0  s3c2440-uart
 83:          0           -  s3c2410-wdt
Err:          0

# ls /dev/dma     //查看設備
/dev/dma

# ./dmatest       //如此就會打印用法
Usage:
./dmatest <nodma | dma>

# ./dmatest dma     //以DMA方式拷貝,CPU能夠作其餘事情
MEM_CPY_DMA OK
MEM_CPY_DMA OK
MEM_CPY_DMA OK
MEM_CPY_DMA OK
MEM_CPY_DMA OK
MEM_CPY_DMA OK
MEM_CPY_DMA OK

# ./dmatest dma     //CPU拷貝,各類競爭CPU
MEM_CPY_DMA OK
MEM_CPY_DMA OK
MEM_CPY_DMA OK
MEM_CPY_DMA OK

 

 
 


相關文章
相關標籤/搜索