Windows驅動中的PCI, DMA, ISR, DPC, ScatterGater, MapRegsiter, CommonBuffer, ConfigSpace

風靈使發表於2018-09-04

最近有些人問我PCI裝置驅動的問題, 和他們交流過後, 我建議他們先看一看<<The Windows NT Device Driver Book>>這本書, 個人感覺, 這本書寫得非常連貫流暢.

PCI裝置驅動基本包括了PCI的資源獲取, 配置空間的讀寫, 中斷的處理, 中斷後半部在DPC中的處理.

同時, 也必須瞭解DMA, ScatterGater, MapRegister, Common Buffer等基礎.

1.1 PCI裝置資源獲取

PCI裝置的資源是系統根據裝置的屬性(配置空間中暫存器的值)來動態分配的.

驅動中只需在PNP START中獲取這些系統分配的資源:

例如: 筆者開發的PCI電視卡驅動中, 就使用到了其中了兩類資源, CmResourceTypePortCmResourceTypeInterrupt.

Port地址作為裝置暫存器首地址, 之後, 可以使用WRITE_PORT_ULONGREAD_PORT_ULONG加上相應的OFFSET來對裝置暫存器進行訪問.

Interrupt資源中解釋出來的內容, 則主要作為IoConnectInterrupt系統函式的引數, 將裝置的硬體中斷與ISR相關聯, KINTERRUPT的例項則是裝置中斷的軟體形式的載體.

1.2 DMA

DMA裝置, 在系統中分為MASTERSLAVE, 另外一個很重要的能力就是是否支援Scatter/Gather.

這些能力最終表現在DEVICE_DESCRIPTION所定義的資料結構的成員中, 例如:DmaWidth, ScatterGather, Master, Dma32BitAddresses, Dma64BitAddresses.

系統最終將各種不同型別的裝置DMA抽象為DMA_ADAPTER的例項, 它是裝置DMA軟體形式的載體.

驅動程式碼通過IoGetDmaAdapter系統呼叫, 將物理裝置物件PDODMA描述結構作為引數, 最終得到這個DMA_ADAPTER物件, 作為後續一系列DMA相關操作的實體物件.

1.3 Map Register

使用者空間, 核心空間的虛擬記憶體與實體記憶體的關聯是通過頁表來對映的, 驅動程式設計師常常會使用MDL, 它也是某一特定區域虛擬記憶體與實體記憶體的對映關係.

DMA裝置則需要從匯流排地址(MSDN中又叫邏輯地址)與記憶體物理的對映關係角度去看待系統記憶體.

這個對映的關係就是由Map Register承擔的.

不過, 這批Map Register則根據系統而定, 有些是硬體實現, 有些是軟體中劃分出來的特定的一塊記憶體.

IoGetDmaAdapter的呼叫, 也是向系統申請Map Register的過程.

1.4 Common Buffer

這也是大家問得最多的問題

簡單地講, Common buffer是以DMA_ADAPTER為代表所申請的, 申請成功後, 既能通過虛擬地址訪問, 也可以通過DMA控制器所屬邏輯地址空間的地址來訪問的連續實體記憶體.

它的好處就是物理上連續, 存在的問題是系統中連續實體記憶體是隨著系統的執行時間的流逝, 越來越稀缺.

AllocateCommonBuffer系統呼叫是作為DMA_ADAPTERDmaOperations形式存在的, 所以, 具體的一塊Common Buffer可以說, 是與具體的一個DMA控制器所關聯的.

AllocateCommonBuffer成功呼叫後, 會返回虛擬地址與DMA控制器所屬邏輯空間的邏輯地址.

筆者開發的PCI電視卡, 就是通過AllocateCommonBuffer分配一塊較小的連續實體記憶體, 用來存放Scatter/Gather列表 (某塊記憶體的邏輯地址SCATTER_GATHER_LIST.Elements[i].Address.LowPart 與該記憶體的長度SCATTER_GATHER_LIST.Elements[i].Length, 相應操作通過common buffer的虛擬地址 ).

這個Scatter/Gather List列表最終由具有S/G能力的DMA控制器來讀取(相應操作通過common buffer的邏輯地址), 根據其中的表項, 進行DMA讀/寫操作.

1.5 S/G

S/G的能力是DMA控制器的特性, 如果具有S/G的能力, 則可以批量地DMA操作, 否則, 必須一次一次地使用MapTransfer來完成DMA操作.

系統空間的中虛擬記憶體與實體記憶體之間的聯絡通過IoAllocateMdlMmBuildMdlForNonPagedPool建立特定的MDL來表示.

其後,通過DMA_ADAPTERDmaOperations中的GetScatterGatherList獲取MDL所描述的虛擬地址記憶體的S/G列表, 最後, 在GetScatterGatherList
ExecutionRoutine 函式中, 將該列表填入Common bufferTABLE(起始邏輯地址 與 長度)中, 以供DMA Controller所用.

1.6 ISRDPC剛才已經提到, ISR是通過IoConnectInterrupt註冊的.ISR在裝置中斷到來時實呼叫, 但具體的事項則交由(KeInsertQueueDpc)DPC來處理.而DPC則是通過KeInitializeDpc系統呼叫, 將DPC物件KDPC與具體的KDEFERRED_ROUTINE DPC處理函式相關聯的.

1.7 PCI裝置配置空間的訪問

事實上, 一般情況下, Windows PCI裝置並不需要訪問PCI裝置配置空間.但作為一個完整的PCI裝置驅動, 這裡提及一下.
由於PCI裝置的配置空間與IO/MEM空間是分開的, 前面已經提及IO/MEM的訪問方式, 配置空間的訪問如下:定義變數:BUS_INTERFACE_STANDARD m_BusInterfaceStandard;建立: IRP, 主與次分別為IRP_MJ_PNP, IRP_MN_QUERY_INTERFACE, 得到BUS_INTERFACE_STANDARD資料結構.之後, 通過BUS_INTERFACE_STANDARD中的SetBusDataGetBusData來進行PCI配置空間的暫存器讀寫.

PCI裝置驅動完全可以用在PCIe裝置上, 畢竟上層來講, 他們沒有太多的區別.

USB驅動不同, PCI裝置需要考慮驅動設計中的方方面面, 希望這篇文章對大家有所借鑑作用.

相關文章