/*++

Copyright (c) 1990  Microsoft Corporation

Module Name:

    ntapi.c

Abstract:

    NT api level routines for the po component reside in this file

Author:

    Bryan Willman (bryanwi) 14-Nov-1996

Revision History:

--*/


#include "pop.h"

#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE, NtSetThreadExecutionState)
#pragma alloc_text(PAGE, NtRequestWakeupLatency)
#pragma alloc_text(PAGE, NtInitiatePowerAction)
#pragma alloc_text(PAGE, NtGetDevicePowerState)
#pragma alloc_text(PAGE, NtCancelDeviceWakeupRequest)
#pragma alloc_text(PAGE, NtIsSystemResumeAutomatic)
#pragma alloc_text(PAGE, NtRequestDeviceWakeup)
#pragma alloc_text(PAGELK, NtSetSystemPowerState)
#endif

extern POBJECT_TYPE IoFileObjectType;

WORK_QUEUE_ITEM PopShutdownWorkItem;
WORK_QUEUE_ITEM PopUnlockAfterSleepWorkItem;
KEVENT          PopUnlockComplete;
extern ERESOURCE ExpTimeRefreshLock;

NTSYSAPI
NTSTATUS
NTAPI
NtSetThreadExecutionState(
    IN EXECUTION_STATE NewFlags,                // ES_xxx flags
    OUT EXECUTION_STATE *PreviousFlags
    )
/*++

Routine Description:

    Implements Win32 API functionality.  Tracks thread execution state
    attributes.  Keeps global count of all such attributes set.

Arguments:

    NewFlags        - Attributes to set or pulse

    PreviousFlags   - Threads 'set' attributes before applying NewFlags

Return Value:

    Status

--*/
{
    ULONG               OldFlags;
    PKTHREAD            Thread;
    KPROCESSOR_MODE     PreviousMode;
    NTSTATUS            Status;

    PAGED_CODE();

    Thread = KeGetCurrentThread();
    Status = STATUS_SUCCESS;

    //
    // Verify no reserved bits set
    //

    if (NewFlags & ~(ES_SYSTEM_REQUIRED | ES_DISPLAY_REQUIRED | ES_CONTINUOUS)) {
        return STATUS_INVALID_PARAMETER;
    }

    try {
        //
        // Verify callers params
        //

        PreviousMode = KeGetPreviousMode();
        if (PreviousMode != KernelMode) {
            ProbeForWriteUlong (PreviousFlags);
        }
    } except (EXCEPTION_EXECUTE_HANDLER) {
        Status = GetExceptionCode();
    }

    //
    // Get current flags
    //

    OldFlags = Thread->PowerState | ES_CONTINUOUS;

    if (NT_SUCCESS(Status)) {

        PopAcquirePolicyLock ();

        //
        // If the continous bit is set, modify current thread flags
        //

        if (NewFlags & ES_CONTINUOUS) {
            Thread->PowerState = (UCHAR) NewFlags;
            PopApplyAttributeState (NewFlags, OldFlags);
        } else {
            PopApplyAttributeState (NewFlags, 0);
        }

        //
        // Release the lock here, but don't steal the poor caller's thread to
        // do the work. Otherwise we can get in weird message loop deadlocks as
        // this thread is waiting for the USER32 thread, which is broadcasting a
        // system message to this thread's window.
        //
        PopReleasePolicyLock (FALSE);
        PopCheckForWork(TRUE);

        //
        // Return the results
        //

        try {
            *PreviousFlags = OldFlags;
        } except(EXCEPTION_EXECUTE_HANDLER) {
            Status = GetExceptionCode();
        }
    }

    return Status;
}

NTSYSAPI
NTSTATUS
NTAPI
NtRequestWakeupLatency(
    IN LATENCY_TIME latency             // LT_xxx flags
    )
/*++

Routine Description:

    Tracks process wakeup latecy attribute.  Keeps global count
    of all such attribute settings.

Arguments:

    latency     - Current latency setting for process

Return Value:

    Status

--*/
{
    PEPROCESS   Process;
    ULONG       OldFlags, NewFlags;


    PAGED_CODE();

    //
    // Verify latency is known
    //

    switch (latency) {
        case LT_DONT_CARE:
            NewFlags = ES_CONTINUOUS;
            break;

        case LT_LOWEST_LATENCY:
            NewFlags = ES_CONTINUOUS | POP_LOW_LATENCY;
            break;

        default:
            return STATUS_INVALID_PARAMETER;
    }


    Process = PsGetCurrentProcess();
    PopAcquirePolicyLock ();

    //
    // Get changes
    //

    OldFlags = Process->Pcb.PowerState | ES_CONTINUOUS;

    //
    // Udpate latency flag in process field
    //

    Process->Pcb.PowerState = (UCHAR) NewFlags;

    //
    // Handle flags
    //

    PopApplyAttributeState (NewFlags, OldFlags);

    //
    // Done
    //

    PopReleasePolicyLock (TRUE);
    return STATUS_SUCCESS;
}

