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 控制器處於某個特定狀態,那麼你必須在恢復時恢復這些暫存器。