/*++

Copyright (c) 1996  Microsoft Corporation

Module Name:

    power.c

Abstract

    Power handling

Author:

    ervinp

Environment:

    Kernel mode only

Revision History:


--*/


#include "pch.h"


BOOLEAN HidpIsWaitWakePending(FDO_EXTENSION *fdoExt, BOOLEAN setIfNotPending)
{
    KIRQL irql;
    BOOLEAN isWaitWakePending;

    KeAcquireSpinLock(&fdoExt->waitWakeSpinLock, &irql);
    isWaitWakePending = fdoExt->isWaitWakePending;
    if (fdoExt->isWaitWakePending == FALSE) {
        if (setIfNotPending) {
            fdoExt->isWaitWakePending = TRUE;
        }
    }
    KeReleaseSpinLock(&fdoExt->waitWakeSpinLock, irql);

    return isWaitWakePending;
}

VOID
HidpPowerDownFdo(IN PHIDCLASS_DEVICE_EXTENSION HidDeviceExtension)
        {
    POWER_STATE powerState;
    FDO_EXTENSION *fdoExt;

    DBGVERBOSE(("powering down fdo 0x%x\n", HidDeviceExtension));

    fdoExt = &HidDeviceExtension->fdoExt;

    powerState.DeviceState = fdoExt->deviceCapabilities.DeviceWake;

    PoRequestPowerIrp(HidDeviceExtension->hidExt.PhysicalDeviceObject,
                      IRP_MN_SET_POWER,
                      powerState,
                      NULL,    // completion routine
                      NULL,    // completion routine context
                      NULL);
}

VOID HidpPowerUpPdos(IN PFDO_EXTENSION fdoExt)
{
    PDEVICE_OBJECT pdo;
    PDO_EXTENSION *pdoExt;
    POWER_STATE powerState;
    ULONG iPdo;

    iPdo = 0;

    powerState.DeviceState = PowerDeviceD0;

    for (iPdo = 0; iPdo < fdoExt->deviceRelations->Count; iPdo++) {
        pdoExt = &fdoExt->collectionPdoExtensions[iPdo]->pdoExt;
        pdo = pdoExt->pdo;

        DBGVERBOSE(("power up pdos, requesting D0 on pdo #%d %x\n", iPdo, pdo));

        //
        // We could check // pdoExt->devicePowerState != PowerDeviceD0
        // but, if the stack gets 2 D0 irps in a row, nothing bad should happen
        //
        PoRequestPowerIrp(pdo,
                          IRP_MN_SET_POWER,
                          powerState,
                          NULL,        // completion routine
                          NULL,        // context
                          NULL);
    }
    HidpSetDeviceBusy(fdoExt);
    KeSetEvent(&fdoExt->idleDoneEvent, 0, FALSE);
}

VOID
HidpPdoIdleOutComplete(
    IN PDEVICE_OBJECT DeviceObject,
    IN UCHAR MinorFunction,
    IN POWER_STATE PowerState,
    IN PHIDCLASS_DEVICE_EXTENSION HidDeviceExtension,
    IN PIO_STATUS_BLOCK IoStatus
    )
{
    FDO_EXTENSION *fdoExt = &HidDeviceExtension->fdoExt;
    LONG prevIdleState;
    BOOLEAN idleCancelling = FALSE;
    KIRQL irql;

    DBGSUCCESS(IoStatus->Status, TRUE)

    if (InterlockedDecrement(&fdoExt->numIdlePdos) == 0) {
        HidpPowerDownFdo(HidDeviceExtension);

        KeAcquireSpinLock(&fdoExt->idleNotificationSpinLock, &irql);

        prevIdleState = InterlockedCompareExchange(&fdoExt->idleState,
                                                   IdleComplete,
                                                   IdleCallbackReceived);
        if (fdoExt->idleCancelling) {
            DBGINFO(("Cancelling idle in pdoidleoutcomplete on 0x%x\n", HidDeviceExtension));
            idleCancelling = TRUE;
        }

        DBGASSERT (prevIdleState == IdleCallbackReceived,
                   ("Race condition in HidpPdoIdleOutComplete. Prev state = %x",
                    prevIdleState),
                   TRUE);

        KeReleaseSpinLock(&fdoExt->idleNotificationSpinLock, irql);

        KeResetEvent(&fdoExt->idleDoneEvent);
        if (idleCancelling) {
            POWER_STATE powerState;
            powerState.DeviceState = PowerDeviceD0;
            DBGINFO(("Cancelling idle. Send power irp from pdo idle complete."))
            PoRequestPowerIrp(((PHIDCLASS_DEVICE_EXTENSION) fdoExt->fdo->DeviceExtension)->hidExt.PhysicalDeviceObject,
                              IRP_MN_SET_POWER,
                              powerState,
                              HidpDelayedPowerPoRequestComplete,
                              fdoExt,
                              NULL);
        }
    }
}

VOID HidpIdleNotificationCallback(IN PHIDCLASS_DEVICE_EXTENSION HidDeviceExtension)
{
    PDEVICE_OBJECT pdo;
    FDO_EXTENSION *fdoExt;
    POWER_STATE powerState;
    ULONG iPdo;
    BOOLEAN ok = TRUE;
    KIRQL irql;
    LONG idleState, prevIdleState;

    iPdo = 0;
    fdoExt = &HidDeviceExtension->fdoExt;

    DBGINFO(("------ IDLE NOTIFICATION on fdo 0x%x\n", fdoExt->fdo));

    KeAcquireSpinLock(&fdoExt->idleNotificationSpinLock, &irql);

    if (fdoExt->idleCancelling) {
        DBGINFO(("We are cancelling idle on fdo 0x%x", fdoExt->fdo));
        fdoExt->idleState = IdleWaiting;
        if (ISPTR(fdoExt->idleTimeoutValue)) {
            InterlockedExchange(fdoExt->idleTimeoutValue, 0);
        }
        fdoExt->idleCancelling = FALSE;
        KeReleaseSpinLock(&fdoExt->idleNotificationSpinLock, irql);

        IoCancelIrp(fdoExt->idleNotificationRequest);
        return;
    }
    prevIdleState = InterlockedCompareExchange(&fdoExt->idleState,
                                           IdleCallbackReceived,
                                           IdleIrpSent);
    DBGASSERT(prevIdleState == IdleIrpSent,
              ("Idle callback in wrong state %x for fdo %x. Exitting.",
               prevIdleState, fdoExt->fdo),
              FALSE);

    KeReleaseSpinLock(&fdoExt->idleNotificationSpinLock, irql);

    if (prevIdleState != IdleIrpSent) {
        return;
    }

    if (HidpIsWaitWakePending(fdoExt, TRUE) == FALSE) {
        SubmitWaitWakeIrp((HIDCLASS_DEVICE_EXTENSION *) fdoExt->fdo->DeviceExtension);
    }

    powerState.DeviceState = fdoExt->deviceCapabilities.DeviceWake;

    fdoExt->numIdlePdos = fdoExt->deviceRelations->Count+1;

    for (iPdo = 0; iPdo < fdoExt->deviceRelations->Count; iPdo++) {
        pdo = fdoExt->collectionPdoExtensions[iPdo]->pdoExt.pdo;

        DBGVERBOSE(("power down pdos, requesting D%d on pdo #%d %x\n",
                 powerState.DeviceState-1, iPdo, pdo));

        //
        // We could check // pdoExt->devicePowerState != PowerDeviceD0
        // but, if the stack gets 2 D0 irps in a row, nothing bad should happen
        //
        PoRequestPowerIrp(pdo,
                          IRP_MN_SET_POWER,
                          powerState,
                          HidpPdoIdleOutComplete,
                          HidDeviceExtension,
                          NULL);
    }

    if (InterlockedDecrement(&fdoExt->numIdlePdos) == 0) {
        BOOLEAN idleCancelling = FALSE;
        HidpPowerDownFdo(HidDeviceExtension);
        KeAcquireSpinLock(&fdoExt->idleNotificationSpinLock, &irql);

        prevIdleState = InterlockedCompareExchange(&fdoExt->idleState,
                                                   IdleComplete,
                                                   IdleCallbackReceived);
        idleCancelling = fdoExt->idleCancelling;

        DBGASSERT (prevIdleState == IdleCallbackReceived,
                   ("Race condition in HidpPdoIdleOutComplete. Prev state = %x",
                    prevIdleState),
                   FALSE);

        KeReleaseSpinLock(&fdoExt->idleNotificationSpinLock, irql);

        KeResetEvent(&fdoExt->idleDoneEvent);
        if (idleCancelling) {
            POWER_STATE powerState;
            powerState.DeviceState = PowerDeviceD0;
            DBGINFO(("Cancelling idle. Send power irp from idle callback."))
            PoRequestPowerIrp(((PHIDCLASS_DEVICE_EXTENSION) fdoExt->fdo->DeviceExtension)->hidExt.PhysicalDeviceObject,
                              IRP_MN_SET_POWER,
                              powerState,
                              HidpDelayedPowerPoRequestComplete,
                              fdoExt,
                              NULL);
        }
    }
}

