文/段晗晗
在雷達(dá)系統(tǒng)應(yīng)用中,常需要在具有多種功能的處理板間進(jìn)行數(shù)據(jù)交互,這些交互往往要求實(shí)時(shí)性高、支持多設(shè)備通信等特點(diǎn)。PCI(Peripheral Component Interconnect)總線因極具擴(kuò)展性,傳輸高效性,速率高等特點(diǎn),非常適用于多設(shè)備間的數(shù)據(jù)傳輸。在工程中,硬件上一般由FPGA加PCI橋片來(lái)實(shí)現(xiàn)自研電路板的PCI總線通信功能。軟件上如在 Windows平臺(tái)上則是加載相應(yīng)的驅(qū)動(dòng),然后由上層應(yīng)用程序調(diào)用驅(qū)動(dòng)來(lái)訪問(wèn)PCI設(shè)備完成自研電路板與PC間的數(shù)據(jù)交互。
Windows上開(kāi)發(fā)驅(qū)動(dòng)程序常有兩種方法:
(1)使用微軟提供的DDK(Driver Development Kits)套件,調(diào)用其 API 來(lái)實(shí)現(xiàn)驅(qū)動(dòng)的開(kāi)發(fā)。這種方法需要對(duì)驅(qū)動(dòng)原理和系統(tǒng)內(nèi)核有一定的了解,但開(kāi)發(fā)自由度大,代碼精煉,執(zhí)行效率高。
(2)通過(guò)使用Jungo公司的 WinDriver軟件生成驅(qū)動(dòng)模板函數(shù),根據(jù)自己需求使用其提供的 API。此法可以不需了解內(nèi)核,容易入門,但其由于集成度高而使靈活度降低。本文選擇前者來(lái)開(kāi)發(fā)驅(qū)動(dòng)。使用DDK開(kāi)發(fā)驅(qū)動(dòng)還可借助一個(gè)工具——DriverStudio,它能簡(jiǎn)化Windows下設(shè)備驅(qū)動(dòng)程序的開(kāi)發(fā)和調(diào)試。
在窗口概念還沒(méi)出現(xiàn)的時(shí)代驅(qū)動(dòng)就已經(jīng)誕生了。要在操作系統(tǒng)中操作硬件,程序必須通過(guò)系統(tǒng)內(nèi)核上的驅(qū)動(dòng)來(lái)控制,這些驅(qū)動(dòng)必須完全符合操作系統(tǒng)對(duì)驅(qū)動(dòng)加載、連接、讀寫的規(guī)定,并且使用相關(guān)系統(tǒng)API函數(shù)。目前主流的驅(qū)動(dòng)模型——WDM模型,標(biāo)準(zhǔn)的WDM驅(qū)動(dòng)應(yīng)包含一個(gè)總線驅(qū)動(dòng)和一個(gè)功能驅(qū)動(dòng)。
總線驅(qū)動(dòng)已經(jīng)包含在Windows中,包括PCI、AGP、串口等??偩€驅(qū)動(dòng)主要負(fù)責(zé)管理總線設(shè)備,例如當(dāng)PCI插槽上插入新硬件,開(kāi)啟電腦并進(jìn)入系統(tǒng)后總線驅(qū)動(dòng)開(kāi)始工作——報(bào)告發(fā)現(xiàn)新硬件,并提示用戶安裝驅(qū)動(dòng)程序。此外總線驅(qū)動(dòng)還會(huì)實(shí)時(shí)向操作系統(tǒng)報(bào)告總線設(shè)備狀態(tài),檢測(cè)總線上有什么類型的設(shè)備,這就是“即插即用”。功能驅(qū)動(dòng)就是常說(shuō)的驅(qū)動(dòng)程序。
驅(qū)動(dòng)的工作流程如下:
(1)創(chuàng)建設(shè)備。在總線驅(qū)動(dòng)提示發(fā)現(xiàn)新硬件后,設(shè)備管理器通過(guò)讀取驅(qū)動(dòng)程序inf文件來(lái)創(chuàng)建新設(shè)備,將此設(shè)備注冊(cè)為特定的設(shè)備接口并建立符號(hào)鏈接,這樣使操作系統(tǒng)能正確識(shí)別這個(gè)硬件。在驅(qū)動(dòng)安裝過(guò)程中設(shè)備管理器將驅(qū)動(dòng)程序拷貝到系統(tǒng)目錄并通過(guò)寫入注冊(cè)表建立服務(wù),以便于驅(qū)動(dòng)程序能在系統(tǒng)啟動(dòng)時(shí)被加載。
(2)硬件資源分配。系統(tǒng)將自動(dòng)為設(shè)備分配硬件資源。常見(jiàn)硬件資源有IO端口、存儲(chǔ)器、中斷和DMA。
完成以上兩項(xiàng)最基本的操作,就可以在用戶程序中調(diào)用驅(qū)動(dòng)程序中具體的訪問(wèn)操作硬件的函數(shù)。
圖1:Memory讀測(cè)試
文中以易操作實(shí)現(xiàn)為目的,少原理性而多實(shí)踐性且以重要項(xiàng)描述為原則。
(1)選擇PCI總線。如果硬件方面設(shè)置了橋片配置空間的內(nèi)容,在此步驟中則可自動(dòng)獲取設(shè)備的供應(yīng)商ID和產(chǎn)品ID。
(2)添加硬件資源。在此根據(jù)硬件所提供的資源添加相應(yīng)的類目。有IO和 Memory類型,用DMA方式操作也選擇Memory 類型。
(3)添加資源訪問(wèn)的方法。一般來(lái)說(shuō)對(duì)應(yīng)一個(gè)資源就可設(shè)置一個(gè)與它相關(guān)的控制碼、數(shù)據(jù)交換方式等。此外,選擇打開(kāi)設(shè)備的方法為 SymbolicLink,這樣只需根據(jù)唯一的 GUID號(hào)便可找到對(duì)應(yīng)設(shè)備。
生成代碼中包含兩個(gè)源碼文件XXXDevice.cpp和 XXXDriver.cpp(XXX為 工程名稱),只需對(duì)前者及相應(yīng)頭文件進(jìn)行修改。
(1)內(nèi)核在打開(kāi)設(shè)備時(shí)會(huì)調(diào)用OnStartDevice 函數(shù),在此函數(shù)中對(duì)資源進(jìn)行初始化的操作。
1.用 Initialize函數(shù)初始化資源,給它傳第三個(gè)參數(shù)基地址索引時(shí)應(yīng)根據(jù)硬件定義BAR 的編號(hào)來(lái)確定。
2.對(duì)于DMA方式訪問(wèn)的資源與IO、Memory型資源初始化操作不同,它需要對(duì)一個(gè)設(shè)備描述對(duì)象(DEVICE_DESCRIPTION)進(jìn)行參數(shù)設(shè)置,再對(duì)創(chuàng)建的DMA適配器對(duì)象和DMA 緩存區(qū)對(duì)象使用Initialize 初始化。
3.對(duì)于DMA的讀和寫筆者都分別創(chuàng)建了適配器對(duì)象和緩存區(qū)對(duì)象。
(2)在應(yīng)用程序中,用戶可通過(guò)調(diào)用系統(tǒng)內(nèi)核提供的 DeviceIoControl 函數(shù),由內(nèi)核調(diào)用驅(qū)動(dòng)程序中的 DeviceControl 函數(shù)。前者傳遞參數(shù)中包含了操作碼、輸入緩存、輸出緩存等。內(nèi)核會(huì)將這些參數(shù)寫入名為 IRP 的結(jié)構(gòu)體中并將此結(jié)構(gòu)體作為后者的參數(shù)傳入。在驅(qū)動(dòng)程序中,由此結(jié)構(gòu)體可以獲知上層應(yīng)用程序傳來(lái)的數(shù)據(jù),如要讀寫的地址、字節(jié)數(shù)等信息。
對(duì)IO和Memory 資源的讀寫采用相同的API,有以字節(jié)、字和雙字為單位進(jìn)行讀寫,以字節(jié)讀寫為例。
讀取單個(gè)字節(jié):
UCHAR inb(ULONG ByteOffset);傳入要讀取的地址,返回地址對(duì)應(yīng)的值。
讀取多個(gè)字節(jié):
VOID inb(ULONG ByteOffset, PUCHAR Buf,ULONG cnt);需傳入要讀取的地址,讀回來(lái)數(shù)據(jù)緩沖區(qū)地址和要讀入的字節(jié)數(shù)。
寫入單個(gè)字節(jié):
VOID outb(ULONG ByteOffset, UCHAR Data);需傳入要寫入的地址,待寫入字節(jié)的值。
VOID outb(ULONG ByteOffset, PUCHAR Buf, ULONG cnt);傳入要寫入的地址,待寫入字節(jié)的首地址,寫入字節(jié)個(gè)數(shù)。
在需要頻繁傳送大量數(shù)據(jù)的場(chǎng)合中,如果仍然采用讀寫Memory的一般方法將會(huì)使CPU 運(yùn)行效率降低。使用DMA方式讀寫數(shù)據(jù),只需提供地址和傳輸?shù)臄?shù)量則可由DMA控制器實(shí)現(xiàn)數(shù)據(jù)傳輸而解放CPU的資源。DDK為此封裝了適配器類KDmaAdapter和緩存區(qū)類KCommonDmaBuffer,其對(duì)象初始化在此不贅述。在操作緩沖區(qū)時(shí),驅(qū)動(dòng)程序能夠訪問(wèn)的是緩沖區(qū)的內(nèi)核地址VirtualAddress,對(duì)于緩沖區(qū)的物理地址LogicalAddress只能由內(nèi)核去操作。由DeviceIoControl 函數(shù)傳入待操作字節(jié)數(shù)據(jù)的地址、個(gè)數(shù)等。
(1)寫數(shù)據(jù)。在驅(qū)動(dòng)程序中用RtlCopyMemory函數(shù)將應(yīng)用程序傳來(lái)的待寫數(shù)據(jù)復(fù)制到內(nèi)核地址。
(2)設(shè)置 DMA 相關(guān)寄存器,啟動(dòng) DMA傳輸。對(duì)于寫或讀的緩沖區(qū)在初始化之后,其對(duì)應(yīng)物理地址即確定,則將LogicalAddress 地址賦給DMA物理地址寄存器。同時(shí)設(shè)置 PCI本地端的地址(根據(jù)硬件方面設(shè)置)、突發(fā)模式、讀寫模式、打開(kāi)DMA中斷并啟動(dòng)傳輸。
(3)讀數(shù)據(jù)。
1.進(jìn)行 b 所述操作。
2.等待讀取完成后的中斷。
3.用RtlCopyMomory函數(shù)將內(nèi)核地址中的數(shù)據(jù)復(fù)制到應(yīng)用程序能訪問(wèn)的緩沖區(qū)中。
在執(zhí)行完上述過(guò)程之后應(yīng)關(guān)閉DMA中斷。
中斷服務(wù)程序也是驅(qū)動(dòng)框架代碼中已有的一部分。進(jìn)行 DMA 操作和獲取 PCI 本地端中斷信號(hào)都需要用到中斷服務(wù)程序。只要設(shè)備有中斷,就會(huì)被內(nèi)核捕捉到從而進(jìn)入中斷服務(wù)程序,在此可以進(jìn)行中斷類型的甄別。值得注意的是,中斷程序中不宜放置較多代碼以免影響中斷的效率。確需進(jìn)行中斷后較多處理的程序可放置在延時(shí)處理函數(shù)中。
在退出應(yīng)用程序時(shí)對(duì)驅(qū)動(dòng)的使用就暫告一段落,此時(shí)內(nèi)核將調(diào)用OnStopDevice 函數(shù)。其中將調(diào)用Invalidate 函數(shù),由它對(duì)以上生成的對(duì)象進(jìn)行銷毀回收。對(duì)于DMA的適配器對(duì)象和緩存對(duì)象應(yīng)手動(dòng)添加銷毀操作。
假設(shè)硬件方面對(duì) PCI 橋片進(jìn)行了正確配置,則在自研開(kāi)發(fā)板接入 PCI 總線時(shí),同一總線上的計(jì)算機(jī)將能夠發(fā)現(xiàn)此設(shè)備,加載所編寫驅(qū)動(dòng)。如圖1所示①為Windriver中讀取到Memory空間零地址的字節(jié)內(nèi)容;②所示為筆者根據(jù)項(xiàng)目工程要求編寫測(cè)試軟件;③是通過(guò)驅(qū)動(dòng)監(jiān)測(cè)軟件看到相應(yīng)地址上的字節(jié)內(nèi)容,這些作為調(diào)試驅(qū)動(dòng)添加在驅(qū)動(dòng)程序中的測(cè)試信息。由圖可發(fā)現(xiàn)雙方結(jié)果一致,即實(shí)現(xiàn)了PCI總線的數(shù)據(jù)交互。
參考文獻(xiàn)
[1]Chris Cant著;孫義,馬莉波等譯.Windows WDM設(shè)備驅(qū)動(dòng)程序開(kāi)發(fā)指南[M].北京:機(jī)械工業(yè)出版社,2001.
[2]武安河.Windows 2000/XP WDM設(shè)備驅(qū)動(dòng)程序開(kāi)發(fā)(第二版)[M].北京:電子工業(yè)出版社,2005.