控制器和多功能设备

来源:互联网 发布:java设置cookie有效期 编辑:程序博客网 时间:2024/04/28 08:00
在第六章提到过,有两种类型的设备不太符合PNP的框架。它们是控制器设备和多功能设备。控制器设备管理一些子设备,而多功能设备在同一个设备上有几种功能。它们的共同之处在于,必须使用独立的I/O资源来创建多个设备对象。
在Windows XP 下,支持那些遵守各自总线标准的设备很容易,例如: PCI, PCMCIA,USB设备等。 PCI 总线驱动可自动识别 PCI 多功能卡。对PCMCIA 设备,可以参展DDK中的 MF.sys 驱动的详细说明,该驱动是一个多功能卡的功能驱动,MF.sys 可以枚举卡的各个功能,然后为每个功能加载各自的功能驱动。对具有一个配置的USB设备,USB总线驱动会为该配置的每个接口分别加载相应的驱动。
Windows 98 和 Windows XP 相比,只提供了对USB多功能设备的支持。在Windows 98中,为了支持多功能设备,开发者必须做更多的工作。不仅需要为主设备提供一个功能驱动还要为连在上面的所有子设备提供单独的功能驱动。主设备的功能驱动需要枚举子设备,提供对子设备PNP和电源的缺省处理,它有点像一个微型总线驱动。写一个完整的总线驱动是一个相当大的工程,本文并不试图描述其详细过程。本文会描述一些基本的处理机制,以使读者可以枚举子设备,完成那些不太符合微软模型的多功能设备的驱动。
总体结构
下图显示了一个有子设备的父设备(就像总线设备)的设备对象的拓扑图。控制器和多功能设备有相似的拓扑结构。
 
