/*++

Copyright (c) 2000  Microsoft Corporation

Module Name:

    vfpower.c

Abstract:

    This module handles Power Irp verification.

Author:

    Adrian J. Oney (adriao) 20-Apr-1998

Environment:

    Kernel mode

Revision History:

     AdriaO      06/15/2000 - Seperated out from ntos\io\flunkirp.c

--*/

#include "vfdef.h"

#ifdef ALLOC_PRAGMA
#pragma alloc_text(INIT,     VfPowerInit)
#pragma alloc_text(PAGEVRFY, VfPowerDumpIrpStack)
#pragma alloc_text(PAGEVRFY, VfPowerVerifyNewRequest)
#pragma alloc_text(PAGEVRFY, VfPowerVerifyIrpStackDownward)
#pragma alloc_text(PAGEVRFY, VfPowerVerifyIrpStackUpward)
#pragma alloc_text(PAGEVRFY, VfPowerIsSystemRestrictedIrp)
#pragma alloc_text(PAGEVRFY, VfPowerAdvanceIrpStatus)
#pragma alloc_text(PAGEVRFY, VfPowerTestStartedPdoStack)
#endif

#ifdef ALLOC_DATA_PRAGMA
#pragma const_seg("PAGEVRFC")
#endif

const PCHAR PowerIrpNames[] = {
    "IRP_MN_WAIT_WAKE",                       // 0x00
    "IRP_MN_POWER_SEQUENCE",                  // 0x01
    "IRP_MN_SET_POWER",                       // 0x02
    "IRP_MN_QUERY_POWER",                     // 0x03
    NULL
    };

#define MAX_NAMED_POWER_IRP 0x3

const PCHAR SystemStateNames[] = {
    "PowerSystemUnspecified",           // 0x00
    "PowerSystemWorking.S0",            // 0x01
    "PowerSystemSleeping1.S1",          // 0x02
    "PowerSystemSleeping2.S2",          // 0x03
    "PowerSystemSleeping3.S3",          // 0x04
    "PowerSystemHibernate.S4",          // 0x05
    "PowerSystemShutdown.S5",           // 0x06
    NULL
    };

#define MAX_NAMED_SYSTEM_STATES 0x6

const PCHAR DeviceStateNames[] = {
    "PowerDeviceUnspecified",           // 0x00
    "PowerDeviceD0",                    // 0x01
    "PowerDeviceD1",                    // 0x02
    "PowerDeviceD2",                    // 0x03
    "PowerDeviceD3",                    // 0x04
    NULL
    };

#define MAX_NAMED_DEVICE_STATES 0x4

const PCHAR ActionNames[] = {
    "PowerActionNone",                  // 0x00
    "PowerActionReserved",              // 0x01
    "PowerActionSleep",                 // 0x02
    "PowerActionHibernate",             // 0x03
    "PowerActionShutdown",              // 0x04
    "PowerActionShutdownReset",         // 0x05
    "PowerActionShutdownOff",           // 0x06
    "PowerActionWarmEject",             // 0x07
    NULL
    };

#define MAX_ACTION_NAMES 0x7

#ifdef ALLOC_DATA_PRAGMA
#pragma const_seg()
#endif // ALLOC_DATA_PRAGMA


VOID
VfPowerInit(
    VOID
    )
{
    VfMajorRegisterHandlers(
        IRP_MJ_POWER,
        VfPowerDumpIrpStack,
        VfPowerVerifyNewRequest,
        VfPowerVerifyIrpStackDownward,
        VfPowerVerifyIrpStackUpward,
        VfPowerIsSystemRestrictedIrp,
        NULL,
        NULL,
        NULL,
        NULL,
        NULL,
        VfPowerTestStartedPdoStack
        );
}