/*
 ********************************************************************************
 *  EnqueueCollectionWaitWakeIrp
 ********************************************************************************
 *
 */
NTSTATUS
EnqueueCollectionWaitWakeIrp(
    IN FDO_EXTENSION *FdoExt,
    IN PDO_EXTENSION *PdoExt,
    IN PIRP WaitWakeIrp)
{
    PDRIVER_CANCEL oldCancelRoutine;
    KIRQL oldIrql;
    NTSTATUS status;
    PHIDCLASS_DEVICE_EXTENSION devExt = (PHIDCLASS_DEVICE_EXTENSION)FdoExt->fdo->DeviceExtension;

    KeAcquireSpinLock(&FdoExt->collectionWaitWakeIrpQueueSpinLock, &oldIrql);

    if (InterlockedCompareExchangePointer(&PdoExt->waitWakeIrp,
                                          WaitWakeIrp,
                                          NULL) != NULL) {
        //
        // More than one WW irp?  Unthinkable!
        //
        DBGWARN(("Another WW irp was already queued on pdoExt %x", PdoExt))
        status = STATUS_INVALID_DEVICE_STATE;
    } else {
        /*
         *  Must set a cancel routine before checking the Cancel flag
         *  (this makes the cancel code path for the IRP have to contend
         *  for our local spinlock).
         */
        oldCancelRoutine = IoSetCancelRoutine(WaitWakeIrp, CollectionWaitWakeIrpCancelRoutine);
        ASSERT(!oldCancelRoutine);

        if (WaitWakeIrp->Cancel){
            /*
             *  This IRP has already been cancelled.
             */
            oldCancelRoutine = IoSetCancelRoutine(WaitWakeIrp, NULL);
            if (oldCancelRoutine){
                /*
                 *  Cancel routine was NOT called, so complete the IRP here
                 *  (caller will do this when we return error).
                 */
                ASSERT(oldCancelRoutine == CollectionWaitWakeIrpCancelRoutine);
                status = STATUS_CANCELLED;
            }
            else {
                /*
                 *  Cancel routine was called, and it will dequeue and complete the IRP
                 *  as soon as we drop the spinlock.
                 *  Initialize the IRP's listEntry so the dequeue doesn't corrupt the list.
                 *  Then return STATUS_PENDING so we don't touch the IRP
                 */
                InitializeListHead(&WaitWakeIrp->Tail.Overlay.ListEntry);

                IoMarkIrpPending(WaitWakeIrp);
                status = STATUS_PENDING;
            }
        }
        else {
            /*
             *  IoMarkIrpPending sets a bit in the current stack location
             *  to indicate that the Irp may complete on a different thread.
             */
            InsertTailList(&FdoExt->collectionWaitWakeIrpQueue, &WaitWakeIrp->Tail.Overlay.ListEntry);

            IoMarkIrpPending(WaitWakeIrp);
            status = STATUS_PENDING;
        }
    }

    if (status != STATUS_PENDING) {
        //
        // The irp was cancelled. Remove it from the extension.
        //
        InterlockedExchangePointer(&PdoExt->waitWakeIrp, NULL);
    }

    KeReleaseSpinLock(&FdoExt->collectionWaitWakeIrpQueueSpinLock, oldIrql);

    if (status == STATUS_PENDING){
        #if WIN95_BUILD
            DBGERR(("WaitWake IRP sent by client on Win98 ???"))
        #else
            if (!HidpIsWaitWakePending(FdoExt, TRUE)){
                DBGVERBOSE(("WW 5 %x\n", devExt))
                SubmitWaitWakeIrp(devExt);
            }
        #endif
    }

    return status;
}

