/** @file
  Generic IPMI stack during PEI phase

  @copyright
  Copyright 2017 - 2021 Intel Corporation. <BR>
  SPDX-License-Identifier: BSD-2-Clause-Patent
**/

#include <IndustryStandard/Ipmi.h>
#include "PeiGenericIpmi.h"
#include <Library/ReportStatusCodeLib.h>
#include <Library/IpmiPlatformHookLib.h>

///////////////////////////////////////////////////////////////////////////////
// Function Implementations
//

/*****************************************************************************
 @brief
  Internal function

 @param[in] PeiServices          General purpose services available to every PEIM.

 @retval EFI_SUCCESS             Always return EFI_SUCCESS
**/
EFI_STATUS
EFIAPI
PeiInitializeIpmiKcsPhysicalLayer (
  IN CONST EFI_PEI_SERVICES             **PeiServices
  )
{
  EFI_STATUS                       Status;
  PEI_IPMI_BMC_INSTANCE_DATA       *mIpmiInstance;

  mIpmiInstance = NULL;

  //
  // Send Pre-Boot signal to BMC
  //
  if (PcdGetBool (PcdSignalPreBootToBmc)) {
    Status = SendPreBootSignaltoBmc (PeiServices);
    if (EFI_ERROR (Status)) {
      return Status;
    }
  }

  //
  // Enable OEM specific southbridge SIO KCS I/O address range 0xCA0 to 0xCAF at here
  // if the the I/O address range has not been enabled.
  //
  Status = PlatformIpmiIoRangeSet (PcdGet16 (PcdIpmiIoBaseAddress));
  DEBUG ((DEBUG_INFO, "IPMI Peim:PlatformIpmiIoRangeSet - %r!\n", Status));
  if (EFI_ERROR(Status)) {
    return Status;
  }

  mIpmiInstance = AllocateZeroPool (sizeof (PEI_IPMI_BMC_INSTANCE_DATA));
  if (mIpmiInstance == NULL) {
    DEBUG ((EFI_D_ERROR,"IPMI Peim:EFI_OUT_OF_RESOURCES of memory allocation\n"));
    return EFI_OUT_OF_RESOURCES;
  }

  //
  // Calibrate TSC Counter.  Stall for 10ms, then multiply the resulting number of
  // ticks in that period by 100 to get the number of ticks in a 1 second timeout
  //
  DEBUG ((DEBUG_INFO,"IPMI Peim:IPMI STACK Initialization\n"));
  mIpmiInstance->KcsTimeoutPeriod = (BMC_KCS_TIMEOUT_PEI *1000*1000) / KCS_DELAY_UNIT_PEI;
  DEBUG ((EFI_D_INFO,"IPMI Peim:KcsTimeoutPeriod = 0x%x\n", mIpmiInstance->KcsTimeoutPeriod));

  //
  // Initialize IPMI IO Base.
  //
  mIpmiInstance->IpmiIoBase                         = PcdGet16 (PcdIpmiIoBaseAddress);
  DEBUG ((EFI_D_INFO,"IPMI Peim:IpmiIoBase=0x%x\n",mIpmiInstance->IpmiIoBase));
  mIpmiInstance->Signature                          = SM_IPMI_BMC_SIGNATURE;
  mIpmiInstance->SlaveAddress                       = BMC_SLAVE_ADDRESS;
  mIpmiInstance->BmcStatus                          = BMC_NOTREADY;
  mIpmiInstance->IpmiTransportPpi.IpmiSubmitCommand = PeiIpmiSendCommand;
  mIpmiInstance->IpmiTransportPpi.GetBmcStatus      = PeiGetIpmiBmcStatus;

  mIpmiInstance->PeiIpmiBmcDataDesc.Flags         = EFI_PEI_PPI_DESCRIPTOR_PPI | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST;
  mIpmiInstance->PeiIpmiBmcDataDesc.Guid          = &gPeiIpmiTransportPpiGuid;
  mIpmiInstance->PeiIpmiBmcDataDesc.Ppi           = &mIpmiInstance->IpmiTransportPpi;

  //
  // Get the Device ID and check if the system is in Force Update mode.
  //
  Status = GetDeviceId (mIpmiInstance);
  if (EFI_ERROR (Status)) {
    DEBUG ((EFI_D_ERROR,"IPMI Peim:Get BMC Device Id Failed. Status=%r\n",Status));
  }

  //
  // Do not continue initialization if the BMC is in Force Update Mode.
  //
  if (mIpmiInstance->BmcStatus == BMC_UPDATE_IN_PROGRESS || mIpmiInstance->BmcStatus == BMC_HARDFAIL) {
    return EFI_UNSUPPORTED;
  }

  //
  // Just produce PPI
  //
  Status = PeiServicesInstallPpi (&mIpmiInstance->PeiIpmiBmcDataDesc);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  return EFI_SUCCESS;
}