VOID
FASTCALL
VfPowerVerifyNewRequest(
    IN PIOV_REQUEST_PACKET  IovPacket,
    IN PDEVICE_OBJECT       DeviceObject,
    IN PIO_STACK_LOCATION   IrpLastSp           OPTIONAL,
    IN PIO_STACK_LOCATION   IrpSp,
    IN PIOV_STACK_LOCATION  StackLocationData,
    IN PVOID                CallerAddress       OPTIONAL
    )
{
    PIRP irp;
    NTSTATUS currentStatus;

    UNREFERENCED_PARAMETER (DeviceObject);
    UNREFERENCED_PARAMETER (IrpSp);
    UNREFERENCED_PARAMETER (IrpLastSp);

    irp = IovPacket->TrackedIrp;
    currentStatus = irp->IoStatus.Status;

    //
    // Verify new IRPs start out life accordingly
    //
    if (currentStatus!=STATUS_NOT_SUPPORTED) {

        WDM_FAIL_ROUTINE((
            DCERROR_POWER_IRP_BAD_INITIAL_STATUS,
            DCPARAM_IRP + DCPARAM_ROUTINE,
            CallerAddress,
            irp
            ));

        //
        // Don't blame anyone else for this driver's mistake.
        //
        if (!NT_SUCCESS(currentStatus)) {

            StackLocationData->Flags |= STACKFLAG_FAILURE_FORWARDED;
        }
    }
}


VOID
FASTCALL
VfPowerVerifyIrpStackDownward(
    IN PIOV_REQUEST_PACKET  IovPacket,
    IN PDEVICE_OBJECT       DeviceObject,
    IN PIO_STACK_LOCATION   IrpLastSp                   OPTIONAL,
    IN PIO_STACK_LOCATION   IrpSp,
    IN PIOV_STACK_LOCATION  RequestHeadLocationData,
    IN PIOV_STACK_LOCATION  StackLocationData,
    IN PVOID                CallerAddress               OPTIONAL
    )
{
    PIRP irp = IovPacket->TrackedIrp;
    NTSTATUS currentStatus, lastStatus;
    BOOLEAN statusChanged;
    PDRIVER_OBJECT driverObject;
    PIOV_SESSION_DATA iovSessionData;

    UNREFERENCED_PARAMETER (IrpSp);

    currentStatus = irp->IoStatus.Status;
    lastStatus = RequestHeadLocationData->LastStatusBlock.Status;
    statusChanged = (BOOLEAN)(currentStatus != lastStatus);
    iovSessionData = VfPacketGetCurrentSessionData(IovPacket);

    //
    // Verify the IRP was forwarded properly
    //
    if (iovSessionData->ForwardMethod == SKIPPED_A_DO) {

        WDM_FAIL_ROUTINE((
            DCERROR_SKIPPED_DEVICE_OBJECT,
            DCPARAM_IRP + DCPARAM_ROUTINE,
            CallerAddress,
            irp
            ));
    }

    //
    // For some IRP major's going down a stack, there *must* be a handler
    //
    driverObject = DeviceObject->DriverObject;

    if (!IovUtilHasDispatchHandler(driverObject, IRP_MJ_POWER)) {

        RequestHeadLocationData->Flags |= STACKFLAG_BOGUS_IRP_TOUCHED;

        WDM_FAIL_ROUTINE((
            DCERROR_MISSING_DISPATCH_FUNCTION,
            DCPARAM_IRP + DCPARAM_ROUTINE,
            driverObject->DriverInit,
            irp
            ));

        StackLocationData->Flags |= STACKFLAG_NO_HANDLER;
    }

    //
    // The following is only executed if we are not a new IRP...
    //
    if (IrpLastSp == NULL) {
        return;
    }

    //
    // The only legit failure code to pass down is STATUS_NOT_SUPPORTED
    //
    if ((!NT_SUCCESS(currentStatus)) && (currentStatus != STATUS_NOT_SUPPORTED) &&
        (!(RequestHeadLocationData->Flags & STACKFLAG_FAILURE_FORWARDED))) {

        WDM_FAIL_ROUTINE((
            DCERROR_POWER_FAILURE_FORWARDED,
            DCPARAM_IRP + DCPARAM_ROUTINE,
            CallerAddress,
            irp
            ));

        //
        // Don't blame anyone else for this drivers's mistakes.
        //
        RequestHeadLocationData->Flags |= STACKFLAG_FAILURE_FORWARDED;
    }

    //
    // Status of a Power IRP may not be converted to STATUS_NOT_SUPPORTED on
    // the way down.
    //
    if ((currentStatus == STATUS_NOT_SUPPORTED)&&statusChanged) {

        WDM_FAIL_ROUTINE((
            DCERROR_POWER_IRP_STATUS_RESET,
            DCPARAM_IRP + DCPARAM_ROUTINE,
            CallerAddress,
            irp
            ));
    }
}


