?
13.4??PCI卡的驅(qū)動程序設(shè)計
13.4.1??WDM驅(qū)動程序模型
設(shè)計完成的信號采集設(shè)備在插入計算機后,在對其進行控制之前,需要編寫基于操作系統(tǒng)平臺上的驅(qū)動程序。設(shè)備驅(qū)動程序是一個包含了許多操作系統(tǒng)可調(diào)用例程的容器,這些例程可以使硬件設(shè)備執(zhí)行相應(yīng)的動作,它是硬件與上層軟件之間溝通的橋梁。
在本案例中,我們針對最常使用的操作系統(tǒng)Windows?98/2000/XP系統(tǒng),使用了WDM(Windows?Driver?Model)驅(qū)動程序模型進行程序開發(fā)。
WDM模型是從WinNT?3.51和WinNT?4的內(nèi)核模式設(shè)備驅(qū)動程序發(fā)展而來的。WDM主要的變化是增加了對即插即用、電源管理、Windows?Management?Interface(WMI)、設(shè)備接口的支持。WDM模型的主要目標是實現(xiàn)能夠跨平臺使用、更安全、更靈活、編制更簡單的Windows設(shè)備驅(qū)動程序。
WDM采用了“基于對象”的技術(shù),建立了一個分層的驅(qū)動程序結(jié)構(gòu)。通過WDM模型的引入,可以減輕設(shè)備驅(qū)動程序的開發(fā)難度和周期,逐漸規(guī)范設(shè)備驅(qū)動程序的開發(fā),應(yīng)該說,WDM是當前基于Windows平臺的設(shè)備驅(qū)動程序的主流。
WDM模型主要采用分層的方法,模仿面向?qū)ο蟮募夹g(shù),先進行邏輯上的“分層”,然后將標準的實現(xiàn)和低層細節(jié)“封裝”起來,形成“基類”,客戶程序通過“繼承”的方式來擴展“基類”的功能,完成所需要的實現(xiàn)。
13.4.2??設(shè)備和驅(qū)動程序的層次結(jié)構(gòu)
在WDM模型中,每個硬件設(shè)備至少有兩個驅(qū)動程序:一個功能驅(qū)動程序(function?driver)和一個總線驅(qū)動程序(bus?driver)。
如圖13.14所示為WDM中設(shè)備對象和驅(qū)動程序的層次結(jié)構(gòu)。
圖13.14??WDM中設(shè)備對象和驅(qū)動程序的層次結(jié)構(gòu)
?
1.過濾驅(qū)動程序
過濾驅(qū)動程序是一個可選項,當一個用戶需要改變或新添一些功能到一個設(shè)備、一類設(shè)備或一種總線時,就可以編寫一個過濾驅(qū)動程序。在設(shè)備棧里,過濾驅(qū)動程序安裝在一個或幾個設(shè)備驅(qū)動程序的上面或下面。
過濾驅(qū)動程序攔截對具體設(shè)備、類設(shè)備、總線的請求,做相應(yīng)的處理,以改變設(shè)備的行為或添加新的功能。但過濾驅(qū)動程序只處理那些它所關(guān)心的I/O請求,對于其他的請求可以交給其他的驅(qū)動程序來處理,這樣可以非常靈活地改變設(shè)備的行為。
2.功能驅(qū)動程序
功能驅(qū)動程序是物理設(shè)備的主要驅(qū)動程序,它實現(xiàn)設(shè)備的具體功能,一般由設(shè)備的生產(chǎn)商來編寫。功能驅(qū)動程序的主要功能是:提供對設(shè)備的操作接口、操作對設(shè)備的讀寫、管理設(shè)備的電源策略等。
功能驅(qū)動程序由類驅(qū)動程序和微型驅(qū)動程序組成。類驅(qū)動程序?qū)崿F(xiàn)了某一類設(shè)備的常用操作,驅(qū)動程序的開發(fā)者可以只編寫非常小的微型驅(qū)動程序,去處理具體設(shè)備特殊的操作,而對于其他大量的常規(guī)操作,可以調(diào)用該類的類驅(qū)動程序,這也是WDM驅(qū)動程序的優(yōu)點之一。
微軟公司提供的類驅(qū)動程序處理常用的系統(tǒng)任務(wù),比如,即插即用功能和電源管理。類驅(qū)動程序保證了操作系統(tǒng)在處理類似的任務(wù)時的一致性,從而提高了系統(tǒng)的穩(wěn)定性。?
設(shè)備生產(chǎn)商提供微型驅(qū)動程序,以實現(xiàn)自己設(shè)備的特殊功能,同時調(diào)用合適的類驅(qū)動程序完成其他的通用工作。將大量的標準操作的代碼通過各種類驅(qū)動程序來實現(xiàn),并集成在操作系統(tǒng)中,這樣的方式可以有效地減少具體設(shè)備的微型驅(qū)動程序的大小,也就減小了程序出錯的可能。
如果某一類設(shè)備存在著工業(yè)標準,微軟公司就會提供一個該類設(shè)備的WDM類驅(qū)動程序。這個類驅(qū)動程序?qū)崿F(xiàn)了該類設(shè)備所有必須的任務(wù),但不實現(xiàn)任何具體設(shè)備所特有的東西。
3.總線驅(qū)動程序
總線驅(qū)動程序為實際的I/O總線服務(wù)。在WDM的定義中,總線是用來連接其他的物理的、邏輯的、虛擬的設(shè)備??偩€包括傳統(tǒng)的總線SCSI和PCI,也包括并口、串口以及i8042端口。微軟公司已經(jīng)為Windows操作系統(tǒng)提供了總線驅(qū)動程序??偩€驅(qū)動程序已經(jīng)包含在操作系統(tǒng)里了,用戶不必安裝。
一個總線驅(qū)動程序負責以下的工作:枚舉總線上的設(shè)備,向操作系統(tǒng)報告總線上的動態(tài)事件,響應(yīng)即插即用和電源管理的I/O請求,提供總線的多路存取,管理總線上的設(shè)備等。
?
13.4.3??PCI設(shè)備驅(qū)動程序例程
PCI設(shè)備的WDM驅(qū)動程序一般需要使用Windows?DDK(Drivers?Develop?Kits)及C語言進行開發(fā)。下面介紹一些PCI設(shè)備最常見的例程,這些例程將告訴我們?nèi)绾螌CI設(shè)備進行控制。
1.DriverEntry例程
每個WDM驅(qū)動程序,不管它的用途是什么,都要對外界顯示一個名字為DriverEntry的例程。該例程初始化各種驅(qū)動程序數(shù)據(jù)結(jié)構(gòu),并為所有其他驅(qū)動程序組件準備好執(zhí)行環(huán)境。主要的工作是在傳遞的DriverObject中存儲一系列的回調(diào)例程指針。DRIVER_OBJECT結(jié)構(gòu)有操作系統(tǒng)用于存儲與驅(qū)動程序有關(guān)的任何信息。
在DriverEntry例程中通常要完成如下步驟。
·??DriverEntry?找到它將要控制的硬件。那個硬件是經(jīng)過分配的,即被標志為由該驅(qū)動程序控制。
·??通過聲明另一個驅(qū)動程序入口點,初始化驅(qū)動程序?qū)ο蟆Mㄟ^把函數(shù)指針直接保存到驅(qū)動程序?qū)ο笾型瓿陕暶鞴ぷ鳌?/p>
·??如果成功,DriverEntry應(yīng)該把STATUS_SUCCESS返回給I/O管理程序。
DriverEntry的函數(shù)原型為:
NTSTATUS?DriverEntry?(
PDRIVER_OBJECT?pDriverObject,?
PUNICODE_STRING?pRegistryPath
)
它接收一個指向它本身的驅(qū)動程序?qū)ο蟮闹羔?,DriverEntry例程必須對它(指針)初始化。它還接收一個UNICODE_STRING,它包含注冊表中驅(qū)動程序服務(wù)鍵的路徑。內(nèi)核模式驅(qū)動程序根據(jù)該字符串從系統(tǒng)注冊表中提取任何驅(qū)動程序?qū)S械膮?shù)。注冊表字符串采用如下形式:
HKEY_LOCAL_MACHINESystemCurrentControlSetServicesDriverName
下面是驅(qū)動程序的DriverEntry例程的部分代碼,里面定義了將要用到的回調(diào)函數(shù)。
NTSTATUS?DriverEntry?(
????PDRIVER_OBJECT???pDriverObject,
????PUNICODE_STRING???pRegistryPath
????)
{ …
????//?回調(diào)函數(shù)
????pDriverObject->DriverUnload????????????????????????? =?DriverUnload;
????pDriverObject->MajorFunction[IRP_MJ_CREATE]????????? =Dispatch_Create;
????pDriverObject->MajorFunction[IRP_MJ_CLOSE]????????? =Dispatch_Close;
????pDriverObject->MajorFunction[IRP_MJ_READ]??????????? =Dispatch_Read;
????pDriverObject->MajorFunction[IRP_MJ_WRITE]?????????? =Dispatch_Write;
????pDriverObject->MajorFunction[IRP_MJ_CLEANUP]??????? =Dispatch_Cleanup;
????pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL]?
???????????????????????????????? ??=Dispatch_IoControl;
????pDriverObject->MajorFunction[IRP_MJ_PNP]????????????? =?Dispatch_Pnp;
????pDriverObject->MajorFunction[IRP_MJ_POWER]?????????? =Dispatch_Power;
????pDriverObject->MajorFunction[IRP_MJ_SYSTEM_CONTROL]
? ??=Dispatch_System?? ????????????Control;
????pDriverObject->DriverExtension->AddDevice????????? ??=?AddDevice;
????…
????return?STATUS_SUCCESS;
}
?
2.AddDevice例程
大多數(shù)的WDM?PDO?都是在PnP管理器調(diào)用該程序入口點時被創(chuàng)建的。插入新設(shè)備后,系統(tǒng)啟動時,總線枚舉器會發(fā)現(xiàn)總線上的所有設(shè)備會自動尋找并安裝設(shè)備的驅(qū)動程序,并由驅(qū)動程序中的處理PnP功能模塊自動處理AddDevice例程及其他的PnP消息。
AddDevice例程使用IoCreateDevice函數(shù)創(chuàng)建設(shè)備對象,再使用IoCreateSymbolicLink函數(shù)將設(shè)備組成為一個特定的設(shè)備接口,供Win32使用。
其函數(shù)原型為:
NTSTATUS?AddDevice?(PDRIVER_OBJECT?pDriverObject,?PDEVICE_OBJECT?pdo)
必須在DriverEntry入口函數(shù)中進行聲明,下面是該函數(shù)的部分代碼:
NTSTATUS?AddDevice?(
????PDRIVER_OBJECT?pDriverObject,
????PDEVICE_OBJECT?pdo
????)
{…
????//?建立設(shè)備名稱并創(chuàng)建它
????for?(i=0;?i?<?20;?i++)
????{
????//轉(zhuǎn)成String格式
????????Swprintf?(DeviceName,?L"\Device\"?PLX_DRIVER_NAME_UNICODE?L"-%d",?i);
????//初始化DeviceName_Unicode
????????RtlInitUnicodeString?(&DeviceName_Unicode,?DeviceName);
????//?創(chuàng)建設(shè)備
????????status?=?IoCreateDevice(
????????????????pDriverObject,
????????????????sizeof(DEVICE_EXTENSION),
????????????????&DeviceName_Unicode,
????????????????FILE_DEVICE_UNKNOWN,
????????????????0,
????????????????FALSE,?
????????????????&fdo
????????????????);
????…
?????//為用戶應(yīng)用程序創(chuàng)建Win32關(guān)聯(lián)名
????//轉(zhuǎn)成String格式
????swprintf?(DeviceLinkName,?L"\DosDevices\"?PLX_DRIVER_NAME_UNICODE?L"-%d",?i);
????//初始化DeviceLinkName_Unicode
????RtlInitUnicodeString?(
????????&DeviceLinkName_Unicode,
????????DeviceLinkName
????????);
????//建立設(shè)備關(guān)聯(lián)符號
????status?=?IoCreateSymbolicLink?(
????????????&DeviceLinkName_Unicode,
????????????&DeviceName_Unicode
????????????);
????…
????return?STATUS_SUCCESS;
}
3.DispatchPnp例程
支持即插即用主要是指實現(xiàn)一個AddDevice例程和一個IRP_MJ_PNP處理程序。這個PnP?IRP有8個主要次功能代碼,大多數(shù)的WDM驅(qū)動程序需要支持這些次功能代碼。
·??IRP_MN_START_DEVICE。
·??IRP_MN_QUERY_REMOVE_DEVICE。
·??IRP_MN_REMOVE_DEVICE。
·??IRP_MN_CANCLE_REMOVE_DEVICE。
·??IRP_MN_STOP_DEVICE。
·??IRP_MN_QUERY_STOP_DEVICE。
·??IRP_MN_CANCLE_STOP_DEVICE。
·??IRP_MN_QUERY_CAPABILITIES。
還有一些不太常用的IRP,這里就不再一一介紹,下面是這部分驅(qū)動的部分代碼。
NTSTATUS?Dispatch_Pnp(
????PDEVICE_OBJECT?fdo,
????PIRP???????????pIrp
????)
{ …
????//?檢查次功能代碼
????switch?(stack?->?MinorFunction)
????{
????????case?IRP_MN_START_DEVICE:????????????????????? //配置并初始化設(shè)備
????????????status?=?HandleStartDevice(fdo,?pIrp);
????????????break;
????????case?IRP_MN_STOP_DEVICE: ????????????????????? //關(guān)閉設(shè)備
????????????status?=?HandleStopDevice(fdo,?pIrp);
????????????break;
????????case?IRP_MN_REMOVE_DEVICE:????????????????? //關(guān)閉并刪除設(shè)備
????????????Unlock?=?FALSE;
????????????status?=?HandleRemoveDevice(fdo,?pIrp);
????????????break;
????????case?IRP_MN_QUERY_REMOVE_DEVICE: ??????????? //查詢設(shè)備是否可被安全刪除
????????????status?=?DefaultPnpHandler(fdo,?pIrp);
????????????break;
????????case?IRP_MN_CANCEL_REMOVE_DEVICE:?????????? //忽略以前的QUERY_REMOVE
????????????status?=?DefaultPnpHandler(fdo,?pIrp);
????????????break;
????????case?IRP_MN_QUERY_STOP_DEVICE:?????????????? //查詢設(shè)備是否可被安全關(guān)閉
????????????status?=?DefaultPnpHandler(fdo,?pIrp);
????????????break;
????????case?IRP_MN_CANCEL_STOP_DEVICE:???????????? //忽略以前的QUERY_STOP
????????????status?=?DefaultPnpHandler(fdo,?pIrp);
????????????break;
????????case?IRP_MN_QUERY_CAPABILITIES:???????????? //取設(shè)備能力
????????????status?=?DefaultPnpHandler(fdo,?pIrp);
????????????break;
????…?}
????return?status;
}
這些功能代碼函數(shù)都在DriverEntry()入口函數(shù)中進行了聲明。?
?
4.DispatchPower例程
WDM設(shè)備驅(qū)動程序支持電源管理,一個設(shè)備可以改變它的電源使用來響應(yīng)系統(tǒng)電源狀態(tài)變化,且在處于空閑狀態(tài)時可以減少它自己的電源使用。一個休眠的設(shè)備可以喚醒系統(tǒng),如當調(diào)制解調(diào)器收到到達的呼叫時。
驅(qū)動程序的電源管理例程圍繞電源IRP_MJ_POWER?IRP?進行處理,這些例程處理這個IRP,并在需要時產(chǎn)生這個IRP。這個IRP有4個電源管理次功能代碼。
·??IRP_MN_WAIT_WAKE。
·??IRP_MN_POWER_SEQUENCE。
·??IRP_MN_SET_POWER。
·??IRP_MN_QUERY_POWER。
這部分的代碼如下:
NTSTATUS?Dispatch_Power(
????PDEVICE_OBJECT?fdo,
????PIRP???????????pIrp
????)
{
????NTSTATUS???????????status;
????PIO_STACK_LOCATION?stack;
//獲取指向被調(diào)用的Irp的棧位置的指針
????stack?=?IoGetCurrentIrpStackLocation(pIrp);
????switch?(stack->MinorFunction)
????{
????????case?IRP_MN_WAIT_WAKE:??????????????? //喚醒計算機,響應(yīng)一個外部事件
????????????status?=?DefaultPowerHandler(fdo,?pIrp);
????????????break;
????????case?IRP_MN_POWER_SEQUENCE:????????? //確定設(shè)備是否真正進入特定的電源狀態(tài)
????????????status?=?DefaultPowerHandler(fdo,?pIrp);
????????????break;
????????case?IRP_MN_SET_POWER:??????????????? //設(shè)置系統(tǒng)或設(shè)備電源狀態(tài)
????????????status?=?HandleSetPower(fdo,?pIrp);
????????????break;
????????case?IRP_MN_QUERY_POWER:???????????? //查詢系統(tǒng)或設(shè)備狀態(tài)變化是否可行
????????????status?=?HandleQueryPower(fdo,?pIrp);
????????????break;
????????default:??????????????????????????????? //確定設(shè)備是否真正進入特定的電源狀態(tài)
????????????status?=?DefaultPowerHandler(fdo,?pIrp);
????????????break;
????}
????return?status;
}
5.Unload例程
在默認情況下,驅(qū)動程序裝入之后,在重新引導(dǎo)之前就一直保持在系統(tǒng)中。要使系統(tǒng)可卸載,必須有一個Unload例程。Uload例程在DriverEntry期間聲明,然后,每當驅(qū)動程序被手動或自動卸載時,I/O管理程序就調(diào)用該例程。
該例程函數(shù)原型為:
VOID?Unload?(?PDRIVER_OBJECT?pDriverObject?)
通常一個Uload例程執(zhí)行如下工作。
·??對于某些種類的硬件,設(shè)備狀態(tài)應(yīng)當保存在注冊表中。那樣,在DriverEntry下一次執(zhí)行時,設(shè)備能夠恢復(fù)到最近已知的狀態(tài)。
·??如果啟動了設(shè)備中斷,Unload例程必須僅用它們,并斷開它們與中斷對象的連接。
·??必須釋放屬于驅(qū)動程式的硬件。
·??必須從Win32名字空間中刪除符號鏈接名字,這可以用IoDeleteSymbolicLink完成。
·??必須用IoDeleteDevice刪除設(shè)備對象自身。
·??釋放驅(qū)動程序占據(jù)的任何池內(nèi)存。
驅(qū)動中的部分代碼如下:
VOID?DriverUnload(PDRIVER_OBJECT?pDriverObject)
{
????//?釋放公共緩沖區(qū)
????if?(Gbl_CommonBuffer.PciMem.PhysicalAddr?!=?(U32)NULL)
????{
????????//?釋放內(nèi)存描述列表(MDL)
????????if?(Gbl_CommonBuffer.pMdl?!=?NULL)
????????{
????????????IoFreeMdl(Gbl_CommonBuffer.pMdl);
????????????Gbl_CommonBuffer.pMdl?=?NULL;
????????}
????????//?釋放公共緩沖區(qū)
????????if?(Gbl_CommonBuffer.pKernelVa?!=?NULL)
????????{
????????????MmFreeContiguousMemory(Gbl_CommonBuffer.pKernelVa);
????????????Gbl_CommonBuffer.pKernelVa?=?NULL;
????????}
????}
//釋放DMA使用的緩沖區(qū)
#if?defined(DMA_SUPPORT)
????DriverBufferTerminate();
#endif
}
?
6.Dispatch例程
驅(qū)動程序裝載是第一步工作,但是最終驅(qū)動程序的作業(yè)是響應(yīng)I/O請求——來自用戶模式應(yīng)用程序的請求或者來自系統(tǒng)其他地方的請求。Windows?2000驅(qū)動程序通過Dispatch例程來處理這些請求。I/O管理程序調(diào)用這些例程以響應(yīng)請求。
要啟用特定的I/O函數(shù)代碼,驅(qū)動函數(shù)必須首先“聲明”響應(yīng)這樣一個請求的Dispatch例程。聲明機制是DriverEntry執(zhí)行的工作,它把Dispatch例程函數(shù)地址保存在驅(qū)動程序?qū)ο蟮腗ajorFunction表的合適位置上。
I/O函數(shù)代碼是用于表的索引。其中,每個I/O函數(shù)代碼(表索引)由一個IRP_MJ_XXX形式的惟一符號標示,在NTDDK.h(或WDM.h)包含文件中定義。
所有驅(qū)動程序必須支持函數(shù)代碼IRP_MJ_CREATE,因為編寫此代碼的目的是響應(yīng)Win32?CreateFile調(diào)用。如果不支持該代碼,Win32應(yīng)用程序?qū)o法獲取設(shè)備的句柄。同樣也必須支持IRP_MJ_CLOSE,以處理Win32?CloserHandle調(diào)用。
驅(qū)動程序應(yīng)當支持的其他函數(shù)代碼取決它控制的設(shè)備的本質(zhì)。表13.3將用到的I/O函數(shù)代碼與產(chǎn)生它們的Win32調(diào)用相關(guān)聯(lián)。
表13.3 Dispatch例程表
Win32函數(shù) |
IRP主功能代碼 |
驅(qū)動程序例程的名稱 |
說????明 |
CreateFile |
IRP_MJ_CREATE |
Dispatch_Create |
請求一個句柄 |
CloseHandle |
IRP_MJ_CLEANUP |
Dispatch_Cleanup |
關(guān)閉句柄 |
CloseHandle |
IRP_MJ_CLOSE |
Dispatch_Close |
取消掛起的IRP |
ReadFile |
IRP_MJ_READ |
Dispatch_Read |
從設(shè)備獲取數(shù)據(jù) |
WriteFile |
IRP_MJ_WRITE |
Dispatch_Write |
將數(shù)據(jù)發(fā)送到設(shè)備 |
DeviceIoControl |
IRP_MJ_DEVICE_CONTROL |
Dispatch_IoControl |
控制操作 |