/*****************************************************************************
 @bref
  PRE-BOOT signal will be sent in very early PEI phase, to enable necessary KCS access for host boot.

  @param[in] PeiServices          General purpose services available to every PEIM.

  @retval EFI_SUCCESS   Indicates that the signal is sent successfully.
**/
EFI_STATUS
SendPreBootSignaltoBmc (
  IN CONST EFI_PEI_SERVICES             **PeiServices
  )
{
  EFI_STATUS                        Status;
  EFI_PEI_CPU_IO_PPI                *CpuIoPpi;
  UINT32                            ProvisionPort = 0;
  UINT8                             PreBoot = 0;

  //
  // Locate CpuIo service
  //
  CpuIoPpi = (**PeiServices).CpuIo;
  ProvisionPort = PcdGet32 (PcdSioMailboxBaseAddress) + MBXDAT_B;
  PreBoot = 0x01;// PRE-BOOT

  Status = CpuIoPpi->Io.Write (
                          PeiServices,
                          CpuIoPpi,
                          EfiPeiCpuIoWidthUint8,
                          ProvisionPort,
                          1,
                          &PreBoot
                          );
  if (EFI_ERROR (Status)) {
    DEBUG ((EFI_D_ERROR, "SendPreBootSignaltoBmc () Write PRE-BOOT Status=%r\n", Status));
    return Status;
  }

  return EFI_SUCCESS;
}

/*****************************************************************************
 @bref
  The entry point of the Ipmi PEIM. Instals Ipmi PPI interface.

  @param  FileHandle  Handle of the file being invoked.
  @param  PeiServices Describes the list of possible PEI Services.

  @retval EFI_SUCCESS   Indicates that Ipmi initialization completed successfully.
**/
EFI_STATUS
PeimIpmiInterfaceInit (
  IN       EFI_PEI_FILE_HANDLE  FileHandle,
  IN CONST EFI_PEI_SERVICES     **PeiServices
  )
{
  EFI_STATUS  Status;

  //
  // Performing Ipmi KCS physical layer initialization
  //
  Status = PeiInitializeIpmiKcsPhysicalLayer (PeiServices);

  return Status;
} // PeimIpmiInterfaceInit()


EFI_STATUS
PeiIpmiSendCommand (
  IN      PEI_IPMI_TRANSPORT_PPI       *This,
  IN      UINT8                        NetFunction,
  IN      UINT8                        Lun,
  IN      UINT8                        Command,
  IN      UINT8                        *CommandData,
  IN      UINT32                       CommandDataSize,
  IN OUT  UINT8                        *ResponseData,
  IN OUT  UINT32                       *ResponseDataSize
  )
/*++

Routine Description:

  Send Ipmi Command in the right mode: HECI or KCS,  to the
  appropiate device, ME or BMC.

Arguments:

  This              - Pointer to IPMI protocol instance
  NetFunction       - Net Function of command to send
  Lun               - LUN of command to send
  Command           - IPMI command to send
  CommandData       - Pointer to command data buffer, if needed
  CommandDataSize   - Size of command data buffer
  ResponseData      - Pointer to response data buffer
  ResponseDataSize  - Pointer to response data buffer size

Returns:

  EFI_INVALID_PARAMETER - One of the input values is bad
  EFI_DEVICE_ERROR      - IPMI command failed
  EFI_BUFFER_TOO_SMALL  - Response buffer is too small
  EFI_UNSUPPORTED       - Command is not supported by BMC
  EFI_SUCCESS           - Command completed successfully

--*/
{
  //
  // This Will be unchanged ( BMC/KCS style )
  //
  return PeiIpmiSendCommandToBmc (
           This,
           NetFunction,
           Lun,
           Command,
           CommandData,
           (UINT8) CommandDataSize,
           ResponseData,
           (UINT8 *) ResponseDataSize,
           NULL
           );
} // IpmiSendCommand()

EFI_STATUS
PeiGetIpmiBmcStatus (
  IN      PEI_IPMI_TRANSPORT_PPI       *This,
  OUT BMC_STATUS                       *BmcStatus,
  OUT SM_COM_ADDRESS                   *ComAddress
  )
