📄 power.cpp
字号:
NTSTATUS status = IoOpenDeviceRegistryKey(pdx->Pdo, PLUGPLAY_REGKEY_DEVICE, KEY_ALL_ACCESS, &hkey);
if (!NT_SUCCESS(status))
{
KdPrint((DRIVERNAME " - Error %X trying to open registry key\n", status));
return status;
}
UNICODE_STRING valname;
RtlInitUnicodeString(&valname, L"PowerSettings");
status = ZwSetValueKey(hkey, &valname, 0, REG_BINARY, pip, 2 * sizeof(ULONG));
if (!NT_SUCCESS(status))
KdPrint((DRIVERNAME " - ZwSetValueKey failed - %X\n", status));
ZwClose(hkey);
return status;
} // WritePowerInfoToRegistry
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
#pragma LOCKEDCODE
NTSTATUS MainCompletionRoutine(PDEVICE_OBJECT junk, PIRP Irp, PPOWCONTEXT ctx);
NTSTATUS SequenceCompletionRoutine(PDEVICE_OBJECT junk, PIRP Irp, PPOWCONTEXT ctx);
VOID PoCompletionRoutine(PDEVICE_OBJECT junk, UCHAR fcn, POWER_STATE state, PPOWCONTEXT ctx, PIO_STATUS_BLOCK pstatus);
NTSTATUS SafePoCallDriver(PDEVICE_OBJECT DeviceObject, PIRP Irp);
VOID PassivePowerComplete(PPOWCONTEXT ctx) {HandlePowerEvent(ctx, AsyncNotify);}
NTSTATUS HandlePowerEvent(PPOWCONTEXT ctx, enum POWEVENT event)
{ // HandlePowerEvent
NTSTATUS status = -1; // an invalid value
ASSERT(ctx);
ASSERT((ULONG) event < NUMPOWEVENTS);
PIRP Irp = ctx->irp; // could be null if we did an early S0 completion
PIO_STACK_LOCATION stack = Irp ? IoGetCurrentIrpStackLocation(Irp) : NULL;
PGENERIC_EXTENSION pdx = ctx->pdx;
enum POWACTION {
InvalidAction, // code for invalid state/event combinations
TriageNewIrp, // decide what to do with new IRP
QueueStallComplete, // device queue has been stalled
ForwardMainIrp, // begin system or device IRP for more power
SysPowerUpComplete, // system power-up IRP completed
SysPowerDownComplete, // system power-down IRP completed
SelectDState, // choose D-state corresponding to main IRP's S-state
SendDeviceIrp, // send device IRP
CompleteMainIrp, // complete the main IRP
DestroyContext, // terminate FSM
SubPowerUpComplete, // nested power-up IRP finished or failed
SubPowerDownComplete, // nested power-down IRP finished or failed
DevPowerUpComplete, // device power-up IRP has completed
SaveContext, // save context in preparation for powering down
ContextSaveComplete, // device context has been saved
ContextRestoreComplete, // device context has been restored
DevQueryUpComplete, // device query for power-up complete
DevQueryDown, // see if device can power down
DevQueryDownComplete, // device query for power-down complete
SendPowerSequence, // create and send POWER_SEQUENCE request
SaveSeqComplete, // sequence fetch after context save complete
ForwardDevDown, // forward device set-power down IRP
RestoreSeqComplete, // sequence fetch before context save complete
RestoreContext, // restore device context
};
#ifdef VERBOSETRACE
static char* powstatenames[] = {
"InitialState",
"SysPowerUpPending",
"SubPowerUpPending",
"SubPowerDownPending",
"SysPowerDownPending",
"DevPowerUpPending",
"DevPowerDownPending",
"ContextSavePending",
"ContextRestorePending",
"DevQueryUpPending",
"DevQueryDownPending",
"QueueStallPending",
"SaveSeqPending",
"RestoreSeqPending",
"PassiveCompletePending",
"FinalState",
};
static char* eventnames[] = {
"NewIrp",
"MainIrpComplete",
"AsyncNotify",
};
static char* actionnames[] = {
"InvalidAction",
"TriageNewIrp",
"QueueStallComplete",
"ForwardMainIrp",
"SysPowerUpComplete",
"SysPowerDownComplete",
"SelectDState",
"SendDeviceIrp",
"CompleteMainIrp",
"DestroyContext",
"SubPowerUpComplete",
"SubPowerDownComplete",
"DevPowerUpComplete",
"SaveContext",
"ContextSaveComplete",
"ContextRestoreComplete",
"DevQueryUpComplete",
"DevQueryDown",
"DevQueryDownComplete",
"SendPowerSequence",
"SaveSeqComplete",
"ForwardDevDown",
"RestoreSeqComplete",
"RestoreContext",
};
#endif // VERBOSETRACE
static enum POWACTION actiontable[NUMPOWSTATES][NUMPOWEVENTS] = {
/* NewIrp MainIrpComplete AsyncNotify */
/* InitialState */ {TriageNewIrp, InvalidAction, InvalidAction},
/* SysPowerUpPending */ {InvalidAction, SysPowerUpComplete, InvalidAction},
/* SubPowerUpPending */ {InvalidAction, InvalidAction, SubPowerUpComplete},
/* SubPowerDownPending */ {InvalidAction, InvalidAction, SubPowerDownComplete},
/* SysPowerDownPending */ {InvalidAction, SysPowerDownComplete, InvalidAction},
/* DevPowerUpPending */ {InvalidAction, DevPowerUpComplete, InvalidAction},
/* DevPowerDownPending */ {InvalidAction, CompleteMainIrp, InvalidAction},
/* ContextSavePending */ {InvalidAction, InvalidAction, ContextSaveComplete},
/* ContextRestorePending */ {InvalidAction, InvalidAction, ContextRestoreComplete},
/* DevQueryUpPending */ {InvalidAction, DevQueryUpComplete, InvalidAction},
/* DevQueryDownPending */ {InvalidAction, DevQueryDownComplete, InvalidAction},
/* QueueStallPending */ {InvalidAction, InvalidAction, QueueStallComplete},
/* SaveSeqPending */ {InvalidAction, InvalidAction, SaveSeqComplete},
/* RestoreSeqPending */ {InvalidAction, InvalidAction, RestoreSeqComplete},
/* PassiveCompletePending */{InvalidAction, InvalidAction, CompleteMainIrp},
/* FinalState */ {InvalidAction, InvalidAction, InvalidAction},
};
// Determine the first action to take based on the current state of the FSM and the event that occurred.
// Note that this isn't as complicated as the use of 2-D array might suggest: all states except
// the initial state lead to a single action for the one-and-only event that's possible to get in
// that state.
enum POWACTION action = actiontable[ctx->state][event];
// Structurally, the following code is a switch on "action" imbedded within an
// infinite loop. A case that does a "break" from the switch executes a "break"
// from the loop, whereupon we return whatever value is left in "status". A case
// that does a "continue" from the switch repeats the loop -- this is how actions
// can be strung together during one call to this routine. I coded it this way to
// avoid return statements in the middle that make it harder to prove that the
// routine behaves in a predictable way. Note that any "break" should be preceded
// by a change to the state recorded in the context structure and to the initially
// invalid valid of "status". There are ASSERTs at the end to check this.
// Concerning the required change to "ctx->state": there are many cases where we
// call PoRequestPowerIrp or PoCallDriver, whereupon the context structure gets
// released before those routines return. We use a SETSTATE macro so we don't
// have to dereference a possibly invalid "ctx" pointer at the end of the loop. Any
// action that calls a routine that might result in completing the current IRP
// should also take care not to touch "ctx" afterwards. (These are always cases that
// "break" from the switch, so you can just verify that the break always immediately
// follows the PoXxx call.)
// Concerning the required change to "status": only TriageNewIrp
// will arrange to return STATUS_PENDING. Many of the other initial actions are entered
// from a standard I/O completion routine and will need to return STATUS_MORE_PROCESSING_REQUIRED
// to hold off final completion. Any action for MainIrpComplete that goes out through
// CompleteMainIrp will end up returning ctx->status, which gets set in MainCompletionRoutine
// to whatever's in the IRP -- this allows the IRP to complete normally. Any action off of
// AsyncNotify should be changing "status" explicitly (and they do -- I checked).
// Note: avoid doing ASSERTs in this code because they're basically impossible to debug.
// (Usually, the display is turned off before anything else, and then the serial port.
// So the debugger is usually deaf and dumb when something goes wrong here.)
#if DBG
enum POWSTATE originalstate = ctx->state;
enum POWSTATE nextstate = originalstate;
#define SETSTATE(s) ctx->state = nextstate = s
LONG eventid = InterlockedIncrement(&ctx->eventcount);
LONG ctxid = ctx->id;
#else
#define SETSTATE(s) ctx->state = s
#endif
POWTRACE(("%s - %d.%d is %s in state %s\n", pdx->DebugName, ctxid, eventid, eventnames[event], powstatenames[originalstate]));
while (TRUE)
{ // handle this event
POWTRACE(("%s - %d.%d %s\n", pdx->DebugName, ctxid, eventid, actionnames[action]));
switch (action)
{ // perform next action
///////////////////////////////////////////////////////////////////////
// TriageNewIrp is the first action for a newly receive query or set IRP
case TriageNewIrp:
{ // TriageNewIrp
// We want the power dispatch routine to return STATUS_PENDING unless
// something goes wrong right away. If we do return STATUS_PENDING, we
// need to be sure we mark the IRP pending,
status = STATUS_PENDING;
IoMarkIrpPending(Irp);
// Acquire remove lock an extra time. We'll release it when we eventually
// complete this IRP.
if (pdx->RemoveLock)
IoAcquireRemoveLock(pdx->RemoveLock, Irp);
// For a system IRP, we'll request the corresponding device IRP. If system power is
// being restored, we wait until the lower level drivers finish the system IRP. If
// system power is being removed, we do it now and forward the system IRP when the
// device IRP finishes.
if (stack->Parameters.Power.Type == SystemPowerState)
{ // system IRP
if (stack->Parameters.Power.State.SystemState < pdx->syspower)
{
action = ForwardMainIrp;
SETSTATE(SysPowerUpPending);
}
else
{
action = SelectDState;
SETSTATE(SubPowerDownPending);
}
} // system IRP
// For a device set-power IRP, we have a variety of tasks to carry out. If device
// power is being restored, we do those tasks when the lower level drivers complete
// the IRP. If device power is being removed or staying the same, we do those tasks
// before passing this IRP down. In either case, we ensure that the device isn't busy
// with any substantive IRPs first.
else
{ // device IRP
SETSTATE(QueueStallPending);
DEVICE_POWER_STATE newstate = stack->Parameters.Power.State.DeviceState;
// Conceivably, we could cancel any outstanding wait-wake when going to a D-state
// that doesn't support wakeup. In practice, the bus driver will do this for us.
// According to the DDK, we're supposed to call PoSetPowerState any time we
// change the power state, *before* calling PoStartNextPowerIrp and at IRQL
// < DISPATCH_LEVEL (except = DISPATCH_LEVEL is okay for D0). In fact, it seems
// to make no difference whether we do this or not, and bus drivers seem to make
// all the necessary calls. Therefore, don't bother.
// If the device is currently sleeping or off, the queue should already be
// stalled. If the device is in D0, however, it might be servicing a
// request right now, and there might be requests queued up behind the
// active one. StallRequestsAndNotify will stall the queue and establish
// a callback for when the client driver calls StartNextPacket.
if (pdx->nqueues && !pdx->StalledForPower)
{ // stall request queue
// We mustn't touch the context structure after calling a function that
// might recursively invoke this routine...
ctx->UnstallQueue = TRUE;
pdx->StalledForPower = TRUE;
UCHAR minor = stack->MinorFunction;
DEVICE_POWER_STATE oldstate = pdx->devpower;
NTSTATUS qstatus = StallAllRequestsAndNotify(pdx->queues, pdx->nqueues, GenericSaveRestoreComplete, ctx);
if (!NT_SUCCESS(qstatus))
{ // can't stall queue
ctx->status = qstatus;
action = CompleteMainIrp;
ctx->UnstallQueue = FALSE;
pdx->StalledForPower = FALSE;
continue;
} // can't stall queue
// The UnstallQueue flag tells CompleteMainIrp to unstall the queue as part
// of the cleanup process. We may clear this flag somewhere along the way
// in order to keep the queue stalled during a period of low power.
POWTRACE(("%s - %d.%d stalling queue\n", pdx->DebugName, ctxid, eventid));
if (qstatus == STATUS_PENDING)
{
if (pdx->FlushPendingIo)
(*pdx->FlushPendingIo)(pdx->DeviceObject, IRP_MJ_POWER, minor, oldstate, newstate);
break; // wait for notification that device is idle
}
} // stall request queue
action = QueueStallComplete;
} // device IRP
continue;
} // TriageNewIrp
///////////////////////////////////////////////////////////////////////
// QueueStallComplete is the action for an AsyncNotify event in the
// QueueStallPending state. It's reached when StartNextPacket calls
// GenericSaveRestoreComplete, which we specified as the current-irp
// complete notification routine in our earlier call to StallRequestsAndNotify.
// This action can also be reached directly from TriageNewIrp if the
// device was idle to begin with or if we were already in a low-power
// state (so that the queue should have been stalled)
case QueueStallComplete:
{ // QueueStallComplete
if (stack->MinorFunction == IRP_MN_SET_POWER)
{ // device set-power IRP
if (stack->Parameters.Power.State.DeviceState < pdx->devpower)
{
action = ForwardMainIrp;
SETSTATE(DevPowerUpPending);
}
else
action = SaveContext;
} // device set-power IRP
else
{ // device query-power IRP
if (stack->Parameters.Power.State.DeviceState < pdx->devpower)
{
action = ForwardMainIrp;
SETSTATE(DevQueryUpPending);
}
else
action = DevQueryDown;
} // device query-power IRP
continue;
} // QueueStallComplete
///////////////////////////////////////////////////////////////////////
// ForwardMainIrp sends the current power IRP to the next driver in the
// stack. We regain control in MainCompletionRoutine.
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -