Linux 內核DMA機制

 

DMA控制器硬件結構php

DMA容許外圍設備和主內存之間直接傳輸 I/O 數據, DMA 依賴於系統。每一種體系結構DMA傳輸不一樣,編程接口也不一樣。node

數據傳輸能夠以兩種方式觸發:一種軟件請求數據,另外一種由硬件異步傳輸。程序員

在第一種狀況下,調用的步驟能夠歸納以下(以read爲例):編程

(1)在進程調用 read 時,驅動程序的方法分配一個 DMA 緩衝區,隨後指示硬件傳送它的數據。進程進入睡眠。數組

(2)硬件將數據寫入 DMA 緩衝區並在完成時產生一箇中斷。緩存

(3)中斷處理程序得到輸入數據,應答中斷,最後喚醒進程,該進程如今能夠讀取數據了。網絡

第二種情形是在 DMA 被異步使用時發生的。以數據採集設備爲例:app

(1)硬件發出中斷來通知新的數據已經到達。less

(2)中斷處理程序分配一個DMA緩衝區。異步

(3)外圍設備將數據寫入緩衝區,而後在完成時發出另外一箇中斷。

(4)處理程序利用DMA分發新的數據,喚醒任何相關進程。

網卡傳輸也是如此,網卡有一個循環緩衝區(一般叫作 DMA 環形緩衝區)創建在與處理器共享的內存中。每個輸入數據包被放置在環形緩衝區中下一個可用緩衝區,而且發出中斷。而後驅動程序將網絡數據包傳給內核的其它部分處理,並在環形緩衝區中放置一個新的 DMA 緩衝區。

驅動程序在初始化時分配DMA緩衝區,並使用它們直到中止運行。

DMA控制器依賴於平臺硬件,這裏只對i386的8237 DMA控制器作簡單的說明,它有兩個控制器,8個通道,具體說明以下:

控制器1: 通道0-3,字節操做, 端口爲 00-1F

控制器2: 通道 4-7, 字操做, 端口咪 C0-DF

- 全部寄存器是8 bit,與傳輸大小無關。

- 通道 4 被用來將控制器1與控制器2級聯起來。

- 通道 0-3 是字節操做,地址/計數都是字節的。

- 通道 5-7 是字操做,地址/計數都是以字爲單位的。

- 傳輸器對於(0-3通道)必須不超過64K的物理邊界,對於5-7必須不超過128K邊界。

- 對於5-7通道page registers 不用數據 bit 0, 表明128K頁

- 對於0-3通道page registers 使用 bit 0, 表示 64K頁

DMA 傳輸器限制在低於16M物理內存裏。裝入寄存器的地址必須是物理地址,而不是邏輯地址。

對於0-3通道來講地址對寄存器的映射以下:

A23 ... A16 A15 ... A8  A7 ... A0    (物理地址)
     |  ...  |   |  ... |   |  ... |
     |  ...  |   |  ... |   |  ... |
     |  ...  |   |  ... |   |  ... |
    P7  ...  P0  A7 ... A0  A7 ... A0   
  |    Page    | Addr MSB | Addr LSB |   (DMA 
地址寄存器)

對於5-7通道來講地址對寄存器的映射以下:

A23 ... A17 A16 A15 ... A9 A8 A7 ... A1 A0    (物理地址)

    |  ...  |   \   \   ... \  \  \  ... \  \
    |  ...  |    \   \   ... \  \  \  ... \  (
沒用)
    |  ...  |     \   \   ... \  \  \  ... \
   P7  ...  P1 (0) A7 A6  ... A0 A7 A6 ... A0  

|      Page      |  Addr MSB   |  Addr LSB  |   (DMA 地址寄存器)

通道 5-7 傳輸以字爲單位地址和計數都必須是以字對齊的。

在include/asm-i386/dma.h中有i386平臺的8237 DMA控制器的各處寄存器的地址及寄存器的定義,這裏只對控制寄存器加以說明:

DMA Channel Control/Status Register (DCSRX)

第31位 代表是否開始

第30位選定Descriptor和Non-Descriptor模式

第29位 判斷有無中斷

第8位 請求處理 (Request Pending)

第3位 Channel是否運行

第2位 當前數據交換是否完成

第1位是否由Descriptor產生中斷

第0位 是否由總線錯誤引發中斷

DMA通道使用的地址

