/*++

Copyright (c) 1997 Microsoft Corporation

Module Name:

    power.c

Abstract:

    This module contains the code that handles the power IRPs for the serial
    driver.


Environment:

    Kernel mode

Revision History :


--*/

#include "precomp.h"

#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGESRP0, SerialGotoPowerState)
#pragma alloc_text(PAGESRP0, SerialPowerDispatch)
#pragma alloc_text(PAGESRP0, SerialSetPowerD0)
#pragma alloc_text(PAGESRP0, SerialSetPowerD3)
#pragma alloc_text(PAGESRP0, SerialSaveDeviceState)
#pragma alloc_text(PAGESRP0, SerialRestoreDeviceState)
#pragma alloc_text(PAGESRP0, SerialSendWaitWake)
#endif // ALLOC_PRAGMA


NTSTATUS
SerialSystemPowerCompletion(IN PDEVICE_OBJECT PDevObj, UCHAR MinorFunction,
                            IN POWER_STATE PowerState, IN PVOID Context,
                            PIO_STATUS_BLOCK IoStatus)
/*++

Routine Description:

    This routine is the completion routine for PoRequestPowerIrp calls
    in this module.

Arguments:

    PDevObj - Pointer to the device object the irp is completing for

    MinorFunction - IRP_MN_XXXX value requested

    PowerState - Power state request was made of

    Context - Event to set or NULL if no setting required

    IoStatus - Status block from request

Return Value:

    VOID


--*/
{
   if (Context != NULL) {
      KeSetEvent((PKEVENT)Context, IO_NO_INCREMENT, 0);
   }

   return STATUS_SUCCESS;
}



VOID
SerialSaveDeviceState(IN PSERIAL_DEVICE_EXTENSION PDevExt)
/*++

Routine Description:

    This routine saves the device state of the UART

Arguments:

    PDevExt - Pointer to the device extension for the devobj to save the state
              for.

Return Value:

    VOID


--*/
{
   PSERIAL_DEVICE_STATE pDevState = &PDevExt->DeviceState;
#if defined(NEC_98)
   //
   // This argument use at MACRO only.
   //
   PSERIAL_DEVICE_EXTENSION Extension = PDevExt;
#else
#endif //defined(NEC_98)
   PAGED_CODE();

   SerialDump(SERTRACECALLS, ("SERIAL: Entering SerialSaveDeviceState\n"));

   //
   // Read necessary registers direct
   //

#if defined(NEC_98)
   pDevState->IER = READ_INTERRUPT_ENABLE(Extension->Controller);
#else
   pDevState->IER = READ_INTERRUPT_ENABLE(PDevExt->Controller);
#endif //defined(NEC_98)
   pDevState->MCR = READ_MODEM_CONTROL(PDevExt->Controller);
   pDevState->LCR = READ_LINE_CONTROL(PDevExt->Controller);


   SerialDump(SERTRACECALLS, ("SERIAL: Leaving SerialSaveDeviceState\n"));
}