NTSYSAPI
NTSTATUS
NTAPI
NtInitiatePowerAction(
    IN POWER_ACTION SystemAction,
    IN SYSTEM_POWER_STATE LightestSystemState,
    IN ULONG Flags,                 // POWER_ACTION_xxx flags
    IN BOOLEAN Asynchronous
    )
/*++

Routine Description:

    Implements functionality for Win32 APIs to initiate a power
    action.   Causes s/w initiated trigger of requested action.

Arguments:

    SystemAction    - The action to initiate

    LightestSystemState  - If a sleep action, the minimum state which must be
                           entered

    Flags           - Attributes of action

    Asynchronous    - Function should initiate action and return, or should wait
                      for the action to complete before returning

Return Value:

    Status

--*/
{
    KPROCESSOR_MODE         PreviousMode;
    POWER_ACTION_POLICY     Policy;
    POP_ACTION_TRIGGER      Trigger;
    PPOP_TRIGGER_WAIT       Wait;
    NTSTATUS                Status;

    PAGED_CODE();

    //
    // Verify callers access
    //

    PreviousMode = KeGetPreviousMode();
    if (!SeSinglePrivilegeCheck( SeShutdownPrivilege, PreviousMode )) {
        return STATUS_PRIVILEGE_NOT_HELD;
    }

    if (SystemAction == PowerActionWarmEject) {

        if (PreviousMode != KernelMode) {
            return STATUS_INVALID_PARAMETER_1;
        }
    }

    if (Flags & POWER_ACTION_LIGHTEST_FIRST) {

        return STATUS_INVALID_PARAMETER_3;
    }

    //
    // Build a policy & trigger to cause the action
    //

    RtlZeroMemory (&Policy, sizeof(Policy));
    RtlZeroMemory (&Trigger, sizeof(Trigger));

    Policy.Action = SystemAction;
    Policy.Flags  = Flags;
    Trigger.Type  = PolicyInitiatePowerActionAPI;
    Trigger.Flags = PO_TRG_SET;
    Status        = STATUS_SUCCESS;

    //
    // If this is a synchronous power action request attach trigger
    // wait structure to action
    //

    Wait = NULL;
    if (!Asynchronous) {
        Wait = ExAllocatePoolWithTag (
                    NonPagedPool,
                    sizeof (POP_TRIGGER_WAIT),
                    POP_PACW_TAG
                    );
        if (!Wait) {
            return STATUS_INSUFFICIENT_RESOURCES;
        }

        RtlZeroMemory (Wait, sizeof(POP_TRIGGER_WAIT));
        Wait->Status = STATUS_SUCCESS;
        Wait->Trigger = &Trigger;
        KeInitializeEvent (&Wait->Event, NotificationEvent, FALSE);
        Trigger.Flags |= PO_TRG_SYNC;
        Trigger.Wait = Wait;
    }

    //
    // Acquire lock and fire it
    //

    PopAcquirePolicyLock ();

    try {

        PopSetPowerAction(
            &Trigger,
            0,
            &Policy,
            LightestSystemState,
            SubstituteLightestOverallDownwardBounded
            );

    } except (PopExceptionFilter(GetExceptionInformation(), TRUE)) {
        Status = GetExceptionCode();
    }

    PopReleasePolicyLock (TRUE);

    //
    // If queued, wait for it to complete
    //

    if (Wait) {


        if (Wait->Link.Flink) {

            ASSERT(NT_SUCCESS(Status));
            Status = KeWaitForSingleObject (&Wait->Event, Suspended, KernelMode, TRUE, NULL);
            if (NT_SUCCESS(Status)) {
                Status = Wait->Status;
            }

            //
            // Remove wait block from the queue
            //

            PopAcquirePolicyLock ();
            RemoveEntryList (&Wait->Link);
            PopReleasePolicyLock (FALSE);
        } else {
            //
            // The wait block was not queued, it must have either failed or succeeded
            // immediately.
            //
            Status = Wait->Status;
        }

        ExFreePool (Wait);
    }

    return Status;
}

NTSYSAPI
NTSTATUS
NTAPI
NtSetSystemPowerState (
    IN POWER_ACTION SystemAction,
    IN SYSTEM_POWER_STATE LightestSystemState,
    IN ULONG Flags                  // POWER_ACTION_xxx flags
    )