VOID
FASTCALL
VfPowerVerifyIrpStackUpward(
    IN PIOV_REQUEST_PACKET  IovPacket,
    IN PIO_STACK_LOCATION   IrpSp,
    IN PIOV_STACK_LOCATION  RequestHeadLocationData,
    IN PIOV_STACK_LOCATION  StackLocationData,
    IN BOOLEAN              IsNewlyCompleted,
    IN BOOLEAN              RequestFinalized
    )
{
    PIRP irp;
    NTSTATUS currentStatus;
    BOOLEAN mustPassDown, isBogusIrp, isPdo;
    PVOID routine;

    UNREFERENCED_PARAMETER (IrpSp);
    UNREFERENCED_PARAMETER (RequestFinalized);

    irp = IovPacket->TrackedIrp;
    currentStatus = irp->IoStatus.Status;

    //
    // Who'd we call for this one?
    //
    routine = StackLocationData->LastDispatch;
    ASSERT(routine) ;

    //
    // If this "Request" has been "Completed", perform some checks
    //
    if (IsNewlyCompleted) {

        //
        // Remember bogosity...
        //
        isBogusIrp = (BOOLEAN)((IovPacket->Flags&TRACKFLAG_BOGUS)!=0);

        //
        // Is this a PDO?
        //
        isPdo = (BOOLEAN)((StackLocationData->Flags&STACKFLAG_REACHED_PDO)!=0);

        //
        // Was anything completed too early?
        // A driver may outright fail almost anything but a bogus IRP
        //
        mustPassDown = (BOOLEAN)(!(StackLocationData->Flags&STACKFLAG_NO_HANDLER));
        mustPassDown &= (!isPdo);

        mustPassDown &= (isBogusIrp || NT_SUCCESS(currentStatus) || (currentStatus == STATUS_NOT_SUPPORTED));
        if (mustPassDown) {

            //
            // Print appropriate error message
            //
            if (IovPacket->Flags&TRACKFLAG_BOGUS) {

                WDM_FAIL_ROUTINE((
                    DCERROR_BOGUS_POWER_IRP_COMPLETED,
                    DCPARAM_IRP + DCPARAM_ROUTINE,
                    routine,
                    irp
                    ));

            } else if (NT_SUCCESS(currentStatus)) {

                WDM_FAIL_ROUTINE((
                    DCERROR_SUCCESSFUL_POWER_IRP_NOT_FORWARDED,
                    DCPARAM_IRP + DCPARAM_ROUTINE,
                    routine,
                    irp
                    ));

            } else if (currentStatus == STATUS_NOT_SUPPORTED) {

                WDM_FAIL_ROUTINE((
                    DCERROR_UNTOUCHED_POWER_IRP_NOT_FORWARDED,
                    DCPARAM_IRP + DCPARAM_ROUTINE,
                    routine,
                    irp
                    ));
            }
        }
    }

    //
    // Did anyone stomp the status erroneously?
    //
    if ((currentStatus == STATUS_NOT_SUPPORTED) &&
        (currentStatus != RequestHeadLocationData->LastStatusBlock.Status)) {

        //
        // Status of a PnP or Power IRP may not be converted from success to
        // STATUS_NOT_SUPPORTED on the way down.
        //
        WDM_FAIL_ROUTINE((
            DCERROR_POWER_IRP_STATUS_RESET,
            DCPARAM_IRP + DCPARAM_ROUTINE,
            routine,
            irp
            ));
    }
}


