本文介绍 KMDF 函数驱动程序如何支持 USB 选择性挂起。
如果 USB 驱动程序需要用户模式下不可用的功能或资源,则应提供 KMDF 函数驱动程序。 KMDF 驱动程序通过在 KMDF 初始化结构中设置相关值,然后提供相应的回调函数来实现选择性挂起。 KMDF 处理与下层驱动程序的通信,以实现设备的暂停和恢复操作。
KMDF 驱动程序中的选择性挂起指南
支持选择性挂起的 KMDF 驱动程序必须遵循以下准则:
- KMDF 函数驱动程序必须是其设备堆栈的 PPO。 默认情况下,KMDF 函数驱动程序是 PPO。
- 支持选择性挂起的 KMDF 函数驱动程序可以使用经过电源管理的队列或不经过电源管理的队列。 默认情况下,PPO 的队列对象是由电源管理的。
电源策略所有权和 KMDF USB 驱动程序
默认情况下,USB 设备的 KMDF 函数驱动程序是设备堆栈的 PPO。 KMDF 代表此驱动程序管理选择性挂起和恢复。
KMDF 驱动程序中的 I/O 队列配置
支持选择性挂起的 KMDF 函数驱动程序可以使用受电源管理的队列或不受电源管理的队列。 通常,驱动程序将配置一个队列,该队列不受电源管理以接收传入设备 I/O 控制请求,并配置一个或多个电源托管队列来接收读取、写入和其他依赖电源的请求。 当请求到达电源管理的队列时,KMDF 可确保设备处于 D0 中,然后再将请求呈现给驱动程序。
如果编写的是设备堆栈中位于 PPO 上方的 KMDF 筛选器驱动程序,则必须避免使用电源管理队列。 原因与 UMDF 驱动程序的原因相同。 当设备暂停时,框架不会显示来自电源托管队列的请求,因此此类队列的使用可能会停止设备堆栈。
KMDF 函数驱动程序的选择性挂起机制
KMDF 负责处理支持 USB 选择性挂起所需的大部分任务。 它跟踪 I/O 活动,管理空闲计时器,并发送设备 I/O 控制请求,导致父驱动程序(Usbhub.sys 或 Usbccgp.sys)暂停和恢复设备。
如果 KMDF 函数驱动程序支持选择性挂起,KMDF 会跟踪每个设备对象拥有的所有电源管理队列上的 I/O 活动。 只要 I/O 计数达到零,框架就会启动空闲计时器。 默认超时值为 5 秒。
如果输入/输出请求在空闲超时时间到期之前到达属于设备对象的电源管理队列,则框架将取消空闲计时器,从而不暂停设备。
空闲计时器过期时,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 会向设备堆栈发送启动请求,然后以与其他电源启动序列相同的方式调用驱动程序的回调函数。
有关与电源关闭和开启序列相关的回调的详细信息,请参阅 WDF 驱动程序白皮书中的即插即用和电源管理。
在 KMDF 函数驱动程序中支持 USB 选择性挂起功能
若要在 KMDF 函数驱动程序中实现 USB 选择性挂起,
- 初始化与空闲相关的电源策略设置,包括空闲超时。
- 可选地,您可以包括逻辑,当驱动程序判断由于打开句柄或其他与设备 I/O 队列无关的原因设备不应暂停时,以暂时防止设备暂停或恢复其操作。
- 在 USB 驱动程序的人机接口设备(HID)中,在 INF 文件中声明它支持选择性暂停。
初始化 KMDF 函数驱动程序中的电源策略设置
若要配置对 USB 选择性挂起的支持,KMDF 驱动程序使用 WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS 结构。 驱动程序必须先初始化结构,然后才能设置字段,提供有关驱动程序及其设备功能的详细信息。 通常,驱动程序在其 EvtDriverDeviceAdd 或 EvtDevicePrepareHardware 函数中填充此结构。
初始化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,后者使用设备的报告电源功能来确定将空闲设备转换到的状态。
配置 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 期限尚未过期且设备尚未暂停,框架将取消空闲计时器,并且不会暂停设备。 如果设备已经处于挂起状态,框架会将设备恢复到正常工作状态。 当系统更改为 Sx 睡眠状态时,WdfDeviceStopIdle不会阻止框架挂起设备。 它的唯一作用是在系统处于 S0 工作状态时防止设备挂起。 WdfDeviceResumeIdle 重新启动空闲计时器。 这两种方法管理设备上的引用计数,因此,如果驱动程序多次调用WdfDeviceStopIdle,则框架在驱动程序调用相同次数的WdfDeviceResumeIdle之前,不会暂停设备。 驱动程序在未首先调用 WdfDeviceStopIdle 的情况下,不得调用 WdfDeviceResumeIdle。
包括一个注册表项(仅限 HID 驱动程序)
USB HID 设备的 KMDF 上层过滤器驱动程序必须在 INF 中指示它们支持选择性挂起,以便由 Microsoft 提供的 HIDClass.sys 端口驱动程序可以为 HID 堆栈启用选择性挂起。 INF 应包括一个 AddReg 指令,该指令添加 SelectiveSuspendEnabled 键并将其值设置为 1,如以下字符串所示:
HKR,,"SelectiveSuspendEnabled",0x00000001,0x1
有关示例,请参阅 WDK 中的 Hidusbfx2.inx,%WinDDK%\BuildNumber\Src\Hid\ Hidusbfx2\sys。
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 结构的指针。 从函数成功返回后,结构的 Traits 字段包含指示设备是否自供电、可以高速运行并支持远程唤醒的标志。
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 请求并处理启用远程唤醒所需的硬件操作。
初始化唤醒设置
- 调用 WDF_DEVICE_POWER_POLICY_WAKE_SETTINGS_INIT 以初始化 WDF_DEVICE_POWER_POLICY_WAKE_SETTINGS 结构。 此函数将结构的 Enabled 成员设置为 WdfUseDefault,将 DxState 成员设置为 PowerDeviceMaximum,并将 UserControlOfWakeSettings 成员设置为 WakeAllowUserControl。
- 使用初始化的结构调用 WdfDeviceAssignSxWakeSettings 。 因此,设备能够从 D3 状态唤醒,用户可以在 Device Manager 中的设备属性页中启用或禁用唤醒信号。
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 回调。 相反,框架和基础总线驱动程序处理将设备返回到工作状态的所有要求。