Windows NT 4.0 source code leak
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

754 lines
19 KiB

/*++
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_