ISA 和 LPC 裝置的 DMA¶
- 作者:
Pierre Ossman <drzeus@drzeus.cx>
本文件描述瞭如何使用舊的 ISA DMA 控制器進行 DMA 傳輸。儘管 ISA 在今天已基本“死亡”,但 LPC 匯流排使用相同的 DMA 系統,因此它將在相當長一段時間內繼續存在。
標頭檔案和依賴項¶
要進行 ISA 風格的 DMA,你需要包含兩個標頭檔案
#include <linux/dma-mapping.h>
#include <asm/dma.h>
第一個是通用的 DMA API,用於將虛擬地址轉換為匯流排地址(詳見使用通用裝置的動態 DMA 對映)。
第二個包含特定於 ISA DMA 傳輸的例程。由於並非所有平臺都提供此功能,請確保你的 Kconfig 依賴於 ISA_DMA_API(而非 ISA),這樣就不會有人嘗試在不支援的平臺上構建你的驅動程式。
緩衝區分配¶
ISA DMA 控制器對可訪問的記憶體有非常嚴格的要求,因此在分配緩衝區時必須格外小心。
(你通常需要一個特殊的 DMA 傳輸緩衝區,而不是直接在你的常規資料結構之間傳輸。)
可進行 DMA 的地址空間是_物理_記憶體的最低 16 MB。此外,傳輸塊不能跨越頁面邊界(根據你使用的通道,頁面大小為 64 或 128 KiB)。
為了分配一塊滿足所有這些要求的記憶體,你需要向 kmalloc 傳遞標誌 GFP_DMA。
不幸的是,可用於 ISA DMA 的記憶體稀缺,因此除非你在啟動時分配記憶體,否則最好同時傳遞 __GFP_RETRY_MAYFAIL 和 __GFP_NOWARN,以使分配器更努力地嘗試。
(這種稀缺性也意味著你應該儘早分配緩衝區,並且在驅動程式解除安裝之前不要釋放它。)
地址轉換¶
要將虛擬地址轉換為匯流排地址,請使用正常的 DMA API。不要使用 isa_virt_to_bus(),即使它做同樣的事情。原因是 isa_virt_to_bus() 函式將需要 Kconfig 依賴於 ISA,而你真正需要的只是 ISA_DMA_API。請記住,儘管 DMA 控制器起源於 ISA,但它在其他地方也有使用。
注意:x86_64 在 ISA 方面曾有一個損壞的 DMA API,但後來已修復。如果你的架構有問題,請修復 DMA API,而不是恢復到 ISA 函式。
通道¶
一個正常的 ISA DMA 控制器有 8 個通道。低四位用於 8 位傳輸,高四位用於 16 位傳輸。
(實際上,DMA 控制器是兩個獨立的控制器,其中通道 4 用於為第二個控制器(0-3)提供 DMA 訪問。這意味著在四個 16 位通道中,只有三個可用。)
你以類似所有基本資源的方式分配這些通道
extern int request_dma(unsigned int dmanr, const char * device_id); extern void free_dma(unsigned int dmanr);
使用 16 位或 8 位傳輸的能力_不取決於_你作為驅動程式作者,而是取決於硬體支援什麼。請查閱你的規格或測試不同的通道。
傳輸資料¶
現在是好戲,實際的 DMA 傳輸。:)
在使用任何 ISA DMA 例程之前,你需要使用 claim_dma_lock() 宣告 DMA 鎖。原因是某些 DMA 操作不是原子的,因此一次只能有一個驅動程式操作暫存器。
第一次使用 DMA 控制器時,你應該呼叫 clear_dma_ff()。這將清除 DMA 控制器中用於非原子操作的內部暫存器。只要你(和所有其他人)使用鎖定函式,就只需要重置一次。
接下來,你使用 set_dma_mode() 告訴控制器你打算進行傳輸的方向。目前你有 DMA_MODE_READ 和 DMA_MODE_WRITE 選項。
設定傳輸的起始地址(對於 16 位傳輸,這需要 16 位對齊)以及要傳輸的位元組數。請注意是_位元組_。DMA 例程將完成所有必要的轉換,將其轉換為 DMA 控制器能夠理解的值。
最後一步是啟用 DMA 通道並釋放 DMA 鎖。
DMA 傳輸完成後(或超時),你應該再次停用該通道。你還應該檢查 get_dma_residue() 以確保所有資料都已傳輸。
示例
int flags, residue;
flags = claim_dma_lock();
clear_dma_ff();
set_dma_mode(channel, DMA_MODE_WRITE);
set_dma_addr(channel, phys_addr);
set_dma_count(channel, num_bytes);
dma_enable(channel);
release_dma_lock(flags);
while (!device_done());
flags = claim_dma_lock();
dma_disable(channel);
residue = dma_get_residue(channel);
if (residue != 0)
printk(KERN_ERR "driver: Incomplete DMA transfer!"
" %d bytes left!\n", residue);
release_dma_lock(flags);
掛起/恢復¶
驅動程式有責任確保在 DMA 傳輸進行時機器不會掛起。此外,系統掛起時所有 DMA 設定都會丟失,因此如果你的驅動程式依賴於 DMA 控制器處於某個特定狀態,那麼你必須在恢復時恢復這些暫存器。