Programming the Microsoft Windows Driver Model
Additional Power-Management Details
In this section, I ll describe some additional details about power management, including flags you might need to set in your device object, controlling your device s wake-up feature, arranging for power-down requests after your device has been idle for a predetermined time, and optimizing context-restore operations.
Flags to Set in AddDevice
Two flag bits in a device object see Table 8-5 control various aspects of power management. After you call IoCreateDevice in your AddDevice function, both of these bits will be set to 0, and you can set one or the other of them depending on circumstances.
Flag | Description |
DO_POWER_PAGABLE | Driver s IRP_MJ_POWER dispatch routine must run at PASSIVE_LEVEL. |
DO_POWER_INRUSH | Powering on this device requires a large amount of current. |
Set the DO_POWER_PAGABLE flag if your dispatch function for IRP_MJ_POWER requests must run at PASSIVE_LEVEL. The flag has the name it does because, as you know, paging is allowed at PASSIVE_LEVEL only. If you leave this flag set to 0, the Power Manager is free to send you power requests at DISPATCH_LEVEL. In fact, it always will do so in the current release of Windows XP.
Set the DO_POWER_INRUSH flag if your device draws so much current when powering up that other devices should not be allowed to power up simultaneously. The problem solved by this flag is familiar to people who ve experienced multiple simultaneous spikes of electricity demand at the end of a power outage having all your appliances trying to cycle on at the same time can blow the main breaker. The Power Manager guarantees that only one inrush device at a time will be powered up. Furthermore, it sends power requests to inrush devices at DISPATCH_LEVEL, which implies that you should not also set the DO_POWER_PAGABLE flag.
The system s ACPI filter driver will set the INRUSH flag in the PDO automatically if the ASL description of the device so indicates. All that s required for the system to properly serialize inrush power is that some device object in the stack have the INRUSH flag; you won t need to set the flag in your own device object too. If the system can t automatically determine that you require inrush treatment, however, you ll need to set the flag yourself.
The settings of the PAGABLE and INRUSH flags need to be consistent in all the device objects for a particular device. If the PDO has the PAGABLE flag set, every device object should also have PAGABLE set. Otherwise, a bug check with the code DRIVER_POWER_STATE_FAILURE can occur. (It s legal for a PAGABLE device to be layered on top of a non-PAGABLE device, just not the other way around.) If a device object has the INRUSH flag set, neither it nor any lower device objects should be PAGABLE, or else an INTERNAL_POWER_ERROR bug check will occur. If you re writing a disk driver, don t forget that you may change back and forth from time to time between pageable and nonpageable status in response to device usage PnP notifications about paging files.
Device Wake-Up Features
Some devices have a hardware wake-up feature, which allows them to wake up a sleeping computer when an external event occurs. See Figure 8-15. The power switch on the current crop of PCs is such a device. So are many modems and network cards, which are able to listen for incoming calls and packets, respectively. USB devices ordinarily claim wake-up capability, and many hubs and host controllers implement the wake-up signaling needed to support that claim.
Figure 8-15. Examples of devices that wake the system.
If your device has a wake-up feature, your function driver has additional power-management responsibilities beyond the ones we ve already discussed. The additional responsibilities revolve around the IRP_MN_WAIT_WAKE flavor of IRP_MJ_POWER:
-
You ll inspect the device capabilities to determine whether and under what circumstances, in the opinion of the bus driver and the ACPI filter driver, your device will be capable of waking the system.
-
You ll maintain some persistent setting to indicate whether the end user wants you to arm your device s wake-up feature.
-
At a time when your device stack isn t in the middle of a power transition, you ll use PoRequestPowerIrp to originate an IRP_MN_WAIT_WAKE request, which the bus driver will ordinarily pend.
-
When you have a choice about which power state to put your device into, you ll try to choose the lowest available state consistent with your device s wake-up capabilities.
-
If your device causes the system to wake from a standby state, the bus driver will complete your WAIT_WAKE request. Thereafter, the Power Manager will call your power callback routine, from within which you should originate a device set-power request to restore your device to the D0 state.
NOTE
The WAKEUP sample in the companion content illustrates how to implement wake-up functionality using GENERIC.SYS. GENERIC itself, also in the companion content, contains the code discussed throughout this section. Note that WDMWIZ will generate exactly the same code (but with slightly different function names) if you use it to generate a skeleton project that doesn t use GENERIC.
Giving the End User Control
The end user has ultimate control over whether your device s wake-up feature, if any, should actually be armed. The standard way to provide this control is to support a Windows Management Instrumentation (WMI) class named MSPower_DeviceWakeEnable. (See Chapter 10 for more information about WMI.) The Device Manager automatically generates a Power Management tab in the device properties if the driver supports either MSPower_Device Wake Enable or MSPower_DeviceEnable. See Figure 8-16.
Figure 8-16. Power Management tab in device properties.
Your driver should remember the current state of MSPower_Device Wake Enable s Enable member in both your device extension structure and in a registry key. You ll probably want to initialize your device extension variable from the registry at AddDevice time.
WAIT_WAKE Mechanics
As the power policy owner for your device, your driver originates a WAIT_WAKE request by calling PoRequestPowerIrp:
if (InterlockedExchange(&pdx->wwoutstanding, 1)) <skip remaining statements>pdx->wwcancelled = 0; POWER_STATE junk; junk.SystemState = pdx->devcaps.SystemWake status = PoRequestPowerIrp(pdx->Pdo, IRP_MN_WAIT_WAKE, junk, (PREQUEST_POWER_COMPLETE) WaitWakeCallback, pdx, &pdx->WaitWakeIrp);if (!NT_SUCCESS(status)) { pdx->WakeupEnabled = FALSE; pdx->wwoutstanding = 0;
PoRequestPowerIrp creates an IRP_MJ_POWER with the minor function IRP_MN_WAIT_WAKE and sends it to the topmost driver in your own PnP stack. (I ll explain the other statements shortly.) This fact means that your own POWER dispatch function will see the IRP as it travels down the stack. You should install a completion routine and pass it further down the stack:
IoCopyCurrentIrpStackLocationToNext(Irp); IoSetCompletionRoutine(Irp, (PIO_COMPLETION_ROUTINE) WaitWakeCompletionRoutine, pdx, TRUE, TRUE, TRUE); PoStartNextPowerIrp(Irp); status = PoCallDriver(pdx->LowerDeviceObject, Irp);
The bus driver will normally pend the IRP and return STATUS_PENDING. That status code will percolate back up through the dispatch routines of all the drivers in your stack and will eventually cause PoRequestPowerIrp to return STATUS_PENDING. Several other actions are possible, however:
-
If you send more than one WAIT_WAKE request, the bus driver will complete the second and subsequent ones with STATUS_DE VICE_BUSY. In other words, you can have only one WAIT_WAKE outstanding.
-
If the device is already in too low a power state (less than the DeviceWake capability, in other words), the bus driver completes a WAIT_WAKE with STATUS_INVALID_DEVICE_STATE.
-
If the device capabilities indicate that your device doesn t support wake-up in the first place, the bus driver completes a WAIT_WAKE with STATUS_NOT_SUPPORTED.
Note that if the bus driver immediately causes the IRP to fail, your I/O completion routine (WaitWakeCompletionRoutine in the preceding example) will be called. Your power callback routine (WaitWakeCallback) will not.
Completing IRP_MN_WAIT_WAKE
In the normal case, when the bus driver returns STATUS_PENDING, you simply leave the IRP sitting there, waiting for one of several things to happen:
-
The system goes into standby and, still later, wakes up because your device asserts its wake-up signal. The computer hardware repowers automatically. The bus driver detects that your device is responsible and completes your WAIT_WAKE with STATUS_SUCCESS. Later on, you and all other drivers receive a PowerSystemWorkingSET_POWER request. In response, you originate a device power IRP to put your device into an appropriate power state. Since your device is attempting to communicate with the computer, you ll probably want to put the device into the D0 state at this point.
-
The system stays in (or returns to) the PowerSystemWorking state, but your device ends up in a low power state. Thereafter, your device asserts its wake-up signal, and the bus driver completes your WAIT_WAKE. In this case, the Power Manager won t send you a system power IRP because the system is already in the working state, but your device is still in its low power state. To handle this case properly, your power callback routine (WaitWakeCallback in the example) needs to originate a device power IRP to put your device into (probably) the D0 state.
-
Your device or the system enters a power state inconsistent with your wake-up signaling. That is, your device goes to a state less powered than DeviceWake, or the system goes to a state less powered than SystemWake. The bus driver realizes that your wake-up signal can no longer occur, and it completes your WAIT_WAKE with STATUS_INVALID_DEVICE_STATE.
-
You decide to abandon the WAIT_WAKE yourself, so you call IoCancel Irp. The bus driver s cancel routine completes the IRP with STATUS_CANCELLED. You should cancel your WAIT_WAKE (at least) when you process an IRP_MN_STOP_DEVICE, IRP_MN_SUR PRISE_RE MOVAL, or IRP_MN_REMOVE_DEVICE request.
In all of these situations, the I/O Manager calls your I/O completion routine (WaitWakeCompletionRoutine) as a normal part of completing the IRP. In addition, the Power Manager calls your power callback routine (WaitWakeCallback) once the IRP is totally complete.
Your I/O Completion Routine
Cancelling an IRP_MN_WAIT_WAKE exposes the same race condition we discussed in Chapter 5 between your call to IoCancelIrp and the bus driver s call to IoCompleteRequest. To safely cancel the IRP, you can use a variation of the technique I showed you for cancelling asynchronous IRPs. This technique relies on interlocking your cancel logic with your completion routine in a special way, as shown here:
VOID CancelWaitWake(PDEVICE_EXTENSION pdx) { PIRP Irp = (PIRP) InterlockedExchangePointer( (PVOID*) &pdx->WaitWakeIrp, NULL); if (Irp) { IoCancelIrp(Irp); if (InterlockedExchange(&pdx->wwcancelled, 1)) IoCompleteRequest(Irp, IO_NO_INCREMENT); } } NTSTATUS WaitWakeCompletionRoutine(PDEVICE_OBJECT junk, PIRP Irp, PDEVICE_EXTENSION pdx) { if (Irp->PendingReturned) IoMarkIrpPending(Irp); if (InterlockedExchangePointer( (PVOID*) &pdx->WaitWakeIrp, NULL)) return STATUS_SUCCESS; if (InterlockedExchange(&pdx->wwcancelled, 1)) return STATUS_SUCCESS; return STATUS_MORE_PROCESSING_REQUIRED; }
In the example I showed you in Chapter 5, we were dealing with an asynchronous IRP that we had created. We had the responsibility for calling Io FreeIrp to delete the IRP when it was finally complete. In this situation, the Power Manager has created the WAIT_WAKE IRP and will make its own call to IoFreeIrp when the IRP completes. To avoid the cancel/complete race condition, therefore, we need to delay completion until we re past the point where we might want to cancel the request.
The cancel logic depends partly on an undocumented but essential side effect of how PoRequestPowerIrp works internally. Recall that the last argument to PoRequestPowerIrp is the address of a PIRP variable that will receive the address of the IRP that gets created. PoRequestPowerIrp sets that variable before it sends the IRP to the topmost driver. In the example, I used pdx->WaitWakeIrp for that parameter. This is the same data member used in the I/O completion routine and in the CancelWaitWake routine. I m deliberately relying on the fact that PoRequestPowerIrp will set this member before sending the IRP rather than after so that my completion and cancel logic will work correctly, even if invoked before the topmost dispatch routine returns to PoRequestPowerIrp. Note that it would be a mistake to write your code this way:
PIRP foo; status = PoRequestPowerIrp(pdx->Pdo, IRP_MN_WAIT_WAKE, junk, (PREQUEST_POWER_COMPLETE) WaitWakeCallback, pdx, &foo); pdx->WaitWakeIrp = foo; // <== don't do this
This sequence risks setting WaitWakeIrp to the address of a completed IRP. Havoc would occur were you to later try to cancel that IRP.
Your Callback Routine
Your power callback routine for WAIT_WAKE will look something like this one:
VOID WaitWakeCallback(PDEVICE_OBJECT junk, UCHAR MinorFunction, POWER_STATE state, PDEVICE_EXTENSION pdx, PIO_STATUS_BLOCK pstatus) { InterlockedExchange(&pdx->wwoutstanding, 0); if (!NT_SUCCESS(pstatus->Status)) return; else { SendDeviceSetPower(pdx, PowerDeviceD0, FALSE); } }
Here I imagine that you ve written (or copied!) a subroutine named SendDeviceSetPower that will originate a device SET_POWER request. I don t want to show you that function here because it needs to dovetail with your other power logic. There s a routine by that name in GENERIC.SYS or in any driver you build using WDMWIZ, so you can check out my implementation of that function. You call SendDeviceSetPower to put your device into the D0 state to deal with the possibility that the system is already in the working state when your wake-up signal occurs. As I discussed earlier, you need to bring your own device out of its sleep state because you won t get a system SET_POWER in the near future.
A Modest Proposal
System wake-up appears to have been afflicted with very many bugs across the different releases of WDM operating system platforms, different buses, and different chip sets. Here is some anecdotal evidence that wake-up is pretty well broken on platforms earlier than Windows XP:
-
Not long ago, I tested several brands and models of laptop computer, all of them running Windows XP Home or Windows XP Professional, at a superstore before finding one that actually supported system wake-up from a USB device. This feature is supposed to be standardized in the USB specification, and all the machines should have behaved the same way. Not only did just one computer actually work, but it also stopped working when I upgraded from Windows XP Home to Windows XP Professional. My developer contacts at Microsoft said, basically, Gee, that shouldn t have happened.
-
I have an old Advanced Power Management (APM)-based laptop on which I installed Windows 2000 as an upgrade. If I plug in a USB mouse and put the machine into standby, the system hangs trying to resume. This occurs because the USB hub driver forces the system into a power state that the BIOS doesn t actually honor, in order not to dishonor a pending WAIT_WAKE that wouldn t work anyway because the USB controller on the machine doesn t support wake-up signaling. There are no user interface choices that would let me disable the default behavior of issuing the WAIT_WAKE either. Nothing in the DDK suggests cancelling a pending WAIT_WAKE before sending a system power query down the stack, but that s the only action that would actually forestall the incorrect choice of standby state. Of course, it wasn t my driver requesting the wake-up either: it was the MOUCLASS driver crafted by a large software company near Seattle. I m sure that there s plenty of blame to share between the operating system vendor, the laptop vendor, and the various manufacturers of components in the laptop.
-
On the same laptop, Windows 98 Second Edition (the operating system installed by the manufacturer) doesn t hang when resuming from standby. Instead, it (surprise!) removes the driver for the mouse, reenumerates the USB bus, and reloads the same driver. It does the same with any USB device that has wake-up enabled when the machine was put into standby. Not issuing the WAIT_WAKE causes the system to behave sensibly and not reload the driver. Note that if you had an application using the device when the machine went into standby, the surprise removal would orphan the application s handle. I can predict a flurry of very confusing support calls about something like this.
-
On a different machine, a defect in the ACPI description of the machine causes SystemWakeup to be set to PowerSystemWorking, which in turn causes USBHUB to assign a DeviceState mapping of PowerDeviceD2 for PowerSystemWorking to any USB device. In other words, any driver that believes the device capabilities will find it impossible to power the device on since it can apparently never rise above D2, even when the system is working!
-
Windows 98 Second Edition never seems to complete a WAIT_WAKE. Windows Me does, but only long after the wake-up has occurred.
-
Prior to Windows XP, HIDCLASS, the standard driver for human input devices such as mice and keyboards (see Chapter 13) issued its WAIT_WAKE with the silly PowerState value PowerSystemWorking. Taken at face value, this means that the system shouldn t wake from any of the standby states. Obviously, no one lower down the stack is paying any attention because the system manifestly will wake up from lower states. Apropos of this observation is a comment in the DDK toaster sample to the effect that the PowerState parameter of WAIT_WAKE is ignored. So what s a driver writer to do? Set the parameter to PowerSystemWorking, set it to the SystemWake value from the capabilities, or simply ignore it? Anyway, how could you tell that a mistake in this regard affected the behavior of your driver without exhaustively testing on every conceivable permutation of hardware?
Faced with an apparently insoluble puzzle, I have formulated the following bit of advice: Never arm your device s wake-up feature without providing a user interface element (such as the Device Manager s Power Management tab) that gives the user control over whether you re actually going to use the feature. Set the default for this feature to off (that is, no wake-up) in all platforms prior to Windows XP. In Windows 98/Me, you ll need to provide your own user interface for controlling your wake-up feature since the Device Manager doesn t generate the property page even if you support the WMI controls. At the very least, provide for a registry setting that your tech support people know about.
There! I feel much better having gotten that off my chest!
Powering Off When Idle
As a general matter, the end user would prefer that your device not draw any power if it isn t being used. Your driver might use two schemes (at least) to implement such a policy. You can register with the Power Manager to be sent a low-power device IRP when your device remains idle for a specified period. Alternatively, you can decide to keep your device in a low power state if no handles happen to be open.
Powering Off After an Idle Period
The mechanics of the time-based idle detection scheme involve two service functions: PoRegisterDeviceForIdleDetection and PoSetDeviceBusy.
To register for idle detection, make this service function call:
pdx->idlecount = PoRegisterDeviceForIdleDetection(pdx->Pdo, ulConservationTimeout, ulPerformanceTimeout, PowerDeviceD3);
The first argument to PoRegisterDeviceForIdleDetection is the address of the PDO for your device. The second and third arguments specify timeout periods measured in seconds. The conservation period will apply when the system is trying to conserve power, such as when running on battery power. The performance period will apply when the system is trying to maximize performance, such as when running on AC power. The fourth argument specifies the device power state into which you want your device to be forced if it s idle for longer than whichever of the timeout periods applies.
Indicating That You re Not Idle
The return value from PoRegisterDeviceForIdleDetection is the address of a long integer that the system uses as a counter. Every second, the Power Manager increments that integer. If it reaches the appropriate timeout value, the Power Manager sends you a device set-power IRP indicating the power state you registered. At various places in your driver, you ll reset this counter to 0 to restart the idle detection period:
if (pdx->idlecount) PoSetDeviceBusy(pdx->idlecount);
PoSetDeviceBusy is a macro in the WDM.H header file that uncritically dereferences its pointer argument to store a 0. It turns out that PoRegisterDeviceForIdleDetection can return a NULL pointer, so you should check for NULL before calling PoSetDeviceBusy.
Now that I ve described what PoSetDeviceBusy does, you can see that its name is slightly misleading. It doesn t tell the Power Manager that your device is busy, in which case you d expect to have to make another call later to indicate that your device is no longer busy. Rather, it indicates that, at the particular instant you use the macro, your device isn t idle. I m not making this point as a mere semantic quibble. If your device is busy with some sort of active request, you ll want to have logic that forestalls idle detection. So you might want to call PoSetDeviceBusy from many places in your driver: from various dispatch routines, from your StartIo routine, and so on. Basically, you want to make sure that the detection period is longer than the longest time that can elapse between the calls to PoSetDeviceBusy that you make during the normal processing of a request.
NOTE
PoRegisterSystemState allows you to prevent the Power Manager from changing the system power state, but you can t use it to forestall idle timeouts. Besides, it isn t implemented in Windows 98/Me, so calling it is contraindicated for drivers that need to be portable between Windows XP and Windows 98/Me.
Choosing Idle Timeouts
Picking the idle timeout values isn t necessarily simple. Certain kinds of devices can specify -1 to indicate the standard power policy time out for their class of device. At the time of this writing, only FILE_DEVICE_DISK and FILE_DEVICE_MASS_STORAGE devices are in this category. While you ll probably want to have default values for the timeout constants, their values should ultimately be under end user control. Underlying the method by which a user gives you these values is a tale of considerable complexity.
Unless your device is one for which the system designers planned a generic idle detection scheme, you ll need to provide a user-mode component that allows the end user to specify timeout values. To fit in best with the rest of the operating system, that piece should be a property page extension of the Power control panel applet. That is, you should provide a user-mode DLL that implements the IShellPropSheetExt and IShellExtInit COM interfaces. This DLL will fit the general description of a shell extension DLL, which is the topic you ll research if you want to learn all the ins and outs of writing this particular piece of user interface software.
NOTE
Learning about COM in general and shell extension DLLs in particular seems to me like a case of the tail wagging the dog insofar as driver programming goes. You can download a free Visual Studio application wizard from my Web site (http://www.oneysoft.com) and use it to construct a property-page extension DLL for the Power applet in the Control Panel. You could define a private IOCTL interface between your DLL and your driver for specifying idle timeout constants and other policy values. Alternatively, you could define a custom WMI schema that includes idle timeout functionality. As you ll see in Chapter 10, it s exceptionally easy to use WMI from a scripting language.
Restoring Power
If you implement idle detection, you ll also have to provide a way to restore power to your device at some later time for example, when you next receive an IRP that requires power. You ll need some relatively complex coding to make this feature work:
-
Often you receive substantive IRPs in an arbitrary thread or at an elevated interrupt request level (IRQL), so you can t block the dispatch thread while you restore power to the device.
-
Other power or PnP operations can be going on when you receive an IRP that would require you to restore power to your device.
-
You might receive more than one IRP requiring power in quick succession. You want to restore power only once, and you don t want to handle the IRPs out of order.
-
The IRP that triggers you to restore power might be cancelled while you re waiting for power to come back.
I think the best way to handle all of these complications is to always route IRPs that require power through a DEVQUEUE. A skeletal dispatch routine might look like this one:
NTSTATUS DispatchReadWrite(PDEVICE_OBJECT fdo, PIRP Irp) { PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fdo->DeviceExtension; if (pdx->powerstate > PowerDeviceD0) SendDeviceSetPower(pdx, PowerDeviceD0, FALSE); IoMarkIrpPending(Irp); StartPacket(&pdx->dqReadWrite, fdo, Irp, OnCancel); return STATUS_PENDING; }
The idea is to unconditionally queue the IRP after starting a power operation that will finish asynchronously. The power-management code elsewhere in your driver will unstall the queue when power finally comes back, and that will release the IRP.
Powering Off When Handles Close
The other basic strategy for idle power management involves keeping your device in a low power state except while an application has a handle open. Your driver should honor the MSPower_DeviceEnable WMI control to decide whether to implement this strategy and should maintain a persistent registry entry that records the end user s most recent specification of the value of this control. Let s suppose you define a member of your device extension structure to record the value of MSPower_DeviceEnable and another to record the number of open handles:
typedef struct _DEVICE_EXTENSION {
Your IRP_MJ_CREATE and IRP_MJ_CLOSE dispatch functions will do something like this:
NTSTATUS DispatchCreate(PDEVICE_OBJECT fdo, PIRP Irp) { PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fdo->DevicExtension; if (InterlockedIncrement(&pdx->handles) == 1) SendDeviceSetPower(fdo, PowerDeviceD0, TRUE);
These are synchronous calls to SendDeviceSetPower, so we don t have to worry about race conditions between the power-up operation initiated by DispatchCreate and the power-down operation initiated by the matching call to DispatchClose.
When you employ this strategy, you rely on the DEVQUEUE or an equivalent package to stall delivery of substantive IRPs to a StartIo routine while power is off.
NOTE
A USB driver running in Windows XP or later should use the Selective Suspend protocol instead of directly sending a device IRP to idle the device. Refer to Chapter 12 for details about Selective Suspend. The only change to the sample code shown above is that you don t just call SendDeviceSetPower. Instead, you send a special idle registration IRP down the PnP stack with a pointer to a callback routine. The parent driver calls you back when it s time for you to actually put your device into a low power state, whereupon you make the call to SendDeviceSetPower. The WAKEUP sample driver illustrates the necessary mechanics.
Using Sequence Numbers to Optimize State Changes
You might want to use an optimization technique in connection with removing and restoring power to your device. Two background facts will help you make sense of the optimization technique. First, the bus driver doesn t always power down a device, even when it receives a device set-power IRP. This particular bit of intransigence arises because of the way computers are wired together. There might be one or more power channels, and any random collection of devices might be wired to any given channel. These devices are said to share a power relation. A particular device can t be powered down unless all the other devices on the same power channel are powered down as well. So to use the macabre example that I sometimes give my seminar students, suppose the modem you want to power down happens to share a power channel with your computer s heart-lung machine the system can t power down your modem until the bypass operation is over.
The second background fact is that some devices require a great deal of time to change power. To return to the preceding example, suppose your modem were such a device. At some point, you received and passed along a device set-power request to put your modem to sleep. Unbeknownst to you, however, the bus driver didn t actually power down the modem. When the time came to restore power, you could have saved some time if you had known that your modem hadn t lost power. That s where this particular optimization comes into play.
At the time you remove power, you can create and send a power request with the minor function code IRP_MN_POWER_SEQUENCE to the drivers underneath yours. Even though this IRP is technically an IRP_MJ_POWER, you use IoBuildAsynchronousFsdRequest instead of PoRequestPowerIrp to create it. You still use PoStartNext PowerIrp and PoCallDriver when you handle it, though. The request completes after the bus driver stores three sequence numbers in an array you provide. The sequence numbers indicate how many times your device has been put into the D1, D2, and D3 states. When you re later called upon to restore power, you create and send another IRP_MN_POWER_SEQUENCE request to obtain a new set of sequence numbers. If the new set is the same as the set you captured at power-down time, you know that no state change has occurred and that you can bypass whatever expensive process would be required to restore power.
Since IRP_MN_POWER_SEQUENCE simply optimizes a process that will work without the optimization, you needn t use it. Furthermore, the bus driver needn t support it, and you shouldn t treat failure of a power-sequence request as indicative of any sort of error. The GENERIC sample in the companion content actually includes code to use the optimization, but I didn t want to further complicate the textual discussion of the state machine by showing it here.