VOID
SerialRestoreDeviceState(IN PSERIAL_DEVICE_EXTENSION PDevExt)
/*++

Routine Description:

    This routine restores the device state of the UART

Arguments:

    PDevExt - Pointer to the device extension for the devobj to restore the
    state for.

Return Value:

    VOID


--*/
{
   PSERIAL_DEVICE_STATE pDevState = &PDevExt->DeviceState;
   SHORT divisor;
   SERIAL_IOCTL_SYNC S;
#if defined(NEC_98)
   //
   // This argument use at MACRO only.
   //
   PSERIAL_DEVICE_EXTENSION Extension = PDevExt;
#else
#endif //defined(NEC_98)

   PAGED_CODE();

   SerialDump(SERTRACECALLS, ("SERIAL: Enter SerialRestoreDeviceState\n"));
   SerialDump(SERTRACECALLS, ("------  PDevExt: %x\n", PDevExt));

   //
   // Disable interrupts both via OUT2 and IER
   //

   WRITE_MODEM_CONTROL(PDevExt->Controller, 0);
   DISABLE_ALL_INTERRUPTS(PDevExt->Controller);

   //
   // Set the baud rate
   //

   SerialGetDivisorFromBaud(PDevExt->ClockRate, PDevExt->CurrentBaud, &divisor);
   S.Extension = PDevExt;
   S.Data = (PVOID)divisor;
   SerialSetBaud(&S);

   //
   // Reset / Re-enable the FIFO's
   //

   if (PDevExt->FifoPresent) {
      WRITE_FIFO_CONTROL(PDevExt->Controller, (UCHAR)0);
      READ_RECEIVE_BUFFER(PDevExt->Controller);
      WRITE_FIFO_CONTROL(PDevExt->Controller,
                         (UCHAR)(SERIAL_FCR_ENABLE | PDevExt->RxFifoTrigger
                                 | SERIAL_FCR_RCVR_RESET
                                 | SERIAL_FCR_TXMT_RESET));
   } else {
      WRITE_FIFO_CONTROL(PDevExt->Controller, (UCHAR)0);
   }

   //
   // In case we are dealing with a bitmasked multiportcard,
   // that has the mask register enabled, enable the
   // interrupts.
   //

   if (PDevExt->InterruptStatus) {
      if (PDevExt->Indexed) {
            WRITE_PORT_UCHAR(PDevExt->InterruptStatus, (UCHAR)0xFF);
      } else {
         //
         // Either we are standalone or already mapped
         //

         if (PDevExt->OurIsrContext == PDevExt) {
            //
            // This is a standalone
            //

            WRITE_PORT_UCHAR(PDevExt->InterruptStatus,
                             (UCHAR)(1 << (PDevExt->PortIndex - 1)));
         } else {
            //
            // One of many
            //

            WRITE_PORT_UCHAR(PDevExt->InterruptStatus,
                             (UCHAR)((PSERIAL_MULTIPORT_DISPATCH)PDevExt->
                                     OurIsrContext)->UsablePortMask);
         }
      }
   }

   //
   // Restore a couple more registers
   //

#if defined(NEC_98)
   WRITE_INTERRUPT_ENABLE(Extension->Controller, pDevState->IER);
#else
   WRITE_INTERRUPT_ENABLE(PDevExt->Controller, pDevState->IER);
#endif //defined(NEC_98)
   WRITE_LINE_CONTROL(PDevExt->Controller, pDevState->LCR);

   //
   // Clear out any stale interrupts
   //

   READ_INTERRUPT_ID_REG(PDevExt->Controller);
   READ_LINE_STATUS(PDevExt->Controller);
   READ_MODEM_STATUS(PDevExt->Controller);


   if (PDevExt->DeviceState.Reopen == TRUE) {
      SerialDump(SERPNPPOWER, ("SERIAL: Reopening device\n"));

      PDevExt->DeviceIsOpened = TRUE;
      PDevExt->DeviceState.Reopen = FALSE;

      //
      // This enables interrupts on the device!
      //

      WRITE_MODEM_CONTROL(PDevExt->Controller,
                          (UCHAR)(pDevState->MCR | SERIAL_MCR_OUT2));

      //
      // Refire the state machine
      //

      DISABLE_ALL_INTERRUPTS(PDevExt->Controller);
      ENABLE_ALL_INTERRUPTS(PDevExt->Controller);
   }

}


NTSTATUS
SerialPowerDispatch(IN PDEVICE_OBJECT PDevObj, IN PIRP PIrp)

/*++

Routine Description:

    This is a dispatch routine for the IRPs that come to the driver with the
    IRP_MJ_POWER major code (power IRPs).

Arguments:

    PDevObj - Pointer to the device object for this device

    PIrp - Pointer to the IRP for the current request

Return Value:

    The function value is the final status of the call


--*/

