高速PCI信號采集卡設計與實現綜合實例之:PCI卡的驅動程序設計
13.4PCI卡的驅動程序設計
13.4.1WDM驅動程序模型
設計完成的信號采集設備在插入計算機后,在對其進行控制之前,需要編寫基于操作系統平臺上的驅動程序。設備驅動程序是一個包含了許多操作系統可調用例程的容器,這些例程可以使硬件設備執行相應的動作,它是硬件與上層軟件之間溝通的橋梁。
在本案例中,我們針對最常使用的操作系統Windows98/2000/XP系統,使用了WDM(WindowsDriverModel)驅動程序模型進行程序開發。
WDM模型是從WinNT3.51和WinNT4的內核模式設備驅動程序發展而來的。WDM主要的變化是增加了對即插即用、電源管理、WindowsManagementInterface(WMI)、設備接口的支持。WDM模型的主要目標是實現能夠跨平臺使用、更安全、更靈活、編制更簡單的Windows設備驅動程序。
WDM采用了“基于對象”的技術,建立了一個分層的驅動程序結構。通過WDM模型的引入,可以減輕設備驅動程序的開發難度和周期,逐漸規范設備驅動程序的開發,應該說,WDM是當前基于Windows平臺的設備驅動程序的主流。
WDM模型主要采用分層的方法,模仿面向對象的技術,先進行邏輯上的“分層”,然后將標準的實現和低層細節“封裝”起來,形成“基類”,客戶程序通過“繼承”的方式來擴展“基類”的功能,完成所需要的實現。
13.4.2設備和驅動程序的層次結構
在WDM模型中,每個硬件設備至少有兩個驅動程序:一個功能驅動程序(functiondriver)和一個總線驅動程序(busdriver)。
如圖13.14所示為WDM中設備對象和驅動程序的層次結構。
圖13.14WDM中設備對象和驅動程序的層次結構
1.過濾驅動程序
過濾驅動程序是一個可選項,當一個用戶需要改變或新添一些功能到一個設備、一類設備或一種總線時,就可以編寫一個過濾驅動程序。在設備棧里,過濾驅動程序安裝在一個或幾個設備驅動程序的上面或下面。
過濾驅動程序攔截對具體設備、類設備、總線的請求,做相應的處理,以改變設備的行為或添加新的功能。但過濾驅動程序只處理那些它所關心的I/O請求,對于其他的請求可以交給其他的驅動程序來處理,這樣可以非常靈活地改變設備的行為。
2.功能驅動程序
功能驅動程序是物理設備的主要驅動程序,它實現設備的具體功能,一般由設備的生產商來編寫。功能驅動程序的主要功能是:提供對設備的操作接口、操作對設備的讀寫、管理設備的電源策略等。
功能驅動程序由類驅動程序和微型驅動程序組成。類驅動程序實現了某一類設備的常用操作,驅動程序的開發者可以只編寫非常小的微型驅動程序,去處理具體設備特殊的操作,而對于其他大量的常規操作,可以調用該類的類驅動程序,這也是WDM驅動程序的優點之一。
微軟公司提供的類驅動程序處理常用的系統任務,比如,即插即用功能和電源管理。類驅動程序保證了操作系統在處理類似的任務時的一致性,從而提高了系統的穩定性。
設備生產商提供微型驅動程序,以實現自己設備的特殊功能,同時調用合適的類驅動程序完成其他的通用工作。將大量的標準操作的代碼通過各種類驅動程序來實現,并集成在操作系統中,這樣的方式可以有效地減少具體設備的微型驅動程序的大小,也就減小了程序出錯的可能。
如果某一類設備存在著工業標準,微軟公司就會提供一個該類設備的WDM類驅動程序。這個類驅動程序實現了該類設備所有必須的任務,但不實現任何具體設備所特有的東西。
3.總線驅動程序
總線驅動程序為實際的I/O總線服務。在WDM的定義中,總線是用來連接其他的物理的、邏輯的、虛擬的設備。總線包括傳統的總線SCSI和PCI,也包括并口、串口以及i8042端口。微軟公司已經為Windows操作系統提供了總線驅動程序。總線驅動程序已經包含在操作系統里了,用戶不必安裝。
一個總線驅動程序負責以下的工作:枚舉總線上的設備,向操作系統報告總線上的動態事件,響應即插即用和電源管理的I/O請求,提供總線的多路存取,管理總線上的設備等。
13.4.3PCI設備驅動程序例程
PCI設備的WDM驅動程序一般需要使用WindowsDDK(DriversDevelopKits)及C語言進行開發。下面介紹一些PCI設備最常見的例程,這些例程將告訴我們如何對PCI設備進行控制。
1.DriverEntry例程
每個WDM驅動程序,不管它的用途是什么,都要對外界顯示一個名字為DriverEntry的例程。該例程初始化各種驅動程序數據結構,并為所有其他驅動程序組件準備好執行環境。主要的工作是在傳遞的DriverObject中存儲一系列的回調例程指針。DRIVER_OBJECT結構有操作系統用于存儲與驅動程序有關的任何信息。
在DriverEntry例程中通常要完成如下步驟。
·DriverEntry找到它將要控制的硬件。那個硬件是經過分配的,即被標志為由該驅動程序控制。
·通過聲明另一個驅動程序入口點,初始化驅動程序對象。通過把函數指針直接保存到驅動程序對象中完成聲明工作。
·如果成功,DriverEntry應該把STATUS_SUCCESS返回給I/O管理程序。
DriverEntry的函數原型為:
NTSTATUSDriverEntry(
PDRIVER_OBJECTpDriverObject,
PUNICODE_STRINGpRegistryPath
)
它接收一個指向它本身的驅動程序對象的指針,DriverEntry例程必須對它(指針)初始化。它還接收一個UNICODE_STRING,它包含注冊表中驅動程序服務鍵的路徑。內核模式驅動程序根據該字符串從系統注冊表中提取任何驅動程序專有的參數。注冊表字符串采用如下形式:
HKEY_LOCAL_MACHINESystemCurrentControlSetServicesDriverName
下面是驅動程序的DriverEntry例程的部分代碼,里面定義了將要用到的回調函數。
NTSTATUSDriverEntry(
PDRIVER_OBJECTpDriverObject,
PUNICODE_STRINGpRegistryPath
)
{ …
//回調函數
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;
…
returnSTATUS_SUCCESS;
}
2.AddDevice例程
大多數的WDMPDO都是在PnP管理器調用該程序入口點時被創建的。插入新設備后,系統啟動時,總線枚舉器會發現總線上的所有設備會自動尋找并安裝設備的驅動程序,并由驅動程序中的處理PnP功能模塊自動處理AddDevice例程及其他的PnP消息。
AddDevice例程使用IoCreateDevice函數創建設備對象,再使用IoCreateSymbolicLink函數將設備組成為一個特定的設備接口,供Win32使用。
其函數原型為:
NTSTATUSAddDevice(PDRIVER_OBJECTpDriverObject,PDEVICE_OBJECTpdo)
必須在DriverEntry入口函數中進行聲明,下面是該函數的部分代碼:
NTSTATUSAddDevice(
PDRIVER_OBJECTpDriverObject,
PDEVICE_OBJECTpdo
)
{…
//建立設備名稱并創建它
for(i=0;i20;i++)
{
//轉成String格式
Swprintf(DeviceName,L\Device\PLX_DRIVER_NAME_UNICODEL-%d,i);
//初始化DeviceName_Unicode
RtlInitUnicodeString(DeviceName_Unicode,DeviceName);
//創建設備
status=IoCreateDevice(
pDriverObject,
sizeof(DEVICE_EXTENSION),
DeviceName_Unicode,
FILE_DEVICE_UNKNOWN,
0,
FALSE,
fdo
);
…
//為用戶應用程序創建Win32關聯名
//轉成String格式
swprintf(DeviceLinkName,L\DosDevices\PLX_DRIVER_NAME_UNICODEL-%d,i);
//初始化DeviceLinkName_Unicode
RtlInitUnicodeString(
DeviceLinkName_Unicode,
DeviceLinkName
);
//建立設備關聯符號
status=IoCreateSymbolicLink(
DeviceLinkName_Unicode,
DeviceName_Unicode
);
…
returnSTATUS_SUCCESS;
}
3.DispatchPnp例程
支持即插即用主要是指實現一個AddDevice例程和一個IRP_MJ_PNP處理程序。這個PnPIRP有8個主要次功能代碼,大多數的WDM驅動程序需要支持這些次功能代碼。
·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,這里就不再一一介紹,下面是這部分驅動的部分代碼。
NTSTATUSDispatch_Pnp(
PDEVICE_OBJECTfdo,
PIRPpIrp
)
{ …
//檢查次功能代碼
switch(stack->MinorFunction)
{
caseIRP_MN_START_DEVICE: //配置并初始化設備
status=HandleStartDevice(fdo,pIrp);
break;
caseIRP_MN_STOP_DEVICE: //關閉設備
status=HandleStopDevice(fdo,pIrp);
break;
caseIRP_MN_REMOVE_DEVICE: //關閉并刪除設備
Unlock=FALSE;
status=HandleRemoveDevice(fdo,pIrp);
break;
caseIRP_MN_QUERY_REMOVE_DEVICE: //查詢設備是否可被安全刪除
status=DefaultPnpHandler(fdo,pIrp);
break;
caseIRP_MN_CANCEL_REMOVE_DEVICE: //忽略以前的QUERY_REMOVE
status=DefaultPnpHandler(fdo,pIrp);
break;
caseIRP_MN_QUERY_STOP_DEVICE: //查詢設備是否可被安全關閉
status=DefaultPnpHandler(fdo,pIrp);
break;
caseIRP_MN_CANCEL_STOP_DEVICE: //忽略以前的QUERY_STOP
status=DefaultPnpHandler(fdo,pIrp);
break;
caseIRP_MN_QUERY_CAPABILITIES: //取設備能力
status=DefaultPnpHandler(fdo,pIrp);
break;
…}
returnstatus;
}
這些功能代碼函數都在DriverEntry()入口函數中進行了聲明。
4.DispatchPower例程
WDM設備驅動程序支持電源管理,一個設備可以改變它的電源使用來響應系統電源狀態變化,且在處于空閑狀態時可以減少它自己的電源使用。一個休眠的設備可以喚醒系統,如當調制解調器收到到達的呼叫時。
驅動程序的電源管理例程圍繞電源IRP_MJ_POWERIRP進行處理,這些例程處理這個IRP,并在需要時產生這個IRP。這個IRP有4個電源管理次功能代碼。
·IRP_MN_WAIT_WAKE。
·IRP_MN_POWER_SEQUENCE。
·IRP_MN_SET_POWER。
·IRP_MN_QUERY_POWER。
這部分的代碼如下:
NTSTATUSDispatch_Power(
PDEVICE_OBJECTfdo,
PIRPpIrp
)
{
NTSTATUSstatus;
PIO_STACK_LOCATIONstack;
//獲取指向被調用的Irp的棧位置的指針
stack=IoGetCurrentIrpStackLocation(pIrp);
switch(stack->MinorFunction)
{
caseIRP_MN_WAIT_WAKE: //喚醒計算機,響應一個外部事件
status=DefaultPowerHandler(fdo,pIrp);
break;
caseIRP_MN_POWER_SEQUENCE: //確定設備是否真正進入特定的電源狀態
status=DefaultPowerHandler(fdo,pIrp);
break;
caseIRP_MN_SET_POWER: //設置系統或設備電源狀態
status=HandleSetPower(fdo,pIrp);
break;
caseIRP_MN_QUERY_POWER: //查詢系統或設備狀態變化是否可行
status=HandleQueryPower(fdo,pIrp);
break;
default: //確定設備是否真正進入特定的電源狀態
status=DefaultPowerHandler(fdo,pIrp);
break;
}
returnstatus;
}
5.Unload例程
在默認情況下,驅動程序裝入之后,在重新引導之前就一直保持在系統中。要使系統可卸載,必須有一個Unload例程。Uload例程在DriverEntry期間聲明,然后,每當驅動程序被手動或自動卸載時,I/O管理程序就調用該例程。
該例程函數原型為:
VOIDUnload(PDRIVER_OBJECTpDriverObject)
通常一個Uload例程執行如下工作。
·對于某些種類的硬件,設備狀態應當保存在注冊表中。那樣,在DriverEntry下一次執行時,設備能夠恢復到最近已知的狀態。
·如果啟動了設備中斷,Unload例程必須僅用它們,并斷開它們與中斷對象的連接。
·必須釋放屬于驅動程式的硬件。
·必須從Win32名字空間中刪除符號鏈接名字,這可以用IoDeleteSymbolicLink完成。
·必須用IoDeleteDevice刪除設備對象自身。
·釋放驅動程序占據的任何池內存。
驅動中的部分代碼如下:
VOIDDriverUnload(PDRIVER_OBJECTpDriverObject)
{
//釋放公共緩沖區
if(Gbl_CommonBuffer.PciMem.PhysicalAddr!=(U32)NULL)
{
//釋放內存描述列表(MDL)
if(Gbl_CommonBuffer.pMdl!=NULL)
{
IoFreeMdl(Gbl_CommonBuffer.pMdl);
Gbl_CommonBuffer.pMdl=NULL;
}
//釋放公共緩沖區
if(Gbl_CommonBuffer.pKernelVa!=NULL)
{
MmFreeContiguousMemory(Gbl_CommonBuffer.pKernelVa);
Gbl_CommonBuffer.pKernelVa=NULL;
}
}
//釋放DMA使用的緩沖區
#ifdefined(DMA_SUPPORT)
DriverBufferTerminate();
#endif
}
6.Dispatch例程
驅動程序裝載是第一步工作,但是最終驅動程序的作業是響應I/O請求——來自用戶模式應用程序的請求或者來自系統其他地方的請求。Windows2000驅動程序通過Dispatch例程來處理這些請求。I/O管理程序調用這些例程以響應請求。
要啟用特定的I/O函數代碼,驅動函數必須首先“聲明”響應這樣一個請求的Dispatch例程。聲明機制是DriverEntry執行的工作,它把Dispatch例程函數地址保存在驅動程序對象的MajorFunction表的合適位置上。
I/O函數代碼是用于表的索引。其中,每個I/O函數代碼(表索引)由一個IRP_MJ_XXX形式的惟一符號標示,在NTDDK.h(或WDM.h)包含文件中定義。
所有驅動程序必須支持函數代碼IRP_MJ_CREATE,因為編寫此代碼的目的是響應Win32CreateFile調用。如果不支持該代碼,Win32應用程序將無法獲取設備的句柄。同樣也必須支持IRP_MJ_CLOSE,以處理Win32CloserHandle調用。
驅動程序應當支持的其他函數代碼取決它控制的設備的本質。表13.3將用到的I/O函數代碼與產生它們的Win32調用相關聯。
表13.3 Dispatch例程表
Win32函數 | IRP主功能代碼 | 驅動程序例程的名稱 | 說明 |
CreateFile | IRP_MJ_CREATE | Dispatch_Create | 請求一個句柄 |
CloseHandle | IRP_MJ_CLEANUP | Dispatch_Cleanup | 關閉句柄 |
CloseHandle | IRP_MJ_CLOSE | Dispatch_Close | 取消掛起的IRP |
ReadFile | IRP_MJ_READ | Dispatch_Read | 從設備獲取數據 |
WriteFile | IRP_MJ_WRITE | Dispatch_Write | 將數據發送到設備 |
DeviceIoControl | IRP_MJ_DEVICE_CONTROL | Dispatch_IoControl | 控制操作 |
評論