/** @file
  Update and publish processors' BIST information.

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

**/

#include "CpuMpPei.h"

EFI_SEC_PLATFORM_INFORMATION2_PPI  mSecPlatformInformation2Ppi = {
  SecPlatformInformation2
};

EFI_PEI_PPI_DESCRIPTOR  mPeiSecPlatformInformation2Ppi = {
  (EFI_PEI_PPI_DESCRIPTOR_PPI | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST),
  &gEfiSecPlatformInformation2PpiGuid,
  &mSecPlatformInformation2Ppi
};

/**
  Implementation of the PlatformInformation2 service in EFI_SEC_PLATFORM_INFORMATION2_PPI.

  @param  PeiServices                The pointer to the PEI Services Table.
  @param  StructureSize              The pointer to the variable describing size of the input buffer.
  @param  PlatformInformationRecord2 The pointer to the EFI_SEC_PLATFORM_INFORMATION_RECORD2.

  @retval EFI_SUCCESS                The data was successfully returned.
  @retval EFI_BUFFER_TOO_SMALL       The buffer was too small. The current buffer size needed to
                                     hold the record is returned in StructureSize.

**/
EFI_STATUS
EFIAPI
SecPlatformInformation2 (
  IN CONST EFI_PEI_SERVICES                 **PeiServices,
  IN OUT UINT64                             *StructureSize,
  OUT EFI_SEC_PLATFORM_INFORMATION_RECORD2  *PlatformInformationRecord2
  )
{
  EFI_HOB_GUID_TYPE  *GuidHob;
  VOID               *DataInHob;
  UINTN              DataSize;

  GuidHob = GetFirstGuidHob (&gEfiSecPlatformInformation2PpiGuid);
  if (GuidHob == NULL) {
    *StructureSize = 0;
    return EFI_SUCCESS;
  }

  DataInHob = GET_GUID_HOB_DATA (GuidHob);
  DataSize  = GET_GUID_HOB_DATA_SIZE (GuidHob);

  //
  // return the information from BistHob
  //
  if ((*StructureSize) < (UINT64)DataSize) {
    *StructureSize = (UINT64)DataSize;
    return EFI_BUFFER_TOO_SMALL;
  }

  *StructureSize = (UINT64)DataSize;
  CopyMem (PlatformInformationRecord2, DataInHob, DataSize);
  return EFI_SUCCESS;
}

/**
  Worker function to get CPUs' BIST by calling SecPlatformInformationPpi
  or SecPlatformInformation2Ppi.

  @param  PeiServices         Pointer to PEI Services Table
  @param  Guid                PPI Guid
  @param  PpiDescriptor       Return a pointer to instance of the
                              EFI_PEI_PPI_DESCRIPTOR
  @param  BistInformationData Pointer to BIST information data
  @param  BistInformationSize Return the size in bytes of BIST information

  @retval EFI_SUCCESS         Retrieve of the BIST data successfully
  @retval EFI_NOT_FOUND       No sec platform information(2) ppi export
  @retval EFI_DEVICE_ERROR    Failed to get CPU Information

**/
EFI_STATUS
GetBistInfoFromPpi (
  IN CONST EFI_PEI_SERVICES   **PeiServices,
  IN CONST EFI_GUID           *Guid,
  OUT EFI_PEI_PPI_DESCRIPTOR  **PpiDescriptor,
  OUT VOID                    **BistInformationData,
  OUT UINT64                  *BistInformationSize OPTIONAL
  )
{
  EFI_STATUS                            Status;
  EFI_SEC_PLATFORM_INFORMATION2_PPI     *SecPlatformInformation2Ppi;
  EFI_SEC_PLATFORM_INFORMATION_RECORD2  *SecPlatformInformation2;
  UINT64                                InformationSize;

  Status = PeiServicesLocatePpi (
             Guid,                                // GUID
             0,                                   // INSTANCE
             PpiDescriptor,                       // EFI_PEI_PPI_DESCRIPTOR
             (VOID **)&SecPlatformInformation2Ppi // PPI
             );
  if (Status == EFI_NOT_FOUND) {
    return EFI_NOT_FOUND;
  }

  if (Status == EFI_SUCCESS) {
    //
    // Get the size of the sec platform information2(BSP/APs' BIST data)
    //
    InformationSize         = 0;
    SecPlatformInformation2 = NULL;
    Status                  = SecPlatformInformation2Ppi->PlatformInformation2 (
                                                            PeiServices,
                                                            &InformationSize,
                                                            SecPlatformInformation2
                                                            );
    if (Status == EFI_BUFFER_TOO_SMALL) {
      Status = PeiServicesAllocatePool (
                 (UINTN)InformationSize,
                 (VOID **)&SecPlatformInformation2
                 );
      if (Status == EFI_SUCCESS) {
        //
        // Retrieve BIST data
        //
        Status = SecPlatformInformation2Ppi->PlatformInformation2 (
                                               PeiServices,
                                               &InformationSize,
                                               SecPlatformInformation2
                                               );
        if (Status == EFI_SUCCESS) {
          *BistInformationData = SecPlatformInformation2;
          if (BistInformationSize != NULL) {
            *BistInformationSize = InformationSize;
          }

          return EFI_SUCCESS;
        }
      }
    }
  }

  return EFI_DEVICE_ERROR;
}

