/*++

Copyright (c) 2015 Minoca Corp. All Rights Reserved

Module Name:

    power.c

Abstract:

    This module implements generic support for device runtime power managment
    within the kernel.

Author:

    Evan Green 2-Sep-2015

Environment:

    Kernel

--*/

//
// ------------------------------------------------------------------- Includes
//

#include <minoca/kernel/kernel.h>
#include "pmp.h"
#include "iop.h"

//
// ---------------------------------------------------------------- Definitions
//

//
// Define the default delay in seconds before a device that has no power
// references is sent an idle request.
//

#define PM_INITIAL_IDLE_DELAY_SECONDS 1

//
// Define the number of data points of device idle history to keep, expressed
// as a bit shift.
//

#define PM_DEVICE_HISTORY_SIZE_SHIFT 5

//
// ------------------------------------------------------ Data Type Definitions
//

//
// ----------------------------------------------- Internal Function Prototypes
//

KSTATUS
PmpInitializeDevice (
    PDEVICE Device
    );

VOID
PmpDeviceIdleTimerDpc (
    PDPC Dpc
    );

VOID
PmpDeviceIdleWorker (
    PVOID Parameter
    );

VOID
PmpDeviceIncrementActiveChildren (
    PDEVICE Device
    );

VOID
PmpDeviceDecrementActiveChildren (
    PDEVICE Device
    );

KSTATUS
PmpDeviceAddReference (
    PDEVICE Device,
    DEVICE_POWER_REQUEST Request
    );

KSTATUS
PmpDeviceQueuePowerTransition (
    PDEVICE Device,
    DEVICE_POWER_REQUEST Request
    );

KSTATUS
PmpDeviceResume (
    PDEVICE Device,
    DEVICE_POWER_REQUEST Request
    );

VOID
PmpDeviceIdle (
    PDEVICE Device
    );

VOID
PmpDeviceSuspend (
    PDEVICE Device
    );

//
// -------------------------------------------------------------------- Globals
//

//
// Set this boolean to TRUE to print power transitions.
//

BOOL PmDebugPowerTransitions;

//
// ------------------------------------------------------------------ Functions
//

KERNEL_API
KSTATUS
PmInitialize (
    PDEVICE Device
    )

/*++

Routine Description:

    This routine initializes power management infrastructure for a given
    device.

Arguments:

    Device - Supplies a pointer to the device to prepare to do power management
        calls on.

Return Value:

    Status code.

--*/

{

    if (Device->Power != NULL) {
        return STATUS_SUCCESS;
    }

    return PmpInitializeDevice(Device);
}

KERNEL_API
KSTATUS
PmDeviceAddReference (
    PDEVICE Device
    )

/*++

Routine Description:

    This routine adds a power management reference on the given device,
    and waits for the device to transition to the active state.

Arguments:

    Device - Supplies a pointer to the device to add a power reference to.

Return Value:

    Status code. On failure, the caller will not have a reference on the
    device, and should not assume that the device or its parent lineage is
    active.

--*/

{

    return PmpDeviceAddReference(Device, DevicePowerRequestResume);
}

KERNEL_API
KSTATUS
PmDeviceAddReferenceAsynchronous (
    PDEVICE Device
    )

/*++

Routine Description:

    This routine adds a power management reference on the given device,
    preventing the device from idling until the reference is released.

Arguments:

    Device - Supplies a pointer to the device to add a power reference to.

Return Value:

    Status code indicating if the request was successfully queued. On failure,
    the caller will not have the reference on the device.

--*/

{

    UINTN PreviousCount;
    PDEVICE_POWER State;
    KSTATUS Status;

    State = Device->Power;
    PreviousCount = RtlAtomicAdd(&(State->ReferenceCount), 1);

    ASSERT(PreviousCount < 0x10000000);

    if (PreviousCount != 0) {
        return STATUS_SUCCESS;
    }

    Status = PmpDeviceQueuePowerTransition(Device, DevicePowerRequestResume);
    if (!KSUCCESS(Status)) {
        RtlAtomicAdd(&(State->ReferenceCount), -1);
    }

    return Status;
}

KERNEL_API
KSTATUS
PmDeviceReleaseReference (
    PDEVICE Device
    )

/*++

Routine Description:

    This routine releases a power management reference on a device.

Arguments:

    Device - Supplies a pointer to the device to subtract a power reference
        from.

Return Value:

    Status code indicating if the idle timer was successfully queued. The
    reference itself is always dropped, even on failure.

--*/

