/** @file
    IPMI stack initialization.

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

**/

#include <PiDxe.h>
#include <Library/BaseLib.h>
#include <Library/DebugLib.h>
#include <Library/TimerLib.h>
#include <Library/IpmiCommandLib.h>

#define BMC_TIMEOUT          30  // [s] How long shall BIOS wait for BMC
#define BMC_KCS_TIMEOUT      5   // [s] Single KSC request timeout

EFI_STATUS
GetSelfTest (
  VOID
  )
/*++

Routine Description:

  Execute the Get Self Test results command to determine whether or not the BMC self tests
  have passed

Arguments:

  mIpmiInstance   - Data structure describing BMC variables and used for sending commands
  StatusCodeValue - An array used to accumulate error codes for later reporting.
  ErrorCount      - Counter used to keep track of error codes in StatusCodeValue

Returns:

  EFI_SUCCESS       - BMC Self test results are retrieved and saved into BmcStatus
  EFI_DEVICE_ERROR  - BMC failed to return self test results.

--*/
{
  EFI_STATUS                       Status;
  IPMI_SELF_TEST_RESULT_RESPONSE   TestResult;

  //
  // Get the SELF TEST Results.
  //
  Status = IpmiGetSelfTestResult (&TestResult);
  if (EFI_ERROR(Status)) {
    DEBUG((DEBUG_ERROR, "\n[IPMI] BMC does not respond (status: %r)!\n\n", Status));
    return Status;
  }

  DEBUG((DEBUG_INFO, "[IPMI] BMC self-test result: %02X-%02X\n", TestResult.Result, TestResult.Param));

  return EFI_SUCCESS;
}

EFI_STATUS
GetDeviceId (
  OUT BOOLEAN *UpdateMode
  )
/*++

Routine Description:
  Execute the Get Device ID command to determine whether or not the BMC is in Force Update
  Mode.  If it is, then report it to the error manager.

Arguments:

Returns:
  Status

--*/
{
  EFI_STATUS                   Status;
  IPMI_GET_DEVICE_ID_RESPONSE  BmcInfo;
  UINT32                       Retries;

  //
  // Set up a loop to retry for up to 30 seconds. Calculate retries not timeout
  // so that in case KCS is not enabled and IpmiSendCommand() returns
  // immediately we will not wait all the 30 seconds.
  //
  Retries = BMC_TIMEOUT / BMC_KCS_TIMEOUT + 1;
  //
  // Get the device ID information for the BMC.
  //
  do {
    Status = IpmiGetDeviceId (&BmcInfo);
    if (!EFI_ERROR(Status)) {
      break;
    }
    DEBUG ((DEBUG_ERROR, "[IPMI] BMC does not respond (status: %r), %d retries left\n", Status, Retries));
    MicroSecondDelay(50 * 1000);
    if (Retries-- == 0) {
      return Status;
    }
  } while (TRUE);

  DEBUG((
    DEBUG_INFO,
    "[IPMI] BMC Device ID: 0x%02X, firmware version: %d.%02X\n",
    BmcInfo.DeviceId,
    BmcInfo.FirmwareRev1.Bits.MajorFirmwareRev,
    BmcInfo.MinorFirmwareRev
    ));
  *UpdateMode = (BOOLEAN)BmcInfo.FirmwareRev1.Bits.UpdateMode;
  return Status;
}

/**
  The entry point of the Ipmi DXE.

@param[in] ImageHandle - Handle of this driver image
@param[in] SystemTable - Table containing standard EFI services

@retval EFI_SUCCESS - Always success is returned even if KCS does not function

 **/
EFI_STATUS
EFIAPI
IpmiInterfaceInit (
  IN EFI_HANDLE             ImageHandle,
  IN EFI_SYSTEM_TABLE       *SystemTable
  )
{
  BOOLEAN      UpdateMode;
  EFI_STATUS   Status;

  DEBUG((DEBUG_ERROR,"IPMI Dxe:Get BMC Device Id\n"));

  //
  // Get the Device ID and check if the system is in Force Update mode.
  //
  Status = GetDeviceId (&UpdateMode);
  //
  // Do not continue initialization if the BMC is in Force Update Mode.
  //
  if (!EFI_ERROR(Status) && !UpdateMode) {
    //
    // Get the SELF TEST Results.
    //
    GetSelfTest ();
  }

  return EFI_SUCCESS;
}


