/** @file
  Support FSP Wrapper MultiPhase process.

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

**/

#include <Library/BaseLib.h>
#include <Library/DebugLib.h>
#include <Library/PcdLib.h>
#include <Library/FspWrapperApiLib.h>
#include <Library/FspWrapperPlatformLib.h>
#include <FspEas.h>
#include <FspGlobalData.h>
#include <Ppi/ReadOnlyVariable2.h>
#include <Ppi/Variable.h>
#include <Library/PeiServicesLib.h>
#include <Library/FspWrapperPlatformMultiPhaseLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/HobLib.h>

/**
  Execute 32-bit FSP API entry code.

  @param[in] Function     The 32bit code entry to be executed.
  @param[in] Param1       The first parameter to pass to 32bit code.
  @param[in] Param2       The second parameter to pass to 32bit code.

  @return EFI_STATUS.
**/
EFI_STATUS
Execute32BitCode (
  IN UINT64  Function,
  IN UINT64  Param1,
  IN UINT64  Param2
  );

/**
  Execute 64-bit FSP API entry code.

  @param[in] Function     The 64bit code entry to be executed.
  @param[in] Param1       The first parameter to pass to 64bit code.
  @param[in] Param2       The second parameter to pass to 64bit code.

  @return EFI_STATUS.
**/
EFI_STATUS
Execute64BitCode (
  IN UINT64  Function,
  IN UINT64  Param1,
  IN UINT64  Param2
  );

/**
  Call FspsMultiPhase API.

  @param[in] FspsMultiPhaseParams - Parameters for MultiPhase API.
  @param[in] FspHobListPtr        - Pointer to FSP HobList (valid after FSP-M completed)
  @param[in] ComponentIndex       - FSP Component which executing MultiPhase initialization.

  @return EFI_UNSUPPORTED  - the requested FspsMultiPhase API is not supported.
  @return EFI_DEVICE_ERROR - the FSP header was not found.
  @return EFI status returned by FspsMultiPhase API.
**/
EFI_STATUS
EFIAPI
CallFspMultiPhaseEntry (
  IN VOID      *FspMultiPhaseParams,
  IN OUT VOID  **FspHobListPtr,
  IN UINT8     ComponentIndex
  )
{
  FSP_INFO_HEADER  *FspHeader;
  //
  // FSP_MULTI_PHASE_INIT and FSP_MULTI_PHASE_SI_INIT API functions having same prototype.
  //
  UINTN                   FspMultiPhaseApiEntry;
  UINTN                   FspMultiPhaseApiOffset;
  EFI_STATUS              Status;
  BOOLEAN                 InterruptState;
  BOOLEAN                 IsVariableServiceRequest;
  FSP_MULTI_PHASE_PARAMS  *FspMultiPhaseParamsPtr;

  FspMultiPhaseApiOffset = 0;

  FspMultiPhaseParamsPtr   = (FSP_MULTI_PHASE_PARAMS *)FspMultiPhaseParams;
  IsVariableServiceRequest = FALSE;
  if ((FspMultiPhaseParamsPtr->MultiPhaseAction == EnumMultiPhaseGetVariableRequestInfo) ||
      (FspMultiPhaseParamsPtr->MultiPhaseAction == EnumMultiPhaseCompleteVariableRequest))
  {
    IsVariableServiceRequest = TRUE;
  }

  if (ComponentIndex == FspMultiPhaseMemInitApiIndex) {
    FspHeader = (FSP_INFO_HEADER *)FspFindFspHeader (PcdGet32 (PcdFspmBaseAddress));
    if (FspHeader == NULL) {
      return EFI_DEVICE_ERROR;
    } else if (FspHeader->SpecVersion < 0x24) {
      return EFI_UNSUPPORTED;
    }

    FspMultiPhaseApiOffset = FspHeader->FspMultiPhaseMemInitEntryOffset;
  } else if (ComponentIndex == FspMultiPhaseSiInitApiIndex) {
    FspHeader = (FSP_INFO_HEADER *)FspFindFspHeader (PcdGet32 (PcdFspsBaseAddress));
    if (FspHeader == NULL) {
      return EFI_DEVICE_ERROR;
    } else if (FspHeader->SpecVersion < 0x22) {
      return EFI_UNSUPPORTED;
    } else if ((FspHeader->SpecVersion < 0x24) && (IsVariableServiceRequest == TRUE)) {
      return EFI_UNSUPPORTED;
    }

    FspMultiPhaseApiOffset = FspHeader->FspMultiPhaseSiInitEntryOffset;
  }

  if (FspMultiPhaseApiOffset == 0) {
    return EFI_UNSUPPORTED;
  }

  FspMultiPhaseApiEntry = FspHeader->ImageBase + FspMultiPhaseApiOffset;
  InterruptState        = SaveAndDisableInterrupts ();
  if ((FspHeader->ImageAttribute & BIT2) == 0) {
    // BIT2: IMAGE_ATTRIBUTE_64BIT_MODE_SUPPORT
    Status = Execute32BitCode ((UINTN)FspMultiPhaseApiEntry, (UINTN)FspMultiPhaseParams, (UINTN)NULL);
  } else {
    Status = Execute64BitCode ((UINTN)FspMultiPhaseApiEntry, (UINTN)FspMultiPhaseParams, (UINTN)NULL);
  }

  SetInterruptState (InterruptState);

  DEBUG ((DEBUG_ERROR, "CallFspMultiPhaseEntry return Status %r \n", Status));

  return Status;
}