{

   PSERIAL_DEVICE_EXTENSION pDevExt = PDevObj->DeviceExtension;
   PIO_STACK_LOCATION pIrpStack = IoGetCurrentIrpStackLocation(PIrp);
   NTSTATUS status;
   PDEVICE_OBJECT pLowerDevObj = pDevExt->LowerDeviceObject;
   PDEVICE_OBJECT pPdo = pDevExt->Pdo;
   BOOLEAN acceptingIRPs;

   PAGED_CODE();

   if ((status = SerialIRPPrologue(PIrp, pDevExt)) != STATUS_SUCCESS) {
      PoStartNextPowerIrp(PIrp);
      SerialCompleteRequest(pDevExt, PIrp, IO_NO_INCREMENT);
      return status;
   }

   status = STATUS_SUCCESS;

   switch (pIrpStack->MinorFunction) {

   case IRP_MN_WAIT_WAKE:
      SerialDump(SERPNPPOWER, ("SERIAL: Got IRP_MN_WAIT_WAKE Irp\n"));
      break;


   case IRP_MN_POWER_SEQUENCE:
      SerialDump(SERPNPPOWER, ("SERIAL: Got IRP_MN_POWER_SEQUENCE Irp\n"));
      break;


   case IRP_MN_SET_POWER:
      SerialDump(SERPNPPOWER, ("SERIAL: Got IRP_MN_SET_POWER Irp\n"));

      //
      // Perform different ops if it was system or device
      //

      switch (pIrpStack->Parameters.Power.Type) {
      case SystemPowerState: {
            POWER_STATE powerState;

            //
            // They asked for a system power state change
            //

            SerialDump(SERPNPPOWER, ("------: SystemPowerState\n"));

            //
            // We will only service this if we are policy owner -- we
            // don't need to lock on this value since we only service
            // one power request at a time.
            //

            if (pDevExt->OwnsPowerPolicy != TRUE) {
               status = STATUS_SUCCESS;
               goto PowerExit;
            }


            switch (pIrpStack->Parameters.Power.State.SystemState) {
            case PowerSystemUnspecified:
               powerState.DeviceState = PowerDeviceUnspecified;
               break;

            case PowerSystemWorking:
               powerState.DeviceState = PowerDeviceD0;
               break;

            case PowerSystemSleeping1:
            case PowerSystemSleeping2:
            case PowerSystemSleeping3:
            case PowerSystemHibernate:
            case PowerSystemShutdown:
            case PowerSystemMaximum:
               powerState.DeviceState = PowerDeviceD3;
               break;

            default:
               status = STATUS_SUCCESS;
               goto PowerExit;
               break;
            }


            PoSetPowerState(PDevObj, pIrpStack->Parameters.Power.Type,
                            pIrpStack->Parameters.Power.State);

            //
            // Send IRP to change device state if we should change
            //

            //
            // We only power up the stack if the device is open.  This is based
            // on our policy of keeping the device powered down unless it is
            // open.
            //

            if (((powerState.DeviceState < pDevExt->PowerState)
                 && pDevExt->OpenCount)) {
               PoRequestPowerIrp(pPdo, IRP_MN_SET_POWER, powerState, NULL, NULL,
                                 NULL);
            }else {
               //
               // If powering down, we can't go past wake state
               // if wait-wake pending
               //

               if (powerState.DeviceState >= pDevExt->PowerState) {

                  //
                  // Power down -- ensure there is no wake-wait pending OR
                  // we can do down to that level and still wake the machine
                  //

                  if ((pDevExt->PendingWakeIrp == NULL && !pDevExt->SendWaitWake)
                      || powerState.DeviceState <= pDevExt->DeviceWake) {
                     PoRequestPowerIrp(pPdo, IRP_MN_SET_POWER, powerState, NULL,
                                       NULL, NULL);
                  }
               }
            }


            status = STATUS_SUCCESS;
            goto PowerExit;
         }

      case DevicePowerState:
         SerialDump(SERPNPPOWER, ("------: DevicePowerState\n"));
         break;

      default:
         SerialDump(SERPNPPOWER, ("------: UNKNOWN PowerState\n"));
         status = STATUS_SUCCESS;
         goto PowerExit;
      }


      //
      // If we are already in the requested state, just pass the IRP down
      //

      if (pDevExt->PowerState
          == pIrpStack->Parameters.Power.State.DeviceState) {
         SerialDump(SERPNPPOWER, ("SERIAL: Already in requested power state\n")
                    );
         status = STATUS_SUCCESS;
         break;
      }


      switch (pIrpStack->Parameters.Power.State.DeviceState) {

      case PowerDeviceD0:
         SerialDump(SERPNPPOWER, ("SERIAL: Going to power state D0\n"));
         return SerialSetPowerD0(PDevObj, PIrp);

      case PowerDeviceD1:
      case PowerDeviceD2:
      case PowerDeviceD3:
         SerialDump(SERPNPPOWER, ("SERIAL: Going to power state D3\n"));
         return SerialSetPowerD3(PDevObj, PIrp);

      default:
         break;
      }
      break;



   case IRP_MN_QUERY_POWER:

      SerialDump (SERPNPPOWER, ("SERIAL: Got IRP_MN_QUERY_POWER Irp\n"));

      //
      // Check if we have a wait-wake pending and if so,
      // ensure we don't power down too far.
      //

      if (pDevExt->PendingWakeIrp != NULL || pDevExt->SendWaitWake) {
         if (pIrpStack->Parameters.Power.Type == DevicePowerState
             && pIrpStack->Parameters.Power.State.DeviceState
             > pDevExt->DeviceWake) {
            status = PIrp->IoStatus.Status = STATUS_INVALID_DEVICE_STATE;
            PoStartNextPowerIrp(PIrp);
            SerialCompleteRequest(pDevExt, PIrp, IO_NO_INCREMENT);
            return status;
         }
      }

      //
      // If no wait-wake, always successful
      //

      PIrp->IoStatus.Status = STATUS_SUCCESS;
      status = STATUS_SUCCESS;
      PoStartNextPowerIrp(PIrp);
      IoSkipCurrentIrpStackLocation(PIrp);
      return SerialPoCallDriver(pDevExt, pLowerDevObj, PIrp);

   }   // switch (pIrpStack->MinorFunction)


   PowerExit:;

   PoStartNextPowerIrp(PIrp);


   //
   // Pass to the lower driver
   //
   IoSkipCurrentIrpStackLocation(PIrp);
   status = SerialPoCallDriver(pDevExt, pLowerDevObj, PIrp);

   return status;
}