/**
  Collects BIST data from PPI.

  This function collects BIST data from Sec Platform Information2 PPI
  or SEC Platform Information PPI.

  @param PeiServices         Pointer to PEI Services Table

**/
VOID
CollectBistDataFromPpi (
  IN CONST EFI_PEI_SERVICES  **PeiServices
  )
{
  EFI_STATUS                            Status;
  EFI_PEI_PPI_DESCRIPTOR                *SecInformationDescriptor;
  EFI_SEC_PLATFORM_INFORMATION_RECORD2  *SecPlatformInformation2;
  EFI_SEC_PLATFORM_INFORMATION_RECORD   *SecPlatformInformation;
  UINTN                                 NumberOfData;
  EFI_SEC_PLATFORM_INFORMATION_CPU      *CpuInstance;
  EFI_SEC_PLATFORM_INFORMATION_CPU      BspCpuInstance;
  UINTN                                 ProcessorNumber;
  UINTN                                 CpuIndex;
  EFI_PROCESSOR_INFORMATION             ProcessorInfo;
  EFI_HEALTH_FLAGS                      BistData;
  UINTN                                 NumberOfProcessors;
  UINTN                                 NumberOfEnabledProcessors;
  UINTN                                 BistInformationSize;
  EFI_SEC_PLATFORM_INFORMATION_RECORD2  *PlatformInformationRecord2;
  EFI_SEC_PLATFORM_INFORMATION_CPU      *CpuInstanceInHob;

  Status = MpInitLibGetNumberOfProcessors (&NumberOfProcessors, &NumberOfEnabledProcessors);
  ASSERT_EFI_ERROR (Status);

  if (EFI_ERROR (Status)) {
    NumberOfProcessors        = 1;
    NumberOfEnabledProcessors = 1;
  }

  BistInformationSize = sizeof (EFI_SEC_PLATFORM_INFORMATION_RECORD2) +
                        sizeof (EFI_SEC_PLATFORM_INFORMATION_CPU) * NumberOfProcessors;
  Status = PeiServicesAllocatePool (
             (UINTN)BistInformationSize,
             (VOID **)&PlatformInformationRecord2
             );
  ASSERT_EFI_ERROR (Status);
  PlatformInformationRecord2->NumberOfCpus = (UINT32)NumberOfProcessors;

  SecPlatformInformation2 = NULL;
  SecPlatformInformation  = NULL;
  NumberOfData            = 0;
  CpuInstance             = NULL;
  //
  // Get BIST information from Sec Platform Information2 Ppi firstly
  //
  Status = GetBistInfoFromPpi (
             PeiServices,
             &gEfiSecPlatformInformation2PpiGuid,
             &SecInformationDescriptor,
             (VOID *)&SecPlatformInformation2,
             NULL
             );
  if (Status == EFI_SUCCESS) {
    //
    // Sec Platform Information2 PPI includes BSP/APs' BIST information
    //
    NumberOfData = SecPlatformInformation2->NumberOfCpus;
    CpuInstance  = SecPlatformInformation2->CpuInstance;
  } else {
    //
    // Otherwise, get BIST information from Sec Platform Information Ppi
    //
    Status = GetBistInfoFromPpi (
               PeiServices,
               &gEfiSecPlatformInformationPpiGuid,
               &SecInformationDescriptor,
               (VOID *)&SecPlatformInformation,
               NULL
               );
    if (Status == EFI_SUCCESS) {
      NumberOfData = 1;
      //
      // SEC Platform Information only includes BSP's BIST information
      // and does not have BSP's APIC ID
      //
      BspCpuInstance.CpuLocation                       = GetInitialApicId ();
      BspCpuInstance.InfoRecord.IA32HealthFlags.Uint32 = SecPlatformInformation->IA32HealthFlags.Uint32;
      CpuInstance                                      = &BspCpuInstance;
    } else {
      DEBUG ((DEBUG_INFO, "Does not find any stored CPU BIST information from PPI!\n"));
    }
  }

  for (ProcessorNumber = 0; ProcessorNumber < NumberOfProcessors; ProcessorNumber++) {
    MpInitLibGetProcessorInfo (ProcessorNumber, &ProcessorInfo, &BistData);
    for (CpuIndex = 0; CpuIndex < NumberOfData; CpuIndex++) {
      ASSERT (CpuInstance != NULL);
      if (ProcessorInfo.ProcessorId == CpuInstance[CpuIndex].CpuLocation) {
        //
        // Update processor's BIST data if it is already stored before
        //
        BistData = CpuInstance[CpuIndex].InfoRecord.IA32HealthFlags;
      }
    }

    if (BistData.Uint32 != 0) {
      //
      // Report Status Code that self test is failed
      //
      REPORT_STATUS_CODE (
        EFI_ERROR_CODE | EFI_ERROR_MAJOR,
        (EFI_COMPUTING_UNIT_HOST_PROCESSOR | EFI_CU_HP_EC_SELF_TEST)
        );
    }

    DEBUG ((
      DEBUG_INFO,
      "  APICID - 0x%08x, BIST - 0x%08x\n",
      (UINT32)ProcessorInfo.ProcessorId,
      BistData
      ));
    CpuInstanceInHob                                             = PlatformInformationRecord2->CpuInstance;
    CpuInstanceInHob[ProcessorNumber].CpuLocation                = (UINT32)ProcessorInfo.ProcessorId;
    CpuInstanceInHob[ProcessorNumber].InfoRecord.IA32HealthFlags = BistData;
  }

  //
  // Build SecPlatformInformation2 PPI GUIDed HOB that also could be consumed
  // by CPU MP driver to get CPU BIST data
  //
  BuildGuidDataHob (
    &gEfiSecPlatformInformation2PpiGuid,
    PlatformInformationRecord2,
    (UINTN)BistInformationSize
    );

  if (SecPlatformInformation2 != NULL) {
    if (NumberOfData < NumberOfProcessors) {
      //
      // Reinstall SecPlatformInformation2 PPI to include new BIST information
      //
      Status = PeiServicesReInstallPpi (
                 SecInformationDescriptor,
                 &mPeiSecPlatformInformation2Ppi
                 );
      ASSERT_EFI_ERROR (Status);
    }
  } else {
    //
    // Install SecPlatformInformation2 PPI
    //
    Status = PeiServicesInstallPpi (&mPeiSecPlatformInformation2Ppi);
    ASSERT_EFI_ERROR (Status);
  }
}