/**
  FSP Wrapper Variable Request Handler

  @param[in, out] FspHobListPtr        - Pointer to FSP HobList (valid after FSP-M completed)
  @param[in]      ComponentIndex       - FSP Component which executing MultiPhase initialization.

  @retval EFI_UNSUPPORTED   FSP Wrapper cannot support the specific variable request,
                            or FSP does not support VariableService
  @retval EFI_STATUS        Return FSP returned status

**/
EFI_STATUS
EFIAPI
FspWrapperVariableRequestHandler (
  IN OUT VOID  **FspHobListPtr,
  IN UINT8     ComponentIndex
  )
{
  EFI_STATUS                                        Status;
  FSP_MULTI_PHASE_PARAMS                            FspMultiPhaseParams;
  FSP_MULTI_PHASE_VARIABLE_REQUEST_INFO_PARAMS      *FspVariableRequestParams;
  EFI_PEI_READ_ONLY_VARIABLE2_PPI                   *ReadOnlyVariablePpi;
  EDKII_PEI_VARIABLE_PPI                            *VariablePpi;
  BOOLEAN                                           WriteVariableSupport;
  FSP_MULTI_PHASE_COMPLETE_VARIABLE_REQUEST_PARAMS  CompleteVariableRequestParams;
  VOID                                              *GuidHob;
  VOID                                              *HobData;

  WriteVariableSupport = TRUE;
  Status               = PeiServicesLocatePpi (
                           &gEdkiiPeiVariablePpiGuid,
                           0,
                           NULL,
                           (VOID **)&VariablePpi
                           );
  if (EFI_ERROR (Status)) {
    WriteVariableSupport = FALSE;
    Status               = PeiServicesLocatePpi (
                             &gEfiPeiReadOnlyVariable2PpiGuid,
                             0,
                             NULL,
                             (VOID **)&ReadOnlyVariablePpi
                             );
    ASSERT_EFI_ERROR (Status);
    if (EFI_ERROR (Status)) {
      return EFI_UNSUPPORTED;
    }
  }

  Status = FSP_STATUS_VARIABLE_REQUEST;
  while (Status == FSP_STATUS_VARIABLE_REQUEST) {
    //
    // Get the variable request information from FSP.
    //
    FspMultiPhaseParams.MultiPhaseAction = EnumMultiPhaseGetVariableRequestInfo;
    FspMultiPhaseParams.PhaseIndex       = 0;
    Status                               = CallFspMultiPhaseEntry (&FspMultiPhaseParams, FspHobListPtr, ComponentIndex);
    ASSERT_EFI_ERROR (Status);
    //
    // FSP should output this pointer for variable request information.
    //
    FspVariableRequestParams = (FSP_MULTI_PHASE_VARIABLE_REQUEST_INFO_PARAMS *)FspMultiPhaseParams.MultiPhaseParamPtr;
    switch (FspVariableRequestParams->VariableRequest) {
      case EnumFspVariableRequestGetVariable:
        if (WriteVariableSupport) {
          Status = VariablePpi->GetVariable (
                                  VariablePpi,
                                  FspVariableRequestParams->VariableName,
                                  FspVariableRequestParams->VariableGuid,
                                  FspVariableRequestParams->Attributes,
                                  (UINTN *)FspVariableRequestParams->DataSize,
                                  FspVariableRequestParams->Data
                                  );
        } else {
          Status = ReadOnlyVariablePpi->GetVariable (
                                          ReadOnlyVariablePpi,
                                          FspVariableRequestParams->VariableName,
                                          FspVariableRequestParams->VariableGuid,
                                          FspVariableRequestParams->Attributes,
                                          (UINTN *)FspVariableRequestParams->DataSize,
                                          FspVariableRequestParams->Data
                                          );
        }

        CompleteVariableRequestParams.VariableRequestStatus = Status;
        FspMultiPhaseParams.MultiPhaseParamPtr              = (VOID *)&CompleteVariableRequestParams;
        FspMultiPhaseParams.MultiPhaseAction                = EnumMultiPhaseCompleteVariableRequest;
        Status                                              = CallFspMultiPhaseEntry (&FspMultiPhaseParams, FspHobListPtr, ComponentIndex);
        break;

      case EnumFspVariableRequestSetVariable:
        if (WriteVariableSupport) {
          Status = VariablePpi->SetVariable (
                                  VariablePpi,
                                  FspVariableRequestParams->VariableName,
                                  FspVariableRequestParams->VariableGuid,
                                  *FspVariableRequestParams->Attributes,
                                  (UINTN)*FspVariableRequestParams->DataSize,
                                  FspVariableRequestParams->Data
                                  );
        } else {
          Status = EFI_UNSUPPORTED;
        }

        CompleteVariableRequestParams.VariableRequestStatus = Status;
        FspMultiPhaseParams.MultiPhaseParamPtr              = (VOID *)&CompleteVariableRequestParams;
        FspMultiPhaseParams.MultiPhaseAction                = EnumMultiPhaseCompleteVariableRequest;
        Status                                              = CallFspMultiPhaseEntry (&FspMultiPhaseParams, FspHobListPtr, ComponentIndex);
        break;

      case EnumFspVariableRequestGetNextVariableName:
        if (WriteVariableSupport) {
          Status = VariablePpi->GetNextVariableName (
                                  VariablePpi,
                                  (UINTN *)FspVariableRequestParams->VariableNameSize,
                                  FspVariableRequestParams->VariableName,
                                  FspVariableRequestParams->VariableGuid
                                  );
        } else {
          Status = ReadOnlyVariablePpi->NextVariableName (
                                          ReadOnlyVariablePpi,
                                          (UINTN *)FspVariableRequestParams->VariableNameSize,
                                          FspVariableRequestParams->VariableName,
                                          FspVariableRequestParams->VariableGuid
                                          );
        }

        CompleteVariableRequestParams.VariableRequestStatus = Status;
        FspMultiPhaseParams.MultiPhaseParamPtr              = (VOID *)&CompleteVariableRequestParams;
        FspMultiPhaseParams.MultiPhaseAction                = EnumMultiPhaseCompleteVariableRequest;
        Status                                              = CallFspMultiPhaseEntry (&FspMultiPhaseParams, FspHobListPtr, ComponentIndex);
        break;

      case EnumFspVariableRequestQueryVariableInfo:
        if (WriteVariableSupport) {
          Status = VariablePpi->QueryVariableInfo (
                                  VariablePpi,
                                  *FspVariableRequestParams->Attributes,
                                  FspVariableRequestParams->MaximumVariableStorageSize,
                                  FspVariableRequestParams->RemainingVariableStorageSize,
                                  FspVariableRequestParams->MaximumVariableSize
                                  );
        } else {
          Status = EFI_UNSUPPORTED;
        }

        CompleteVariableRequestParams.VariableRequestStatus = Status;
        FspMultiPhaseParams.MultiPhaseParamPtr              = (VOID *)&CompleteVariableRequestParams;
        FspMultiPhaseParams.MultiPhaseAction                = EnumMultiPhaseCompleteVariableRequest;
        Status                                              = CallFspMultiPhaseEntry (&FspMultiPhaseParams, FspHobListPtr, ComponentIndex);
        break;

      default:
        DEBUG ((DEBUG_ERROR, "Unknown VariableRequest type!\n"));
        Status = EFI_UNSUPPORTED;
        break;
    }
  }

  //
  // Refresh FspHobList pointer stored in HOB.
  //
  GuidHob = GetFirstGuidHob (&gFspHobGuid);
  ASSERT (GuidHob != NULL);
  if (GuidHob != NULL) {
    HobData = GET_GUID_HOB_DATA (GuidHob);
    CopyMem (HobData, FspHobListPtr, sizeof (*FspHobListPtr));
  }

  //
  // Reset the system if FSP API returned FSP_STATUS_RESET_REQUIRED status
  //
  if ((Status >= FSP_STATUS_RESET_REQUIRED_COLD) && (Status <= FSP_STATUS_RESET_REQUIRED_8)) {
    DEBUG ((DEBUG_INFO, "FspMultiPhaseApi-0x%x requested reset %r\n", ComponentIndex, Status));
    CallFspWrapperResetSystem ((UINTN)Status);
  }

  return Status;
}

