755 lines
19 KiB
C
755 lines
19 KiB
C
/*++
|
||
|
||
Copyright (c) 1995 Microsoft Corporation
|
||
|
||
Module Name:
|
||
|
||
Abstract:
|
||
|
||
Suspend/Hibernate system
|
||
|
||
Author:
|
||
|
||
Ken Reneris (kenr) 19-July-1994
|
||
|
||
Environment:
|
||
|
||
Kernel mode
|
||
|
||
Revision History:
|
||
|
||
--*/
|
||
|
||
|
||
#include "pop.h"
|
||
#include "zwapi.h"
|
||
|
||
NTSTATUS
|
||
IopInvalidDeviceRequest (
|
||
PDEVICE_OBJECT DeviceObject,
|
||
PIRP Irp
|
||
);
|
||
|
||
|
||
|
||
#define NUMPASSES 4
|
||
|
||
//
|
||
// Pass 1 - other
|
||
// Pass 2 - disk & direct_io type drivers
|
||
// Pass 3 - video type drivers
|
||
// Pass 4 - ?
|
||
//
|
||
|
||
|
||
typedef struct _BROADCAST_Link {
|
||
PDEVOBJ_EXTENSION DeviceObjectExt;
|
||
LIST_ENTRY NotifyLink;
|
||
LIST_ENTRY FailedLink;
|
||
} BROADCAST_LINK, *PBROADCAST_LINK;
|
||
|
||
|
||
typedef struct _BROADCAST_PASS {
|
||
LIST_ENTRY NotifyList[1];
|
||
} BROADCAST_PASS, *PBROADCAST_PASS;
|
||
|
||
typedef struct _BROADCAST_ORDER {
|
||
ULONG NumPasses;
|
||
ULONG NumLevels;
|
||
PBROADCAST_PASS Pass[NUMPASSES];
|
||
} BROADCAST_ORDER, *PBROADCAST_ORDER;
|
||
|
||
|
||
|
||
#ifdef ALLOC_PRAGMA
|
||
#pragma alloc_text(PAGEPO, NtSetSystemPowerState)
|
||
#pragma alloc_text(PAGEPO, PopGetBroadcastOrder)
|
||
#pragma alloc_text(PAGEPO, PopBroadcastSetPower)
|
||
#pragma alloc_text(PAGEPO, PopReleaseBroadcast)
|
||
#pragma alloc_text(PAGEPO, PoSystemResume)
|
||
#endif
|
||
|
||
|
||
NTSTATUS
|
||
NtSetSystemPowerState(
|
||
IN POWER_STATE SystemPowerState,
|
||
IN BOOLEAN NoResumeAlarm,
|
||
IN BOOLEAN ForcePowerDown
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine is invoked to suspend or hibernate the system. If the system
|
||
can not currently support a suspend or hibernate then an error is returned.
|
||
Suspend/Hibernate support requires that all currently loaded driver have
|
||
power management support.
|
||
|
||
This call requires system Shutdown access.
|
||
|
||
Arguments:
|
||
|
||
NoResumeAlarm - If TRUE, then the system will be suspended or hibernated
|
||
without setting any resume alarm. This should be used in the case
|
||
of a critical low battery.
|
||
|
||
ForcePowerDown - If FALSE , then the devices are first sent a
|
||
SET_POWER-Query. If all power off queries succeed then devices are
|
||
sent a SET_POWER to suspend or hibernate; otherwise an error is
|
||
returned.
|
||
|
||
Return Value:
|
||
|
||
Success - System was hibernated and has been resumed
|
||
|
||
error - Neither suspend nor hibernate did not occur due to the reported
|
||
error.
|
||
|
||
--*/
|
||
{
|
||
PVOID CodeLockHandle;
|
||
NTSTATUS Status, Status2;
|
||
LONGLONG Interval;
|
||
PTIME_FIELDS ResumeTime;
|
||
TIME_FIELDS TimeFields;
|
||
LARGE_INTEGER LocalTime, SystemTime;
|
||
PVOID BroadcastOrder;
|
||
LIST_ENTRY FailedHead;
|
||
PLIST_ENTRY Link;
|
||
PBROADCAST_LINK BroadcastLink;
|
||
PDEVICE_OBJECT FailedDeviceObject;
|
||
POBJECT_NAME_INFORMATION ObjectName;
|
||
BOOLEAN DeviceName;
|
||
|
||
|
||
#ifndef _PNP_POWER_
|
||
|
||
return STATUS_NOT_IMPLEMENTED;
|
||
|
||
#else
|
||
|
||
//
|
||
// Verify PO system is fully initilaized
|
||
//
|
||
|
||
if (!PoEnabled || !PoPowerSequence) {
|
||
return STATUS_UNSUCCESSFUL;
|
||
}
|
||
|
||
//
|
||
// Verify caller has shutdown privilege
|
||
//
|
||
#if 0
|
||
// BUGBUG: add this
|
||
if (!SeSinglePrivilegeCheck(SeShutdownPrivilege, KeGetPreviousMode())) {
|
||
return STATUS_PRIVILEGE_NOT_HELD;
|
||
}
|
||
#endif
|
||
|
||
//
|
||
// BUGBUG:
|
||
// For now only support suspend
|
||
//
|
||
|
||
if (SystemPowerState != PowerSuspend) {
|
||
return STATUS_INVALID_PARAMETER;
|
||
}
|
||
|
||
//
|
||
// BUGBUGs:
|
||
// Need to flush some/all dirty cache manager data
|
||
//
|
||
|
||
BroadcastOrder = NULL;
|
||
InitializeListHead (&FailedHead);
|
||
|
||
//
|
||
// Entry critical region
|
||
//
|
||
|
||
KeEnterCriticalRegion ();
|
||
|
||
//
|
||
// About to attempt a system suspend / hibernate
|
||
//
|
||
|
||
ExNotifyCallback (ExCbSuspendHibernateSystem, 0, 0);
|
||
|
||
//
|
||
// Page in and Lock suspend/hibernate code in place
|
||
// Obtain lock for DeviceObject list
|
||
//
|
||
|
||
CodeLockHandle = MmLockPagableCodeSection (&NtSetSystemPowerState);
|
||
PopLockDeviceList (TRUE);
|
||
|
||
//
|
||
// Determine broadcasting order for all current device objects
|
||
//
|
||
|
||
BroadcastOrder = PopGetBroadcastOrder ();
|
||
if (!BroadcastOrder) {
|
||
Status = STATUS_NO_MEMORY;
|
||
goto CleanUp;
|
||
}
|
||
|
||
//
|
||
// If not force power down, attempt to put all devices into
|
||
// the queried powered down state
|
||
//
|
||
|
||
if (!ForcePowerDown) {
|
||
Status = PopBroadcastSetPower (
|
||
BroadcastOrder,
|
||
PowerQuery,
|
||
&FailedHead
|
||
);
|
||
|
||
if (!NT_SUCCESS(Status)) {
|
||
goto CleanUp;
|
||
}
|
||
|
||
//
|
||
// Query was sucessfull, release suspending flags so we
|
||
// can flush the cache
|
||
//
|
||
|
||
PopReleaseBroadcast (BroadcastOrder);
|
||
|
||
// BUGBUG: Flush some/all dirty cache manager data
|
||
|
||
//
|
||
// Put devices back into the powered-down query state
|
||
//
|
||
|
||
Status = PopBroadcastSetPower (
|
||
BroadcastOrder,
|
||
PowerQuery,
|
||
&FailedHead
|
||
) ;
|
||
|
||
if (!NT_SUCCESS(Status)) {
|
||
// Ohps, query failed this time around
|
||
goto CleanUp;
|
||
}
|
||
}
|
||
|
||
//
|
||
// Put devices into PowerDown-Suspend or PowerDown-Hibernate state
|
||
//
|
||
|
||
Status = PopBroadcastSetPower (
|
||
BroadcastOrder,
|
||
SystemPowerState,
|
||
&FailedHead
|
||
);
|
||
|
||
if (!NT_SUCCESS(Status)) {
|
||
goto CleanUp;
|
||
}
|
||
|
||
//
|
||
// Determine systems resume time
|
||
//
|
||
|
||
ResumeTime = NULL;
|
||
Interval = 0;
|
||
NoResumeAlarm = FALSE; // BUGBUG
|
||
|
||
if (!NoResumeAlarm) {
|
||
|
||
// fill in Interval and TimeFields
|
||
|
||
if (Interval < 150000000) {
|
||
PoDbgPrint (PODIAG1, "PO: Suspend aborted due to critical resume\n");
|
||
goto CleanUp;
|
||
}
|
||
|
||
ResumeTime = &TimeFields;
|
||
}
|
||
|
||
//
|
||
// Suspend or PowerOff the box
|
||
//
|
||
|
||
Status = KeSuspendHibernateSystem (
|
||
ResumeTime,
|
||
NULL /* bugbug hibernate */
|
||
);
|
||
|
||
//
|
||
// If machine was successfully suspended/hibernated then resumed,
|
||
// reset the time
|
||
//
|
||
|
||
if (NT_SUCCESS(Status) &&
|
||
HalQueryRealTimeClock(&TimeFields) &&
|
||
RtlTimeFieldsToTime(&TimeFields, &LocalTime) ) {
|
||
|
||
ExLocalTimeToSystemTime(&LocalTime,&SystemTime);
|
||
ZwSetSystemTime(&SystemTime,NULL);
|
||
}
|
||
|
||
|
||
CleanUp:
|
||
ExNotifyCallback (ExCbSuspendHibernateSystem, (PVOID) 1, (PVOID) Status);
|
||
|
||
//
|
||
// Release suspending flags and allow drivers to powerup if they want.
|
||
// But, don't free the BroadcastOrder memory yet
|
||
//
|
||
|
||
if (BroadcastOrder) {
|
||
PopReleaseBroadcast (BroadcastOrder);
|
||
}
|
||
|
||
//
|
||
// Release DeviceObject list
|
||
// Release suspend/hibernate code
|
||
//
|
||
|
||
PopUnlockDeviceList ();
|
||
MmUnlockPagableImageSection (CodeLockHandle);
|
||
|
||
//
|
||
// If there was a failure
|
||
//
|
||
|
||
if (!IsListEmpty(&FailedHead)) {
|
||
|
||
//
|
||
// Failure due to some device - capture it's name and generate
|
||
// a popup
|
||
//
|
||
|
||
|
||
//
|
||
// If there's a failing device object, capture it's name
|
||
//
|
||
|
||
|
||
|
||
for (Link=FailedHead.Flink; Link != &FailedHead; Link = Link->Flink) {
|
||
BroadcastLink = CONTAINING_RECORD (Link,
|
||
BROADCAST_LINK,
|
||
FailedLink );
|
||
|
||
FailedDeviceObject = BroadcastLink->DeviceObjectExt->DeviceObject;
|
||
|
||
ObjectName = PopGetDeviceName (FailedDeviceObject);
|
||
if (ObjectName) {
|
||
#if DBG
|
||
DbgPrint ("PO: Device %08x '%Z' failed system suspend\n",
|
||
FailedDeviceObject, &ObjectName->Name);
|
||
#endif
|
||
|
||
ExFreePool (ObjectName);
|
||
}
|
||
ObDereferenceObject (FailedDeviceObject);
|
||
}
|
||
}
|
||
|
||
if (BroadcastOrder) {
|
||
ExFreePool (BroadcastOrder);
|
||
}
|
||
|
||
//
|
||
// Leave critial region
|
||
//
|
||
|
||
KeLeaveCriticalRegion ();
|
||
return Status;
|
||
|
||
#endif
|
||
}
|
||
|
||
#ifdef _PNP_POWER_
|
||
|
||
PVOID
|
||
PopGetBroadcastOrder (
|
||
VOID
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Builds information on how to notify current drivers of a
|
||
PowerDown due to a suspend or hibernate request
|
||
|
||
--*/
|
||
{
|
||
PLIST_ENTRY Link;
|
||
PDEVOBJ_EXTENSION DeviceObjectExt;
|
||
PDRIVER_OBJECT DriverObject;
|
||
PDEVICE_OBJECT DeviceObject;
|
||
ULONG MaxStackSize;
|
||
ULONG NumObjects;
|
||
ULONG Size1, Size2;
|
||
ULONG p, l;
|
||
PVOID LastLoc;
|
||
PBROADCAST_ORDER BroadcastOrder;
|
||
PBROADCAST_PASS BroadcastPass;
|
||
PBROADCAST_LINK BroadcastLink;
|
||
|
||
//
|
||
// Run all DeviceObjects are determine the largest StackSize
|
||
//
|
||
|
||
MaxStackSize = 0;
|
||
NumObjects = 0;
|
||
for (Link = PopDeviceList.Flink; Link != &PopDeviceList; Link = Link->Flink) {
|
||
DeviceObjectExt = CONTAINING_RECORD(Link, DEVOBJ_EXTENSION, AllDeviceObjects);
|
||
|
||
NumObjects++;
|
||
if ((ULONG) DeviceObjectExt->DeviceObject->StackSize > MaxStackSize) {
|
||
MaxStackSize = DeviceObjectExt->DeviceObject->StackSize;
|
||
ASSERT (MaxStackSize < 50);
|
||
}
|
||
}
|
||
|
||
//
|
||
// Allocate memory to track ordering
|
||
//
|
||
|
||
Size1 = sizeof(BROADCAST_PASS) * NUMPASSES * (MaxStackSize+2) +
|
||
sizeof(BROADCAST_ORDER);
|
||
Size2 = sizeof(BROADCAST_LINK) * (NumObjects+2);
|
||
|
||
BroadcastOrder = ExAllocatePoolWithTag ( NonPagedPool, Size1+Size2, 'psuS');
|
||
LastLoc = ((PUCHAR) BroadcastOrder) + Size1 + Size2;
|
||
if (!BroadcastOrder) {
|
||
return NULL;
|
||
}
|
||
|
||
//
|
||
// Initialize structure
|
||
//
|
||
|
||
RtlZeroMemory (BroadcastOrder, Size1+Size2);
|
||
BroadcastOrder->NumPasses = NUMPASSES;
|
||
BroadcastOrder->NumLevels = MaxStackSize;
|
||
BroadcastPass = (PBROADCAST_PASS) (((PUCHAR) BroadcastOrder) +
|
||
sizeof (BROADCAST_ORDER));
|
||
|
||
for (p=0; p < NUMPASSES; p++) {
|
||
BroadcastOrder->Pass[p] = BroadcastPass;
|
||
for (l=0; l <= MaxStackSize; l++) {
|
||
InitializeListHead (&BroadcastPass->NotifyList[l]);
|
||
}
|
||
|
||
BroadcastPass += MaxStackSize + 1;
|
||
ASSERT ((PVOID) BroadcastPass < LastLoc);
|
||
}
|
||
|
||
//
|
||
// Run drivers and put each driver on appropiate list
|
||
//
|
||
|
||
BroadcastLink = (PBROADCAST_LINK) BroadcastPass;
|
||
for (Link = PopDeviceList.Flink; Link != &PopDeviceList; Link = Link->Flink) {
|
||
DeviceObjectExt = CONTAINING_RECORD(Link, DEVOBJ_EXTENSION, AllDeviceObjects);
|
||
DeviceObject = DeviceObjectExt->DeviceObject;
|
||
DriverObject = DeviceObject->DriverObject;
|
||
|
||
//
|
||
// Pick pass DeviceType
|
||
//
|
||
|
||
switch (DeviceObject->DeviceType) {
|
||
case FILE_DEVICE_DISK: p = 1; break;
|
||
case FILE_DEVICE_CD_ROM: p = 1; break;
|
||
case FILE_DEVICE_VIRTUAL_DISK: p = 1; break;
|
||
case FILE_DEVICE_VIDEO: p = 2; break;
|
||
default: p = 0; break;
|
||
}
|
||
|
||
|
||
if (p < 1 && DeviceObject->Flags & DO_DIRECT_IO) {
|
||
// Some type of disk device, should be at least in 2nd pass
|
||
p = 1;
|
||
}
|
||
|
||
//
|
||
// Insert into queue
|
||
//
|
||
|
||
BroadcastLink->DeviceObjectExt = DeviceObjectExt;
|
||
InsertTailList (
|
||
&BroadcastOrder->Pass[p]->NotifyList[DeviceObject->StackSize],
|
||
&BroadcastLink->NotifyLink
|
||
);
|
||
|
||
BroadcastLink += 1;
|
||
ASSERT ((PVOID) BroadcastLink < LastLoc);
|
||
}
|
||
|
||
return BroadcastOrder;
|
||
}
|
||
|
||
|
||
|
||
|
||
NTSTATUS
|
||
PopBroadcastSetPower (
|
||
IN PVOID pBroadcastOrder,
|
||
IN POWER_STATE PowerState,
|
||
OUT PLIST_ENTRY FailedHead
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine attempts to set every device into the power state of
|
||
PowerState. In addition the devices will stay in the requested
|
||
state until released. (ie, any requested power on will be blocked).
|
||
|
||
Arguments:
|
||
|
||
BroadcastOrder - Value returned from PopGetBroadcastOrder
|
||
PowerState - System suspend or hibernate state to put every device in
|
||
FailedHead - If not successful the first device object encounted
|
||
which the requested state failed
|
||
|
||
Return Value:
|
||
|
||
Success - All devices have been put into the requested state
|
||
|
||
error - All devices were not set into the requested state (some may
|
||
have been).
|
||
|
||
--*/
|
||
{
|
||
ULONG p;
|
||
LONG l;
|
||
PLIST_ENTRY ListHead, Link;
|
||
KIRQL OldIrql;
|
||
NTSTATUS Status;
|
||
LARGE_INTEGER Timeout;
|
||
PBROADCAST_ORDER BroadcastOrder;
|
||
PBROADCAST_LINK BroadcastLink;
|
||
PDEVOBJ_EXTENSION DeviceObjectExt;
|
||
POBJECT_NAME_INFORMATION ObjectName;
|
||
POWER_STATE NewState;
|
||
|
||
ASSERT (IsListEmpty (FailedHead));
|
||
|
||
//
|
||
// Notify all drivers
|
||
//
|
||
|
||
Status = STATUS_SUCCESS;
|
||
BroadcastOrder = (PBROADCAST_ORDER) pBroadcastOrder;
|
||
for (p=0; NT_SUCCESS(Status) && p < BroadcastOrder->NumPasses; p++) {
|
||
|
||
//
|
||
// Start at high levels and work down
|
||
//
|
||
|
||
for (l=BroadcastOrder->NumLevels; NT_SUCCESS(Status) && l >=0; l--) {
|
||
|
||
ListHead = &BroadcastOrder->Pass[p]->NotifyList[l];
|
||
|
||
//
|
||
// Lock database & notify this run of drivers
|
||
//
|
||
|
||
PopLockStateDatabase (&OldIrql);
|
||
for (Link=ListHead->Flink; Link != ListHead; Link = Link->Flink) {
|
||
BroadcastLink = CONTAINING_RECORD (Link,
|
||
BROADCAST_LINK,
|
||
NotifyLink );
|
||
|
||
//
|
||
// Don't let device power back on
|
||
//
|
||
|
||
BroadcastLink->DeviceObjectExt->Suspending = TRUE;
|
||
|
||
//
|
||
// Request device to Power down
|
||
//
|
||
|
||
PopRequestPowerChange (
|
||
BroadcastLink->DeviceObjectExt,
|
||
PowerState,
|
||
0
|
||
);
|
||
}
|
||
|
||
//
|
||
// Wait for all current power irps to complete
|
||
//
|
||
|
||
if (!PopIsStateDatabaseIdle()) {
|
||
KeClearEvent (&PopStateDatabaseIdle);
|
||
|
||
PopUnlockStateDatabase (OldIrql);
|
||
|
||
//
|
||
// BUGBUG: need some deadlock detection here
|
||
//
|
||
|
||
Status = KeWaitForSingleObject (
|
||
&PopStateDatabaseIdle,
|
||
Executive,
|
||
KernelMode,
|
||
FALSE,
|
||
NULL
|
||
);
|
||
|
||
PopLockStateDatabase (&OldIrql);
|
||
}
|
||
|
||
if (!NT_SUCCESS (Status)) {
|
||
break;
|
||
}
|
||
|
||
//
|
||
// Verify all devices sucessfully set their power settings
|
||
//
|
||
|
||
for (Link=ListHead->Flink; Link != ListHead; Link = Link->Flink) {
|
||
BroadcastLink = CONTAINING_RECORD (Link,
|
||
BROADCAST_LINK,
|
||
NotifyLink );
|
||
|
||
DeviceObjectExt = BroadcastLink->DeviceObjectExt;
|
||
|
||
NewState = DeviceObjectExt->CurrentPowerState;
|
||
if (NewState != PowerState) {
|
||
|
||
//
|
||
// The device's state does not match the target state.
|
||
// Check to see if the combination of newstate to targetstate
|
||
// is the current device's state.
|
||
//
|
||
|
||
if (NewState != PopNewPendingState[NewState][PowerState]) {
|
||
|
||
//
|
||
// Device is not in an acceptable state
|
||
//
|
||
|
||
ObReferenceObjectByPointer (
|
||
DeviceObjectExt->DeviceObject,
|
||
(ACCESS_MASK) NULL,
|
||
IoDeviceObjectType,
|
||
KernelMode
|
||
);
|
||
|
||
InsertTailList (FailedHead, &BroadcastLink->FailedLink);
|
||
|
||
// BUGBUG: for now let it pass...
|
||
// BUGBUG: return STATUS_SUSPEND_FAILED;
|
||
Status = STATUS_NOT_SUPPORTED;
|
||
}
|
||
}
|
||
}
|
||
|
||
PopUnlockStateDatabase (OldIrql);
|
||
} // next level
|
||
} // next pass
|
||
|
||
return Status;
|
||
}
|
||
|
||
|
||
VOID
|
||
PopReleaseBroadcast (
|
||
IN PVOID BroadcastOrder
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine releases the suspend lock on any devices power state such
|
||
that the device can power on if it requests too.
|
||
|
||
Arguments:
|
||
|
||
BroadcastOrder - Value returned from PopGetBroadcastOrder
|
||
|
||
--*/
|
||
{
|
||
KIRQL OldIrql;
|
||
PLIST_ENTRY Link;
|
||
PDEVOBJ_EXTENSION DeviceObjectExt;
|
||
|
||
PopLockStateDatabase (&OldIrql);
|
||
|
||
for (Link = PopDeviceList.Flink; Link != &PopDeviceList; Link = Link->Flink) {
|
||
DeviceObjectExt = CONTAINING_RECORD(Link, DEVOBJ_EXTENSION, AllDeviceObjects);
|
||
|
||
//
|
||
// Request to set to pending state
|
||
//
|
||
|
||
if (DeviceObjectExt->Suspending) {
|
||
|
||
//
|
||
// Let device go
|
||
//
|
||
|
||
DeviceObjectExt->Suspending = FALSE;
|
||
PopRequestPowerChange (DeviceObjectExt, 0, 0);
|
||
}
|
||
}
|
||
|
||
PopUnlockStateDatabase (OldIrql);
|
||
}
|
||
|
||
|
||
|
||
VOID
|
||
PoSystemResume (
|
||
VOID
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine is called by the kernel as the machine is resuming from
|
||
a successful system suspend or hibernate.
|
||
|
||
WARNING: This function is called at HIGH_LEVEL while all processors
|
||
(expect the calling processor) are still frozen.
|
||
|
||
--*/
|
||
{
|
||
PLIST_ENTRY Link;
|
||
PDEVOBJ_EXTENSION DeviceObjectExt;
|
||
PDEVICE_OBJECT DeviceObject;
|
||
PDRIVER_OBJECT DriverObject;
|
||
PDRIVER_DISPATCH MajorFunction;
|
||
|
||
//
|
||
// Increase PowerSequnce
|
||
//
|
||
|
||
PoPowerSequence += 1;
|
||
|
||
|
||
#if 0
|
||
//
|
||
// Notify all driver's PowerNotify routines
|
||
//
|
||
|
||
for (Link = PopDeviceList.Flink; Link != &PopDeviceList; Link = Link->Flink) {
|
||
DeviceObjectExt = CONTAINING_RECORD(Link, DEVOBJ_EXTENSION, AllDeviceObjects);
|
||
DeviceObject = DeviceObjectExt->DeviceObject;
|
||
DriverObject = DeviceObject->DriverObject;
|
||
|
||
//
|
||
// Notify device's PowerNotify routine
|
||
//
|
||
|
||
MajorFunction = DriverObject->MajorFunction[IRP_MJ_POWER_NOTIFY];
|
||
if (MajorFunction != IopInvalidDeviceRequest) {
|
||
MajorFunction (DeviceObject, (PIRP) 0);
|
||
}
|
||
}
|
||
#endif
|
||
}
|
||
|
||
#endif // _PNP_POWER_
|