VOID
FASTCALL
VfPowerDumpIrpStack(
    IN PIO_STACK_LOCATION IrpSp
    )
{
    DbgPrint("IRP_MJ_POWER.");

    if (IrpSp->MinorFunction <= MAX_NAMED_POWER_IRP) {

        DbgPrint(PowerIrpNames[IrpSp->MinorFunction]);

        if ((IrpSp->MinorFunction == IRP_MN_QUERY_POWER) ||
            (IrpSp->MinorFunction == IRP_MN_SET_POWER)) {

            DbgPrint("(");

            if (IrpSp->Parameters.Power.Type == SystemPowerState) {

                if (IrpSp->Parameters.Power.State.SystemState <= MAX_NAMED_SYSTEM_STATES) {

                    DbgPrint(SystemStateNames[IrpSp->Parameters.Power.State.SystemState]);
                }

            } else {

                if (IrpSp->Parameters.Power.State.DeviceState <= MAX_NAMED_DEVICE_STATES) {

                    DbgPrint(DeviceStateNames[IrpSp->Parameters.Power.State.DeviceState]);
                }
            }

            if (IrpSp->Parameters.Power.ShutdownType <= MAX_ACTION_NAMES) {

                DbgPrint(".%s", ActionNames[IrpSp->Parameters.Power.ShutdownType]);
            }

            DbgPrint(")");
        }

    } else if (IrpSp->MinorFunction == 0xFF) {

        DbgPrint("IRP_MN_BOGUS");

    } else {

        DbgPrint("(Bogus)");
    }
}


BOOLEAN
FASTCALL
VfPowerIsSystemRestrictedIrp(
    IN PIO_STACK_LOCATION IrpSp
    )
{
    switch(IrpSp->MinorFunction) {
        case IRP_MN_POWER_SEQUENCE:
            return FALSE;
        case IRP_MN_QUERY_POWER:
        case IRP_MN_SET_POWER:
        case IRP_MN_WAIT_WAKE:
            return TRUE;
        default:
            break;
    }

    return TRUE;
}


BOOLEAN
FASTCALL
VfPowerAdvanceIrpStatus(
    IN     PIO_STACK_LOCATION   IrpSp,
    IN     NTSTATUS             OriginalStatus,
    IN OUT NTSTATUS             *StatusToAdvance
    )
/*++

  Description:

     Given an IRP stack pointer, is it legal to change the status for
     debug-ability? If so, this function determines what the new status
     should be. Note that for each stack location, this function is iterated
     over n times where n is equal to the number of drivers who IoSkip'd this
     location.

  Arguments:

     IrpSp           - Current stack right after complete for the given stack
                       location, but before the completion routine for the
                       stack location above has been called.

     OriginalStatus  - The status of the IRP at the time listed above. Does
                       not change over iteration per skipping driver.

     StatusToAdvance - Pointer to the current status that should be updated.

  Return Value:

     TRUE if the status has been adjusted, FALSE otherwise (in this case
         StatusToAdvance is untouched).

--*/
{
    UNREFERENCED_PARAMETER (IrpSp);

    if (((ULONG) OriginalStatus) >= 256) {

        return FALSE;
    }

    (*StatusToAdvance)++;
    if ((*StatusToAdvance) == STATUS_PENDING) {
        (*StatusToAdvance)++;
    }

    return TRUE;
}


VOID
FASTCALL
VfPowerTestStartedPdoStack(
    IN PDEVICE_OBJECT   PhysicalDeviceObject
    )
/*++

    Description:
        As per the title, we are going to throw some IRPs at the stack to
        see if they are handled correctly.

    Returns:

        Nothing
--*/
{
    IO_STACK_LOCATION irpSp;

    PAGED_CODE();

    //
    // Initialize the stack location to pass to IopSynchronousCall()
    //
    RtlZeroMemory(&irpSp, sizeof(IO_STACK_LOCATION));

    if (VfSettingsIsOptionEnabled(NULL, VERIFIER_OPTION_SEND_BOGUS_POWER_IRPS)) {

        //
        // And a bogus Power IRP
        //
        irpSp.MajorFunction = IRP_MJ_POWER;
        irpSp.MinorFunction = 0xff;
        VfIrpSendSynchronousIrp(
            PhysicalDeviceObject,
            &irpSp,
            TRUE,
            STATUS_NOT_SUPPORTED,
            0,
            NULL,
            NULL
            );
    }
}