直接內(nèi)存存取, 或者 DMA, 是結(jié)束我們的內(nèi)存問(wèn)題概覽的高級(jí)主題. DMA 是硬件機(jī)制允許外設(shè)組件來(lái)直接傳輸它們的 I/O 數(shù)據(jù)到和從主內(nèi)存, 而不需要包含系統(tǒng)處理器. 這種機(jī)制的使用能夠很大提高吞吐量到和從一個(gè)設(shè)備, 因?yàn)榇罅康挠?jì)算開(kāi)銷(xiāo)被削減了.
在介紹程序細(xì)節(jié)之前, 讓我們回顧一個(gè) DMA 傳輸如何發(fā)生的, 只考慮輸入傳輸來(lái)簡(jiǎn)化討論.
數(shù)據(jù)傳輸可由 2 種方法觸發(fā):或者軟件請(qǐng)求數(shù)據(jù)(通過(guò)一個(gè)函數(shù)例如 read)或者硬件異步推數(shù)據(jù)到系統(tǒng).
在第一種情況, 包含的步驟總結(jié)如下:
第 2 種情況到來(lái)是當(dāng) DMA 被異步使用. 例如, 這發(fā)生在數(shù)據(jù)獲取設(shè)備, 它在沒(méi)有人讀它們的時(shí)候也持續(xù)推入數(shù)據(jù). 在這個(gè)情況下, 驅(qū)動(dòng)應(yīng)當(dāng)維護(hù)一個(gè)緩沖以至于后續(xù)的讀調(diào)用能返回所有的累積的數(shù)據(jù)給用戶(hù)空間. 這類(lèi)傳輸包含的步驟有點(diǎn)不同:
異步方法的變體常常在網(wǎng)卡中見(jiàn)到. 這些卡常常期望見(jiàn)到一個(gè)在內(nèi)存中和處理器共享的環(huán)形緩沖(常常被稱(chēng)為一個(gè) DMA 的緩沖); 每個(gè)到來(lái)的報(bào)文被放置在環(huán)中下一個(gè)可用的緩沖, 并且發(fā)出一個(gè)中斷. 驅(qū)動(dòng)接著傳遞網(wǎng)絡(luò)本文到內(nèi)核其他部分并且在環(huán)中放置一個(gè)新 DMA 緩沖.
在所有這些情況中的處理的步驟都強(qiáng)調(diào), 有效的 DMA 處理依賴(lài)中斷報(bào)告. 雖然可能實(shí)現(xiàn) DMA 使用一個(gè)輪詢(xún)驅(qū)動(dòng), 它不可能有意義, 因?yàn)橐粋€(gè)輪詢(xún)驅(qū)動(dòng)可能浪費(fèi) DMA 提供的性能益處超過(guò)更容易的處理器驅(qū)動(dòng)的I/O.[49]
在這里介紹的另一個(gè)相關(guān)項(xiàng)是 DMA 緩沖. DMA 要求設(shè)備驅(qū)動(dòng)來(lái)分配一個(gè)或多個(gè)特殊的適合 DMA 的緩沖. 注意許多驅(qū)動(dòng)分配它們的緩沖在初始化時(shí)并且使用它們直到關(guān)閉 -- 在之前列表中的分配一詞, 意思是"獲得一個(gè)之前分配的緩沖".
本節(jié)涵蓋 DMA 緩沖在底層的分配; 我們稍后介紹一個(gè)高級(jí)接口, 但是來(lái)理解這里展示的內(nèi)容仍是一個(gè)好主意.
隨 DMA 緩沖帶來(lái)的主要問(wèn)題是, 當(dāng)它們大于一頁(yè), 它們必須占據(jù)物理內(nèi)存的連續(xù)頁(yè)因?yàn)樵O(shè)備使用 ISA 或者 PCI 系統(tǒng)總線(xiàn)傳輸數(shù)據(jù), 它們都使用物理地址. 注意有趣的是這個(gè)限制不適用 SBus ( 見(jiàn) 12 章的"SBus"一節(jié) ), 它在外設(shè)總線(xiàn)上使用虛擬地址. 一些體系結(jié)構(gòu)還可以在 PCI 總線(xiàn)上使用虛擬地址, 但是一個(gè)可移植的驅(qū)動(dòng)不能依賴(lài)這個(gè)功能.
盡管 DMA 緩沖可被分配或者在系統(tǒng)啟動(dòng)時(shí)或者在運(yùn)行時(shí), 模塊只可在運(yùn)行時(shí)分配它們的緩沖. (第 8 章介紹這些技術(shù); "獲取大緩沖"一節(jié)涵蓋在系統(tǒng)啟動(dòng)時(shí)分配, 而"kmalloc 的真實(shí)"和"get_free_page 和其友"描述在運(yùn)行時(shí)分配). 驅(qū)動(dòng)編寫(xiě)者必須關(guān)心分配正確的內(nèi)存,當(dāng)它被用做 DMA 操作時(shí); 不是所有內(nèi)存區(qū)是合適的. 特別的, 在一些系統(tǒng)中的一些設(shè)備上高端內(nèi)存可能不為 DMA 工作 - 外設(shè)完全無(wú)法使用高端地址.
在現(xiàn)代總線(xiàn)上的大部分設(shè)備可以處理 32-位 地址, 意思是正常的內(nèi)存分配對(duì)它們是剛剛好的. 一些 PCI 設(shè)備, 但是, 不能實(shí)現(xiàn)完整的 PCI 標(biāo)準(zhǔn)并且不能使用 32-位 地址. 并且 ISA 設(shè)備, 當(dāng)然, 限制只在 24-位 地址.
對(duì)于有這種限制的設(shè)備, 內(nèi)存應(yīng)當(dāng)從 DMA 區(qū)進(jìn)行分配, 通過(guò)添加 GFP_DMA 標(biāo)志到 kmalloc 或者 get_free_pages 調(diào)用. 當(dāng)這個(gè)標(biāo)志存在, 只有可用 24-位 尋址的內(nèi)存被分配. 另一種選擇, 你可以使用通用的 DMA 層( 我們馬上討論這個(gè) )來(lái)分配緩沖以解決你的設(shè)備的限制.
我們已見(jiàn)到 get_free_pages 如何分配直到幾個(gè) MByte (由于 order 可以直到 MAX_ORDER, 當(dāng)前是 11), 但是高級(jí)數(shù)的請(qǐng)求容易失敗當(dāng)請(qǐng)求的緩沖遠(yuǎn)遠(yuǎn)小于 128 KB, 因?yàn)橄到y(tǒng)內(nèi)存時(shí)間長(zhǎng)了變得碎裂.[50]
當(dāng)內(nèi)核無(wú)法返回請(qǐng)求數(shù)量的內(nèi)存或者當(dāng)你需要多于 128 KB(例如, 一個(gè)通常的 PCI 幀抓取的請(qǐng)求), 一個(gè)替代返回 -ENOMEM 的做法是在啟動(dòng)時(shí)分配內(nèi)存或者保留物理 RAM 的頂部給你的緩沖. 我們?cè)诘?8 章的 "獲得大量緩沖" 一節(jié)描述在啟動(dòng)時(shí)間分配, 但是它對(duì)模塊是不可用的. 保留 RAM 的頂部是通過(guò)在啟動(dòng)時(shí)傳遞一個(gè) mem= 參數(shù)給內(nèi)核實(shí)現(xiàn)的. 例如, 如果你有 256 MB, 參數(shù) mem=255M 使內(nèi)核不使用頂部的 MByte. 你的模塊可能后來(lái)使用下列代碼來(lái)獲得對(duì)這個(gè)內(nèi)存的存取:
dmabuf = ioremap (0xFF00000 /* 255M */, 0x100000 /* 1M */);
分配器, 配合本書(shū)的例子代碼的一部分, 提供了一個(gè)簡(jiǎn)單的 API 來(lái)探測(cè)和管理這樣的保留 RAM 并且已在幾個(gè)體系上被成功使用. 但是, 這個(gè)技巧當(dāng)你有一個(gè)高內(nèi)存系統(tǒng)時(shí)無(wú)效(即, 一個(gè)有比適合 CPU 地址空間更多的物理內(nèi)存的系統(tǒng) ).
當(dāng)然, 另一個(gè)選項(xiàng), 是使用 GFP_NOFAIL 來(lái)分配你的緩沖. 這個(gè)方法, 但是, 確實(shí)嚴(yán)重地對(duì)內(nèi)存管理子系統(tǒng)有壓力, 并且它冒鎖住系統(tǒng)的風(fēng)險(xiǎn); 最好是避免除非確實(shí)沒(méi)有其他方法.
如果你分配一個(gè)大 DMA 緩沖到這樣的長(zhǎng)度, 但是, 值得想一下替代的方法. 如果你的設(shè)備可以做發(fā)散/匯聚 I/O, 你可以分配你的緩沖以更小的片段并且讓設(shè)備做其他的. 發(fā)散/匯聚 I/O 也可以用當(dāng)進(jìn)行直接 I/O 到用戶(hù)空間時(shí), 它可能是最好地解決方法當(dāng)需要一個(gè)真正大緩沖時(shí).
一個(gè)使用 DMA 的設(shè)備驅(qū)動(dòng)必須和連接到接口總線(xiàn)的硬件通訊, 總線(xiàn)使用物理地址, 而程序代碼使用虛擬地址.
事實(shí)上, 情況比這個(gè)稍微有些復(fù)雜. 基于DMA 的硬件使用總線(xiàn)地址, 而不是物理地址. 盡管 ISA 和 PCI 總線(xiàn)地址在 PC 上完全是物理地址, 這對(duì)每個(gè)平臺(tái)卻不總是真的. 有時(shí)接口總線(xiàn)被通過(guò)橋接電路連接, 它映射 I/O 地址到不同的物理地址. 一些系統(tǒng)甚至有一個(gè)頁(yè)映射機(jī)制, 使任意的頁(yè)連續(xù)出現(xiàn)在外設(shè)總線(xiàn).
在最低級(jí)別(再次, 我們將馬上查看一個(gè)高級(jí)解決方法), Linux 內(nèi)核提供一個(gè)可移植的方法, 通過(guò)輸出下列函數(shù), 在 <asm/io.h> 定義. 這些函數(shù)的使用不被推薦, 因?yàn)樗鼈冎辉谟蟹浅:?jiǎn)單的 I/O 體系的系統(tǒng)上正常工作; 但是, 你可能遇到它們當(dāng)使用內(nèi)核代碼時(shí).
unsigned long virt_to_bus(volatile void *address);
void *bus_to_virt(unsigned long address);
這些函數(shù)進(jìn)行一個(gè)簡(jiǎn)單的轉(zhuǎn)換在內(nèi)核邏輯地址和總線(xiàn)地址之間. 它們?cè)谠S多情況下不工作, 一個(gè) I/O 內(nèi)存管理單元必須被編程的地方或者必須使用反彈緩沖的地方. 做這個(gè)轉(zhuǎn)換的正確方法是使用通用的 DMA 層, 因此我們現(xiàn)在轉(zhuǎn)移到這個(gè)主題.
DMA 操作, 最后, 下到分配一個(gè)緩沖并且傳遞總線(xiàn)地址到你的設(shè)備. 但是, 編寫(xiě)在所有體系上安全并正確進(jìn)行 DMA 的可移植啟動(dòng)的任務(wù)比想象的要難. 不同的系統(tǒng)有不同的概念, 關(guān)于緩存一致性應(yīng)當(dāng)如何工作的概念; 如果你不正確處理這個(gè)問(wèn)題, 你的驅(qū)動(dòng)可能破壞內(nèi)存. 一些系統(tǒng)有復(fù)雜的總線(xiàn)硬件, 它使 DMA 任務(wù)更容易 - 或者更難. 并且不是所有的系統(tǒng)可以在內(nèi)存所有部分進(jìn)行 DMA. 幸運(yùn)的是, 內(nèi)核提供了一個(gè)總線(xiàn)和體系獨(dú)立的 DMA 層來(lái)對(duì)驅(qū)動(dòng)作者隱藏大部分這些問(wèn)題. 我們非常鼓勵(lì)你來(lái)使用這個(gè)層來(lái) DMA 操作, 在任何你編寫(xiě)的驅(qū)動(dòng)中.
下面的許多函數(shù)需要一個(gè)指向 struct device 的指針. 這個(gè)結(jié)構(gòu)是 Linux 設(shè)備模型中設(shè)備的低級(jí)表示. 它不是驅(qū)動(dòng)常常必須直接使用的東西, 但是你確實(shí)需要它當(dāng)使用通用 DMA 層時(shí). 常常地, 你可發(fā)現(xiàn)這個(gè)結(jié)構(gòu), 深埋在描述你的設(shè)備的總線(xiàn). 例如, 它可在 struct pci_device 或者 struct usb_device 中發(fā)現(xiàn)它作為 dev 成員. 設(shè)備結(jié)構(gòu)在 14 章中詳細(xì)描述.
使用下面函數(shù)的驅(qū)動(dòng)應(yīng)當(dāng)包含 <linux/dma-mapping.h>.
在嘗試 DMA 之前必須回答的第一個(gè)問(wèn)題是給定設(shè)備是否能夠在當(dāng)前主機(jī)上做這樣的操作. 許多設(shè)備受限于它們能夠?qū)ぶ返膬?nèi)存范圍, 因?yàn)樵S多理由. 缺省地, 內(nèi)核假定你的設(shè)備能夠?qū)θ魏?32-位 地址進(jìn)行 DMA. 如果不是這樣, 你應(yīng)當(dāng)通知內(nèi)核這個(gè)事實(shí), 使用一個(gè)調(diào)用:
int dma_set_mask(struct device *dev, u64 mask);
mask 應(yīng)當(dāng)顯示你的設(shè)備能夠?qū)ぶ返奈? 如果它被限制到 24 位, 例如, 你要傳遞 mask 作為 0x0FFFFFF. 返回值是非零如果使用給定的 mask 可以 DMA; 如果 dma_set_mask 返回 0, 你不能對(duì)這個(gè)設(shè)備使用 DMA 操作. 因此, 設(shè)備的驅(qū)動(dòng)中的初始化代碼限制到 24-位 DMA 操作可能看來(lái)如:
if (dma_set_mask (dev, 0xffffff))
card->use_dma = 1;
else
{
card->use_dma = 0; /* We'll have to live without DMA */
printk (KERN_WARN, "mydev: DMA not supported\n");
}
再次, 如果你的設(shè)備支持正常的, 32-位 DMA 操作, 沒(méi)有必要調(diào)用 dma_set_mask.
一個(gè) DMA 映射是分配一個(gè) DMA 緩沖和產(chǎn)生一個(gè)設(shè)備可以存取的地址的結(jié)合. 它試圖使用一個(gè)簡(jiǎn)單的對(duì) virt_to_bus 的調(diào)用來(lái)獲得這個(gè)地址, 但是有充分的理由來(lái)避免那個(gè)方法. 它們中的第一個(gè)是合理的硬件帶有一個(gè) IOMMU 來(lái)為總線(xiàn)提供一套映射寄存器. IOMMU 可為任何物理內(nèi)存安排來(lái)出現(xiàn)在設(shè)備可存取的地址范圍內(nèi), 并且它可使物理上散布的緩沖對(duì)設(shè)備看來(lái)是連續(xù)的. 使用 IOMMU 需要使用通用的 DMA 層; virt_to_bus 不負(fù)責(zé)這個(gè)任務(wù).
注意不是所有的體系都有一個(gè) IOMMU; 特別的, 流行的 x86 平臺(tái)沒(méi)有 IOMMU 支持. 一個(gè)正確編寫(xiě)的驅(qū)動(dòng)不需要知道它在之上運(yùn)行的 I/O 支持硬件, 但是.
為設(shè)備設(shè)置一個(gè)有用的地址可能也, 在某些情況下, 要求一個(gè)反彈緩沖的建立. 反彈緩沖是當(dāng)一個(gè)驅(qū)動(dòng)試圖在一個(gè)外設(shè)不能達(dá)到的地址上進(jìn)行 DMA 時(shí)創(chuàng)建的, 比如一個(gè)高內(nèi)存地址. 數(shù)據(jù)接著根據(jù)需要被拷貝到和從反彈緩沖. 無(wú)需說(shuō), 反彈緩沖的使用能拖慢事情, 但是有時(shí)沒(méi)有其他選擇.
DMA 映射也必須解決緩存一致性問(wèn)題. 記住現(xiàn)代處理器保持最近存取的內(nèi)存區(qū)的拷貝在一個(gè)快速的本地緩沖中; 如果沒(méi)有這個(gè)緩存, 合理的性能是不可能的. 如果你的設(shè)備改變主存一個(gè)區(qū), 會(huì)強(qiáng)制使任何包含那個(gè)區(qū)的處理器緩存被失效; 負(fù)責(zé)處理器可能使用不正確的主存映象, 并且導(dǎo)致數(shù)據(jù)破壞. 類(lèi)似地, 當(dāng)你的設(shè)備使用 DMA 來(lái)從主存中讀取數(shù)據(jù), 任何對(duì)那個(gè)駐留在處理器緩存的內(nèi)存的改變必須首先被刷新. 這些緩存一致性問(wèn)題可以產(chǎn)生無(wú)頭的模糊和難尋的錯(cuò)誤, 如果編程者不小心. 一個(gè)體系在硬件中管理緩存一致性, 但是其他的要求軟件支持. 通用的 DMA 層深入很多來(lái)保證在所有體系上事情都正確工作, 但是, 如同我們將見(jiàn)到的, 正確的行為要求符合一些規(guī)則.
DMA 映射設(shè)置一個(gè)新類(lèi)型, dma_addr_t, 來(lái)代表總線(xiàn)地址. 類(lèi)型 dma_addr_t 的變量應(yīng)當(dāng)被驅(qū)動(dòng)當(dāng)作不透明的; 唯一可允許的操作是傳遞它們到 DMA 支持過(guò)程和設(shè)備自身. 作為一個(gè)總線(xiàn)地址, dma_addr_t 可導(dǎo)致不期望的問(wèn)題如果被 CPU 直接使用.
PCI 代碼在 2 類(lèi) DMA 映射中明顯不同, 依賴(lài) DMA 緩沖被期望停留多長(zhǎng)時(shí)間:
Coherent DMA mappings
連貫的 DMA 映射. 這些映射常常在驅(qū)動(dòng)的生命期內(nèi)存在. 一個(gè)連貫的緩沖必須是同時(shí)對(duì) CPU 和外設(shè)可用(其他的映射類(lèi)型, 如同我們之后將看到的, 在任何給定時(shí)間只對(duì)一個(gè)或另一個(gè)可用). 結(jié)果, 一致的映射必須在緩沖一致的內(nèi)存. 一致的映射建立和使用可能是昂貴的.
Streaming DMA mappings
流 DMA 映射. 流映射常常為一個(gè)單個(gè)操作建立. 一些體系當(dāng)使用流映射時(shí)允許大的優(yōu)化, 如我們所見(jiàn), 但是這些映射也服從一個(gè)更嚴(yán)格的關(guān)于如何存取它們的規(guī)則. 內(nèi)核開(kāi)發(fā)者建議使用一致映射而不是流映射在任何可能的時(shí)候. 這個(gè)建議有 2 個(gè)原因. 第一個(gè), 在支持映射寄存器的系統(tǒng)上, 每個(gè) DMA 映射在總線(xiàn)上使用它們一個(gè)或多個(gè). 一致映射, 有長(zhǎng)的生命周期, 可以長(zhǎng)時(shí)間獨(dú)占這些寄存器, 甚至當(dāng)它們不在使用時(shí). 另外一個(gè)原因是, 在某些硬件上, 流映射可以用無(wú)法在一致映射中使用的方法來(lái)優(yōu)化.
這 2 種映射類(lèi)型必須以不同的方式操作; 是時(shí)候看看細(xì)節(jié)了.
一個(gè)驅(qū)動(dòng)可以建立一個(gè)一致映射, 使用對(duì) dma_alloc_coherent 的調(diào)用:
void *dma_alloc_coherent(struct device *dev, size_t size, dma_addr_t *dma_handle, int flag);
這個(gè)函數(shù)處理緩沖的分配和映射. 前 2 個(gè)參數(shù)是設(shè)備結(jié)果和需要的緩沖大小. 這個(gè)函數(shù)返回 DMA 映射的結(jié)果在 2 個(gè)地方. 來(lái)自這個(gè)函數(shù)的返回值是緩沖的一個(gè)內(nèi)核虛擬地址, 它可被驅(qū)動(dòng)使用; 其間相關(guān)的總線(xiàn)地址在 dma_handle 中返回. 分配在這個(gè)函數(shù)中被處理以至緩沖被放置在一個(gè)可以使用 DMA 的位置; 常常地內(nèi)存只是使用 get_freepages 來(lái)分配(但是注意大小是以字節(jié)計(jì)的, 而不是一個(gè) order 值). flag 參數(shù)是通常的 GFP 值來(lái)描述內(nèi)存如何被分配; 常常應(yīng)當(dāng)是 GFP_KERNEL (常常) 或者 GFP_ATOMIC (當(dāng)在原子上下文中運(yùn)行時(shí)).
當(dāng)不再需要緩沖(常常在模塊卸載時(shí)), 它應(yīng)當(dāng)被返回給系統(tǒng), 使用 dma_free_coherent:
void dma_free_coherent(struct device *dev, size_t size,
void *vaddr, dma_addr_t dma_handle);
注意, 這個(gè)函數(shù)象許多通常的 DMA 函數(shù), 需要提供所有的大小, CPU 地址, 和 總線(xiàn)地址參數(shù).
一個(gè) DMA池 是分配小的, 一致DMA映射的分配機(jī)制. 從 dma_alloc_coherent 獲得的映射可能有一頁(yè)的最小大小. 如果你的驅(qū)動(dòng)需要比那個(gè)更小的 DMA 區(qū)域, 你應(yīng)當(dāng)可能使用一個(gè) DMA 池. DMA 池也在這種情況下有用, 當(dāng)你可能試圖對(duì)嵌在一個(gè)大結(jié)構(gòu)中的小區(qū)域進(jìn)行 DMA 操作. 一些非常模糊的驅(qū)動(dòng)錯(cuò)誤已被追蹤到緩存一致性問(wèn)題, 在靠近小 DMA 區(qū)域的結(jié)構(gòu)成員. 為避免這個(gè)問(wèn)題, 你應(yīng)當(dāng)一直明確分配進(jìn)行 DMA 操作的區(qū)域, 和其他的非 DMA 數(shù)據(jù)結(jié)構(gòu)分開(kāi).
DMA 池函數(shù)定義在 <linux/dmapool.h>.
一個(gè) DMA 池必須在使用前創(chuàng)建, 使用一個(gè)調(diào)用:
struct dma_pool *dma_pool_create(const char *name, struct device *dev,
size_t size, size_t align,
size_t allocation);
這里, name 是池的名子, dev 是你的設(shè)備結(jié)構(gòu), size 是要從這個(gè)池分配的緩沖區(qū)大小, align 是來(lái)自池的分配要求的硬件對(duì)齊(以字節(jié)表達(dá)的), 以及 allocation是, 如果非零, 一個(gè)分配不應(yīng)當(dāng)越過(guò)的內(nèi)存邊界. 如果 allocation 以 4096 傳遞, 例如, 從池分配的緩沖不越過(guò) 4-KB 邊界.
當(dāng)你用完一個(gè)池, 可被釋放, 用:
void dma_pool_destroy(struct dma_pool *pool);
你應(yīng)當(dāng)返回所有的分配給池, 在銷(xiāo)毀它之前. 分配被用 dma_pool_alloc 處理:
void *dma_pool_alloc(struct dma_pool *pool, int mem_flags, dma_addr_t *handle);
對(duì)這個(gè)調(diào)用, memflags 是常用的 GFP 分配標(biāo)志的設(shè)置. 如果所有都進(jìn)行順利, 一個(gè)內(nèi)存區(qū)(大小是當(dāng)池創(chuàng)建時(shí)指定的)被分配和返回. 至于 dam_alloc_coherent, 結(jié)果 DMA 緩沖地址被返回作為一個(gè)內(nèi)核虛擬地址, 并作為一個(gè)總線(xiàn)地址被存于 handle.
不需要的緩沖應(yīng)當(dāng)返回池, 使用:
void dma_pool_free(struct dma_pool *pool, void *vaddr, dma_addr_t addr);
流映射比一致映射有更復(fù)雜的接口, 有幾個(gè)原因. 這些映射行為使用一個(gè)由驅(qū)動(dòng)已經(jīng)分配的緩沖, 因此, 必須處理它們沒(méi)有選擇的地址. 在一些體系上, 流映射也可以有多個(gè)不連續(xù)的頁(yè)和多部分的"發(fā)散/匯聚"緩沖. 所有這些原因, 流映射有它們自己的一套映射函數(shù).
當(dāng)建立一個(gè)流映射時(shí), 你必須告知內(nèi)核數(shù)據(jù)移向哪個(gè)方向. 一些符號(hào)(enum dam_data_direction 類(lèi)型)已為此定義:
DMA_TO_DEVICEDMA_FROM_DEVICE
這 2 個(gè)符號(hào)應(yīng)當(dāng)是自解釋的. 如果數(shù)據(jù)被發(fā)向這個(gè)設(shè)備(相應(yīng)地, 也許, 到一個(gè) write 系統(tǒng)調(diào)用), DMA_IO_DEVICE 應(yīng)當(dāng)被使用; 去向 CPU 的數(shù)據(jù), 相反, 用 DMA_FROM_DEVICE 標(biāo)志.
DMA_BIDIRECTIONAL
如果數(shù)據(jù)被在任一方向移動(dòng), 使用 DMA_BIDIRECTIONAL.
DMA_NONE
這個(gè)符號(hào)只作為一個(gè)調(diào)試輔助而提供. 試圖使用帶這個(gè)方向的緩沖導(dǎo)致內(nèi)核崩潰.
可能在所有時(shí)間里試圖只使用 DMA_BIDIRECTIONAL, 但是驅(qū)動(dòng)作者應(yīng)當(dāng)?shù)謸踝∵@個(gè)誘惑. 在一些體系上, 這個(gè)選擇會(huì)有性能損失.
當(dāng)你有單個(gè)緩沖要發(fā)送, 使用 dma_map_single 來(lái)映射它:
dma_addr_t dma_map_single(struct device *dev, void *buffer, size_t size, enum dma_data_direction direction);
返回值是總線(xiàn)地址, 你可以傳遞到設(shè)備, 或者是 NULL 如果有錯(cuò)誤.
一旦傳輸完成, 映射應(yīng)當(dāng)用 dma_unmap_single 來(lái)刪除:
void dma_unmap_single(struct device *dev, dma_addr_t dma_addr, size_t size, enum dma_data_direction direction);
這里, size 和 direction 參數(shù)必須匹配那些用來(lái)映射緩沖的.
一些重要的規(guī)則適用于流 DMA 映射:
緩沖必須用在只匹配它被映射時(shí)給定的方向的傳輸.
一旦一個(gè)緩沖已被映射, 它屬于這個(gè)設(shè)備, 不是處理器. 直到這個(gè)緩沖已被去映射, 驅(qū)動(dòng)不應(yīng)當(dāng)以任何方式觸動(dòng)它的內(nèi)容. 只在調(diào)用 dma_unmap_single 后驅(qū)動(dòng)才可安全存取緩沖的內(nèi)容(有一個(gè)例外, 我們馬上見(jiàn)到). 其他的事情, 這個(gè)規(guī)則隱含一個(gè)在被寫(xiě)入設(shè)備的緩沖不能被映射, 直到它包含所有的要寫(xiě)的數(shù)據(jù).
這個(gè)緩沖必須不被映射, 當(dāng) DMA 仍然激活, 否則肯定會(huì)有嚴(yán)重的系統(tǒng)不穩(wěn)定.
你可能奇怪為什么一旦一個(gè)緩沖已被映射驅(qū)動(dòng)就不能再使用它. 為什么這個(gè)規(guī)則有意義實(shí)際上有 2 個(gè)原因. 第一, 當(dāng)一個(gè)緩沖為 DMA 而被映射, 內(nèi)核必須確保緩沖中的所有的數(shù)據(jù)實(shí)際上已被寫(xiě)入內(nèi)存. 有可能一些數(shù)據(jù)在處理器的緩存當(dāng) dma_unmap_single 被調(diào)用時(shí), 并且必須被明確刷新. 被處理器在刷新后寫(xiě)入緩沖的數(shù)據(jù)可能對(duì)設(shè)備不可見(jiàn).
第二, 考慮一下會(huì)發(fā)生什么, 當(dāng)被映射的緩沖在一個(gè)對(duì)設(shè)備不可存取的內(nèi)存區(qū). 一些體系在這種情況下完全失敗, 但是其他的創(chuàng)建一個(gè)反彈緩沖. 反彈緩沖只是一個(gè)分開(kāi)的內(nèi)存區(qū), 它對(duì)設(shè)備可存取. 如果一個(gè)緩沖被映射使用 DMA_TO_DEVICE 方向, 并且要求一個(gè)反彈緩沖, 原始緩沖的內(nèi)容作為映射操作的一部分被拷貝. 明顯地, 在拷貝后的對(duì)原始緩沖的改變?cè)O(shè)備見(jiàn)不到. 類(lèi)似地, DMA_FROM_DEVICE 反彈緩沖被 dma_unmap_single 拷回到原始緩沖; 來(lái)自設(shè)備的數(shù)據(jù)直到拷貝完成才出現(xiàn).
偶然地, 為什么獲得正確方向是重要的, 反彈緩沖是一個(gè)原因. DMA_BIDIRECTIONAL 反彈緩沖在操作前后被拷貝, 這常常是一個(gè) CPU 周期的不必要浪費(fèi).
偶爾一個(gè)驅(qū)動(dòng)需要存取一個(gè)流 DMA 緩沖的內(nèi)容而不映射它. 已提供了一個(gè)調(diào)用來(lái)做這個(gè):
void dma_sync_single_for_cpu(struct device *dev, dma_handle_t bus_addr, size_t size, enum dma_data_direction direction);
這個(gè)函數(shù)應(yīng)當(dāng)在處理器存取一個(gè)流 DMA 緩沖前調(diào)用. 一旦已做了這個(gè)調(diào)用, CPU "擁有" DMA 緩沖并且可以按需使用它. 在設(shè)備存取這個(gè)緩沖前, 但是, 擁有權(quán)應(yīng)當(dāng)傳遞回給它, 使用:
void dma_sync_single_for_device(struct device *dev, dma_handle_t bus_addr, size_t size, enum dma_data_direction direction);
處理器, 再一次, 在調(diào)用這個(gè)之后不應(yīng)當(dāng)存取 DMA 緩沖.
偶然地, 你可能想建立一個(gè)緩沖的映射, 這個(gè)緩沖你有一個(gè) struct page 指針; 例如, 這可能發(fā)生在使用 get_user_pages 映射用戶(hù)緩沖. 為建立和取消流映射使用 struct page 指針, 使用下面:
dma_addr_t dma_map_page(struct device *dev, struct page *page,
unsigned long offset, size_t size,
enum dma_data_direction direction);
void dma_unmap_page(struct device *dev, dma_addr_t dma_address,
size_t size, enum dma_data_direction direction);
offset 和 size 參數(shù)可被用來(lái)映射頁(yè)的部分. 但是, 建議部分頁(yè)映射應(yīng)當(dāng)避免, 除非你真正確信你在做什么. 映射一頁(yè)的部分可能導(dǎo)致緩存一致性問(wèn)題, 如果這個(gè)分配只覆蓋一個(gè)緩存線(xiàn)的一部分; 這, 隨之, 會(huì)導(dǎo)致內(nèi)存破壞和嚴(yán)重的難以調(diào)試的錯(cuò)誤.
發(fā)散/匯聚映射是一個(gè)特殊類(lèi)型的流 DMA 映射. 假設(shè)你有幾個(gè)緩沖, 都需要傳送數(shù)據(jù)到或者從設(shè)備. 這個(gè)情況可來(lái)自幾個(gè)方式, 包括從一個(gè) readv 或者 writev 系統(tǒng)調(diào)用, 一個(gè)成簇的磁盤(pán) I/O 請(qǐng)求, 或者一個(gè)頁(yè)鏈表在一個(gè)被映射的內(nèi)核 I/O 緩沖. 你可簡(jiǎn)單地映射每個(gè)緩沖, 輪流的, 并且進(jìn)行要求的操作, 但是有幾個(gè)優(yōu)點(diǎn)來(lái)一次映射整個(gè)鏈表.
許多設(shè)備可以接收一個(gè)散布表數(shù)組指針和長(zhǎng)度, 并且傳送它們?nèi)吭谝粋€(gè) DMA 操作中; 例如, "零拷貝"網(wǎng)絡(luò)是更輕松如果報(bào)文在多個(gè)片中建立. 另一個(gè)映射發(fā)散列表為一個(gè)整體的理由是利用在總線(xiàn)硬件上有映射寄存器的系統(tǒng). 在這樣的系統(tǒng)上, 物理上不連續(xù)的頁(yè)從設(shè)備的觀(guān)點(diǎn)看可被匯集為一個(gè)單個(gè)的, 連續(xù)的數(shù)組. 這個(gè)技術(shù)只當(dāng)散布表中的項(xiàng)在長(zhǎng)度上等于頁(yè)大小(除了第一個(gè)和最后一個(gè)), 但是當(dāng)它做這個(gè)工作時(shí), 它可轉(zhuǎn)換多個(gè)操作到一個(gè)單個(gè)的 DMA, 和有針對(duì)性的加速事情.
最后, 如果一個(gè)反彈緩沖必須被使用, 應(yīng)該連接整個(gè)列表為一個(gè)單個(gè)緩沖(因?yàn)樗诒灰匀魏畏绞娇截?.
因此現(xiàn)在你確信散布表的映射在某些情況下是值得的. 映射一個(gè)散布表的第一步是創(chuàng)建和填充一個(gè) struct scatterlist 數(shù)組, 它描述被傳輸?shù)木彌_. 這個(gè)結(jié)構(gòu)是體系依賴(lài)的, 并且在 <asm/scatterlist.h> 中描述. 但是, 它常常包含 3 個(gè)成員:
struct page *page;
struct page 指針, 對(duì)應(yīng)在發(fā)散/匯聚操作中使用的緩沖.
unsigned int length;unsigned int offset;
緩沖的長(zhǎng)度和它的頁(yè)內(nèi)偏移.
為映射一個(gè)發(fā)散/匯聚 DMA 操作, 你的驅(qū)動(dòng)應(yīng)當(dāng)設(shè)置 page, offset, 和 length 成員在一個(gè) struct scatterlist 項(xiàng)給每個(gè)要被發(fā)送的緩沖. 接著調(diào)用:
int dma_map_sg(struct device *dev, struct scatterlist *sg, int nents, enum dma_data_direction direction)
這里 nents 是傳入的散布表項(xiàng)的數(shù)目. 返回值是要發(fā)送的 DMA 緩沖的數(shù)目. 它可能小于 nents.
對(duì)于輸入散布表中的每個(gè)緩沖, dma_map_sg 決定了正確的給設(shè)備的總線(xiàn)地址. 作為任務(wù)的一部分, 它也連接在內(nèi)存中相近的緩沖. 如果你的驅(qū)動(dòng)運(yùn)行的系統(tǒng)有一個(gè) I/O 內(nèi)存管理單元, dma_map_sg 也編程這個(gè)單元的映射寄存器, 可能的結(jié)果是, 從你的驅(qū)動(dòng)的觀(guān)點(diǎn), 你能夠傳輸一個(gè)單個(gè)的, 連續(xù)的緩沖. 你將不會(huì)知道傳送的結(jié)果將看來(lái)如何, 但是, 直到在調(diào)用之后.
你的驅(qū)動(dòng)應(yīng)當(dāng)傳送由 pci_map_sg 返回的每個(gè)緩沖. 總線(xiàn)地址和每個(gè)緩沖的長(zhǎng)度存儲(chǔ)于 struct scatterlist 項(xiàng), 但是它們?cè)诮Y(jié)構(gòu)中的位置每個(gè)體系不同. 2 個(gè)宏定義已被定義來(lái)使得可能編寫(xiě)可移植的代碼:
dma_addr_t sg_dma_address(struct scatterlist *sg);
從這個(gè)散布表入口返回總線(xiàn)( DMA )地址.
unsigned int sg_dma_len(struct scatterlist *sg);
返回這個(gè)緩沖的長(zhǎng)度.
再次, 記住要傳送的緩沖的地址和長(zhǎng)度可能和傳遞給 dma_map_sg 的不同.
一旦傳送完成, 一個(gè) 發(fā)散/匯聚 映射被使用 dma_unmap_sg 去映射:
void dma_unmap_sg(struct device *dev, struct scatterlist *list, int nents, enum dma_data_direction direction);
注意 nents 必須是你起初傳遞給 dma_map_sg 的入口項(xiàng)的數(shù)目, 并且不是這個(gè)函數(shù)返回給你的 DMA 緩沖的數(shù)目.
發(fā)散/匯聚映射是流 DMA 映射, 并且同樣的存取規(guī)則如同單一映射一樣適用. 如果你必須存取一個(gè)被映射的發(fā)散/匯聚列表, 你必須首先同步它:
void dma_sync_sg_for_cpu(struct device *dev, struct scatterlist *sg,
int nents, enum dma_data_direction direction);
void dma_sync_sg_for_device(struct device *dev, struct scatterlist *sg,
int nents, enum dma_data_direction direction);
正常地, DMA 支持層使用 32-位 總線(xiàn)地址, 可能受限于一個(gè)特定設(shè)備的 DMA 掩碼. PCI 總線(xiàn), 但是, 也支持一個(gè) 64-位地址模式, 雙地址周期(DAC). 通常的 DMA 層不支持這個(gè)模式, 因?yàn)閹讉€(gè)理由, 第一個(gè)是它是一個(gè) PCI-特定 的特性. 還有, 許多 DAC 的實(shí)現(xiàn)滿(mǎn)是錯(cuò)誤, 并且, 因?yàn)?DAC 慢于一個(gè)常規(guī)的, 32-位 DMA, 可能有一個(gè)性能開(kāi)銷(xiāo). 即便如此, 有的應(yīng)用程序使用 DAC 是正確的事情; 如果你有一個(gè)設(shè)備可能使用非常大的位于高內(nèi)存的緩沖, 你可能要考慮實(shí)現(xiàn) DAC 支持. 這個(gè)支持只對(duì) PCI 總線(xiàn)適用, 因此 PCI-特定的函數(shù)必須被使用.
為使用 DAC, 你的驅(qū)動(dòng)必須包含 <linux/pci.h>. 你必須設(shè)置一個(gè)單獨(dú)的 DMA 掩碼:
int pci_dac_set_dma_mask(struct pci_dev *pdev, u64 mask);
你可使用 DAC 尋址只在這個(gè)調(diào)用返回 0 時(shí). 一個(gè)特殊的類(lèi)型 (dma64_addr_t) 被用作 DAC 映射. 為建立一個(gè)這些映射, 調(diào)用 pci_dac_page_to_dma:
dma64_addr_t pci_dac_page_to_dma(struct pci_dev *pdev, struct page *page, unsigned long offset, int direction);
DAC 映射, 你將注意到, 可能被完成只從 struct page 指針(它們應(yīng)當(dāng)位于高內(nèi)存, 畢竟, 否則使用它們沒(méi)有意義了); 它們必須一次一頁(yè)地被創(chuàng)建. direction 參數(shù)是在通用 DMA 層中使用的 enum dma_data_direction 的 PCI 對(duì)等體; 它應(yīng)當(dāng)是 PCI_DMA_TODEVICE, PCI_DMA_FROMDEVICE, 或者 PCI_DMA_BIRDIRECTIONAL.
DAC 映射不要求外部資源, 因此在使用后沒(méi)有必要明確釋放它們. 但是, 有必要象對(duì)待其他流映射一樣對(duì)待 DAC 映射, 并且遵守關(guān)于緩沖所有權(quán)的規(guī)則. 有一套函數(shù)來(lái)同步 DMA 緩沖, 和通常的變體相似:
void pci_dac_dma_sync_single_for_cpu(struct pci_dev *pdev,
dma64_addr_t dma_addr,
size_t len,
int direction);
void pci_dac_dma_sync_single_for_device(struct pci_dev *pdev,
dma64_addr_t dma_addr,
size_t len,
int direction);
作為一個(gè) DMA 映射如何被使用的例子, 我們展示了一個(gè)簡(jiǎn)單的給一個(gè) PCI 設(shè)備的 DMA 編碼的例子. 在 PCI 總線(xiàn)上的數(shù)據(jù)的 DMA 操作的形式非常依賴(lài)被驅(qū)動(dòng)的設(shè)備. 因此, 這個(gè)例子不適用于任何真實(shí)的設(shè)備; 相反, 它是一個(gè)稱(chēng)為 dad ( DMA Acquisiton Device) 的假想驅(qū)動(dòng)的一部分. 一個(gè)給這個(gè)設(shè)備的驅(qū)動(dòng)可能定義一個(gè)傳送函數(shù)象這樣:
int dad_transfer(struct dad_dev *dev, int write, void *buffer,
size_t count)
{
dma_addr_t bus_addr;
/* Map the buffer for DMA */
dev->dma_dir = (write ? DMA_TO_DEVICE : DMA_FROM_DEVICE);
dev->dma_size = count;
bus_addr = dma_map_single(&dev->pci_dev->dev, buffer, count,
dev->dma_dir);
dev->dma_addr = bus_addr;
/* Set up the device */
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));
writel(dev->registers.len, cpu_to_le32(count));
/* Start the operation */
writeb(dev->registers.command, DAD_CMD_ENABLEDMA);
return 0;
}
這個(gè)函數(shù)映射要被傳送的緩沖并且啟動(dòng)設(shè)備操作. 這個(gè)工作的另一半必須在中斷服務(wù)過(guò)程中完成, 這個(gè)看來(lái)如此:
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 */
dma_unmap_single(dev->pci_dev->dev, dev->dma_addr,
dev->dma_size, dev->dma_dir);
/* Only now is it safe to access the buffer, copy to user, etc. */
...
}
顯然, 這個(gè)例子缺乏大量的細(xì)節(jié), 包括可能需要的任何步驟來(lái)阻止啟動(dòng)多個(gè)同時(shí)的 DMA 操作.
ISA 總線(xiàn)允許 2 類(lèi) DMA 傳送: 本地 DMA 和 ISA 總線(xiàn)主 DMA. 本地 DMA 使用在主板上的標(biāo)準(zhǔn) DMA-控制器電路來(lái)驅(qū)動(dòng) ISA 總線(xiàn)上的信號(hào)線(xiàn). ISA 總線(xiàn)主 DMA, 另一方面, 完全由外設(shè)處理, 至少?gòu)尿?qū)動(dòng)的觀(guān)點(diǎn)看. 一個(gè) ISA 總線(xiàn)主的例子是 1542 SCSI 控制器, 在內(nèi)核源碼中是在 drivers/scsi/aha1542.c.
至于本地 DMA, 有 3 個(gè)實(shí)體包含在 ISA 總線(xiàn)上的 DMA 數(shù)據(jù)傳送.
The 8237 DMA controller (DMAC)
控制器持有關(guān)于 DMA 傳送的信息, 諸如方向, 內(nèi)存地址, 以及傳送的大小. 它還包含一個(gè)計(jì)數(shù)器來(lái)跟蹤進(jìn)行中的傳送的狀態(tài). 當(dāng)這個(gè)控制器收到一個(gè) DMA 請(qǐng)求信號(hào), 它獲得總線(xiàn)的控制權(quán)并且驅(qū)動(dòng)信號(hào)線(xiàn)以便設(shè)備可讀或些它的數(shù)據(jù).
The peripheral device
這個(gè)設(shè)備必須激活 DMA 請(qǐng)求線(xiàn)當(dāng)它準(zhǔn)備傳送數(shù)據(jù)時(shí). 實(shí)際的傳送由 DMAC 管理; 硬件設(shè)備順序讀或?qū)憯?shù)據(jù)到總線(xiàn)當(dāng)控制器探測(cè)設(shè)備時(shí). 設(shè)備常常觸發(fā)中斷當(dāng)傳送結(jié)束時(shí).
The device driver
這個(gè)驅(qū)動(dòng)什么不做; 它提供給 DMA 控制器方向, 總線(xiàn)地址,和傳送的大小. 它還和它的外設(shè)通訊來(lái)準(zhǔn)備傳送數(shù)據(jù)和響應(yīng)中斷當(dāng) DMA 結(jié)束時(shí).
開(kāi)始的在 PC 上使用的 DMA 控制器管理 4 個(gè)"通道", 每個(gè)有一套 DMA 寄存器. 4 個(gè)設(shè)備可同時(shí)存儲(chǔ)它們的 DMA 信息在控制器中. 更新的 PC 包含相同的 2 個(gè) DMAC 設(shè)備[51]: 第 2 個(gè)控制器(主)被連接到系統(tǒng)的處理器, 并且第 1 個(gè)(從)被連接到第 2 個(gè)控制器的通道 0.
最初的 PC 只有一個(gè)控制器; 第 2 個(gè)是在基于 286 的平臺(tái)上增加的. 但是, 第 2 個(gè)控制器如同主控制器一樣被連接, 因?yàn)樗幚?16-位的傳送; 第 1 個(gè)只傳送 8 位每次并且它為向后兼容而存在.
通道的編號(hào)從 0 到 7: 通道 4 對(duì) ISA 外設(shè)不可用, 因?yàn)樗趦?nèi)部用來(lái)層疊從控制器到主控制器. 因此, 可用的通道是 0 到 3 在從控制器上( 8-位 通道) 和 5 到 7 到主控制器上( 16-位通道). 任何 DMA 傳送的大小, 當(dāng)被存儲(chǔ)于控制器中, 是一個(gè)代表總線(xiàn)周期的數(shù)目的 16-位數(shù). 最大的傳送大小是, 因此, 64KB 對(duì)于從控制器(因?yàn)樗鼈魉?8 位在一個(gè)周期)和 128KB 對(duì)于主控制器( 它進(jìn)行 16-位 傳送).
因?yàn)?DMA 控制器是一個(gè)系統(tǒng)范圍的資源, 內(nèi)核幫助處理這個(gè). 它使用一個(gè) DMA 注冊(cè)來(lái)提供一個(gè)請(qǐng)求并釋放機(jī)制給 DMA 通道, 和一套函數(shù)來(lái)在 DMA 控制器中配置通道信息.
你應(yīng)當(dāng)熟悉內(nèi)核注冊(cè) -- 我們已經(jīng)見(jiàn)到它們?cè)?I/O 端口和中斷線(xiàn). DMA 通道注冊(cè)和其他的類(lèi)似. 在 <asm/dma.h> 中已經(jīng)包含, 下面的函數(shù)可用來(lái)獲得和釋放一個(gè) DMA 通道的擁有權(quán):
int request_dma(unsigned int channel, const char *name);
void free_dma(unsigned int channel);
通道參數(shù)是一個(gè)在 0 到 7 之間的數(shù), 更精確些, 一個(gè)小于 MAX_DMA_CHANNELS 的正值. 在 PC 上, MAX_DMA_CHANNELS 定義為 8 來(lái)匹配硬件. name 參數(shù)是一個(gè)字符串來(lái)標(biāo)識(shí)設(shè)備. 特定的 name 出現(xiàn)在文件 /proc/dma, 它可被用戶(hù)程序讀.
從 request_dma 的返回值是 0 對(duì)于成功, 是 -EINVAL 或者 -EBUSY 如果有錯(cuò)誤. 前者意思是請(qǐng)求的通道超范圍, 后者意思是另一個(gè)設(shè)備持有這個(gè)通道.
我們推薦你象對(duì)待 I/O 端口和中斷線(xiàn)一樣小心對(duì)待 DMA 通道; 在打開(kāi)時(shí)請(qǐng)求通道好于從模塊初始化函數(shù)里請(qǐng)求它. 延后請(qǐng)求允許在驅(qū)動(dòng)之間的一些共享; 例如, 你的聲卡和模擬 I/O 接口可以共享 DMA 通道只要它們不同時(shí)使用.
我們還建議你請(qǐng)求 DMA 通道在你已請(qǐng)求中斷線(xiàn)之后并且你在中斷前釋放它. 這是慣用的順序來(lái)請(qǐng)求這 2 個(gè)資源; 遵循這個(gè)慣例避免了死鎖的可能. 注意每個(gè)使用 DMA 的設(shè)備需要一個(gè) IRQ 線(xiàn); 否則, 它不能指示數(shù)據(jù)傳送的完成.
在一個(gè)典型的情況, open 代碼看來(lái)如下, 引用了我們的假想的 dad 模塊. dad 設(shè)備使用了一個(gè)快速中斷處理, 不帶共享 IRQ 線(xiàn)支持.
int dad_open (struct inode *inode, struct file *filp)
{
struct dad_device *my_device;
/* ... */
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 實(shí)現(xiàn)看來(lái)如此:
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);
/* ... */
}
這是 /proc/dma 文件 在一個(gè)安裝有聲卡的系統(tǒng)中的樣子:
merlino% cat /proc/dma
1: Sound Blaster8
4: cascade
注意, 缺省的聲音驅(qū)動(dòng)獲得 DMA 通道在系統(tǒng)啟動(dòng)時(shí)并且從不釋放它. 層疊的入口是一個(gè)占位者, 指出通道 4 對(duì)驅(qū)動(dòng)不可用, 如同前面解釋的.
在注冊(cè)后, 驅(qū)動(dòng)工作的主要部分包括配置 DMA 控制器正確操作. 這個(gè)任務(wù)并非微不足道的, 但是幸運(yùn)的是, 內(nèi)核輸出了典型驅(qū)動(dòng)需要的所有的函數(shù).
驅(qū)動(dòng)需要配置 DMA 控制器或者讀或?qū)懕徽{(diào)用時(shí), 或者當(dāng)準(zhǔn)備異步傳送時(shí). 后面這個(gè)任務(wù)或者在打開(kāi)時(shí)進(jìn)行或者響應(yīng)一個(gè) ioctl 命令, 根據(jù)驅(qū)動(dòng)和它實(shí)現(xiàn)的策略. 這里展示的代碼是典型地被讀或?qū)懺O(shè)備方法調(diào)用的.
這一小節(jié)提供一個(gè)對(duì)于 DMA 控制器內(nèi)部的快速概覽, 這樣你可理解這里介紹的代碼. 如果你想知道更多, 我們勸你讀 <asm/dma.h> 和一些描述 PC 體系的硬件手冊(cè). 特別地, 我們不處理 8-位 和 16-位 傳送的問(wèn)題. 如果你在編寫(xiě)設(shè)備驅(qū)動(dòng)給 ISA 設(shè)備板, 你應(yīng)當(dāng)在設(shè)備的硬件手冊(cè)中找到相關(guān)的信息.
DMA 控制器是一個(gè)共享的資源, 并且如果多個(gè)處理器試圖同時(shí)對(duì)它編程會(huì)引起混亂. 為此, 控制器被一個(gè)自旋鎖保護(hù), 稱(chēng)為 dma_spin_lock. 驅(qū)動(dòng)不應(yīng)當(dāng)直接操作這個(gè)鎖; 但是, 2 個(gè)函數(shù)已提供給你來(lái)做這個(gè):
unsigned long claim_dma_lock( );
獲取 DMA 自旋鎖. 這個(gè)函數(shù)還在本地處理器上阻塞中斷; 因此, 返回值是一些描述之前中斷狀態(tài)的標(biāo)志; 它必須被傳遞給隨后的函數(shù)來(lái)恢復(fù)中斷狀態(tài), 當(dāng)你用完這個(gè)鎖.
void release_dma_lock(unsigned long flags);
返回 DMA 自旋鎖并且恢復(fù)前面的中斷狀態(tài).
自旋鎖應(yīng)當(dāng)被持有, 當(dāng)使用下面描述的函數(shù)時(shí). 但是, 它不應(yīng)當(dāng)被持有, 在實(shí)際的 I/O 當(dāng)中. 一個(gè)驅(qū)動(dòng)應(yīng)當(dāng)從不睡眠當(dāng)持有一個(gè)自旋鎖時(shí).
必須被加載到控制器中的信息包括 3 項(xiàng): RAM 地址, 必須被傳送的原子項(xiàng)的數(shù)目(以字節(jié)或字計(jì)), 以及傳送的方向. 為此, 下列函數(shù)由 <asm/dma.h> 輸出:
void set_dma_mode(unsigned int channel, char mode);
指示是否這個(gè)通道必須從設(shè)備讀( DMA_MODE_READ)或者寫(xiě)到設(shè)備(DMA_MODE_WRITE). 存在第 3 個(gè)模式, DMA_MODE_CASCADE, 它被用來(lái)釋放對(duì)總線(xiàn)的控制. 層疊是第 1 個(gè)控制器連接到第 2 個(gè)控制器頂部的方式, 但是它也可以被真正的 ISA 總線(xiàn)主設(shè)備使用. 我們這里不討論總線(xiàn)控制.
void set_dma_addr(unsigned int channel, unsigned int addr);
分配 DMA 緩沖的地址. 這個(gè)函數(shù)存儲(chǔ) addr 的低 24 有效位在控制器中. addr 參數(shù)必須是一個(gè)總線(xiàn)地址(見(jiàn)"總線(xiàn)地址"一節(jié), 在本章前面).
void set_dma_count(unsigned int channel, unsigned int count);
分配傳送的字節(jié)數(shù). count 參數(shù)也表示給 16-位 通道的字節(jié); 在這個(gè)情況下, 這個(gè)數(shù)必須是偶數(shù).
除了這些函數(shù), 有一些維護(hù)工具必須用, 當(dāng)處理 DMA 設(shè)備時(shí):
void disable_dma(unsigned int channel);
一個(gè) DMA 通道可在控制器內(nèi)部被關(guān)閉. 這個(gè)通道應(yīng)當(dāng)在控制器被配置為阻止進(jìn)一步不正確的操作前被關(guān)閉. (否則, 會(huì)因?yàn)榭刂破鞅煌ㄟ^(guò) 8-位數(shù)據(jù)傳送被編程而發(fā)生破壞, 并且, 因此, 之前的功能都不自動(dòng)執(zhí)行.
void enable_dma(unsigned int channel);
這個(gè)函數(shù)告知控制器 DMA 通道包含有效數(shù)據(jù).
int get_dma_residue(unsigned int channel);
這個(gè)驅(qū)動(dòng)有時(shí)需要知道是否一個(gè) DMA 傳輸已經(jīng)完成. 這個(gè)函數(shù)返回仍要被傳送的字節(jié)數(shù). 在一次成功的傳送后的返回值是 0 并且在控制器在工作時(shí)是不可預(yù)測(cè)的 (但不是 0). 這種不可預(yù)測(cè)性來(lái)自需要通過(guò) 2 個(gè)8-位輸入操作來(lái)獲得 16-位 的余數(shù).
void clear_dma_ff(unsigned int channel) ;
這個(gè)函數(shù)清理 DMA flip-flop. 這個(gè) flip-flop 用來(lái)控制對(duì) 16-位 寄存器的存取. 這些寄存器被 2 個(gè)連續(xù)的 8-位操作來(lái)存取, 并且這個(gè) flip-flop 被用來(lái)選擇低有效字節(jié)(當(dāng)它被清零)或者是最高有效字節(jié)(當(dāng)它被置位). flip-flop 自動(dòng)翻轉(zhuǎn)當(dāng)已經(jīng)傳送了 8 位; 程序員必須清除 flip-flop( 來(lái)設(shè)置它為已知的狀態(tài) )在存取 DMA 寄存器之前.
使用這些, 一個(gè)驅(qū)動(dòng)可如下實(shí)現(xiàn)一個(gè)函數(shù)來(lái)準(zhǔn)備一次 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;
}
接著, 一個(gè)象下一個(gè)的函數(shù)被用來(lái)檢查 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);
}
未完成的唯一一個(gè)事情是配置設(shè)備板. 這個(gè)設(shè)備特定的任務(wù)常常包含讀或?qū)憥讉€(gè) I/O 端口. 設(shè)備在幾個(gè)大的方面不同. 例如, 一些設(shè)備期望程序員告訴硬件 DMA 緩沖有多大, 并且有時(shí)驅(qū)動(dòng)不得不讀一個(gè)被硬連到設(shè)備中的值. 為配置板, 硬件手冊(cè)是你唯一的朋友.
[49] 當(dāng)然, 什么事情都有例外; 見(jiàn)"接收中斷緩解"一節(jié)在 17 章, 演示了高性能網(wǎng)絡(luò)驅(qū)動(dòng)如何被使用輪詢(xún)最好地實(shí)現(xiàn).
[50] 碎片一詞常常用于磁盤(pán)來(lái)表達(dá)文件沒(méi)有連續(xù)存儲(chǔ)在磁介質(zhì)上. 相同的概念適用于內(nèi)存, 這里每個(gè)虛擬地址空間在整個(gè)物理 RAM 散布, 并且難于獲取連續(xù)的空閑頁(yè)當(dāng)請(qǐng)求一個(gè) DMA 緩沖.
[51] 這些電路現(xiàn)在是主板芯片組的一部分, 但是幾年前它們是 2 個(gè)單獨(dú)的 8237 芯片.
更多建議: