默认情况下,WDF框架处理系统发送到基于框架的驱动程序的所有 PnP 和电源管理请求。 此外,默认情况下,仅当驱动程序的硬件可用且处于工作 (D0) 状态时,框架才会向函数驱动程序传递 I/O 请求。
编写基于WDF框架的驱动程序时,可以使用WDF框架的大部分默认行为轻松支持设备的 PnP 和电源管理功能。 但是,如果驱动程序堆栈中的所有驱动程序仅使用WDF框架的默认 PnP 和电源管理行为,则设备可能无法正常工作。 例如,当设备进入其工作 (D0) 状态时,设备的功能驱动程序可能必须启用设备。
因此,框架设备对象提供一组事件回调函数和一组对象方法,使基于框架的驱动程序能够参与 PnP 和电源管理操作。 这些回调函数和对象方法允许堆栈中的每个驱动程序仅提供所需的 PnP 和电源管理支持。
通常,驱动程序堆栈中的每个各种驱动程序都负责支持一些 PnP 和电源管理操作。 驱动程序必须支持的操作取决于你正在编写的驱动程序类型和设备提供的功能。
注意: 仅限软件的驱动程序 是无法访问任何硬件的驱动程序。 某些仅限软件的驱动程序驻留在无法访问硬件的驱动程序堆栈中。 由于这些驱动程序无法访问硬件,因此它们通常不必执行任何 PnP 或电源管理操作。
其他仅限软件的驱动程序是Filter驱动程序:它们驻留在访问硬件的驱动程序堆栈中,但Filter驱动程序不访问硬件。 当Filter驱动程序收到指定 PnP 或电源管理操作的 I/O 请求时,驱动程序通常只是将请求传递给下一个驱动程序。 框架截获并传递这些请求,因此基于框架的驱动程序永远不会看到这些请求。
如果要编写仅限软件的驱动程序,驱动程序 会创建设备对象 ,但通常不需要提供任何事件回调函数来处理 PnP 或电源管理事件。 如果驱动程序使用框架队列对象,则需要将队列WDF_IO_QUEUE_CONFIG结构的 PowerManaged 成员设置为 WdfFalse 或 WdfUseDefault。
一些仅限软件的驱动程序也是 功能驱动程序。 换句话说,单个驱动程序可以充当仅限软件的驱动程序来支持无法访问硬件的虚拟设备,并充当功能驱动程序来支持硬件设备。
功能驱动程序中进行 PnP 和电源管理
功能驱动程序 控制设备的操作,因此它们访问设备硬件。 这些驱动程序必须支持 PnP 和电源管理操作,并且通常在 创建设备对象时注册多个事件回调函数。
通常,功能驱动程序的 EvtDriverDeviceAdd 事件回调函数调用 WdfDeviceInitSetPnpPowerEventCallbacks 来注册以下回调函数:
- EvtDevicePrepareHardware,它将设备的系统分配资源传送到驱动程序。 驱动程序可以执行一些操作,例如将设备的总线相对内存映射到处理器的虚拟地址空间,以便驱动程序可以访问硬件;
- EvtDeviceD0Entry,执行驱动程序设备每次进入其工作状态时所需的操作,例如加载固件 (D0) 状态;
- EvtDeviceD0Exit:每次驱动程序的设备离开其工作 (D0) 状态并进入低功耗状态时,执行所需的操作;
- EvtDeviceReleaseHardware,用于释放 EvtDevicePrepareHardware 分配的任何系统资源;
与所有框架定义的回调函数一样,上述列表中的回调函数也是可选的。 仅当驱动程序需要时,才需要提供它们。
功能驱动程序可以调用 WdfDeviceSetPnpCapabilities 和 WdfDeviceSetPowerCapabilities ,以向操作系统报告设备的 PnP 和电源管理功能。
通常,将框架的 电源托管 I/O 队列 用于大多数 I/O 请求。 如果 I/O 队列由电源管理,则框架仅当其设备处于工作状态 (D0) 状态时,才会向驱动程序传递请求。 有关电源管理的 I/O 队列的详细信息,请参阅 I/O 队列的电源管理。
通常,设备的功能驱动程序是驱动程序堆栈的 电源策略所有者 。 电源策略所有者确定 设备的相应设备电源状态 ,并在设备的电源状态发生更改时向设备的驱动程序堆栈发送请求。 对于基于框架的驱动程序,框架负责处理此责任,因此无需在驱动程序中提供代码即可请求更改设备的电源状态。
电源策略所有者有两个额外的责任:它控制设备在空闲时进入低功耗状态的能力,以及系统保持 工作状态 (S0) 状态;控制设备在检测到来自低功耗状态的外部事件时生成唤醒信号的能力。 如果设备具有空闲或唤醒功能,则功能驱动程序可以提供其他回调函数。
I/O 队列的电源管理
当框架收到定向到驱动程序设备之一的 I/O 请求时,框架会将该请求置于 I/O 队列中。 驱动程序可以通过提供请求处理程序或轮询队列从 I/O 队列获取 I/O 请求。
在设计驱动程序时,应将驱动程序收到的 I/O 请求分为两类:
1. 要求设备处于其工作 (D0) 状态的请求,包括:
- 需要设备函数驱动程序从设备读取数据或向设备写入数据的读取或写入请求;
- 设备控制请求函数或总线驱动程序在没有访问设备的情况下无法提供服务;
2. 不需要设备处于其工作状态的请求 (D0) 状态,包括:
- 设备控制请求函数或总线驱动程序可以在不访问设备的情况下提供服务;
- 可能是筛选器驱动程序接收的所有请求;
- 驱动程序堆栈中所有驱动程序接收的所有请求,如果堆栈支持不与任何硬件通信的仅限软件的设备;
除非你正在为不与硬件通信的堆栈编写筛选器驱动程序,否则驱动程序可能会收到一些要求设备处于其工作状态的请求,以及一些不工作的请求。
为了支持这两种类型的请求,框架提供了两种类型的 I/O 队列:电源管理的队列和未 托管 的 I/O 队列。 当驱动程序创建其每个 I/O 队列时,它会将队列WDF_IO_QUEUE_CONFIG结构中的 PowerManaged 成员设置为 WdfTrue 或 WdfFalse 以指示以下其中一项:
1. 如果驱动程序将 PowerManaged 设置为 WdfTrue,则队列由电源管理。
当 I/O 请求在电源托管队列中可用时,框架仅当设备处于其工作 (D0) 状态时,才会将请求传送到驱动程序。 因此,每当驱动程序收到来自电源托管队列的请求时,框架都保证设备可用。 如果设备未处于工作状态,框架会将请求存储在队列中,直到设备可用。
如果设备由于处于空闲状态而处于低功耗状态,并且框架将 I/O 请求置于驱动程序的电源托管队列之一中,框架会要求驱动程序堆栈在将请求传递到驱动程序之前将设备还原到其工作状态。
如果设备由于系统不处于工作 (S0) 状态而处于低功耗状态,并且如果框架将 I/O 请求放入驱动程序的电源管理队列之一,框架将等待,直到设备返回到其工作 (D0) 状态,然后将请求传递给驱动程序。
由于如果设备未处于工作状态,框架不会将 I/O 请求从电源管理的队列传递到驱动程序,因此位于驱动程序堆栈中电源策略所有者上方的驱动程序不得使用电源管理的 I/O 队列。 如果位于电源策略所有者上方的驱动程序使用电源管理的队列,并且设备处于低功耗状态,则驱动程序不会接收请求,并且无法将其传递给电源策略所有者。 因此,控制设备的电源状态的电源策略所有者不会唤醒设备。
2. 如果驱动程序将 PowerManaged 设置为 WdfFalse,则队列不是电源管理的。
当 I/O 请求在非电源管理的队列中可用时,框架会将请求传递给驱动程序,而不考虑设备是否处于其工作 (D0) 状态。 如果已将队列设置为仅接收不需要访问设备的请求,则即使设备不可用,驱动程序也可以处理每个请求。
一些驱动程序需要对即插即用 (PnP) 和电源管理操作进行一些直接控制。 这些驱动程序可以使用 自我管理的 I/O。
使用自我管理的 I/O
大多数基于框架的驱动程序利用框架的 PnP 和电源管理功能来获取它们支持的设备。 换句话说,大多数基于框架的驱动程序都允许框架通过执行以下所有操作来管理设备的 PnP 和电源状态:
- 提供 EvtDeviceD0Entry 和 EvtDeviceD0Exit 回调函数;
- 提供 EvtDevicePrepareHardware 和 EvtDeviceReleaseHardware 回调函数;
- 对要求设备处于其工作状态的 I/O 请求使用电源托管队列,并为所有其他请求使用非电源管理的队列;
但是,一些基于框架的驱动程序需要更深入地了解其设备的状态,包括以下情况下的驱动程序:
- 驱动程序执行的操作不由驱动程序从框架 I/O 队列接收的一组 I/O 请求决定;
- 驱动程序与较旧的非框架驱动程序通信,并直接处理 WDM 接口;
- 驱动程序接收的 I/O 请求不能分为两组:要求设备处于其工作状态的 I/O 请求和不工作状态的 I/O 请求;
大多数驱动程序不是上述情况之一,但如果驱动程序是,它可能需要更直接地控制设备的 PnP 和电源管理操作。 此类驱动程序可以使用 自我管理的 I/O。 使用自我管理的 I/O 意味着,每当设备插入或拔出电源时,以及设备暂时停止时,需要通过一组回调函数来通知驱动程序 。
请注意,驱动程序可以使用自托管 I/O,并且仍然可以将框架的 I/O 队列用作电源管理的队列。 例如,驱动程序可以将框架的 I/O 队列(而不是电源托管)与一组自我管理的 I/O 回调函数配合使用。
若要使用自托管 I/O,驱动程序会在调用 WdfDeviceInitSetPnpPowerEventCallbacks 时注册一组额外的事件回调函数。 这些事件回调函数包括:
- EvtDeviceSelfManagedIoInit,用于初始化和启动设备的 I/O 操作;
- EvtDeviceSelfManagedIoSuspend,可挂起 I/O 操作;
- EvtDeviceSelfManagedIoRestart,它将在设备的 I/O 操作暂停后重启;
- EvtDeviceSelfManagedIoFlush,可删除未服务的 I/O 请求;
- EvtDeviceSelfManagedIoCleanup,用于解除分配 由 EvtDeviceSelfManagedIoInit 分配的资源;
当设备首次进入其工作 (D0) 状态时,框架会调用驱动程序的 EvtDeviceSelfManagedIoInit 回调函数。 每次用户将设备插入系统以及每次重启系统时,都会发生这种情况。
驱动程序必须停止设备的 I/O 操作有三种情况:设备即将进入低功耗状态、即将删除或已意外删除。 以下详细检查了其中每种情况:
- 设备即将进入低功耗状态,最终将恢复其工作状态:当设备即将进入低功耗状态 (,因为设备处于空闲状态、整个系统进入低功耗状态,或者 PnP 管理器) 重新分发系统硬件资源 时,框架会调用驱动程序的 EvtDeviceSelfManagedIoSuspend 回调函数。 设备重新进入其工作状态后,框架会调用驱动程序的 EvtDeviceSelfManagedIoRestart 回调函数;
- 设备即将删除:为了处理 用户请求的设备删除,框架在停止设备之前调用驱动程序的 EvtDeviceSelfManagedIoSuspend 回调函数。 停止设备后,框架调用驱动程序的 EvtDeviceSelfManagedIoFlush 回调函数。 删除设备后,框架将调用 EvtDeviceSelfManagedIoCleanup 回调函数;
- 设备已被意外删除:如果设备总线的驱动程序确定设备不再存在,或者堆栈中的另一个驱动程序确定设备未响应,则发现问题的驱动程序会通知 PnP 管理器。 然后,PnP 管理器通知其余驱动程序设备已消失。 对于基于框架的驱动程序,框架接收 PnP 管理器的消息,并调用驱动程序的 EvtDeviceSelfManagedIoSuspend、 EvtDeviceSelfManagedIoFlush 和 EvtDeviceSelfManagedIoCleanup 回调函数;
驱动程序还可以注册 EvtDeviceSurpriseRemoval 回调函数。 如果设备在删除时处于工作状态 (D0) 状态,框架会在调用自托管 I/O 回调函数之前调用 EvtDeviceSurpriseRemoval 。 如果设备在移除时处于低功耗状态,则会在 EvtDeviceSelfManagedIoSuspend 之后调用 EvtDeviceSurpriseRemoval。
尽管很少必要,但该框架允许驱动程序通过访问 框架中的状态机来更好地控制设备的 PnP 和电源状态。