NTSTATUS
HidpPdoPower(
    IN PHIDCLASS_DEVICE_EXTENSION HidDeviceExtension,
    IN OUT PIRP Irp
    )
{
    NTSTATUS status = NO_STATUS;
    PIO_STACK_LOCATION irpSp;
    FDO_EXTENSION *fdoExt;
    PDO_EXTENSION *pdoExt;
    KIRQL oldIrql;
    UCHAR minorFunction;
    LIST_ENTRY dequeue, *entry;
    PIO_STACK_LOCATION stack;
    PIRP irp;
    ULONG count;
    POWER_STATE powerState;
    SYSTEM_POWER_STATE systemState;
    BOOLEAN justReturnPending = FALSE;
    BOOLEAN runPowerCode;

    irpSp = IoGetCurrentIrpStackLocation(Irp);

    /*
     *  Keep these privately so we still have it after the IRP completes
     *  or after the device extension is freed on a REMOVE_DEVICE
     */
    minorFunction = irpSp->MinorFunction;

    pdoExt = &HidDeviceExtension->pdoExt;
    fdoExt = &HidDeviceExtension->pdoExt.deviceFdoExt->fdoExt;

    runPowerCode =
        (pdoExt->state == COLLECTION_STATE_RUNNING) ||
        (pdoExt->state == COLLECTION_STATE_STOPPED) ||
        (pdoExt->state == COLLECTION_STATE_STOPPING);

    if (runPowerCode) {
        switch (minorFunction){

        case IRP_MN_SET_POWER:
            PoSetPowerState(pdoExt->pdo,
                            irpSp->Parameters.Power.Type,
                            irpSp->Parameters.Power.State);

            switch (irpSp->Parameters.Power.Type) {

            case SystemPowerState:
                systemState = irpSp->Parameters.Power.State.SystemState;

                pdoExt->systemPowerState = systemState;

                if (systemState == PowerSystemWorking){
                    powerState.DeviceState = PowerDeviceD0;
                }
                else {
                    powerState.DeviceState = PowerDeviceD3;
                }

                DBGVERBOSE(("S irp, requesting D%d on pdo %x\n",
                         powerState.DeviceState-1, pdoExt->pdo));

                IoMarkIrpPending(Irp);
                PoRequestPowerIrp(pdoExt->pdo,
                                  IRP_MN_SET_POWER,
                                  powerState,
                                  CollectionPowerRequestCompletion,
                                  Irp,    // context
                                  NULL);

                /*
                 *  We want to complete the system-state power Irp
                 *  with the result of the device-state power Irp.
                 *  We'll complete the system-state power Irp when
                 *  the device-state power Irp completes.
                 *
                 *  Note: this may have ALREADY happened, so don't
                 *        touch the original Irp anymore.
                 */
                status = STATUS_PENDING;
                justReturnPending = TRUE;

                break;

            case DevicePowerState:
                switch (irpSp->Parameters.Power.State.DeviceState) {

                case PowerDeviceD0:
                    /*
                     *  Resume from APM Suspend
                     *
                     *  Do nothing here; Send down the read IRPs in the
                     *  completion routine for this (the power) IRP.
                     */
                    DBGVERBOSE(("pdo %x on fdo %x going to D0\n", pdoExt->pdo,
                             fdoExt->fdo));

                    pdoExt->devicePowerState =
                        irpSp->Parameters.Power.State.DeviceState;
                    status = STATUS_SUCCESS;

                    //
                    // Resend all power delayed IRPs
                    //
                    count = DequeueAllPdoPowerDelayedIrps(pdoExt, &dequeue);
                    DBGVERBOSE(("dequeued %d requests\n", count));

                    while (!IsListEmpty(&dequeue)) {
                        entry = RemoveHeadList(&dequeue);
                        irp = CONTAINING_RECORD(entry, IRP, Tail.Overlay.ListEntry);
                        stack = IoGetCurrentIrpStackLocation(irp);

                        DBGINFO(("resending %x to pdo %x in set D0 for pdo.\n", irp, pdoExt->pdo));

                        pdoExt->pdo->DriverObject->
                            MajorFunction[stack->MajorFunction]
                                (pdoExt->pdo, irp);
                    }
                    break;

                case PowerDeviceD1:
                case PowerDeviceD2:
                case PowerDeviceD3:
                    /*
                     *  Suspend
                     */

                    DBGVERBOSE(("pdo %x on fdo %x going to D%d\n", pdoExt->pdo,
                             fdoExt->fdo,
                             irpSp->Parameters.Power.State.DeviceState-1));

                    pdoExt->devicePowerState =
                        irpSp->Parameters.Power.State.DeviceState;
                    status = STATUS_SUCCESS;

                    //
                    // Only manually power down the PDO if the
                    // machine is not going into low power,
                    // the PDO is going into a D state we can
                    // wake out of, and we have idle time out
                    // enabled.
                    //
                    if (pdoExt->systemPowerState == PowerSystemWorking &&
                        pdoExt->devicePowerState <= fdoExt->deviceCapabilities.DeviceWake &&
                        fdoExt->idleState != IdleDisabled) {
                        DBGVERBOSE(("maybe powering down fdo\n"));

                        HidpPowerDownFdo(HidDeviceExtension->pdoExt.deviceFdoExt);
                    }

                    break;

                default:
                    /*
                     *  Do not return STATUS_NOT_SUPPORTED;
                     *  keep the default status
                     *  (this allows filter drivers to work).
                     */
                    status = Irp->IoStatus.Status;
                    break;
                }
                break;

            default:
                /*
                 *  Do not return STATUS_NOT_SUPPORTED;
                 *  keep the default status
                 *  (this allows filter drivers to work).
                 */
                status = Irp->IoStatus.Status;
                break;
            }
            break;

        case IRP_MN_WAIT_WAKE:
            /*
             *  WaitWake IRPs to the collection-PDO's
             *  just get queued in the base device's extension;
             *  when the base device's WaitWake IRP gets
             *  completed, we'll also complete these collection
             *  WaitWake IRPs.
             */

            if (fdoExt->systemPowerState > fdoExt->deviceCapabilities.SystemWake) {
                status = STATUS_POWER_STATE_INVALID;
            } else {
                status = EnqueueCollectionWaitWakeIrp(fdoExt, pdoExt, Irp);
                if (status == STATUS_PENDING){
                    justReturnPending = TRUE;
                }
            }

            break;

        case IRP_MN_POWER_SEQUENCE:
            TRAP;  // client-PDO should never get this
            status = Irp->IoStatus.Status;
            break;

        case IRP_MN_QUERY_POWER:
            /*
             *  We allow all power transitions.
             *  But make sure that there's no WW down that shouldn't be.
             */
            DBGVERBOSE(("Query power"));
            status = HidpCheckIdleState(HidDeviceExtension, Irp);
            if (status != STATUS_SUCCESS) {
                justReturnPending = TRUE;
            }
            break;

        default:
            /*
             *  'fail' the Irp by returning the default status.
             *  Do not return STATUS_NOT_SUPPORTED;
             *  keep the default status
             *  (this allows filter drivers to work).
             */
            status = Irp->IoStatus.Status;
            break;
        }
    } else {
        switch (minorFunction){
        case IRP_MN_SET_POWER:
        case IRP_MN_QUERY_POWER:
            status = STATUS_SUCCESS;
            break;
        default:
            status = Irp->IoStatus.Status;
            break;
        }
    }

    if (!justReturnPending) {
        /*
         *  Whether we are completing or relaying this power IRP,
         *  we must call PoStartNextPowerIrp on Windows NT.
         */
        PoStartNextPowerIrp(Irp);

        ASSERT(status != NO_STATUS);
        Irp->IoStatus.Status = status;

        IoCompleteRequest(Irp, IO_NO_INCREMENT);
    }

    DBGSUCCESS(status, FALSE)
    return status;
}

NTSTATUS
HidpFdoPower(
    IN PHIDCLASS_DEVICE_EXTENSION HidDeviceExtension,
    IN OUT PIRP Irp
    )
{
    NTSTATUS status = NO_STATUS;
    PIO_STACK_LOCATION irpSp;
    FDO_EXTENSION *fdoExt;
    KIRQL oldIrql;
    BOOLEAN completeIrpHere = FALSE;
    BOOLEAN returnPending = FALSE;
    UCHAR minorFunction;
    SYSTEM_POWER_STATE systemState;
    BOOLEAN runPowerCode;

    irpSp = IoGetCurrentIrpStackLocation(Irp);

    /*
     *  Keep these privately so we still have it after the IRP completes
     *  or after the device extension is freed on a REMOVE_DEVICE
     */
    minorFunction = irpSp->MinorFunction;

    fdoExt = &HidDeviceExtension->fdoExt;

    runPowerCode =
        (fdoExt->state == DEVICE_STATE_START_SUCCESS) ||
        (fdoExt->state == DEVICE_STATE_STOPPING) ||
        (fdoExt->state == DEVICE_STATE_STOPPED);

    if (runPowerCode) {
        switch (minorFunction){

        case IRP_MN_SET_POWER:
            PoSetPowerState(fdoExt->fdo,
                            irpSp->Parameters.Power.Type,
                            irpSp->Parameters.Power.State);

            switch (irpSp->Parameters.Power.Type) {

            case SystemPowerState:

                systemState = irpSp->Parameters.Power.State.SystemState;

                if (systemState < PowerSystemMaximum) {
                    /*
                     *  For the 'regular' system power states,
                     *  we convert to a device power state
                     *  and request a callback with the device power state.
                     */
                    PDEVICE_OBJECT pdo = HidDeviceExtension->hidExt.PhysicalDeviceObject;
                    POWER_STATE powerState;
                    KIRQL oldIrql;
                    BOOLEAN isWaitWakePending;

                    if (systemState != PowerSystemWorking) {
                        //
                        // We don't want to be idling during regular system
                        // power stuff.
                        //
                        HidpCancelIdleNotification(fdoExt, FALSE);
                    }

                    fdoExt->systemPowerState = systemState;
                    isWaitWakePending = HidpIsWaitWakePending(fdoExt, FALSE);

                    if (isWaitWakePending &&
                        systemState > fdoExt->deviceCapabilities.SystemWake){
                        /*
                         *  We're transitioning to a system state from which
                         *  this device cannot perform a wake-up.
                         *  So fail all the WaitWake IRPs.
                         */
                        CompleteAllCollectionWaitWakeIrps(fdoExt, STATUS_POWER_STATE_INVALID);
                    }
                    returnPending = TRUE;
                }
                else {
                    TRAP;
                    /*
                     *  For the remaining system power states,
                     *  just pass down the IRP.
                     */
                    runPowerCode = FALSE;
                    Irp->IoStatus.Status = STATUS_SUCCESS;
                }

                break;

            case DevicePowerState:
                switch (irpSp->Parameters.Power.State.DeviceState) {

                case PowerDeviceD0:
                    /*
                     *  Resume from APM Suspend
                     *
                     *  Do nothing here; Send down the read IRPs in the
                     *  completion routine for this (the power) IRP.
                     */
                    DBGVERBOSE(("fdo powering up to D0\n"));
                    break;

                case PowerDeviceD1:
                case PowerDeviceD2:
                case PowerDeviceD3:
                    /*
                     *  Suspend
                     */

                    DBGVERBOSE(("fdo going down to D%d\n", fdoExt->devicePowerState-1));

                    if (fdoExt->state == DEVICE_STATE_START_SUCCESS &&
                        fdoExt->devicePowerState == PowerDeviceD0){
                        CancelAllPingPongIrps(fdoExt);
                    }
                    fdoExt->devicePowerState =
                        irpSp->Parameters.Power.State.DeviceState;

                    break;
                }
                break;
            }
            break;

        case IRP_MN_WAIT_WAKE:
            KeAcquireSpinLock(&fdoExt->waitWakeSpinLock, &oldIrql);
            if (fdoExt->waitWakeIrp == BAD_POINTER) {
                DBGVERBOSE(("new wait wake irp 0x%x\n", Irp));
                fdoExt->waitWakeIrp = Irp;
            } else {
                DBGVERBOSE(("1+ wait wake irps 0x%x\n", Irp));
                completeIrpHere = TRUE;
                status = STATUS_POWER_STATE_INVALID;
            }
            KeReleaseSpinLock(&fdoExt->waitWakeSpinLock, oldIrql);

            break;
        }
    } else {
        switch (minorFunction){
        case IRP_MN_SET_POWER:
        case IRP_MN_QUERY_POWER:
            Irp->IoStatus.Status = STATUS_SUCCESS;
            break;
        default:
            // nothing
            break;
        }
    }

    /*
     *  Whether we are completing or relaying this power IRP,
     *  we must call PoStartNextPowerIrp on Windows NT.
     */
    PoStartNextPowerIrp(Irp);

    /*
     *  If this is a call for a collection-PDO, we complete it ourselves here.
     *  Otherwise, we pass it to the minidriver stack for more processing.
     */
    if (completeIrpHere){

        /*
         *  Note:  Don't touch the Irp after completing it.
         */
        ASSERT(status != NO_STATUS);
        Irp->IoStatus.Status = status;

        IoCompleteRequest(Irp, IO_NO_INCREMENT);
    }
    else {
        /*
         *  Call the minidriver with this Irp.
         *  The rest of our processing will be done in our completion routine.
         *
         *  Note:  Don't touch the Irp after sending it down, since it may
         *         be completed immediately.
         */

        if (runPowerCode) {
            IoCopyCurrentIrpStackLocationToNext(Irp);
            IoSetCompletionRoutine(Irp, HidpFdoPowerCompletion, (PVOID)HidDeviceExtension, TRUE, TRUE, TRUE);
        } else {
            IoSkipCurrentIrpStackLocation(Irp);
        }

        /*
         *
         *  Want to use PoCallDriver here, but PoCallDriver
         *  uses IoCallDriver,
         *  which uses the driverObject->MajorFunction[] array
         *  instead of the hidDriverExtension->MajorFunction[] functions.
         *  SHOULD FIX THIS FOR NT -- should use PoCallDriver
         *
         */
        // status = PoCallDriver(HidDeviceExtension->hidExt.NextDeviceObject, Irp);
        // status = PoCallDriver(fdoExt->fdo, Irp);
        if (returnPending) {
            DBGASSERT(runPowerCode, ("We are returning pending, but not running completion routine.\n"), TRUE)
            IoMarkIrpPending(Irp);
            HidpCallDriver(fdoExt->fdo, Irp);
            status = STATUS_PENDING;
        } else {
            status = HidpCallDriver(fdoExt->fdo, Irp);
        }
    }

    DBGSUCCESS(status, FALSE)
    return status;
}

