標籤:windows pci pcie
最近有些人問我PCI裝置驅動的問題, 和他們交流過後, 我建議他們先看一看<<The Windows NT Device Driver Book>>這本書, 個人感覺, 這本書寫得非常連貫流暢.
PCI裝置驅動基本包括了PCI的資源擷取, 配置空間的讀寫, 中斷的處理, 中斷後半部在DPC中的處理.
同時, 也必須瞭解DMA, ScatterGater, MapRegister, Common Buffer等基礎.
1.1 PCI裝置資源擷取
PCI裝置的資源是系統根據裝置的屬性(配置空間中寄存器的值)來動態分配的.
驅動中只需在PNP START中擷取這些系統分配的資源:
例如: 筆者開發的PCI電視卡驅動中, 就使用到了其中了兩類資源, CmResourceTypePort與CmResourceTypeInterrupt.
Port地址作為裝置寄存器首地址, 之後, 可以使用WRITE_PORT_ULONG與READ_PORT_ULONG加上相應的OFFSET來對裝置寄存器進行訪問.
Interrupt資源中解釋出來的內容, 則主要作為IoConnectInterrupt系統函數的參數, 將裝置的硬體中斷與ISR相關聯, KINTERRUPT的執行個體則是裝置中斷的軟體形式的載體.
1.2 DMA
DMA裝置, 在系統中分為MASTER與SLAVE, 另外一個很重要的能力就是是否支援Scatter/Gather.
這些能力最終表現在DEVICE_DESCRIPTION所定義的資料結構的成員中, 例如:DmaWidth, ScatterGather, Master, Dma32BitAddresses, Dma64BitAddresses.
系統最終將各種不同類型的裝置DMA抽象為DMA_ADAPTER的執行個體, 它是裝置DMA軟體形式的載體.
驅動代碼通過IoGetDmaAdapter系統調用, 將物理裝置對象PDO與DMA描述結構作為參數, 最終得到這個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_ADAPTER的DmaOperations形式存在的, 所以, 具體的一塊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操作.
系統空間的中虛擬記憶體與實體記憶體之間的聯絡通過IoAllocateMdl與MmBuildMdlForNonPagedPool建立特定的MDL來表示.
其後,通過DMA_ADAPTER的DmaOperations中的GetScatterGatherList擷取MDL所描述的虛擬位址記憶體的S/G列表, 最後, 在GetScatterGatherList的
ExecutionRoutine 函數中, 將該列表填入Common buffer的TABLE(起始邏輯地址 與 長度)中, 以供DMA Controller所用.
1.6 ISR與DPC
剛才已經提到, 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中的SetBusData與GetBusData來進行PCI配置空間的寄存器讀寫.
PCI裝置驅動完全可以用在PCIe裝置上, 畢竟上層來講, 他們沒有太多的區別.
與USB驅動不同, PCI裝置需要考慮驅動設計中的方方面面, 希望這篇文章對大家有所借鑒作用.