DMA通道用dma_chan結構數組表示,這個結構在kernel/dma.c中,列出以下:

struct dma_chan {
        int  lock;
        const char *device_id;
};
 
static struct dma_chan dma_chan_busy[MAX_DMA_CHANNELS] = {
        [4] = { 1, "cascade" },
};

若是dma_chan_busy[n].lock != 0表示忙,DMA0保留爲DRAM更新用,DMA4用做級聯。DMA 緩衝區的主要問題是,當它大於一頁時,它必須佔據物理內存中的連續頁。

因爲DMA須要連續的內存,於是在引導時分配內存或者爲緩衝區保留物理 RAM 的頂部。在引導時給內核傳遞一個"mem="參數能夠保留 RAM 的頂部。例如,若是系統有 32MB 內存,參數"mem=31M"阻止內核使用最頂部的一兆字節。稍後,模塊可使用下面的代碼來訪問這些保留的內存:

dmabuf = ioremap( 0x1F00000 /* 31M */, 0x100000 /* 1M */);

分配 DMA 空間的方法,代碼調用 kmalloc(GFP_ATOMIC) 直到失敗爲止,而後它等待內核釋放若干頁面,接下來再一次進行分配。最終會發現由連續頁面組成的DMA 緩衝區的出現。

一個使用 DMA 的設備驅動程序一般會與鏈接到接口總線上的硬件通信,這些硬件使用物理地址,而程序代碼使用虛擬地址。基於 DMA 的硬件使用總線地址而不是物理地址,有時,接口總線是經過將 I/O 地址映射到不一樣物理地址的橋接電路鏈接的。甚至某些系統有一個頁面映射方案,可以使任意頁面在外圍總線上表現爲連續的。

當驅動程序須要向一個 I/O 設備(例如擴展板或者DMA控制器)發送地址信息時,必須使用 virt_to_bus 轉換,在接受到來自鏈接到總線上硬件的地址信息時,必須使用 bus_to_virt 了。

DMA操做函數

由於 DMA 控制器是一個系統級的資源,因此內核協助處理這一資源。內核使用 DMA 註冊表爲 DMA 通道提供了請求/釋放機制,而且提供了一組函數在 DMA 控制器中配置通道信息。

DMA 控制器使用函數request_dma和free_dma來獲取和釋放 DMA 通道的全部權,請求 DMA 通道應在請求了中斷線以後,而且在釋放中斷線以前釋放它。每個使用 DMA 的設備也必須使用中斷信號線,不然就沒法發出數據傳輸完成的通知。這兩個函數的聲明列出以下(在kernel/dma.c中):

int request_dma(unsigned int channel, const char *name); 
void free_dma(unsigned int channel);

DMA 控制器被dma_spin_lock 的自旋鎖所保護。使用函數claim_dma_lockrelease_dma_lock對得到和釋放自旋鎖。這兩個函數的聲明列出以下(在kernel/dma.c中):

unsigned long claim_dma_lock(); 獲取 DMA 自旋鎖,該函數會阻塞本地處理器上的中斷,所以,其返回值是"標誌"值,在從新打開中斷時必須使用該值。

void release_dma_lock(unsigned long flags); 釋放 DMA 自旋鎖,而且恢復之前的中斷狀態。

DMA 控制器的控制設置信息由RAM 地址、傳輸的數據(以字節或字爲單位),以及傳輸的方向三部分組成。下面是i386平臺的8237 DMA控制器的操做函數說明(在include/asm-i386/dma.h中),使用這些函數設置DMA控制器時,應該持有自旋鎖。但在驅動程序作I/O 操做時,不能持有自旋鎖。

void set_dma_mode(unsigned int channel, char mode); 該函數指出通道從設備讀(DMA_MODE_WRITE)或寫(DMA_MODE_READ)數據方式,當mode設置爲DMA_MODE_CASCADE時,表示釋放對總線的控制。

void set_dma_addr(unsigned int channel, unsigned int addr); 函數給 DMA 緩衝區的地址賦值。該函數將 addr 的最低 24 位存儲到控制器中。參數 addr 是總線地址。

void set_dma_count(unsigned int channel, unsigned int count);該函數對傳輸的字節數賦值。參數 count 也表明 16 位通道的字節數,在此狀況下,這個數字必須是偶數。

除了這些操做函數外,還有些對DMA狀態進行控制的工具函數:

void disable_dma(unsigned int channel); 該函數設置禁止使用DMA 通道。這應該在配置 DMA 控制器以前設置。

void enable_dma(unsigned int channel); 在DMA 通道中包含了合法的數據時,該函數激活DMA 控制器。

int get_dma_residue(unsigned int channel); 該函數查詢一個 DMA 傳輸還有多少字節還沒傳輸完。函數返回沒傳完的字節數。當傳輸成功時,函數返回值是0

void clear_dma_ff(unsigned int channel) 該函數清除 DMA 觸發器(flip-flop),該觸發器用來控制對 16 位寄存器的訪問。能夠經過兩個連續的 8 位操做來訪問這些寄存器,觸發器被清除時用來選擇低字節,觸發器被置位時用來選擇高字節。在傳輸 8 位後,觸發器會自動反轉;在訪問DMA 寄存器以前,程序員必須清除觸發器(將它設置爲某個已知狀態)。

DMA映射

一個DMA映射就是分配一個 DMA 緩衝區併爲該緩衝區生成一個可以被設備訪問的地址的組合操做。通常狀況下,簡單地調用函數virt_to_bus 就設備總線上的地址,但有些硬件映射寄存器也被設置在總線硬件中。映射寄存器(mapping register)是一個相似於外圍設備的虛擬內存等價物。在使用這些寄存器的系統上,外圍設備有一個相對較小的、專用的地址區段,能夠在此區段執行DMA。經過映射寄存器,這些地址被重映射到系統 RAM。映射寄存器具備一些好的特性,包括使分散的頁面在設備地址空間看起來是連續的。但不是全部的體系結構都有映射寄存器,特別地,PC 平臺沒有映射寄存器。

在某些狀況下,爲設備設置有用的地址也意味着須要構造一個反彈(bounce)緩衝區。例如,當驅動程序試圖在一個不能被外圍設備訪問的地址(一個高端內存地址)上執行 DMA 時,反彈緩衝區被建立。而後,按照須要,數據被複制到反彈緩衝區,或者從反彈緩衝區複製。

根據 DMA 緩衝區指望保留的時間長短,PCI 代碼區分兩種類型的 DMA 映射:

  • 一致 DMA 映射 它們存在於驅動程序的生命週期內。一個被一致映射的緩衝區必須同時可被 CPU 和外圍設備訪問,這個緩衝區被處理器寫時,可當即被設備讀取而沒有cache效應,反之亦然,使用函數pci_alloc_consistent創建一致映射。
  • 流式 DMA映射 流式DMA映射是爲單個操做進行的設置。它映射處理器虛擬空間的一塊地址,以至它能被設備訪問。應儘量使用流式映射,而不是一致映射。這是由於在支持一致映射的系統上,每一個 DMA 映射會使用總線上一個或多個映射寄存器。具備較長生命週期的一致映射,會獨佔這些寄存器很長時間――即便它們沒有被使用。使用函數dma_map_single創建流式映射。

(1)創建一致 DMA 映射

函數pci_alloc_consistent處理緩衝區的分配和映射,函數分析以下(在include/asm-generic/pci-dma-compat.h中):

static inline void *pci_alloc_consistent(struct pci_dev *hwdev, 
size_t size, dma_addr_t *dma_handle)
{
        return dma_alloc_coherent(hwdev == NULL ? NULL : &hwdev->dev, 
                       size, dma_handle, GFP_ATOMIC);
}

結構dma_coherent_mem定義了DMA一致性映射的內存的地址、大小和標識等。結構dma_coherent_mem列出以下(在arch/i386/kernel/pci-dma.c中):

struct dma_coherent_mem {

void                *virt_base;
        u32                device_base;
        int                size;
        int                flags;
        unsigned long        *bitmap;

};

函數dma_alloc_coherent分配size字節的區域的一致內存,獲得的dma_handle是指向分配的區域的地址指針,這個地址做爲區域的物理基地址。dma_handle是與總線同樣的位寬的無符號整數。函數dma_alloc_coherent分析以下(在arch/i386/kernel/pci-dma.c中):