{

    UINTN PreviousCount;
    PDEVICE_POWER State;
    KSTATUS Status;
    ULONG TimerQueued;

    State = Device->Power;
    Status = STATUS_SUCCESS;
    PreviousCount = RtlAtomicAdd(&(State->ReferenceCount), -1);

    ASSERT((PreviousCount != 0) && (PreviousCount < 0x10000000));

    if (PreviousCount > 1) {
        return Status;
    }

    //
    // Bump up the idle deadline even if the timer is already queued. The
    // timer will see this and requeue itself.
    //

    State->IdleTimeout = HlQueryTimeCounter() + State->IdleDelay;

    //
    // Try to win the race to queue the timer.
    //

    TimerQueued = RtlAtomicCompareExchange32(&(State->TimerQueued), 1, 0);
    if (TimerQueued == 0) {
        Status = KeQueueTimer(State->IdleTimer,
                              TimerQueueSoftWake,
                              State->IdleTimeout,
                              0,
                              0,
                              State->IdleTimerDpc);

        if (!KSUCCESS(Status)) {
            RtlDebugPrint("PM: Cannot queue idle timer: device %x: %x\n",
                          Device,
                          Status);
        }
    }

    return Status;
}

KERNEL_API
KSTATUS
PmDeviceSetState (
    PDEVICE Device,
    DEVICE_POWER_STATE PowerState
    )

/*++

Routine Description:

    This routine sets a new power state for the device. This can be used to
    clear an error. It should not be called from a power IRP.

Arguments:

    Device - Supplies a pointer to the device to set the power state for.

    PowerState - Supplies the new power management state to set. The only
        valid states to set are active and suspended.

Return Value:

    Status code.

--*/

{

    PDEVICE_POWER State;
    KSTATUS Status;

    State = Device->Power;
    Status = STATUS_SUCCESS;
    switch (PowerState) {
    case DevicePowerStateActive:
        if (State->State == DevicePowerStateActive) {
            Status = STATUS_SUCCESS;

        } else {

            //
            // Add a reference on the device to bring it up, then release that
            // reference to send it down towards sleepytown.
            //

            Status = PmpDeviceAddReference(Device,
                                           DevicePowerRequestMarkActive);

            if (KSUCCESS(Status)) {
                Status = PmDeviceReleaseReference(Device);
            }
        }

        break;

    case DevicePowerStateSuspended:
        KeAcquireQueuedLock(State->Lock);
        if (State->State == DevicePowerStateRemoved) {
            Status = STATUS_DEVICE_NOT_CONNECTED;

        } else {
            if ((State->State == DevicePowerStateActive) ||
                ((State->State == DevicePowerStateTransitioning) &&
                 (State->PreviousState == DevicePowerStateActive))) {

                PmpDeviceDecrementActiveChildren(Device->ParentDevice);
            }

            State->State = DevicePowerStateSuspended;
            State->Request = DevicePowerRequestNone;
        }

        KeReleaseQueuedLock(State->Lock);
        break;

    case DevicePowerStateIdle:
    default:

        ASSERT(FALSE);

        Status = STATUS_INVALID_PARAMETER;
        break;
    }

    return Status;
}

KSTATUS
PmInitializeLibrary (
    VOID
    )

/*++

Routine Description:

    This routine performs global initialization for the power management
    library. It is called towards the end of I/O initialization.

Arguments:

    None.

Return Value:

    None.

--*/

{

    KSTATUS Status;

    Status = PmpArchInitialize();
    if (!KSUCCESS(Status)) {
        goto InitializeLibraryEnd;
    }

InitializeLibraryEnd:
    return Status;
}

VOID
PmpRemoveDevice (
    PDEVICE Device
    )

/*++

Routine Description:

    This routine is called when a device is removed from the system. It cleans
    up the power management state. It is assumed that the device lock is
    already held exclusive.

Arguments:

    Device - Supplies a pointer to the device to remove.

Return Value:

    None.

--*/

{

    DEVICE_POWER_STATE OldPreviousState;
    DEVICE_POWER_STATE OldState;
    PDEVICE_POWER State;

    State = Device->Power;
    if (State == NULL) {
        return;
    }

    //
    // A transition to the removed state is effective immediately, but must be
    // synchronized with all other transitions.
    //

    KeAcquireQueuedLock(State->Lock);
    OldState = State->State;
    OldPreviousState = State->PreviousState;
    State->State = DevicePowerStateRemoved;
    State->Request = DevicePowerRequestNone;
    RtlAtomicExchange32(&(State->TimerQueued), 1);
    KeCancelTimer(State->IdleTimer);
    KeCancelDpc(State->IdleTimerDpc);
    KeCancelWorkItem(State->IdleTimerWorkItem);
    if (OldState != DevicePowerStateTransitioning) {
        State->PreviousState = OldState;
    }

    KeReleaseQueuedLock(State->Lock);

    //
    // If an active child was just removed, decrement the parent's count.
    //

    if ((OldState == DevicePowerStateActive) ||
        ((OldState == DevicePowerStateTransitioning) &&
         (OldPreviousState == DevicePowerStateActive))) {

        PmpDeviceDecrementActiveChildren(Device->ParentDevice);
    }

    return;
}

