一、DMA簡介
DMA(Direct Memory Access,直接內存存取),DMA傳輸將數據從一個地址空間復制到另外一個地址空間。傳輸過程由DMA控制器獨立完成,它並沒有拖延CPU的工作,可以讓CPU效率提高。
既然DMA用於傳輸,那么就需要具備傳輸三要素:源、目的、長度。在傳輸完成后,DMA會通過產生中斷的方式匯報。
由於DMA不使用頁表機制,因此必須分配連續的物理內存,這一點需要我們注意,我們可以使用dma_alloc_writecombine()或dma_alloc_coherent()。dma_alloc_coherent()分配的內存不使用緩存,也不會使用寫緩沖區,性能較差,dma_alloc_writecombine()不使用緩存,使用寫緩沖區。
我們來看4412數據手冊Direct Memory Access Controller一章。
可以看到4412上有3個DMA控制器,圖中DMA是支持內存到內存的DMA控制器,DMA0和DMA1是同時支持外設到內存和內存到外設的DMA控制器。
那么為什么圖中把DMA單獨分類,DMA0和DMA1一類呢?
從方向上來說,DMA傳輸可以分為4類:memory到memory、memory到device、device到memory和device到device。從CPU的角度看,device都是slave,因此將這些有device參與的傳輸分為一類,稱為Slave-DMA傳輸。而另一種memory到memory的傳輸,被稱為Async TX。
讀者若希望了解DMA傳輸更多信息,可參考:32.Linux-2440下的DMA驅動(詳解)
二、DMA Engine介紹和DMA設備驅動步驟
DMA驅動框架定義在drivers/dma/dmaengine.c中,整體關系如下圖。下面介紹DMA需要使用到的概念。
1. DMA Channels:如下圖,一個DMA控制器同時傳輸的數據個數是有限的,這個限度稱為channel
2. DMA Request Lines:DMA控制器和DMA傳輸設備之間需要有多條數據線線(稱作DMA request,DRQ
3. 傳輸描述符:DMA傳輸屬於異步傳輸,在傳輸前,slave驅動需要將本次傳輸的信息(如傳輸大小、方向等)提交給engine,engine返回描述符
編寫DMA的設備驅動一般步驟如下:
1. 使用dma_request_channel()函數申請一個DMA通道
2. 使用dmaengine_slave_config()設置DMA通道參數
3. 使用dmaengine_prep_slave_single()或dmaengine_prep_slave_sg()或dmaengine_prep_dma_cyclic()獲取傳輸描述符
4. 使用dmaengine_submit()將描述符提交到DMA等待隊列
5. 使用dma_async_issue_pending()啟動傳輸
6. 等待傳輸完成
步驟中所使用函數聲明如下:
/* 1. 申請一個DMA通道 */ /* mask是使用dma_cap_sets()指定的DMA傳輸類型 * filter_param是slave ID * eg: * dma_cap_mask_t mask; * dma_cap_zero(mask); * dma_cap_set(DMA_MEMCPY, mask); * dma_chan0 = dma_request_channel(mask, 0, NULL); */ struct dma_chan *dma_request_channel(dma_cap_mask_t *mask, dma_filter_fn fn, void *fn_param) /* 2. 設置DMA通道參數 */ /* config用於設置DMA通道寬度、數據傳輸寬帶、源和目的等信息 */ int dmaengine_slave_config(struct dma_chan *chan, struct dma_slave_config *config) /* 3. 獲取傳輸描述符 */ struct dma_async_tx_descriptor *dmaengine_prep_slave_single( struct dma_chan *chan, dma_addr_t buf, size_t len, enum dma_transfer_direction dir, unsigned long flags) struct dma_async_tx_descriptor *dmaengine_prep_slave_sg( struct dma_chan *chan, struct scatterlist *sgl, unsigned int sg_len, enum dma_transfer_direction dir, unsigned long flags) struct dma_async_tx_descriptor *dmaengine_prep_dma_cyclic( struct dma_chan *chan, dma_addr_t buf_addr, size_t buf_len, size_t period_len, enum dma_transfer_direction dir) /* 4. 將描述符提交到DMA等待隊列 */ dma_cookie_t dmaengine_submit(struct dma_async_tx_descriptor *desc) /* 5. 啟動傳輸 */ void dma_async_issue_pending(struct dma_chan *chan)
其中,
1. 傳輸類型具體列為:
enum dma_transaction_type { DMA_MEMCPY, DMA_XOR, DMA_PQ, DMA_XOR_VAL, DMA_PQ_VAL, DMA_MEMSET, DMA_INTERRUPT, DMA_SG, DMA_PRIVATE, DMA_ASYNC_TX, DMA_SLAVE, DMA_CYCLIC, DMA_INTERLEAVE, /* last transaction type for creation of the capabilities mask */ DMA_TX_TYPE_END, };
2. struct dma_slave_config含有DMA傳輸所需要的參數
struct dma_slave_config { enum dma_transfer_direction direction; /* 傳輸方向,DMA_MEM_TO_MEM、DMA_MEM_TO_DEV等 */ dma_addr_t src_addr; /* 源,讀數據的地址 */ dma_addr_t dst_addr; /* 目的,寫數據的地址 */ enum dma_slave_buswidth src_addr_width; /* 最大可傳輸的burst size */ enum dma_slave_buswidth dst_addr_width; u32 src_maxburst; u32 dst_maxburst; bool device_fc; /* 當device是flow controller時,需要設置為true */ };
3. struct dma_async_tx_descriptor是DMA的傳輸描述符
struct dma_async_tx_descriptor { dma_cookie_t cookie; /* 用於追蹤本次傳輸 */ enum dma_ctrl_flags flags; /* DMA_CTRL_開頭的標志, */ dma_addr_t phys; struct dma_chan *chan; /* 對應的通道 */ dma_cookie_t (*tx_submit)(struct dma_async_tx_descriptor *tx); /* 控制器驅動提供的回調函數 */ dma_async_tx_callback callback; /* 傳輸完成的回調函數和參數 */ void *callback_param; #ifdef CONFIG_ASYNC_TX_ENABLE_CHANNEL_SWITCH struct dma_async_tx_descriptor *next; struct dma_async_tx_descriptor *parent; spinlock_t lock; #endif };
4. 對於struct scatterlist,讀者可參考:Linux內核scatterlist API介紹
三、DMA設備驅動程序
源代碼:

1 #include <linux/init.h> 2 #include <linux/module.h> 3 #include <linux/fs.h> 4 #include <linux/sched.h> 5 #include <linux/miscdevice.h> 6 #include <linux/device.h> 7 #include <linux/string.h> 8 #include <linux/errno.h> 9 #include <linux/types.h> 10 #include <linux/slab.h> 11 #include <linux/dmaengine.h> 12 #include <linux/dma-mapping.h> 13 14 #include <asm/io.h> 15 #include <asm/uaccess.h> 16 17 #include <linux/amba/pl330.h> 18 #include <mach/dma.h> 19 20 #define BUF_SIZE 512 21 #define PL_NO_DMA _IOW('M', 0x1, int) /* magic num */ 22 #define PL_USE_DMA _IOW('M', 0x2, int) 23 24 static char *src = NULL; 25 static char *dst = NULL; 26 static dma_addr_t dma_src; 27 static dma_addr_t dma_dst; 28 static enum dma_ctrl_flags flags; 29 static dma_cookie_t cookie; 30 static struct dma_chan *chan0 = NULL; 31 static struct dma_device *dev0 = NULL; 32 static struct dma_async_tx_descriptor *tx0 = NULL; 33 34 static void dma_callback(void *dma_async_param) 35 { 36 if (0 == memcmp(src, dst, BUF_SIZE)) 37 printk("PL_USE_DMA succeed\n"); 38 else 39 printk("PL_USE_DMA error\n"); 40 } 41 42 static int pl330_dma_open(struct inode *inode, struct file *filp) 43 { 44 return 0; 45 } 46 47 static long pl330_dma_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) 48 { 49 int i = 0; 50 51 memset(src, 0xAA, BUF_SIZE); 52 memset(dst, 0xBB, BUF_SIZE); 53 54 switch (cmd) 55 { 56 case PL_NO_DMA: 57 for (i = 0; i < BUF_SIZE; i++) 58 dst[i] = src[i]; 59 60 if (0 == memcmp(src, dst, BUF_SIZE)) 61 printk("PL_NO_DMA succeed\n"); 62 else 63 printk("PL_NO_DMA error\n"); 64 break; 65 66 case PL_USE_DMA: 67 flags = DMA_CTRL_ACK | DMA_PREP_INTERRUPT; 68 dev0 = chan0->device; 69 tx0 = dev0->device_prep_dma_memcpy(chan0, dma_dst, dma_src, BUF_SIZE, flags); 70 if (!tx0) 71 printk("device_prep_dma_memcpy error\n"); 72 73 tx0->callback = dma_callback; 74 tx0->callback_param = NULL; 75 cookie = tx0->tx_submit(tx0); 76 if (dma_submit_error(cookie)) 77 printk("tx_submit error\n"); 78 79 dma_async_issue_pending(chan0); 80 break; 81 82 default: 83 break; 84 } 85 86 return 0; 87 } 88 89 static int pl330_dma_release(struct inode *inode, struct file *filp) 90 { 91 return 0; 92 } 93 94 static struct file_operations pl330_dma_fops = { 95 .open = pl330_dma_open, 96 .unlocked_ioctl = pl330_dma_ioctl, 97 .release = pl330_dma_release, 98 }; 99 100 static struct miscdevice dma_misc = { 101 .minor = MISC_DYNAMIC_MINOR, 102 .name = "dma_test", 103 .fops = &pl330_dma_fops, 104 }; 105 106 extern bool pl330_filter(struct dma_chan *chan, void *param); 107 extern void msleep(unsigned int msecs); 108 109 static int __init pl330_dma_init(void) 110 { 111 int ret = misc_register(&dma_misc); 112 if (ret < 0) { 113 printk("misc_register error\n"); 114 return -EINVAL; 115 } 116 117 src = dma_alloc_writecombine(NULL, BUF_SIZE, &dma_src, GFP_KERNEL); 118 dst = dma_alloc_writecombine(NULL, BUF_SIZE, &dma_dst, GFP_KERNEL); 119 120 dma_cap_mask_t mask; 121 dma_cap_zero(mask); 122 dma_cap_set(DMA_SLAVE, mask); 123 124 chan0 = dma_request_channel(mask, pl330_filter, NULL); 125 if (NULL == chan0){ 126 msleep(100); 127 chan0 = dma_request_channel(mask, NULL, NULL); 128 } 129 130 if (NULL == chan0) 131 printk("dma_request_channel error\n"); 132 133 return 0; 134 } 135 136 static void __exit pl330_dma_exit(void) 137 { 138 dma_release_channel(chan0); 139 dma_free_writecombine(NULL, BUF_SIZE, src, dma_src); 140 dma_free_writecombine(NULL, BUF_SIZE, dst, dma_dst); 141 misc_deregister(&dma_misc); 142 } 143 144 module_init(pl330_dma_init); 145 module_exit(pl330_dma_exit); 146 147 MODULE_LICENSE("GPL");
Makefile:

1 KERN_DIR = /work/itop4412/tools/linux-3.5 2 3 all: 4 make -C $(KERN_DIR) M=`pwd` modules 5 6 clean: 7 make -C $(KERN_DIR) M=`pwd` modules clean 8 rm -rf modules.order 9 10 obj-m += pl330dma.o
測試文件:

1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 5 #include <sys/types.h> 6 #include <sys/stat.h> 7 #include <sys/ioctl.h> 8 #include <unistd.h> 9 #include <fcntl.h> 10 11 #define PL_NO_DMA _IOW('M', 0x1, int) /* magic num */ 12 #define PL_USE_DMA _IOW('M', 0x2, int) 13 14 int main(void) 15 { 16 int fd; 17 fd = open("/dev/dma_test", O_RDWR); 18 if (fd < 0) { 19 printf("can't open /dev/dma_test\n"); 20 return -1; 21 } 22 23 ioctl(fd, PL_NO_DMA, 0); 24 sleep(1); 25 26 ioctl(fd, PL_USE_DMA, 0); 27 sleep(1); 28 29 return 0; 30 }
在測試之前,我們需要確定內核中是否配置了DMA框架:
Device Drivers ---> [*] DMA Engine support ---> [*] DMA Engine debugging <*> DMA API Driver for PL330 [*] Async_tx: Offload support for the async_tx api
測試:
在編譯並在開發板上insmod后執行測試文件,可以看到設備驅動printk()輸出。