NTSTATUS
SerialSetPowerD0(IN PDEVICE_OBJECT PDevObj, IN PIRP PIrp)

/*++

Routine Description:

    This routine Decides if we need to pass the power Irp down the stack
    or not.  It then either sets up a completion handler to finish the
    initialization or calls the completion handler directly.

Arguments:

    PDevObj - Pointer to the devobj we are changing power state on

    PIrp - Pointer to the IRP for the current request

Return Value:

    Return status of either PoCallDriver of the call to the initialization
    routine.


--*/

{
   PSERIAL_DEVICE_EXTENSION pDevExt = PDevObj->DeviceExtension;
   PIO_STACK_LOCATION pIrpStack = IoGetCurrentIrpStackLocation(PIrp);
   NTSTATUS status;

   PAGED_CODE();

   SerialDump(SERTRACECALLS, ("SERIAL: In SerialSetPowerD0\n"));
   SerialDump(SERPNPPOWER, ("SERIAL: SetPowerD0 has IRP %x\n", PIrp));

   ASSERT(pDevExt->LowerDeviceObject);

   //
   // Set up completion to init device when it is on
   //

   KeClearEvent(&pDevExt->PowerD0Event);


   IoCopyCurrentIrpStackLocationToNext(PIrp);
   IoSetCompletionRoutine(PIrp, SerialSyncCompletion, &pDevExt->PowerD0Event,
                          TRUE, TRUE, TRUE);

   SerialDump(SERPNPPOWER, ("SERIAL: Calling next driver\n"));

   status = PoCallDriver(pDevExt->LowerDeviceObject, PIrp);

   if (status == STATUS_PENDING) {
      SerialDump(SERPNPPOWER, ("SERIAL: Waiting for next driver\n"));
      KeWaitForSingleObject (&pDevExt->PowerD0Event, Executive, KernelMode,
                             FALSE, NULL);
   } else {
      if (!NT_SUCCESS(status)) {
         PIrp->IoStatus.Status = status;
         PoStartNextPowerIrp(PIrp);
         SerialCompleteRequest(pDevExt, PIrp, IO_NO_INCREMENT);
         return status;
      }
   }

   if (!NT_SUCCESS(PIrp->IoStatus.Status)) {
      status = PIrp->IoStatus.Status;
      PoStartNextPowerIrp(PIrp);
      SerialCompleteRequest(pDevExt, PIrp, IO_NO_INCREMENT);
      return status;
   }

   //
   // Restore the device
   //

   pDevExt->PowerState = PowerDeviceD0;


   //
   // Theoretically we could change states in the middle of processing
   // the restore which would result in a bad PKINTERRUPT being used
   // in SerialRestoreDeviceState().
   //

   if (pDevExt->PNPState == SERIAL_PNP_STARTED) {
      SerialRestoreDeviceState(pDevExt);
   }

   //
   // Now that we are powered up, call PoSetPowerState
   //

   PoSetPowerState(PDevObj, pIrpStack->Parameters.Power.Type,
                   pIrpStack->Parameters.Power.State);

   PoStartNextPowerIrp(PIrp);
   SerialCompleteRequest(pDevExt, PIrp, IO_NO_INCREMENT);


   SerialDump(SERTRACECALLS, ("SERIAL: Leaving SerialSetPowerD0\n"));
   return status;
}


