/*++

Copyright (c) 1990-2000    Microsoft Corporation All Rights Reserved

Module Name:

    power.c

Abstract:

    The power management related processing.

    The Power Manager uses IRPs to direct drivers to change system
    and device power levels, to respond to system wake-up events,
    and to query drivers about their devices. All power IRPs have
    the major function code IRP_MJ_POWER.

    Most function and filter drivers perform some processing for
    each power IRP, then pass the IRP down to the next lower driver
    without completing it. Eventually the IRP reaches the bus driver,
    which physically changes the power state of the device and completes
    the IRP.

    When the IRP has been completed, the I/O Manager calls any
    IoCompletion routines set by drivers as the IRP traveled
    down the device stack. Whether a driver needs to set a completion
    routine depends upon the type of IRP and the driver's individual
    requirements.

    The power policy of this driver is simple. The device enters the
    device working state D0 when the system enters the system
    working state S0. The device enters the lowest-powered sleeping state
    D3 for all the other system power states (S1-S5).

Environment:

    Kernel mode

Revision History:

--*/

#include "toaster.h"
#include "power.h"


NTSTATUS
ToasterDispatchPower (
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
    )
/*++

Routine Description:

    The power dispatch routine.

Arguments:

   DeviceObject - pointer to a device object.

   Irp - pointer to an I/O Request Packet.

Return Value:

      NT status code

--*/
{
    PIO_STACK_LOCATION  stack;
    PFDO_DATA           fdoData;
    NTSTATUS            status;

    stack   = IoGetCurrentIrpStackLocation(Irp);
    fdoData = (PFDO_DATA) DeviceObject->DeviceExtension;

    ToasterDebugPrint((2, "FDO %s IRP:0x%x %s %s\n",
                  PowerMinorFunctionString(stack->MinorFunction),Irp,
                  DbgSystemPowerString(fdoData->SystemPowerState),
                  DbgDevicePowerString(fdoData->DevicePowerState)));

    //
    // We don't queue power Irps, we'll only check if the
    // device was removed, otherwise we'll take appropriate
    // action and send it to the next lower driver. In general
    // drivers should not cause long delays while handling power
    // IRPs. If a driver cannot handle a power IRP in a brief time,
    // it should return STATUS_PENDING and queue all incoming
    // IRPs until the IRP completes.
    //


    if (Deleted == fdoData->DevicePnPState) {

        //
        // Even if a driver fails the IRP, it must nevertheless call
        // PoStartNextPowerIrp to inform the Power Manager that it
        // is ready to handle another power IRP.
        //

        PoStartNextPowerIrp (Irp);
        Irp->IoStatus.Status = status = STATUS_DELETE_PENDING;
        IoCompleteRequest (Irp, IO_NO_INCREMENT);
        return status;
    }

    //
    // If the device is not stated yet, just pass it down.
    //

    if (NotStarted == fdoData->DevicePnPState ) {
        PoStartNextPowerIrp(Irp);
        IoSkipCurrentIrpStackLocation(Irp);
        return PoCallDriver(fdoData->NextLowerDriver, Irp);
    }

    ToasterIoIncrement (fdoData);

    //
    // Check the request type.
    //

    switch  (stack->MinorFunction)  {

        case IRP_MN_SET_POWER   :

            //
            // The Power Manager sends this IRP for one of the
            // following reasons:
            // 1) To notify drivers of a change to the system power state.
            // 2) To change the power state of a device for which
            //    the Power Manager is performing idle detection.
            // A driver sends IRP_MN_SET_POWER to change the power
            // state of its device if it's a power policy owner for the
            // device.
            //

            status = ToasterSetPowerState(DeviceObject, Irp);

            break;


        case IRP_MN_QUERY_POWER   :


            //
            // The Power Manager sends a power IRP with the minor
            // IRP code IRP_MN_QUERY_POWER to determine whether it
            // can safely change to the specified system power state
            // (S1-S5) and to allow drivers to prepare for such a change.
            // If a driver can put its device in the requested state,
            // it sets status to STATUS_SUCCESS and passes the IRP down.
            //

            status = ToasterQueryPowerState(DeviceObject, Irp);
            break;

        case IRP_MN_WAIT_WAKE   :
            //
            // The minor power IRP code IRP_MN_WAIT_WAKE provides
            // for waking a device or waking the system. Drivers
            // of devices that can wake themselves or the system
            // send IRP_MN_WAIT_WAKE. The system sends IRP_MN_WAIT_WAKE
            // only to devices that always wake the system, such as
            // the power-on switch.
            //

        case IRP_MN_POWER_SEQUENCE   :

            //
            // A driver sends this IRP as an optimization to determine
            // whether its device actually entered a specific power state.
            // This IRP is optional. Power Manager cannot send this IRP.
            //

        default:
            //
            // Pass it down
            //
            status = ToasterDefaultPowerHandler(DeviceObject, Irp);
            ToasterIoDecrement(fdoData);
            break;
    }

    return status;
}