/*
 ********************************************************************************
 *  HidpIrpMajorPower
 ********************************************************************************
 *
 *
 *  Note:  This function cannot be pageable because (on Win98 anyway)
 *         NTKERN calls it back on the thread of the completion routine
 *         that returns the "Cntrl-Alt-Del" keystrokes.
 *         Also, we may or may not have set the DO_POWER_PAGABLE;
 *         so power IRPs may or may not come in at DISPATCH_LEVEL.
 *         So we must keep this code locked.
 */
NTSTATUS HidpIrpMajorPower(
    IN PHIDCLASS_DEVICE_EXTENSION HidDeviceExtension,
    IN OUT PIRP Irp
    )
{
    PIO_STACK_LOCATION irpSp;
    BOOLEAN isClientPdo;
    NTSTATUS status;
    UCHAR minorFunction;

    irpSp = IoGetCurrentIrpStackLocation(Irp);

    minorFunction = irpSp->MinorFunction;

    isClientPdo = HidDeviceExtension->isClientPdo;

    if (minorFunction != IRP_MN_SET_POWER){
        DBG_LOG_POWER_IRP(HidDeviceExtension, minorFunction, isClientPdo, FALSE, "", -1, -1)
    } else {
        switch (irpSp->Parameters.Power.Type) {
        case SystemPowerState:
            DBG_LOG_POWER_IRP(HidDeviceExtension, minorFunction, isClientPdo, FALSE, "SystemState", irpSp->Parameters.Power.State.SystemState, 0xffffffff);
        case DevicePowerState:
            DBG_LOG_POWER_IRP(HidDeviceExtension, minorFunction, isClientPdo, FALSE, "DeviceState", irpSp->Parameters.Power.State.DeviceState, 0xffffffff);
        }
    }

    if (isClientPdo){
        status = HidpPdoPower(HidDeviceExtension, Irp);
    } else {
        status = HidpFdoPower(HidDeviceExtension, Irp);
    }

    DBG_LOG_POWER_IRP(HidDeviceExtension, minorFunction, isClientPdo, TRUE, "", -1, status)

    return status;
}


/*
 ********************************************************************************
 *  SubmitWaitWakeIrp
 ********************************************************************************
 *
 *
 */
NTSTATUS SubmitWaitWakeIrp(IN PHIDCLASS_DEVICE_EXTENSION HidDeviceExtension)
{
    NTSTATUS status;
    POWER_STATE powerState;
    FDO_EXTENSION *fdoExt;

    ASSERT(!HidDeviceExtension->isClientPdo);
    fdoExt = &HidDeviceExtension->fdoExt;

    powerState.SystemState = fdoExt->deviceCapabilities.SystemWake;

    DBGVERBOSE(("SystemWake=%x, submitting waitwake irp.", fdoExt->deviceCapabilities.SystemWake))

    status = PoRequestPowerIrp( HidDeviceExtension->hidExt.PhysicalDeviceObject,
                                IRP_MN_WAIT_WAKE,
                                powerState,
                                HidpWaitWakeComplete,
                                HidDeviceExtension, // context
                                NULL);

    // if (status != STATUS_PENDING){
    //     fdoExt->waitWakeIrp = BAD_POINTER;
    // }

    DBGASSERT((status == STATUS_PENDING),
              ("Expected STATUS_PENDING when submitting WW, got %x", status),
              TRUE)
    return status;
}




/*
 ********************************************************************************
 *  HidpFdoPowerCompletion
 ********************************************************************************
 *
 *
 */
