支持可弹出设备
可弹出设备 是可以插入扩展坞并从扩展坞弹出的设备。 通常,必须先禁用可弹出设备的总线电源,然后才能移除设备。
如果设备可弹出,则设备总线的总线驱动程序必须在设备的WDF_DEVICE_PNP_CAPABILITIES结构中设置弹出成员。
当总线驱动程序确定其枚举子设备之一即将弹出时,它会调用 WdfPdoRequestEject 或 WdfChildListRequestChildEject。 例如,总线驱动程序可能会检测到用户按下了弹出按钮。
当驱动程序调用 WdfChildListRequestChildEject 或 WdfPdoRequestEject 时,PnP 管理器使用 有序删除 方案通知设备的驱动程序正在删除该设备。 框架在设备总线的总线驱动程序中调用 EvtDeviceReleaseHardware 回调函数后,框架将调用总线驱动程序的 EvtDeviceEject 回调函数,该回调函数执行物理弹出设备所需的任何操作。
如果弹出设备导致其他设备也被弹出,则总线驱动程序可以维护 弹出关系的列表。 当用户删除你的设备时,PnP 管理器会通知列表中的设备的驱动程序,他们的设备也会被删除。 若要维护弹出关系的列表,总线驱动程序可以使用 WdfPdoAddEjectionRelationsPhysicalDevice、 WdfPdoRemoveEjectionRelationsPhysicalDevice 和 WdfPdoClearEjectionRelationsDevices 方法。
如果设备可以在扩展坞中锁定,则总线驱动程序必须在设备的WDF_DEVICE_PNP_CAPABILITIES结构中设置 LockSupported 成员。 总线驱动程序还必须提供 EvtDeviceSetLock 回调函数,该功能可锁定设备以禁用弹出或解锁设备以启用弹出。
框架中的状态机
为了跟踪每个设备的状态,框架使用 PnP 状态机、电源状态机和电源策略状态机。 框架为插入系统的每个设备创建每个状态机的实例。
注意:此功能仅供 Microsoft 内部使用。
对于需要知道此信息的驱动程序,框架提供了两组接口:
- 一组驱动程序提供的事件回调函数。每当其中一个状态机进入或退出特定状态时,驱动程序都可以请求框架调用以下回调函数之一:
- EvtDevicePnpStateChange,驱动程序通过调用 WdfDeviceInitRegisterPnpStateChangeCallback 进行注册;
- EvtDevicePowerStateChange,驱动程序通过调用 WdfDeviceInitRegisterPowerStateChangeCallback 进行注册;
- EvtDevicePowerPolicyStateChange,驱动程序通过调用 WdfDeviceInitRegisterPowerPolicyStateChangeCallback 进行注册;
- 返回状态机的当前状态的一组方法。驱动程序可以调用以下方法之一来确定特定设备之一状态机的当前状态:
- WdfDeviceGetDevicePnpState
- WdfDeviceGetDevicePowerState
- WdfDeviceGetDevicePowerPolicyState
使用驱动程序定义的接口
驱动程序可以定义其他驱动程序可以访问的设备特定接口。 这些 驱动程序定义的接口 可以包含一组可调用的例程和/或一组数据结构。 驱动程序通常在驱动程序定义的接口结构中提供指向这些例程和结构的指针,驱动程序会将这些例程和结构提供给其他驱动程序。
例如,如果子设备的资源列表中没有该信息,总线驱动程序可能会提供一个或多个例程,较高级别的驱动程序可以调用该例程来获取有关子设备的信息。
创建接口
每个驱动程序定义的接口都由以下各项指定:
- GUID
- 版本号
- 驱动程序定义的接口结构
- 引用和取消引用例程
若要创建接口并使其可供其他驱动程序使用,基于框架的驱动程序可以使用以下步骤:
1. 定义接口结构:此驱动程序定义的结构的第一个成员必须是 INTERFACE 标头结构。 其他成员可能包括接口数据和指向其他驱动程序可以调用的其他结构或例程的指针。
驱动程序必须提供 WDF_QUERY_INTERFACE_CONFIG 结构,该结构描述已定义的接口。
使用 WDF_QUERY_INTERFACE_CONFIG 时,WDF 不支持使用同一接口 GUID 的单个接口的多个版本。
因此,在引入现有接口的新版本时,建议创建新的 GUID,而不是修改 INTERFACE 结构的 Size 或 Version 字段。
如果驱动程序确实使用修改后的 Size 或 Version 字段重用同一接口 GUID,则驱动程序不应提供WDF_QUERY_INTERFACE_CONFIG,而应为IRP_MN_QUERY_INTERFACE提供 EvtDeviceWdmIrpPreprocess 回调例程。
2. 调用 WdfDeviceAddQueryInterface。
WdfDeviceAddQueryInterface 方法执行以下操作:
存储有关接口的信息,例如其 GUID、版本号和结构大小,以便框架可以识别另一个驱动程序对接口的请求。
注册可选的 EvtDeviceProcessQueryInterfaceRequest 事件回调函数,框架在另一个驱动程序请求接口时调用该函数。
驱动程序定义接口的每个实例都与单个设备相关联,因此驱动程序通常从 EvtDriverDeviceAdd 或 EvtChildListCreateDevice 回调函数中调用 WdfDeviceAddQueryInterface。
访问接口
如果驱动程序已定义接口,则另一个基于框架的驱动程序可以通过调用 WdfFdoQueryForInterface 并传递 GUID、版本号、指向结构的指针和结构大小来请求访问该接口。 框架创建 I/O 请求并将其发送到驱动程序堆栈的顶部。
驱动程序通常从 EvtDriverDeviceAdd 回调函数中调用 WdfFdoQueryForInterface。 或者,如果驱动程序必须在设备不处于工作状态时释放接口,则驱动程序可以从 EvtDevicePrepareHardware 回调函数中调用 WdfFdoQueryForInterface,并从 EvtDeviceReleaseHardware 回调函数中调用接口的取消引用例程。
如果驱动程序 A 要求驱动程序 B 提供驱动程序 B 已定义的接口,框架将处理驱动程序 B 的请求。框架验证 GUID 和版本是否表示受支持的接口,以及驱动程序 A 提供的结构大小是否足以容纳接口。
当驱动程序调用 WdfFdoQueryForInterface 时,框架创建的 I/O 请求将一直传播到驱动程序堆栈的底部。 如果简单的驱动程序堆栈由三个驱动程序(A、B 和 C)组成,并且驱动程序 A 要求接口,则驱动程序 B 和驱动程序 C 都可以支持该接口。 例如,驱动程序 B 可能会在将请求向下传递到驱动程序 C 之前填充驱动程序 A 的接口结构。驱动程序 C 可以提供 EvtDeviceProcessQueryInterfaceRequest 回调函数,用于检查接口结构的内容并可能对其进行修改。
如果驱动程序 A 需要访问驱动程序 B 的接口,而驱动程序 B 是远程 I/O 目标 (即位于其他驱动程序堆栈) 中的驱动程序,则驱动程序 A 必须调用 WdfIoTargetQueryForInterface 而不是 WdfFdoQueryForInterface。
使用One-Way或Two-Way通信
可以定义提供单向通信的接口,也可以定义提供双向通信的接口。 若要指定双向通信,驱动程序将其WDF_QUERY_INTERFACE_CONFIG结构的 ImportInterface 成员设置为 TRUE。
如果接口提供单向通信,并且驱动程序 A 要求驱动程序 B 的接口,则接口数据仅从驱动程序 B 流向驱动程序 A。当框架收到驱动程序 A 对支持单向通信的接口的请求时,框架会将驱动程序定义的接口值复制到驱动程序 A 的接口结构中。 然后,它会调用驱动程序 B 的 EvtDeviceProcessQueryInterfaceRequest 回调函数(如果存在),以便可以检查并可能修改接口值。
如果接口提供双向通信,则接口结构包含驱动程序 A 在向驱动程序 B 发送请求之前填充的一些成员。驱动程序 B 可以读取驱动程序 A 提供的参数值,并根据这些值选择要提供给驱动程序 A 的信息。当框架收到驱动程序 A 对支持双向通信的接口的请求时,框架会调用驱动程序 B 的 EvtDeviceProcessQueryInterfaceRequest 回调函数,以便它可以检查收到的值并提供输出值。 对于双向通信,回调函数是必需的,因为框架不会将任何接口值复制到驱动程序 A 的接口结构。
维护引用计数
每个接口必须包含一个引用函数和一个取消引用函数,这些函数递增和递减接口的引用计数。 定义接口的驱动程序在其 INTERFACE 结构中指定这些函数的地址。
当驱动程序 A 向驱动程序 B 请求接口时,框架在使接口可供驱动程序 A 使用之前调用接口的引用函数。当驱动程序 A 使用完 接口后,它必须调用接口的取消引用函数。
大多数接口的引用函数和取消引用函数可以是不执行任何操作的无操作函数。 框架提供大多数驱动程序可以使用的无操作引用计数函数 WdfDeviceInterfaceReferenceNoOp 和 WdfDeviceInterfaceDereferenceNoOp。
驱动程序必须跟踪接口的引用计数并提供实际引用和取消引用函数的唯一时间是驱动程序 A 从远程 I/O 目标请求接口 (即位于不同驱动程序堆栈) 的驱动程序。 在这种情况下,驱动程序 B (在不同的堆栈) 必须实现引用计数,以便它可以阻止在驱动程序 A 使用驱动程序 B 的接口时删除其设备。
如果要设计定义接口的驱动程序 B,则必须确定是否将从其他驱动程序堆栈访问驱动程序的接口。 驱动程序 B 无法确定对其接口的请求是来自本地驱动程序堆栈还是来自远程堆栈。如果驱动程序将支持来自远程堆栈的接口请求,则驱动程序必须实现引用计数。
如果要设计访问远程 I/O 目标上的接口的驱动程序 A,则驱动程序必须提供 EvtIoTargetQueryRemove 回调函数,该回调函数在驱动程序 B 的设备即将删除时释放接口,提供 EvtIoTargetRemoveComplete 回调函数在驱动程序 B 的设备意外删除时释放接口和 EvtIoTargetRemoveCanceled 如果取消尝试删除设备,则重新获取接口的回调函数。