/**
  FSP Wrapper MultiPhase Handler

  @param[in, out] FspHobListPtr        - Pointer to FSP HobList (valid after FSP-M completed)
  @param[in]      ComponentIndex       - FSP Component which executing MultiPhase initialization.

  @retval EFI_UNSUPPORTED   Specific MultiPhase action was not supported.
  @retval EFI_SUCCESS       MultiPhase action were completed successfully.

**/
EFI_STATUS
EFIAPI
FspWrapperMultiPhaseHandler (
  IN OUT VOID  **FspHobListPtr,
  IN UINT8     ComponentIndex
  )
{
  EFI_STATUS                                   Status;
  FSP_MULTI_PHASE_PARAMS                       FspMultiPhaseParams;
  FSP_MULTI_PHASE_GET_NUMBER_OF_PHASES_PARAMS  FspMultiPhaseGetNumber;
  UINT32                                       Index;
  UINT32                                       NumOfPhases;
  VOID                                         *GuidHob;
  VOID                                         *HobData;

  //
  // Query FSP for the number of phases supported.
  //
  FspMultiPhaseParams.MultiPhaseAction   = EnumMultiPhaseGetNumberOfPhases;
  FspMultiPhaseParams.PhaseIndex         = 0;
  FspMultiPhaseParams.MultiPhaseParamPtr = (VOID *)&FspMultiPhaseGetNumber;
  Status                                 = CallFspMultiPhaseEntry (&FspMultiPhaseParams, FspHobListPtr, ComponentIndex);
  if (Status == EFI_UNSUPPORTED) {
    //
    // MultiPhase API was not supported
    //
    return Status;
  } else {
    ASSERT_EFI_ERROR (Status);
  }

  NumOfPhases = FspMultiPhaseGetNumber.NumberOfPhases;

  for (Index = 1; Index <= NumOfPhases; Index++) {
    DEBUG ((DEBUG_ERROR, "MultiPhase Index/NumOfPhases = %d of %d\n", Index, NumOfPhases));
    //
    // Platform actions can be added in below function for each component and phase before returning control back to FSP.
    //
    FspWrapperPlatformMultiPhaseHandler (FspHobListPtr, ComponentIndex, Index);

    FspMultiPhaseParams.MultiPhaseAction   = EnumMultiPhaseExecutePhase;
    FspMultiPhaseParams.PhaseIndex         = Index;
    FspMultiPhaseParams.MultiPhaseParamPtr = NULL;
    Status                                 = CallFspMultiPhaseEntry (&FspMultiPhaseParams, FspHobListPtr, ComponentIndex);

    //
    // Refresh FspHobList pointer stored in HOB.
    //
    GuidHob = GetFirstGuidHob (&gFspHobGuid);
    ASSERT (GuidHob != NULL);
    if (GuidHob != NULL) {
      HobData = GET_GUID_HOB_DATA (GuidHob);
      CopyMem (HobData, FspHobListPtr, sizeof (*FspHobListPtr));
    }

    if (Status == FSP_STATUS_VARIABLE_REQUEST) {
      //
      // call to Variable request handler
      //
      FspWrapperVariableRequestHandler (FspHobListPtr, ComponentIndex);
    }

    //
    // Reset the system if FSP API returned FSP_STATUS_RESET_REQUIRED status
    //
    if ((Status >= FSP_STATUS_RESET_REQUIRED_COLD) && (Status <= FSP_STATUS_RESET_REQUIRED_8)) {
      DEBUG ((DEBUG_INFO, "FspMultiPhaseApi-0x%x requested reset %r\n", ComponentIndex, Status));
      CallFspWrapperResetSystem ((UINTN)Status);
    }

    ASSERT_EFI_ERROR (Status);
  }

  return EFI_SUCCESS;
}
