USB KMDF 功能驱动程序中的选择性挂起
如果 USB 驱动程序需要用户模式下不可用的功能或资源,则应提供 KMDF 函数驱动程序。 KMDF 驱动程序通过在 KMDF 初始化结构中设置相关值,然后提供适当的回调函数来实现选择性挂起。 KMDF 处理与较低驱动程序通信以挂起和恢复设备的详细信息。
KMDF 驱动程序中的选择性挂起指南
支持选择性挂起的 KMDF 驱动程序必须遵循以下准则:
- KMDF 函数驱动程序必须是其设备堆栈的 PPO。 默认情况下,KMDF 函数驱动程序是 PPO。
- 支持选择性挂起的 KMDF 函数驱动程序可以使用电源管理的队列或不受电源管理的队列。 默认情况下,PDO 的队列对象受电源管理。
电源策略所有权和 KMDF USB 驱动程序
默认情况下,USB 设备的 KMDF 函数驱动程序是设备堆栈的 PPO。 KMDF 代表此驱动程序管理选择性挂起和恢复。
KMDF 驱动程序中的 I/O 队列配置
支持选择性挂起的 KMDF 函数驱动程序可以使用电源管理的队列或不受电源管理的队列。 通常,驱动程序将非电源管理的队列配置为接收传入的设备 I/O 控制请求,并将一个或多个电源管理的队列配置为接收读取、写入和其他电源依赖请求。 当请求到达电源管理的队列时,KMDF 确保设备处于 D0 状态,然后再向驱动程序提出请求。
如果要编写在设备堆栈中 PPO 之上分层的 KMDF 筛选器驱动程序,则不得使用电源管理的队列。 原因与 UMDF 驱动程序相同。 当设备暂停时,框架不会显示来自电源管理的队列的请求,因此使用此类队列可能会停止设备堆栈。
KMDF 函数驱动程序的选择性挂起机制
KMDF 处理支持 USB 选择性挂起所需的大部分工作。 它跟踪 I/O 活动,管理空闲计时器,并发送导致父驱动程序 (Usbhub.sys 或 Usbccgp.sys) 挂起和恢复设备的设备 I/O 控制请求。
如果 KMDF 函数驱动程序支持选择性挂起,则 KMDF 跟踪每个设备对象拥有的所有电源托管队列上的 I/O 活动。 每当 I/O 计数达到零时,框架就会启动空闲计时器。 默认超时值为 5 秒。
如果在空闲超时期限到期之前,I/O 请求到达属于设备对象的电源托管队列,框架将取消空闲计时器,并且不会暂停设备。
空闲计时器过期时,KMDF 会发出将 USB 设备置于挂起状态所需的请求。 如果函数驱动程序在 USB 端点 上使用连续读取器,则读取器的重复轮询不会算作 KMDF 空闲计时器的活动。 但是,在 EvtDeviceD0Exit 回调函数中,USB 驱动程序必须手动停止连续读取器和由不受电源管理的队列提供的任何其他 I/O 目标,以确保驱动程序在设备未处于工作状态时不会发送 I/O 请求。 若要停止目标,驱动程序调用 WdfIoTargetStop 并将 WdfIoTargetWaitForSentIoToComplete 指定为目标操作。 作为响应,框架仅在目标 I/O 队列中的所有 I/O 请求都已完成且任何关联的 I/O 完成回调运行后,才会停止 I/O 目标。
默认情况下,KMDF 将设备从 D0 转换为驱动程序在空闲设置中指定的设备电源状态。 作为转换的一部分,KMDF 调用驱动程序的电源回调函数的方式与任何其他关机序列的调用方式相同。
设备挂起后,当发生以下任何事件时,框架会自动恢复设备:
- 任何驱动程序的电源托管队列的 I/O 请求都到达。
- 用户通过使用设备管理器禁用 USB 选择性挂起。
- 驱动程序调用 WdfDeviceStopIdle,如 防止 USB 设备挂起中所述。
为了恢复设备,KMDF 在设备堆栈中向下发送一个通电请求,然后调用驱动程序的回调函数,其方式与任何其他启动序列的调用方式相同。
在 KMDF 函数驱动程序中支持 USB 选择性挂起
若要在 KMDF 函数驱动程序中实现 USB 选择性挂起,请执行以下操作:
- 初始化与空闲相关的电源策略设置,包括空闲超时。
- (可选)包括逻辑,以在驱动程序确定不应由于打开的句柄或与设备的 I/O 队列无关的其他原因而暂停设备时暂时阻止挂起或恢复操作。
- 在人机接口设备的 USB 驱动程序 (HID) 中,在 INF 中指示它支持选择性挂起。
在 KMDF 函数驱动程序中初始化电源策略设置
为了配置对 USB 选择性挂起的支持,KMDF 驱动程序使用 WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS 结构。 驱动程序必须首先初始化结构,然后才能设置字段来提供有关驱动程序及其设备功能的详细信息。 通常,驱动程序在其 EvtDriverDeviceAdd 或 EvtDevicePrepareHardware 函数中填充此结构。
1. 初始化WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS结构
驱动程序创建设备对象后,驱动程序使用 WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS_INIT 函数初始化结构。 此函数采用两个参数:
- 指向要初始化 的WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS 结构的指针。
- 一个枚举值,指示支持选择性挂起。 驱动程序应指定 IdleUsbSelectiveSuspend。
如果驱动程序指定 IdleUsbSelectiveSuspend,则函数初始化结构的成员,如下所示:
- IdleTimeout 设置为 IdleTimeoutDefaultValue (当前为 5000 毫秒或 5 秒) 。
- UserControlOfIdleSettings 设置为 IdleAllowUserControl 。
- Enabled 设置为 WdfUseDefault,这表示选择性挂起已启用,但如果 UserControlOfIdleSettings 成员允许,用户可以禁用它。
- DxState 设置为 PowerDeviceMaximum,这将使用设备报告的电源功能来确定要转换空闲设备的状态。
2. 配置 USB 选择性挂起
驱动程序初始化 WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS 结构后,驱动程序可以在结构中设置其他字段,然后调用 WdfDeviceAssignS0IdleSettings 将这些设置传递给框架。 以下字段适用于 USB 函数驱动程序:
- IdleTimeout - 在框架认为设备处于空闲状态之前,必须在不收到 I/O 请求的情况下经过的间隔(以毫秒为单位)。 驱动程序可以指定 ULONG 值,也可以接受默认值。
- UserControlOfIdleSettings - 用户是否可以修改设备的空闲设置。 可能的值为 IdleDoNotAllowUserControl 和 IdleAllowUserControl。
- DxState - 框架将设备挂起的设备电源状态。 可能的值为 PowerDeviceD1、PowerDeviceD2 和 PowerDeviceD3。
USB 驱动程序不应更改此值的初始设置。 WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS_INIT 函数将此值设置为 PowerDeviceMaximum,这可确保框架根据设备功能选择正确的值。
以下代码片段来自 Osrusbfx2 示例驱动程序的 Device.c 文件:
WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS idleSettings;
NTSTATUS status = STATUS_SUCCESS;
//
// Initialize the idle policy structure.
//
WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS_INIT(&idleSettings,
IdleUsbSelectiveSuspend);
idleSettings.IdleTimeout = 10000; // 10 sec
status = WdfDeviceAssignS0IdleSettings(Device, &idleSettings);
if ( !NT_SUCCESS(status)) {
TraceEvents(TRACE_LEVEL_ERROR, DBG_PNP,
"WdfDeviceSetPowerPolicyS0IdlePolicy failed %x\n",
status);
return status;
}
在此示例中,驱动程序调用 WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS_INIT,指定 IdleUsbSelectiveSuspend。 驱动程序将 IdleTimeout 设置为 10,000 毫秒 (10 秒) ,并接受 DxState 和 UserControlOfIdleSettings 的框架默认值。 因此,框架会在设备处于空闲状态时将其转换为 D3 状态,并创建一个设备管理器属性页,允许具有管理员特权的用户启用或禁用设备空闲支持。 然后,驱动程序调用 WdfDeviceAssignS0IdleSettings 来启用空闲支持,并在框架中注册这些设置。
驱动程序可以在创建设备对象后随时调用 WdfDeviceAssignS0IdleSettings 。 尽管大多数驱动程序最初从 EvtDriverDeviceAdd 回调调用此方法,但这并非始终可行,甚至不可取。 如果驱动程序支持多个设备或设备版本,则在查询硬件之前,驱动程序可能不知道所有设备功能。 此类驱动程序可以推迟调用 WdfDeviceAssignS0IdleSettings ,直到 EvtDevicePrepareHardware 回调。
在最初调用 WdfDeviceAssignS0IdleSettings 后,驱动程序可以随时更改空闲超时值和设备空闲的设备状态。 若要更改一个或多个设置,驱动程序只需初始化前面所述的另一 个WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS 结构,然后再次调用 WdfDeviceAssignS0IdleSettings 。
阻止 USB 设备挂起
有时,即使超时期间没有 I/O 请求存在,也不应关闭 USB 设备,这通常是在向设备打开句柄或设备正在充电时。 在这种情况下,USB 驱动程序可以通过调用 WdfDeviceStopIdle 并调用 WdfDeviceResumeIdle 来阻止框架挂起空闲设备。如果设备被暂停,则它再次可以接受。
WdfDeviceStopIdle 停止空闲计时器。 如果 IdleTimeout 期限尚未过期且设备尚未暂停,则框架将取消空闲计时器,并且不会暂停设备。 如果设备已挂起,框架会将设备返回到工作状态。 WdfDeviceStopIdle不会阻止框架在系统更改为 Sx 睡眠状态时挂起设备。 它的唯一效果是在系统处于 S0 工作状态时防止设备挂起。 WdfDeviceResumeIdle 重启空闲计时器。 这两种方法管理设备上的引用计数,因此,如果驱动程序多次调用 WdfDeviceStopIdle ,框架不会暂停设备,直到驱动程序调用 WdfDeviceResumeIdle 的次数相同。 驱动程序在未首先调用 WdfDeviceStopIdle的情况下不得调用 WdfDeviceResumeIdle。
HID 驱动程序包含注册表项
USB HID 设备的 KMDF 上层筛选器驱动程序必须在 INF 中指示它们支持选择性挂起,以便 Microsoft 提供的 HIDClass.sys 端口驱动程序可以为 HID 堆栈启用选择性挂起。 INF 应包含 AddReg 指令,该指令添加 SelectiveSuspendEnabled 键并将其值设置为 1,如以下字符串所示:
HKR,,"SelectiveSuspendEnabled",0x00000001,0x1
有关示例,请参阅 WDK 中 %WinDDK%\BuildNumber\Src\Hid\ Hidusbfx2\sys 中的 Hidusbfx2.inx。
KMDF 驱动程序的远程唤醒支持
与选择性暂停一样,KMDF 包含对唤醒的支持,以便 USB 设备可以在设备空闲且系统处于工作状态 (S0) 或处于睡眠状态 (S1-S4) 时触发唤醒信号。 在 KMDF 术语中,这两个功能分别称为“从 S0 唤醒”和“从 Sx 唤醒”。
对于 USB 设备,唤醒仅指示设备本身可以启动从低功耗状态到工作状态的转换。 因此,在 USB 术语中,从 S0 唤醒和从 Sx 唤醒是相同的,称为“远程唤醒”。
KMDF USB 函数驱动程序不需要任何代码来支持从 S0 唤醒,因为 KMDF 将此功能作为选择性挂起机制的一部分提供。 但是,若要在系统处于 Sx 中时支持远程唤醒,函数驱动程序必须:
- 通过调用 WdfUsbTargetDeviceRetrieveInformation 检查设备是否支持远程唤醒。
- 通过初始化唤醒设置并调用 WdfDeviceAssignSxWakeSettings 来启用远程唤醒。
KMDF 驱动程序通常在配置对 EvtDriverDeviceAdd 或 EvtDevicePrepareHardware 函数中的 USB 选择性挂起的支持的同时配置唤醒支持。
检查设备功能
在 KMDF USB 函数驱动程序初始化其电源策略设置以用于空闲和唤醒之前,它应验证设备是否支持远程唤醒。 为了获取有关设备硬件功能的信息,驱动程序初始化 WDF_USB_DEVICE_INFORMATION 结构并调用 WdfUsbTargetDeviceRetrieveInformation,通常在其 EvtDriverDeviceAdd 或 EvtDevicePrepareHardware 回调中。
在调用 WdfUsbTargetDeviceRetrieveInformation 时,驱动程序将句柄传递给设备对象,并将指针传递给初始化 的 WDF_USB_DEVICE_INFORMATION 结构。 从函数成功返回后,结构的“特征”字段包含标志,这些标志指示设备是否为自供电、是否可以高速运行并支持远程唤醒。
来自 Osrusbfx2 KMDF 示例的以下示例演示如何调用此方法来确定设备是否支持远程唤醒。 运行这些代码行后,如果设备支持远程唤醒,则 waitWakeEnable 变量包含 TRUE;如果不支持,则为 FALSE:
WDF_USB_DEVICE_INFORMATION deviceInfo;
// Retrieve USBD version information, port driver capabilities and device
// capabilities such as speed, power, etc.
//
WDF_USB_DEVICE_INFORMATION_INIT(&deviceInfo);
status = WdfUsbTargetDeviceRetrieveInformation(
pDeviceContext->UsbDevice,
&deviceInfo);
waitWakeEnable = deviceInfo.Traits & WDF_USB_DEVICE_TRAIT_REMOTE_WAKE_CAPABLE;
启用远程唤醒
在 USB 术语中,当设置 USB 设备DEVICE_REMOTE_WAKEUP功能时,会启用远程唤醒。 根据 USB 规范,主机软件必须在“仅在将设备置于睡眠状态之前”设备上设置远程唤醒功能。 仅初始化唤醒设置时,才需要 KMDF 函数驱动程序。 KMDF 和 Microsoft 提供的 USB 总线驱动程序发出 I/O 请求,并处理启用远程唤醒所需的硬件操作。
初始化远程唤醒:
1.调用 WDF_DEVICE_POWER_POLICY_WAKE_SETTINGS_INIT 以初始化 WDF_DEVICE_POWER_POLICY_WAKE_SETTINGS 结构。 此函数将结构的 Enabled 成员设置为 WdfUseDefault,将 DxState 成员设置为 PowerDeviceMaximum,并将 UserControlOfWakeSettings 成员设置为 WakeAllowUserControl。
2.使用初始化的结构调用 WdfDeviceAssignSxWakeSettings 。 因此,设备能够从 D3 状态唤醒,用户可以在 设备管理器 中的设备属性页启用或禁用唤醒信号。
Osrusbfx2 示例中的以下代码片段演示如何将唤醒设置初始化为其默认值:
WDF_DEVICE_POWER_POLICY_WAKE_SETTINGS wakeSettings;
WDF_DEVICE_POWER_POLICY_WAKE_SETTINGS_INIT(&wakeSettings);
status = WdfDeviceAssignSxWakeSettings(Device, &wakeSettings);
if (!NT_SUCCESS(status)) {
return status;
}
对于支持选择性挂起的 USB 设备,基础总线驱动程序准备要唤醒的设备硬件。 因此,USB 函数驱动程序很少需要 EvtDeviceArmWakeFromS0 回调。 当空闲超时过期时,框架会向 USB 总线驱动程序发送选择性挂起请求。
出于同一原因,USB 函数驱动程序很少需要 EvtDeviceWakeFromS0Triggered 或 EvtDeviceWakeFromSxTriggered 回调。 相反,框架和基础总线驱动程序会处理将设备返回到工作状态的所有要求。