NTSTATUS
ToasterDefaultPowerHandler  (
    IN  PDEVICE_OBJECT  DeviceObject,
    IN OUT  PIRP        Irp
    )

/*++

Routine Description:

    If a driver does not support a particular power IRP,
    it must nevertheless pass the IRP down the device stack
    to the next-lower driver. A driver further down the stack
    might be prepared to handle the IRP and must have the
    opportunity to do so.

Arguments:

   DeviceObject - pointer to a device object.

   Irp - pointer to an I/O Request Packet.

Return Value:

   NT status code

--*/
{
    NTSTATUS         status;
    PFDO_DATA        fdoData;

    //
    // Drivers must call PoStartNextPowerIrp while the current
    // IRP stack location points to the current driver.
    // This routine can be called from the DispatchPower routine
    // or from the IoCompletion routine. However, PoStartNextPowerIrp
    // must be called before IoCompleteRequest, IoSkipCurrentIrpStackLocation,
    // and PoCallDriver. Calling the routines in the other order might
    // cause a system deadlock.
    //

    PoStartNextPowerIrp(Irp);

    IoSkipCurrentIrpStackLocation(Irp);

    fdoData = (PFDO_DATA)DeviceObject->DeviceExtension;

    //
    // Drivers must use PoCallDriver, rather than IoCallDriver,
    // to pass power IRPs. PoCallDriver allows the Power Manager
    // to ensure that power IRPs are properly synchronized throughout
    // the system.
    //

    status = PoCallDriver(fdoData->NextLowerDriver, Irp);

    if (!NT_SUCCESS(status)) {
        ToasterDebugPrint((0, "Lower driver fails a power irp\n"));
    }

    return status;


}

/*      ------- Generic handlers --------     */

NTSTATUS
ToasterSetPowerState(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
    )
/*++

Routine Description:

   Processes IRP_MN_SET_POWER.

Arguments:

   DeviceObject - pointer to a device object.

   Irp - pointer to an I/O Request Packet.

Return Value:

   NT status code

--*/
{
    PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);

    ToasterDebugPrint((3, "Entered ToasterSetPowerState\n"));

    return (stack->Parameters.Power.Type == SystemPowerState) ?
        HandleSystemPowerIrp(DeviceObject, Irp) :
        HandleDeviceSetPower(DeviceObject, Irp);
}

NTSTATUS
ToasterQueryPowerState(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
    )
/*++

Routine Description:

   Processes IRP_MN_QUERY_POWER.

Arguments:

   DeviceObject - pointer to a device object.

   Irp - pointer to an I/O Request Packet.

Return Value:

   NT status code

--*/
{
    PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);

    ToasterDebugPrint((3, "Entered ToasterQueryPower\n"));

    return (stack->Parameters.Power.Type == SystemPowerState) ?
        HandleSystemPowerIrp(DeviceObject, Irp) :
        HandleDeviceQueryPower(DeviceObject, Irp);
}



