/** @file
  CPU Register Table Library functions.

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

**/

#include <PiDxe.h>

#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiLib.h>

#include "RegisterCpuFeatures.h"

CPU_FEATURES_DATA  mCpuFeaturesData = { 0 };

/**
  Worker function to get CPU_FEATURES_DATA pointer.

  @return Pointer to CPU_FEATURES_DATA.
**/
CPU_FEATURES_DATA *
GetCpuFeaturesData (
  VOID
  )
{
  return &mCpuFeaturesData;
}

/**
  Worker function to get EFI_MP_SERVICES_PROTOCOL pointer.

  @return MP_SERVICES variable.
**/
MP_SERVICES
GetMpService (
  VOID
  )
{
  EFI_STATUS   Status;
  MP_SERVICES  MpService;

  //
  // Get MP Services Protocol
  //
  Status = gBS->LocateProtocol (
                  &gEfiMpServiceProtocolGuid,
                  NULL,
                  (VOID **)&MpService.Protocol
                  );
  ASSERT_EFI_ERROR (Status);

  return MpService;
}

/**
  Worker function to return processor index.

  @param  CpuFeaturesData    Cpu Feature Data structure.

  @return  The processor index.
**/
UINTN
GetProcessorIndex (
  IN CPU_FEATURES_DATA  *CpuFeaturesData
  )
{
  EFI_STATUS                Status;
  UINTN                     ProcessorIndex;
  EFI_MP_SERVICES_PROTOCOL  *MpServices;

  MpServices = CpuFeaturesData->MpService.Protocol;
  Status     = MpServices->WhoAmI (MpServices, &ProcessorIndex);
  ASSERT_EFI_ERROR (Status);
  return ProcessorIndex;
}

/**
  Gets detailed MP-related information on the requested processor at the
  instant this call is made.

  @param[in]  ProcessorNumber       The handle number of processor.
  @param[out] ProcessorInfoBuffer   A pointer to the buffer where information for
                                    the requested processor is deposited.

  @return Status of MpServices->GetProcessorInfo().
**/
EFI_STATUS
GetProcessorInformation (
  IN  UINTN                      ProcessorNumber,
  OUT EFI_PROCESSOR_INFORMATION  *ProcessorInfoBuffer
  )
{
  EFI_STATUS                Status;
  EFI_MP_SERVICES_PROTOCOL  *MpServices;
  CPU_FEATURES_DATA         *CpuFeaturesData;

  CpuFeaturesData = GetCpuFeaturesData ();
  MpServices      = CpuFeaturesData->MpService.Protocol;

  Status = MpServices->GetProcessorInfo (
                         MpServices,
                         ProcessorNumber | CPU_V2_EXTENDED_TOPOLOGY,
                         ProcessorInfoBuffer
                         );
  return Status;
}

/**
  Worker function to execute a caller provided function on all enabled APs.

  @param[in]  Procedure               A pointer to the function to be run on
                                      enabled APs of the system.
  @param[in]  MpEvent                 A pointer to the event to be used later
                                      to check whether procedure has done.
**/
VOID
StartupAllAPsWorker (
  IN  EFI_AP_PROCEDURE  Procedure,
  IN  EFI_EVENT         MpEvent
  )
{
  EFI_STATUS                Status;
  EFI_MP_SERVICES_PROTOCOL  *MpServices;
  CPU_FEATURES_DATA         *CpuFeaturesData;

  CpuFeaturesData = GetCpuFeaturesData ();
  MpServices      = CpuFeaturesData->MpService.Protocol;

  //
  // Wakeup all APs
  //
  Status = MpServices->StartupAllAPs (
                         MpServices,
                         Procedure,
                         FALSE,
                         MpEvent,
                         0,
                         CpuFeaturesData,
                         NULL
                         );
  ASSERT_EFI_ERROR (Status);
}

/**
  Worker function to switch the requested AP to be the BSP from that point onward.

  @param[in] ProcessorNumber   The handle number of AP that is to become the new BSP.
**/
VOID
SwitchNewBsp (
  IN  UINTN  ProcessorNumber
  )
{
  EFI_STATUS                Status;
  EFI_MP_SERVICES_PROTOCOL  *MpServices;
  CPU_FEATURES_DATA         *CpuFeaturesData;

  CpuFeaturesData = GetCpuFeaturesData ();
  MpServices      = CpuFeaturesData->MpService.Protocol;

  //
  // Wakeup all APs
  //
  Status = MpServices->SwitchBSP (
                         MpServices,
                         ProcessorNumber,
                         TRUE
                         );
  ASSERT_EFI_ERROR (Status);
}

/**
  Worker function to retrieve the number of logical processor in the platform.

  @param[out] NumberOfCpus                Pointer to the total number of logical
                                          processors in the system, including the BSP
                                          and disabled APs.
  @param[out] NumberOfEnabledProcessors   Pointer to the number of enabled logical
                                          processors that exist in system, including
                                          the BSP.
**/
VOID
GetNumberOfProcessor (
  OUT UINTN  *NumberOfCpus,
  OUT UINTN  *NumberOfEnabledProcessors
  )
{
  EFI_STATUS                Status;
  EFI_MP_SERVICES_PROTOCOL  *MpServices;
  CPU_FEATURES_DATA         *CpuFeaturesData;

  CpuFeaturesData = GetCpuFeaturesData ();
  MpServices      = CpuFeaturesData->MpService.Protocol;

  //
  // Get the number of CPUs
  //
  Status = MpServices->GetNumberOfProcessors (
                         MpServices,
                         NumberOfCpus,
                         NumberOfEnabledProcessors
                         );
  ASSERT_EFI_ERROR (Status);
}

/**
  Performs CPU features Initialization.

  This service will invoke MP service to perform CPU features
  initialization on BSP/APs per user configuration.

  @note This service could be called by BSP only.
**/
VOID
EFIAPI
CpuFeaturesInitialize (
  VOID
  )
{
  CPU_FEATURES_DATA  *CpuFeaturesData;
  UINTN              OldBspNumber;
  EFI_EVENT          MpEvent;
  EFI_STATUS         Status;

  CpuFeaturesData = GetCpuFeaturesData ();

  OldBspNumber               = GetProcessorIndex (CpuFeaturesData);
  CpuFeaturesData->BspNumber = OldBspNumber;

  //
  //
  // Initialize MpEvent to suppress incorrect compiler/analyzer warnings.
  //
  MpEvent = NULL;

  if (CpuFeaturesData->NumberOfCpus > 1) {
    Status = gBS->CreateEvent (
                    EVT_NOTIFY_WAIT,
                    TPL_CALLBACK,
                    EfiEventEmptyFunction,
                    NULL,
                    &MpEvent
                    );
    ASSERT_EFI_ERROR (Status);

    //
    // Wakeup all APs for programming.
    //
    StartupAllAPsWorker (SetProcessorRegister, MpEvent);
  }

  //
  // Programming BSP
  //
  SetProcessorRegister (CpuFeaturesData);

  if (CpuFeaturesData->NumberOfCpus > 1) {
    //
    // Wait all processors to finish the task.
    //
    do {
      Status = gBS->CheckEvent (MpEvent);
    } while (Status == EFI_NOT_READY);

    ASSERT_EFI_ERROR (Status);
  }

  //
  // Switch to new BSP if required
  //
  if (CpuFeaturesData->BspNumber != OldBspNumber) {
    SwitchNewBsp (CpuFeaturesData->BspNumber);
  }
}