VOID
PmpDestroyDevice (
    PDEVICE Device
    )

/*++

Routine Description:

    This routine tears down the power management structures associated with a
    device.

Arguments:

    Device - Supplies a pointer to the device to tear down.

Return Value:

    None.

--*/

{

    PDEVICE_POWER State;

    if (Device->Power != NULL) {
        State = Device->Power;

        //
        // Work through the timer, DPC, work item flow, starting at the source
        // and squeezing the tube along the way.
        //

        if (State->IdleTimer != NULL) {
            KeDestroyTimer(State->IdleTimer);
        }

        if (State->IdleTimerDpc != NULL) {
            KeDestroyDpc(State->IdleTimerDpc);
        }

        if (State->IdleTimerWorkItem != NULL) {
            KeCancelWorkItem(State->IdleTimerWorkItem);
            KeFlushWorkItem(State->IdleTimerWorkItem);
            KeDestroyWorkItem(State->IdleTimerWorkItem);
        }

        if (State->ActiveEvent != NULL) {
            KeDestroyEvent(State->ActiveEvent);
        }

        if (State->Lock != NULL) {
            KeDestroyQueuedLock(State->Lock);
        }

        if (State->Irp != NULL) {
            IoDestroyIrp(State->Irp);
        }

        if (State->History != NULL) {
            PmpDestroyIdleHistory(State->History);
        }

        Device->Power = NULL;
        MmFreeNonPagedPool(State);
    }

    return;
}

VOID
PmpDevicePowerTransition (
    PDEVICE Device
    )

/*++

Routine Description:

    This routine is called by the worker thread to perform a device power
    transition.

Arguments:

    Device - Supplies a pointer to the device to work on.

Return Value:

    None.

--*/

{

    PDEVICE_POWER State;

    State = Device->Power;
    if (State->State == DevicePowerStateTransitioning) {
        switch (State->Request) {
        case DevicePowerRequestIdle:
            PmpDeviceIdle(Device);
            break;

        case DevicePowerRequestSuspend:
            PmpDeviceSuspend(Device);
            break;

        //
        // It is OK to do a second unprotected read of the state's request.
        // When a resume or activate request is set, no other request can trump
        // it, not even another resume or active (as only one thread grabs the
        // first reference on the device and starts the resume process).
        //

        case DevicePowerRequestResume:
        case DevicePowerRequestMarkActive:
            PmpDeviceResume(Device, State->Request);
            break;

        default:
            break;
        }
    }

    return;
}

//
// --------------------------------------------------------- Internal Functions
//

KSTATUS
PmpInitializeDevice (
    PDEVICE Device
    )

/*++

Routine Description:

    This routine initializes the power management portion of a device structure.

Arguments:

    Device - Supplies a pointer to the device to initialize.

Return Value:

    Status code.

--*/

