22、DMA驅動


一、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");
View Code

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
View Code

測試文件:

 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 }
View Code

 

在測試之前,我們需要確定內核中是否配置了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()輸出。

 

 

下一章  23、uevnet機制和U盤自動掛載

 


免責聲明!

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



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