文/段晗晗
在雷達系統(tǒng)應(yīng)用中,常需要在具有多種功能的處理板間進行數(shù)據(jù)交互,這些交互往往要求實時性高、支持多設(shè)備通信等特點。PCI(Peripheral Component Interconnect)總線因極具擴展性,傳輸高效性,速率高等特點,非常適用于多設(shè)備間的數(shù)據(jù)傳輸。在工程中,硬件上一般由FPGA加PCI橋片來實現(xiàn)自研電路板的PCI總線通信功能。軟件上如在 Windows平臺上則是加載相應(yīng)的驅(qū)動,然后由上層應(yīng)用程序調(diào)用驅(qū)動來訪問PCI設(shè)備完成自研電路板與PC間的數(shù)據(jù)交互。
Windows上開發(fā)驅(qū)動程序常有兩種方法:
(1)使用微軟提供的DDK(Driver Development Kits)套件,調(diào)用其 API 來實現(xiàn)驅(qū)動的開發(fā)。這種方法需要對驅(qū)動原理和系統(tǒng)內(nèi)核有一定的了解,但開發(fā)自由度大,代碼精煉,執(zhí)行效率高。
(2)通過使用Jungo公司的 WinDriver軟件生成驅(qū)動模板函數(shù),根據(jù)自己需求使用其提供的 API。此法可以不需了解內(nèi)核,容易入門,但其由于集成度高而使靈活度降低。本文選擇前者來開發(fā)驅(qū)動。使用DDK開發(fā)驅(qū)動還可借助一個工具——DriverStudio,它能簡化Windows下設(shè)備驅(qū)動程序的開發(fā)和調(diào)試。
在窗口概念還沒出現(xiàn)的時代驅(qū)動就已經(jīng)誕生了。要在操作系統(tǒng)中操作硬件,程序必須通過系統(tǒng)內(nèi)核上的驅(qū)動來控制,這些驅(qū)動必須完全符合操作系統(tǒng)對驅(qū)動加載、連接、讀寫的規(guī)定,并且使用相關(guān)系統(tǒng)API函數(shù)。目前主流的驅(qū)動模型——WDM模型,標(biāo)準(zhǔn)的WDM驅(qū)動應(yīng)包含一個總線驅(qū)動和一個功能驅(qū)動。
總線驅(qū)動已經(jīng)包含在Windows中,包括PCI、AGP、串口等??偩€驅(qū)動主要負(fù)責(zé)管理總線設(shè)備,例如當(dāng)PCI插槽上插入新硬件,開啟電腦并進入系統(tǒng)后總線驅(qū)動開始工作——報告發(fā)現(xiàn)新硬件,并提示用戶安裝驅(qū)動程序。此外總線驅(qū)動還會實時向操作系統(tǒng)報告總線設(shè)備狀態(tài),檢測總線上有什么類型的設(shè)備,這就是“即插即用”。功能驅(qū)動就是常說的驅(qū)動程序。
驅(qū)動的工作流程如下:
(1)創(chuàng)建設(shè)備。在總線驅(qū)動提示發(fā)現(xiàn)新硬件后,設(shè)備管理器通過讀取驅(qū)動程序inf文件來創(chuàng)建新設(shè)備,將此設(shè)備注冊為特定的設(shè)備接口并建立符號鏈接,這樣使操作系統(tǒng)能正確識別這個硬件。在驅(qū)動安裝過程中設(shè)備管理器將驅(qū)動程序拷貝到系統(tǒng)目錄并通過寫入注冊表建立服務(wù),以便于驅(qū)動程序能在系統(tǒng)啟動時被加載。
(2)硬件資源分配。系統(tǒng)將自動為設(shè)備分配硬件資源。常見硬件資源有IO端口、存儲器、中斷和DMA。
完成以上兩項最基本的操作,就可以在用戶程序中調(diào)用驅(qū)動程序中具體的訪問操作硬件的函數(shù)。