void *dma_alloc_coherent(struct device *dev, size_t size,
                           dma_addr_t *dma_handle, int gfp)
{
        void *ret;
//如果設備,獲得設備的dma內存區域,即mem= dev->dma_mem
        struct dma_coherent_mem *mem = dev ? dev->dma_mem : NULL;
        int order = get_order(size);//
size轉換成order,即 
        //
忽略特定的區域,於是忽略這兩個標識
        gfp &= ~(__GFP_DMA | __GFP_HIGHMEM);
 
        if (mem) {//
設備的DMA映射,mem= dev->dma_mem
//找到mem對應的頁
                int page = bitmap_find_free_region(mem->bitmap, mem->size,
                                                     order);
                if (page >= 0) {
                        *dma_handle = mem->device_base + (page << PAGE_SHIFT);
                        ret = mem->virt_base + (page << PAGE_SHIFT);
                        memset(ret, 0, size);
                        return ret;
                }
                if (mem->flags & DMA_MEMORY_EXCLUSIVE)
                        return NULL;
        }
 
//不是設備的DMA映射
        if (dev == NULL || (dev->coherent_dma_mask < 0xffffffff))
                gfp |= GFP_DMA;
//分配空閒頁
        ret = (void *)__get_free_pages(gfp, order);
 
        if (ret != NULL) {
                memset(ret, 0, size);//
0
                *dma_handle = virt_to_phys(ret);//
獲得物理地址
        }
        return ret;
}

當再也不須要緩衝區時(一般在模塊卸載時),應該調用函數 pci_free_consitent 將它返還給系統。

(2)創建流式 DMA 映射

在流式 DMA 映射的操做中,緩衝區傳送方向應匹配於映射時給定的方向值。緩衝區被映射後,它就屬於設備而再也不屬於處理器了。在緩衝區調用函數pci_unmap_single撤銷映射以前,驅動程序不該該觸及其內容。

在緩衝區爲 DMA 映射時,內核必須確保緩衝區中全部的數據已經被實際寫到內存。可能有些數據還會保留在處理器的高速緩衝存儲器中,所以必須顯式刷新。在刷新以後,由處理器寫入緩衝區的數據對設備來講也許是不可見的。

若是欲映射的緩衝區位於設備不能訪問的內存區段時,某些體系結構僅僅會操做失敗,而其它的體系結構會建立一個反彈緩衝區。反彈緩衝區是被設備訪問的獨立內存區域,反彈緩衝區複製原始緩衝區的內容。

函數pci_map_single映射單個用於傳送的緩衝區,返回值是能夠傳遞給設備的總線地址,若是出錯的話就爲 NULL。一旦傳送完成,應該使用函數pci_unmap_single 刪除映射。其中,參數direction爲傳輸的方向,取值以下:

PCI_DMA_TODEVICE 數據被髮送到設備。

PCI_DMA_FROMDEVICE若是數據將發送到 CPU。

PCI_DMA_BIDIRECTIONAL數據進行兩個方向的移動。

PCI_DMA_NONE 這個符號只是爲幫助調試而提供。

函數pci_map_single分析以下(在arch/i386/kernel/pci-dma.c中):

static inline dma_addr_t pci_map_single(struct pci_dev *hwdev, 
void *ptr, size_t size, int direction)
{
        return dma_map_single(hwdev == NULL ? NULL : &hwdev->dev, ptr, size, 
                                      (enum ma_data_direction)direction);
}

函數dma_map_single映射一塊處理器虛擬內存,這塊虛擬內存能被設備訪問,返回內存的物理地址,函數dma_map_single分析以下(在include/asm-i386/dma-mapping.h中):

static inline dma_addr_t dma_map_single(struct device *dev, void *ptr,

                        size_t size, enum dma_data_direction direction)

{
        BUG_ON(direction == DMA_NONE);
//可能有些數據還會保留在處理器的高速緩衝存儲器中,所以必須顯式刷新
        flush_write_buffers();
        return virt_to_phys(ptr);//
虛擬地址轉化爲物理地址

}

(3)分散/集中映射

分散/集中映射是流式 DMA 映射的一個特例。它將幾個緩衝區集中到一塊兒進行一次映射,並在一個 DMA 操做中傳送全部數據。這些分散的緩衝區由分散表結構scatterlist來描述,多個分散的緩衝區的分散表結構組成緩衝區的struct scatterlist數組。

分散表結構列出以下(在include/asm-i386/scatterlist.h):

struct scatterlist {
    struct page                *page;
    unsigned int        offset;
    dma_addr_t                dma_address;  //
用在分散/集中操做中的緩衝區地址
    unsigned int        length;//
該緩衝區的長度
};

