吳成英,樊戰(zhàn)友
?
時統(tǒng)設(shè)備的PCI設(shè)備驅(qū)動程序設(shè)計
吳成英1,2,樊戰(zhàn)友1
(1. 中國科學(xué)院國家授時中心,西安 710600;2. 中國科學(xué)院研究生院,北京 100039)
介紹了在Windows系統(tǒng)下用VisualC++、DDK和DriverStudio軟件對PCI(peripheral component interconnect)設(shè)備驅(qū)動程序的編寫。闡述了PCI設(shè)備驅(qū)動程序的分層結(jié)構(gòu)和編寫方式。PCI驅(qū)動屬于內(nèi)核模式驅(qū)動程序中的即插即用驅(qū)動,PCI的即插即用功能是本文重點介紹對象。另外介紹了PCI設(shè)備驅(qū)動通過配置空間獲取設(shè)備資源的過程與方法,以及驅(qū)動程序與操作系統(tǒng)之間的通訊機(jī)制。
PCI驅(qū)動程序;即插即用;配置空間
隨著科學(xué)技術(shù)的發(fā)展,作為基本物理量之一的時間在國民經(jīng)濟(jì)、國防等領(lǐng)域的重要性日益顯著。在向信息化時代邁步的今天,人們的日常生活正處在時間和頻率的“包圍”中,各類定時器、數(shù)據(jù)傳輸、B碼終端以及GPS導(dǎo)航定位都離不開高精度的時間。中國科學(xué)院國家授時中心(NTSC)的時間基準(zhǔn)系統(tǒng)為國民經(jīng)濟(jì)做出了重大貢獻(xiàn),同時國家重大項目和基礎(chǔ)建設(shè)也對時間工作提出了更高的要求[1]。本文提出的PCI設(shè)備驅(qū)動程序的設(shè)計對NTSC相關(guān)的時頻產(chǎn)品有很大的幫助,主要是解決時碼信號傳輸速度慢的問題。如今,PCI總線以優(yōu)異的性能逐步取代了ISA(industry standard architecture)和EISA(extension industry standard architecture)等其他總線,成為當(dāng)今總線發(fā)展中的主流。一方面是因為PCI總線的數(shù)據(jù)吞吐量大,另一方面是因為該總線與具體的處理器無關(guān)。PCI設(shè)備驅(qū)動程序位于PCI總線的上層,而且PCI驅(qū)動程序不會因掛在PCI上的硬件不同而不同。本文將以“通用高速PCI總線”的驅(qū)動設(shè)計為例,探討通用PCI設(shè)備驅(qū)動程序的開發(fā)過程。
搭建環(huán)境是編寫驅(qū)動程序的根基。驅(qū)動程序不像一般應(yīng)用程序,運行在用戶模式下。驅(qū)動程序在內(nèi)核模式下加載,而且需要操作系統(tǒng)的各個核心組件相互配合。因此驅(qū)動程序編譯環(huán)境搭建不好必然會產(chǎn)生很多致命問題,比如在加載驅(qū)動程序時,計算機(jī)突然死機(jī)、藍(lán)屏。如今,開發(fā)設(shè)備驅(qū)動程序主要是在VC、DDK、Visual Studio 3個綜合平臺上,安裝這3個軟件時,必須按照VC→DDK→Visual Studio這一先后順序來進(jìn)行。安裝完成后首先編譯DriverStudio自帶的2個工程文件Vdwlibs.dws和NdisWdm.dsw,當(dāng)這2個工程文件編譯無錯時,說明DDK軟件安裝成功。驅(qū)動程序用到了很多DDK自帶的頭文件,而且不同的驅(qū)動程序包含不同的頭文件。編譯PCI驅(qū)動程序時,要包含wdm.h頭文件,wdm.h頭文件是編譯WDM式驅(qū)動程序所必須的頭文件,它是DDK編譯環(huán)境封裝好的文件。為了讓VC、DDK、Visual Studio這3個軟件配合使用,編譯時路徑設(shè)置必須正確,確保不出錯。啟動編譯環(huán)境VC時,按以下順序啟動:開始→所有程序→Compuware DriverStudio→Develop→DDK Build Setting→Launch Program(如圖1所示)。啟動VC成功后,編譯DriverStudio自帶的例子HellowWdm.dsw,如果運行成功,開發(fā)環(huán)境到此都配置成功。
圖1 編譯環(huán)境啟動界面
PCI總線是連接計算機(jī)各個硬件單元的通用總線,不同總線之間的通訊通過相應(yīng)的橋芯片來轉(zhuǎn)接。PCI總線是計算機(jī)南橋芯片與北橋芯片通訊的橋梁,PCI總線周期轉(zhuǎn)換和地址空間映射是南橋和北橋通訊的關(guān)鍵點。
根據(jù)協(xié)議,PCI設(shè)備占用的內(nèi)存、I/O、中斷等資源是浮動的。PCI總線提供了資源配置機(jī)制,描述資源配置信息的寄存器稱為配置寄存器,配置寄存器的整體就構(gòu)成了配置空間。PCI有3個相互獨立的物理地址空間:設(shè)備存儲器地址空間、I/O地址空間和配置空間,這3種空間是并列且相互獨立的地址空間,處理器訪問內(nèi)存空間或 I/O 空間時使用對應(yīng)的內(nèi)存指令或 I/O 讀寫指令。訪問配置空間則需要通過訪問配置地址寄存器和配置數(shù)據(jù)寄存器來完成,配置空間是最能顯示PCI特性的物理空間[2]。
PCI的配置空間是實現(xiàn)PCI即插即用功能的關(guān)鍵因素。配置寄存器是PCI硬件設(shè)備與PCI設(shè)備初始化軟件的信息交接區(qū),使得應(yīng)用程序能夠識別PCI硬件設(shè)備并能控制該設(shè)備。PCI總線規(guī)范定義的配置空間總長度是256個字節(jié),256個字節(jié)的配置空間包括頭標(biāo)區(qū)(64字節(jié))和設(shè)備關(guān)聯(lián)區(qū)(192字節(jié))2部分,頭標(biāo)區(qū)是每個PCI功能配置空間里都具有的區(qū)域,主要的功能是用來識別設(shè)備和控制通用的設(shè)備;關(guān)聯(lián)區(qū)的設(shè)置取決于PCI設(shè)備本身的需求。
頭標(biāo)區(qū)根據(jù)PCI設(shè)備的不同而擁有不同的類型,本文以常用類型0為例進(jìn)行解釋(如表1所示)。操作系統(tǒng)根據(jù)廠商ID判斷PCI設(shè)備是否存在,結(jié)合設(shè)備ID找到相應(yīng)的驅(qū)動程序。中斷線和中斷引腳一起管理著PCI設(shè)備的中斷,PCI總線一般有24個中斷。
由于PCI設(shè)備驅(qū)動是通過WDM驅(qū)動程序完成的,WDM會為PCI總線上的設(shè)備提供一個物理設(shè)備對象(PDO),當(dāng)功能設(shè)備對象(FDO)掛在PDO上時,就可以將IRP_MN_START_DEVICE傳遞給底層的PDO去處理。PCI總線的PDO就會得到PCI的配置空間,并從中得到有用的信息,如中斷號、設(shè)備物理內(nèi)存以及I/O端口信息。
表1 配置空間布局
如今驅(qū)動程序有2類:一類是WDM式;另一類是NT式。它們主要的區(qū)別在于是否支持即插即用功能(前者支持,后者不支持),同時編寫時所包含的頭文件也不同。本文的PCI設(shè)備驅(qū)動是WDM式驅(qū)動,WDM式驅(qū)動是基于分層的,完成一個WDM式的驅(qū)動程序至少需要物理設(shè)備對象和功能設(shè)備對象[2]。
當(dāng)PC機(jī)的PCI總線上插入一個設(shè)備時,總線驅(qū)動程序識別總線上的該設(shè)備并為設(shè)備產(chǎn)生物理設(shè)備對象PDO,此時操作系統(tǒng)會提示用戶安裝PCI設(shè)備的FDO。提示界面和平常插入USB設(shè)備時提示“找到新的硬件向?qū)А毕嗤?。此時把驅(qū)動程序?qū)?yīng)的.inf文件添加到硬件向?qū)Ю锩婢涂梢允褂眯碌腜CI設(shè)備了。當(dāng)1個FDO附加在PDO上的時候,PDO設(shè)備對象的子域AttachedDevice會記錄FDO的位置。每個設(shè)備對象中有個StackSize子域,表明操作這個設(shè)備對象需要幾層才能到達(dá)最下層的物理設(shè)備。FDO和PDO的具體關(guān)系如圖2所示。
圖2 驅(qū)動的分層結(jié)構(gòu)
驅(qū)動程序?qū)?chuàng)建的FDO附加到PDO上,是靠IoAttachDeviceToDeviceStack函數(shù)實現(xiàn)的。函數(shù)的申明如下:
PDEVICE_OBJECT IoAttachDeviceToDeviceStack(IN PDEVICE SourceDevice,IN PDEVICE TargetDevice)。其中SourceDevice表示FDO的地址,TargetDevice表示PDO的地址,返回值是返回附加設(shè)備的對象。PCI設(shè)備驅(qū)動在PDO和FDO之間有很多過濾驅(qū)動程序,所以在此返回的是過濾驅(qū)動。PDO和FDO之間是要互動的,要讓FDO知道它下層的驅(qū)動必須通過設(shè)備擴(kuò)展記錄FDO下層的設(shè)備。設(shè)備擴(kuò)展的定義如下:
typedef struct _DEVICE_EXTENSION
{
PDEVICE_OBJECT fdo;//功能設(shè)備對象,保存FDO的地址
PDEVICE_OBJECT NextStackDevice;//下層驅(qū)動設(shè)備
UNICODE_STRING ustrDeviceName;//設(shè)備名
UNICODE_STRING ustrSymLinkName;//符號鏈接
}DEVICE_EXTENSION, *PDEVICE_EXTENSION
設(shè)備驅(qū)動程序就是控制硬件設(shè)備的一組函數(shù)。PCI驅(qū)動程序的開發(fā),就是取得PCI板卡占用的各種資源(內(nèi)存、端口、中斷和DMA等),并提供給用戶一條可以訪問這些資源的途徑[3-4]。當(dāng)用戶模式程序需要讀取設(shè)備數(shù)據(jù)時,它就調(diào)用Win32 API函數(shù),如ReadFile。Win32子系統(tǒng)模塊(如KERNEL32.DLL)通過調(diào)用平臺相關(guān)的系統(tǒng)服務(wù)接口實現(xiàn)該 API(application programming interface,應(yīng)用程序編程接口),而平臺相關(guān)的系統(tǒng)服務(wù)將調(diào)用內(nèi)核模式支持例程。驅(qū)動程序通過NTCreateFile()函數(shù)以軟中斷的方式從用戶模式進(jìn)入到內(nèi)核模式。NTCreateFile()系統(tǒng)函數(shù)的調(diào)用通過I/O管理器,創(chuàng)建IRP(I/O request package)并傳輸?shù)皆O(shè)備驅(qū)動程序使得硬件設(shè)備能夠被識別。
運行在內(nèi)核模式中的驅(qū)動程序通常使用硬件抽象層(HAL)提供的函數(shù)訪問硬件。如IN、OUT用READ_PORT_BUFFER_UCHAR、WRITE_PORT_BUFFER_UCHAR指令代替。HAL例程執(zhí)行的操作具有平臺相關(guān)性。在Intel x86計算機(jī)上,HAL使用IN指令訪問設(shè)備端口。驅(qū)動程序完成1個I/O操作后,通過調(diào)用一個特殊的內(nèi)核模式服務(wù)例程來完成該IRP。完成該操作是處理IRP的最后動作,它使等待的應(yīng)用程序恢復(fù)運行。
基本上Windows應(yīng)用程序都有一個入口函數(shù),C語言程序的入口函數(shù)是main(),C++程序的入口函數(shù)是WinMain(),驅(qū)動程序同樣有一個入口函數(shù)DriverEntry()。形式如下:
Extern〝C〞NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject,IN PUNICODE_STRING pRegistryPath)。
用extern〝C〞是因為C++語言和C語言對產(chǎn)生的函數(shù)名字處理不一樣。C++語言為了支持函數(shù)重載機(jī)制,在編譯時,要對函數(shù)的名字做額外處理,而在C編譯器中不需要對函數(shù)名字進(jìn)行處理。DDK入口函數(shù)的定義是在C環(huán)境中編寫的,在VC編譯器中用這個函數(shù)就必須加extern〝C〞;IN表示該參數(shù)是1個輸入?yún)?shù);NTSTATUS是32位無符號長整形,常用的NTSTATUS有STATUS_SUCCESS、STATUS_BUFFER_OVERFLOW。幾乎所有的驅(qū)動程序例程都要返回1個NTSTATUS的值。PDRIVER_OBJECT是驅(qū)動對象,是結(jié)構(gòu)體指針變量;PUNICODE_STRING是UNICODE結(jié)構(gòu)體指針,UNICODE是寬字符集;pRegistPath指定了驅(qū)動程序在注冊表中的路徑,一旦驅(qū)動程序安裝,注冊表中會有相應(yīng)的記錄。注冊表中的記錄是為了幫助PCI設(shè)備驅(qū)動程序識別和定位該設(shè)備使用的資源。
PCI驅(qū)動程序與一般的驅(qū)動程序不同的就是支持即插即用(PnP)的功能。當(dāng)PCI總線插入1個PCI設(shè)備時,即插即用能夠通過操作系統(tǒng)協(xié)調(diào)自動分配PCI設(shè)備的中斷號、設(shè)備物理內(nèi)存、I/O地址等。即插即用功能是通過IRP派遣函數(shù)(dispatch function)發(fā)送給PCI設(shè)備驅(qū)動程序,然后設(shè)備驅(qū)動程序發(fā)送到底層的驅(qū)動程序。不同的情況下會有不同的IRP函數(shù)。設(shè)備啟動時,就會發(fā)送IRP_MN_START_DEVICE給WDM驅(qū)動程序,而設(shè)備被突然拔出后會發(fā)送IRP_MN_SURPRISE_REMOVAL給WDM驅(qū)動程序。對即插即用派遣函數(shù)的處理和對其他派遣函數(shù)的處理一樣,首先必須在入口函數(shù)DriverEntry中注冊。形式如下:
Extern〝C〞NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject,IN PUNICODE_STRING pRegistryPath)
{
KdPrint((〝Enter DriverEntry 〞));
//驅(qū)動對象中有驅(qū)動擴(kuò)展子域,驅(qū)動擴(kuò)展里面有添加設(shè)備子域,通過這個子域得到添加設(shè)備函數(shù)的地址
pDriverObject->DriverExtension->AddDevice = PCIWDMAddDevice;
//MajorFunction域記錄的是函數(shù)指針數(shù)組,數(shù)組中的每個成員記錄著一個指針,指針指向的是一個
IPR派遣函數(shù)
//派遣函數(shù)注冊
pDriverObject->MajorFunction[IRP_MJ_PNP] = PCIWDMPnp;
pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = PCIDispatchRoutine;
pDriverObject->MajorFunction[IRP_MJ_CREATE] = PCIDispatchRoutine;
pDriverObject->MajorFunction[IRP_MJ_READ] = PCIDispatchRoutine;
pDriverObject->MajorFunction[IRP_MJ_WRITE] = PCIDispatchRoutine;
//指定驅(qū)動卸載時回調(diào)函數(shù)的地址
pDriverObject->DriverUnload = PCIWDMUnload;
KdPrint((〝Leave DriverEntry 〞));
return STATUS_SUCCESS
}
以上是一個對即插即用IRP派遣函數(shù)的注冊實例,對其他的派遣函數(shù)是默認(rèn)處理。但在實際的編寫過程中是要對其他的派遣函數(shù)做相應(yīng)處理的。
派遣函數(shù)是Windows驅(qū)動程序中的重要函數(shù)。驅(qū)動程序的主要功能是負(fù)責(zé)處理I/O請求,I/O請求由操作系統(tǒng)轉(zhuǎn)換為內(nèi)核數(shù)據(jù)結(jié)構(gòu)IRP,不同的IRP有不同的派遣函數(shù)。上層應(yīng)用程序與底層驅(qū)動程序通訊時,應(yīng)用程序會發(fā)出I/O請求。具體過程如圖3所示。
圖3 驅(qū)動程序與IRP
驅(qū)動程序與PCI總線設(shè)備之間的通訊主要是通過IRP_MN_START_DEVICE獲取設(shè)備資源、用WRITE_REGISTER_XX和READ-REGISTER_XX(其中XX可以是UCHAR、USHORT、BUFFER_UCHAR)系列函數(shù)向設(shè)備物理內(nèi)存讀數(shù)據(jù)和寫數(shù)據(jù)。
PCI設(shè)備驅(qū)動程序通過設(shè)備接口與Windows應(yīng)用程序進(jìn)行通訊。該接口是軟件如何訪問硬件的說明,它由一個128位的GUID(全局唯一標(biāo)識符)標(biāo)識,并能保證所有設(shè)備的標(biāo)識符不沖突。這個標(biāo)識符可以由Windows自帶的創(chuàng)建工具guidgen.exe生成,在運行中輸入guidgen.exe打開如圖4的界面。Guidgen.exe為用戶提供4種產(chǎn)生GUID的方式,它們都是128位的數(shù)字,只是輸出的形式不同,一般選擇圖4的第2種方式產(chǎn)生一個新的GUID(Result里面的顯示結(jié)果),單擊Copy按鈕,會將產(chǎn)生的GUID復(fù)制到內(nèi)存里,在程序中粘貼即可。通過這個GUID可以在注冊表中查到PCI驅(qū)動的相關(guān)信息。
圖4 GUID的獲取
VC調(diào)試 PCI驅(qū)動程序不能用VC自帶的調(diào)試版本和編譯版本,需要增加一個驅(qū)動版本并修改編譯和鏈接選項卡。編譯驅(qū)動程序時會因找不到很多頭文件而造成很多錯誤。解決此問題的方法首先是在DDK安裝目錄中搜索編譯時找不到的頭文件。如果DDK安裝成功的話,馬上就能搜索到編譯器要的頭文件,然后把該頭文件的目錄復(fù)制到VC編譯器的路徑中。VC要包含的頭文件路徑在“Tools→Options→Directories”選項卡中設(shè)置。如圖5所示是驅(qū)動程序所需要包含的一些庫文件和頭文件。要用標(biāo)準(zhǔn)調(diào)用驅(qū)動程序,在project setting中的C++選項卡中設(shè)置成_stadcall即可。
驅(qū)動代碼編譯完成后,生成設(shè)備驅(qū)動系統(tǒng)文件(.sys文件)。添加1個新的PCI硬件設(shè)備時,即將實驗板插入計算機(jī)的PCI插槽,啟動計算機(jī)。如果計算機(jī)不能正常啟動,則實驗板上的PCI芯片焊接有問題或者是計算機(jī)的PCI插槽可能有問題。如果能正常啟動,系統(tǒng)會提示安裝硬件的驅(qū)動程序。此時系統(tǒng)需要尋找與.sys驅(qū)動程序文件同目錄下的.inf文件。.inf文件的基本作用是告訴操作系統(tǒng)設(shè)備及其驅(qū)動程序的相關(guān)信息:要創(chuàng)建或修改的注冊表信息以及驅(qū)動程序的位置、文件名和需要拷貝到的目標(biāo)位置等。
安裝驅(qū)動程序時操作系統(tǒng)通過.inf文件定位驅(qū)動程序文件,把它拷貝到指定位置,并在注冊表記錄驅(qū)動信息。系統(tǒng)重新啟動的時候,一旦發(fā)現(xiàn)這個設(shè)備,就從注冊表來查找設(shè)備的VID(vender identification,又稱vender ID)和DID(device identification)對應(yīng)的驅(qū)動程序,然后加載編譯好的驅(qū)動程序。
完成上述過程后重新啟動計算機(jī)時操作系統(tǒng)會自動搜索到“其他PCI橋設(shè)備”。通過WinDriver也可以檢測到PCI芯片的存在以及芯片的配置信息。
圖5 頭文件配置
本文設(shè)計的驅(qū)動程序主要是為了用PCI總線傳輸NTSC高新技術(shù)產(chǎn)品B碼終端設(shè)備的時碼信號,這對NTSC高新技術(shù)產(chǎn)品提供了更好的應(yīng)用前景。同時,按照本文的設(shè)計再結(jié)合各自的需求稍加改動就可以設(shè)計出各自需求的PCI板卡驅(qū)動程序。
[1] 董邵武, 屈俐俐, 李煥信, 等. NTSC的守時工作進(jìn)展[J]. 時間頻率學(xué)報, 2010, 33(1): 1-4.
[2] 李海. PCI設(shè)備Windows通用驅(qū)動程序設(shè)計[J]. 電子技術(shù)應(yīng)用, 2000, 26(1): 19-22.
[3] 張帆, 史彩成. 驅(qū)動開發(fā)技術(shù)詳解[M]. 北京: 電子工業(yè)出版社, 2008.
[4] 武安河, 邰銘, 于洪濤. Windows 2000/XP WDM設(shè)備驅(qū)動程序開發(fā)[M]. 北京: 電子工業(yè)出版社, 2003.
A design of PCI driver program for time synchronization devices
WU Cheng-ying1, 2, FAN Zhan-you1
(1. National Time Service Center, Chinese Academy of Sciences, Xi′an 710600, China; 2. Graduate University of Chinese Academy of Sciences, Beijing 100039, China)
This paper introduces a designing of PCI driver program in the Windows environment using the tools of Visual C++, DDK and DriverStudio. The hierarchical structure and programming method for PCI driver are described.PCI driver is a kernel-mode driver and it has the function of plug-and-play which is expounded emphatically in this paper. The process and method of getting the device resources through configure space for the PCI device driver, as well as the mechanism of communication between the operating system and the driver program are introduced.
PCI driver program; plug-and-play; configure space
P127.1+2
A
1674-0637(2011)02-0117-08
2010-10-19
國家自然科學(xué)基金資助項目(10773012)
吳成英,女,碩士研究生,主要從事高精度時間同步終端設(shè)計研究。