NTSTATUS HidpFdoPowerCompletion(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp,
    IN PVOID Context
    )
{
    PIO_STACK_LOCATION irpSp;
    FDO_EXTENSION *fdoExt;
    NTSTATUS status = Irp->IoStatus.Status;
    PHIDCLASS_DEVICE_EXTENSION HidDeviceExtension = (PHIDCLASS_DEVICE_EXTENSION)Context;
    SYSTEM_POWER_STATE systemState;

    if (Irp->PendingReturned) {
        IoMarkIrpPending(Irp);
    }

    ASSERT(ISPTR(HidDeviceExtension));

    if (HidDeviceExtension->isClientPdo){
        fdoExt = &HidDeviceExtension->pdoExt.deviceFdoExt->fdoExt;
    }
    else {
        fdoExt = &HidDeviceExtension->fdoExt;
    }

    irpSp = IoGetCurrentIrpStackLocation(Irp);
    ASSERT(irpSp->MajorFunction == IRP_MJ_POWER);

    if (NT_SUCCESS(status)) {
        switch (irpSp->MinorFunction) {

        case IRP_MN_SET_POWER:
            switch (irpSp->Parameters.Power.Type) {
            case DevicePowerState:
                switch (irpSp->Parameters.Power.State.DeviceState){
                case PowerDeviceD0:

                    if (fdoExt->devicePowerState != PowerDeviceD0) {
                        KIRQL irql;
                        LONG prevIdleState;

                        fdoExt->devicePowerState = irpSp->Parameters.Power.State.DeviceState;

                        ASSERT(!HidDeviceExtension->isClientPdo);

                        //
                        // Reset the idle stuff if it's not disabled.
                        //
                        KeAcquireSpinLock(&fdoExt->idleNotificationSpinLock, &irql);
                        if (fdoExt->idleState != IdleDisabled) {
                            prevIdleState = InterlockedExchange(&fdoExt->idleState, IdleWaiting);
                            DBGASSERT(prevIdleState == IdleComplete,
                                      ("Previous idle state while completing actually %x",
                                       prevIdleState),
                                      TRUE);
                            fdoExt->idleCancelling = FALSE;
                        }
                        KeReleaseSpinLock(&fdoExt->idleNotificationSpinLock, irql);

                        /*
                         *  On APM resume, restart the ping-pong IRPs
                         *  for interrupt devices.
                         */
                        if (!fdoExt->driverExt->DevicesArePolled &&
                            !fdoExt->isOutputOnlyDevice) {
                            NTSTATUS ntStatus = HidpStartAllPingPongs(fdoExt);
                            if (!NT_SUCCESS(ntStatus)) {
                                fdoExt->state = DEVICE_STATE_START_FAILURE;
                            }
                        }
                    }
                    break;
                }
                break;
            case SystemPowerState:
                ASSERT (!HidDeviceExtension->isClientPdo);

                systemState = irpSp->Parameters.Power.State.SystemState;

                ASSERT((ULONG)systemState < PowerSystemMaximum);

                if (systemState < PowerSystemMaximum){
                    /*
                     *  For the 'regular' system power states,
                     *  we convert to a device power state
                     *  and request a callback with the device power state.
                     */
                    PDEVICE_OBJECT pdo = HidDeviceExtension->hidExt.PhysicalDeviceObject;
                    POWER_STATE powerState;
                    KIRQL oldIrql;
                    BOOLEAN isWaitWakePending;

                    fdoExt->systemPowerState = systemState;
                    isWaitWakePending = HidpIsWaitWakePending(fdoExt, FALSE);

                    if (isWaitWakePending){
                        if (systemState == PowerSystemWorking){
                            powerState.DeviceState = PowerDeviceD0;
                        }
                        else {
                            powerState.DeviceState = fdoExt->deviceCapabilities.DeviceState[systemState];

                            /*
                             *  If the bus does not map the system state to
                             *  a defined device state, request PowerDeviceD3
                             *  and cancel the WaitWake IRP.
                             */
                            if (powerState.DeviceState == PowerDeviceUnspecified){
                                DBGERR(("IRP_MN_SET_POWER: systemState %d mapped not mapped so using device state PowerDeviceD3.", systemState))
                                powerState.DeviceState = PowerDeviceD3;
                            }
                        }
                    }
                    else {
                        /*
                         *  If we don't have a WaitWake IRP pending,
                         *  then every reduced-power system state
                         *  should get mapped to D3.
                         */
                        if (systemState == PowerSystemWorking){
                            powerState.DeviceState = PowerDeviceD0;
                        }
                        else {
                            DBGVERBOSE(("IRP_MN_SET_POWER: no waitWake IRP, so requesting PowerDeviceD3."))
                            powerState.DeviceState = PowerDeviceD3;
                        }
                    }

                    DBGVERBOSE(("IRP_MN_SET_POWER: mapped systemState %d to device state %d.", systemState, powerState.DeviceState))

                    IoMarkIrpPending(Irp);
                    fdoExt->currentSystemStateIrp = Irp;
                    PoRequestPowerIrp(  pdo,
                                        IRP_MN_SET_POWER,
                                        powerState,
                                        DevicePowerRequestCompletion,
                                        fdoExt,    // context
                                        NULL);

                    status = STATUS_MORE_PROCESSING_REQUIRED;
                }
                else {
                    TRAP;
                    /*
                     *  For the remaining system power states,
                     *  just pass down the IRP.
                     */
                }
                break;
            }
            break;
        }
    }
    else if (status == STATUS_CANCELLED){
        /*
         *  Client cancelled the power IRP, probably getting removed.
         */
    }
    else {
        DBGWARN(("HidpPowerCompletion: Power IRP %ph (minor function %xh) failed with status %xh.", Irp, irpSp->MinorFunction, Irp->IoStatus.Status))
    }

    return status;
}



/*
 ********************************************************************************
 *  DevicePowerRequestCompletion
 ********************************************************************************
 *
 *  Note: the DeviceObject here is the PDO (e.g. usbhub's PDO), not our FDO,
 *        so we cannot use its device context.
 */
VOID DevicePowerRequestCompletion(
    IN PDEVICE_OBJECT DeviceObject,
    IN UCHAR MinorFunction,
    IN POWER_STATE PowerState,
    IN PVOID Context,
    IN PIO_STATUS_BLOCK IoStatus
    )
{
    FDO_EXTENSION *fdoExt = (FDO_EXTENSION *)Context;
    PIRP systemStateIrp;

    DBG_COMMON_ENTRY()

    systemStateIrp = fdoExt->currentSystemStateIrp;
    fdoExt->currentSystemStateIrp = BAD_POINTER;
    ASSERT(systemStateIrp);

    DBGSUCCESS(IoStatus->Status, TRUE)
//  systemStateIrp->IoStatus.Status = IoStatus->Status;
    PoStartNextPowerIrp(systemStateIrp);

    /*
     *  Complete the system-state IRP.
     */
    IoCompleteRequest(systemStateIrp, IO_NO_INCREMENT);

    if (PowerState.DeviceState == PowerDeviceD0) {
        //
        // Powering up. Restart the idling.
        //
        HidpStartIdleTimeout(fdoExt, FALSE);
    }

    DBG_COMMON_EXIT()
}



/*
 ********************************************************************************
 *  CollectionPowerRequestCompletion
 ********************************************************************************
 *
 *
 */