/*      ------- S-IRP logic --------     */

NTSTATUS
HandleSystemPowerIrp(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
    )
/*++

Routine Description:

   Processes IRP_MN_SET_POWER and IRP_MN_QUERY_POWER
   for the system power Irp (S-IRP).

Arguments:

   DeviceObject - pointer to a device object.

   Irp - pointer to an I/O Request Packet.

Return Value:

   NT status code

--*/
{
    PIO_STACK_LOCATION  stack = IoGetCurrentIrpStackLocation(Irp);
    POWER_STATE_TYPE    type = stack->Parameters.Power.Type;
    PFDO_DATA           fdoData = (PFDO_DATA) DeviceObject->DeviceExtension;
    SYSTEM_POWER_STATE  newSystemState;

    ToasterDebugPrint((3, "Entered HandleSystemPowerIrp\n"));

    IoMarkIrpPending(Irp);


    newSystemState = stack->Parameters.Power.State.SystemState;

    //
    // Here we update our cached away system power state.
    //
    if (stack->MinorFunction == IRP_MN_SET_POWER) {
        fdoData->SystemPowerState = newSystemState;
        ToasterDebugPrint((3, "\tsetting the system state to %s\n",
                    DbgSystemPowerString(fdoData->SystemPowerState)));
    }

    //
    // Send the IRP down
    //
    IoCopyCurrentIrpStackLocationToNext(Irp);

    IoSetCompletionRoutine(Irp,
        (PIO_COMPLETION_ROUTINE) OnFinishSystemPowerUp,
        NULL, TRUE, TRUE, TRUE);

    PoCallDriver(fdoData->NextLowerDriver, Irp);

    return STATUS_PENDING;
}

NTSTATUS
OnFinishSystemPowerUp(
    IN PDEVICE_OBJECT   Fdo,
    IN PIRP             Irp,
    IN PVOID            NotUsed
    )
/*++

Routine Description:

   The completion routine for Power Up S-IRP.
   It queues a corresponding D-IRP.

Arguments:

   DeviceObject - pointer to a device object.

   Irp - pointer to an I/O Request Packet.

   Not used  - context pointer

Return Value:

   NT status code

--*/
{
    PFDO_DATA   fdoData = (PFDO_DATA) Fdo->DeviceExtension;
    NTSTATUS    status = Irp->IoStatus.Status;

    ToasterDebugPrint((3, "Entered OnFinishSystemPowerUp\n"));

    if (!NT_SUCCESS(status)) {

        PoStartNextPowerIrp(Irp);
        ToasterIoDecrement(fdoData);
        return STATUS_SUCCESS;
    }

    QueueCorrespondingDeviceIrp(Irp, Fdo);

    return STATUS_MORE_PROCESSING_REQUIRED;
}

///////////////////////////////////////////////////////////////

VOID
QueueCorrespondingDeviceIrp(
    IN PIRP SIrp,
    IN PDEVICE_OBJECT DeviceObject
    )
/*++

Routine Description:

   This routine gets the D-State for a particular S-State
   from DeviceCaps and generates a D-IRP.

Arguments:

   Irp - pointer to an S-IRP.

   DeviceObject - pointer to a device object.

Return Value:

   NT status code

--*/

