通过设备树了解等待/唤醒 IRP 的路径
在单个设备堆栈中,电源策略所有者发送等待/唤醒 IRP,所有驱动程序处理等待/唤醒 IRP,如 等待/唤醒操作概述 中所述,以及分别在 发送等待/唤醒 IRP 和 接收等待/唤醒 IRP 中详述。
在 设备树 (分支中,由叶开发节点及其父级、祖父级等) 的 devnode 组成,驱动程序必须合作,以确保等待/唤醒 IRP 到达能够启用唤醒所需的所有硬件的驱动程序。
在 ACPI 计算机上,ACPI 负责启用与每个叶设备的唤醒信号关联的特定于系统的 常规用途 事件 (GPE) 寄存器。 因此,驱动程序必须请求并转发等待/唤醒 IRP,直到某个驱动程序到达在启动) 时插入设备堆栈 (ACPI 筛选器驱动程序或基础 Windows ACPI 驱动程序,Acpi.sys。 作为响应,ACPI 启用寄存器,将 IRP 挂起,直到信号到达,然后完成 IRP。 由于 ACPI 可以响应唤醒信号,因此它不会将 IRP 转发到较低的驱动程序。
ACPI 筛选器驱动程序(如基础 ACPI 驱动程序本身)对其他驱动程序是透明的。 为了在硬件设计中提供最大的灵活性,ACPI 筛选器驱动程序在任何设备堆栈中的确切位置都是特定于设备和系统的。 在设计驱动程序时,不能对 ACPI 筛选器在设备堆栈中的存在或位置做出任何假设。
请记住,枚举子设备的驱动程序为每个子设备创建 PDO,为父设备创建 FDO。 因此,驱动程序充当子设备的总线驱动程序,以及父设备的函数驱动程序/策略所有者。 因此,每当总线驱动程序收到子 PDO 的等待/唤醒 IRP 时,它都应为其父 PDO 请求另一个等待/唤醒 IRP。
下图显示了发生此类情况的示例配置。
在示例配置中,键盘和调制解调器是 USB 集线器的子级,后者又是由 PCI 总线枚举的 USB 主控制器的子级。 下图显示了示例配置中键盘的设备堆栈。
如上图所示,从下到上读取:
Windows ACPI 驱动程序(Acpi.sys)为 PCI 创建 PDO。
PCI 驱动程序创建 PCI FDO 和 USB 主机控制器 PDO,并拥有 PCI 设备堆栈的策略。
USB 主机控制器驱动程序 (主机端口/微型端口驱动程序对) 创建 USB 主机控制器 FDO 和 USB 集线器 PDO。 它拥有 USB 主机控制器设备堆栈的策略。 请注意,Acpi.sys 也会在此堆栈中创建筛选器 DO。
USB 集线器驱动程序创建 USB 集线器 FDO 和键盘 PDO。 此驱动程序拥有 USB 集线器设备堆栈的电源策略。
键盘的功能驱动程序是 USB HID 类驱动程序/微型驱动程序对。 此驱动程序为键盘创建 FDO 并拥有其电源策略。 由于键盘没有子设备,因此此驱动程序不会创建 PDO。
请注意,每个设备堆栈可能包含未显示的其他可选筛选器DO。
若要允许键盘输入唤醒系统,键盘的策略所有者请求其 PDO 的IRP_MN_WAIT_WAKE 。 该 IRP 会引发一系列其他等待/唤醒 IRP,如下图所示。
当总线驱动程序收到针对其创建的 PDO 的IRP_MN_WAIT_WAKE 时,它必须为其拥有电源策略并创建了 FDO 的设备堆栈请求另一个 IRP_MN_WAIT_WAKE 。
如上图所示:
键盘驱动程序调用 PoRequestPowerIrp 以将等待/唤醒 IRP (IRP1) 发送到其 PDO。
电源管理器分配 IRP,并通过 I/O 管理器将其发送到键盘的设备堆栈顶部。 驱动程序设置 IoCompletion 例程,并将 IRP 向下传递堆栈,直到它到达键盘 PDO。 充当键盘总线驱动程序的 USB 集线器驱动程序保留 IRP1 挂起。
由于 USB 集线器驱动程序无法在唤醒信号到达时唤醒系统,因此 USB 集线器驱动程序必须调用 PoRequestPowerIrp ,以请求等待/唤醒 IRP (IRP2) USB 集线器设备堆栈。
电源管理器将此 IRP 发送到 USB 集线器设备堆栈的顶部。 此堆栈中的驱动程序设置 IoCompletion 例程,并将 IRP 向下传递到 USB 主机控制器驱动程序 (该驱动程序充当 USB 集线器) 的总线驱动程序。 USB 主机控制器驱动程序保持 IRP2 挂起,直到键盘发出唤醒事件信号。
同样,USB 主控制器驱动程序无法唤醒系统,因此 USB 主机控制器驱动程序调用 PoRequestPowerIrp 以将等待/唤醒 IRP (IRP3) 发送到 USB 主机控制器设备堆栈。
电源管理器将此 IRP 发送到 USB 主机控制器设备堆栈的顶部,其中驱动程序设置 IoCompletion 例程并将 IRP 向下传递到 PCI 驱动程序 (该驱动程序充当 USB 集线器) 的总线驱动程序。 PCI 驱动程序保持 IRP3 挂起,直到键盘发出唤醒事件信号。
PCI 驱动程序无法唤醒系统,因此 PCI 驱动程序调用 PoRequestPowerIrp 以将等待/唤醒 IRP (IRP4) 发送到 PCI 设备堆栈。 其父级是根设备,ACPI 是其总线驱动程序。
电源管理器将 IRP 发送到 PCI 总线设备堆栈的顶部;其驱动程序设置完成例程并将 IRP 向下传递到 Windows ACPI 驱动程序,Acpi.sys。
Acpi.sys 可以唤醒系统,因此它不会向任何其他 PDO 发送等待/唤醒 IRP。 Acpi.sys 挂起 IRP4,直到唤醒信号到达。
当键盘断言唤醒信号时,Acpi.sys 截获它。 但是,ACPI 无法确定键盘是否断言了信号,只能确定信号来自根设备。 Acpi.sys 然后完成 IRP4,I/O 管理器调用 IoCompletion 例程,这些例程将备份到 PCI 设备堆栈。 当 IRP4 完成并且所有 IoCompletion 例程都已运行时,将调用 PCI 驱动程序的回调例程。 在其回调例程中,PCI 驱动程序确定信号通过 USB 主控制器。 然后,PCI 驱动程序完成 IRP3。 相同的顺序通过 USB 主机控制器堆栈和 USB 集线器堆栈发生,直到键盘驱动程序收到 IRP1。 此时,键盘驱动程序可以根据需要为唤醒事件提供服务。
每次驱动程序将等待/唤醒 IRP 发送到父 PDO 时,它都必须为其自己的 IRP 设置 Cancel 例程。 如果取消触发新 IRP 的 IRP 被取消,则设置 Cancel 例程可让驱动程序取消新 IRP。 在 USB 示例中,如果键盘驱动程序取消其等待/唤醒 IRP (因此禁用键盘唤醒) ,则 USB 集线器、USB 主机控制器和 PCI 驱动程序必须取消由于键盘 IRP 而发送的 IRP。 有关详细信息,请参阅 取消等待/唤醒 IRP 的例程。
尽管父驱动程序可能会枚举多个可启用等待/唤醒的子级,但 PDO 只能有一个等待/唤醒 IRP 挂起。 在这种情况下,父驱动程序应确保每当其任何设备启用唤醒时,它都保持等待/唤醒 IRP 挂起状态。 为此,驱动程序会在每次收到等待/唤醒 IRP 时递增内部计数器。 每次驱动程序完成等待/唤醒 IRP 时,它都会递减计数,如果生成的值为非零,则向其设备堆栈发送另一个等待/唤醒 IRP。
例如,在前面示例 USB 配置图中显示的 USB 配置 中,USB 集线器枚举两个设备:一个键盘和一个调制解调器。 当 USB 集线器驱动程序收到键盘 PDO 的等待/唤醒 IRP 时,它会在为其自己的 PDO 请求 IRP 之前递增等待/唤醒 IRP 的计数。 如果调制解调器的策略所有者稍后为调制解调器启用唤醒,USB 集线器驱动程序会为调制解调器 PDO 设置新的 IRP,并递增其等待/唤醒引用计数。 但是,由于 USB 集线器 PDO 不能有两个同时挂起的等待/唤醒 IRP,因此 USB 集线器驱动程序不会为 USB 集线器 PDO 请求新的等待/唤醒 IRP。
当唤醒信号从键盘或调制解调器到达时,USB 集线器驱动程序将确定发出信号的设备、完成相应的 IRP 并减小其引用计数。 由于两个设备都启用了唤醒 (因此其引用计数为非零) ,因此它必须发送自己的设备堆栈另一个等待/唤醒 IRP,以“重新配置”自己的 PDO 进行唤醒。 (USB 主机控制器和 PCI 驱动程序也是如此。)
但是,驱动程序不会向自己发送 IRP,以在刚刚到达唤醒信号的同一设备上重新启用等待/唤醒。 只有设备电源策略管理器可以执行此操作。 重新启用等待/唤醒不是自动的。