{

    PDEVICE_POWER Power;
    KSTATUS Status;

    Power = MmAllocateNonPagedPool(sizeof(DEVICE_POWER),
                                   PM_DEVICE_ALLOCATION_TAG);

    if (Power == NULL) {
        Status = STATUS_INSUFFICIENT_RESOURCES;
    }

    RtlZeroMemory(Power, sizeof(DEVICE_POWER));
    Device->Power = Power;
    Power->State = DevicePowerStateSuspended;
    Power->IdleDelay = HlQueryTimeCounterFrequency() *
                       PM_INITIAL_IDLE_DELAY_SECONDS;

    Power->Lock = KeCreateQueuedLock();
    Power->ActiveEvent = KeCreateEvent(NULL);
    Power->IdleTimer = KeCreateTimer(PM_DEVICE_ALLOCATION_TAG);

    //
    // This work item should go on the same work queue as the device worker
    // thread to avoid an extra context switch.
    //

    Power->IdleTimerWorkItem = KeCreateWorkItem(IoDeviceWorkQueue,
                                                WorkPriorityNormal,
                                                PmpDeviceIdleWorker,
                                                Device,
                                                PM_DEVICE_ALLOCATION_TAG);

    Power->IdleTimerDpc = KeCreateDpc(PmpDeviceIdleTimerDpc,
                                      Power->IdleTimerWorkItem);

    Power->Irp = IoCreateIrp(Device, IrpMajorStateChange, 0);
    Power->History = PmpCreateIdleHistory(IDLE_HISTORY_NON_PAGED,
                                          PM_DEVICE_HISTORY_SIZE_SHIFT);

    if ((Power->Lock == NULL) ||
        (Power->ActiveEvent == NULL) ||
        (Power->IdleTimer == NULL) ||
        (Power->IdleTimerDpc == NULL) ||
        (Power->IdleTimerWorkItem == NULL) ||
        (Power->Irp == NULL) ||
        (Power->History == NULL)) {

        Status = STATUS_INSUFFICIENT_RESOURCES;
        goto InitializeDeviceEnd;
    }

    //
    // Start the active event as unsignaled since the device is in the
    // suspended state.
    //

    KeSignalEvent(Power->ActiveEvent, SignalOptionUnsignal);
    Status = STATUS_SUCCESS;

InitializeDeviceEnd:
    if (!KSUCCESS(Status)) {
        PmpDestroyDevice(Device);
    }

    return Status;
}

VOID
PmpDeviceIdleTimerDpc (
    PDPC Dpc
    )

/*++

Routine Description:

    This routine is called at dispatch level when the device's idle timer
    expires.

Arguments:

    Dpc - Supplies a pointer to the DPC that is running.

Return Value:

    None.

--*/

{

    KSTATUS Status;

    //
    // The user data for the DPC is the work item itself, which is important
    // since the power state structure is paged and cannot be touched here.
    //

    Status = KeQueueWorkItem(Dpc->UserData);

    ASSERT(KSUCCESS(Status));

    return;
}

VOID
PmpDeviceIdleWorker (
    PVOID Parameter
    )

/*++

Routine Description:

    This routine implements the work item queued when a device's idle timer
    expires.

Arguments:

    Parameter - Supplies a context parameter, which in this case is a pointer
        to the device.

Return Value:

    None.

--*/

{

    ULONGLONG CurrentTime;
    PDEVICE Device;
    PDEVICE_POWER State;
    KSTATUS Status;
    ULONGLONG Timeout;
    ULONG TimerQueued;

    Device = Parameter;
    State = Device->Power;

    //
    // The timer gets left on a lot, even if it's no longer needed. If there
    // are references on the device now, just do nothing.
    //

    if (State->ReferenceCount != 0) {
        RtlAtomicExchange32(&(State->TimerQueued), 0);

        //
        // Assuming there are still references, then this is done.
        //

        if (State->ReferenceCount != 0) {
            return;

        //
        // The references went away, but may have lost the race to queue the
        // timer. Try to reclaim the right to queue the timer. If someone else
        // got there first, then the timer is requeued and doesn't need
        // the rest of this routine.
        //

        } else {
            TimerQueued = RtlAtomicCompareExchange32(&(State->TimerQueued),
                                                     1,
                                                     0);

            if (TimerQueued == 0) {
                return;
            }
        }
    }

    //
    // If the idle timeout has moved beyond the current time, then re-queue
    // the timer for that new time.
    //

    CurrentTime = HlQueryTimeCounter();
    Timeout = State->IdleTimeout;
    if (CurrentTime < Timeout) {
        Status = KeQueueTimer(State->IdleTimer,
                              TimerQueueSoftWake,
                              Timeout,
                              0,
                              0,
                              State->IdleTimerDpc);

        if (!KSUCCESS(Status)) {
            RtlAtomicExchange32(&(State->TimerQueued), 0);
        }

    //
    // The idle timer really did expire. Reset the timer queued variable, and
    // queue the idle transition.
    //

    } else {
        RtlAtomicExchange32(&(State->TimerQueued), 0);
        Status = PmpDeviceQueuePowerTransition(Device, DevicePowerRequestIdle);
        if (!KSUCCESS(Status)) {
            RtlDebugPrint("PM: Failed to queue idle work: %x %x\n",
                          Device,
                          Status);
        }
    }

    return;
}

VOID
PmpDeviceDecrementActiveChildren (
    PDEVICE Device
    )

/*++

Routine Description:

    This routine decrements the active child count on a given device.

Arguments:

    Device - Supplies a pointer to the device to subtract an active child count
        from.

Return Value:

    None.

--*/