{
    POWER_COMPLETION_CONTEXT* powerContext;
    NTSTATUS            status;
    POWER_STATE         state;
    PFDO_DATA           fdoData = (PFDO_DATA) DeviceObject->DeviceExtension;
    PIO_STACK_LOCATION  stack = IoGetCurrentIrpStackLocation(SIrp);
    POWER_STATE         systemState = stack->Parameters.Power.State;

    ToasterDebugPrint((3, "Entered QueueCorrespondingDeviceIrp\n"));

    //
    // Read the D-IRP out of the S->D mapping array we captured in QueryCap's.
    // We can choose deeper sleep states than our mapping (with the appropriate
    // caveats if we have children), but we can never choose lighter ones, as
    // what hardware stays on in a given S-state is a function of the
    // motherboard wiring.
    //
    // Also note that if a driver rounds down it's D-state, it must ensure that
    // such a state is supported. A driver can do this by examining the
    // D1Supported? and D2Supported? flags (Win2k), or by examining the entire
    // S->D state mapping (Win98).
    //
    state.DeviceState = fdoData->DeviceCaps.DeviceState[systemState.SystemState];

    powerContext = (POWER_COMPLETION_CONTEXT*)
                ExAllocatePool(NonPagedPool, sizeof(POWER_COMPLETION_CONTEXT));

    if (!powerContext) {

        status = STATUS_INSUFFICIENT_RESOURCES;

    } else {

        powerContext->DeviceObject = DeviceObject;
        powerContext->SIrp = SIrp;

        //
        // Note: Win2k's PoRequestPowerIrp can take an FDO,
        // but Win9x's requires the PDO.
        //

        status = PoRequestPowerIrp(fdoData->UnderlyingPDO,
                                    stack->MinorFunction,
                                    state, OnPowerRequestComplete,
                                    powerContext, NULL);
    }

    if (!NT_SUCCESS(status)) {

        if (powerContext) {
            ExFreePool(powerContext);
        }

        PoStartNextPowerIrp(SIrp);
        SIrp->IoStatus.Status = status;
        IoCompleteRequest(SIrp, IO_NO_INCREMENT);
        ToasterIoDecrement(fdoData);

    }
}


VOID
OnPowerRequestComplete(
    PDEVICE_OBJECT DeviceObject,
    UCHAR MinorFunction,
    POWER_STATE state,
    POWER_COMPLETION_CONTEXT* PowerContext,
    PIO_STATUS_BLOCK IoStatus
    )
/*++

Routine Description:

   Completion routine for D-IRP.

Arguments:


Return Value:

   NT status code

--*/
{
    PFDO_DATA   fdoData = (PFDO_DATA) PowerContext->DeviceObject->DeviceExtension;
    PIRP        sIrp = PowerContext->SIrp;

    ToasterDebugPrint((3, "Entered OnPowerRequestComplete\n"));

    //
    // Here we copy the D-IRP status into the S-IRP
    //
    sIrp->IoStatus.Status = IoStatus->Status;

    //
    // Release the IRP
    //
    PoStartNextPowerIrp(sIrp);
    IoCompleteRequest(sIrp, IO_NO_INCREMENT);

    //
    // Cleanup
    //
    ExFreePool(PowerContext);
    ToasterIoDecrement(fdoData);

}

/*      ------- D-IRP logic --------     */


NTSTATUS
HandleDeviceQueryPower(
    PDEVICE_OBJECT DeviceObject,
    PIRP Irp
    )
