DMA允許外圍設(shè)備和主內(nèi)存之間直接傳輸 I/O 數(shù)據(jù), DMA 依賴(lài)于系統(tǒng)。每一種體系結(jié)構(gòu)DMA傳輸不同,編程接口也不同。 數(shù)據(jù)傳輸可以以?xún)煞N方式觸發(fā):一種軟件請(qǐng)求數(shù)據(jù),另一種由硬件異步傳輸。 在第一種情況下,調(diào)用的步驟可以概括如下(以read為例): (1)在進(jìn)程調(diào)用 read 時(shí),驅(qū)動(dòng)程序的方法分配一個(gè) DMA 緩沖區(qū),隨后指示硬件傳送它的數(shù)據(jù)。進(jìn)程進(jìn)入睡眠。 (2)硬件將數(shù)據(jù)寫(xiě)入 DMA 緩沖區(qū)并在完成時(shí)產(chǎn)生一個(gè)中斷。 (3)中斷處理程序獲得輸入數(shù)據(jù),應(yīng)答中斷,最后喚醒進(jìn)程,該進(jìn)程現(xiàn)在可以讀取數(shù)據(jù)了。 第二種情形是在 DMA 被異步使用時(shí)發(fā)生的。以數(shù)據(jù)采集設(shè)備為例: (1)硬件發(fā)出中斷來(lái)通知新的數(shù)據(jù)已經(jīng)到達(dá)。 (2)中斷處理程序分配一個(gè)DMA緩沖區(qū)。 (3)外圍設(shè)備將數(shù)據(jù)寫(xiě)入緩沖區(qū),然后在完成時(shí)發(fā)出另一個(gè)中斷。 (4)處理程序利用DMA分發(fā)新的數(shù)據(jù),喚醒任何相關(guān)進(jìn)程。 網(wǎng)卡傳輸也是如此,網(wǎng)卡有一個(gè)循環(huán)緩沖區(qū)(通常叫做 DMA 環(huán)形緩沖區(qū))建立在與處理器共享的內(nèi)存中。每一個(gè)輸入數(shù)據(jù)包被放置在環(huán)形緩沖區(qū)中下一個(gè)可用緩沖區(qū),并且發(fā)出中斷。然后驅(qū)動(dòng)程序?qū)⒕W(wǎng)絡(luò)數(shù)據(jù)包傳給內(nèi)核的其 它部分處理,并在環(huán)形緩沖區(qū)中放置一個(gè)新的 DMA 緩沖區(qū)。 驅(qū)動(dòng)程序在初始化時(shí)分配DMA緩沖區(qū),并使用它們直到停止運(yùn)行。 DMA控制器依賴(lài)于平臺(tái)硬件,這里只對(duì)i386的8237 DMA控制器做簡(jiǎn)單的說(shuō)明,它有兩個(gè)控制器,8個(gè)通道,具體說(shuō)明如下: 控制器1: 通道0-3,字節(jié)操作, 端口為 00-1F 控制器2: 通道 4-7, 字操作, 端口咪 C0-DF - 所有寄存器是8 bit,與傳輸大小無(wú)關(guān)。 - 通道 4 被用來(lái)將控制器1與控制器2級(jí)聯(lián)起來(lái)。 - 通道 0-3 是字節(jié)操作,地址/計(jì)數(shù)都是字節(jié)的。 - 通道 5-7 是字操作,地址/計(jì)數(shù)都是以字為單位的。 - 傳輸器對(duì)于(0-3通道)必須不超過(guò)64K的物理邊界,對(duì)于5-7必須不超過(guò)128K邊界。 - 對(duì)于5-7通道page registers 不用數(shù)據(jù) bit 0, 代表128K頁(yè) - 對(duì)于0-3通道page registers 使用 bit 0, 表示 64K頁(yè) DMA 傳輸器限制在低于16M物理內(nèi)存里。裝入寄存器的地址必須是物理地址,而不是邏輯地址。 對(duì)于0-3通道來(lái)說(shuō)地址對(duì)寄存器的映射如下:A23 ... A16 A15 ... A8 A7 ... A0 (物理地址) | ... | | ... | | ... | | ... | | ... | | ... | | ... | | ... | | ... | P7 ... P0 A7 ... A0 A7 ... A0 | Page | Addr MSB | Addr LSB | (DMA 地址寄存器) 對(duì)于5-7通道來(lái)說(shuō)地址對(duì)寄存器的映射如下: A23 ... A17 A16 A15 ... A9 A8 A7 ... A1 A0 (物理地址)| ... | \ \ ... \ \ \ ... \ | ... | \ \ ... \ \ \ ... \ (沒(méi)用) | ... | \ \ ... \ \ \ ... P7 ... P1 (0) A7 A6 ... A0 A7 A6 ... A0| Page | Addr MSB | Addr LSB | (DMA 地址寄存器) 通道 5-7 傳輸以字為單位, 地址和計(jì)數(shù)都必須是以字對(duì)齊的。 在include/asm-i386/dma.h中有i386平臺(tái)的8237 DMA控制器的各處寄存器的地址及寄存器的定義,這里只對(duì)控制寄存器加以說(shuō)明: DMA Channel Control/Status Register (DCSRX) 第31位 表明是否開(kāi)始 第30位 選定Descriptor和Non-Descriptor模式 第29位 判斷有無(wú)中斷 第8位 請(qǐng)求處理 (Request Pending) 第3位 Channel是否運(yùn)行 第2位 當(dāng)前數(shù)據(jù)交換是否完成 第1位 是否由Descriptor產(chǎn)生中斷 第0位 是否由總線(xiàn)錯(cuò)誤引起中斷 DMA通道使用的地址DMA通道用dma_chan結(jié)構(gòu)數(shù)組表示,這個(gè)結(jié)構(gòu)在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用作級(jí)聯(lián)。DMA 緩沖區(qū)的主要問(wèn)題是,當(dāng)它大于一頁(yè)時(shí),它必須占據(jù)物理內(nèi)存中的連續(xù)頁(yè)。 由于DMA需要連續(xù)的內(nèi)存,因而在引導(dǎo)時(shí)分配內(nèi)存或者為緩沖區(qū)保留物理 RAM 的頂部。在引導(dǎo)時(shí)給內(nèi)核傳遞一個(gè)"mem="參數(shù)可以保留 RAM 的頂部。例如,如果系統(tǒng)有 32MB 內(nèi)存,參數(shù)"mem=31M"阻止內(nèi)核使用最頂部的一兆字節(jié)。稍后,模塊可以使用下面的代碼來(lái)訪問(wèn)這些保留的內(nèi)存: dmabuf = ioremap( 0x1F00000 /* 31M */, 0x100000 /* 1M */); 分配 DMA 空間的方法,代碼調(diào)用 kmalloc(GFP_ATOMIC) 直到失敗為止,然后它等待內(nèi)核釋放若干頁(yè)面,接下來(lái)再一次進(jìn)行分配。最終會(huì)發(fā)現(xiàn)由連續(xù)頁(yè)面組成的DMA 緩沖區(qū)的出現(xiàn)。 一個(gè)使用 DMA 的設(shè)備驅(qū)動(dòng)程序通常會(huì)與連接到接口總線(xiàn)上的硬件通訊,這些硬件使用物理地址,而程序代碼使用虛擬地址?;?DMA 的硬件使用總線(xiàn)地址而不是物理地址,有時(shí),接口總線(xiàn)是通過(guò)將 I/O 地址映射到不同物理地址的橋接電路連接的。甚至某些系統(tǒng)有一個(gè)頁(yè)面映射方案,能夠使任意頁(yè)面在外圍總線(xiàn)上表現(xiàn)為連續(xù)的。 當(dāng)驅(qū)動(dòng)程序需要向一個(gè) I/O 設(shè)備(例如擴(kuò)展板或者DMA控制器)發(fā)送地址信息時(shí),必須使用 virt_to_bus 轉(zhuǎn)換,在接受到來(lái)自連接到總線(xiàn)上硬件的地址信息時(shí),必須使用 bus_to_virt 了。 DMA操作函數(shù)因?yàn)?DMA 控制器是一個(gè)系統(tǒng)級(jí)的資源,所以?xún)?nèi)核協(xié)助處理這一資源。內(nèi)核使用 DMA 注冊(cè)表為 DMA 通道提供了請(qǐng)求/釋放機(jī)制,并且提供了一組函數(shù)在 DMA 控制器中配置通道信息。 DMA 控制器使用函數(shù)request_dma和free_dma來(lái)獲取和釋放 DMA 通道的所有權(quán),請(qǐng)求 DMA 通道應(yīng)在請(qǐng)求了中斷線(xiàn)之后,并且在釋放中斷線(xiàn)之前釋放它。每一個(gè)使用 DMA 的設(shè)備也必須使用中斷信號(hào)線(xiàn),否則就無(wú)法發(fā)出數(shù)據(jù)傳輸完成的通知。這兩個(gè)函數(shù)的聲明列出如下(在kernel/dma.c中):int request_dma(unsigned int channel, const char *name); void free_dma(unsigned int channel); DMA 控制器被dma_spin_lock 的自旋鎖所保護(hù)。使用函數(shù)claim_dma_lock和release_dma_lock對(duì)獲得和釋放自旋鎖。這兩個(gè)函數(shù)的聲明列出如下(在kernel/dma.c中): unsigned long claim_dma_lock(); 獲取 DMA 自旋鎖,該函數(shù)會(huì)阻塞本地處理器上的中斷,因此,其返回值是"標(biāo)志"值,在重新打開(kāi)中斷時(shí)必須使用該值。 void release_dma_lock(unsigned long flags); 釋放 DMA 自旋鎖,并且恢復(fù)以前的中斷狀態(tài)。 DMA 控制器的控制設(shè)置信息由RAM 地址、傳輸?shù)臄?shù)據(jù)(以字節(jié)或字為單位),以及傳輸?shù)姆较蛉糠纸M成。下面是i386平臺(tái)的8237 DMA控制器的操作函數(shù)說(shuō)明(在include/asm-i386/dma.h中),使用這些函數(shù)設(shè)置DMA控制器時(shí),應(yīng)該持有自旋鎖。但在驅(qū)動(dòng)程序做I /O 操作時(shí),不能持有自旋鎖。 void set_dma_mode(unsigned int channel, char mode); 該函數(shù)指出通道從設(shè)備讀(DMA_MODE_WRITE)或?qū)懀―MA_MODE_READ)數(shù)據(jù)方式,當(dāng)mode設(shè)置為 DMA_MODE_CASCADE時(shí),表示釋放對(duì)總線(xiàn)的控制。 void set_dma_addr(unsigned int channel, unsigned int addr); 函數(shù)給 DMA 緩沖區(qū)的地址賦值。該函數(shù)將 addr 的最低 24 位存儲(chǔ)到控制器中。參數(shù) addr 是總線(xiàn)地址。 void set_dma_count(unsigned int channel, unsigned int count);該函數(shù)對(duì)傳輸?shù)淖止?jié)數(shù)賦值。參數(shù) count 也代表 16 位通道的字節(jié)數(shù),在此情況下,這個(gè)數(shù)字必須是偶數(shù)。 除了這些操作函數(shù)外,還有些對(duì)DMA狀態(tài)進(jìn)行控制的工具函數(shù): void disable_dma(unsigned int channel); 該函數(shù)設(shè)置禁止使用DMA 通道。這應(yīng)該在配置 DMA 控制器之前設(shè)置。 void enable_dma(unsigned int channel); 在DMA 通道中包含了合法的數(shù)據(jù)時(shí),該函數(shù)激活DMA 控制器。 int get_dma_residue(unsigned int channel); 該函數(shù)查詢(xún)一個(gè) DMA 傳輸還有多少字節(jié)還沒(méi)傳輸完。函數(shù)返回沒(méi)傳完的字節(jié)數(shù)。當(dāng)傳輸成功時(shí),函數(shù)返回值是0。 void clear_dma_ff(unsigned int channel) 該函數(shù)清除 DMA 觸發(fā)器(flip-flop),該觸發(fā)器用來(lái)控制對(duì) 16 位寄存器的訪問(wèn)??梢酝ㄟ^(guò)兩個(gè)連續(xù)的 8 位操作來(lái)訪問(wèn)這些寄存器,觸發(fā)器被清除時(shí)用來(lái)選擇低字節(jié),觸發(fā)器被置位時(shí)用來(lái)選擇高字節(jié)。在傳輸 8 位后,觸發(fā)器會(huì)自動(dòng)反轉(zhuǎn);在訪問(wèn) DMA 寄存器之前,程序員必須清除觸發(fā)器(將它設(shè)置為某個(gè)已知狀態(tài))。 DMA映射一個(gè)DMA映射就是分配一個(gè) DMA 緩沖區(qū)并為該緩沖區(qū)生成一個(gè)能夠被設(shè)備訪問(wèn)的地址的組合操作。一般情況下,簡(jiǎn)單地調(diào)用函數(shù)virt_to_bus 就設(shè)備總線(xiàn)上的地址,但有些硬件映射寄存器也被設(shè)置在總線(xiàn)硬件中。映射寄存器(mapping register)是一個(gè)類(lèi)似于外圍設(shè)備的虛擬內(nèi)存等價(jià)物。在使用這些寄存器的系統(tǒng)上,外圍設(shè)備有一個(gè)相對(duì)較小的、專(zhuān)用的地址區(qū)段,可以在此區(qū)段執(zhí)行 DMA。通過(guò)映射寄存器,這些地址被重映射到系統(tǒng) RAM。映射寄存器具有一些好的特性,包括使分散的頁(yè)面在設(shè)備地址空間看起來(lái)是連續(xù)的。但不是所有的體系結(jié)構(gòu)都有映射寄存器,特別地,PC 平臺(tái)沒(méi)有映射寄存器。 在某些情況下,為設(shè)備設(shè)置有用的地址也意味著需要構(gòu)造一個(gè)反彈(bounce)緩沖區(qū)。例如,當(dāng)驅(qū)動(dòng)程序試圖在一個(gè)不能被外圍設(shè)備訪問(wèn)的地址(一個(gè)高端內(nèi)存地址)上執(zhí)行 DMA 時(shí),反彈緩沖區(qū)被創(chuàng)建。然后,按照需要,數(shù)據(jù)被復(fù)制到反彈緩沖區(qū),或者從反彈緩沖區(qū)復(fù)制。 根據(jù) DMA 緩沖區(qū)期望保留的時(shí)間長(zhǎng)短,PCI 代碼區(qū)分兩種類(lèi)型的 DMA 映射:
(1)建立一致 DMA 映射函數(shù)pci_alloc_consistent處理緩沖區(qū)的分配和映射,函數(shù)分析如下(在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); } 結(jié)構(gòu)dma_coherent_mem定義了DMA一致性映射的內(nèi)存的地址、大小和標(biāo)識(shí)等。結(jié)構(gòu)dma_coherent_mem列出如下(在arch/i386/kernel/pci-dma.c中): struct dma_coherent_mem { 函數(shù)dma_alloc_coherent分配size字節(jié)的區(qū)域的一致內(nèi)存,得到的dma_handle是指向分配的區(qū)域的地址指針,這個(gè)地址作為區(qū)域的物理基地址。dma_handle是與總線(xiàn)一樣的位寬的無(wú)符號(hào)整數(shù)。 函數(shù)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; //若是設(shè)備,得到設(shè)備的dma內(nèi)存區(qū)域,即mem= dev->dma_mem struct dma_coherent_mem *mem = dev dev->dma_mem : NULL; int order = get_order(size);//將size轉(zhuǎn)換成order,即 //忽略特定的區(qū)域,因而忽略這兩個(gè)標(biāo)識(shí) gfp &= ~(__GFP_DMA | __GFP_HIGHMEM); if (mem) {//設(shè)備的DMA映射,mem= dev->dma_mem //找到mem對(duì)應(yīng)的頁(yè) 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; } //不是設(shè)備的DMA映射 if (dev == NULL || (dev->coherent_dma_mask < 0xffffffff)) gfp |= GFP_DMA; //分配空閑頁(yè) ret = (void *)__get_free_pages(gfp, order); if (ret != NULL) { memset(ret, 0, size);//清0 *dma_handle = virt_to_phys(ret);//得到物理地址 } return ret; } 當(dāng)不再需要緩沖區(qū)時(shí)(通常在模塊卸載時(shí)),應(yīng)該調(diào)用函數(shù) pci_free_consitent 將它返還給系統(tǒng)。 (2)建立流式 DMA 映射在流式 DMA 映射的操作中,緩沖區(qū)傳送方向應(yīng)匹配于映射時(shí)給定的方向值。緩沖區(qū)被映射后,它就屬于設(shè)備而不再屬于處理器了。在緩沖區(qū)調(diào)用函數(shù)pci_unmap_single撤銷(xiāo)映射之前,驅(qū)動(dòng)程序不應(yīng)該觸及其內(nèi)容。 在緩沖區(qū)為 DMA 映射時(shí),內(nèi)核必須確保緩沖區(qū)中所有的數(shù)據(jù)已經(jīng)被實(shí)際寫(xiě)到內(nèi)存??赡苡行?shù)據(jù)還會(huì)保留在處理器的高速緩沖存儲(chǔ)器中,因此必須顯式刷新。在刷新之后,由處理器寫(xiě)入緩沖區(qū)的數(shù)據(jù)對(duì)設(shè)備來(lái)說(shuō)也許是不可見(jiàn)的。 如果欲映射的緩沖區(qū)位于設(shè)備不能訪問(wèn)的內(nèi)存區(qū)段時(shí),某些體系結(jié)構(gòu)僅僅會(huì)操作失敗,而其它的體系結(jié)構(gòu)會(huì)創(chuàng)建一個(gè)反彈緩沖區(qū)。反彈緩沖區(qū)是被設(shè)備訪問(wèn)的獨(dú)立內(nèi)存區(qū)域,反彈緩沖區(qū)復(fù)制原始緩沖區(qū)的內(nèi)容。 函數(shù)pci_map_single映射單個(gè)用于傳送的緩沖區(qū),返回值是可以傳遞給設(shè)備的總線(xiàn)地址,如果出錯(cuò)的話(huà)就為 NULL。一旦傳送完成,應(yīng)該使用函數(shù)pci_unmap_single 刪除映射。其中,參數(shù)direction為傳輸?shù)姆较?,取值如下? PCI_DMA_TODEVICE 數(shù)據(jù)被發(fā)送到設(shè)備。 PCI_DMA_FROMDEVICE如果數(shù)據(jù)將發(fā)送到 CPU。 PCI_DMA_BIDIRECTIONAL數(shù)據(jù)進(jìn)行兩個(gè)方向的移動(dòng)。 PCI_DMA_NONE 這個(gè)符號(hào)只是為幫助調(diào)試而提供。 函數(shù)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); } 函數(shù)dma_map_single映射一塊處理器虛擬內(nèi)存,這塊虛擬內(nèi)存能被設(shè)備訪問(wèn),返回內(nèi)存的物理地址,函數(shù)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) (3)分散/集中映射分散/集中映射是流式 DMA 映射的一個(gè)特例。它將幾個(gè)緩沖區(qū)集中到一起進(jìn)行一次映射,并在一個(gè) DMA 操作中傳送所有數(shù)據(jù)。這些分散的緩沖區(qū)由分散表結(jié)構(gòu)scatterlist來(lái)描述,多個(gè)分散的緩沖區(qū)的分散表結(jié)構(gòu)組成緩沖區(qū)的struct scatterlist數(shù)組。 分散表結(jié)構(gòu)列出如下(在include/asm-i386/scatterlist.h):struct scatterlist { struct page *page; unsigned int offset; dma_addr_t dma_address; //用在分散/集中操作中的緩沖區(qū)地址 unsigned int length;//該緩沖區(qū)的長(zhǎng)度 }; 每一個(gè)緩沖區(qū)的地址和長(zhǎng)度會(huì)被存儲(chǔ)在 struct scatterlist 項(xiàng)中,但在不同的體系結(jié)構(gòu)中它們?cè)诮Y(jié)構(gòu)中的位置是不同的。下面的兩個(gè)宏定義來(lái)解決平臺(tái)移植性問(wèn)題,這些宏定義應(yīng)該在一個(gè)pci_map_sg 被調(diào)用后使用: //從該分散表項(xiàng)中返回總線(xiàn)地址 函數(shù)pci_map_sg完成分散/集中映射,其返回值是要傳送的 DMA 緩沖區(qū)數(shù);它可能會(huì)小于 nents(也就是傳入的分散表項(xiàng)的數(shù)量),因?yàn)榭赡苡械木彌_區(qū)地址上是相鄰的。一旦傳輸完成,分散/集中映射通過(guò)調(diào)用函數(shù)pci_unmap_sg 來(lái)撤銷(xiāo)映射。 函數(shù)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); //將頁(yè)及頁(yè)偏移地址轉(zhuǎn)化為物理地址 sg[i].dma_address = page_to_phys(sg[i].page) + sg[i].offset; } //可能有些數(shù)據(jù)還會(huì)保留在處理器的高速緩沖存儲(chǔ)器中,因此必須顯式刷新 flush_write_buffers(); return nents; } DMA池許多驅(qū)動(dòng)程序需要又多又小的一致映射內(nèi)存區(qū)域給DMA描述子或I/O緩存buffer,這使用DMA池比用dma_alloc_coherent分 配的一頁(yè)或多頁(yè)內(nèi)存區(qū)域好,DMA池用函數(shù)dma_pool_create創(chuàng)建,用函數(shù)dma_pool_alloc從DMA池中分配一塊一致內(nèi)存,用函 數(shù)dmp_pool_free放內(nèi)存回到DMA池中,使用函數(shù)dma_pool_destory釋放DMA池的資源。 結(jié)構(gòu)dma_pool是DMA池描述結(jié)構(gòu),列出如下:struct dma_pool { /* the pool */ struct list_head page_list;//頁(yè)鏈表 spinlock_t lock; size_t blocks_per_page; //每頁(yè)的塊數(shù) size_t size; //DMA池里的一致內(nèi)存塊的大小 struct device *dev; //將做DMA的設(shè)備 size_t allocation; //分配的沒(méi)有跨越邊界的塊數(shù),是size的整數(shù)倍 char name [32]; //池的名字 wait_queue_head_t waitq; //等待隊(duì)列 struct list_head pools; }; 函數(shù)dma_pool_create給DMA創(chuàng)建一個(gè)一致內(nèi)存塊池,其參數(shù)name是DMA池的名字,用于診斷用,參數(shù)dev是將做DMA的設(shè)備,參數(shù) size是DMA池里的塊的大小,參數(shù)align是塊的對(duì)齊要求,是2的冪,參數(shù)allocation返回沒(méi)有跨越邊界的塊數(shù)(或0)。 函數(shù)dma_pool_create返回創(chuàng)建的帶有要求字符串的DMA池,若創(chuàng)建失敗返回null。對(duì)被給的DMA池,函數(shù) dma_pool_alloc被用來(lái)分配內(nèi)存,這些內(nèi)存都是一致DMA映射,可被設(shè)備訪問(wèn),且沒(méi)有使用緩存刷新機(jī)制,因?yàn)閷?duì)齊原因,分配的塊的實(shí)際尺寸比 請(qǐng)求的大。如果分配非0的內(nèi)存,從函數(shù)dma_pool_alloc返回的對(duì)象將不跨越size邊界(如不跨越4K字節(jié)邊界)。這對(duì)在個(gè)體的DMA傳輸上 有地址限制的設(shè)備來(lái)說(shuō)是有利的。 函數(shù)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) {//對(duì)齊處理 size += align + 1; size &= ~(align - 1); } //如果一致內(nèi)存塊比頁(yè)大,是分配為一致內(nèi)存塊大小,否則,分配為頁(yè)大小 if (allocation == 0) { if (PAGE_SIZE < size)//頁(yè)比一致內(nèi)存塊小 allocation = size; else allocation = PAGE_SIZE;//頁(yè)大小 // FIXME: round up for less fragmentation } else if (allocation < size) return NULL; //分配dma_pool結(jié)構(gòu)對(duì)象空間 if (!(retval = kmalloc (sizeof *retval, SLAB_KERNEL))) return retval; strlcpy (retval->name, name, sizeof retval->name); retval->dev = dev; //初始化dma_pool結(jié)構(gòu)對(duì)象retval INIT_LIST_HEAD (&retval->page_list);//初始化頁(yè)鏈表 spin_lock_init (&retval->lock); retval->size = size; retval->allocation = allocation; retval->blocks_per_page = allocation / size; init_waitqueue_head (&retval->waitq);//初始化等待隊(duì)列 if (dev) {//設(shè)備存在時(shí) down (&pools_lock); if (list_empty (&dev->dma_pools)) //給設(shè)備創(chuàng)建sysfs文件系統(tǒng)屬性文件 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; } 函數(shù)dma_pool_alloc從DMA池中分配一塊一致內(nèi)存,其參數(shù)pool是將產(chǎn)生塊的DMA池,參數(shù)mem_flags是GFP_*位掩碼,參數(shù) handle是指向塊的DMA地址,函數(shù)dma_pool_alloc返回當(dāng)前沒(méi)用的塊的內(nèi)核虛擬地址,并通過(guò)handle給出它的DMA地址,如果內(nèi)存 塊不能被分配,返回null。 函數(shù)dma_pool_alloc包裹了dma_alloc_coherent頁(yè)分配器,這樣小塊更容易被總線(xiàn)的主控制器使用。這可能共享slab分配器的內(nèi)容。 函數(shù)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 ... */ //遍歷一頁(yè)的每塊,而每塊又以32字節(jié)遞增 for (map = 0, i = 0; i < pool->blocks_per_page; //每頁(yè)的塊數(shù) i += BITS_PER_LONG, map++) { // BITS_PER_LONG定義為32 if (page->bitmap [map] == 0) continue; block = ffz (~ page->bitmap [map]);//找出第一個(gè)0 if ((i + block) < pool->blocks_per_page) { clear_bit (block, &page->bitmap [map]); //得到相對(duì)于頁(yè)邊界的偏移 offset = (BITS_PER_LONG * map) + block; offset *= pool->size; goto ready; } } } //給DMA池分配dma_page結(jié)構(gòu)空間,加入到pool->page_list鏈表, //并作DMA一致映射,它包括分配給DMA池一頁(yè)。 // SLAB_ATOMIC表示調(diào)用 kmalloc(GFP_ATOMIC) 直到失敗為止, //然后它等待內(nèi)核釋放若干頁(yè)面,接下來(lái)再一次進(jìn)行分配。 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; //相對(duì)DMA地址 #ifdef CONFIG_DEBUG_SLAB memset (retval, POOL_POISON_ALLOCATED, pool->size); #endif done: spin_unlock_irqrestore (&pool->lock, flags); return retval; } 一個(gè)簡(jiǎn)單的使用DMA 例子示例:下面是一個(gè)簡(jiǎn)單的使用DMA進(jìn)行傳輸?shù)尿?qū)動(dòng)程序,它是一個(gè)假想的設(shè)備,只列出DMA相關(guān)的部分來(lái)說(shuō)明驅(qū)動(dòng)程序中如何使用DMA的。 函數(shù)dad_transfer是設(shè)置DMA對(duì)內(nèi)存buffer的傳輸操作函數(shù),它使用流式映射將buffer的虛擬地址轉(zhuǎn)換到物理地址,設(shè)置好DMA控制器,然后開(kāi)始傳輸數(shù)據(jù)。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的虛擬地址轉(zhuǎn)化成物理地址 bus_addr = pci_map_single(dev->pci_dev, buffer, count, dev->dma_dir); dev->dma_addr = bus_addr; //DMA傳送的buffer物理地址 //將操作控制寫(xiě)入到DMA控制器寄存器,從而建立起設(shè)備 writeb(dev->registers.command, DAD_CMD_DISABLEDMA); //設(shè)置傳輸方向--讀還是寫(xiě) 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)); //傳輸?shù)淖止?jié)數(shù) //開(kāi)始激活DMA進(jìn)行數(shù)據(jù)傳輸操作 writeb(dev->registers.command, DAD_CMD_ENABLEDMA); return 0; } 函數(shù)dad_interrupt是中斷處理函數(shù),當(dāng)DMA傳輸完時(shí),調(diào)用這個(gè)中斷函數(shù)來(lái)取消buffer上的DMA映射,從而讓內(nèi)核程序可以訪問(wèn)這個(gè)buffer。 void dad_interrupt(int irq, void *dev_id, struct pt_regs *regs) 函數(shù)dad_open打開(kāi)設(shè)備,此時(shí)應(yīng)申請(qǐng)中斷號(hào)及DMA通道。 int dad_open (struct inode *inode, struct file *filp) 在與open 相對(duì)應(yīng)的 close 函數(shù)中應(yīng)該釋放DMA及中斷號(hào)。 void dad_close (struct inode *inode, struct file *filp) 函數(shù)dad_dma_prepare初始化DMA控制器,設(shè)置DMA控制器的寄存器的值,為 DMA 傳輸作準(zhǔn)備。 int dad_dma_prepare(int channel, int mode, unsigned int buf,unsigned int count) 函數(shù)dad_dma_isdone用來(lái)檢查 DMA 傳輸是否成功結(jié)束。 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); } |
聯(lián)系客服