Newer
Older
AMI-Aptio-BIOS-Reversed / LenovoServerPkg / GetCpuInfo / GetCpuInfo.c
@Ajax Dong Ajax Dong 2 days ago 30 KB Full restructure
/** @file
  GetCpuInfo - Lenovo HR650X BIOS CPU Information Collection DXE Driver

  Source file: PurleyPlatPkg/Cpu/Dxe/GetCpuInfo/GetCpuInfo.c
  Image:       GetCpuInfo.efi
  SHA256:      0abaf97cc019b0b5c9d98419d8ad527559307ad2afa7d8bb84dbc33d7589688e

  This DXE driver collects detailed CPU information from all processors in the
  system by executing CPUID instructions and reading model-specific registers
  (MSRs). The collected data -- including Platform ID, Stepping, Microcode
  revision, CPUID signature, core frequency, and actual operating frequency --
  is displayed through an HII form in the BIOS setup menu.

  The driver uses the following protocols:
  - EFI CPU Architecture Protocol (to enumerate processors)
  - HII Database Protocol (to manage forms)
  - HII Package List Protocol (to register string packages)
  - HII String Protocol (for string management)
  - HII Config Access Protocol (for form callbacks)
  - HII Config Routing Protocol (for configuration routing)
**/

#include <Uefi.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiRuntimeServicesTableLib.h>
#include <Library/DxeHobLib.h>
#include <Library/BaseLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/PrintLib.h>
#include <Library/DxePcdLib.h>
#include <Library/IoLib.h>
#include <Library/PciExpressLib.h>
#include <Library/UefiHiiServicesLib.h>
#include <Library/UefiHiiLib.h>
#include <Library/DevicePathLib.h>
#include <Library/UefiLib.h>
#include <Protocol/HiiDatabase.h>
#include <Protocol/HiiPackageList.h>
#include <Protocol/HiiString.h>
#include <Protocol/HiiConfigAccess.h>
#include <Protocol/HiiConfigRouting.h>
#include <Protocol/Cpu.h>
#include <Guid/HobList.h>

//
//============================================================================
//  GLOBAL VARIABLES  (.data section, 0x3D30 - 0x3DF0)
//============================================================================
//

EFI_HANDLE              gImageHandle;                    ///< 0x3D40
EFI_SYSTEM_TABLE        *gST;                            ///< 0x3D30
EFI_BOOT_SERVICES       *gBS;                            ///< 0x3D38
EFI_RUNTIME_SERVICES    *gRT;                            ///< 0x3D48

VOID                    *gPcdProtocol;                   ///< 0x3D58  PCD protocol (lazy init)
VOID                    *gHobList;                       ///< 0x3D60  HOB list pointer (lazy init)
UINT64                  gPciExpressBaseAddress;          ///< 0x3D68  PciExpressBaseAddress from PCD

VOID                    *gHiiDatabase;                   ///< 0x3D70  EFI_HII_DATABASE_PROTOCOL
VOID                    *gHiiConfigAccess;               ///< 0x3D78  EFI_HII_CONFIG_ACCESS_PROTOCOL
VOID                    *gHiiString;                     ///< 0x3D80  EFI_HII_STRING_PROTOCOL
VOID                    *gHiiConfigRouting;              ///< 0x3D88  EFI_HII_CONFIG_ROUTING_PROTOCOL
VOID                    *gHiiPackageList;                ///< 0x3D90  EFI_HII_PACKAGE_LIST_PROTOCOL

CHAR16                  *gAllCpuInfoString;              ///< 0x3DA0  "Total CPU Number: <N>"
CHAR16                  *gActualCpuFreqStrings;          ///< 0x3DA8  Per-CPU actual frequency strings
CHAR16                  *gSteppingStrings;               ///< 0x3DB0  Per-CPU stepping strings
CHAR16                  *gCpuCoreFreqStrings;            ///< 0x3DB8  Per-CPU core frequency strings
CHAR16                  *gMicroCodeRevStrings;           ///< 0x3DC0  Per-CPU microcode revision strings
UINT32                  gNumberOfProcessors;             ///< 0x3DC8  Total number of processors
CHAR16                  *gCpuIdStrings;                  ///< 0x3DD0  Per-CPU CPUID strings
CHAR16                  *gPlatformIdStrings;             ///< 0x3DD8  Per-CPU platform ID strings
CHAR16                  *gCpuNameStrings;                ///< 0x3DE0  Per-CPU name strings ("CPU<N>")
EFI_CPU_ARCH_PROTOCOL   *gCpuProtocol;                   ///< 0x3DE8  EFI_CPU_ARCH_PROTOCOL

UINT64                  gMaxTurboRatio;                  ///< 0x3D28  Cached MPERF value for frequency calc

//
//============================================================================
//  GUID DEFINITIONS  (.rdata section)
//============================================================================
//
// The GUIDs are defined externally in the PE/COFF .rdata section:
//   unk_3B00 = EFI_CPU_ARCH_PROTOCOL_GUID
//   unk_3B10 = gEfiRuntimeServicesTableGuid
//   unk_3B30 = HII package list GUID for VFR
//   unk_3B40 = gEfiPcdProtocolGuid
//   unk_3B50 = PackageListGuid for HII string packages
//   unk_3C08 = gEfiHiiStringProtocolGuid
//   unk_3C18 = gEfiHiiConfigRoutingProtocolGuid
//   unk_3C28 = gEfiHiiPackageListProtocolGuid
//   unk_3C38 = gEfiHiiDatabaseProtocolGuid
//   unk_3C48 = gEfiHobListGuid
//   unk_3C58 = gEfiHiiConfigAccessProtocolGuid
//   unk_3C68 = CPU Info formset GUID
//   unk_3C80 = CPU Info form GUID (VFR binary data)
//

//
//============================================================================
//  FORWARD DECLARATIONS
//============================================================================
//

EFI_STATUS
EFIAPI
GetCpuInfoEntryPoint (
  IN EFI_HANDLE         ImageHandle,
  IN EFI_SYSTEM_TABLE   *SystemTable
  );

EFI_STATUS
CpuInfoDriverEntry (
  IN EFI_HANDLE         ImageHandle,
  IN EFI_SYSTEM_TABLE   *SystemTable
  );

VOID
CpuInfoCollection (
  VOID
  );

EFI_STATUS
CpuInfoDisplayForm (
  IN EFI_HII_HANDLE   HiiHandle
  );

EFI_HII_HANDLE
CpuInfoRegisterPackageList (
  IN CONST EFI_GUID    *PackageListGuid,
  ...
  );

VOID
CpuInfoDelayMicro (
  IN UINT32   DelayCount
  );

EFI_STATUS
GetPlatformLanguage (
  OUT CHAR16          **PlatformLang
  );

//
//============================================================================
//  CPUID / MSR / I/O HELPERS (inlined wrappers)
//============================================================================
//

/**
  Executes CPUID instruction with given leaf and subleaf.

  @param[in]  Leaf     CPUID input EAX.
  @param[in]  SubLeaf  CPUID input ECX.
  @param[out] Eax      Output EAX (may be NULL).
  @param[out] Ebx      Output EBX (may be NULL).
  @param[out] Ecx      Output ECX (may be NULL).
  @param[out] Edx      Output EDX (may be NULL).
**/
VOID
EFIAPI
CpuIdEx (
  IN UINT32  Leaf,
  IN UINT32  SubLeaf,
  OUT UINT32 *Eax OPTIONAL,
  OUT UINT32 *Ebx OPTIONAL,
  OUT UINT32 *Ecx OPTIONAL,
  OUT UINT32 *Edx OPTIONAL
  )
{
  __asm {
    mov eax, Leaf
    mov ecx, SubLeaf
    cpuid
    mov edi, Eax
    test edi, edi
    jz  @f
    mov [edi], eax
  @@:
    mov edi, Ebx
    test edi, edi
    jz  @f
    mov [edi], ebx
  @@:
    mov edi, Ecx
    test edi, edi
    jz  @f
    mov [edi], ecx
  @@:
    mov edi, Edx
    test edi, edi
    jz  @f
    mov [edi], edx
  @@:
  }
}

/**
  Reads an MSR.

  @param[in]  Index  MSR index.

  @return The 64-bit MSR value.
**/
UINT64
EFIAPI
AsmReadMsr64 (
  IN UINT32  Index
  )
{
  return __readmsr (Index);
}

/**
  Writes an MSR.

  @param[in] Index  MSR index.
  @param[in] Value  64-bit value to write.
**/
VOID
EFIAPI
AsmWriteMsr64 (
  IN UINT32  Index,
  IN UINT64  Value
  )
{
  __writemsr (Index, Value);
}

//
//============================================================================
//  MODULE ENTRY POINT
//============================================================================
//

/**
  GetCpuInfo DXE driver entry point.

  Initializes UEFI global variables (ImageHandle, SystemTable, BootServices,
  RuntimeServices), locates required protocols, reads platform type,
  and dispatches the main CPU info collection routine.

  @param[in] ImageHandle  The firmware allocated handle for the EFI image.
  @param[in] SystemTable  A pointer to the EFI System Table.

  @return EFI_SUCCESS on success.
**/
EFI_STATUS
EFIAPI
GetCpuInfoEntryPoint (
  IN EFI_HANDLE         ImageHandle,
  IN EFI_SYSTEM_TABLE   *SystemTable
  )
{
  //
  // Call the actual driver entry
  //
  return CpuInfoDriverEntry (ImageHandle, SystemTable);
}

