通用串行总线 (USB) 3.0 规范定义了一项称为 设备挂起的新功能。 该功能使复合设备的单个功能能够独立于其他功能进入低功耗状态。 考虑一个复合设备,它为键盘定义一个设备,为鼠标定义另一个设备。 用户使键盘功能保持工作状态,但在一段时间内不移动鼠标。 鼠标的客户端驱动程序可以检测设备的空闲状态,并在键盘功能保持工作状态时发送设备挂起状态。
无论设备中任何功能的电源状态如何,整个设备都可以转换为挂起状态。 如果某个特定设备和整个设备进入挂起状态,则当设备处于挂起状态时,以及在整个设备的暂停进入和退出过程中,设备的挂起状态将保留。
与 USB 2.0 设备的远程唤醒功能类似,USB 3.0 复合设备中的单个功能可以从低功耗状态唤醒,而不会影响其他功能的电源状态。 此功能称为 设备远程唤醒。 主机通过发送协议请求在设备固件中设置远程唤醒位来显式启用该功能。 此过程称为 支持设备进行远程唤醒。
如果某个设备已准备好进行远程唤醒,则当处于挂起状态时设备在物理设备上发生用户事件时保留足够的电源来生成唤醒 恢复信号 。 由于该恢复信号,客户端驱动程序随后可以退出关联设备的挂起状态。 在复合设备中的鼠标功能示例中,当用户摆动处于空闲状态的鼠标时,鼠标设备会向主机发送恢复信号。 在主机上,USB 驱动程序堆栈检测哪个设备唤醒,并将通知传播到相应设备的客户端驱动程序。 然后,客户端驱动程序可以唤醒设备并进入工作状态。
对于客户端驱动程序,发送设备以挂起状态并唤醒设备的步骤类似于将整个设备发送到挂起状态的单设备设备驱动程序。 以下过程总结了这些步骤。
- 检测关联设备何时处于空闲状态。
- (IRP) 发送空闲 I/O 请求数据包。
- 通过 (IRP) 发送等待唤醒 I/O 请求数据包,提交请求以支持其设备进行远程唤醒。
- 通过将 Dx 电源 IRP (D2 或 D3) 将设备转换为低功耗状态。
复合驱动程序为复合设备中的每个设备创建一个物理设备对象 (PDO) ,并处理客户端驱动程序 (设备设备堆栈) 的 FDO 发送的电源请求。 为了使客户端驱动程序成功进入和退出其设备的挂起状态,复合驱动程序必须支持设备挂起和远程唤醒功能,并处理收到的电源请求。
在 Windows 8 中,USB 3.0 设备的 USB 驱动程序堆栈支持这些功能。 此外,设备挂起和设备远程唤醒实现已添加到 Microsoft 提供的 USB 泛型父驱动程序 (Usbccgp.sys,它是Windows 默认复合驱动程序)。 如果要编写自定义复合驱动程序,则驱动程序必须按照以下过程处理与设备挂起和远程唤醒请求相关的请求。
步骤 1:确定 USB 驱动程序堆栈是否支持功能挂起
在复合驱动程序的 start-device 例程 (IRP_MN_START_DEVICE) 中,执行以下步骤:
- 1.调用 USBD_QueryUsbCapability 例程以确定基础 USB 驱动程序堆栈是否支持功能挂起功能。 调用需要你在上一次调用 USBD_CreateHandle 例程中获得的有效 USBD 句柄。成功调用 USBD_QueryUsbCapability 可确定基础 USB 驱动程序堆栈是否支持功能挂起。 调用可能会返回错误代码,指示 USB 驱动程序堆栈不支持功能挂起或连接的设备不是 USB 3.0 多功能设备。
- 2.如果 USBD_QueryUsbCapability 调用指示支持设备挂起,请将复合设备注册到基础 USB 驱动程序堆栈。 若要注册复合设备,必须发送 IOCTL_INTERNAL_USB_REGISTER_COMPOSITE_DEVICE I/O 控制请求。
- 3. 注册请求使用 REGISTER_COMPOSITE_DEVICE 结构来指定有关复合驱动程序的信息。 请确保将 CapabilityFunctionSuspend 设置为 1 以指示复合驱动程序支持设备挂起。
步骤 2:处理空闲 IRP
客户端驱动程序可以发送空闲的 IRP, 查看 IOCTL_INTERNAL_USB_SUBMIT_IDLE_NOTIFICATION。 客户端驱动程序检测到设备的空闲状态后发送请求。 IRP 包含指向回调完成例程的指针, 客户端驱动程序实现的称为 空闲回调。 在空闲回调中,客户端在将设备发送到挂起状态之前执行任务,例如取消挂起的 I/O 传输。
备注
对于 USB 3.0 设备的客户端驱动程序,空闲 IRP 机制是可选的。 但是,大多数客户端驱动程序都编写为支持 USB 2.0 和 USB 3.0 设备。 若要支持 USB 2.0 设备,驱动程序必须发送空闲 IRP,因为复合驱动程序依赖于该 IRP 来跟踪每个设备的电源状态。 如果所有设备都处于空闲状态,复合驱动程序会将整个设备发送到挂起状态。
从客户端驱动程序接收空闲 IRP 后,复合驱动程序必须立即调用空闲回调,以通知客户端驱动程序,客户端驱动程序可能会将设备发送到挂起状态。
步骤 3:发送远程唤醒通知的请求
客户端驱动程序可以通过提交将次要功能代码设置为 IRP_MN_WAIT_WAKE ( 等待唤醒 IRP) 的IRP_MJ_POWER IRP 来提交请求,以为其设备提供远程唤醒。 仅当驱动程序因用户事件而进入工作状态时,客户端驱动程序才会提交此请求。
收到等待唤醒 IRP 后,复合驱动程序必须将 IOCTL_INTERNAL_USB_REQUEST_REMOTE_WAKE_NOTIFICATION I/O 控制请求发送到 USB 驱动程序堆栈。 请求使 USB 驱动程序堆栈能够在堆栈收到有关恢复信号的通知时通知复合驱动程序。 IOCTL_INTERNAL_USB_REQUEST_REMOTE_WAKE_NOTIFICATION使用 REQUEST_REMOTE_WAKE_NOTIFICATION 结构来指定请求参数。 复合驱动程序必须指定的值之一是用于远程唤醒的设备的设备句柄。 复合驱动程序在上一个请求中获取该句柄,以将复合设备注册到 USB 驱动程序堆栈。
在请求的 IRP 中,复合驱动程序提供指向 (远程唤醒) 完成例程的指针,该例程由复合驱动程序实现。
以下示例代码演示如何发送远程唤醒请求。
/*++
Description:
This routine sends a IOCTL_INTERNAL_USB_REQUEST_REMOTE_WAKE_NOTIFICATION request
to the USB driver stack. The IOCTL is completed by the USB driver stack
when the function wakes up from sleep.
Parameters:
parentFdoExt: The device context associated with the FDO for the
composite driver.
functionPdoExt: The device context associated with the PDO (created by
the composite driver) for the client driver.
--*/
VOID
SendRequestForRemoteWakeNotification(
__inout PPARENT_FDO_EXT parentFdoExt,
__inout PFUNCTION_PDO_EXT functionPdoExt
)
{
PIRP irp;
REQUEST_REMOTE_WAKE_NOTIFICATION remoteWake;
PIO_STACK_LOCATION nextStack;
NTSTATUS status;
// Allocate an IRP
irp = IoAllocateIrp(parentFdoExt->topDevObj->StackSize, FALSE);
if (irp)
{
//Initialize the USBDEVICE_REMOTE_WAKE_NOTIFICATION structure
remoteWake.Version = 0;
remoteWake.Size = sizeof(REQUEST_REMOTE_WAKE_NOTIFICATION);
remoteWake.UsbdFunctionHandle = functionPdoExt->functionHandle;
remoteWake.Interface = functionPdoExt->baseInterfaceNumber;
nextStack = IoGetNextIrpStackLocation(irp);
nextStack->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL;
nextStack->Parameters.DeviceIoControl.IoControlCode = IOCTL_INTERNAL_USB_REQUEST_REMOTE_WAKE_NOTIFICATION;
nextStack->Parameters.Others.Argument1 = &remoteWake;
// Caller's completion routine will free the IRP when it completes.
SetCompletionRoutine(functionPdoExt->debugLog,
parentFdoExt->fdo,
irp,
CompletionRemoteWakeNotication,
(PVOID)functionPdoExt,
TRUE, TRUE, TRUE);
// Pass the IRP
IoCallDriver(parentFdoExt->topDevObj, irp);
}
return;
}
IOCTL_INTERNAL_USB_REQUEST_REMOTE_WAKE_NOTIFICATION请求在唤醒过程中由 USB 驱动程序堆栈完成,当它收到有关恢复信号的通知时。 在此期间,USB 驱动程序堆栈还会调用远程唤醒完成例程。
复合驱动程序必须使等待-唤醒 IRP 保持挂起状态,并将其排队以供以后处理。 当 USB 驱动程序堆栈调用驱动程序的远程唤醒完成例程时,复合驱动程序必须完成该 IRP。
步骤 4:发送请求以支持子设备进行远程唤醒
若要将设备发送到低功耗状态,客户端驱动程序会提交 IRP_MN_SET_POWER IRP,请求将 Windows 驱动程序模型 (WDM) 设备电源状态更改为 D2 或 D3。 通常,如果驱动程序提前发送等待唤醒 IRP 以请求远程唤醒,则客户端驱动程序会发送 D2 IRP。 否则,客户端驱动程序会发送 D3 IRP。
收到 D2 IRP 后,复合驱动程序必须首先确定等待唤醒 IRP 是否正在等待客户端驱动程序发送的请求。 如果该 IRP 处于挂起状态,则复合驱动程序必须支持设备进行远程唤醒。 为此,复合驱动程序必须将SET_FEATURE控制请求发送到设备的第一个接口,使设备能够发送恢复信号。 若要发送控制请求,请通过调用 USBD_UrbAllocate 例程来分配 URB 结构,并调用 UsbBuildFeatureRequest 宏以格式化SET_FEATURE请求的 URB。 在调用中,将 URB_FUNCTION_SET_FEATURE_TO_INTERFACE 指定为操作代码,将 USB_FEATURE_FUNCTION_SUSPEND 指定为功能选择器。 在 Index 参数中,设置最有效字节的 位 1 。 该值将复制到传输的设置数据包中的 wIndex 字段。
以下示例演示如何发送SET_FEATURE控制请求。
/*++
Routine Description:
Sends a SET_FEATURE for REMOTE_WAKEUP to the device using a standard control request.
Parameters:
parentFdoExt: The device context associated with the FDO for the
composite driver.
functionPdoExt: The device context associated with the PDO (created by
the composite driver) for the client driver.
Returns:
NTSTATUS code.
--*/
VOID
NTSTATUS SendSetFeatureControlRequestToSuspend(
__inout PPARENT_FDO_EXT parentFdoExt,
__inout PFUNCTION_PDO_EXT functionPdoExt,
)
{
PURB urb
PIRP irp;
PIO_STACK_LOCATION nextStack;
NTSTATUS status;
status = USBD_UrbAllocate(parentFdoExt->usbdHandle, &urb);
if (!NT_SUCCESS(status))
{
//USBD_UrbAllocate failed.
goto Exit;
}
//Format the URB structure.
UsbBuildFeatureRequest (
urb,
URB_FUNCTION_SET_FEATURE_TO_INTERFACE, // Operation code
USB_FEATURE_FUNCTION_SUSPEND, // feature selector
functionPdoExt->firstInterface, // first interface of the function
NULL);
irp = IoAllocateIrp(parentFdoExt->topDevObj->StackSize, FALSE);
if (!irp)
{
// IoAllocateIrp failed.
status = STATUS_INSUFFICIENT_RESOURCES;
goto Exit;
}
nextStack = IoGetNextIrpStackLocation(irp);
nextStack->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL;
nextStack->Parameters.DeviceIoControl.IoControlCode = IOCTL_INTERNAL_USB_SUBMIT_URB;
// Attach the URB to the IRP.
USBD_AssignUrbToIoStackLocation(nextStack, (PURB)urb);
// Caller's completion routine will free the IRP when it completes.
SetCompletionRoutine(functionPdoExt->debugLog,
parentFdoExt->fdo,
irp,
CompletionForSuspendControlRequest,
(PVOID)functionPdoExt,
TRUE, TRUE, TRUE);
// Pass the IRP
IoCallDriver(parentFdoExt->topDevObj, irp);
Exit:
if (urb)
{
USBD_UrbFree( parentFdoExt->usbdHandle, urb);
}
return status;
}
然后,复合驱动程序将 D2 IRP 向下发送到 USB 驱动程序堆栈。 如果所有其他设备都处于挂起状态,USB 驱动程序堆栈将通过操作控制器上的某些端口寄存器来挂起端口。
在鼠标功能示例中,由于远程唤醒功能已启用 (请参阅步骤 4) ,因此当用户摆动鼠标时,鼠标设备会在上游主控制器的线路上生成恢复信号。 然后,控制器通过发送包含唤醒设备相关信息的通知数据包来通知 USB 驱动程序堆栈。
收到通知数据包后,USB 驱动程序堆栈将完成挂起 的IOCTL_INTERNAL_USB_REQUEST_REMOTE_WAKE_NOTIFICATION 请求 (请参阅步骤 3) ,并调用 (请求中指定的并由复合驱动程序实现的远程唤醒) 完成回调例程。 当通知到达复合驱动程序时,它会通过完成客户端驱动程序先前发送的等待唤醒 IRP,通知相应的客户端驱动程序设备已进入工作状态。
在 远程唤醒完成例程中,复合驱动程序应将工作项排队以完成挂起的等待唤醒 IRP。 对于 USB 3.0 设备,复合驱动程序仅唤醒发送恢复信号的设备,并使其他设备处于挂起状态。 对工作项进行排队可确保与 USB 2.0 设备的功能驱动程序的现有实现兼容。
工作线程完成等待-唤醒 IRP 并调用客户端驱动程序的完成例程。 然后,完成例程发送 D0 IRP 以进入工作状态的设备。 在完成等待-唤醒 IRP 之前,复合驱动程序应调用 PoSetSystemWake ,将等待唤醒 IRP 标记为导致系统从挂起状态中唤醒的 IRP。 Power Manager 记录 Windows (ETW) 事件跟踪, (全局系统通道) 中查看,其中包含有关唤醒系统的设备的信息。
USB 设备的远程唤醒
在暂停时可以响应外部唤醒信号的 USB 设备据说具有 远程唤醒 功能。 具有远程唤醒功能的设备示例包括鼠标、键盘、USB 集线器、调制解调器 (环形唤醒) 、NIC、电缆插入唤醒。 所有这些设备都能够生成远程唤醒信号。 无法生成远程唤醒信号的设备包括摄像机、大容量存储设备、音频设备和打印机。
支持远程唤醒信号的设备驱动程序必须发出 IRP_MN_WAIT_WAKE IRP(也称为等待唤醒 IRP)来支持设备进行远程唤醒。 支持具有 Wake-Up 功能的设备部分中介绍了等待唤醒机制。
USB 设备上的远程唤醒
在 USB 术语中,设置 USB 设备DEVICE_REMOTE_WAKEUP功能时,会启用远程唤醒。 USB 规范指定主机软件必须在“仅在将设备置于睡眠状态之前”设备上设置远程唤醒功能。
因此,在收到设备的等待唤醒 IRP 后,USB 堆栈不会在设备上设置DEVICE_REMOTE_WAKEUP功能。 相反,它会等待,直到收到 IRP_MN_SET_POWER 请求,将设备的 WDM 设备状态更改为 D1/D2。 在大多数情况下,当 USB 堆栈收到此请求时,它会在设备上设置远程唤醒功能,并通过暂停设备的上游端口使设备进入睡眠状态。 设计和调试驱动程序时,应记住,通过等待唤醒 IRP 在软件中为 USB 设备提供唤醒与通过设置远程唤醒功能在硬件中为设备提供唤醒之间存在着松散的关系。
当收到将设备更改为 D3 睡眠状态的请求时,USB 堆栈不会为设备启用远程唤醒,因为根据 WDM 电源模型,D3 中的设备无法唤醒系统。
连接或分离 USB 设备时的唤醒行为
WDM 电源模式的 USB 实现的另一个独特方面是连接 USB 集线器以便进行远程唤醒。 如果总线上的 USB 叶设备用于唤醒,则 USB 堆栈还会为 USB 主机控制器提供唤醒的支持,但它不一定将任何 USB 集线器上游设备支持。 仅当 USB 堆栈配置为在连接时唤醒系统并分离 (插头/拔出) 事件时,USB 集线器驱动程序才会为集线器提供远程唤醒。
UHCI) USB 主机控制器 (通用主机控制器接口不区分根集线器端口上的远程唤醒信号和连接更改事件。 这意味着,当 USB 设备连接到根集线器端口或从根集线器端口断开连接时,如果 UHCI 控制器后面至少有一个设备用于唤醒,系统将始终从低系统电源状态唤醒。