/*++

Routine Description:

    N.B. This function is only called by Winlogon.

    Winlogon calls this function in response to the policy manager calling
    PopStateCallout once user mode operations have completed.

Arguments:

    SystemAction        - The current system action being processed.

    LightestSystemState - The min system state for the action.

    Flags               - The attribute flags for the action.


Return Value:

    Status

--*/
{
    KPROCESSOR_MODE         PreviousMode;
    NTSTATUS                Status, Status2;
    POWER_ACTION_POLICY     Action;
    BOOLEAN                 QueryDevices;
    BOOLEAN                 TimerRefreshLockOwned;
    BOOLEAN                 BootStatusUpdated;
    BOOLEAN                 VolumesFlushed;
    BOOLEAN                 PolicyLockOwned;
    BOOLEAN                 OptionsExhausted;
    PVOID                   WakeTimerObject;
    PVOID                   S4DozeObject;
    HANDLE                  S4DozeTimer;
    OBJECT_ATTRIBUTES       ObjectAttributes;
    TIMER_BASIC_INFORMATION TimerInformation;
    POP_ACTION_TRIGGER      Trigger;
    SYSTEM_POWER_STATE      DeepestSystemState;
    ULONGLONG               WakeTime;
    ULONGLONG               SleepTime;
    TIME_FIELDS             WakeTimeFields;
    LARGE_INTEGER           DueTime;
    POP_SUBSTITUTION_POLICY SubstitutionPolicy;
    NT_PRODUCT_TYPE         NtProductType;
    PIO_ERROR_LOG_PACKET    ErrLog;
    BOOLEAN                 WroteErrLog=FALSE;

    //
    // Verify callers access
    //

    PreviousMode = KeGetPreviousMode();
    if (PreviousMode != KernelMode) {
        if (!SeSinglePrivilegeCheck( SeShutdownPrivilege, PreviousMode )) {
            return STATUS_PRIVILEGE_NOT_HELD;
        }

        //
        // Turn into kernel mode operation
        //

        return ZwSetSystemPowerState (SystemAction, LightestSystemState, Flags);
    }

    //
    // disable registry's lazzy flusher
    //
    CmSetLazyFlushState(FALSE);

    //
    // Setup
    //

    Status = STATUS_SUCCESS;
    TimerRefreshLockOwned = FALSE;
    BootStatusUpdated = FALSE;
    VolumesFlushed = FALSE;
    S4DozeObject = NULL;
    WakeTimerObject = NULL;
    WakeTime = 0;

    RtlZeroMemory (&Action, sizeof(Action));
    Action.Action = SystemAction;
    Action.Flags  = Flags;

    RtlZeroMemory (&Trigger, sizeof(Trigger));
    Trigger.Type  = PolicySetPowerStateAPI;
    Trigger.Flags = PO_TRG_SET;

    //
    // Lock any code dealing with shutdown or sleep
    //
    // PopUnlockComplete event is used to make sure that any previous unlock
    // has completed before we try and lock everything again.
    //

    ASSERT(ExPageLockHandle);
    KeWaitForSingleObject(&PopUnlockComplete, WrExecutive, KernelMode, FALSE, NULL);
    MmLockPagableSectionByHandle(ExPageLockHandle);
    ExNotifyCallback (ExCbPowerState, (PVOID) PO_CB_SYSTEM_STATE_LOCK, (PVOID) 0);
    ExSwapinWorkerThreads(FALSE);

    //
    // Acquire policy manager lock
    //

    PopAcquirePolicyLock ();
    PolicyLockOwned = TRUE;

    //
    // If we're not in the callout state, don't re-enter.
    // The caller (paction.c) will handle the collision.
    //

    if (PopAction.State != PO_ACT_IDLE  &&  PopAction.State != PO_ACT_CALLOUT) {
        PoPrint (PO_PACT, ("NtSetSystemPowerState: already committed\n"));
        PopReleasePolicyLock (FALSE);
        MmUnlockPagableImageSection (ExPageLockHandle);
        ExSwapinWorkerThreads(TRUE);
        KeSetEvent(&PopUnlockComplete, 0, FALSE);

        //
        // try to catch weird case where we exit this routine with the
        // time refresh lock held.
        //
        ASSERT(!ExIsResourceAcquiredExclusive(&ExpTimeRefreshLock));
        return STATUS_ALREADY_COMMITTED;
    }

    if (PopAction.State == PO_ACT_IDLE) {
        //
        // If there is no other request, we want to clean up PopAction before we start,
        // PopSetPowerAction() will not do this after we set State=PO_ACT_SET_SYSTEM_STATE.
        //
        PopResetActionDefaults();
    }
    //
    // Update to action state to setting the system state
    //

    PopAction.State = PO_ACT_SET_SYSTEM_STATE;

    //
    // Set status to cancelled to start off as if this is a new request
    //

    Status = STATUS_CANCELLED;

    try {

        //
        // Verify params and promote the current action.
        //
        PopSetPowerAction(
            &Trigger,
           0,
           &Action,
           LightestSystemState,
           SubstituteLightestOverallDownwardBounded
           );

    } except (EXCEPTION_EXECUTE_HANDLER) {
        Status = GetExceptionCode();
        ASSERT (!NT_SUCCESS(Status));
    }

    //
    // Lagecy hal support.  If the original action was PowerDown
    // change the action to be power down (as presumbly even if
    // there's no handler HalReturnToFirmware will know what to do)
    //

    if (SystemAction == PowerActionShutdownOff) {
        PopAction.Action = PowerActionShutdownOff;
    }

    //
    // Allocate the DevState here. From this point out we must be careful
    // that we never release the policy lock with State == PO_ACT_SET_SYSTEM_STATE
    // and PopAction.DevState not valid. Otherwise there is a race condition
    // with PopRestartSetSystemState.
    //
    PopAllocateDevState();
    if (PopAction.DevState == NULL) {
        PopAction.State = PO_ACT_IDLE;
        PopReleasePolicyLock(FALSE);
        MmUnlockPagableImageSection( ExPageLockHandle );
        ExSwapinWorkerThreads(TRUE);
        KeSetEvent(&PopUnlockComplete, 0, FALSE);
        //
        // try to catch weird case where we exit this routine with the
        // time refresh lock held.
        //
        ASSERT(!ExIsResourceAcquiredExclusive(&ExpTimeRefreshLock));
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    //
    // At this point in the cycle, its not possible to abort the operation
    // so this is a good time to ensure that the CPU is back running as close
    // to 100% as we can make it.
    //
    PopSetPerfFlag( PSTATE_DISABLE_THROTTLE_NTAPI, FALSE );
    PopUpdateAllThrottles();

    //
    // While there's some action pending handle it.
    //
    // N.B. We will never get here if no sleep states are supported, as
    // NtInitiatePowerAction will fail (PopVerifyPowerActionPolicy will return
    // Disabled == TRUE). Therefore we won't accidentally querying for S0. Note
    // that all the policy limitations were also verified at some point too.
    //

    for (; ;) {
        //
        // N.B. The system must be in the working state to be here
        //

        if (!PolicyLockOwned) {
            PopAcquirePolicyLock ();
            PolicyLockOwned = TRUE;
        }

        //
        // If there's nothing to do, stop
        //

        if (PopAction.Action == PowerActionNone) {
            break;
        }

        //
        // Hibernate actions are converted to sleep actions before here.
        //

        ASSERT (PopAction.Action != PowerActionHibernate);

        //
        // We're handling it - clear update flags
        //

        PopAction.Updates &= ~(PO_PM_USER | PO_PM_REISSUE | PO_PM_SETSTATE);

        //
        // If the last operation was cancelled, update state for the
        // new operation
        //

        if (Status == STATUS_CANCELLED) {

            //
            // If Re-issue is set we may need to abort back to PopSetPowerAction
            // to let apps know of the promotion
            //

            if (PopAction.Updates & PO_PM_REISSUE) {

                //
                // Only abort if apps notificiation is allowed
                //

                if (!(PopAction.Flags & (POWER_ACTION_CRITICAL))  &&
                     (PopAction.Flags & (POWER_ACTION_QUERY_ALLOWED |
                                         POWER_ACTION_UI_ALLOWED))
                    ) {

                    // abort with STATUS_CANCELLED to PopSetPowerAction
                    PopGetPolicyWorker (PO_WORKER_ACTION_NORMAL);
                    break;
                }
            }

            //
            // Get limits and start (over) with the first sleep state to try.
            //
            PopActionRetrieveInitialState(
                &PopAction.LightestState,
                &DeepestSystemState,
                &PopAction.SystemState,
                &QueryDevices
                );

            ASSERT (PopAction.SystemState != PowerActionNone);

            if ((PopAction.Action == PowerActionShutdown) ||
                (PopAction.Action == PowerActionShutdownReset) ||
                (PopAction.Action == PowerActionShutdownOff)) {

                //
                // This is a shutdown.
                //
                PopAction.Shutdown = TRUE;

            }

            Status = STATUS_SUCCESS;
        }

        //
        // Quick debug check. Our first sleep state must always be valid, ie
        // validation doesn't change it.
        //
#if DBG
        if (QueryDevices && (PopAction.SystemState < PowerSystemShutdown)) {

            SYSTEM_POWER_STATE TempSystemState;

            TempSystemState = PopAction.SystemState;
            PopVerifySystemPowerState(&TempSystemState, SubstituteLightestOverallDownwardBounded);

            if ((TempSystemState != PopAction.SystemState) ||
                (TempSystemState == PowerSystemWorking)) {

                PopInternalError (POP_INFO);
            }
        }
#endif

        //
        // If not success, abort SetSystemPowerState operation
        //

        if (!NT_SUCCESS(Status)) {
            break;
        }

        //
        // Only need the lock while updating PopAction.Action + Updates, and
        // can not hold the lock while sending irps to device drivers
        //

        PopReleasePolicyLock(FALSE);
        PolicyLockOwned = FALSE;

        //
        // Fish PopSimulate out of the registry so that it can
        // modify some of our sleep/hiber behavior.
        //

        PopInitializePowerPolicySimulate();

        //
        // Dump any previous device state error
        //

        PopReportDevState (FALSE);

        //
        // What would be our next state to try?
        //
        PopAction.NextSystemState = PopAction.SystemState;
        if (PopAction.Flags & POWER_ACTION_LIGHTEST_FIRST) {

            //
            // We started light, now we deepen our sleep state.
            //
            SubstitutionPolicy = SubstituteDeepenSleep;

        } else {

            //
            // We started deep, now we're lightening up.
            //
            SubstitutionPolicy = SubstituteLightenSleep;
        }

        PopAdvanceSystemPowerState(&PopAction.NextSystemState,
                                   SubstitutionPolicy,
                                   PopAction.LightestState,
                                   DeepestSystemState);

        //
        // If allowed, query devices
        //

        PopAction.IrpMinor = IRP_MN_QUERY_POWER;
        if (QueryDevices) {

            //
            // Issue query to devices
            //

            Status = PopSetDevicesSystemState (FALSE);

            //
            // If the last operation was a failure, but wasn't a total abort
            // continue with next best state
            //

            if (!NT_SUCCESS(Status) && Status != STATUS_CANCELLED) {

                //
                // Try next sleep state
                //
                PopAction.SystemState = PopAction.NextSystemState;

                //
                // If we're already exhausted all possible states, check
                // if we need to continue regardless of the device failures.
                //

                if (PopAction.SystemState == PowerSystemWorking) {

                    if (PopAction.Flags & POWER_ACTION_CRITICAL) {

                        //
                        // It's critical.  Stop querying and since the devices
                        // aren't particularly happy with any of the possible
                        // states, might as well use the max state
                        //

                        ASSERT( PopAction.Action != PowerActionWarmEject );
                        ASSERT( !(PopAction.Flags & POWER_ACTION_LIGHTEST_FIRST) );

                        QueryDevices = FALSE;
                        PopAction.SystemState = DeepestSystemState;
                        PopAction.Flags &= ~POWER_ACTION_LIGHTEST_FIRST;

                    } else {

                        //
                        // The query failure is final.  Don't retry
                        //

                        break;
                    }
                }

                //
                // Try new settings
                //

                Status = STATUS_SUCCESS;
                continue;
            }
        }

        //
        // If some error, start over
        //

        if (!NT_SUCCESS(Status)) {
            continue;
        }

        //
        // Flush out any D irps on the queue. There shouldn't be any, but by
        // setting LastCall == TRUE this also resets PopCallSystemState so that
        // any D irps which occur as a side-effect of flushing the volumes get
        // processed correctly.
        //
        PopSystemIrpDispatchWorker(TRUE);

        //
        // If this is a server and we are going into hibernation, write an entry
        // into the eventlog. This allows for easy tracking of system downtime
        // by searching the eventlog for hibernate/resume events.
        //
        if (RtlGetNtProductType(&NtProductType) &&
            (NtProductType != NtProductWinNt)   &&
            (PopAction.SystemState == PowerSystemHibernate)) {

            ErrLog = IoAllocateGenericErrorLogEntry(sizeof(IO_ERROR_LOG_PACKET));
            if (ErrLog) {

                //
                // Fill it in and write it out
                //
                ErrLog->FinalStatus = STATUS_HIBERNATED;
                ErrLog->ErrorCode = STATUS_HIBERNATED;
                IoWriteErrorLogEntry(ErrLog);
                WroteErrLog = TRUE;
            }
        }


        //
        // Get hibernation context
        //


        Status = PopAllocateHiberContext ();
        if (!NT_SUCCESS(Status) || (PopAction.Updates & (PO_PM_REISSUE | PO_PM_SETSTATE))) {
              continue;
        }

        //
        // If boot status hasn't already been updated then do so now.
        //

        if(!BootStatusUpdated) {

            if(PopAction.Shutdown) {

                NTSTATUS bsdStatus;
                HANDLE bsdHandle;

                bsdStatus = RtlLockBootStatusData(&bsdHandle);

                if(NT_SUCCESS(bsdStatus)) {

                    BOOLEAN t = TRUE;

                    RtlGetSetBootStatusData(bsdHandle,
                                            FALSE,
                                            RtlBsdItemBootShutdown,
                                            &t,
                                            sizeof(t),
                                            NULL);

                    RtlUnlockBootStatusData(bsdHandle);
                }
            }

            BootStatusUpdated = TRUE;
        }

        //
        // If not already flushed, flush the volumes
        //

        if (!VolumesFlushed) {
            VolumesFlushed = TRUE;
            PopFlushVolumes ();
        }

        //
        // Enter the SystemState
        //

        PopAction.IrpMinor = IRP_MN_SET_POWER;
        if (PopAction.Shutdown) {

            //
            // Force reacquisition of the dev list. We will be telling Pnp
            // to unload all possible devices, and therefore Pnp needs us to
            // release the Pnp Engine Lock.
            //
            IoFreePoDeviceNotifyList(&PopAction.DevState->Order);
            PopAction.DevState->GetNewDeviceList = TRUE;

            //
            // We shut down via a system worker thread so that the
            // current active process will exit cleanly.
            //

            if (PsGetCurrentProcess() != PsInitialSystemProcess) {

                ExInitializeWorkItem(&PopShutdownWorkItem,
                                     &PopGracefulShutdown,
                                     NULL);

                ExQueueWorkItem(&PopShutdownWorkItem,
                                PO_SHUTDOWN_QUEUE);

                // Clean up in prep for wait...
                ASSERT(!PolicyLockOwned);

                //
                // If we acquired the timer refresh lock (can happen if we promoted to shutdown)
                // then we need to release it so that suspend actually suspends.
                //
                if (TimerRefreshLockOwned) {
                    ExReleaseTimeRefreshLock();
                }

                // And sleep until we're terminated.

                // Note that we do NOT clean up the dev state -- it's now
                // owned by the shutdown worker thread.

                // Note that we also do not unlock the pagable image
                // section referred to by ExPageLockHandle -- this keeps
                // all of our shutdown code in memory.

                KeSuspendThread(KeGetCurrentThread());

                return STATUS_SYSTEM_SHUTDOWN;
            } else {
                PopGracefulShutdown (NULL);
            }
        }

        //
        // Get the timer refresh lock to hold off automated time of day
        // adjustments.  On wake the time will be explicitly reset from Cmos
        //

        if (!TimerRefreshLockOwned) {
            TimerRefreshLockOwned = TRUE;
            ExAcquireTimeRefreshLock(TRUE);
        }

        // This is where PopAllocateHiberContext used to be before bug #212420

        //
        // If there's a Doze to S4 timeout set, and this wasn't an S4 action
        // and the system can support and S4 state, set a timer for the doze time
        //
        // N.B. this must be set before the paging devices are turned off
        //

        if (S4DozeObject) {
            S4DozeObject = NULL;
            NtClose (S4DozeTimer);
        }

        if (PopPolicy->DozeS4Timeout  &&
            !S4DozeObject &&
            PopAction.SystemState != PowerSystemHibernate &&
            SystemAction != PowerActionHibernate &&
            PopCapabilities.SystemS4 &&
            PopCapabilities.SystemS5 &&
            PopCapabilities.HiberFilePresent) {

            //
            // Create a timer to wake the machine up when we need to hibernate
            //

            InitializeObjectAttributes (&ObjectAttributes, NULL, 0, NULL, NULL);

            Status2 = NtCreateTimer (
                        &S4DozeTimer,
                        TIMER_ALL_ACCESS,
                        &ObjectAttributes,
                        NotificationTimer
                        );

            if (NT_SUCCESS(Status2)) {

                //
                // Get the timer object for this timer
                //

                Status2 = ObReferenceObjectByHandle (
                             S4DozeTimer,
                             TIMER_ALL_ACCESS,
                             NULL,
                             KernelMode,
                             &S4DozeObject,
                             NULL
                             );

                ASSERT(NT_SUCCESS(Status2));
                ObDereferenceObject(S4DozeObject);
            }
        }

        //
        // Inform drivers of the system sleeping state
        //

        Status = PopSetDevicesSystemState (FALSE);
        if (!NT_SUCCESS(Status)) {
            continue;
        }

        //
        // Drivers have been informed, this operation is now committed,
        // get the next wakeup time
        //

        if (!(PopAction.Flags & POWER_ACTION_DISABLE_WAKES)) {
            //
            // Set S4Doze wakeup timer
            //

            if (S4DozeObject) {
                DueTime.QuadPart = -(LONGLONG) (US2SEC*US2TIME) * PopPolicy->DozeS4Timeout;
                NtSetTimer(S4DozeTimer, &DueTime, NULL, NULL, TRUE, 0, NULL);
            }

            ExGetNextWakeTime(&WakeTime, &WakeTimeFields, &WakeTimerObject);
        }

        //
        // Only enable RTC wake if the system is going to an S-state that
        // supports the RTC wake.
        //
        if (PopCapabilities.RtcWake != PowerSystemUnspecified &&
            PopCapabilities.RtcWake >= PopAction.SystemState &&
            WakeTime) {

#if DBG
            ULONGLONG       InterruptTime;

            InterruptTime = KeQueryInterruptTime();
            PoPrint (PO_PACT, ("Wake alarm set%s: %d:%02d:%02d %d (%d seconds from now)\n",
                WakeTimerObject == S4DozeObject ? " for s4doze" : "",
                WakeTimeFields.Hour,
                WakeTimeFields.Minute,
                WakeTimeFields.Second,
                WakeTimeFields.Year,
                (WakeTime - InterruptTime) / (US2TIME * US2SEC)
                ));
#endif
            HalSetWakeEnable(TRUE);
            HalSetWakeAlarm(WakeTime, &WakeTimeFields);

        } else {

            HalSetWakeEnable(TRUE);
            HalSetWakeAlarm( 0, NULL );

        }

        //
        // Capture the last sleep time.
        //
        SleepTime = KeQueryInterruptTime();

        //
        // Implement system handler for sleep operation
        //

        Status = PopSleepSystem (PopAction.SystemState,
                                 PopAction.HiberContext);
        //
        // A sleep or shutdown operation attempt was performed, clean up
        //

        break;
    }

    //
    // If the system slept successfully, update the system time to
    // match the CMOS clock.
    //
    if (NT_SUCCESS(Status)) {
        PopAction.SleepTime = SleepTime;
        ASSERT(TimerRefreshLockOwned);
        ExUpdateSystemTimeFromCmos (TRUE, 1);

        PERFINFO_HIBER_START_LOGGING();
    }

    //
    // If DevState was allocated, notify drivers the system is awake
    //

    if (PopAction.DevState) {

        //
        // Log any failures
        //

        PopReportDevState (TRUE);

        //
        // Notify drivers that the system is now running
        //
        PopSetDevicesSystemState (TRUE);

    }

    //
    // Free the device notify list. This must be done before acquiring
    // the policy lock, otherwise we can deadlock with the PNP device
    // tree lock.
    //
    ASSERT(PopAction.DevState != NULL);
    IoFreePoDeviceNotifyList(&PopAction.DevState->Order);

    //
    // Get the policy lock for the rest of the cleanup
    //

    if (!PolicyLockOwned) {
        PopAcquirePolicyLock ();
        PolicyLockOwned = TRUE;
    }

    //
    // Cleanup DevState
    //
    PopCleanupDevState ();

    if (NT_SUCCESS(Status)) {

        //
        // Now that the time has been fixed, record the last state
        // the system has awoken from and the current time
        //

        PopAction.LastWakeState = PopAction.SystemState;
        PopAction.WakeTime = KeQueryInterruptTime();

        //
        // If full wake hasn't been signalled, then set then start a
        // really agressive idle detection
        //

        if (!AnyBitsSet (PopFullWake, PO_FULL_WAKE_STATUS | PO_FULL_WAKE_PENDING)) {

            //
            // If there was an S4Doze timer set, check to see if it's
            // expired and update the idle detection to enter S4
            //

            if (S4DozeObject) {

                NtQueryTimer (S4DozeTimer,
                              TimerBasicInformation,
                              &TimerInformation,
                              sizeof (TimerInformation),
                              NULL);

                if (TimerInformation.TimerState) {

                    //
                    // Update the idle detection action to be hibernate
                    //

                    PoPrint (PO_PACT, ("Wake with S4 timer expired\n"));

                    //
                    // If the s4timer was the alarm time, and we're awake
                    // in under the idle reenter time, just drop right into
                    // S4 without any idle detection.  (we check the current
                    // time in case  in case the alarm time expired but for
                    // some reason the system did not wake at that time)
                    //

                    if ((WakeTimerObject == S4DozeObject) &&
                        (PopAction.WakeTime - WakeTime <
                            SYS_IDLE_REENTER_TIMEOUT * US2TIME * US2SEC)) {

                        PopAction.Action = PowerActionSleep;
                        PopAction.LightestState = PowerSystemHibernate;
                        PopAction.Updates |= PO_PM_REISSUE;
                    }
                }

            }

            //
            // Set the system idle detection code to re-enter this state
            // real agressively (assuming a full wake doesn't happen)
            //

            PopInitSIdle ();
        }
    }

    //
    // Free anything that's left of the hiber context
    //

    PopFreeHiberContext (TRUE);

    //
    // Clear out PopAction unless we have promoted directly to hibernate
    //
    if ((PopAction.Updates & PO_PM_REISSUE) == 0) {
        PopResetActionDefaults();
    }

    //
    // We are no longer active
    // We don't check for work here as this may be "the thread" from winlogon.
    // So we explicitly queue pending policy work off to a worker thread below
    // after setting the win32k wake notifications.
    //

    PopAction.State = PO_ACT_CALLOUT;
    PopReleasePolicyLock (FALSE);

    //
    // If there's been some sort of error, make sure gdi is enabled
    //

    if (!NT_SUCCESS(Status)) {
        PopDisplayRequired (0);
    }

    //
    // If some win32k wake event is pending, tell win32k
    //

    if (PopFullWake & PO_FULL_WAKE_PENDING) {
        PopSetNotificationWork (PO_NOTIFY_FULL_WAKE);
    } else if (PopFullWake & PO_GDI_ON_PENDING) {
        PopSetNotificationWork (PO_NOTIFY_DISPLAY_REQUIRED);
    }

    //
    // If the timer refresh lock was acquired, release it
    //

    if (TimerRefreshLockOwned) {
        ExReleaseTimeRefreshLock();
    } else {
        //
        // try to catch weird case where we exit this routine with the
        // time refresh lock held.
        //
        ASSERT(!ExIsResourceAcquiredExclusive(&ExpTimeRefreshLock));
    }

    //
    // Unlock pageable code. The unlock is queued off to a delayed worker queue
    // since it is likely to block on pagable code, registry, etc. The PopUnlockComplete
    // event is used to prevent the unlock from racing with a subsequent lock.
    //
    ExQueueWorkItem(&PopUnlockAfterSleepWorkItem, DelayedWorkQueue);

    //
    // If a timer for s4 dozing was allocated, close it
    //

    if (S4DozeObject) {
        NtClose (S4DozeTimer);
    }

    //
    // If we wrote an errlog message indicating that we were hibernating, write a corresponding
    // one to indicate we have woken.
    //
    if (WroteErrLog) {

        ErrLog = IoAllocateGenericErrorLogEntry(sizeof(IO_ERROR_LOG_PACKET));
        if (ErrLog) {

            //
            // Fill it in and write it out
            //
            ErrLog->FinalStatus = STATUS_RESUME_HIBERNATION;
            ErrLog->ErrorCode = STATUS_RESUME_HIBERNATION;
            IoWriteErrorLogEntry(ErrLog);
        }
    }

    //
    // Finally, we can revert the throttle back to a normal value
    //
    PopSetPerfFlag( PSTATE_DISABLE_THROTTLE_NTAPI, TRUE );
    PopUpdateAllThrottles();

    //
    // Done - kick off the policy worker thread to process any outstanding work in
    // a worker thread.
    //
    PopCheckForWork(TRUE);
    //
    // enable registry's lazzy flusher
    //
    CmSetLazyFlushState(TRUE);

    //
    // try to catch weird case where we exit this routine with the
    // time refresh lock held.
    //
    ASSERT(!ExIsResourceAcquiredExclusive(&ExpTimeRefreshLock));
    return Status;
}


NTSYSAPI
NTSTATUS
NTAPI
NtRequestDeviceWakeup(
    IN HANDLE Device
    )
/*++

Routine Description:

    This routine requests a WAIT_WAKE Irp on the specified handle.

    If the handle is to a device object, the WAIT_WAKE irp is sent
    to the top of that device's stack.

    If a WAIT_WAKE is already outstanding on the device, this routine
    increments the WAIT_WAKE reference count and return success.

Arguments:

    Device - Supplies the device which should wake the system

Return Value:

    NTSTATUS

--*/

{
    PFILE_OBJECT fileObject;
    PDEVICE_OBJECT deviceObject;
    PDEVICE_OBJECT targetDevice;
    NTSTATUS status;
    PDEVICE_OBJECT_POWER_EXTENSION dope;

    //
    // Reference the file object in order to get to the device object
    // in question.
    //
    status = ObReferenceObjectByHandle(Device,
                                       0L,
                                       IoFileObjectType,
                                       KeGetPreviousMode(),
                                       (PVOID *)&fileObject,
                                       NULL);
    if (!NT_SUCCESS(status)) {
        return(status);
    }

    //
    // Get the address of the target device object.
    //
    if (!(fileObject->Flags & FO_DIRECT_DEVICE_OPEN)) {
        deviceObject = IoGetAttachedDeviceReference( IoGetRelatedDeviceObject( fileObject ));
    } else {
        deviceObject = IoGetAttachedDeviceReference( fileObject->DeviceObject );
    }

    //
    // Now that we have the device object, we are done with the file object
    //
    ObDereferenceObject(fileObject);

    ObDereferenceObject(deviceObject);
    return (STATUS_NOT_IMPLEMENTED);
}


NTSYSAPI
NTSTATUS
NTAPI
NtCancelDeviceWakeupRequest(
    IN HANDLE Device
    )
/*++

Routine Description:

    This routine cancels a WAIT_WAKE irp sent to a device previously
    with NtRequestDeviceWakeup.

    The WAIT_WAKE reference count on the device is decremented. If this
    count goes to zero, the WAIT_WAKE irp is cancelled.

Arguments:

    Device - Supplies the device which should wake the system

Return Value:

    NTSTATUS

--*/

{
    return(STATUS_NOT_IMPLEMENTED);
}


NTSYSAPI
BOOLEAN
NTAPI
NtIsSystemResumeAutomatic(
    VOID
    )
/*++

Routine Description:

    Returns whether or not the most recent wake was automatic
    or due to a user action.

Arguments:

    None

Return Value:

    TRUE - The system was awakened due to a timer or device wake

    FALSE - The system was awakened due to a user action

--*/

{
    if (AnyBitsSet(PopFullWake, PO_FULL_WAKE_STATUS | PO_FULL_WAKE_PENDING)) {
        return(FALSE);
    } else {
        return(TRUE);
    }
}


NTSYSAPI
NTSTATUS
NTAPI
NtGetDevicePowerState(
    IN HANDLE Device,
    OUT DEVICE_POWER_STATE *State
    )
/*++

Routine Description:

    Queries the current power state of a device.

Arguments:

    Device - Supplies the handle to a device.

    State - Returns the current power state of the device.

Return Value:

    NTSTATUS

--*/

{
    PFILE_OBJECT fileObject;
    PDEVICE_OBJECT deviceObject;
    NTSTATUS status;
    PDEVOBJ_EXTENSION   doe;
    KPROCESSOR_MODE     PreviousMode;
    DEVICE_POWER_STATE dev_state;

    PAGED_CODE();

    //
    // Verify caller's parameter
    //
    PreviousMode = KeGetPreviousMode();
    if (PreviousMode != KernelMode) {
        try {
            ProbeForWriteUlong((PULONG)State);
        } except (EXCEPTION_EXECUTE_HANDLER) {
            status = GetExceptionCode();
            return(status);
        }
    }

    //
    // Reference the file object in order to get to the device object
    // in question.
    //
    status = ObReferenceObjectByHandle(Device,
                                       0L,
                                       IoFileObjectType,
                                       KeGetPreviousMode(),
                                       (PVOID *)&fileObject,
                                       NULL);
    if (!NT_SUCCESS(status)) {
        return(status);
    }

    //
    // Get the address of the target device object.
    //
    status = IoGetRelatedTargetDevice(fileObject, &deviceObject);

    //
    // Now that we have the device object, we are done with the file object
    //
    ObDereferenceObject(fileObject);
    if (!NT_SUCCESS(status)) {
        return(status);
    }

    doe = deviceObject->DeviceObjectExtension;
    dev_state = PopLockGetDoDevicePowerState(doe);
    try {
        *State = dev_state;
    } except (EXCEPTION_EXECUTE_HANDLER) {
        status = GetExceptionCode();
    }

    ObDereferenceObject(deviceObject);
    return (status);
}