/**
  Internal driver initialization.

  1. Saves UEFI handles and protocols
  2. Locates HII Database, PackageList, String, ConfigAccess, ConfigRouting
  3. Checks platform language and configures IO
  4. Reads platform type through IO port 0x80
  5. Waits for IO completion (polling port 0x508)
  6. In setup mode, calls CpuInfoFormUpdate() to display the form

  @param[in] ImageHandle  The firmware allocated handle.
  @param[in] SystemTable  A pointer to the EFI System Table.

  @return EFI_SUCCESS in setup mode, or EFI_INVALID_PARAMETER otherwise.
**/
EFI_STATUS
CpuInfoDriverEntry (
  IN EFI_HANDLE         ImageHandle,
  IN EFI_SYSTEM_TABLE   *SystemTable
  )
{
  EFI_STATUS  Status;
  UINT16      PlatformType;
  BOOLEAN     SetupMode;
  UINT64      PcdAddress;
  UINTN       WaitValue;

  //
  // Step 1: Save UEFI handles
  //
  gImageHandle = ImageHandle;
  gST          = SystemTable;
  gBS          = SystemTable->BootServices;
  gRT          = SystemTable->RuntimeServices;

  ASSERT (ImageHandle != NULL);
  ASSERT (gST != NULL);
  ASSERT (gBS != NULL);
  ASSERT (gRT != NULL);

  //
  // Step 2: Initialize HOB list
  //
  GetHobList ();

  //
  // Step 3: Get PCD for PCI Express base address (lazy init via PCD protocol)
  //
  CpuInfoLocatePcdProtocol ();
  gPciExpressBaseAddress = PcdGet64 (PcdPciExpressBaseAddress);

  //
  // Step 4: Locate HII protocols
  //
  Status = gBS->LocateProtocol (&gEfiHiiDatabaseProtocolGuid, NULL, &gHiiDatabase);
  ASSERT_EFI_ERROR (Status);

  Status = gBS->LocateProtocol (&gEfiHiiPackageListProtocolGuid, NULL, &gHiiPackageList);
  ASSERT_EFI_ERROR (Status);

  Status = gBS->LocateProtocol (&gEfiHiiStringProtocolGuid, NULL, &gHiiString);
  ASSERT_EFI_ERROR (Status);

  Status = gBS->LocateProtocol (&gEfiHiiConfigAccessProtocolGuid, NULL, &gHiiConfigAccess);
  ASSERT_EFI_ERROR (Status);

  Status = gBS->LocateProtocol (&gEfiHiiConfigRoutingProtocolGuid, NULL, &gHiiConfigRouting);
  ASSERT_EFI_ERROR (Status);

  //
  // Step 5: Check and enable platform language via PCI config
  //  - reads byte at PciExpressBaseAddress + 0xF4244
  //  - if bit 7 is clear, writes 0x500 (1280) to IO port 0xF4240
  //
  PcdAddress = 1024068; // Offset 0xF4244 from PciExpressBaseAddress
  if ((*(INT8 *)PciExpressRead32 (PcdAddress) & 0x80) == 0) {
    //
    // Write to IO port to enable
    //
    CpuInfoEnablePlatformLanguage (PcdAddress - 4);  // 0xF4240
    *(UINT8 *)PciExpressRead32 (PcdAddress) |= 0x80;
  }

  //
  // Step 6: Read platform type from IO port 0x80
  //
  PlatformType = IoRead32 (IO_PORT_0x80);
  IoRead32 (IO_PORT_0x80);    // Consume/clear

  SetupMode = (PlatformType & 0x200) != 0;

  //
  // Step 7: Wait for IO completion by polling port 0x508
  //
  do {
    IoRead32 (IO_PORT_0x80);      // Small delay via IO port read
  } while (((IoRead32 (IO_PORT_0x508) + 357
             - IoRead32 (IO_PORT_0x508)) & 0x800000) == 0);

  //
  // Step 8: Return based on setup mode
  //
  if (SetupMode) {
    return CpuInfoCollection ();
  } else {
    return EFI_INVALID_PARAMETER;
  }
}

//
//============================================================================
//  CPU INFORMATION COLLECTION
//============================================================================
//