/*++

Routine Description:

    Handles IRP_MN_QUERY_POWER for D-IRP

Arguments:

   DeviceObject - pointer to a device object.

   Irp - pointer to an I/O Request Packet.

Return Value:

      NT status code

--*/
{
    PIO_STACK_LOCATION  stack = IoGetCurrentIrpStackLocation(Irp);
    PFDO_DATA           fdoData = (PFDO_DATA) DeviceObject->DeviceExtension;
    DEVICE_POWER_STATE  deviceState = stack->Parameters.Power.State.DeviceState;
    NTSTATUS            status;

    ToasterDebugPrint((3, "Entered HandleDeviceQueryPower\n"));

    //
    // Here we check to see if it's OK for our hardware to be suspended. Note
    // that our driver may have requests that would cause us to fail this
    // check. If so, we need to begin queuing requests after succeeding this
    // call (otherwise we may succeed such an IRP *after* we've said we can
    // power down).
    //
    // Note - we may be at DISPATCH_LEVEL here. As such the below code assumes
    //        all I/O is handled at DISPATCH_LEVEL under a spinlock
    //        (IoStartNextPacket style), or that this function cannot fail. If
    //        such operations are to be handled at PASSIVE_LEVEL (meaning we
    //        need to block on an *event* here), then this code should mark the
    //        Irp pending (via IoMarkIrpPending), queue a workitem, and return
    //        STATUS_PENDING.
    //

    if (deviceState == PowerDeviceD0) {

        //
        // Note - this driver does not queue IRPs if the S-to-D state mapping
        //        specifies that the device will remain in D0 during standby.
        //        For some devices, this could be a problem. For instance, if
        //        an audio card where in a machine where S1->D0, it not want to
        //        stay "active" during standby (could be noisy).
        //
        //        Ideally, a driver would be able to use the ShutdownType field
        //        in the D-IRP to distinguish these cases. Unfortunately, this
        //        field cannot be trusted for D0 IRPs. A driver can get the same
        //        information however by maintaining a pointer to the current
        //        S-IRP in its device extension. Of course, such a driver must
        //        be very very careful if it also does idle detection (which is
        //        not shown here).
        //
        status = STATUS_SUCCESS;
    } else {

        status = HoldIoRequests(DeviceObject, Irp, IRP_NEEDS_FORWARDING);
        if(STATUS_PENDING == status)
        {
            return status;
        }
    }

    status = FinishDevicePowerIrp(DeviceObject, Irp, IRP_NEEDS_FORWARDING, status);

    return status;
}

NTSTATUS
HandleDeviceSetPower(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
    )
/*++

Routine Description:

    Handles IRP_MN_SET_POWER for D-IRP

Arguments:

   DeviceObject - pointer to a device object.

   Irp - pointer to an I/O Request Packet.

Return Value:

      NT status code

--*/
{
    PIO_STACK_LOCATION  stack = IoGetCurrentIrpStackLocation(Irp);
    POWER_STATE_TYPE    type = stack->Parameters.Power.Type;
    POWER_STATE         state = stack->Parameters.Power.State;
    NTSTATUS            status;
    PFDO_DATA           fdoData = (PFDO_DATA) DeviceObject->DeviceExtension;

    ToasterDebugPrint((3, "Entered HandleDeviceSetPower\n"));

    if (state.DeviceState < fdoData->DevicePowerState) { // adding power

        IoCopyCurrentIrpStackLocationToNext(Irp);
        IoSetCompletionRoutine(Irp,
                      (PIO_COMPLETION_ROUTINE) OnFinishDevicePowerUp,
                      NULL, TRUE, TRUE, TRUE);

        PoCallDriver(fdoData->NextLowerDriver, Irp);
        return STATUS_PENDING;

    } else {

        //
        // We are here if we are entering a deeper sleep or entering a state
        // we are already in.
        //
        // As non-D0 IRPs are not alike (some may be for hibernate, shutdown,
        // or sleeping actions), we present these to our state machine.
        //
        // All D0 IRPs are alike though, and we don't want to touch our hardware
        // on a D0->D0 transition. However, we must still present them to our
        // state machine in case we succeeded a Query-D call (which may begin
        // queueing future requests) and the system has sent an S0 IRP to cancel
        // that preceeding query.
        //
        status = BeginSetDevicePowerState(DeviceObject, Irp, IRP_NEEDS_FORWARDING);
        return status;
    }
}

NTSTATUS
OnFinishDevicePowerUp(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp,
    IN PVOID NotUsed
    )