{

    UINTN PreviousCount;
    PDEVICE_POWER State;

    State = Device->Power;
    if (State == NULL) {
        return;
    }

    PreviousCount = RtlAtomicAdd(&(State->ActiveChildren), -1);

    ASSERT((PreviousCount != 0) && (PreviousCount < 0x10000000));

    //
    // If this is the first active child, release a power reference on this
    // device.
    //

    if (PreviousCount == 1) {
        PmDeviceReleaseReference(Device);
    }

    return;
}

KSTATUS
PmpDeviceAddReference (
    PDEVICE Device,
    DEVICE_POWER_REQUEST Request
    )

/*++

Routine Description:

    This routine asynchronously adds a power management reference on the given
    device.

Arguments:

    Device - Supplies a pointer to the device to add a power reference to.

    Request - Supplies the request type, which can either be actually becoming
        active or just marking it as such.

Return Value:

    Status code.

--*/

{

    UINTN PreviousCount;
    PDEVICE_POWER State;
    KSTATUS Status;

    Status = STATUS_SUCCESS;
    State = Device->Power;
    PreviousCount = RtlAtomicAdd(&(State->ReferenceCount), 1);

    ASSERT(PreviousCount < 0x10000000);

    //
    // Attempt to the resume the device if this is the first reference.
    //

    if (PreviousCount == 0) {
        Status = PmpDeviceResume(Device, Request);
        if (!KSUCCESS(Status)) {
            RtlAtomicAdd(&(State->ReferenceCount), -1);
        }

    //
    // All subsequent references need to wait for the active event and then
    // check the state to see if the resume succeeded.
    //

    } else {
        KeWaitForEvent(Device->Power->ActiveEvent, FALSE, WAIT_TIME_INDEFINITE);
        if (Device->Power->State != DevicePowerStateActive) {
            RtlAtomicAdd(&(State->ReferenceCount), -1);
            Status = STATUS_NOT_READY;
        }
    }

    return Status;
}

KSTATUS
PmpDeviceQueuePowerTransition (
    PDEVICE Device,
    DEVICE_POWER_REQUEST Request
    )

/*++

Routine Description:

    This routine queues a power request on a device.

Arguments:

    Device - Supplies a pointer to the device to affect.

    Request - Supplies the type of request to queue.

Return Value:

    Status code.

--*/

{

    BOOL QueueRequest;
    PDEVICE_POWER State;
    KSTATUS Status;

    State = Device->Power;
    Status = STATUS_SUCCESS;

    //
    // Do a quick exit for resuming a device that's not idle.
    //

    if ((Request == DevicePowerRequestResume) &&
        (State->State == DevicePowerStateActive)) {

        return STATUS_SUCCESS;
    }

    //
    // If the state is already what it should be and there are no other
    // requests, also exit.
    //

    QueueRequest = FALSE;
    KeAcquireQueuedLock(State->Lock);

    //
    // Don't bother if the same request is already queued.
    //

    if ((State->State != DevicePowerStateRemoved) &&
        ((State->State != DevicePowerStateTransitioning) ||
         (State->Request != Request))) {

        switch (Request) {

        //
        // Resume trumps all other requests.
        //

        case DevicePowerRequestResume:
        case DevicePowerRequestMarkActive:
            if (State->State != DevicePowerStateActive) {
                State->Request = Request;
                QueueRequest = TRUE;
            }

            break;

        //
        // Suspend trumps idle.
        //

        case DevicePowerRequestSuspend:
            if (State->State != DevicePowerStateSuspended) {
                if ((State->Request != DevicePowerRequestResume) &&
                    (State->Request != DevicePowerRequestMarkActive)) {

                    State->Request = Request;
                    QueueRequest = TRUE;
                }
            }

            break;

        //
        // Idle only happens if nothing else is going on.
        //

        case DevicePowerRequestIdle:
            if (State->State != DevicePowerStateIdle) {
                if (State->Request == DevicePowerRequestNone) {
                    State->Request = Request;
                    QueueRequest = TRUE;
                }
            }

            break;

        default:

            ASSERT(FALSE);

            break;
        }
    }

    //
    // If a request is needed, set the state correctly while the lock is held.
    //

    if (QueueRequest != FALSE) {
        if (State->State != DevicePowerStateTransitioning) {
            State->PreviousState = State->State;
        }

        State->State = DevicePowerStateTransitioning;
    }

    KeReleaseQueuedLock(State->Lock);

    //
    // If needed, actually queue the work request now that the lock is released.
    //

    if (QueueRequest != FALSE) {
        Status = IopQueueDeviceWork(Device,
                                    DeviceActionPowerTransition,
                                    NULL,
                                    0);

        //
        // If queueing the work fails, then attempt to transition the state
        // back to what it was. There may already be an item on the queue and
        // the request may still run, but there is no guarantee of that. The
        // state must be rolled back.
        //

        if (!KSUCCESS(Status)) {
            KeAcquireQueuedLock(State->Lock);

            //
            // If the request is still set, then roll back the state. If it's
            // not, then there is a subsequent attempt at queueing action that
            // may well succeed.
            //

            if (Request == State->Request) {
                State->State = State->PreviousState;
            }

            KeReleaseQueuedLock(State->Lock);

            //
            // If this is a failed resume action, then signal the event. Other
            // threads may be waiting on the event for the resume to succeed.
            //

            if ((Request == DevicePowerRequestResume) ||
                (Request == DevicePowerRequestMarkActive)) {

                KeSignalEvent(State->ActiveEvent, SignalOptionSignalAll);
            }
        }
    }

    return Status;
}