/**
  Main CPU information collection routine.

  Locates the CPU Architecture protocol to get number of processors,
  allocates per-processor Unicode string buffers (8 buffers x 100 chars each),
  writes label strings and per-CPU data, then calls CpuInfoReadAll() to
  populate values. Registers a callback with the CPU protocol and creates
  an HII form for display.

  @return EFI_SUCCESS on success.
**/
EFI_STATUS
CpuInfoCollection (
  VOID
  )
{
  EFI_STATUS                    Status;
  UINT32                        Index;
  UINT32                        ProcessorNumber;
  EFI_CPU_ARCH_PROTOCOL         *CpuProtocol;
  EFI_HII_HANDLE                HiiHandle;
  UINT16                        *Buffer;

  //
  // Step 1: PCD enable check
  //
  if (!PcdGetBool (PcdCpuInfoDisplayEnable)) {
    return EFI_SUCCESS;
  }

  //
  // Step 2: Locate CPU Architecture Protocol
  //
  CpuProtocol = CpuInfoLocateCpuProtocol ();
  if (CpuProtocol == NULL) {
    return EFI_UNSUPPORTED;
  }

  //
  // Step 3: Get number of processors from HOB
  //
  ProcessorNumber = CpuInfoGetProcessorCountFromHob ();
  if (ProcessorNumber == 0) {
    return EFI_NOT_FOUND;
  }

  gNumberOfProcessors = ProcessorNumber;

  //
  // Step 4: Allocate per-processor string buffers
  //
  gAllCpuInfoString    = AllocateZeroPool (CPU_INFO_STRING_SIZE * ProcessorNumber);
  gCpuNameStrings       = AllocateZeroPool (CPU_INFO_STRING_SIZE * ProcessorNumber);
  gPlatformIdStrings    = AllocateZeroPool (CPU_INFO_STRING_SIZE * ProcessorNumber);
  gSteppingStrings      = AllocateZeroPool (CPU_INFO_STRING_SIZE * ProcessorNumber);
  gMicroCodeRevStrings  = AllocateZeroPool (CPU_INFO_STRING_SIZE * ProcessorNumber);
  gCpuIdStrings         = AllocateZeroPool (CPU_INFO_STRING_SIZE * ProcessorNumber);
  gCpuCoreFreqStrings   = AllocateZeroPool (CPU_INFO_STRING_SIZE * ProcessorNumber);
  gActualCpuFreqStrings = AllocateZeroPool (CPU_INFO_STRING_SIZE * ProcessorNumber);

  if (gAllCpuInfoString == NULL || gCpuNameStrings == NULL ||
      gPlatformIdStrings == NULL || gSteppingStrings == NULL ||
      gMicroCodeRevStrings == NULL || gCpuIdStrings == NULL ||
      gCpuCoreFreqStrings == NULL || gActualCpuFreqStrings == NULL) {

    CpuInfoFreeAllBuffers ();
    return EFI_OUT_OF_RESOURCES;
  }

  //
  // Step 5: Build header string "Total CPU Number: %d\n"
  //
  UnicodeSPrint (
    gAllCpuInfoString,
    CPU_INFO_STRING_SIZE,
    L"Total CPU Number: %d\n",
    ProcessorNumber
    );

  //
  // Step 6: Initialize per-processor label strings
  //
  for (Index = 0; Index < ProcessorNumber; Index++) {
    StrCpyS (
      &gCpuNameStrings[Index * CPU_INFO_STRING_SIZE],
      CPU_INFO_STRING_SIZE,
      L"CPU"
      );
    StrCpyS (
      &gPlatformIdStrings[Index * CPU_INFO_STRING_SIZE],
      CPU_INFO_STRING_SIZE,
      L"PlatformID: "
      );
    StrCpyS (
      &gSteppingStrings[Index * CPU_INFO_STRING_SIZE],
      CPU_INFO_STRING_SIZE,
      L"Stepping: "
      );
    StrCpyS (
      &gMicroCodeRevStrings[Index * CPU_INFO_STRING_SIZE],
      CPU_INFO_STRING_SIZE,
      L"MicroCodeRev: "
      );
    StrCpyS (
      &gCpuIdStrings[Index * CPU_INFO_STRING_SIZE],
      CPU_INFO_STRING_SIZE,
      L"CPUID: "
      );
    StrCpyS (
      &gCpuCoreFreqStrings[Index * CPU_INFO_STRING_SIZE],
      CPU_INFO_STRING_SIZE,
      L"CpuCoreFreq (MHz): "
      );
    StrCpyS (
      &gActualCpuFreqStrings[Index * CPU_INFO_STRING_SIZE],
      CPU_INFO_STRING_SIZE,
      L"ActualCpuFreq (MHz): "
      );
  }

  //
  // Step 7: Collect actual CPU data from all processors
  //
  CpuInfoReadAll ();

  //
  // Step 8: Register callback for form update
  //
  ((EFI_HII_CONFIG_ACCESS_PROTOCOL *)gCpuProtocol)->CallBack (
    gCpuProtocol,
    CpuInfoReadAll,
    0, 0, 0, 0, 0
    );

  //
  // Step 9: Create and display HII form
  //
  HiiHandle = CpuInfoRegisterPackageList (
                &gCpuInfoFormSetGuid,
                &gCpuInfoVfrBin,
                NULL
                );

  CpuInfoDisplayForm (HiiHandle);

  //
  // Step 10: Free buffers
  //
  CpuInfoFreeAllBuffers ();

  return EFI_SUCCESS;
}