每個緩衝區的地址和長度會被存儲在 struct scatterlist 項中,但在不一樣的體系結構中它們在結構中的位置是不一樣的。下面的兩個宏定義來解決平臺移植性問題,這些宏定義應該在一個pci_map_sg 被調用後使用:

//從該分散表項中返回總線地址

#define sg_dma_address(sg)        �sg)->dma_address)
//返回該緩衝區的長度

#define sg_dma_len(sg)                �sg)->length)

函數pci_map_sg完成分散/集中映射,其返回值是要傳送的 DMA 緩衝區數;它可能會小於nents(也就是傳入的分散表項的數量),由於可能有的緩衝區地址上是相鄰的。一旦傳輸完成,分散/集中映射經過調用函數pci_unmap_sg 來撤銷映射。 函數pci_map_sg分析以下(在include/asm-generic/pci-dma-compat.h中):

static inline int pci_map_sg(struct pci_dev *hwdev, struct scatterlist *sg,
                                    int nents, int direction)
{
        return dma_map_sg(hwdev == NULL ? NULL : &hwdev->dev, sg, nents, 
(enum dma_data_direction)direction);
}
include/asm-i386/dma-mapping.h
static inline int dma_map_sg(struct device *dev, struct scatterlist *sg, 
int nents, enum dma_data_direction direction)
{
        int i;
 
        BUG_ON(direction == DMA_NONE);
 
        for (i = 0; i < nents; i++ ) {
                BUG_ON(!sg[i].page);
//將頁及頁偏移地址轉化爲物理地址
                sg[i].dma_address = page_to_phys(sg[i].page) + sg[i].offset;
        }
    //
可能有些數據還會保留在處理器的高速緩衝存儲器中,所以必須顯式刷新
        flush_write_buffers();
        return nents;
}

DMA池

許多驅動程序須要又多又小的一致映射內存區域給DMA描述子或I/O緩存buffer,這使用DMA池比用dma_alloc_coherent分配的一 頁或多頁內存區域好,DMA池用函數dma_pool_create建立,用函數dma_pool_alloc從DMA池中分配一塊一致內存,用函數 dmp_pool_free放內存回到DMA池中,使用函數dma_pool_destory釋放DMA池的資源。

結構dma_pool是DMA池描述結構,列出以下:

struct dma_pool {        /* the pool */
        struct list_head        page_list;//
頁鏈表
        spinlock_t                lock;
        size_t                        blocks_per_page;//
每頁的塊數
        size_t                        size;     //DMA
池裏的一致內存塊的大小
        struct device                *dev; //
將作DMA的設備
        size_t                        allocation; //
分配的沒有跨越邊界的塊數,是size的整數倍
        char                        name [32];//
池的名字
        wait_queue_head_t        waitq;  //
等待隊列
        struct list_head        pools;
};

函數dma_pool_create給DMA建立一個一致內存塊池,其參數name是DMA池的名字,用於診斷用,參數dev是將作DMA的設備,參數 size是DMA池裏的塊的大小,參數align是塊的對齊要求,是2的冪,參數allocation返回沒有跨越邊界的塊數(或0)。

函數dma_pool_create返回建立的帶有要求字符串的DMA池,若建立失敗返回null。對被給的DMA池,函數dma_pool_alloc 被用來分配內存,這些內存都是一致DMA映射,可被設備訪問,且沒有使用緩存刷新機制,由於對齊緣由,分配的塊的實際尺寸比請求的大。若是分配非0的內 存,從函數dma_pool_alloc返回的對象將不跨越size邊界(如不跨越4K字節邊界)。這對在個體的DMA傳輸上有地址限制的設備來講是有利 的。

函數dma_pool_create分析以下(在drivers/base/dmapool.c中):