/*++

Routine Description:

   The completion routine for Power Up D-IRP.

Arguments:

   DeviceObject - pointer to a device object.

   Irp - pointer to an I/O Request Packet.

   Not used  - context pointer

Return Value:

   NT status code

--*/
{
    PFDO_DATA           fdoData = (PFDO_DATA) DeviceObject->DeviceExtension;
    NTSTATUS            status = Irp->IoStatus.Status;
    PIO_STACK_LOCATION  stack = IoGetCurrentIrpStackLocation(Irp);
    POWER_STATE_TYPE    type = stack->Parameters.Power.Type;

    ToasterDebugPrint((3, "Entered OnFinishDevicePowerUp\n"));

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

    if (!NT_SUCCESS(status)) {

        PoStartNextPowerIrp(Irp);
        ToasterIoDecrement(fdoData);
        return STATUS_SUCCESS;
    }

    ASSERT(stack->MajorFunction == IRP_MJ_POWER);
    ASSERT(stack->MinorFunction == IRP_MN_SET_POWER);

    BeginSetDevicePowerState(DeviceObject, Irp, IRP_ALREADY_FORWARDED);

    return STATUS_MORE_PROCESSING_REQUIRED;
}

///////////////////////////////////////////////////////////////

NTSTATUS
BeginSetDevicePowerState(
    IN  PDEVICE_OBJECT      DeviceObject,
    IN  PIRP                Irp,
    IN  IRP_DIRECTION       Direction
    )
/*++

Routine Description:

   This routine performs the actual power changes to the device.

Arguments:

   DeviceObject - pointer to a device object.

   Irp - pointer to an D-IRP.

   Direction -  Whether to forward the D-IRP down or not.
                This depends on whether the system is powering
                up or down.
Return Value:

   NT status code

--*/
{
    PFDO_DATA           fdoData = (PFDO_DATA) DeviceObject->DeviceExtension;
    PIO_STACK_LOCATION  stack = IoGetCurrentIrpStackLocation(Irp);
    POWER_ACTION        newDeviceAction;
    DEVICE_POWER_STATE  newDeviceState, oldDeviceState;
    POWER_STATE         newState;
    NTSTATUS            status = STATUS_SUCCESS;

    ToasterDebugPrint((3, "Entered BeginSetDevicePowerState\n"));

    //
    // Note - Here we may be at DISPATCH_LEVEL. The below code assumes all I/O
    //        is handled at DISPATCH_LEVEL under a spinlock (IoStartNextPacket
    //        style). If such operations are to be handled at PASSIVE_LEVEL then
    //        this code should queue a workitem if called at DISPATCH_LEVEL.
    //
    //        Also note that we'd want to mark the IRP appropriately (via
    //        IoMarkIrpPending) and we'd want to return STATUS_PENDING. As this
    //        example does things synchronously, it is enough for us to return
    //        the result of FinishSetDevicePowerState.
    //

    //
    // Update our state.
    //
    newState =  stack->Parameters.Power.State;
    newDeviceState = newState.DeviceState;
    oldDeviceState = fdoData->DevicePowerState;
    fdoData->DevicePowerState = newDeviceState;

    ToasterDebugPrint((3, "\tset the device state %s\n",
                            DbgDevicePowerString(fdoData->DevicePowerState)));

    if (newDeviceState > PowerDeviceD0) {

        //
        // We are here if our hardware is about to be turned off. HoldRequests
        // queues a workitem and returns immediately with STATUS_PENDING.
        //
        status = HoldIoRequests(DeviceObject, Irp, Direction);

        if(STATUS_PENDING == status)
        {
            return status;
        }
    }

    newDeviceAction = stack->Parameters.Power.ShutdownType;

    if (newDeviceState > oldDeviceState) {

        //
        // We are entering a deeper sleep state. Save away the appropriate
        // state and update our hardware. Note that this particular driver does
        // not care to distinguish Hibernates from shutdowns or standbys. If we
        // did the logic would also have to examine newDeviceAction.
        //
        PoSetPowerState(DeviceObject, DevicePowerState, newState);

    } else if (newDeviceState < oldDeviceState) {

        //
        // We are entering a lighter sleep state. Restore the appropriate amount
        // of state to our hardware.
        //
        PoSetPowerState(DeviceObject, DevicePowerState, newState);
    }

    if (newDeviceState == PowerDeviceD0) {

        //
        // Our hardware is now on again. Here we empty our existing queue of
        // requests and let in new ones. Note that if this is a D0->D0 (ie
        // no change) we will unblock our queue, which may have been blocked
        // processing our Query-D IRP.
        //
        fdoData->QueueState = AllowRequests;
        ToasterProcessQueuedRequests(fdoData);
        status = STATUS_SUCCESS;
    }

    return FinishDevicePowerIrp(
                            DeviceObject,
                            Irp,
                            Direction,
                            status
                            );

}