KSTATUS
PmpDeviceResume (
    PDEVICE Device,
    DEVICE_POWER_REQUEST Request
    )

/*++

Routine Description:

    This routine performs the actual resume action for a given device.

Arguments:

    Device - Supplies a pointer to the device to resume.

    Request - Supplies the type of resume to request.

Return Value:

    Status code.

--*/

{

    ULONGLONG CurrentTime;
    ULONGLONG Duration;
    PIRP Irp;
    BOOL LockHeld;
    PDEVICE Parent;
    PDEVICE_POWER ParentState;
    UINTN PreviousCount;
    PDEVICE_POWER State;
    KSTATUS Status;

    ASSERT((Request == DevicePowerRequestResume) ||
           (Request == DevicePowerRequestMarkActive));

    //
    // If the state isn't already active, then the caller won the race to
    // transition it out of an idle or suspended state by being the first to
    // increment the device's reference count. As such, other threads may be
    // waiting on the active event. Except for this case where the device is
    // already active, this routine always needs to release others waiting on
    // the resume transition.
    //

    State = Device->Power;
    if (State->State == DevicePowerStateActive) {
        return STATUS_SUCCESS;
    }

    LockHeld = FALSE;
    CurrentTime = HlQueryTimeCounter();

    //
    // First resume the parent recursively. Always resume the parent, even if
    // the initiali request was to mark the device active. The parent is not
    // necessarily resumed.
    //

    Parent = Device->ParentDevice;
    ParentState = Parent->Power;
    if (ParentState != NULL) {
        PreviousCount = RtlAtomicAdd(&(ParentState->ActiveChildren), 1);

        ASSERT(PreviousCount < 0x10000000);

        //
        // If this is the first active child, up the reference count on the
        // device.
        //

        if (PreviousCount == 0) {
            PreviousCount = RtlAtomicAdd(&(ParentState->ReferenceCount), 1);

            ASSERT(PreviousCount < 0x10000000);

            //
            // If this was the first power reference on this device, resume
            // that device, recursing up parents as needed.
            //

            if (PreviousCount == 0) {
                Status = PmpDeviceResume(Parent, DevicePowerRequestResume);
                if (!KSUCCESS(Status)) {
                    goto DeviceResumeEnd;
                }
            }
        }

        //
        // Wait until the parent's state settles. If this thread does not set
        // the active child count to 1 or the reference count to 1, then
        // another thread is doing the work and the device is not resumed until
        // the active event is signaled. Fail the resume transition if the
        // parent doesn't make it into the resumed state.
        //

        KeWaitForEvent(ParentState->ActiveEvent, FALSE, WAIT_TIME_INDEFINITE);
        if (ParentState->State != DevicePowerStateActive) {
            Status = STATUS_NOT_READY;
            goto DeviceResumeEnd;
        }
    }

    //
    // Synchronize the transition to the active state with other requests and
    // work items that might be trying to send the device to idle or suspend.
    //

    KeAcquireQueuedLock(State->Lock);
    LockHeld = TRUE;

    ASSERT(State->State != DevicePowerStateActive);

    if (State->State == DevicePowerStateRemoved) {
        Status = STATUS_DEVICE_NOT_CONNECTED;
        goto DeviceResumeEnd;
    }

    //
    // If the device was transitioning but never made it, then an extra parent
    // reference count was taken (as idle/suspend will return early and not
    // release it).
    //

    if ((State->State == DevicePowerStateTransitioning) &&
        (State->PreviousState == DevicePowerStateActive)) {

        ASSERT((ParentState == NULL) || (ParentState->ActiveChildren > 1));

        PmpDeviceDecrementActiveChildren(Parent);
    }

    if (Request == DevicePowerRequestResume) {
        Irp = State->Irp;
        IoInitializeIrp(Irp);
        Irp->MinorCode = IrpMinorResume;
        Status = IoSendSynchronousIrp(Irp);
        if (KSUCCESS(Status)) {
            Status = IoGetIrpStatus(Irp);
        }

    } else {

        ASSERT(Request == DevicePowerRequestMarkActive);

        Status = STATUS_SUCCESS;
    }

    if (PmDebugPowerTransitions != FALSE) {
        RtlDebugPrint("PM: 0x%08x Active: 0x%08x\n", Device, Status);
    }

    if (KSUCCESS(Status)) {

        //
        // If the device just switched from idle to active, then compute the
        // idle duration.
        //

        if (State->State == DevicePowerStateIdle) {

            ASSERT(State->TransitionTime != 0);

            Duration = CurrentTime - State->TransitionTime;
            PmpIdleHistoryAddDataPoint(State->History, Duration);
        }

        if (State->State != DevicePowerStateTransitioning) {
            State->PreviousState = State->State;
        }

        State->State = DevicePowerStateActive;
        State->TransitionTime = CurrentTime;

        //
        // Smash any outstanding request state. Now that the device is active
        // again, any request associated with a transition is stale.
        //

        State->Request = DevicePowerRequestNone;

    //
    // On failure, the state is either transitioning (with a request), idle, or
    // suspended. Let the device stay idle or suspended and keep any pending
    // transition unless it is a resume transition.
    //

    } else {

        ASSERT((State->State == DevicePowerStateIdle) ||
               (State->State == DevicePowerStateSuspended) ||
               ((State->State == DevicePowerStateTransitioning) &&
                (State->Request != DevicePowerRequestNone)));

        if ((State->State == DevicePowerStateTransitioning) &&
            ((State->Request == DevicePowerRequestResume) ||
             (State->Request == DevicePowerRequestMarkActive))) {

            ASSERT(State->PreviousState != DevicePowerStateTransitioning);
            ASSERT(State->PreviousState != DevicePowerStateActive);

            State->State = State->PreviousState;
            State->Request = DevicePowerRequestNone;
        }
    }

DeviceResumeEnd:
    if (LockHeld != FALSE) {
        KeReleaseQueuedLock(State->Lock);
    }

    //
    // Signal the event to release any threads waiting on the resume transition.
    // They need to check the state when they wake up in case the resume
    // failed.
    //

    KeSignalEvent(State->ActiveEvent, SignalOptionSignalAll);

    //
    // If it failed, release the references taken on the parent.
    //

    if (!KSUCCESS(Status)) {
        RtlDebugPrint("PM: Failed to resume 0x%08x: 0x%08x\n", Device, Status);
        PmpDeviceDecrementActiveChildren(Parent);
    }

    return Status;
}

