Managing Power Transitions
Performing power-management tasks correctly requires very accurate coding, and there are many complicating factors. For example, your device might have the ability to wake up the system from a sleeping state. Deciding whether to have a query succeed or fail, and deciding which device power state corresponds to a given new system power state, depend on whether your wake-up feature is currently armed. You may have powered down your own device because of inactivity, and you need to provide for restoring power when a substantive IRP comes along. Maybe your device is an inrush device that needs a large spike of current to power on, in which case the Power Manager treats you specially. And so on.
I regret being unable to offer a simple explanation of how to handle power management in a driver. It seems to me that a feature like this, which every driver must implement for the system to work properly, ought to have a simple explanation that any programmer can understand. But it doesn t. I recommend, therefore, that you simply adopt someone else s proven power-management code wholesale. If you build your driver to use GENERIC.SYS, you can delegate IRP_MJ_POWER requests like this:
NTSTATUS DispatchPower(PDEVICE_OBJECT fdo, PIRP Irp) { PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fdo->DeviceExtension; return GenericDispatchPower(pdx->pgx, Irp); }
GENERIC will then call back to your driver to handle certain device-dependent details. The power callback functions, all of which are entirely optional, are shown in Table 8-3.
Callback Function | Purpose |
FlushPendingIo | Encourage any pending operations to finish. |
GetDevicePowerState | Get the device power state to use for a specified system power state. |
QueryPowerChange | Determine whether a proposed change in device power state will be permissible. |
RestoreDeviceContext | Initiate nonblocking process to prepare device for use following restoration of power. |
SaveDeviceContext | Initiate nonblocking process to prepare device for loss of power. |
If you use my WDMWIZ wizard to generate a skeleton driver that doesn t use GENERIC, you ll get a source file (POWER.CPP) that implements the same power-handling model as GENERIC. Instead of callback routines, however, this code contains several if (FALSE) blocks with TODO comments where you should insert code to do what the GENERIC callbacks do.
In the following sections, I ll describe the requirements for handling IRP_MN_QUERY_POWER and IRP_MN_SET_POWER requests, and I ll also describe the finite state machine I built to carry out those requirements. I m not going to show you all of the code, but I will show you enough fragments to cover the many fine points you must be aware of to understand anybody s code. If you wanted to implement your own power code, you wouldn t have to use a finite state machine as I ve done. You could, for example, use a traditional set of I/O completion routines. I believe, however, that you would pretty much have to arrange those completion routines in such a way that they would actually be equivalent to the state machine I ll describe. That s why I feel justified in describing what might otherwise seem like an idiosyncratic implementation.
Debugging Power Management
Debugging power-management code in a driver is uncommonly hard. The problems start with the fact that many desktop machines don t actually support standby power states. The reason for that isn t hard to understand: most driver programmers are unable to successfully implement power management (it s just too hard!), and their drivers are running on your development machines and getting in the way. Not only do the machines not support the power states you need for testing, but the operating system also silently decides to suppress the power-management choices on the start menu. There s no way to find out why your machine won't support standby or hibernate choices you just find out that it won t.For Windows 98/Me only, Microsoft publishes a tool named PMTSHOOT that will help you find out why your machine doesn t support standby. When I ran this program on one of my machines, I discovered that I needed to disable my network cards never mind how they got a logo if their drivers didn t support power management correctly and to shut down Internet Connection Sharing. Now, really! I think I could just tell the other users on my home office network (that would be me, myself, and I on a typical day) that the Internet will be temporarily inaccessible, couldn t I? This seems to me to be a case in which automation has run rampant into an area better left to humans.
You may have to purchase a recent vintage laptop if you want to do serious power-management testing. This is a lousy alternative if you re dealing with a Peripheral Component Interconnect (PCI) card because laptops typically lack expansion slots. Power management will work differently, and might not work at all, when the laptop is docked. Thus, any expansion capabilities offered by your docking station might still fall short of what you need. Universal serial bus (USB) host controllers vary widely in their support of power management too, so you may find it hard to buy a laptop that lets you test USB devices.
Supposing you manage to find suitable hardware for testing, you ll then run into problems trying to actually debug your driver. On my previous laptop, the serial port was inaccessible to WinDbg and Soft-Ice alike, apparently because of power-management features (!) associated with the infrared port that I didn t want originally and never used. Whenever there was a problem in my driver, it seemed that the serial port, network adapter, and display had all been turned off already, so I couldn t use any of my normal debugging techniques to investigate failures. Writing messages to log files was also useless because of when the file systems decided to shut down. All failures looked alike from this perspective: the system hung going into standby or resuming afterward, and the screen was black. Good luck drawing deductions from that!
(Notwithstanding that the disk system may not be powered up at the times you need to capture debug prints related to power management, the POWTRACE filter driver in the companion content will log to a disk file. Check out POWTRACE.HTM for hints on how to use it.)
I ve finally stumbled into a laborious procedure that nevertheless has let me debug power management in my drivers. I use Soft-Ice, so I m not dependent on having very many peripherals working. The keyboard and the parallel port appear to keep power longer than my own devices I don t know whether this is on purpose or just accidental, but I m grateful that it s true. So I wait for the system to hang, and I press the Print Screen key. If I m lucky, Soft-Ice has gained control because of some bug check or ASSERT, and I can use the printer as a surrogate for the display. I can type a command and do another Print Screen to see the result. And so on. Tedious, but better than trial and error.
A much better solution to the whole problem of power testing and debugging would be a test program that would feed IRPs through a driver to perform a black box test. I ve been lobbying Microsoft to write such a tool for a while now, and I hope that persistence eventually pays off.
Required Infrastructure
Whoever actually processes power requests in your driver needs to maintain several data values in the device extension structure:
typedef struct _DEVICE_EXTENSION { DEVICE_POWER_STATE devpower; SYSTEM_POWER_STATE syspower; BOOLEAN StalledForPower; } DEVICE_EXTENSION, *PDEVICE_EXTENSION;
The value devpower is the current device power state. You probably initialize this to PowerDeviceD0 at AddDevice time.
The value syspower is the current system power state. You always initialize this to PowerSystemWorking at AddDevice time because the computer is self-evidently in the Working state. (You re executing instructions.)
StalledForPower will be TRUE if your power-management code has stalled your substantive IRP queues across a period of low power.
Note that GENERIC.SYS, if you use it, maintains these variables in its own private portion of the device extension structure; you won t need to declare them yourself.
Initial Triage
Your IRP_MJ_POWER dispatch function should initially distinguish between IRP_MN_QUERY_POWER and IRP_MN_SET_POWER requests on the one hand and other minor function codes on the other. In nearly every case, you will pend the query and set requests by calling IoMarkIrpPending and returning STATUS_PENDING. This is how you obey the rule, stated earlier, that you can t block the system thread in which you receive power IRPs. You will also want to examine the stack parameters to distinguish three basic cases:
A system power IRP that increases system power. That is, an IRP for which Parameters.Power.Type is SystemPowerState and for which Parameters.Power.State.SystemState is numerically less than the remembered syspower value. Note that increasing values of the SYSTEM_POWER_STATE enumeration denote less power.
A system power IRP that decreases system power or leaves it the same.
A device power IRP. That is, an IRP for which Parameters.Power.Type is DevicePowerState, regardless of whether it indicates more or less power than your device currently has.
You ll handle power set and query operations in just about the same way up to a certain point, which is why you needn t initially distinguish between them.
Since I m going to be showing you diagrams of my state machine, I have to explain just a bit more terminology. I put the code for the state machine into a subroutine (HandlePowerEvent), the arguments to which are a context structure containing all of the state information about the machine and an event code that indicates what has happened to cause the state machine to be invoked. There are only three other event codes. The dispatch routine uses NewIrp in the first call to the state machine. MainIrpComplete indicates that an I/O completion routine is invoking the state machine to resume processing after a lower-level driver has completed an IRP, and AsyncNotify means that some other asynchronous process has completed. The state machine performs one or more actions based on the state and event codes. The machine initially occupies the InitialState state, and it performs the TriageNewIrp action when called to process a NewIrp event, for example. I ll explain the other states and actions as they come up in the following narrative.
System Power IRPs That Increase Power
If a system power IRP implies an increase in the system power level, you ll forward it immediately to the next lower driver after installing a completion routine. In the completion routine, you ll request the corresponding device power IRP. You ll select the return value from your completion routine as follows:
In Windows 2000 and later systems, if the system power IRP is IRP_MN_SET_POWER for PowerSystemWorking, you ll return STATUS_SUCCESS to allow the Power Manager to immediately send similar IRPs to other drivers. Doing this speeds up restart from suspend by allowing drivers to overlap their device IRP processing.
In every other case, you ll return STATUS_MORE_PRO CESSING_RE QUIRED to defer the completion of the system IRP. You ll complete the IRP after the device IRP completes.
Figure 8-5 diagrams the flow of the IRP through all of the drivers.
Figure 8-5. IRP flow when increasing system power.
Figure 8-6 illustrates the path taken by my finite state machine.
Figure 8-6. State transitions when increasing system power.
TriageNewIrp, as noted earlier, is the first action for every power IRP. It discovers that we re dealing with a system power IRP that increases power, and it therefore arranges to perform the ForwardMainIrp action next.
ForwardMainIrp installs an I/O completion routine and sends the system IRP down the driver stack after changing the machine state to SysPowerUpPending. At this point, the state machine subroutine returns to the IRP_MJ_POWER dispatch routine, which returns STATUS_PENDING.
When the bus driver completes the system power IRP, the I/O completion routine reinvokes the state machine with the MainIrpComplete event code.
The SysPowerUpComplete action first tests the completion status for the IRP. If the bus driver failed the IRP, we arrange to return STATUS_SUCCESS from the completion routine. That allows the IRP to finish completing with an error status. This is also the point in the code where we test to see whether we re dealing with an IRP_MN_SET_POWER for the PowerSystemWorking state in Windows 2000 or later. If so, we ll also allow the IRP to finish completing.
Unless the system IRP has failed in the bus driver, we go on to perform SelectDState to select the device power state that corresponds to this IRP s system power state and to perform SendDeviceIrp to request a device power IRP with the same minor function code. I ll discuss the mechanics of doing both of these actions in detail in a moment. It s possible for the SendDeviceIrp step to fail, in which case we ll want to change the ending status for the system IRP to a failure code and allow that IRP to complete. We then exit the finite state machine, whereupon our I/O completion routine will return whatever status (STATUS_SUCCESS or STATUS_MORE_PRO CESSING_REQUIRED) we ve decided on after putting the state machine into the SubPowerUpPending state.
Time will pass while our own driver handles the device power IRP we ve just requested. Eventually, the Power Manager will call a callback routine in our driver to inform us that the device power IRP has completed. The callback routine in turn reinvokes the state machine with the AsyncNotify event code.
The SubPowerUpComplete action doesn t actually do much in the retail build of my state machine except chain to the CompleteMainIrp event.
CompleteMainIrp arranges to complete the system IRP if we haven t already done that when performing SysPowerUpComplete. Because the state machine has been invoked this time by an asynchronous event instead of an I/O completion routine, we have to actually call IoCompleteRequest. We might, however, be running at DIS PATCH_LEVEL. In Windows 98/Me, we must be at PASSIVE_LEVEL when we complete power IRPs, so we might need to schedule a work item (see Chapter 14) and return without destroying the state machine. The work-item callback routine then reinvokes the state machine at PASSIVE_LEVEL to finish up.
DestroyContext is the last action performed by the state machine for any given power IRP. It releases the context structure that the machinery has been using to keep track of state information.
I want you to notice what the net result of all of this motion has been: we have requested a device power IRP. I don t want to negatively characterize the kernel power-management architecture because I surely lack encyclopedic knowledge of all the constituencies it serves and all the problems it solves. But all this motion does seem a trifle complex given the net result.
Mapping the System State to a Device State
Our obligation as power policy owner for a device is to originate a device power IRP, either a set or a query, with an appropriate device power state. I broke this into two steps: SelectDState and SendDeviceIrp. I ll discuss the first of these steps now.
In general, we always want to put our device into the lowest power state that s consistent with current device activity, with our own wake-up feature (if any), with device capabilities, and with the impending state of the system. These factors can interplay in a relatively complex way. To explain them fully, I need to digress briefly and talk about a PnP IRP that I avoided discussing in Chapter 6: IRP_MN_QUERY_CAPABILITIES.
The PnP Manager sends a capabilities query shortly after starting your device and perhaps at other times. The parameter for the request is a DEVICE_CAPABILITIES structure that contains several fields relevant to power management. Since this is the only time in this book I m going to discuss this structure, I m showing you the entire declaration:
typedef struct _DEVICE_CAPABILITIES { USHORT Size; USHORT Version; ULONG DeviceD1:1; ULONG DeviceD2:1; ULONG LockSupported:1; ULONG EjectSupported:1; ULONG Removable:1; ULONG DockDevice:1; ULONG UniqueID:1; ULONG SilentInstall:1; ULONG RawDeviceOK:1; ULONG SurpriseRemovalOK:1; ULONG WakeFromD0:1; ULONG WakeFromD1:1; ULONG WakeFromD2:1; ULONG WakeFromD3:1; ULONG HardwareDisabled:1; ULONG NonDynamic:1; ULONG Reserved:16; ULONG Address; ULONG UINumber; DEVICE_POWER_STATE DeviceState[PowerSystemMaximum]; SYSTEM_POWER_STATE SystemWake; DEVICE_POWER_STATE DeviceWake; ULONG D1Latency; ULONG D2Latency; ULONG D3Latency; } DEVICE_CAPABILITIES, *PDEVICE_CAPABILITIES;
Table 8-4 describes the fields in this structure that relate to power management.
Field | Description |
DeviceState | Array of highest device states possible for each system state |
SystemWake | Lowest system power state from which the device can generate a wake-up signal for the system PowerSystemUnspecified indicates that the device can t wake up the system |
DeviceWake | Lowest power state from which the device can generate a wake-up signal PowerDeviceUnspecified indicates that the device can t generate a wake-up signal |
D1Latency | Approximate worst-case time (in 100-microsecond units) required for the device to switch from D1 to D0 state |
D2Latency | Approximate worst-case time (in 100-microsecond units) required for the device to switch from D2 to D0 state |
D3Latency | Approximate worst-case time (in 100-microsecond units) required for the device to switch from D3 to D0 state |
WakeFromD0 | Flag indicating whether the device s system wake-up feature is operative when the device is in the indicated state |
WakeFromD1 | Same as above |
WakeFromD2 | Same as above |
WakeFromD3 | Same as above |
You normally handle the query capabilities IRP synchronously by passing it down and waiting for the lower layers to complete it. After the pass-down, you ll make any desired changes to the capabilities recorded by the bus driver. Your subdispatch routine will look like this one:
NTSTATUS HandleQueryCapabilities(IN PDEVICE_OBJECT fdo, IN PIRP Irp) { PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp); PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fdo->DeviceExtension; PDEVICE_CAPABILITIES pdc = stack-> Parameters.DeviceCapabilities.Capabilities; if (pdc->Version < 1) return DefaultPnpHandler(fdo, Irp); <stuff> NTSTATUS status = ForwardAndWait(fdo, Irp); if (NT_SUCCESS(status)) { stack = IoGetCurrentIrpStackLocation(Irp); pdc = stack->Parameters.DeviceCapabilities.Capabilities; <stuff> AdjustDeviceCapabilities(pdx, pdc); pdx->devcaps = *pdc; } return CompleteRequest(Irp, status); }
The device capabilities structure has a version number member, which is currently always equal to 1. The structure is designed to always be upward compatible, so you ll be able to work with the version defined in the DDK that you build your driver with and with any later incarnation of the structure. If, however, you re confronted with a structure that s older than you re able to work with, you should just ignore this IRP by passing it along.
Here s where the DDK says you add capabilities.
Here s where the DDK says you should remove capabilities set by the bus driver.
Depending on which platform you re running on and which bus your device is connected to, the bus driver may have incorrectly completed the power-related portions of the capabilities structure. AdjustDeviceCapabilities compensates for that shortcoming.
It s a good idea to make a copy of the capabilities structure. You ll use the DeviceState map when you receive a system power IRP. You might have occasion to consult other fields in the structure too.
It s just not clear what the distinction is between adding and removing capabilities, unfortunately, and certain bus drivers have had bugs that cause them to erase whatever changes you make in the capabilities structure as the IRP travels down the stack. My advice, therefore, is to make your changes at both points 2 and 3 in the preceding code snippet that is, both before and after passing the IRP down.
You can modify SystemWake and DeviceWake to specify a higher power state than the bus driver thought was appropriate. You can t specify a lower power state for the wake-up fields, and you can t override the bus driver s decision that your device is incapable of waking the system. If your device is ACPI-compliant, the ACPI filter will set the LockSupported, EjectSupported, and Removable flags automatically based on the ACPI Source Language (ASL) description of the device you won t need to worry about these capabilities.
You might want to set the SurpriseRemovalOK flag. Setting the flag suppresses the dialog box that earlier versions of Windows presented when they detected the sudden and unexpected removal of a device. It s normally OK for the end user to remove a USB or 1394 device without first telling the system, and the function driver should set this flag to avoid annoying the user.
As noted (point 4), some bus drivers don t correctly set the power-related fields in the capabilities structure, so it s up to you to tweak the structure somewhat. My AdjustDeviceCapabilities function, which is based on a talk given at WinHEC 2002 and on an example in the DDK TOASTER sample, does the following:
Examines the DeviceState map in the capabilities structure. If D1 or D2 appears in that map, we can infer that the DeviceD1 and DeviceD2 flags ought to have been set.
Uses the reported DeviceWake value, and the DeviceState value corresponding to the reported SystemWake value, to infer the settings of the WakeFromDx flags and the DeviceD1 or DeviceD2 flag.
Infers SystemWake from the fact that some entry in the DeviceState map must allow the device to be at least as powered as the lowest D-state from which wake-up is possible.
To return to our discussion of how to select a power state, GENERIC will calculate minimum and maximum values and choose the lower of the two. The minimum is D3 unless you have an enabled wake-up feature and the system is in a state from which your device can wake it, in which case the minimum is the remembered DeviceWake capability. The maximum is the remembered DeviceState capability for the current system state. Then GENERIC calls your GetDevicePowerState callback, if you have one, to allow you to override the selection by picking a higher state. You can decide, for example, to put the device into D0 when system power is restored, but only if an application has a handle open to your device. For example:
DEVICE_POWER_STATE GetDevicePowerState(PDEVICE_OBJECT fdo, SYSTEM_POWER_STATE sstate, DEVICE_POWER_STATE dstate) { PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fdo->DeviceExtension; if (sstate > SystemPowerWorking !pdx->handles) return dstate; return PowerDeviceD0; }
Requesting a Device Power IRP
To submit an IRP_MN_SET_POWER or IRP_MN_QUERY_POWER request, you call the special function shown here:
PSOMETHING_OR_ANOTHER ctx; POWER_STATE devstate; devstate.DeviceState = PowerDeviceDx; NTSTATUS postatus = PoRequestPowerIrp(pdx->Pdo, IRP_MN_XXX_POWER, devstate, (PREQUEST_POWER_COMPLETE) PoCallbackRoutine, ctx, NULL);
The first argument to PoRequestPowerIrp is the address of the physical device object (PDO) at the bottom of your device s PnP stack. The second argument is the minor function code for the IRP we want to submit. Insofar as concerns us right now, this would be the same minor function code as the system power IRP that we happen to be processing. That is, it s either IRP_MN_QUERY_POWER or IRP_MN_SET_POWER. The third argument is a power state derived as described in the preceding section. PoCallbackRoutine is a callback routine (not a standard I/O completion routine), and ctx is a context parameter for that function. The final argument (NULL in this example) is the address of a variable where PoRequestPowerIrp will store the address of the IRP it creates. You don t use this particular capability for SET and QUERY requests.
PoRequestPowerIrp creates a device power IRP of the specified type and with the specified power level, and sends it to the topmost driver in your PnP stack. If PoRequestPowerIrp returns with STATUS_PENDING, you can infer that it actually created and sent the IRP. The Power Manager will then eventually call your callback routine. If PoRequestPowerIrp returns anything except STATUS_PENDING, however, the Power Manager will not call your callback routine. This possibility is why the SendDeviceIrp action in my finite state machine might exit through the CompleteMainIrp action in order to complete the system IRP.
You mustn t request a device power IRP if your device is already in the state you re requesting. There is a bug in Windows 98/Me such that PoRequestPowerIrp will appear to succeed in this case, but the CONFIGMG driver won t actually send a configuration event to NTKERN. In this situation, your power code will deadlock waiting for a call to your PoCallbackRoutine that will never happen. It has also been my experience that Windows 2000 and Windows XP will hang while resuming from standby if you ask to set the state that currently obtains.
System Power IRPs That Decrease Power
If the system power IRP implies no change or a reduction in the system power level, you ll request a device power IRP with the same minor function code (set or query) and a device power state that corresponds to the system state. When the device power IRP completes, you ll forward the system power IRP to the next lower driver. You ll need a completion routine for the system power IRP so that you can make the requisite call to PoStartNextPowerIrp and so that you can perform some additional cleanup. See Figure 8-7 for an illustration of how the IRPs flow through the system in this case.
It s not strictly necessary to issue the device-dependent power request as the system IRP is traveling down the stack. That is, we can issue the request from the I/O completion routine for the system power IRP, just as we did in the case of a system power IRP that increases power (studied earlier). In fact, the DDK says to do exactly this. Performing the steps in the order I suggest has the virtue of having been tested and proven to work in many drivers on many WDM platforms since the first edition was published, however. Here, therefore, I follow the admonition If it ain t broke, don t fix it.
Figure 8-7. IRP flow when decreasing system power.
Figure 8-8 diagrams how my finite state machine handles this type of IRP. TriageNewIrp puts the state machine into the SubPowerDownPending state and jumps to the SelectDState action. You already saw that SelectDState selects a device power state and leads to a SendDeviceIrp action to request a device power IRP. In the system power-down scenario, we ll be specifying a lower power state in this device IRP.
Figure 8-8. State transitions when decreasing system power.
Device Power IRPs
All we actually do with system power IRPs is act as a conduit for them and request a device IRP either as the system IRP travels down the driver stack or as it travels back up. We have more work to do with device power IRPs, however.
To begin with, we don t want our device occupied by any substantive I/O operations while a change in the device power state is under way. As early as we can in a sequence that leads to powering down our device, therefore, we wait for any outstanding operation to finish, and we stop processing new operations. Since we re not allowed to block the system thread in which we receive power IRPs, an asynchronous mechanism is required. Once the current IRP finishes, we ll continue processing the device IRP. Each of the next four state diagrams (Figures 8-11 through 8-14), therefore, begins with the same sequence. TriageNewIrp tests the StalledForPower flag to see whether the substantive IRP queues have already been stalled for a power operation. If not, GENERIC does two things:
It calls the DEVQUEUE routine named StallAllRequestsAndNotify. That routine stalls all your substantive IRP queues and returns an indication of whether your device is currently busy servicing an IRP from one of them. In the latter case, GENERIC will end up deferring further processing of the IRP until someone calls StartNextPacket for each currently busy queue.
It calls your FlushPendingIo callback routine if you have specified one. This function solves a problem reported by a reader of the first edition, as follows: Your StartIo routine might have started a time-consuming operation that won t finish of its own accord. For example, you might have repackaged the IRP as an IRP_MJ_INTER NAL_DEVICE_CONTROL and sent it to the USB bus driver, and you re planning to call StartNextPacket from a completion routine when the USB device completes the repackaged IRP. This won t necessarily happen soon without some sort of encouragement (which, in this case, would be a pipe abort command) that your callback can supply.
If the device power IRP implies an increase in the device power level, we ll forward it to the next lower driver. Refer to Figure 8-9 for an illustration of how the IRP flows through the system. The bus driver will process a device set-power IRP by, for example, using whatever bus-specific mechanism is appropriate to turn on the flow of electrons to your device, and it will complete the IRP. Your completion routine will initiate the operations required to restore context information to the device, and it will return STATUS_MORE_PRO CESSING_REQUIRED to interrupt the completion process for the device IRP. When the context-restore operation finishes, you ll resume processing substantive IRPs and finish completing the device IRP.
Figure 8-9. IRP flow when increasing device power.
If the device power IRP implies no change or a reduction in the device power level, you perform any device-specific processing (asynchronously, as we ve discussed) and then forward the device IRP to the next lower driver. See Figure 8-10. The device-specific processing for a query operation involves stalling your queues and waiting for all current substantive IRPs to finish. The device-specific processing for a set operation includes saving device context information, if any, in memory so that you can restore it later. The bus driver completes the request. In the case of a query operation, you can expect the bus driver to complete the request with STATUS_SUCCESS to indicate acquiescence in the proposed power change. In the case of a set operation, you can expect the bus driver to take the bus-dependent steps required to put your device into the specified device power state. Your completion routine cleans up by calling PoStartNextPowerIrp, among other things.
Figure 8-10. IRP flow when decreasing device power.
Setting a Higher Device Power State
Figure 8-11 diagrams the state transitions that occur for an IRP_MN_SET_POWER that specifies a higher device power state than that which is current.
Figure 8-11. State transitions when setting a higher device power state.
The state transitions and actions are as follows:
TriageNewIrp makes sure that your substantive IRP queues are stalled. QueueStallComplete picks up the processing of the device power IRP once this is accomplished.
ForwardMainIrp sends the device IRP down the PnP stack. The bus driver turns on the flow of current to the device and completes the IRP.
When the device IRP completes, our completion routine reinvokes the state machine to perform the DevPowerUpComplete action. If the device IRP failed (I ve never seen this happen, by the way), we ll exit via CompleteMainIrp.
At this point, GENERIC will call your RestoreDeviceContext callback routine, if any, to allow you to initiate a nonblocking process to prepare your device for operation in the new, higher power state. I ll discuss this aspect of the processing in more detail shortly.
When the context-restore operation finishes (or immediately if there s no RestoreDeviceCallback), ContextRestoreComplete unstalls the substantive IRP queues (which have presumably been stalled while power was off) and hands off control to CompleteMainIrp.
CompleteMainIrp arranges to complete the device IRP. We sometimes get here from the I/O completion routine we ve installed for the device IRP, in which case we only need to return STATUS_SUCCESS to allow the completion process to continue. In other cases, our I/O completion routine long ago returned STATUS_MORE_PRO CESSING_REQUIRED, and we need to call IoCompleteRequest to resume the completion process. In either case, since we re usually processing a device IRP that we requested while handling a system IRP, the next thing that will happen is that the Power Manager will call our PoCompletionRoutine to indicate that the device IRP is truly finished. We then go on to destroy this instance of the state machine, and another (earlier) instance picks up its own processing of a system IRP.
The RestoreDeviceContext callback is an important part of how GENERIC helps you manage power for your device. As indicated, you have the opportunity in this routine to initiate any sort of nonblocking process that you need to perform before your device will be ready to operate with the new, higher power state. When GENERIC calls this routine, the bus driver has already restored power to your device. The function has this skeleton:
VOID RestoreDeviceContext(PDEVICE_OBJECT fdo, DEVICE_POWER_STATE oldstate, DEVICE_POWER_STATE newstate, PVOID context) { }
Here oldstate and newstate are the previous and new states of your device, and context is an opaque parameter that you ll supply in an eventual call to GenericSaveRestoreComplete. Inside this function, you can perform any nonblocking activity needed to prepare your device. That is, you can read and write hardware registers or call any other kernel routine that doesn t block the current thread. You can t do something such as send a synchronous IRP to another driver because you would need to block the current thread until that IRP completes. You can, however, send asynchronous IRPs to other drivers. When your device is completely ready to go, make the following call back to GENERIC:
GenericSaveRestoreComplete(context);
where context is the context parameter you got in your RestoreDeviceContext call. GENERIC will then resume handling the device power IRP as previously discussed. Note that you can call GenericSaveRestoreComplete from within your RestoreDeviceContext function if you ve finished all desired power-up operations.
NOTE
For whatever reason, I ve written many drivers for SmartCard readers. My RestoreDeviceContext function for these drivers completes any outstanding card-absent tracking IRP (a requirement for safely dealing with the possibility that someone has replaced a card while power was off). In addition, with devices that require continuous polling to detect card insertion and removal events, I restart the polling thread.
Note that you don t need to supply a RestoreDeviceContext function if there s no work to do at power-up time.
The net result of all the motion for a device set higher power request is that the bus driver repowers our device and we thereafter prepare it for reuse. This is still a lot of motion, but at least something useful comes from it.
Querying for a Higher Device Power State
You shouldn t expect to receive an IRP_MN_QUERY_POWER that refers to a higher power state than your device is already in, but you shouldn t crash the system if you do happen to receive one. Figure 8-12 illustrates the state changes my finite state machine goes through in such a case. The machine simply stalls the IRP queues if they didn t happen to have been stalled when power was removed earlier.
Figure 8-12. State transitions for a query about a higher device power state.
Setting a Lower Device Power State
If the IRP is an IRP_MN_SET_POWER for the same or a lower device power state than current, the finite state machine goes through the state transitions diagrammed in Figure 8-13.
Figure 8-13. State transitions when setting a lower device power state.
The actions and state transitions in this case are as follows:
TriageNewIrp makes sure that your substantive IRP queues are stalled. QueueStallComplete picks up the processing of the device power IRP once this is accomplished.
At this point, GENERIC will call your SaveDeviceContext callback routine, if any, to allow you to initiate a nonblocking process to prepare your device for operation in the new, lower power state. I ll discuss this aspect of the processing in more detail shortly.
When the context-save operation finishes (or immediately if there s no SaveDeviceCallback), ContextSaveComplete hands off control to ForwardMainIrp.
ForwardMainIrp sends the device IRP down the PnP stack. The bus driver turns off the flow of current to the device and completes the IRP.
When the device IRP completes, our completion routine reinvokes the state machine to perform the CompleteMainIrp action.
CompleteMainIrp arranges to complete the device IRP. We always get here from the I/O completion routine we ve installed for the device IRP, so we only need to return STATUS_SUCCESS to allow the completion process to continue. Since we re usually processing a device IRP that we requested while handling a system IRP, the next thing that will happen is that the Power Manager will call our PoComple tion Routine to indicate that the device IRP is truly finished. We then go on to destroy this instance of the state machine, and another (earlier) instance picks up its own processing of a system IRP.
GENERIC s context-save protocol is exactly complementary to the context-restore protocol discussed previously. If you ve supplied a SaveDeviceContext function, GENERIC will call it:
VOID SaveDeviceContext(PDEVICE_OBJECT fdo, DEVICE_POWER_STATE oldstate, DEVICE_POWER_STATE newstate, PVOID context) { }
You initiate any desired nonblocking operation to prepare your device for low power operation, and you call GenericSaveRestoreComplete when you finish. GENERIC then resumes handling the device power IRP as just described. Note that your device still has power at the time GENERIC calls your callback routine.
You don t need to supply a SaveDeviceContext function if there s no work to do at power-down time.
NOTE
To finish the story about my SmartCard drivers, I use SaveDeviceContext to halt any polling thread that I might have for detecting card insertions and removals. Since this operation requires blocking the current thread until the polling thread exits, I ordinarily need to schedule a work item that can block a different system thread, wait for the polling thread to terminate, and then call GenericSaveRestoreComplete.
Querying for a Lower Device Power State
An IRP_MN_QUERY_POWER that specifies the same or a lower device power state than current is the basic vehicle by which a function driver gets to vote on changes in power levels. Figure 8-14 shows how my state machine handles such a query.
TriageNewIrp makes sure that your substantive IRP queues are stalled. QueueStallComplete picks up the processing of the device power IRP once this is accomplished.
At this point (DevQueryDown), GENERIC will call your QueryPower callback routine, if any, to allow you to decide whether to accept the proposed change. If your function returns FALSE, GENERIC then short-circuits around a couple of actions to DevQueryDownComplete and thence to CompleteMainIrp.
ForwardMainIrp sends the device IRP down the PnP stack. The bus driver usually just completes the IRP with a success status.
When the device IRP completes, our completion routine reinvokes the state machine to perform the DevQueryDownComplete action. If the query has failed, we ll unstall our queues just in case we don t later get a set-power IRP to make us do that.
CompleteMainIrp arranges to complete the device IRP. Since we re usually processing a device IRP that we requested while handling a system IRP, the next thing that will happen is that the Power Manager will call our PoCompletionRoutine to indicate that the device IRP is truly finished. We then go on to destroy this instance of the state machine, and another (earlier) instance picks up its own processing of a system IRP.
The net effect of these actions is to stall our substantive IRP queues if the query succeeds.
Figure 8-14. State transitions for a query about a lower device power state.