/**
  Reads CPU information from every processor and populates
  the global string buffers.

  For each processor, this function:
  1. Gets stepping (CPUID.01.EAX[3:0])
  2. Gets CPUID signature (CPUID.01.EAX)
  3. Gets Platform ID from MSR 0x17[50:47]
  4. Triggers microcode update then reads MSR 0x8B[63:32]
  5. Gets core frequency from MSR 0xCE[15:8] * 100 MHz
  6. Measures actual frequency using MPERF/APERF ratio
**/
VOID
CpuInfoReadAll (
  VOID
  )
{
  UINT32      Index;
  UINT32      Eax;
  UINT32      Ebx;
  UINT32      Ecx;
  UINT32      Edx;
  UINT64      PlatformIdMsr;
  UINT64      MicroCodeRevMsr;
  UINT64      MaxEfficiencyRatio;
  UINT64      MperfValue;
  UINT64      AperfValue;
  UINT64      AperfDelta;
  UINT64      MeasuredRatio;
  UINT64      ActualFrequency;
  CHAR16      Buffer[38];   // Max width for number formatting

  for (Index = 0; Index < gNumberOfProcessors; Index++) {
    //
    // ---- CPU name ----
    //
    UnicodeValueToString (Buffer, 0, Index, 4);
    StrCatS (
      &gCpuNameStrings[Index * CPU_INFO_STRING_SIZE],
      CPU_INFO_STRING_SIZE,
      Buffer
      );

    if (Index == 0) {
      //
      // ---- CPUID leaf 1: stepping + CPUID signature ----
      //
      AsmCpuid (CPUID_VERSION_INFO, &Eax, &Ebx, &Ecx, &Edx);

      // Stepping (EAX[3:0])
      UnicodeValueToString (Buffer, 0, Eax & 0xF, 1);
      StrCatS (
        &gSteppingStrings[Index * CPU_INFO_STRING_SIZE],
        CPU_INFO_STRING_SIZE,
        Buffer
        );

      // CPUID (EAX full, printed as hex)
      UnicodeValueToString (
        Buffer,
        FLAG_UNSIGNED | FLAG_ALTERNATE,
        Eax,
        8
        );
      StrCatS (
        &gCpuIdStrings[Index * CPU_INFO_STRING_SIZE],
        CPU_INFO_STRING_SIZE,
        Buffer
        );
    }

    //
    // ---- Platform ID from MSR 0x17 (bits [50:47] in upper word) ----
    //
    PlatformIdMsr = AsmReadMsr64 (MSR_IA32_PLATFORM_ID);
    UnicodeValueToString (Buffer, 0, (PlatformIdMsr >> 32) & 0xFFFF, 4);
    StrCatS (
      &gPlatformIdStrings[Index * CPU_INFO_STRING_SIZE],
      CPU_INFO_STRING_SIZE,
      Buffer
      );

    //
    // ---- Microcode Revision ----
    // Procedure: write 0 to MSR 0x8B, execute CPUID leaf 1,
    // then read MSR 0x8B[63:32].
    //
    AsmWriteMsr64 (MSR_IA32_BIOS_SIGN_ID, 0);
    AsmCpuid (CPUID_VERSION_INFO, NULL, NULL, NULL, NULL);
    MicroCodeRevMsr = AsmReadMsr64 (MSR_IA32_BIOS_SIGN_ID);
    MicroCodeRevMsr >>= 32;

    UnicodeValueToString (Buffer, 0, MicroCodeRevMsr, 8);
    StrCatS (
      &gMicroCodeRevStrings[Index * CPU_INFO_STRING_SIZE],
      CPU_INFO_STRING_SIZE,
      Buffer
      );

    //
    // ---- Core Frequency ----
    // MSR_PLATFORM_INFO (0xCE) byte 1 is the Maximum Efficiency Ratio.
    // Core frequency = ratio * 100 MHz (bus frequency).
    //
    MaxEfficiencyRatio = AsmReadMsr64 (MSR_PLATFORM_INFO);
    MaxEfficiencyRatio = (MaxEfficiencyRatio >> 8) & 0xFF;

    UnicodeValueToString (Buffer, 0, MaxEfficiencyRatio * 100, 8);
    StrCatS (
      &gCpuCoreFreqStrings[Index * CPU_INFO_STRING_SIZE],
      CPU_INFO_STRING_SIZE,
      Buffer
      );

    //
    // ---- Actual Operating Frequency ----
    // Uses the MPERF (MSR 0xE7) and APERF (MSR 0xE8) ratio method:
    //
    //   1. First call: zero MPERF, delay, read MPERF as baseline
    //   2. Zero APERF, delay, read APERF
    //   3. ActualFreq = CoreFreq * (APERF / MPERF)
    //
    if (gMaxTurboRatio == 0) {
      AsmWriteMsr64 (MSR_IA32_MPERF, 0);
      CpuInfoDelayMicro (357);
      gMaxTurboRatio = AsmReadMsr64 (MSR_IA32_MPERF);
    }

    AsmWriteMsr64 (MSR_IA32_APERF, 0);
    CpuInfoDelayMicro (357);
    AperfValue = AsmReadMsr64 (MSR_IA32_APERF);

    if (AperfValue > gMaxTurboRatio) {
      AperfValue = gMaxTurboRatio;
    }

    //
    // Calculate: ratio = (APERF * 100) / MPERF  (as percentage)
    //            freq  = (ratio * CoreFreq * 100) / 10000
    //
    MeasuredRatio = DivU64xU64 (MultU64xU64 (AperfValue, 100), gMaxTurboRatio);

    //
    // actual MHz = (ratio * MaxEfficiencyRatio * 100) / 100
    //
    ActualFrequency = DivU64xU64 (
                       MultU64xU64 (MeasuredRatio, MaxEfficiencyRatio * 100),
                       100
                       );

    //
    // Format and append
    //
    UnicodeValueToString (Buffer, 0, ActualFrequency / 100, 8);
    StrCatS (
      &gActualCpuFreqStrings[Index * CPU_INFO_STRING_SIZE],
      CPU_INFO_STRING_SIZE,
      Buffer
      );
  }
}