VOID
PmpDeviceIdle (
    PDEVICE Device
    )

/*++

Routine Description:

    This routine performs the actual idle action for a given device.

Arguments:

    Device - Supplies a pointer to the device to resume.

Return Value:

    None.

--*/

{

    ULONGLONG CurrentTime;
    BOOL DecrementParent;
    PIRP Irp;
    ULONG Milliseconds;
    PDEVICE_POWER State;
    KSTATUS Status;

    State = Device->Power;

    //
    // Exit quickly if there are references now, which there often will be. The
    // state should be active or about to become active.
    //

    if (State->ReferenceCount != 0) {
        return;
    }

    DecrementParent = FALSE;
    Status = STATUS_UNSUCCESSFUL;
    KeAcquireQueuedLock(State->Lock);
    if (State->State == DevicePowerStateRemoved) {
        goto DeviceIdleEnd;
    }

    //
    // Unsignal the event now so that there isn't a window in between
    // 1) Checking the references here, and
    // 2) Unsignaling the event
    // where an add reference call could zoom through, get past the event, and
    // then get in trouble when this lumbering idle finally rolls through. It
    // means add reference calls may get stuck briefly while this routine
    // figures out it's not needed.
    //

    KeSignalEvent(State->ActiveEvent, SignalOptionUnsignal);

    //
    // Do nothing if it turns out this request was stale.
    //

    if ((State->State != DevicePowerStateTransitioning) ||
        (State->Request != DevicePowerRequestIdle) ||
        (State->ReferenceCount != 0)) {

        goto DeviceIdleEnd;
    }

    Irp = State->Irp;
    IoInitializeIrp(Irp);
    Irp->MinorCode = IrpMinorIdle;
    Irp->U.Idle.ExpectedDuration = PmpIdleHistoryGetAverage(State->History);
    Status = IoSendSynchronousIrp(Irp);
    if (KSUCCESS(Status)) {
        Status = IoGetIrpStatus(Irp);
    }

    if (PmDebugPowerTransitions != FALSE) {
        Milliseconds = (Irp->U.Idle.ExpectedDuration * 1000ULL) /
                       HlQueryTimeCounterFrequency();

        RtlDebugPrint("PM: %x Idle (%d ms): %x\n",
                      Device,
                      Milliseconds,
                      Status);
    }

    ASSERT(State->PreviousState == DevicePowerStateActive);

    if (KSUCCESS(Status)) {
        CurrentTime = HlQueryTimeCounter();
        State->State = DevicePowerStateIdle;
        State->TransitionTime = CurrentTime;
        DecrementParent = TRUE;

    } else {
        State->State = State->PreviousState;
    }

    //
    // Success or failure, this request is old news. No additional idle
    // requests could have been queued while this one was in flight. And this
    // routine bails earlier if the request type is anything other than idle.
    //

    State->Request = DevicePowerRequestNone;

DeviceIdleEnd:

    //
    // If the device is active because a resume happened before the idle or the
    // idle failed, wake up everything waiting on the active event.
    //

    if (State->State == DevicePowerStateActive) {
        KeSignalEvent(State->ActiveEvent, SignalOptionSignalAll);
    }

    KeReleaseQueuedLock(State->Lock);

    //
    // If the device was put down, then decrement the active child count of
    // the parent. It moved to the idle state from the active state, which held
    // a reference on the parent.
    //

    if (DecrementParent != FALSE) {
        PmpDeviceDecrementActiveChildren(Device->ParentDevice);
    }

    return;
}

