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_MACHINE\System\CurrentControlSet\Services\DriverName
下面是驱动程序的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_DEV%&&&&&%E_CONTROL |
Dispatch_IoControl |
操控操作 |