NTSTATUS
SerialGotoPowerState(IN PDEVICE_OBJECT PDevObj,
                     IN PSERIAL_DEVICE_EXTENSION PDevExt,
                     IN DEVICE_POWER_STATE DevPowerState)
/*++

Routine Description:

    This routine causes the driver to request the stack go to a particular
    power state.

Arguments:

    PDevObj - Pointer to the device object for this device

    PDevExt - Pointer to the device extension we are working from

    DevPowerState - the power state we wish to go to

Return Value:

    The function value is the final status of the call


--*/
{
   KEVENT gotoPowEvent;
   NTSTATUS status;
   POWER_STATE powerState;

   PAGED_CODE();

   SerialDump(SERTRACECALLS, ("SERIAL: In SerialGotoPowerState\n"));

   powerState.DeviceState = DevPowerState;

   KeInitializeEvent(&gotoPowEvent, SynchronizationEvent, FALSE);

   status = PoRequestPowerIrp(PDevObj, IRP_MN_SET_POWER, powerState,
                              SerialSystemPowerCompletion, &gotoPowEvent,
                              NULL);

   if (status == STATUS_PENDING) {
      KeWaitForSingleObject(&gotoPowEvent, Executive, KernelMode, FALSE, NULL);
      status = STATUS_SUCCESS;
   }

#if DBG
   if (!NT_SUCCESS(status)) {
      SerialDump(SERPNPPOWER, ("SERIAL: SerialGotoPowerState FAILED\n"));
   }
#endif

   SerialDump(SERTRACECALLS, ("SERIAL: Leaving SerialGotoPowerState\n"));

   return status;
}




NTSTATUS
SerialSetPowerD3(IN PDEVICE_OBJECT PDevObj, IN PIRP PIrp)
/*++

Routine Description:

    This routine handles the SET_POWER minor function.

Arguments:

    PDevObj - Pointer to the device object for this device

    PIrp - Pointer to the IRP for the current request

Return Value:

    The function value is the final status of the call


--*/
{
   NTSTATUS status = STATUS_SUCCESS;
   PSERIAL_DEVICE_EXTENSION pDevExt = PDevObj->DeviceExtension;
   PIO_STACK_LOCATION pIrpStack = IoGetCurrentIrpStackLocation(PIrp);

   PAGED_CODE();

   SerialDump(SERDIAG3, ("SERIAL: In SerialSetPowerD3\n"));

   //
   // Send the wait wake now, just in time
   //


   if (pDevExt->SendWaitWake) {
      SerialSendWaitWake(pDevExt);
   }
   //
   // Before we power down, call PoSetPowerState
   //

   PoSetPowerState(PDevObj, pIrpStack->Parameters.Power.Type,
                   pIrpStack->Parameters.Power.State);

   //
   // If the device is not closed, disable interrupts and allow the fifo's
   // to flush.
   //

   if (pDevExt->DeviceIsOpened == TRUE) {
      LARGE_INTEGER charTime;

      pDevExt->DeviceIsOpened = FALSE;
      pDevExt->DeviceState.Reopen = TRUE;

      charTime.QuadPart = -SerialGetCharTime(pDevExt).QuadPart;

      //
      // Shut down the chip
      //

      SerialDisableUART(pDevExt);

      //
      // Drain the device
      //

      SerialDrainUART(pDevExt, &charTime);

      //
      // Save the device state
      //

      SerialSaveDeviceState(pDevExt);
   }

   //
   // If the device is not open, we don't need to save the state;
   // we can just reset the device on power-up
   //


   PIrp->IoStatus.Status = STATUS_SUCCESS;

   pDevExt->PowerState = PowerDeviceD3;

   //
   // For what we are doing, we don't need a completion routine
   // since we don't race on the power requests.
   //

   PIrp->IoStatus.Status = STATUS_SUCCESS;

   PoStartNextPowerIrp(PIrp);
   IoSkipCurrentIrpStackLocation(PIrp);

   return SerialPoCallDriver(pDevExt, pDevExt->LowerDeviceObject, PIrp);
}