//
//============================================================================
//  UTILITY FUNCTIONS
//============================================================================
//

/**
  Locates the PCD Protocol using gBS->LocateProtocol with lazy init.
**/
EFI_STATUS
CpuInfoLocatePcdProtocol (
  VOID
  )
{
  EFI_STATUS  Status;

  if (gPcdProtocol != NULL) {
    return EFI_SUCCESS;
  }

  Status = gBS->LocateProtocol (
                  &gEfiPcdProtocolGuid,
                  NULL,
                  &gPcdProtocol
                  );

  if (EFI_ERROR (Status)) {
    DEBUG ((EFI_D_ERROR, "ASSERT: !EFI_ERROR (Status)\n"));
  }

  return Status;
}

/**
  Locates the EFI CPU Architecture Protocol with lazy init.
**/
EFI_CPU_ARCH_PROTOCOL *
CpuInfoLocateCpuProtocol (
  VOID
  )
{
  EFI_STATUS  Status;

  if (gCpuProtocol != NULL) {
    return gCpuProtocol;
  }

  Status = gBS->LocateProtocol (
                  &gEfiCpuArchProtocolGuid,
                  NULL,
                  (VOID **)&gCpuProtocol
                  );

  if (EFI_ERROR (Status)) {
    gCpuProtocol = NULL;
  }

  return gCpuProtocol;
}

/**
  Searches the HOB list for CPU info data and returns the processor count.

  This function traverses the HOB list looking for a GUID HOB with
  CPU-related data. The HOB GUID is compared against a specific expected
  GUID. The first 16 bytes past the GUID structure contain the
  processor count.

  @return Number of processors, or 0 on failure.
**/
UINT32
CpuInfoGetProcessorCountFromHob (
  VOID
  )
{
  EFI_HOB_GUID_TYPE     *GuidHob;
  UINT32                CpuCount;
  EFI_GUID              *HobDataGuid;
  UINT32                MagicCheck[4];
  UINT32                MagicReverse[4];

  //
  // These magic values represent GUID comparison data
  // They are precomputed from the CPU-info HOB GUID
  //
  MagicCheck[0]    = 0xDC33DC7E;
  MagicCheck[1]    = 0x46DCC6BE;
  MagicCheck[2]    = 0x7CBB9BAD;
  MagicCheck[3]    = 0x38D7C422;

  MagicReverse[0]  = 0x7FF36C21;
  MagicReverse[1]  = 0x4310A9FD;
  MagicReverse[2]  = 0xCA6C383A;
  MagicReverse[3]  = 0xC08EF412;

  GuidHob = GetNextGuidHob (GetHobList ());
  ASSERT (GuidHob != NULL);

  //
  // Walk through HOBs looking for CPU info
  //
  while (GuidHob != NULL) {
    HobDataGuid = (EFI_GUID *)((UINT8 *)GuidHob + sizeof (EFI_HOB_GENERIC_HEADER));

    //
    // Compare GUID to the expected CPU-info HOB GUID
    //
    if (CompareGuid (HobDataGuid, (EFI_GUID *)MagicCheck)) {
      //
      // Found CPU info HOB -- extract processor count
      //
      CpuCount = *(UINT32 *)((UINT8 *)GuidHob + sizeof (EFI_HOB_GUID_TYPE));
      return CpuCount;
    }

    //
    // Move to next HOB
    //
    GuidHob = GetNextGuidHob (
               (VOID *)((UINT8 *)GuidHob + *(UINT16 *)((UINT8 *)GuidHob + 2))
               );
  }

  ASSERT (GuidHob != NULL);
  return 0;
}

/**
  Microsecond-scale delay using I/O port writes.

  Writes to the PCI Express configuration space (via LPC/eSPI cycle)
  to create a known delay. The delay is achieved by repeatedly
  writing to a port and waiting for completion.

  @param[in] DelayCount  Number of delay iterations (357 = ~1 tick).
**/
VOID
CpuInfoDelayMicro (
  IN UINT32  DelayCount
  )
{
  UINT32  ShiftedCount;
  UINT32  AddressRemainder;

  ShiftedCount    = DelayCount >> 22;
  AddressRemainder = DelayCount & 0x3FFFFF;

  do {
    //
    // Wait for previous I/O to complete by polling port 0x508
    //
    while (((AddressRemainder + (IoRead32 (IO_PORT_0x508) & 0xFFFFFF)
             - IoRead32 (IO_PORT_0x508)) & 0x800000) == 0) {
      IoRead32 (IO_PORT_0x80);   // Delay via IO read
    }

    //
    // After first iteration, always use max remainder for remaining
    // shifted counts
    //
    AddressRemainder = 0x400000;
    ShiftedCount--;
  } while (ShiftedCount > 0);
}