struct dma_pool *dma_pool_create (const char *name, struct device *dev,
        size_t size, size_t align, size_t allocation)
{
        struct dma_pool                *retval;
 
        if (align == 0)
                align = 1;
        if (size == 0)
                return NULL;
        else if (size < align)
                size = align;
        else if ((size % align) != 0) {//
對齊處理
                size += align + 1;
                size &= ~(align - 1);
        }
//若是一致內存塊比頁大,是分配爲一致內存塊大小,不然,分配爲頁大小
        if (allocation == 0) {
                if (PAGE_SIZE < size)//
頁比一致內存塊小
                        allocation = size;
                else
                        allocation = PAGE_SIZE;//
頁大小
                // FIXME: round up for less fragmentation
        } else if (allocation < size)
                return NULL;
//分配dma_pool結構對象空間
        if (!(retval = kmalloc (sizeof *retval, SLAB_KERNEL)))
                return retval;
 
        strlcpy (retval->name, name, sizeof retval->name);
 
        retval->dev = dev;
//初始化dma_pool結構對象retval
        INIT_LIST_HEAD (&retval->page_list);//
初始化頁鏈表
        spin_lock_init (&retval->lock);
        retval->size = size;
        retval->allocation = allocation;
        retval->blocks_per_page = allocation / size;
        init_waitqueue_head (&retval->waitq);//
初始化等待隊列
 
        if (dev) {//
設備存在時
                down (&pools_lock);
                if (list_empty (&dev->dma_pools))
//給設備建立sysfs文件系統屬性文件
                        device_create_file (dev, &dev_attr_pools);
                /* note:  not currently insisting "name" be unique */
                list_add (&retval->pools, &dev->dma_pools);//
DMA池加到dev
                up (&pools_lock);
        } else
                INIT_LIST_HEAD (&retval->pools);
 
        return retval;
}

函數dma_pool_alloc從DMA池中分配一塊一致內存,其參數pool是將產生塊的DMA池,參數mem_flags是GFP_*位掩碼,參數 handle是指向塊的DMA地址,函數dma_pool_alloc返回當前沒用的塊的內核虛擬地址,並經過handle給出它的DMA地址,若是內存 塊不能被分配,返回null。

函數dma_pool_alloc包裹了dma_alloc_coherent頁分配器,這樣小塊更容易被總線的主控制器使用。這可能共享slab分配器的內容。

函數dma_pool_alloc分析以下(在drivers/base/dmapool.c中):

void *dma_pool_alloc (struct dma_pool *pool, int mem_flags, dma_addr_t *handle)
{
        unsigned long                flags;
        struct dma_page                *page;
        int                        map, block;
        size_t                        offset;
        void                        *retval;
 
restart:
        spin_lock_irqsave (&pool->lock, flags);
        list_for_each_entry(page, &pool->page_list, page_list) {
                int                i;
                /* only cachable accesses here ... */
        //
遍歷一頁的每塊,而每塊又以32字節遞增
                for (map = 0, i = 0;
                                i < pool->blocks_per_page;//
每頁的塊數
                                i += BITS_PER_LONG, map++) {// BITS_PER_LONG
定義爲32
                        if (page->bitmap [map] == 0)
                                continue;
                        block = ffz (~ page->bitmap [map]);//
找出第一個0
                        if ((i + block) < pool->blocks_per_page) {
                                clear_bit (block, &page->bitmap [map]);
//獲得相對於頁邊界的偏移
                                offset = (BITS_PER_LONG * map) + block;
                                offset *= pool->size;
                                goto ready;
                        }
                }
        }
//給DMA池分配dma_page結構空間,加入到pool->page_list
鏈表,
//並做DMA一致映射,它包括分配給DMA池一頁。
// SLAB_ATOMIC表示調用 kmalloc(GFP_ATOMIC)直到失敗爲止,
//而後它等待內核釋放若干頁面,接下來再一次進行分配。
        if (!(page = pool_alloc_page (pool, SLAB_ATOMIC))) {
                if (mem_flags & __GFP_WAIT) {
                        DECLARE_WAITQUEUE (wait, current);
 
                        current->state = TASK_INTERRUPTIBLE;
                        add_wait_queue (&pool->waitq, &wait);
                        spin_unlock_irqrestore (&pool->lock, flags);
 
                        schedule_timeout (POOL_TIMEOUT_JIFFIES);
 
                        remove_wait_queue (&pool->waitq, &wait);
                        goto restart;
                }
                retval = NULL;
                goto done;
        }
 
        clear_bit (0, &page->bitmap [0]);
        offset = 0;
ready:
        page->in_use++;
        retval = offset + page->vaddr;//
返回虛擬地址
        *handle = offset + page->dma;//
相對DMA地址
#ifdef        CONFIG_DEBUG_SLAB
        memset (retval, POOL_POISON_ALLOCATED, pool->size);
#endif
done:
        spin_unlock_irqrestore (&pool->lock, flags);
        return retval;
}

