/** @file
    IPMI FRB Driver.

Copyright (c) 2018 - 2019, Intel Corporation. All rights reserved.<BR>
SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include <PiDxe.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/DebugLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/UefiLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/PcdLib.h>
#include <Library/IpmiCommandLib.h>
#include <IndustryStandard/Ipmi.h>

EFI_STATUS
EfiDisableFrb (
  VOID
  )
/*++

  Routine Description:
    This routine disables the specified FRB timer.

  Arguments:
    This        - This pointer
    FrbType     - Type of FRB timer to get data on

  Returns:
    EFI_SUCCESS     - FRB timer was disabled
    EFI_ABORTED     - Timer was already stopped
    EFI_UNSUPPORTED - This type of FRB timer is not supported.

--*/
{
  EFI_STATUS                       Status;
  IPMI_SET_WATCHDOG_TIMER_REQUEST  SetWatchdogTimer;
  UINT8                            CompletionCode;
  IPMI_GET_WATCHDOG_TIMER_RESPONSE GetWatchdogTimer;

  Status = IpmiGetWatchdogTimer (&GetWatchdogTimer);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // Check if timer is still running, if not abort disable routine.
  //
  if (GetWatchdogTimer.TimerUse.Bits.TimerRunning == 0) {
    return EFI_ABORTED;
  }

  ZeroMem (&SetWatchdogTimer, sizeof(SetWatchdogTimer));
  //
  // Just flip the Timer Use bit. This should release the timer.
  //
  SetWatchdogTimer.TimerUse.Bits.TimerRunning    = 0;
  SetWatchdogTimer.TimerUse.Bits.TimerUse        = IPMI_WATCHDOG_TIMER_BIOS_FRB2;
  SetWatchdogTimer.TimerUseExpirationFlagsClear &= ~BIT2;
  SetWatchdogTimer.TimerUseExpirationFlagsClear |= BIT1 | BIT4;

  Status = IpmiSetWatchdogTimer (&SetWatchdogTimer, &CompletionCode);
  return Status;
}

VOID
EFIAPI
DisableFRB2Handler (
  IN EFI_EVENT        Event,
  IN VOID             *Context
  )
/*++

  Routine Description:
    Disables FRB2. This function gets called each time the
    EFI_EVENT_SIGNAL_READY_TO_BOOT gets signaled

  Arguments:
    Standard event notification function arguments:
    Event         - the event that is signaled.
    Context       - not used here.

  Returns:

--*/
{
  DEBUG((DEBUG_ERROR, "!!! enter DisableFRB2Handler()!!!\n"));

  EfiDisableFrb ();
}

EFI_STATUS
CheckForAndReportErrors(
  VOID
  )
/*++

  Routine Description:
    Check the Watchdog timer expiration flags and report the kind of watchdog
    timeout occurred to the Error Manager.

  Arguments:

  Returns:
    EFI_SUCCESS     - Errors retrieved and reported

--*/
{
  EFI_STATUS                          Status;
  IPMI_GET_WATCHDOG_TIMER_RESPONSE    GetWatchdogTimer;
  IPMI_SET_WATCHDOG_TIMER_REQUEST     SetWatchdogTimer;
  UINT8                               CompletionCode;

  //
  // Get the Watchdog timer info to find out what kind of timer expiration occurred.
  //
  Status = IpmiGetWatchdogTimer (&GetWatchdogTimer);
  if (EFI_ERROR (Status)) {
    return Status;
  }
  //
  // If FRB2 Failure occurred, report it to the error manager and log a SEL.
  //
  if ((GetWatchdogTimer.TimerUseExpirationFlagsClear & BIT1) != 0) {
    //
    // Report the FRB2 time-out error
    //
  } else if ((GetWatchdogTimer.TimerUseExpirationFlagsClear & BIT3) != 0) {
    //
    // Report the OS Watchdog timer failure
    //
  }

  //
  // Need to clear Timer expiration flags after checking.
  //
  ZeroMem (&SetWatchdogTimer, sizeof(SetWatchdogTimer));
  SetWatchdogTimer.TimerUse                       = GetWatchdogTimer.TimerUse;
  SetWatchdogTimer.TimerActions                   = GetWatchdogTimer.TimerActions;
  SetWatchdogTimer.PretimeoutInterval             = GetWatchdogTimer.PretimeoutInterval;
  SetWatchdogTimer.TimerUseExpirationFlagsClear   = GetWatchdogTimer.TimerUseExpirationFlagsClear;
  SetWatchdogTimer.InitialCountdownValue          = GetWatchdogTimer.InitialCountdownValue;
  SetWatchdogTimer.TimerUse.Bits.TimerRunning     = 1;
  SetWatchdogTimer.TimerUseExpirationFlagsClear  |= BIT1 | BIT2 | BIT3;

  Status = IpmiSetWatchdogTimer (&SetWatchdogTimer, &CompletionCode);

  return Status;
}

EFI_STATUS
ReportFrb2Status (
  VOID
  )
/*++

  Routine Description:
    This routine is built only when DEBUG_MODE is enabled.  It is used
    to report the status of FRB2 when the FRB2 driver is installed.

  Arguments:
    none

  Returns:
    EFI_SUCCESS: All info was retrieved and reported
    EFI_ERROR:   There was an error during info retrieval

--*/
{
  EFI_STATUS                          Status;
  IPMI_GET_WATCHDOG_TIMER_RESPONSE    GetWatchdogTimer;

  //
  // Get the Watchdog timer info to find out what kind of timer expiration occurred.
  //
  Status = IpmiGetWatchdogTimer (&GetWatchdogTimer);
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_INFO, "Failed to get Watchdog Timer info from BMC.\n"));
    return Status;
  }

  //
  // Check if timer is running, report status to DEBUG_MODE output.
  //
  if (GetWatchdogTimer.TimerUse.Bits.TimerRunning == 1) {
    DEBUG ((DEBUG_INFO, "FRB2 Timer is running.\n"));
  } else {
    DEBUG ((DEBUG_INFO, "FRB2 Timer is not running.\n"));
  }

  return EFI_SUCCESS;
}

EFI_STATUS
EFIAPI
FrbDxeEntryPoint (
  IN EFI_HANDLE         ImageHandle,
  IN EFI_SYSTEM_TABLE   *SystemTable
  )
/*++

  Routine Description:
    This is the standard EFI driver point. This function intitializes
    the private data required for creating FRB Driver.

  Arguments:
    ImageHandle     - Handle for the image of this driver
    SystemTable     - Pointer to the EFI System Table

  Returns:
    EFI_SUCCESS     - Protocol successfully started and installed
    EFI_UNSUPPORTED - Protocol can't be started

--*/
{
  EFI_EVENT   ReadyToBootEvent;
  EFI_STATUS  Status;

  CheckForAndReportErrors();
  ReportFrb2Status ();

  //
  // Register the event to Disable FRB2 before Boot.
  //
  Status = EfiCreateEventReadyToBootEx (
            TPL_NOTIFY,
            DisableFRB2Handler,
            NULL,
            &ReadyToBootEvent
            );

  return Status;
}