/**
  Retrieves the platform language from UEFI variable "PlatformLang".

  First call determines required buffer size (expects EFI_BUFFER_TOO_SMALL),
  second call retrieves the actual string.

  @param[out] PlatformLang  Pointer to receive allocated buffer with language string.

  @return EFI_STATUS.
**/
EFI_STATUS
GetPlatformLanguage (
  OUT CHAR16  **PlatformLang
  )
{
  EFI_STATUS  Status;
  UINTN       StringSize;

  if (PlatformLang == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  *PlatformLang = NULL;
  StringSize = 0;

  //
  // First: query size
  //
  Status = gRT->GetVariable (
                  L"PlatformLang",
                  &gEfiRuntimeServicesTableGuid,
                  NULL,
                  &StringSize,
                  NULL
                  );

  if (Status == EFI_BUFFER_TOO_SMALL) {
    *PlatformLang = AllocateZeroPool (StringSize);
    if (*PlatformLang == NULL) {
      return EFI_OUT_OF_RESOURCES;
    }

    //
    // Second: get actual data
    //
    Status = gRT->GetVariable (
                    L"PlatformLang",
                    &gEfiRuntimeServicesTableGuid,
                    NULL,
                    &StringSize,
                    *PlatformLang
                    );

    if (EFI_ERROR (Status)) {
      gBS->FreePool (*PlatformLang);
      *PlatformLang = NULL;
    }
  }

  return Status;
}

/**
  Frees all per-processor string buffers.
**/
VOID
CpuInfoFreeAllBuffers (
  VOID
  )
{
  if (gAllCpuInfoString    != NULL) gBS->FreePool (gAllCpuInfoString);
  if (gCpuNameStrings       != NULL) gBS->FreePool (gCpuNameStrings);
  if (gPlatformIdStrings    != NULL) gBS->FreePool (gPlatformIdStrings);
  if (gSteppingStrings      != NULL) gBS->FreePool (gSteppingStrings);
  if (gMicroCodeRevStrings  != NULL) gBS->FreePool (gMicroCodeRevStrings);
  if (gCpuIdStrings         != NULL) gBS->FreePool (gCpuIdStrings);
  if (gCpuCoreFreqStrings   != NULL) gBS->FreePool (gCpuCoreFreqStrings);
  if (gActualCpuFreqStrings != NULL) gBS->FreePool (gActualCpuFreqStrings);
}

//
//============================================================================
//  HII FORM DISPLAY
//============================================================================
//

/**
  Displays the CPU information form in the BIOS setup.

  Allocates HII op-code handles, creates the form header/end,
  then populates the form with the collected per-processor data.

  @param[in] HiiHandle  The HII handle for the registered form.

  @return EFI_SUCCESS on success.
**/
EFI_STATUS
CpuInfoDisplayForm (
  IN EFI_HII_HANDLE  HiiHandle
  )
{
  EFI_STATUS      Status;
  VOID            *StartOpCodeHandle;
  VOID            *EndOpCodeHandle;
  UINTN           Index;

  //
  // Allocate op-code handles
  //
  StartOpCodeHandle = HiiAllocateOpCodeHandle ();
  EndOpCodeHandle   = HiiAllocateOpCodeHandle ();

  if (StartOpCodeHandle == NULL || EndOpCodeHandle == NULL) {
    if (StartOpCodeHandle != NULL) HiiFreeOpCodeHandle (StartOpCodeHandle);
    if (EndOpCodeHandle   != NULL) HiiFreeOpCodeHandle (EndOpCodeHandle);
    return EFI_OUT_OF_RESOURCES;
  }

  //
  // Create form header and end markers
  //
  HiiCreateFormOpCode (StartOpCodeHandle, FORM_CPU_INFO_ID, 0);
  HiiCreateEndOpCode  (EndOpCodeHandle);

  //
  // Add "Total CPU Number" header text
  //
  HiiCreateStringOpCode (
    StartOpCodeHandle,
    (UINT16 *)gAllCpuInfoString,
    0,
    0,
    0,
    0,
    NULL
    );

  //
  // Add per-processor data lines (7 fields each)
  //
  for (Index = 0; Index < gNumberOfProcessors; Index++) {
    HiiCreateStringOpCode (
      StartOpCodeHandle,
      &gCpuNameStrings[Index * CPU_INFO_STRING_SIZE],
      0, 0, 0, 0, NULL
      );
    HiiCreateStringOpCode (
      StartOpCodeHandle,
      &gPlatformIdStrings[Index * CPU_INFO_STRING_SIZE],
      0, 0, 0, 0, NULL
      );
    HiiCreateStringOpCode (
      StartOpCodeHandle,
      &gSteppingStrings[Index * CPU_INFO_STRING_SIZE],
      0, 0, 0, 0, NULL
      );
    HiiCreateStringOpCode (
      StartOpCodeHandle,
      &gMicroCodeRevStrings[Index * CPU_INFO_STRING_SIZE],
      0, 0, 0, 0, NULL
      );
    HiiCreateStringOpCode (
      StartOpCodeHandle,
      &gCpuIdStrings[Index * CPU_INFO_STRING_SIZE],
      0, 0, 0, 0, NULL
      );
    HiiCreateStringOpCode (
      StartOpCodeHandle,
      &gCpuCoreFreqStrings[Index * CPU_INFO_STRING_SIZE],
      0, 0, 0, 0, NULL
      );
    HiiCreateStringOpCode (
      StartOpCodeHandle,
      &gActualCpuFreqStrings[Index * CPU_INFO_STRING_SIZE],
      0, 0, 0, 0, NULL
      );
  }

  //
  // Update the form via HII Database
  //
  Status = ((EFI_HII_DATABASE_PROTOCOL *)gHiiDatabase)->UpdateForm (
             gHiiDatabase,
             HiiHandle,
             &gCpuInfoFormSetGuid,
             FORM_CPU_INFO_ID,
             StartOpCodeHandle,
             EndOpCodeHandle
             );

  //
  // Clean up
  //
  HiiFreeOpCodeHandle (StartOpCodeHandle);
  HiiFreeOpCodeHandle (EndOpCodeHandle);

  return Status;
}

/**
  Creates and registers the HII package list with string packages.

  @param[in] PackageListGuid  GUID for the package list.
  @param[in] ...              Variable list of packages (NULL-terminated).

  @return HII handle for the registered package, or 0 on failure.
**/
EFI_HII_HANDLE
CpuInfoRegisterPackageList (
  IN CONST EFI_GUID    *PackageListGuid,
  ...
  )
{
  VA_LIST                     Args;
  VOID                        *Package;
  UINTN                       TotalSize;
  UINT32                      PackageListSize;
  EFI_HII_PACKAGE_LIST_HEADER *PackageListHeader;
  EFI_HII_HANDLE              HiiHandle;
  UINT8                       *CurrentPtr;
  EFI_STATUS                  Status;

  VA_START (Args, PackageListGuid);

  if (PackageListGuid == NULL) {
    return 0;
  }

  //
  // Calculate total size of all packages
  //
  Package = VA_ARG (Args, VOID *);
  TotalSize = 0;
  while (Package != NULL) {
    TotalSize += ReadUnaligned32 (Package) - 4;
    Package = VA_ARG (Args, VOID *);
  }

  if (TotalSize == 0) {
    return 0;
  }

  PackageListSize = (UINT32)TotalSize + sizeof (EFI_HII_PACKAGE_LIST_HEADER);

  PackageListHeader = AllocatePool (PackageListSize);
  if (PackageListHeader == NULL) {
    return 0;
  }

  //
  // Build the package list header
  //
  CopyGuid (&PackageListHeader->PackageListGuid, PackageListGuid);
  PackageListHeader->PackageLength = PackageListSize;

  CurrentPtr = (UINT8 *)(PackageListHeader + 1);

  VA_START (Args, PackageListGuid);
  Package = VA_ARG (Args, VOID *);
  while (Package != NULL) {
    UINT32  PackageSize = ReadUnaligned32 (Package) - 4;
    CopyMem (CurrentPtr, (UINT8 *)Package + 4, PackageSize);
    CurrentPtr += PackageSize;
    Package = VA_ARG (Args, VOID *);
  }

  //
  // Register with HII Database
  //
  Status = ((EFI_HII_PACKAGE_LIST_PROTOCOL *)gHiiPackageList)->NewPackageList (
             gHiiPackageList,
             PackageListHeader,
             0,
             &HiiHandle
             );

  gBS->FreePool (PackageListHeader);

  if (EFI_ERROR (Status)) {
    return 0;
  }

  return HiiHandle;
}

/**
  Allocates a new HII op-code handle.

  @return Pointer to the allocated update data.
**/
VOID *
HiiAllocateOpCodeHandle (
  VOID
  )
{
  EFI_HII_UPDATE_DATA  *UpdateData;
  UINT8                *Buffer;

  UpdateData = AllocateZeroPool (sizeof (EFI_HII_UPDATE_DATA));
  if (UpdateData == NULL) {
    return NULL;
  }

  Buffer = AllocatePool (512);
  if (Buffer == NULL) {
    gBS->FreePool (UpdateData);
    return NULL;
  }

  UpdateData->Buffer     = Buffer;
  UpdateData->BufferSize = 512;
  UpdateData->UsedSize   = 0;

  return (VOID *)UpdateData;
}

/**
  Frees an HII op-code handle.

  @param[in] OpCodeHandle  Handle to free.
**/
VOID
HiiFreeOpCodeHandle (
  IN VOID  *OpCodeHandle
  )
{
  if (OpCodeHandle == NULL) {
    return;
  }

  gBS->FreePool (OpCodeHandle);
}