NTSTATUS
SerialSendWaitWake(PSERIAL_DEVICE_EXTENSION PDevExt)
/*++

Routine Description:

    This routine causes a waitwake IRP to be sent

Arguments:

    PDevExt - Pointer to the device extension for this device

Return Value:

    STATUS_INVALID_DEVICE_STATE if one is already pending, else result
    of call to PoRequestPowerIrp.


--*/
{
   NTSTATUS status;
   PIRP pIrp;
   POWER_STATE powerState;

   PAGED_CODE();

   //
   // Make sure one isn't pending already -- serial will only handle one at
   // a time.
   //

   if (PDevExt->PendingWakeIrp != NULL) {
      return STATUS_INVALID_DEVICE_STATE;
   }

   //
   // Make sure we are capable of waking the machine
   //

   if (PDevExt->SystemWake <= PowerSystemWorking) {
      return STATUS_INVALID_DEVICE_STATE;
   }

   if (PDevExt->DeviceWake == PowerDeviceUnspecified) {
      return STATUS_INVALID_DEVICE_STATE;
   }

   //
   // Send IRP to request wait wake and add a pending irp flag
   //
   //

   InterlockedIncrement(&PDevExt->PendingIRPCnt);

   powerState.SystemState = PDevExt->SystemWake;

   status = PoRequestPowerIrp(PDevExt->Pdo, IRP_MN_WAIT_WAKE,
                              powerState, SerialWakeCompletion, PDevExt, &pIrp);

   if (status == STATUS_PENDING) {
      status = STATUS_SUCCESS;
      PDevExt->PendingWakeIrp = pIrp;
   } else if (!NT_SUCCESS(status)) {
      SerialIRPEpilogue(PDevExt);
   }

   return status;
}

NTSTATUS
SerialWakeCompletion(IN PDEVICE_OBJECT PDevObj, IN UCHAR MinorFunction,
                     IN POWER_STATE PowerState, IN PVOID Context,
                     IN PIO_STATUS_BLOCK IoStatus)
/*++

Routine Description:

    This routine handles completion of the waitwake IRP.

Arguments:

    PDevObj - Pointer to the device object for this device

    MinorFunction - Minor function previously supplied to PoRequestPowerIrp

    PowerState - PowerState previously supplied to PoRequestPowerIrp

    Context - a pointer to the device extension

    IoStatus - current/final status of the waitwake IRP

Return Value:

    The function value is the final status of attempting to process the
    waitwake.


--*/
{
   NTSTATUS status;
   PSERIAL_DEVICE_EXTENSION pDevExt = (PSERIAL_DEVICE_EXTENSION)Context;
   POWER_STATE powerState;

   status = IoStatus->Status;

   if (NT_SUCCESS(status)) {
      //
      // A wakeup has occurred -- powerup our stack
      //

      powerState.DeviceState = PowerDeviceD0;

      PoRequestPowerIrp(pDevExt->Pdo, IRP_MN_SET_POWER, powerState, NULL,
                        NULL, NULL);

   }

   pDevExt->PendingWakeIrp = NULL;
   SerialIRPEpilogue(pDevExt);

   return status;
}