可以看到,父设备连在一个标准的总线上,总线驱动检测到了这个父设备,然后像对任何普通设备一样配置它。当启动父设备后, PNP 管理器发出一个次功能码是 IRP_MN_QUERY_DEVICE_RELATIONS 的IRP给父设备以获得所谓的总线关系。这个请求会对任何设备发出,事实上,PNP管理器不知道某个设备是否有子设备。
为了对这个请求作出回应,父设备的功能驱动会查找或者创建额外的设备对象,所有这些设备对象会成为子设备栈栈底的PDO。然后,PNP管理器接着为子设备加载功能设备和过滤设备。
父设备的驱动必须担当两种角色:对多功能设备担当FDO, 对其子设备,担当PDO。当它做FDO时,需要处理PNP IRP 和 Power IRP。当它做PDO时,它被当作 PNP IRP 和Power IRP 最后处理的地方。
子设备对象
父设备必须为自设备创建PDO,有两种方法:
Ø 对支持热插拔的设备,应该维护一个PDO的列表,在PNP管理器每次发出得到总线关系的请求时更新。同时必须有硬件枚举的功能以检测新插入的设备,和被移开的设备,动态的更新PDO的列表。
Ø 对于有固定功能的父设备来讲,可以创建一个固定数目的PDO列表。
设备扩展结构
FDO 和所有的PDO都属于一个多功能设备驱动,这可能是驱动比较复杂的一点。这意味着所有对PDO和FDO的请求都会由同一组dispatch例程处理。驱动需要自己处理对PDO和对FDO的PNP请求的差别之处。处理这个问题的一个方法是定义公共的设备扩展结构,下面是示例代码:
// The FDO extension
typedef struct _DEVICE_EXTENSION {
ULONG  flags;
.........
} DEVICE_EXTENSION, *PDEVICE_EXTENSION;
//The PDO extension
typedef struct _PDO_EXTENSION {
ULONG  flags;
........
} PDO_EXTENSION, *PPDO_EXTENSION;
// The Commen part
typedef  struct _COMMON_EXTENSION {
ULONG  flags;
.......
} COMMON_EXTENSION, *PCOMMON_EXTENSION;
#define ISPDO 0x00000001
那么,dispatch例程看起来会是这样:
NTSTATUS DispatchSomething(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
PCOMMON_EXTENSION pcx = (PCOMMON_EXTENSION)DeviceObject->DeviceObject;
if (pcx->flags & ISPDO)
{
return DispatchSomethingPdo(DeiveceObject, Irp);
}
else
{
return DispatchSomethingFdo(DeiveceObject, Irp);
}
}
程序需要测试设备扩展公共部分的标志,以确定这个请求是按照PDO规则处理,还是按FDO规则处理。
创建子设备的例子
假定MULFUNC是一个多功能设备,他有两个功能,就是会拥有两个子设备。假定这两个子设备叫 A 和 B。 MULFUNC 会在响应 IRP_MN_START_DEVICE 的请求时为子设备创建PDO.
NTSTATUS StartDevice(DEVICE_PBJECT fdo, ... )
{
PDEVICE_EXTENSION pdx = 
(PDEVICE_EXTENSION) fdo->DeivceExtension;
CreateChild(pdx, CHILDTYPEA, &pdx->ChildA);
CreateChild(pdx, CHILDTYPEB, &pdx->ChildB);
return  STATUS_SUCCESS;
}
NTSTATUS CreateChild(PDEVICE_EXTENSION pdx, ULONG flags, PDEVICE_OBJECT* pdo)
{
PDEVICE_OBJECT child;
IoCreateDevice(pdx->DriverObject, sizeof(PDO_EXTENSION), NULL, 
FILE_DEVICE_UNKNOWN, FILE_AUTOGENERATED_DEVICE_NAME,
    FALSE, &child);
PPDO_EXTENSION px = (PDO_EXTENSION) child->DeiveExtension;
px->flags = ISPDO | flags;
px->DeiveObject = child;
px->Fdo = pdx->DeiviceObject;
child->Flags &= ~DO_DEVICE_INITIALIZING;
return STATUS_SUCCESS;
}
① CHILDTYPEA 和 CHILDTYPEB 是设备扩展公共部分的额外标志位。在一个这是的总线驱动中,一般会在 IRP_MN_QUERY_DEVICE_RELATIONS 的处理中枚举实际的硬件设备。
② 创建了一个命名的设备对象,但是使用了 FILE_AUTOGENERATED_DEVICE_NAME 这个属性让系统为设备自动产生设备名。
最终,在父设备FDO的设备扩展中有指向两个PDO的指针。
处理 PNP 请求
控制器或多功能设备的驱动有两组子例程来处理 IRP_MJ_PNP 一类的请求。一组处理当它作为 FDO 时的系统的 PNP请求, 一组处理当它作为 PDO 时的请求。下图说明了设备驱动不同身份时,对系统PNP请求应采取的动作。
表中使用了几个缩略符,含义如下:
Ø Normal: 像其他功能驱动一样去处理
Ø Succeed: 以 STATUS_SUCCESS 来完成这个IRP
Ø Ignore: 以在原来 IRP 的 IoStatus 域的值来完成这个IRP
Ø Delegate: 在父设备的设备栈中重复这个 IRP, 然后用在父设备栈中返回的IRP状态来完成原来发送到子设备PDO设备栈的这个IRP
向PNP管理器报告子设备
PNP 管理器向设备发送一个带有 BusRelations 码的 IRP_MN_QUERY_DEVICE_RELATIONS 的请求来获得设备的子设备列表。当驱动作为FDO时,对该请求的处理如下:
NTSTATUS HandleQueryRelations(PDEVICE_OBJECT  fdo, PIRP Irp)
{
PDEVICE_EXTENSION pdx = ...;
PIO_STACK_LOCATION stack = ...;
if (stack->Paramters.QueryDeviceRelations.Type != BusRelations)
{
return DefaultPnpHandler(fdo, Irp);
}
PDEVICE_RELATIONS newrel = (PDEVICE_RELATIONS)
ExAllocatePool(PagedPool, sizeof(DEVICE_RELATIONS)+sizeof(PDEVICE_OBJECT) );
newrel->count = 2;
newrel->Object[0] = pdx->ChildA;
newrel->Object[1] = pdx->ChildB;
ObReferenceObject(pdx->ChildA);
ObReferenceObject(pdx->ChildB);
Irp->IoStatus.Information = (ULONG_PTR)newrel;
Irp->IoStatus.status = STATUS_SUCCESS;
return DefaultPnpHandler(fdo, Irp);
}
① 这里设备关系的类型有好几种,我们只关心这一种;
② 这里申请一个可容纳两个设备对象指针的结构。结构DEVICE_RELATION 的尾部是一个下标为1,类型为PDEVICE_OBJECT 的数组,所以这里我们额外再加上一个就好了;  
③ 在把子设备指针返回给PNP管理器之前,对子设备增加一次引用。Pnp管理器会在适当的时机减少引用计数的。
④ 将该请求向下发送,也许下层设备还需要做点什么我们不知道的。我们返回结果的方法是设置 IoStatus的Information 域。
在这里,②的处理不完全正确,有可能上层过滤驱动已经往DEVICE_RELAIONS 中增加了一些设备对象,这时应该分配一个足够容纳以前设备对象指针和新增的两个设备对象指针的结构,然后把以前的拷贝过来,把新的两个加进去,并返回这个新分配的设备结构指针。
在开始的时候,PNP管理器对每个设备发出这个请求。可以通过调用以下函数,使PNP管理器重新发送这个请求以更新设备关系列表。
IoInvalidateDeviceRelations(pdx->Pdo, BusRelations);
一个支持热插拔的总线驱动在检测到一个新插入的设备时,调用此函数以通知PNP管理重新获得设备关系。
PDO 对PNP请求的处理
下面解释上面图中 “PDO Hat” 这一列。
The Succeed Action
对大多数的PNP请求不做过多的处理,直接返回成功;
NTSTATUS SucceedRequest(PDEVICE_OBJECT pdo, PIRP Irp)
{
Irp->IoStatus.Status = STATUS_SUCCESS;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
需要注意的是,以上的代码没有改变 IoStatus.Information域, PNP管理在发起一个IRP前可能对这个域进行了某种特定的初始化,在某种情况下,设备栈中的过滤驱动有可能改变这个域的值以使它指向某个数据结构。
The Ignore Action
驱动可以忽略某种IRP.忽略类似如使IRP以某个错误码失败,唯一不同的是,忽略IRP并不改变IRP的状态域。
NTSTATUS IgnoreRequest(PDEVICE_OBJECT pdo, PIRP Irp)
{
NTSTATUS status = Irp->IoStatus.Status;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return status;
}
很显然的, 这里以本来的 IoStatus.Status 和 IoStatus.Information 的值来完成这个IRP.这样做是有道理的。PNP 请求的发起者会把这两个域分别设置为 STATUS_NOT_SUPPORTED 和 0. PNP 管理器通过这两个域来收集信息: 如果最终返回的IRP的这两个域还是原来的值,那么PNP 管理器认为设备栈中的驱动没有对这个IRP做过什么,否则的,认为某个驱动处理了它。在DDK中关于功能驱动和过滤驱动的章节中指出,如果某个驱动想要处理某个PNP请求,那么应该把 IoStatus.Status 设置成 STATUS_SUCCESS, 然后把IRP向下传。这里,我们不处理这个IRP,但是不能修改上层某个驱动对这个驱动所作的改变,所以不要改变这两个域的值。
The Delegate Action
驱动作为FDO时,可以简单将IRP传给下面的总线驱动。但是驱动作为PDO 时,就不那么简单了。因为此时,驱动栈已经被耗尽了(PDO是作为驱动栈的最底层)。这样应该构造一个中继IRP,把它发给驱动作为FDO的那个驱动栈来处理。
NTSTATUS RepeatRequest(PDEVICE_OBJECT pdo, PIRP irp)
{
PDO_EXTENSION pdx = (PPDO_EXTENSION) pdo->DeviceExtension;
PDEVICE_OBJECT fdo = pdx->Fdo;
PDEVICE_EXTENSION pfx = (PDEVICE_EXTENSION) fdo->DeviceExtension;
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(irp);
PDEVICE_OBJECT tdo = IoGetAttatchedDeviceReference(fdo);
PIRP  subirp = IoAllocateIrp(tdo->StackSize + 1, FALSE);
PIO_STACK_LOCATION substack = IoGetNextIrpStackLocation(subirp);
substack->DeviceObject = tdo;
substack->Paramters.Others.Argument1 = (PVOID)Irp;
IoSetNextIrpStackLocation(subirp);
substack = IoGetNextStackLocation(subirp);
RtlCopyMemory(substack, stack, 
FIELD_OFFSET(IO_STACK_LOCATION, CompletionRoutine) );
substack->Control = 0;
BOOLEAN  needsvote = < I 'll explain later >;
IoSetCompletionRoutine(subirp, OnRepeaterComplete,(PVOID)needsvote,
TRUE, TRUE, TRUE);
subirp->IoStatus.Status = STATUS_NOT_SUPPORTED;
IoMarkIrpPending(irp);
IoCallDriver(tdo, subirp);
return STATUS_PENDING;
}
NTSTATUS OnRepeaterComplete(PDEVICE_OBJECT tdo, PIRP subirp, PVOID needsvote)
{
ObDereferenceObject(tdo);
PIO_STACK_LOCATION substack = IoGetCurrentIrpStackLocation(subirp);
PIRP irp = (PIRP) substack->Paramters.Others.Argument1;
if (subirp->IoStatus.Status == STATUS_NOT_SUPPORTED)
{
if (needvote)
{
    irp->IoStatus.Status = STATUS_UNSUCCESSFUL;
}
}
else
{
irp->IoStatus = subirp->IoStatus;
}
IoFreeIrp(subirp);
IoCompleteRequest(irp, IO_NO_INCREMENT);
return STATUS_MORE_PROCESSING_REQUIRED;
}
 我们要把中继IRP发送给驱动FDO所在的栈的栈顶过滤驱动,这个系统函数得到最上层的设备对象,同时增加该设备对象的计数,防止对象管理器删除它。
 申请IRP的时候,额外的多申请一个栈。我们在这个栈里存放一些参数信息,已备下面要安装的完成例程使用。我们放入的DeviceObject指针会成为完成例程的第一个参数。
 初始化真正的栈,这个栈是给FDO设备栈最顶层的设备对象用的。这里,我们不使用IoCopyCurrentIrpStackLocationToNext 的原因是这里的两个栈是属于两个不同的IRP的。
 我们必须提早预料到这个中继IRP不被处理的情况,这个参数就是为这种情况预备,后面会有描述。
 按照约定,初始化这个域为 STATUS_NOT_SUPPORT.
 减少计数, 参照的说明。
 保存原始IRP的地址
 设置原始IRP 的状态,下面会解释为什么这样做。
 自己分配的,自己释放;
 返回 STATUS_MORE_PROCESSING_REQUIRED, 这样IoCompleteRequest就不会管这个中继IRP了。
上面的代码处理了父设备驱动必须重复向FDO设备栈转发PNP IRP 带来的各种问题。PNP 管理器将IRP的初始状态设置为 STATUS_NOT_SUPPORTED.通过测试IRP最终的状态,PNP 管理器可以得知IRP有否被设备栈中的某个驱动处理过。如果最终返回的状态还是 STATUS_NOT_SUPPORTED, 那么PNP管理器认为没有驱动动过这个IRP,如果返回的是其他的状态,那么PNP管理器认为至少有某个驱动故意使这个IRP成功或者失败,而不是简单的忽略它。
创建PNP IRP的驱动必须遵守这一约定,创建的时候就把IRP的状态设为STATUS_NOT_SUPPORTED. 这样做初始化可能会引出一个问题:如果在PDO上层的某个设备驱动改变了最初的IRP的状态,然后这个IRP发送到我们的PDO,这时,我们要创建一个中继IRP,初始化这个中继IRP的状态为STATUS_NOT_SUPPORTED,然后向父设备FDO设备栈向下传这个IRP,如果这个中继IRP 以STATUS_NOT_SUPPORTED的状态被完成,那么应该以什么状态来完成原始的IRP呢?当然,不应该是 STATUS_NOT_SUPPORTED,否则的话,那需要“PDO上层的设备驱动没有动过原始IRP”这个假设成立。现在就要用到needsvote 这个标志来解决这个问题了。
对某些原始IRP而言,我们并不关心FDO设备栈是否处理这些IRP,对这些IRP,我们的父设备驱动不需要投票,就是 needsvote = 0. 如果你仔细看上面的代码,你会发现在OnRepeaterComplete中,在这种情况下,我们并不改变原始IRP中的状态。对另外一些IRP,如果FDO设备栈决定不处理中继IRP的话,我们也不能决定确切的答案,但是又需要投票的话(needsvote = 1),那么就使原始的IRP以 STATUS_UNSUCCESSFUL 的状态失败。那些IRP需要投票,那些IRP不需要投票,在上面的表中都给出了。顺便提一下,在上面的表中对于给N/A建议的IRP,首先就不应该中继他们,直接以默认值返回就OK了。
如果FDO设备栈处理了中继IRP,那么我们将中继IRP的 IoStatus 复制给原始IRP,包括域 Status 和 Information,Information 可能含有对某一请求的答案,我们就是通过这种方式将结果返回去。
对于原始PNP IRP还有一点特别的处理,我设置IRP 为 pending, 然后返回了状态STATUS_PENDING.大多数的PNP都是同步完成的,以便对IoCallDriver的调用能够立刻完成IRP.这里为什么要使得PNP管理器花费额外的代价排队一个APC来完成原始IRP呢?原因在于,RepeatRequest运行在为IRP_MJ_PNP服务的dispatch的一个子例程中,如果我们在dispatch例程中不返回 STATUS_PENDING的话,那么必须返回我们最终完成IRP的那个状态值。在我们安装的完成例程中,通过对STATUS_NOT_SUPPORTED的检测,然后结合 needsvote这个标志的值才能确定这个状态值。然而,没有有效的方式把这个值从完成例程返传给dispatch 例程。
处理设备删除
PNP管理器知道一个多功能设备的FDO和它的子PDO,在用户删除父设备的时候,PNP管理器自动删除他上面所有子设备。但是奇怪的时,在收到 IRP_MN_REMOVE_DEVICE 的时候,父设备驱动不能删除他的子PDO。设备管理器假定,这些PDO一直存在直到底层的硬件被移除。一个多功能驱动在被告知删除它的FDO的时候才能删除其子PDO.一个支持热插拔的总线驱动在枚举其上设备失败之后,才能在对IRP_MN_REMOVE_DEVICE的应答中删除那个PDO.
处理 IRP_MN_QUERY_ID
父设备驱动要处理的一个重要的IRP就是IRP_MN_QUERY_ID. PNP管理器以好几种方式来处理这个请求,以决定采用那个设备标志符来寻找INF文件和加载子设备驱动。对这个请求的处理中,应该在 IoStatus.Information 中返回一个 MUL_SZ 的之,这个值是必要的设备标志符。 假定 MULFUNC 设备的连个子设备的标志符为 *WCO1104 和 *WCO1105, 那么这样做:
NTSTATUS HandleQueryId(PDEVICE_OBJECT pdo, PIRP irp)
{
PPDO_EXTENSION pdx = (PPDO_EXTENSION) pdo->DeviceExtension;
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLoation(irp);
PWCHAR idstring;
switch(stack->Pramters.QueryId.IdType)
{
case BusQueryInstanceID:
idstring = L"0000";
break;
case BusQueryDeviceID:
if (pdx->flags & CHILDTYPEA)
{
idstring = LDRIVERNAME L"//*WCO1104";
}
else
{
idstring = LDRIVERNAME L"//*WCO1105";
}
break;
case BusQueryHardwareIDs:
if (pdx->flags & CHILDTYPEA)
{
idstring = L"*WCO1104";
}
else
{
idstring = L"*WCO1105";
}
break;
default:
return CompleteRequest(irp, STATUS_NOT_SUPPORTED, 0);
}
ULONG nchars = wcslen(idstring);
ULONG size = (nchars + 2) *sizeof(WCHAR);
PWCHAR id = (PWCHAR)ExAllocatePool(PagedPool, size);
wcscpy(id, idstring);
id[nchars + 1] = 0;
return CompleteRequest(irp, STATUS_SUCCESS, (ULONG_PTR)id);
}
 返回设备实例ID.
 返回设备ID.
 返回硬件ID.
返回的这些字符串用来标志某一设备,PNP 管理器会根据PDO返回的硬件ID(hardware ID)和兼容ID(compatible ID)加载相应的驱动。设备ID(device ID)唯一的标识了一种设备,硬件ID是一组标识符,通常设备ID也是硬件ID的一个成员。PNP管理器在缺少硬件ID的情况下,会使用兼容ID来寻找INF文件,安装驱动。兼容ID和硬件ID的格式相同,只是比硬件ID更通用,一个设备可以没有或有多个兼容ID.设备ID的通常包含某些特定的标识,这些标识是存在设备的固件程序中,并且由总线驱动负责得到。同一种设备A和B,通过实例ID(instance ID)可以区分。设备实例ID(device instance ID)是系统提供的标识设备实例的标识符,通常,这个标识符由设备ID和实例ID组成。
ID的格式:
device ID:
Ø <enumerator>/<enumerator-specific-device-ID>
hardware ID:
Ø <enumerator>/<enumerator-specific-device-ID>
这是典型的硬件ID格式
Ø *<enumerator-specific-ID>
*号表示,这个设备可以被不止一个枚举器支持,例如:ISAPNP 和 BIOS.
Ø <device-class-specific-ID>
对于一个已经存在并且有建立了自己命名约定的设备类可以使用这种格式一个设备可以有一个或者多个硬件ID. <enumerator>的值可以是注册表分支HKLM/SYSTEM/CurrentControlSet/Enum 下面的一种,PNP管理器把为每个设备对象的设备ID建立一个分支: HKLM/SYSTEM/CurrentControlSet/Enum/enumerator/deviceID,在这个分支下的子键是实例ID,这个子键里面存储有该设备的硬件信息,通常有硬件ID,兼容ID,设备描述等。enumerator 可以看成是系统提供的一些总线驱动。
instance ID:
Ø 一个字符串
device instance ID:
Ø <enumerator>/<enumerator-specific-device-ID>/<instance-specific-ID>
一个例子,机器上的移动硬盘,枚举器是 USBSTOR.
device ID: 
USBSTOR/Disk&Ven_SAMSUNG&Prod_MP0402H&Rev_
competible ID: 
USBSTOR/Disk
USBSTOR/RAW
hardware ID:
USBSTOR/DiskSAMSUNG_MP0402H__________
USBSTOR/DiskSAMSUNG_MP0402_______
USBSTOR/DiskSAMSUNG
USBSTOR/SAMSUNG_MP0402H_____
SAMSUNG_MP0402H____
USBSTOR/GenDisk
GenDisk
device instance ID:
USBSTOR/DISK&VEN_SAMSUNG&PROD_MP0402H&REV_/6&493A32C&0&____
处理 IRP_MN_QUERY_DEVICE_RELATIONS
最后一个要考虑的PNP请求是 IRP_MN_QUERY_DEVICE_RELATIONS。之前有提到过,FDO提供了子设备对象的列表来处理这个请求。驱动作为PDO的身份时,只要处理一个类型为目标设备的请求,相应代码如下:
NTSTATUS HandleQueryRelations(PDEVICE_OBJECT pdo, PIRP irp)
{
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(irp);
NTSTATUS   status = irp->IoStatus.Status;
if (stack->Parameters.QueryDeviceRelations.Type == 
TargetDeviceRelations)
{
PDEVICE_RELATIONS newrel = (PDEVICE_RELATIONS)
ExAllocatePool(PagedPool, sizeof(DEVICE_RELATIONS) );
newrel->Count = 1;
newrel->Object[0] = pdo;
ObReferenceObject(pdo);
status = STATUS_SUCCESS;
irp->IoStatus.Information = (ULONG_PTR)newrel;
}
irp->IoStatus.Status = status;
IoCompleteRequest(irp, IO_NO_INCREMENT);
return status;
}
处理 IRP_MN_QUERY_INTERFACE
IRP_MN_QUERY_INTERFACE 可以让在同一个设备栈的驱动得到下层驱动的直接调用接口。所谓直接调用接口,就是说驱动可以直接调用,而不必先构造IRP.关于直接调用接口的机制的基本概念如下:
Ø 每个直接调用接口由一个GUID来标识。接口本身是一个结构体,结构体中含有一组指针,这些指针指向接口实现方法
Ø 想得到直接调用接口的驱动需要先构造一个IRP, IRP的次功能码为 IRP_MN_QUERY_INTERFACE, 这个IRP参数应该有标识接口的GUID和一个接口结构体。获得之后,可以直接使用接口结构体中的指针来调用。当驱动不再需要直接调用接口的话,调用InterfaceDereference函数取消对接口的引用
Ø 导出接口的驱动在QUERY_INTERFACE的请求中,查找IRP中指定的GUID,如果匹配的话,将接口实现方法的地址填到接口结构体中去
标识一个接口
需要创建一个全局的GUID和一个结构体来标识一个接口。按照惯例,GUID的符号链接的名字会是 GUID_XXX_STANDARD 这种形式。MULFUNC 用下面的GUID 来导出接口。
DEFINE_GUID(GUID_RESOURCE_SUBALLOCATE_STANDARD, 0xaa04540,
0x6fd1, 0x11d3, 0x81, 0xb5, 0x0, 0xc0, 0x4f, 0xa3, 0x30, 0xa6);
RESOURCE_SUBALLCATE 接口的目的是和子设备分享 I/O 属于父设备的资源。与RESOURCE_SUBALLOCATE 接口相关的结构体如下:
typedef struct _INTERFACE {
USHORT Size;
USHORT Version;
PVOID Context;
PINTERFACE_REFERENCE  InterfaceReference;
PINTERFACE_DEREFERENCE  InterfaceDereference;
// 接口相关方法的接口
} INTERFACE, *PINTERFACE;
struct _RESOURCE_SUBALLOCATE_STANDARD : public INTERFACE {
PGETRESOURCES GetResources;
};
typdef struct _RESOURCE_SUBALLOCATE_STANDARD
RESOURCE_SUBALLOCATE_STANDARD, *PRESOURCE_SUBALLOCATE_STANDARD;
查找和使用接口
想使用下层驱动导出的直接接口的驱动需要发送一个 QUERY_INTERFACE 的请求。下面展示了在 Paramters.QueryInterface 中的参数:
参数
含义
InterfaceType
指向标识接口的GUID
Size
参数 Interface 指向的接口结构体的大小
Version
接口结构体的版本号
Interface
指向一个接口结构体的指针
InterfaceSpecificData
导出接口的驱动导出的附加数据,跟具体接口相关
下面是示例代码:
RESOURCE_SUBALLOCATE_STANDARD suballoc; // 存放最终的结果
KEVENT  event;
KeInitializeEvent(&event, NotificationEvent, FALSE);
IO_STATUS_BLOCK  iosb;
PIRP  irp = IoBuildSynchronousFsdRequest(IRP_MJ_PNP,
pdx->LowerDeviceObject, NULL, 0, NULL, &event, &iosb);
PIRP  stack = IoGetNextIrpStackLocation(irp);
stack->MinorFunction = IRP_MN_QUERY_INTERFACE;
stack->Paramters.QueryInterface.InterfaceType =
&GUID_RESOURCE_SUBALLOCATE_STANDARD;
stack->Paramters.QueryInterface.Size = sizeof(suballoc);
stack->Paramters.QueryInterface.Version = RESOURCE_SUBALLOCATE_STANDARD_VERSION;
stack->Paramters.QueryInterface.Interface = &suballoc;
stack->Paramters.QueryInterface.InterfaceSpecificData = NULL;
NTSTATUS status = IoCallDriver(pdx->LowerDeviceObject, irp);
if (status == STATUS_PENDING)
{
KeWaitForSingalObject(&event, Executive, KernelMode, FALSE, NULL);
status = irp->IoStatus.Status;
}
在上面的示例代码,我们构造一个了同步IRP,然后底层驱动发送。我们期望下层设备填充suballoc 结构,然后在IRP的状态中返回成功。
如果我们IRP可以成功的返回,那么我们可以直接调用接口中的成员函数了。通常,每个接口函数需要一个上下文参数,这个参数来自返回的接口结构体。例如:
PCM_RESOURCE_LIST raw, translated;
status = suballoc.GetResources(suballoc.Context, pdx->Pdo, &raw, &translated);
当不再需要这个接口时,调用:
suballoc.InterfaceDereference(suballoc.Context);
导出直接调用接口
驱动要导出直接调用接口,需要处理 IRP_MN_QUERY_INTERFACE 请求。第一步要做的就是测试接口GUID,看调用者是否在查找自己支持的接口。如下:
if (*stack->Paramters.QueryInterface.InterfaceType != 
GUID_RESOURCE_SUBALLOCATE_STANDARD)
< default handling >
// 注意,C++ 支持这样比较,如果是C的话,请使用 IsEqualGuid 来比较
DDK中说,一个总线驱动在作为PDO时,收到这种请求,如果请求的是未知的接口的话,那么使IRP失败返回:“驱动应该避免将此请求向下一个设备栈发送以获得请求的接口。这种行为会增加不同设备栈之间的依赖性,从而使设备栈难以管理。例如:第二个设备栈的设备无法移除,如果某个驱动正在使用其导出的接口。”我不赞同这样,我建议对于控制器和多功能设备驱动可以这么做。如果不这样做,上层的子设备就无法使用下层总线导出的功能了。反正在子设备被移走之前,父设备栈不能删除,只要子设备驱动能在处理关闭例程中去掉对直接调用接口的引用,这样做也是可以的。
如果接口发展到多于一个版本,那么下一步就是决定要提供那一个版本的接口了。按照约定,初始的版本为1,以后遇到重要的改变就加1.可以在定义接口GUID的头文件里写一个显示定义的常量来代表当前使用的版本号,然后在构造IRP时,就可是使用这个常量了,这个常量是在编译后,就固定了。下面是示例代码:
USHORT version = RESOURCE_SUBALLOCATE_STANDARD_VERSION;
if (version > stack->Paramters.QueryInterface.Version)
{
version = stack->Parameters.QueryInterface.Version;
}
if (version == 0)
{
return CompleteRequest(irp, irp->IoStatus.Status);
}
这里,如果你的版本号从1开始,而调用者需要版本号为0的接口,那么以IRP 原来的状态值返回该IRP, 这个初始值很可能是 STATUS_NOT_SUPPORTED.
第三步就是初始化接口结构体了,如下:
if (stack->Parameters.QueryInterface.Size < 
sizeof(RESOURCE_SUBALLOCATE_STANDARD) )
{
return CompleteRequest(irp, STATUS_INVALID_PARAMTER);
}
PRESOURCE_SUBALLOCATE_STANDRD ifp = (PRESOURCE_SUBALLOCATE_STANDARD)
stack->Parameters.QueryInterface.Interface;
ifp->Size = sizeof(RESOURCE_SUBALLOCATE_STANDARD);
ifp->Version = 1;
ifp->Context = (PVOID)fdx;
ifp->InterfaceReference = (PINTERFACE_REFERENCE)
SuballocInterfaceReference; 
ifp->InterfaceDereference = (PINTERFACE_DEREFERENCE)
SuballocInterfaceDereference;
ifp->GetResources = (PGETRESOURCE)GetChildResources;
最后,需要调用一下 SuballocInterfaceReference 使驱动可以在调用者调用 InterfaceDereference 之前不被卸载。
处理 IRP_MN_QUERY_PNP_DEVICE_STATE
有时候,你可能不想设备管理器显示一个或者所有的子设备。要想禁止设备管理显示,在回应 IRP_MN_QUERY_PNP_DEVICE_STATUS 中,在设备标志位置位 PNP_DEVICE_DONT_DISPLAY_IN_UI.
原创粉丝点击