/*++

Routine Description:

  Updates the BMC status and returns the Com Address

Arguments:

  This        - Pointer to IPMI protocol instance
  BmcStatus   - BMC status
  ComAddress  - Com Address

Returns:

  EFI_SUCCESS - Success

--*/
{
  return PeiIpmiBmcStatus (
           This,
           BmcStatus,
           ComAddress,
           NULL
           );
}


EFI_STATUS
GetDeviceId (
  IN      PEI_IPMI_BMC_INSTANCE_DATA   *mIpmiInstance
  )
/*++

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:
  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:
  Status

--*/
{
  EFI_STATUS          Status;
  UINT32              DataSize;
  SM_CTRL_INFO        *pBmcInfo;
  UINTN               Retries;
  UINT8               TempData[MAX_TEMP_DATA];

  //
  // Set up a loop to retry for up to PcdIpmiBmcReadyDelayTimer seconds. Calculate retries not timeout
  // so that in case KCS is not enabled and IpmiSendCommand() returns
  // immediately we will not wait all the PcdIpmiBmcReadyDelayTimer seconds.
  //
  Retries = PcdGet8 (PcdIpmiBmcReadyDelayTimer);
  //
  // Get the device ID information for the BMC.
  //
  DataSize = sizeof (TempData);
  while (EFI_ERROR (Status = PeiIpmiSendCommand (
                               &mIpmiInstance->IpmiTransportPpi,
                               IPMI_NETFN_APP,
                               0,
                               IPMI_APP_GET_DEVICE_ID,
                               NULL,
                               0,
                               TempData,
                               &DataSize
                               ))) {
    DEBUG ((EFI_D_ERROR, "[IPMI] BMC does not respond (status: %r), %d retries left\n",
            Status, Retries));

    if (Retries-- == 0) {
      ReportStatusCode (EFI_ERROR_CODE | EFI_ERROR_MAJOR, EFI_COMPUTING_UNIT_FIRMWARE_PROCESSOR | EFI_CU_FP_EC_COMM_ERROR);
      mIpmiInstance->BmcStatus = BMC_HARDFAIL;
      return Status;
    }
    //
    // Handle the case that BMC FW still not enable KCS channel after AC cycle. just stall 1 second
    //
    MicroSecondDelay (1*1000*1000);
  }
  pBmcInfo = (SM_CTRL_INFO*) &TempData[0];
  DEBUG ((DEBUG_INFO, "[IPMI PEI] BMC Device ID: 0x%02X, firmware version: %d.%02X UpdateMode:%x\n",
          pBmcInfo->DeviceId, pBmcInfo->MajorFirmwareRev, pBmcInfo->MinorFirmwareRev, pBmcInfo->UpdateMode));
  //
  // In OpenBMC, UpdateMode: the bit 7 of byte 4 in get device id command is used for the BMC status:
  // 0 means BMC is ready, 1 means BMC is not ready.
  // At the very beginning of BMC power on, the status is 1 means BMC is in booting process and not ready. It is not the flag for force update mode.
  //
  if (pBmcInfo->UpdateMode == BMC_READY) {
    mIpmiInstance->BmcStatus = BMC_OK;
    return EFI_SUCCESS;
  } else {
    //
    // Updatemode = 1 mean BMC is not ready, continue waiting.
    //
    while (Retries-- != 0) {
      MicroSecondDelay(1*1000*1000); //delay 1 seconds
      DEBUG ((DEBUG_INFO, "[IPMI PEI] UpdateMode Retries:%x \n",Retries));
      Status = PeiIpmiSendCommand (
                 &mIpmiInstance->IpmiTransportPpi,
                 IPMI_NETFN_APP,
                 0,
                 IPMI_APP_GET_DEVICE_ID,
                 NULL,
                 0,
                 TempData,
                 &DataSize
                 );
      if (!EFI_ERROR (Status)) {
        pBmcInfo = (SM_CTRL_INFO*) &TempData[0];
        DEBUG ((DEBUG_INFO, "[IPMI PEI] UpdateMode Retries:%x   pBmcInfo->UpdateMode:%x\n", Retries, pBmcInfo->UpdateMode));
        if (pBmcInfo->UpdateMode == BMC_READY) {
          mIpmiInstance->BmcStatus = BMC_OK;
          return EFI_SUCCESS;
        }
      }
    }
  }

  mIpmiInstance->BmcStatus = BMC_HARDFAIL;
  return Status;
} // GetDeviceId()