VOID CollectionPowerRequestCompletion(
    IN PDEVICE_OBJECT DeviceObject,
    IN UCHAR MinorFunction,
    IN POWER_STATE PowerState,
    IN PVOID Context,
    IN PIO_STATUS_BLOCK IoStatus
    )
{
    PIRP systemStateIrp = (PIRP)Context;
    PHIDCLASS_DEVICE_EXTENSION hidDeviceExtension;
    PDO_EXTENSION *pdoExt;
    IO_STACK_LOCATION *irpSp;
    SYSTEM_POWER_STATE systemState;

    DBG_COMMON_ENTRY()

    hidDeviceExtension = (PHIDCLASS_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
    pdoExt = &hidDeviceExtension->pdoExt;

    ASSERT(systemStateIrp);

    /*
     *  This is the completion routine for the device-state power
     *  Irp which we've requested.  Complete the original system-state
     *  power Irp with the result of the device-state power Irp.
     */

    irpSp = IoGetCurrentIrpStackLocation(systemStateIrp);
    systemState = irpSp->Parameters.Power.State.SystemState;

    systemStateIrp->IoStatus.Status = IoStatus->Status;
    PoStartNextPowerIrp(systemStateIrp);

    IoCompleteRequest(systemStateIrp, IO_NO_INCREMENT);

    //
    // If we're powering up, check if we should have a WW irp pending.
    //
    if (systemState == PowerSystemWorking &&
        SHOULD_SEND_WAITWAKE(pdoExt)) {
        HidpCreateRemoteWakeIrp(pdoExt);
    }

    DBG_COMMON_EXIT()
}


/*
 ********************************************************************************
 *  HidpWaitWakePoRequestComplete
 ********************************************************************************
 *
 */
NTSTATUS HidpWaitWakePoRequestComplete(
    IN PDEVICE_OBJECT       DeviceObject,
    IN UCHAR                MinorFunction,
    IN POWER_STATE          PowerState,
    IN PVOID                Context,
    IN PIO_STATUS_BLOCK     IoStatus
    )
{
    PHIDCLASS_DEVICE_EXTENSION hidDevExt = (PHIDCLASS_DEVICE_EXTENSION)Context;
    FDO_EXTENSION *fdoExt;

    ASSERT(!hidDevExt->isClientPdo);
    fdoExt = &hidDevExt->fdoExt;

    DBGVERBOSE(("HidpWaitWakePoRequestComplete!, status == %xh", IoStatus->Status))

    #if WIN95_BUILD
        if (NT_SUCCESS(IoStatus->Status)) {
            //
            // Resubmit the wait wake irp
            //
            HidpPowerUpPdos(fdoExt);
            SubmitWaitWakeIrp(hidDevExt);
        }
    #else

        /*
         *  Complete all the collections' WaitWake IRPs with this same status.
         */
        CompleteAllCollectionWaitWakeIrps(fdoExt, IoStatus->Status);

        if (NT_SUCCESS(IoStatus->Status) && fdoExt->idleState != IdleDisabled) {
            HidpPowerUpPdos(fdoExt);
        }
    #endif
    return STATUS_SUCCESS;
}


/*
 ********************************************************************************
 *  HidpWaitWakeComplete
 ********************************************************************************
 *
 */
NTSTATUS HidpWaitWakeComplete(
    IN PDEVICE_OBJECT       DeviceObject,
    IN UCHAR                MinorFunction,
    IN POWER_STATE          PowerState,
    IN PVOID                Context,
    IN PIO_STATUS_BLOCK     IoStatus
    )
{
    PHIDCLASS_DEVICE_EXTENSION hidDevExt = (PHIDCLASS_DEVICE_EXTENSION)Context;
    FDO_EXTENSION *fdoExt;
    PDO_EXTENSION *pdoExt;
    NTSTATUS status;
    KIRQL oldIrql;

    ASSERT(!hidDevExt->isClientPdo);
    fdoExt = &hidDevExt->fdoExt;

    status = IoStatus->Status;
    DBGVERBOSE(("HidpWaitWakeComplete!, status == %xh", status))

    KeAcquireSpinLock(&fdoExt->waitWakeSpinLock, &oldIrql);
    fdoExt->waitWakeIrp = BAD_POINTER;
    fdoExt->isWaitWakePending = FALSE;
    KeReleaseSpinLock(&fdoExt->waitWakeSpinLock, oldIrql);

    /*
     *  Call HidpWaitWakePoRequestComplete (either directly or
     *  as a completion routine to the power IRP that we request
     *  to wake up the machine); it will complete the clients'
     *  WaitWake IRPs with the same status as this device WaitWake IRP.
     */
    PowerState.DeviceState = PowerDeviceD0;

    if (NT_SUCCESS(status)){
        /*
         *  Our device is waking up the machine.
         *  So request the D0 (working) power state.
         */
        // PowerState is undefined when a wait wake irp is completing
        // ASSERT(PowerState.DeviceState == PowerDeviceD0);

        DBGVERBOSE(("ww irp, requesting D0 on pdo %x\n", DeviceObject))

        PoRequestPowerIrp(  DeviceObject,
                            IRP_MN_SET_POWER,
                            PowerState,
                            HidpWaitWakePoRequestComplete,
                            Context,
                            NULL);
    } else if (status != STATUS_CANCELLED) {

        //
        // If the wait wake failed, then there is no way for us to wake the
        // device when we are in S0.  Turn off idle detection.
        //
        // This doesn't need to be guarded by a spin lock because the only
        // places we look at these values is in the power dispatch routine
        // and when an interrupt read completes...
        //
        // 1)  no interrupt read will be completing b/c the pingpong engine has
        //      been suspended and will not start until we power up the stack
        // 2)  I think we are still considered to be handling a power irp.  If
        //      not, then we need to guard the isIdleTimeoutEnabled field
        //
        // ISSUE! we should also only turn off idle detection if the WW fails in
        //        S0.  If we hiber, then the WW will fail, but we should not turn off
        //        idle detection in this case.  I think that checking
        //        systemPowerState is not PowerSystemWorking will do the trick,
        //        BUT THIS MUST BE CONFIRMED!!!!
        //
        if (fdoExt->idleState != IdleDisabled &&
            fdoExt->systemPowerState == PowerSystemWorking) {
            DBGWARN(("Turning off idle detection due to WW failure, status = %x\n", status))

            ASSERT(ISPTR(fdoExt->idleTimeoutValue));

            //
            // Don't set any state before calling because we may have to power
            // stuff up.
            //
            HidpCancelIdleNotification(fdoExt, FALSE);
        }

        HidpWaitWakePoRequestComplete(  DeviceObject,
                                        MinorFunction,
                                        PowerState,
                                        Context,
                                        IoStatus);
    }

    return STATUS_SUCCESS;
}




/*
 ********************************************************************************
 *  QueuePowerEventIrp
 ********************************************************************************
 *
 */
NTSTATUS QueuePowerEventIrp(
    IN PHIDCLASS_COLLECTION hidCollection,
    IN PIRP Irp
    )
{
    NTSTATUS status;
    KIRQL oldIrql;
    PDRIVER_CANCEL oldCancelRoutine;

    KeAcquireSpinLock(&hidCollection->powerEventSpinLock, &oldIrql);

    /*
     *  Must set a cancel routine before checking the Cancel flag.
     */
    oldCancelRoutine = IoSetCancelRoutine(Irp, PowerEventCancelRoutine);
    ASSERT(!oldCancelRoutine);

    if (Irp->Cancel){
        /*
         *  This IRP was cancelled.  Do not queue it.
         *  The calling function will complete the IRP with error.
         */
        oldCancelRoutine = IoSetCancelRoutine(Irp, NULL);
        if (oldCancelRoutine){
            /*
             *  Cancel routine was NOT called.
             *  Complete the IRP here.
             */
            ASSERT(oldCancelRoutine == PowerEventCancelRoutine);
            status = STATUS_CANCELLED;
        }
        else {
            /*
             *  The cancel routine was called,
             *  and it will complete this IRP as soon as we drop the spinlock.
             *  Return PENDING so the caller doesn't touch this IRP.
             */
            status = STATUS_PENDING;
        }
    }
    else if (ISPTR(hidCollection->powerEventIrp)){
        /*
         *  We already have a power event IRP queued.
         *  This shouldn't happen, but we'll handle it.
         */
        DBGWARN(("Already have a power event irp queued."));
        oldCancelRoutine = IoSetCancelRoutine(Irp, NULL);
        if (oldCancelRoutine){
            /*
             *  Cancel routine was NOT called.
             *  Complete the IRP here.
             */
            ASSERT(oldCancelRoutine == PowerEventCancelRoutine);
            status = STATUS_UNSUCCESSFUL;
        }
        else {
            /*
             *  The irp was cancelled and the cancel routine was called;
             *  it will complete this IRP as soon as we drop the spinlock.
             *  Return PENDING so the caller doesn't touch this IRP.
             */
            ASSERT(Irp->Cancel);
            status = STATUS_PENDING;
        }
    }
    else {
        /*
         *  Save a pointer to this power event IRP and return PENDING.
         *  This qualifies as "queuing" the IRP, so we must have
         *  a cancel routine.
         */
        hidCollection->powerEventIrp = Irp;
        IoMarkIrpPending(Irp);
        status = STATUS_PENDING;
    }

    KeReleaseSpinLock(&hidCollection->powerEventSpinLock, oldIrql);

    return status;
}


/*
 ********************************************************************************
 *  PowerEventCancelRoutine
 ********************************************************************************
 *
 */
VOID PowerEventCancelRoutine(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
    )
{
    PHIDCLASS_DEVICE_EXTENSION hidDeviceExtension = (PHIDCLASS_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
    FDO_EXTENSION *fdoExt;
    PHIDCLASS_COLLECTION hidCollection;
    ULONG collectionIndex;
    KIRQL oldIrql;

    ASSERT(hidDeviceExtension->Signature == HID_DEVICE_EXTENSION_SIG);
    ASSERT(hidDeviceExtension->isClientPdo);
    fdoExt = &hidDeviceExtension->pdoExt.deviceFdoExt->fdoExt;
    collectionIndex = hidDeviceExtension->pdoExt.collectionIndex;
    hidCollection = &fdoExt->classCollectionArray[collectionIndex];

    KeAcquireSpinLock(&hidCollection->powerEventSpinLock, &oldIrql);

    ASSERT(Irp == hidCollection->powerEventIrp);
    hidCollection->powerEventIrp = BAD_POINTER;

    KeReleaseSpinLock(&hidCollection->powerEventSpinLock, oldIrql);

    IoReleaseCancelSpinLock(Irp->CancelIrql);

    Irp->IoStatus.Status = STATUS_CANCELLED;
    IoCompleteRequest(Irp, IO_NO_INCREMENT);
}


/*
 ********************************************************************************
 *  CollectionWaitWakeIrpCancelRoutine
 ********************************************************************************
 *
 */
VOID CollectionWaitWakeIrpCancelRoutine(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
    )
{
    PHIDCLASS_DEVICE_EXTENSION hidDeviceExtension = (PHIDCLASS_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
    KIRQL oldIrql, oldIrql2;
    PIRP deviceWaitWakeIrpToCancel = NULL;
    FDO_EXTENSION *fdoExt;
    PDO_EXTENSION *pdoExt;

    ASSERT(hidDeviceExtension->Signature == HID_DEVICE_EXTENSION_SIG);
    ASSERT(hidDeviceExtension->isClientPdo);
    pdoExt = &hidDeviceExtension->pdoExt;
    fdoExt = &pdoExt->deviceFdoExt->fdoExt;

    KeAcquireSpinLock(&fdoExt->collectionWaitWakeIrpQueueSpinLock, &oldIrql);

    /*
     *  Dequeue the client's WaitWake IRP.
     */
    RemoveEntryList(&Irp->Tail.Overlay.ListEntry);
    InterlockedExchangePointer(&pdoExt->waitWakeIrp, NULL);

    /*
     *  If the last collection WaitWake IRP just got cancelled,
     *  cancel our WaitWake IRP as well.
     *
     *  NOTE:  we only cancel the FDO wait wake irp if we are not doing idle
     *         detection, otherwise, there would be no way for the device to
     *         wake up when we put it into low power
     *
     */
    KeAcquireSpinLock(&fdoExt->waitWakeSpinLock, &oldIrql2);
    if (IsListEmpty(&fdoExt->collectionWaitWakeIrpQueue) &&
        fdoExt->isWaitWakePending                        &&
        fdoExt->idleState == IdleDisabled){
        ASSERT(ISPTR(fdoExt->waitWakeIrp));
        deviceWaitWakeIrpToCancel = fdoExt->waitWakeIrp;
        fdoExt->waitWakeIrp = BAD_POINTER;
        fdoExt->isWaitWakePending = FALSE;
    }
    KeReleaseSpinLock(&fdoExt->waitWakeSpinLock, oldIrql2);

    KeReleaseSpinLock(&fdoExt->collectionWaitWakeIrpQueueSpinLock, oldIrql);

    IoReleaseCancelSpinLock(Irp->CancelIrql);

    /*
     *  Complete the cancelled IRP only if it was in the list.
     */
    Irp->IoStatus.Status = STATUS_CANCELLED;
    IoCompleteRequest(Irp, IO_NO_INCREMENT);

    if (ISPTR(deviceWaitWakeIrpToCancel)){
        IoCancelIrp(deviceWaitWakeIrpToCancel);
    }
}


/*
 ********************************************************************************
 *  CompleteAllCollectionWaitWakeIrps
 ********************************************************************************
 *
 *  Note:   this function cannot be pageable because it is called
 *          from a completion routine.
 */
VOID CompleteAllCollectionWaitWakeIrps(
    IN FDO_EXTENSION *fdoExt,
    IN NTSTATUS status
    )
{
    LIST_ENTRY irpsToComplete;
    KIRQL oldIrql;
    PLIST_ENTRY listEntry;
    PIRP irp;
    PDO_EXTENSION *pdoExt;
    PIO_STACK_LOCATION irpSp;

    InitializeListHead(&irpsToComplete);

    KeAcquireSpinLock(&fdoExt->collectionWaitWakeIrpQueueSpinLock, &oldIrql);

    while (!IsListEmpty(&fdoExt->collectionWaitWakeIrpQueue)){
        PDRIVER_CANCEL oldCancelRoutine;

        listEntry = RemoveHeadList(&fdoExt->collectionWaitWakeIrpQueue);
        InitializeListHead(listEntry);  // in case cancel routine tries to dequeue again

        irp = CONTAINING_RECORD(listEntry, IRP, Tail.Overlay.ListEntry);

        oldCancelRoutine = IoSetCancelRoutine(irp, NULL);
        if (oldCancelRoutine){
            ASSERT(oldCancelRoutine == CollectionWaitWakeIrpCancelRoutine);

            /*
             *  We can't complete an IRP while holding a spinlock.
             *  Also, we don't want to complete a WaitWake IRP while
             *  still processing collectionWaitWakeIrpQueue because a driver
             *  may resend an IRP on the same thread, causing us to loop forever.
             *  So just move the IRPs to a private queue and we'll complete them later.
             */
            InsertTailList(&irpsToComplete, listEntry);
            irpSp = IoGetCurrentIrpStackLocation(irp);
            pdoExt = &((PHIDCLASS_DEVICE_EXTENSION)irpSp->DeviceObject->DeviceExtension)->pdoExt;
            InterlockedExchangePointer(&pdoExt->waitWakeIrp, NULL);
        }
        else {
            /*
             *  This IRP was cancelled and the cancel routine WAS called.
             *  The cancel routine will complete the IRP as soon as we drop the spinlock.
             *  So don't touch the IRP.
             */
            ASSERT(irp->Cancel);
        }
    }

    KeReleaseSpinLock(&fdoExt->collectionWaitWakeIrpQueueSpinLock, oldIrql);

    while (!IsListEmpty(&irpsToComplete)){
        listEntry = RemoveHeadList(&irpsToComplete);
        irp = CONTAINING_RECORD(listEntry, IRP, Tail.Overlay.ListEntry);
        irp->IoStatus.Status = status;
        IoCompleteRequest(irp, IO_NO_INCREMENT);
    }
}

VOID PowerDelayedCancelRoutine(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
    )
{
    PHIDCLASS_DEVICE_EXTENSION hidDeviceExtension = (PHIDCLASS_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
    FDO_EXTENSION *fdoExt;
    KIRQL oldIrql;

    IoReleaseCancelSpinLock(Irp->CancelIrql);

    ASSERT(hidDeviceExtension->Signature == HID_DEVICE_EXTENSION_SIG);
    ASSERT(hidDeviceExtension->isClientPdo);
    fdoExt = &hidDeviceExtension->pdoExt.deviceFdoExt->fdoExt;

    KeAcquireSpinLock(&fdoExt->collectionPowerDelayedIrpQueueSpinLock, &oldIrql);

    RemoveEntryList(&Irp->Tail.Overlay.ListEntry);
    ASSERT(Irp->Tail.Overlay.DriverContext[0] == (PVOID) hidDeviceExtension);
    Irp->Tail.Overlay.DriverContext[0] = NULL;

    ASSERT(fdoExt->numPendingPowerDelayedIrps > 0);
    fdoExt->numPendingPowerDelayedIrps--;

    KeReleaseSpinLock(&fdoExt->collectionPowerDelayedIrpQueueSpinLock, oldIrql);

    Irp->IoStatus.Status = STATUS_CANCELLED;
    IoCompleteRequest(Irp, IO_NO_INCREMENT);
}


NTSTATUS HidpDelayedPowerPoRequestComplete(
    IN PDEVICE_OBJECT       DeviceObject,
    IN UCHAR                MinorFunction,
    IN POWER_STATE          PowerState,
    IN PVOID                Context,
    IN PIO_STATUS_BLOCK     IoStatus)
{
    KIRQL irql;
    LONG prevIdleState;
    PFDO_EXTENSION fdoExt = (PFDO_EXTENSION) Context;

    DBGINFO(("powering up all pdos due to delayed request, 0x%x\n", IoStatus->Status))

    DBGVERBOSE(("HidpDelayedPowerPoRequestComplete!, status == %xh", IoStatus->Status))

    if (NT_SUCCESS(IoStatus->Status)) {
        HidpPowerUpPdos(fdoExt);
    } else {
        //
        // All bets are off.
        //
        KeAcquireSpinLock(&fdoExt->idleNotificationSpinLock, &irql);
        prevIdleState = InterlockedExchange(&fdoExt->idleState, IdleDisabled);
        fdoExt->idleCancelling = FALSE;
        KeReleaseSpinLock(&fdoExt->idleNotificationSpinLock, irql);

        KeSetEvent(&fdoExt->idleDoneEvent, 0, FALSE);
    }

    return STATUS_SUCCESS;
}

NTSTATUS
EnqueuePowerDelayedIrp(
    IN PHIDCLASS_DEVICE_EXTENSION HidDeviceExtension,
    IN PIRP Irp
    )
{
    FDO_EXTENSION *fdoExt;
    NTSTATUS status;
    KIRQL oldIrql;
    PDRIVER_CANCEL oldCancelRoutine;

    ASSERT(HidDeviceExtension->isClientPdo);
    fdoExt = &HidDeviceExtension->pdoExt.deviceFdoExt->fdoExt;

    DBGINFO(("enqueuing irp %x (mj %x, mn %x)\n", Irp,
             (ULONG) IoGetCurrentIrpStackLocation(Irp)->MajorFunction,
             (ULONG) IoGetCurrentIrpStackLocation(Irp)->MinorFunction))

    KeAcquireSpinLock(&fdoExt->collectionPowerDelayedIrpQueueSpinLock, &oldIrql);

    /*
     *  Must set a cancel routine before
     *  checking the Cancel flag.
     */
    oldCancelRoutine = IoSetCancelRoutine(Irp, PowerDelayedCancelRoutine);
    ASSERT(!oldCancelRoutine);

    /*
     *  Make sure this Irp wasn't just cancelled.
     *  Note that there is NO RACE CONDITION here
     *  because we are holding the fileExtension lock.
     */
    if (Irp->Cancel){
        /*
         *  This IRP was cancelled.
         */
        oldCancelRoutine = IoSetCancelRoutine(Irp, NULL);
        if (oldCancelRoutine){
            /*
             *  The cancel routine was NOT called.
             *  Return error so that caller completes the IRP.
             */
            ASSERT(oldCancelRoutine == PowerDelayedCancelRoutine);
            status = STATUS_CANCELLED;
        }
        else {
            /*
             *  The cancel routine was called.
             *  As soon as we drop the spinlock it will dequeue
             *  and complete the IRP.
             *  Initialize the IRP's listEntry so that the dequeue
             *  doesn't cause corruption.
             *  Then don't touch the irp.
             */
            InitializeListHead(&Irp->Tail.Overlay.ListEntry);
            fdoExt->numPendingPowerDelayedIrps++;  // because cancel routine will decrement

            //
            // We assert that this value is set in the cancel routine
            //
            Irp->Tail.Overlay.DriverContext[0] = (PVOID) HidDeviceExtension;

            IoMarkIrpPending(Irp);
            status = Irp->IoStatus.Status = STATUS_PENDING;
        }
    }
    else {
        /*
         *  Queue this irp onto the fdo's power delayed queue
         */
        InsertTailList(&fdoExt->collectionPowerDelayedIrpQueue,
                       &Irp->Tail.Overlay.ListEntry);
        fdoExt->numPendingPowerDelayedIrps++;

        Irp->Tail.Overlay.DriverContext[0] = (PVOID) HidDeviceExtension;

        IoMarkIrpPending(Irp);
        status = Irp->IoStatus.Status = STATUS_PENDING;
    }

    KeReleaseSpinLock(&fdoExt->collectionPowerDelayedIrpQueueSpinLock, oldIrql);

    return status;
}

PIRP DequeuePowerDelayedIrp(FDO_EXTENSION *fdoExt)
{
    KIRQL oldIrql;
    PIRP irp = NULL;

    KeAcquireSpinLock(&fdoExt->collectionPowerDelayedIrpQueueSpinLock, &oldIrql);

    while (!irp && !IsListEmpty(&fdoExt->collectionPowerDelayedIrpQueue)){
        PDRIVER_CANCEL oldCancelRoutine;
        PLIST_ENTRY listEntry = RemoveHeadList(&fdoExt->collectionPowerDelayedIrpQueue);

        irp = CONTAINING_RECORD(listEntry, IRP, Tail.Overlay.ListEntry);

        oldCancelRoutine = IoSetCancelRoutine(irp, NULL);

        if (oldCancelRoutine){
            ASSERT(oldCancelRoutine == PowerDelayedCancelRoutine);
            ASSERT(fdoExt->numPendingPowerDelayedIrps > 0);
            fdoExt->numPendingPowerDelayedIrps--;
        }
        else {
            /*
             *  IRP was cancelled and cancel routine was called.
             *  As soon as we drop the spinlock,
             *  the cancel routine will dequeue and complete this IRP.
             *  Initialize the IRP's listEntry so that the dequeue doesn't cause corruption.
             *  Then, don't touch the IRP.
             */
            ASSERT(irp->Cancel);
            InitializeListHead(&irp->Tail.Overlay.ListEntry);
            irp = NULL;
        }
    }

    KeReleaseSpinLock(&fdoExt->collectionPowerDelayedIrpQueueSpinLock, oldIrql);

    return irp;
}

ULONG DequeueAllPdoPowerDelayedIrps(
    PDO_EXTENSION *pdoExt,
    PLIST_ENTRY dequeue
    )

{
    PDRIVER_CANCEL oldCancelRoutine;
    FDO_EXTENSION *fdoExt;
    PDO_EXTENSION *irpPdoExt;
    PLIST_ENTRY entry;
    KIRQL oldIrql;
    PIRP irp;
    ULONG count = 0;

    InitializeListHead(dequeue);

    fdoExt = &pdoExt->deviceFdoExt->fdoExt;

    KeAcquireSpinLock(&fdoExt->collectionPowerDelayedIrpQueueSpinLock, &oldIrql);

    for (entry = fdoExt->collectionPowerDelayedIrpQueue.Flink;
         entry != &fdoExt->collectionPowerDelayedIrpQueue;
          ) {

        irp = CONTAINING_RECORD(entry, IRP, Tail.Overlay.ListEntry);

        irpPdoExt =
            &((PHIDCLASS_DEVICE_EXTENSION) irp->Tail.Overlay.DriverContext[0])->pdoExt;

        entry = entry->Flink;

        if (irpPdoExt == pdoExt) {

            //
            // Remove the entry from the linked list and then either queue it
            // in the dequeue or init the entry so it is valid for the cancel
            // routine
            //
            RemoveEntryList(&irp->Tail.Overlay.ListEntry);

            oldCancelRoutine = IoSetCancelRoutine(irp, NULL);
            if (oldCancelRoutine != NULL) {
                InsertTailList(dequeue, &irp->Tail.Overlay.ListEntry);
                fdoExt->numPendingPowerDelayedIrps--;
                count++;
            }
            else {
                /*
                 *  This IRP was cancelled and the cancel routine WAS called.
                 *  The cancel routine will complete the IRP as soon as we drop the spinlock.
                 *  So don't touch the IRP.
                 */
                ASSERT(irp->Cancel);
                InitializeListHead(&irp->Tail.Overlay.ListEntry);  // in case cancel routine tries to dequeue again
            }
        }
    }

    KeReleaseSpinLock(&fdoExt->collectionPowerDelayedIrpQueueSpinLock, oldIrql);

    return count;
}