圖1:Memory讀測試
文中以易操作實現(xiàn)為目的,少原理性而多實踐性且以重要項描述為原則。
(1)選擇PCI總線。如果硬件方面設(shè)置了橋片配置空間的內(nèi)容,在此步驟中則可自動獲取設(shè)備的供應(yīng)商ID和產(chǎn)品ID。
(2)添加硬件資源。在此根據(jù)硬件所提供的資源添加相應(yīng)的類目。有IO和 Memory類型,用DMA方式操作也選擇Memory 類型。
(3)添加資源訪問的方法。一般來說對應(yīng)一個資源就可設(shè)置一個與它相關(guān)的控制碼、數(shù)據(jù)交換方式等。此外,選擇打開設(shè)備的方法為 SymbolicLink,這樣只需根據(jù)唯一的 GUID號便可找到對應(yīng)設(shè)備。
生成代碼中包含兩個源碼文件XXXDevice.cpp和 XXXDriver.cpp(XXX為 工程名稱),只需對前者及相應(yīng)頭文件進行修改。
(1)內(nèi)核在打開設(shè)備時會調(diào)用OnStartDevice 函數(shù),在此函數(shù)中對資源進行初始化的操作。
1.用 Initialize函數(shù)初始化資源,給它傳第三個參數(shù)基地址索引時應(yīng)根據(jù)硬件定義BAR 的編號來確定。
2.對于DMA方式訪問的資源與IO、Memory型資源初始化操作不同,它需要對一個設(shè)備描述對象(DEVICE_DESCRIPTION)進行參數(shù)設(shè)置,再對創(chuàng)建的DMA適配器對象和DMA 緩存區(qū)對象使用Initialize 初始化。
3.對于DMA的讀和寫筆者都分別創(chuàng)建了適配器對象和緩存區(qū)對象。
(2)在應(yīng)用程序中,用戶可通過調(diào)用系統(tǒng)內(nèi)核提供的 DeviceIoControl 函數(shù),由內(nèi)核調(diào)用驅(qū)動程序中的 DeviceControl 函數(shù)。前者傳遞參數(shù)中包含了操作碼、輸入緩存、輸出緩存等。內(nèi)核會將這些參數(shù)寫入名為 IRP 的結(jié)構(gòu)體中并將此結(jié)構(gòu)體作為后者的參數(shù)傳入。在驅(qū)動程序中,由此結(jié)構(gòu)體可以獲知上層應(yīng)用程序傳來的數(shù)據(jù),如要讀寫的地址、字節(jié)數(shù)等信息。
對IO和Memory 資源的讀寫采用相同的API,有以字節(jié)、字和雙字為單位進行讀寫,以字節(jié)讀寫為例。
讀取單個字節(jié):
UCHAR inb(ULONG ByteOffset);傳入要讀取的地址,返回地址對應(yīng)的值。
讀取多個字節(jié):
VOID inb(ULONG ByteOffset, PUCHAR Buf,ULONG cnt);需傳入要讀取的地址,讀回來數(shù)據(jù)緩沖區(qū)地址和要讀入的字節(jié)數(shù)。
寫入單個字節(jié):
VOID outb(ULONG ByteOffset, UCHAR Data);需傳入要寫入的地址,待寫入字節(jié)的值。
VOID outb(ULONG ByteOffset, PUCHAR Buf, ULONG cnt);傳入要寫入的地址,待寫入字節(jié)的首地址,寫入字節(jié)個數(shù)。
在需要頻繁傳送大量數(shù)據(jù)的場合中,如果仍然采用讀寫Memory的一般方法將會使CPU 運行效率降低。使用DMA方式讀寫數(shù)據(jù),只需提供地址和傳輸?shù)臄?shù)量則可由DMA控制器實現(xiàn)數(shù)據(jù)傳輸而解放CPU的資源。DDK為此封裝了適配器類KDmaAdapter和緩存區(qū)類KCommonDmaBuffer,其對象初始化在此不贅述。在操作緩沖區(qū)時,驅(qū)動程序能夠訪問的是緩沖區(qū)的內(nèi)核地址VirtualAddress,對于緩沖區(qū)的物理地址LogicalAddress只能由內(nèi)核去操作。由DeviceIoControl 函數(shù)傳入待操作字節(jié)數(shù)據(jù)的地址、個數(shù)等。
(1)寫數(shù)據(jù)。在驅(qū)動程序中用RtlCopyMemory函數(shù)將應(yīng)用程序傳來的待寫數(shù)據(jù)復(fù)制到內(nèi)核地址。
(2)設(shè)置 DMA 相關(guān)寄存器,啟動 DMA傳輸。對于寫或讀的緩沖區(qū)在初始化之后,其對應(yīng)物理地址即確定,則將LogicalAddress 地址賦給DMA物理地址寄存器。同時設(shè)置 PCI本地端的地址(根據(jù)硬件方面設(shè)置)、突發(fā)模式、讀寫模式、打開DMA中斷并啟動傳輸。
(3)讀數(shù)據(jù)。
1.進行 b 所述操作。
2.等待讀取完成后的中斷。
3.用RtlCopyMomory函數(shù)將內(nèi)核地址中的數(shù)據(jù)復(fù)制到應(yīng)用程序能訪問的緩沖區(qū)中。
在執(zhí)行完上述過程之后應(yīng)關(guān)閉DMA中斷。
中斷服務(wù)程序也是驅(qū)動框架代碼中已有的一部分。進行 DMA 操作和獲取 PCI 本地端中斷信號都需要用到中斷服務(wù)程序。只要設(shè)備有中斷,就會被內(nèi)核捕捉到從而進入中斷服務(wù)程序,在此可以進行中斷類型的甄別。值得注意的是,中斷程序中不宜放置較多代碼以免影響中斷的效率。確需進行中斷后較多處理的程序可放置在延時處理函數(shù)中。
在退出應(yīng)用程序時對驅(qū)動的使用就暫告一段落,此時內(nèi)核將調(diào)用OnStopDevice 函數(shù)。其中將調(diào)用Invalidate 函數(shù),由它對以上生成的對象進行銷毀回收。對于DMA的適配器對象和緩存對象應(yīng)手動添加銷毀操作。
假設(shè)硬件方面對 PCI 橋片進行了正確配置,則在自研開發(fā)板接入 PCI 總線時,同一總線上的計算機將能夠發(fā)現(xiàn)此設(shè)備,加載所編寫驅(qū)動。如圖1所示①為Windriver中讀取到Memory空間零地址的字節(jié)內(nèi)容;②所示為筆者根據(jù)項目工程要求編寫測試軟件;③是通過驅(qū)動監(jiān)測軟件看到相應(yīng)地址上的字節(jié)內(nèi)容,這些作為調(diào)試驅(qū)動添加在驅(qū)動程序中的測試信息。由圖可發(fā)現(xiàn)雙方結(jié)果一致,即實現(xiàn)了PCI總線的數(shù)據(jù)交互。
參考文獻
[1]Chris Cant著;孫義,馬莉波等譯.Windows WDM設(shè)備驅(qū)動程序開發(fā)指南[M].北京:機械工業(yè)出版社,2001.
[2]武安河.Windows 2000/XP WDM設(shè)備驅(qū)動程序開發(fā)(第二版)[M].北京:電子工業(yè)出版社,2005.