NTSTATUS
FinishDevicePowerIrp(
    IN  PDEVICE_OBJECT      DeviceObject,
    IN  PIRP                Irp,
    IN  IRP_DIRECTION       Direction,
    IN  NTSTATUS            Result
    )
/*++

Routine Description:

   This is the final step in D-IRP handling.

Arguments:

   DeviceObject - pointer to a device object.

   Irp - pointer to an D-IRP.

   Direction -  Whether to forward the D-IRP down or not.
                This depends on whether the system is powering
                up or down.

   Result  -
Return Value:

   NT status code

--*/
{
    NTSTATUS status;
    PFDO_DATA fdoData = (PFDO_DATA) DeviceObject->DeviceExtension;

    ToasterDebugPrint((3, "Entered FinishDeviceSetPowerState\n"));

    if (Direction == IRP_ALREADY_FORWARDED || (!NT_SUCCESS(Result))) {

        //
        // In either of these cases it is now time to complete the IRP.
        //
        PoStartNextPowerIrp(Irp);
        Irp->IoStatus.Status = Result;
        IoCompleteRequest(Irp, IO_NO_INCREMENT);
        ToasterIoDecrement(fdoData);
        return Result;
    }

    //
    // Here we update our result. Note that ToasterDefaultPowerHandler calls
    // PoStartNextPowerIrp for us.
    //
    Irp->IoStatus.Status = Result;
    status = ToasterDefaultPowerHandler(DeviceObject, Irp);
    ToasterIoDecrement(fdoData);
    return status;
}


NTSTATUS
HoldIoRequests(
    IN  PDEVICE_OBJECT      DeviceObject,
    IN  PIRP                Irp,
    IN  IRP_DIRECTION       Direction
    )
/*++

Routine Description:

    This routine sets queue state and queues an item to
    HoldIoRequestsWorkerRoutine.

Arguments:

Return Value:

   NT status code

--*/
{

    PIO_WORKITEM            item;
    PWORKER_THREAD_CONTEXT  context;
    NTSTATUS                status;
    PFDO_DATA               fdoData;

    fdoData = (PFDO_DATA) DeviceObject->DeviceExtension;

    fdoData->QueueState = HoldRequests;

    //
    // We must wait for the pending I/Os to finish
    // before powering down the device. But we can't wait
    // while handling a power IRP because it can deadlock
    // the system. So let us queue a worker callback
    // item to do the wait and complete the irp.
    //
    context = ExAllocatePoolWithTag (PagedPool,
                                sizeof(WORKER_THREAD_CONTEXT),
                                TOASTER_POOL_TAG);
    if(context)
    {
        item = IoAllocateWorkItem(DeviceObject);
        context->Irp = Irp;
        context->DeviceObject= DeviceObject;
        context->IrpDirection = Direction;
        context->WorkItem = item;
        if (item) {

            IoMarkIrpPending(Irp);
            IoQueueWorkItem (item,
                             HoldIoRequestsWorkerRoutine,
                             DelayedWorkQueue,
                             context);
            status = STATUS_PENDING;
        }
        else
        {
            ExFreePool(context);
            status = STATUS_INSUFFICIENT_RESOURCES;
        }
    }
    else
        status = STATUS_INSUFFICIENT_RESOURCES;

    return status;
}