一個簡單的使用DMA 例子

示例:下面是一個簡單的使用DMA進行傳輸的驅動程序,它是一個假想的設備,只列出DMA相關的部分來講明驅動程序中如何使用DMA的。

函數dad_transfer是設置DMA對內存buffer的傳輸操做函數,它使用流式映射將buffer的虛擬地址轉換到物理地址,設置好DMA控制器,而後開始傳輸數據。

int dad_transfer(struct dad_dev *dev, int write, void *buffer, 
                size_t count) 

   dma_addr_t bus_addr; 
   unsigned long flags; 
 
   /* Map the buffer for DMA */ 
   dev->dma_dir = (write ? PCI_DMA_TODEVICE : PCI_DMA_FROMDEVICE); 
   dev->dma_size = count;
 //
流式映射,將buffer的虛擬地址轉化成物理地址
   bus_addr = pci_map_single(dev->pci_dev, buffer, count, 
                             dev->dma_dir); 
   dev->dma_addr = bus_addr; //DMA
傳送的buffer物理地址
 
   //
將操做控制寫入到DMA控制器寄存器,從而創建起設備 
   writeb(dev->registers.command, DAD_CMD_DISABLEDMA); 
//設置傳輸方向--
讀仍是寫
   writeb(dev->registers.command, write ? DAD_CMD_WR : DAD_CMD_RD); 
   writel(dev->registers.addr, cpu_to_le32(bus_addr));//buffer
物理地址 
   writel(dev->registers.len, cpu_to_le32(count)); //
傳輸的字節數
 
   //
開始激活DMA進行數據傳輸操做 
   writeb(dev->registers.command, DAD_CMD_ENABLEDMA); 
   return 0; 
}

函數dad_interrupt是中斷處理函數,當DMA傳輸完時,調用這個中斷函數來取消buffer上的DMA映射,從而讓內核程序能夠訪問這個buffer。

void dad_interrupt(int irq, void *dev_id, struct pt_regs *regs)

{

  struct dad_dev *dev = (struct dad_dev *) dev_id;

 

  /* Make sure it's really our device interrupting */

 

  /* Unmap the DMA buffer */ 
  pci_unmap_single(dev->pci_dev, dev->dma_addr, dev->dma_size, 
       dev->dma_dir);

 

  /* Only now is it safe to access the buffer, copy to user, etc. */ 
  ...

}

函數dad_open打開設備,此時應申請中斷號及DMA通道。

int dad_open (struct inode *inode, struct file *filp)

{

  struct dad_device *my_device;

 

  // SA_INTERRUPT表示快速中斷處理且不支持共享 IRQ 信號線
  if ( (error = request_irq(my_device.irq, dad_interrupt, 
                            SA_INTERRUPT, "dad", NULL)) ) 
      return error; /* or implement blocking open */

 

  if ( (error = request_dma(my_device.dma, "dad")) ) { 
      free_irq(my_device.irq, NULL); 
      return error; /* or implement blocking open */ 
  }

 

  return 0;

}

在與open 相對應的 close 函數中應該釋放DMA及中斷號。

void dad_close (struct inode *inode, struct file *filp)

{

  struct dad_device *my_device; 
  free_dma(my_device.dma); 
  free_irq(my_device.irq, NULL); 
  ……

}

函數dad_dma_prepare初始化DMA控制器,設置DMA控制器的寄存器的值,爲 DMA 傳輸做準備。

int dad_dma_prepare(int channel, int mode, unsigned int buf,

                  unsigned int count)

{

  unsigned long flags;

 

  flags = claim_dma_lock(); 
  disable_dma(channel); 
  clear_dma_ff(channel); 
  set_dma_mode(channel, mode); 
  set_dma_addr(channel, virt_to_bus(buf)); 
  set_dma_count(channel, count); 
  enable_dma(channel); 
  release_dma_lock(flags);

 

  return 0;

}

函數dad_dma_isdone用來檢查 DMA 傳輸是否成功結束。

int dad_dma_isdone(int channel)  {  int residue;  unsigned long flags = claim_dma_lock ();  residue = get_dma_residue(channel);  release_dma_lock(flags);  return (residue == 0);  }

相關文章
相關標籤/搜索