将 PnP IRP 处理推迟到较低级驱动程序完成为止

某些 PnP 和电源 IRP 必须先由设备的父总线驱动程序处理,然后由设备堆栈中每个更高级的驱动程序进行处理。 例如,父总线驱动程序必须是对设备执行启动操作的第一个驱动程序, (IRP_MN_START_DEVICE) ,然后是每个更高级的驱动程序。 对于此类 IRP,函数和筛选器驱动程序必须设置 I/O 完成例程,将 IRP 传递给下一个较低级别的驱动程序,并推迟处理 IRP 的任何活动,直到较低级别的驱动程序完成 IRP。

可以在 IRQL DISPATCH_LEVEL调用 IoCompletion 例程,但函数或筛选器驱动程序可能需要在 IRQL = PASSIVE_LEVEL处理 IRP。 若要从 IoCompletion 例程返回到PASSIVE_LEVEL,驱动程序可以使用内核事件。 驱动程序注册一个 IoCompletion 例程,该例程设置内核模式事件,然后驱动程序在其 DispatchPnP 例程中等待该事件。 设置 事件后,较低的驱动程序已完成 IRP,并且允许驱动程序处理 IRP。

请注意,驱动程序不得使用此技术等待较低的驱动程序完成电源 IRP (IRP_MJ_POWER) 。 等待 IoCompletion 例程中设置的 DispatchPower 例程中的事件可能会导致死锁。 有关详细信息 ,请参阅传递电源 IRP

以下两个图显示了驱动程序如何等待较低版本的驱动程序完成 PnP IRP 的示例。 该示例演示函数和总线驱动程序必须执行的操作,以及它们如何与 PnP 管理器和 I/O 管理器交互。

说明推迟即插即用 irp 处理的示意图,第 1 部分。

以下注释对应于上图中带圆圈的数字:

  1. PnP 管理器调用 I/O 管理器,将 IRP 发送到设备堆栈中的顶级驱动程序。

  2. I/O 管理器调用顶级驱动程序的 DispatchPnP 例程。 在此示例中,设备堆栈中只有两个驱动程序 (函数驱动程序和父总线驱动程序) ,函数驱动程序是顶级驱动程序。

  3. 函数驱动程序声明并初始化内核模式事件,为下一个较低级别的驱动程序设置堆栈位置,并为此 IRP 设置 IoCompletion 例程。

    函数驱动程序可以使用 IoCopyCurrentIrpStackLocationToNext 设置堆栈位置。

    在调用 IoSetCompletionRoutine 时,函数驱动程序将 InvokeOnSuccessInvokeOnErrorInvokeOnCancel 设置为 TRUE ,并将内核模式事件作为上下文参数的一部分传递。

  4. 函数驱动程序使用 IoCallDriver 将 IRP 传递到设备堆栈,然后执行任何操作来处理 IRP。

  5. I/O 管理器通过调用该驱动程序的 DispatchPnP 例程,将 IRP 发送到设备堆栈中下一个较低的驱动程序。

  6. 此示例中下一个较低的驱动程序是设备堆栈中最低的驱动程序,即父总线驱动程序。 总线驱动程序执行其操作来启动设备。 总线驱动程序设置 Irp-IoStatus.Status>,设置 Irp-IoStatus.Information>(如果与此 IRP 相关),并通过调用 IoCompleteRequest 完成 IRP

    如果总线驱动程序调用其他驱动程序例程或向设备发送 I/O 以启动设备,则总线驱动程序不会在其 DispatchPnP 例程中完成 PnP IRP。 相反,它必须使用 IoMarkIrpPending 将 IRP 标记为挂起,并从其 DispatchPnP 例程返回STATUS_PENDING。 驱动程序稍后从另一个驱动程序例程(可能是 DPC 例程)调用 IoCompleteRequest

下图显示了示例的第二部分,其中设备堆栈中较高级别的驱动程序恢复其推迟的 IRP 处理。

说明推迟即插即用 irp 处理的示意图,第 2 部分。

以下注释对应于上图中带圆圈的数字:

  1. 当总线驱动程序调用 IoCompleteRequest 时,I/O 管理器会检查更高级别的驱动程序的堆栈位置,并调用它找到的任何 IoCompletion 例程。 在此示例中,I/O 管理器查找并调用下一个更高级的驱动程序(即函数驱动程序)的 IoCompletion 例程。

  2. 函数驱动程序的 IoCompletion 例程设置上下文参数中提供的内核模式事件,并返回STATUS_MORE_PROCESSING_REQUIRED。

    IoCompletion 例程必须返回STATUS_MORE_PROCESSING_REQUIRED,以防止 I/O 管理器调用此时由更高级别的驱动程序设置的 IoCompletion 例程。 IoCompletion 例程使用此状态来阻止完成,以便其驱动程序的 DispatchPnP 例程可以重新获得控制权。 当此驱动程序的 DispatchPnP 例程完成 IRP 时,I/O 管理器将恢复为此 IRP 调用更高驱动程序的 IoCompletion 例程。

  3. I/O 管理器停止完成 IRP,并将控制权返回到调用 IoCompleteRequest 的例程,在此示例中,该例程是总线驱动程序的 DispatchPnP 例程。

  4. 总线驱动程序从其 DispatchPnP 例程返回,状态指示其 IRP 处理结果:STATUS_SUCCESS或错误状态。

  5. IoCallDriver 将控制权返回给其调用方,在此示例中为函数驱动程序的 DispatchPnP 例程。

  6. 函数驱动程序的 DispatchPnP 例程继续处理 IRP。

    如果 IoCallDriver 返回STATUS_PENDING,则 表示 DispatchPnP 例程在调用其 IoCompletion 例程之前已恢复执行。 因此, DispatchPnP 例程必须等待其 IoCompletion 例程向内核事件发出信号。 这可确保 DispatchPnP 例程不会继续处理 IRP,直到所有较低的驱动程序都完成它。

    如果 Irp-IoStatus.Status> 设置为错误,则说明较低级别的驱动程序使 IRP 失败,并且除任何必要的清理) 外,函数驱动程序不得继续处理 IRP (。

  7. 较低级驱动程序成功完成 IRP 后,函数驱动程序将处理 IRP。

    对于父总线驱动程序首先处理的 IRP,总线驱动程序通常会在 Irp-IoStatus.Status> 中设置成功状态,并选择性地在 Irp-IoStatus.Information> 中设置值。 函数和筛选器驱动程序将 IoStatus 中的值保留原样,除非它们使 IRP 失败。

    函数驱动程序的 DispatchPnP 例程调用 IoCompleteRequest 来完成 IRP。 I/O 管理器恢复 I/O 完成处理。 在此示例中,函数驱动程序上方没有筛选器驱动程序,因此没有更多要调用 的 IoCompletion 例程。 当 IoCompleteRequest 将控制权返回到函数驱动程序 DispatchPnP 例程时, DispatchPnP 例程将返回状态。

对于某些 IRP,如果函数或筛选器驱动程序在备份设备堆栈时 IRP 失败,PnP 管理器会通知较低的驱动程序。 例如,如果函数或筛选器驱动程序 IRP_MN_START_DEVICE失败,PnP 管理器会将 IRP_MN_REMOVE_DEVICE 发送到设备堆栈。