[Previous] [
Performing power management
When I thought about solving all the problems of handling query-power and set-power operations in a traditional way—that is, with normal-looking dispatch and completion routines—I was daunted by the sheer number of different subroutines that would be required and that would end up doing
I'll explain this finite state machine as it appears in GENERIC.SYS, which is a support driver that most of the code samples on the companion disc use. Appendix B, "Using GENERIC.SYS," explains the client interface to GENERIC.SYS in complete detail. GENERIC.SYS amounts to a kernel-mode DLL containing helper functions for WDM drivers. You could think of it as a generic class driver with broad applicability. Client drivers, including most of my own sample drivers, delegate handling of power IRPs to GENERIC.SYS by calling GenericDispatchPower . GENERIC.SYS also implements the DEVQUEUE object I discussed in Chapter 6, "Plug and Play."
I wrote a function named
HandlePowerEvent
to implement the finite state machine that
NTSTATUSHandlePowerEvent(PPOWCONTEXTctx,enumPOWEVENTevent); |
The first argument is a context structure that contains a state variable, among other things:
typedefstruct_POWCONTEXT{
LONGid;
LONGeventcount;
PGENERIC_EXTENSIONpdx;
PIRPirp;
enumPOWSTATEstate;
NTSTATUSstatus;
PKEVENTpev;
DEVICE_POWER_STATEdevstate;
UCHARMinorFunction;
BOOLEANUnstallQueue;
}POWCONTEXT,*PPOWCONTEXT;
|
The
id
and
eventcount
fields are for debugging. If you compile POWER.CPP in the GENERIC project with the preprocessor macro VERBOSETRACE defined as a nonzero value, the POWTRACE macro will produce
The
pdx
member points to GENERIC's portion of the device extension for a given device. There are just a couple of
The second argument to HandlePowerEvent is an event code that indicates why we're calling the function. There are just these few event codes:
HandlePowerEvent uses the value of the state variable and the event code to determine an action to take. See Table 8-3. (In the table, by the way, an empty
Table 8-3. Table giving initial action for each event and state.
| State | Event | ||
|---|---|---|---|
| NewIrp | MainIrpComplete | AsyncNotify | |
| InitialState | TriageNewIrp | ||
| SysPowerUpPending | SysPowerUpComplete | ||
| SubPowerUpPending | SubPowerUpComplete | ||
| SubPowerDownPending | SubPowerDownComplete | ||
| SysPowerDownPending | SysPowerDownComplete | ||
| DevPowerUpPending | DevPowerUpComplete | ||
| DevPowerDownPending | CompleteMainIrp | ||
| ContextSavePending | ContextSaveComplete | ||
| ContextRestorePending | ContextRestoreComplete | ||
| DevQueryUpPending | DevQueryUpComplete | ||
| DevQueryDownPending | DevQueryDownComplete | ||
| QueueStallPending | QueueStallComplete | ||
| FinalState |
Since many of the events require multiple actions in some situations, I coded
HandlePowerEvent
in what may seem at first like a peculiar way, as
NTSTATUSHandlePowerEvent(...)
{
NTSTATUSstatus;
POWACTIONaction=...;
while(TRUE)
{
switch(action)
{
case
<someaction>
:
action=
<someotheraction>
;
continue;
case
<anotheraction>
:
break;
}
break;
}
returnstatus;
}
|
That is, the function amounts to a
switch
on the
action
code
I adopted this coding style for the state machine because I really took to heart the structured programming precepts I learned in my youth. I wanted there to be just one return statement in this whole function to make it easier to
When we receive a new query-power or set-power IRP, we create a context structure to drive the finite state machine and call HandlePowerEvent:
1 |
NTSTATUSGenericDispatchPower(PGENERIC_EXTENSIONpdx,PIRPIrp)
{
NTSTATUSstatus=IoAcquireRemoveLock(pdx->RemoveLock,Irp);
if(!NT_SUCCESS(status))
returnCompleteRequest(Irp,status);
PIO_STACK_LOCATIONstack=IoGetCurrentIrpStackLocation(Irp);
ULONGfcn=stack->MinorFunction;
if(fcn==IRP_MN_SET_POWERfcn==IRP_MN_QUERY_POWER)
{
PPOWCONTEXTctx=(PPOWCONTEXT)ExAllocatePool(NonPagedPool,
sizeof(POWCONTEXT));
RtlZeroMemory(ctx,sizeof(POWCONTEXT));
ctx->pdx=pdx;
ctx->irp=Irp;
status=HandlePowerEvent(ctx,NewIrp);
}
IoReleaseRemoveLock(pdx->RemoveLock,Irp);
returnstatus;
}
|
The initial state of the finite state machine is InitialState. When we call HandlePowerEvent for the NewIrp event, the first action taken will be the following, which I named TriageNewIrp :
1 |
caseTriageNewIrp:
{
status=STATUS_PENDING;
IoMarkIrpPending(Irp);
IoAcquireRemoveLock(pdx->RemoveLock,Irp);
if(stack->Parameters.Power.Type==SystemPowerState)
{//systemIRP
if(stack->Parameters.Power.State.SystemState<pdx->syspower)
{
action=ForwardMainIrp;
ctx->state=SysPowerUpPending;
}
else
{
action=SelectDState;
ctx->state=SubPowerDownPending;
}
}//systemIRP
else
{//deviceIRP
ctx->state=QueueStallPending;
if(!pdx->StalledForPower)
{
ctx->UnstallQueue=TRUE;
pdx->StalledForPower=TRUE;
NTSTATUSqstatus=StallRequestsAndNotify(pdx->dqReadWrite,
GenericSaveRestoreComplete,ctx);
if(qstatus==STATUS_PENDING)
break;
}
action=QueueStallComplete;
}//deviceIRP
continue;
}
|
Basically, TriageNewIrp is distinguishing between system power IRPs (that is, IRPs whose
Type
is
SystemPowerState
) that increase the power level, system power IRPs that leave the power level alone or reduce it, and device power IRPs (that is, IRPs whose
Type
is
DevicePowerState
), regardless of whether they raise or lower the power level. The state machine doesn't distinguish at this stage between QUERY_POWER and SET_POWER
For us to know whether power is
typedefstruct_GENERIC_EXTENSION{
...
DEVICE_POWER_STATEdevpower;//currentdevpowerstate
SYSTEM_POWER_STATEsyspower;//currentsyspowerstate
}GENERIC_EXTENSION,*PGENERIC_EXTENSION;
|
We initialize these values to PowerDeviceD0 and PowerSystemWorking , respectively, when the client driver first registers with GENERIC.SYS.
You can guess from context that the device extension also has a BOOLEAN member named
StalledForPower
. This flag, when set, indicates that the substantive IRP queue is presently stalled for purposes of power management. Incidentally, you'll notice (if you've got the right
I'll discuss the three initial categories of IRPs separately now.
If a system power IRP implies an increase in the system power level, you'll forward it immediately to the next lower driver. In your completion routine for the system power IRP, you'll request the corresponding device power IRP and return STATUS_MORE_PROCESSING_REQUIRED to temporarily halt the completion process. In a completion routine for the device power IRP, you'll finish the completion processing for the system power IRP. Figure 8-5 diagrams the flow of the IRP through all of the drivers. Figure 8-6 is a state diagram that shows how our finite state machine handles the IRP.
Figure 8-5. IRP flow when increasing system power.
Figure 8-6. State transitions when increasing system power.
In terms of how the code works, I showed you earlier that TriageNewIrp puts the machine into the SysPowerUpPending state and requests the ForwardMainIrp action, which is as follows:
caseForwardMainIrp:
{
IoCopyCurrentIrpStackLocationToNext(Irp);
IoSetCompletionRoutine(Irp,(PIO_COMPLETION_ROUTINE)
MainCompletionRoutine,(PVOID)ctx,TRUE,TRUE,TRUE);
PoCallDriver(pdx->LowerDeviceObject,Irp);
break;
}
|
HandlePowerEvent will now return STATUS_PENDING, as
Our next contact with this IRP is when the bus driver completes it. Our own MainCompletionRoutine gets control as part of the completion process, saves the IRP's ending status in the context structure's status field, and invokes the finite state machine:
NTSTATUSMainCompletionRoutine(PDEVICE_OBJECTjunk,PIRPIrp,
PPOWCONTEXTctx)
{
ctx->status=Irp->IoStatus.Status;
returnHandlePowerEvent(ctx,MainIrpComplete);
}
|
Our initial action will be SysPowerUpComplete :
1 |
caseSysPowerUpComplete:
{
if(!NT_SUCCESS(ctx->status))
action=CompleteMainIrp;
else
{
if(stack->MinorFunction==IRP_MN_SET_POWER)
pdx->syspower=stack->Parameters.Power.State.SystemState;
action=SelectDState;
ctx->state=SubPowerUpPending;
status=STATUS_MORE_PROCESSING_REQUIRED;
}
continue;
}
|
If the IRP failed, you can see that we'll do the CompleteMainIrp action next:
1 |
caseCompleteMainIrp:
{
PoStartNextPowerIrp(Irp);
if(event==MainIrpComplete)
status=ctx->status;
else
{
Irp->IoStatus.Status=ctx->status;
IoCompleteRequest(Irp,IO_NO_INCREMENT);
}
IoReleaseRemoveLock(pdx->RemoveLock,Irp);
if(ctx->UnstallQueue)
{
pdx->StalledForPower=FALSE;
RestartRequests(pdx->dqReadWrite,pdx->DeviceObject);
}
action=DestroyContext;
continue;
}
|
When handling a system power IRP that
1 |
caseDestroyContext:
{
if(ctx->pev)
KeSetEvent(ctx->pev,IO_NO_INCREMENT,FALSE);
else
ExFreePool(ctx);
break;
}
|
DestroyContext is, of course, the last action the finite state machine ever performs.
The other possible path out of SysPowerUpComplete generates a device power IRP with a power state that corresponds to the system power state. We perform the mapping of system to device states in the SelectDState action:
caseSelectDState:
{
SYSTEM_POWER_STATEsysstate=
stack->Parameters.Power.State.SystemState;
if(sysstate==PowerSystemWorking)
ctx->devstate=PowerDeviceD0;
else
{
DEVICE_POWER_STATEmaxstate=
pdx->devcaps.DeviceState[sysstate];
DEVICE_POWER_STATEminstate=pdx->WakeupEnabled?
pdx->devcaps.DeviceWake:PowerDeviceD3;
ctx->devstate=minstate>maxstate?minstate:maxsstate;
}
ctx->MinorFunction=stack->MinorFunction;
action=SendDeviceIrp;
continue;
}
|
By the way, the Power Manager never transitions directly from one low system power state to another: it always moves via PowerSystemWorking. That's why I coded SelectDState to choose one mapping for PowerSystemWorking and a different mapping for all other system power states.
In general, we always want to put our device into the
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:
typedefstruct_DEVICE_CAPABILITIES{
USHORTSize;
USHORTVersion;
ULONGDeviceD1:1;
ULONGDeviceD2:1;
ULONGLockSupported:1;
ULONGEjectSupported:1;
ULONGRemovable:1;
ULONGDockDevice:1;
ULONGUniqueID:1;
ULONGSilentInstall:1;
ULONGRawDeviceOK:1;
ULONGSurpriseRemovalOK:1;
ULONGWakeFromD0:1;
ULONGWakeFromD1:1;
ULONGWakeFromD2:1;
ULONGWakeFromD3:1;
ULONGHardwareDisabled:1;
ULONGNonDynamic:1;
ULONGReserved:16;
ULONGAddress;
ULONGUINumber;
DEVICE_POWER_STATEDeviceState[PowerSystemMaximum];
SYSTEM_POWER_STATESystemWake;
DEVICE_POWER_STATEDeviceWake;
ULONGD1Latency;
ULONGD2Latency;
ULONGD3Latency;
}DEVICE_CAPABILITIES,*PDEVICE_CAPABILITIES;
|
Table 8-4 describes the fields in this structure that relate to power management.
Table 8-4. Power-management fields in DEVICE_CAPABILITIES structure.
| 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 device can't wake up the system |
| DeviceWake | Lowest power state from which the device can generate a wake-up signal—PowerDeviceUnspecified indicates that device can't generate a wake-up signal |
| D1Latency | Approximate worst-case time (in 100-microsecond units) required for device to switch from D1 to D0 states |
| D2Latency | Approximate worst-case time (in 100-microsecond units) required for device to switch from D2 to D0 states |
| D3Latency | Approximate worst-case time (in 100-microsecond units) required for device to switch from D3 to D0 states |
| WakeFromD0 |
Flag indicating whether device's system wake-up feature is
|
| 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
1 |
NTSTATUSHandleQueryCapabilities(INPDEVICE_OBJECTfdo,
INPIRPIrp)
{
PIO_STACK_LOCATIONstack=IoGetCurrentIrpStackLocation(Irp);
PDEVICE_EXTENSIONpdx=(PDEVICE_EXTENSION)fdo->DeviceExtension;
PDEVICE_CAPABILITIESpdc=stack->
Parameters.DeviceCapabilities.Capabilities;
if(pdc->Version<1)
returnDefaultPnpHandler(fdo,Irp);
NTSTATUSstatus=ForwardAndWait(fdo,Irp);
if(NT_SUCCESS(status))
{
stack=IoGetCurrentIrpStackLocation(Irp);
pdc=stack->Parameters.DeviceCapabilities.Capabilities;
<stuff>
pdx->devcaps=*pdc;
}
returnCompleteRequest(Irp,status);
}
|
Don't bother altering the characteristics structure before you pass this IRP down: the bus driver will completely
You might want to set the
SurpriseRemovalOK
flag at point "2" in the capabilities handler. Setting the flag suppresses the dialog box that Windows 2000 normally
To return to our discussion of SelectDState, suppose we're dealing with a set-power request that will take the computer from Working to Sleeping1; we'll therefore execute the second branch of the
if
statement in SelectDState. Let's suppose that the bus driver
Our device might also have a wake-up feature. I'll say more about wake-up later on. If so, the bus driver will have set the DeviceWake member of the capabilities structure equal to the lowest power state from which wake-up can occur. Let's suppose that value is PowerDeviceD1 . If our wake-up feature happens to be enabled right now, we'll set minstate to PowerDeviceD1.
If we don't have a wake-up feature, however, or if we have one and it's not currently enabled, we're free to choose any device power state lower than the
maxstate
value we derived from the device capabilities structure. We could blindly choose D3, but that wouldn't be right for every type of device because
It seems reasonable to leave your device in a low power state when the system resumes from a sleeping state. The DDK suggests you do this, and so does good sense. There are two situations in which you would need to restore your device to D0 when the system goes to Working. The first situation is when your device has the INRUSH characteristic. In this case, the Power Manager won't send power IRPs to any other INRUSH device until you've powered on your device. The second situation is when you've got substantive IRPs queued and waiting to run once power is back. Notwithstanding what a good idea it seems to be to just leave your device in a low power state, you'll notice that the code fragment I just showed you for SelectDState unconditionally picks the D0 state. In my testing, Windows 2000 seemed to hang coming out of standby if I didn't do that. Maybe there's a mistake in my code or in the operating system. Stay
In Chapter 5, "The I/O Request Packet," I discussed support functions such as IoAllocateIrp that you can use to build IRPs. You don't use those functions when you want to create power IRPs, though. (Actually, you would use one of those functions for an IRP_MN_POWER_SEQUENCE request, but not for the other IRP_MJ_POWER requests.) Instead, you use PoRequestPowerIrp , as shown here in the code for the SendDeviceIrp action we'd perform after SelectDState:
1 |
caseSendDeviceIrp:
{
if(win98&&ctx->devstate==pdx->devpower)
{
ctx->status=STATUS_SUCCESS;
action=actiontable[ctx->state][AsyncNotify];
continue;
}
POWER_STATEpowstate;
powstate.DeviceState=ctx->devstate;
NTSTATUSpostatus=PoRequestPowerIrp(pdx->Pdo,
ctx->MinorFunction,powstate,(PREQUEST_POWER_COMPLETE)
PoCompletionRoutine,ctx,NULL);
if(NT_SUCCESS(postatus))
break;
action=CompleteMainIrp;
ctx->status=postatus;
continue;
}
|
In the system
I'll discuss what happens to the device IRP we request via PoRequestPowerIrp later on.
Eventually, the device IRP that SendDeviceIrp requests will finish, whereupon the Power Manager will call the
PoCompletionRoutine
callback routine. It in
caseSubPowerUpComplete:
{
if(status==-1)
status=STATUS_SUCCESS;
action=CompleteMainIrp;
continue;
}
|
The only job performed by this action routine is to alter the status variable. The reason we do that is that we have an ASSERT statement at the end of HandlePowerEvent to make sure someone changes status . In this exact scenario, it doesn't matter what status value we return because PoCompletionRoutine is a void function. But you don't want to trigger an ASSERT and a BSOD unless something is really wrong.
The next action after SubPowerUpComplete is CompleteMainIrp, which leads to DestroyContext. You've already seen what those action routines do.
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.
Figure 8-7.
IRP flow when
Figure 8-8 diagrams how our finite state machine handles this type of IRP. TriageNewIrp puts the state machine into the
SubPowerDownPending
state and
Figure 8-8. State transitions when decreasing system power.
When the device IRP finishes, we execute SubPowerDownComplete :
caseSubPowerDownComplete:
{
if(status==-1)
status=STATUS_SUCCESS;
if(NT_SUCCESS(ctx->status))
{
ctx->state=SysPowerDownPending;
action=ForwardMainIrp;
}
else
action=CompleteMainIrp;
continue;
}
|
As you can see, if the device IRP fails, we fail the system IRP too. If the device IRP succeeds, we enter the SysPowerDownPending state and exit via ForwardMainIrp. When the system IRP finishes, and MainCompletionRoutine runs, we'll execute SysPowerDownComplete :
caseSysPowerDownComplete:
{
if(stack->MinorFunction==IRP_MN_SET_POWER)
pdx->syspower=stack->Parameters.Power.State.SystemState;
action=CompleteMainIrp;
continue;
}
|
The only purpose of this action is to record the new system power state in our device extension and then to exit via CompleteMainIrp and DestroyContext.
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
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.
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 whatever operations are required to restore context information to the device, and it will return STATUS_MORE_PROCESSING_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 set operation includes saving device context information, if any, in memory so that you can restore it later. There probably isn't any device-specific processing for a query operation beyond deciding whether to succeed or fail the query. 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
Figure 8-10. IRP flow when decreasing device power.
I invented StallRequestsAndNotify for use in TriageNewIrp. (It's so new that Chapter 6, where all the other DEVQUEUE functions are described, was already beyond my reach when I created it.) The first step it performs is to stall the request queue. If the device is currently busy, it records a callback routine address—in this case, GenericSaveRestoreComplete , which I'm overloading for purposes of receiving a notification—and returns STATUS_PENDING. TriageNewIrp will then exit in the QueueStallPending state.
If the device isn't busy, StallRequestsAndNotify returns STATUS_SUCCESS without arranging any callback; the device can't become busy now because the queue is stalled. TriageNewIrp will then go directly to the QueueStallComplete action.
We reach the QueueStallComplete routine either directly from TriageNewIrp (when the device is idle or if the queue was previously stalled for some other power-
caseQueueStallComplete:
{
if(stack->MinorFunction==IRP_MN_SET_POWER)
{
if(stack->Parameters.Power.State.DeviceState<pdx->devpower)
{
action=ForwardMainIrp;
SETSTATE(DevPowerUpPending);
}
else
action=SaveContext;
}
else
{
if(stack->Parameters.Power.State.DeviceState<pdx->devpower)
{
action=ForwardMainIrp;
SETSTATE(DevQueryUpPending);
}
else
action=DevQueryDown;
}
continue;
}
|
The upshot of QueueStallComplete is that we perform the next action indicated in Table 8-5 for the type of IRP we're dealing with.
Table 8-5. Next action for device IRPs.
| Minor Function | More or Less Power? | Next Action |
|---|---|---|
| IRP_MN_QUERY_POWER |
More power
Less or same power |
ForwardMainIrp
DevQueryDown |
| IRP_MN_SET_POWER |
More power
Less or same power |
ForwardMainIrp
SaveContext |
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.
ForwardMainIrp will install a completion routine and send the IRP down the driver stack. When MainCompletionRoutine eventually gains control, it signals a MainIrpComplete event. We will be in the DevPowerUpPending state, so we'll execute the DevPowerUpComplete action:
case DevPowerUpComplete:
{
if (!NT_SUCCESS(ctx->status) stack->MinorFunction !=
IRP_MN_SET_POWER)
{
action = CompleteMainIrp;
continue;
}
status = STATUS_MORE_PROCESSING_REQUIRED;
DEVICE_POWER_STATE oldpower = pdx->devpower;
pdx->devpower = stack->Parameters.Power.State.DeviceState;
if (pdx->RestoreContext)
{
ctx->state = ContextRestorePending;
(*pdx->RestoreDeviceContext)(pdx->DeviceObject, oldpower,
pdx->devpower, ctx);
break;
}
action = ContextRestoreComplete;
continue;
}
|
The main task we need to accomplish is restoring any device context that was lost during the previous power-down transition. Since we're not allowed to block our thread, we initiate whatever operations are required and return STATUS_MORE_PROCESSING_REQUIRED to interrupt the completion of the device IRP. When the restore operations finish, the client driver calls GenericSaveRestoreComplete, which signals an AsyncNotify event. We'll be in the ContextRestorePending state at that point, so we'll perform the ContextRestoreComplete action:
caseContextRestoreComplete:
{
if (event == AsyncNotify)
status = STATUS_SUCCESS;
action=CompleteMainIrp;
if(!NT_SUCCESS(ctx->status)pdx->devpower!=PowerDeviceD0)
continue;
ctx->UnstallQueue=TRUE;
continue;
}
|
The main result of this action routine is that we unstall the queue of substantive IRPs at the conclusion of an IRP_MN_SET_POWER to the D0 state. We exit via CompleteMainIrp and DestroyContext.
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 happen to receive one. The following code shows what GENERIC does when such a query completes in the lower level drivers. (Refer to Figure 8-12 for a state diagram.)
caseDevQueryUpComplete:
{
if(NT_SUCCESS(ctx->status)&&pdx->QueryPower)
if(!(*pdx->QueryPower)(pdx->DeviceObject,pdx->devpower,
stack->Parameters.Power.State.DeviceState))
ctx->status=STATUS_UNSUCCESSFUL;
action=CompleteMainIrp;
continue;
}
|
That is, GENERIC allows the client driver to accept or veto the query by calling its QueryPower function, and then it exits via CompleteMainIrp and DestroyContext.
Figure 8-12. State transitions for a query about a higher 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.
SaveContext will initiate an asynchronous process to save any device context that will be lost when the device loses power:
caseSaveContext:
{
DEVICE_POWER_STATEdevpower=
stack->Parameters.Power.State.DeviceState;
if(pdx->SaveDeviceContext&&devpower>pdx->devpower)
{
ctx->state=ContextSavePending;
(*pdx->SaveDeviceContext)(pdx->DeviceObject,pdx->devpower,
devpower,ctx);
break;
}
action=ContextSaveComplete;
}
|
When the save operations finish, the client driver calls GenericSaveRestoreComplete, which signals an AsyncNotify event. We'll be in the ContextSavePending state at that point, so we'll perform the ContextSaveComplete action:
1 |
caseContextSaveComplete:
{
if(event==AsyncNotify)
status=STATUS_SUCCESS;
ctx->state=DevPowerDownPending;
action=ForwardMainIrp;
DEVICE_POWER_STATEdevpower=
stack->Parameters.Power.State.DeviceState;
if(devpower<=pdx->devpower)
continue;
pdx->devpower=devpower;
if(devpower>PowerDeviceD0)
ctx->UnstallQueue=FALSE;
continue;
}
|
The next action, ForwardMainIrp, sends the device IRP down the driver stack. The bus driver will turn the physical flow of current off and complete the IRP. We'll see it next when MainCompletionRoutine signals a MainIrpComplete event, which takes us directly to CompleteMainIrp and thence to DestroyContext.
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. Although the DDK doesn't
The DevQueryDown action follows QueueStallComplete for this kind of IRP:
caseDevQueryDown:
{
DEVICE_POWER_STATEdevpower=
stack->Parameters.Power.State.DeviceState;
if(devpower>pdx->devpower
&&pdx->QueryPower
&&!(*pdx->QueryPower)(pdx->DeviceObject,
pdx->devpower,devpower))
{
ctx->status=STATUS_UNSUCCESSFUL;
action=DevQueryDownComplete;
continue;
}
ctx->state=DevQueryDownPending);
action=ForwardMainIrp;
continue;
}
|
Figure 8-14. State transitions for a query about a lower device power state.
GENERIC basically lets the client driver decide whether the query should succeed. If the client driver says "Yes," we enter the DevQueryDownPending state and exit via ForwardMainIrp to send the query down the driver stack. Completion of the IRP sends us to the DevQueryDownComplete action:
caseDevQueryDownComplete:
{
if(NT_SUCCESS(ctx->status))
ctx->UnstallQueue=FALSE;
action=CompleteMainIrp;
continue;
}
|
The basic action we take is to leave the substantive IRP queue stalled if the query succeeds. (CompleteMainIrp will unstall the queue if it sees the
UnstallQueue
flag set in the context structure. Clearing the flag causes this step to be

The Windows 2000 Device Driver Book: A Guide for Programmers (2nd Edition)

Windows System Programming (4th Edition) (Addison-Wesley Microsoft Technology Series)

Developing Drivers with the Windows Driver Foundation (Pro Developer)

Windowsu00ae Internals: Including Windows Server 2008 and Windows Vista, Fifth Edition (Pro Developer)