VOID
HoldIoRequestsWorkerRoutine(
    IN PDEVICE_OBJECT DeviceObject,
    IN PVOID Context
)
/*++

Routine Description:

    This routine waits for the I/O in progress to finish and
    power downs the device.

Arguments:

Return Value:

   NT status code

--*/
{
    PFDO_DATA               fdoData;
    PWORKER_THREAD_CONTEXT  context = (PWORKER_THREAD_CONTEXT)Context;

    fdoData = (PFDO_DATA) DeviceObject->DeviceExtension;

    ToasterDebugPrint((3, "Waiting for pending requests to complete\n"));

    //
    // Wait for the I/O in progress to be finish.
    // The Stop event gets set when the counter drops
    // to Zero. Since our power handler routines incremented
    // the counter twice - once for the S-IRP and once for the
    // D-IRP - we must call the decrement function twice.
    //

    ToasterIoDecrement(fdoData); // one
    ToasterIoDecrement(fdoData);

    KeWaitForSingleObject(
               &fdoData->StopEvent,
               Executive,   // Waiting for reason of a driver
               KernelMode,  // Waiting in kernel mode
               FALSE,       // No allert
               NULL);       // No timeout

    //
    // Increment the counter back to take into account the S-IRP and D-IRP
    // currently in progress.
    //

    ToasterIoIncrement (fdoData);
    ToasterIoIncrement (fdoData);

    FinishDevicePowerIrp(
                    context->DeviceObject,
                    context->Irp,
                    context->IrpDirection,
                    STATUS_SUCCESS
                    );

    //
    // Cleanup before exiting from the worker thread.
    //
    IoFreeWorkItem(context->WorkItem);
    ExFreePool((PVOID)context);

}

#if DBG

PCHAR
PowerMinorFunctionString (
    UCHAR MinorFunction
)
{
    switch (MinorFunction)
    {
        case IRP_MN_SET_POWER:
            return "IRP_MN_SET_POWER";
        case IRP_MN_QUERY_POWER:
            return "IRP_MN_QUERY_POWER";
        case IRP_MN_POWER_SEQUENCE:
            return "IRP_MN_POWER_SEQUENCE";
        case IRP_MN_WAIT_WAKE:
            return "IRP_MN_WAIT_WAKE";

        default:
            return "IRP_MN_?????";
    }
}

PCHAR
DbgSystemPowerString(
    IN SYSTEM_POWER_STATE Type
    )
{
    switch (Type)
    {
        case PowerSystemUnspecified:
            return "PowerSystemUnspecified";
        case PowerSystemWorking:
            return "PowerSystemWorking";
        case PowerSystemSleeping1:
            return "PowerSystemSleeping1";
        case PowerSystemSleeping2:
            return "PowerSystemSleeping2";
        case PowerSystemSleeping3:
            return "PowerSystemSleeping3";
        case PowerSystemHibernate:
            return "PowerSystemHibernate";
        case PowerSystemShutdown:
            return "PowerSystemShutdown";
        case PowerSystemMaximum:
            return "PowerSystemMaximum";
        default:
            return "UnKnown System Power State";
    }
 }

PCHAR
DbgDevicePowerString(
    IN DEVICE_POWER_STATE Type
    )
{
    switch (Type)
    {
        case PowerDeviceUnspecified:
            return "PowerDeviceUnspecified";
        case PowerDeviceD0:
            return "PowerDeviceD0";
        case PowerDeviceD1:
            return "PowerDeviceD1";
        case PowerDeviceD2:
            return "PowerDeviceD2";
        case PowerDeviceD3:
            return "PowerDeviceD3";
        case PowerDeviceMaximum:
            return "PowerDeviceMaximum";
        default:
            return "UnKnown Device Power State";
    }
}



#endif


