Passing Power IRPs
Power IRPs must be passed all the way down the device stack to the PDO to ensure that power transitions are managed cleanly. Drivers handle an IRP that reduces device power as the IRP travels down the device stack. Drivers handle an IRP that applies device power in IoCompletion routines as the IRP travels back up the device stack.
The following figure shows the steps that drivers need to take to pass a power IRP down a device stack in Windows 7 and Windows Vista.
As the previous figure shows, in Windows 7 and Windows Vista, a driver must do the following:
Call IoCopyCurrentIrpStackLocationToNext if setting an IoCompletion routine, or IoSkipCurrentIrpStackLocation if not setting an IoCompletion routine.
These two routines set the IRP stack location for the next-lower driver. Copying the current stack location ensures that the IRP stack pointer is set to the correct location when the IoCompletion routine runs.
If a badly written driver makes the mistake of calling IoSkipCurrentIrpStackLocation and then setting a completion routine, this driver might overwrite a completion routine set by the driver below it.
Call IoSetCompletionRoutine to set an IoCompletion routine, if a complete routine is required.
Call IoCallDriver to pass the IRP to the next-lower driver in the stack.
The following figure shows the steps that drivers need to take to pass a power IRP down a device stack in Windows Server 2003, Windows XP, and Windows 2000.
As the previous figure shows, a driver must do the following:
Depending on the type of driver, possibly call PoStartNextPowerIrp. For more information, see Calling PoStartNextPowerIrp.
Call IoCopyCurrentIrpStackLocationToNext if setting an IoCompletion routine, or IoSkipCurrentIrpStackLocation if not setting an IoCompletion routine.
These two routines set the IRP stack location for the next-lower driver. Copying the current stack location ensures that the IRP stack pointer is set to the correct location when the IoCompletion routine runs.
Call IoSetCompletionRoutine to set an IoCompletion routine. In the IoCompletion routine, most drivers call PoStartNextPowerIrp to indicate that it is ready to handle the next power IRP.
Call PoCallDriver to pass the IRP to the next-lower driver in the stack.
Drivers must use PoCallDriver, rather than IoCallDriver (as for other IRPs) to ensure that the system synchronizes power IRPs properly. For more information, see Calling IoCallDriver vs. Calling PoCallDriver.
Remember that IoCompletion routines can be called at IRQL = DISPATCH_LEVEL. Therefore, if a driver requires additional processing at IRQL = PASSIVE_LEVEL after lower-level drivers have finished with the IRP, the driver's completion routine should queue a work item and then return STATUS_MORE_PROCESSING_REQUIRED. The worker thread must complete the IRP.
In Windows 98/Me, drivers must complete power IRPs at IRQL = PASSIVE_LEVEL.
Do Not Change the Function Codes in a Power IRP
In addition to the usual rules that govern the processing of IRPs, IRP_MJ_POWER IRPs have the following special requirement: A driver that receives a power IRP must not change the major and minor function codes in any I/O stack locations in the IRP that have been set by the power manager or by higher-level drivers. The power manager relies on these function codes remaining unchanged until the IRP is completed. Violations of this rule can cause problems that are difficult to debug. For example, the operating system might stop responding, or "hang."
Do Not Block While Handling a Power IRP
Drivers must not cause long delays while handling power IRPs.
When passing down a power IRP, a driver should return from its DispatchPower routine as soon as possible after calling IoCallDriver (in Windows 7 and Windows Vista) or PoCallDriver (in Windows Server 2003, Windows XP, and Windows 2000). A driver must not wait for a kernel event or otherwise delay before returning. If a driver cannot handle a power IRP in a brief time, it should return STATUS_PENDING and queue all incoming IRPs until the power IRP completes. (Note that this behavior is different from that of PnP IRPs and DispatchPnP routines, which are allowed to block.)
If the driver must wait for a power action by another driver further down the device stack, it should return STATUS_PENDING from its DispatchPower routine and set an IoCompletion routine for the power IRP. The driver can perform whatever tasks it requires in the IoCompletion routine, and then call PoStartNextPowerIrp (Windows Server 2003, Windows XP, and Windows 2000 only) and IoCompleteRequest.
For example, the power policy owner for a device typically sends a device power IRP while holding a system power IRP in order to set the device power state appropriate for the requested system power state.
In this situation, the power policy owner should set an IoCompletion routine in the system power IRP, pass the system power IRP to the next-lower driver, and return STATUS_PENDING from its DispatchPower routine.
In the IoCompletion routine, it calls PoRequestPowerIrp to send the device power IRP, passing a pointer to a callback routine in the request. The IoCompletion routine should return STATUS_MORE_PROCESSING_REQUIRED.
Finally, the driver passes down the system IRP from the callback routine. The driver must not wait for a kernel event in its DispatchPower routine and signal with the IoCompletion routine for the IRP it is currently handling; a system deadlock might occur. For more information, see Handling a System Set-Power IRP in a Device Power Policy Owner.
In a similar situation, when the system is going to sleep, a power policy owner might need to complete some pending I/O before it sends the device IRP to power down its device. Instead of signaling an event when the I/O completes and waiting in its DispatchPower routine, the driver should queue a work item and return STATUS_PENDING from the DispatchPower routine. In the worker thread, it waits for I/O to complete and then sends the device power IRP. For more information, see IoAllocateWorkItem.