VOID
PmpDeviceSuspend (
    PDEVICE Device
    )

/*++

Routine Description:

    This routine performs the actual device suspension.

Arguments:

    Device - Supplies a pointer to the device to resume.

Return Value:

    None.

--*/

{

    BOOL DecrementParent;
    PIRP Irp;
    PDEVICE_POWER State;
    KSTATUS Status;

    DecrementParent = FALSE;
    State = Device->Power;
    KeAcquireQueuedLock(State->Lock);
    if (State->State == DevicePowerStateRemoved) {
        goto DeviceSuspendEnd;
    }

    KeSignalEvent(State->ActiveEvent, SignalOptionUnsignal);

    //
    // Do nothing if it turns out this request was stale.
    //

    if ((State->State != DevicePowerStateTransitioning) ||
        (State->Request != DevicePowerRequestSuspend)) {

        goto DeviceSuspendEnd;
    }

    Irp = State->Irp;
    IoInitializeIrp(Irp);
    Irp->MinorCode = IrpMinorSuspend;
    Status = IoSendSynchronousIrp(Irp);
    if (KSUCCESS(Status)) {
        Status = IoGetIrpStatus(Irp);
    }

    if (PmDebugPowerTransitions != FALSE) {
        RtlDebugPrint("PM: %x Suspend: %x\n", Device, Status);
    }

    ASSERT((State->PreviousState == DevicePowerStateActive) ||
           (State->PreviousState == DevicePowerStateIdle));

    if (KSUCCESS(Status)) {
        State->State = DevicePowerStateSuspended;
        if (State->PreviousState == DevicePowerStateActive) {
            DecrementParent = TRUE;
        }

    } else {
        State->State = State->PreviousState;
    }

    //
    // Success or failure, this request is old news. No additional suspend
    // requests could have been queued while this one was in flight. And this
    // routine bails earlier if the request type is anything other than suspend.
    //

    State->Request = DevicePowerRequestNone;

DeviceSuspendEnd:

    //
    // If the device is active because a resume happened before the suspend or
    // the suspend failed to transition from active to suspended, wake up
    // everything waiting on the active event.
    //

    if (State->State == DevicePowerStateActive) {
        KeSignalEvent(State->ActiveEvent, SignalOptionSignalAll);
    }

    KeReleaseQueuedLock(State->Lock);

    //
    // If the device was put down, then decrement the active child count of
    // the parent. This only needs to happen if the previous state was the
    // active state. The device may have already been idle, in which case it
    // would not have held a reference on its parent.
    //

    if (DecrementParent != FALSE) {
        PmpDeviceDecrementActiveChildren(Device->ParentDevice);
    }

    return;
}

