/** @file
  ProcessorSubClass.c

  Copyright (c) 2021, NUVIA Inc. All rights reserved.<BR>
  Copyright (c) 2015, Hisilicon Limited. All rights reserved.
  Copyright (c) 2015, Linaro Limited. All rights reserved.
  SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include <Uefi.h>
#include <Protocol/Smbios.h>
#include <IndustryStandard/ArmCache.h>
#include <IndustryStandard/ArmStdSmc.h>
#include <IndustryStandard/SmBios.h>
#include <Library/ArmLib.h>
#include <Library/ArmSmcLib.h>
#include <Library/BaseLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/DebugLib.h>
#include <Library/HiiLib.h>
#include <Library/IoLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/OemMiscLib.h>
#include <Library/PcdLib.h>
#include <Library/PrintLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiLib.h>

#include "SmbiosProcessor.h"

extern UINT8 ProcessorSubClassStrings[];

#define CACHE_SOCKETED_SHIFT       3
#define CACHE_LOCATION_SHIFT       5
#define CACHE_ENABLED_SHIFT        7
#define CACHE_OPERATION_MODE_SHIFT 8

typedef enum {
  CacheModeWriteThrough = 0,  ///< Cache is write-through
  CacheModeWriteBack,         ///< Cache is write-back
  CacheModeVariesWithAddress, ///< Cache mode varies by address
  CacheModeUnknown,           ///< Cache mode is unknown
  CacheModeMax
} CACHE_OPERATION_MODE;

typedef enum {
  CacheLocationInternal = 0, ///< Cache is internal to the processor
  CacheLocationExternal,     ///< Cache is external to the processor
  CacheLocationReserved,     ///< Reserved
  CacheLocationUnknown,      ///< Cache location is unknown
  CacheLocationMax
} CACHE_LOCATION;

EFI_HII_HANDLE       mHiiHandle;

EFI_SMBIOS_PROTOCOL  *mSmbios;

SMBIOS_TABLE_TYPE4 mSmbiosProcessorTableTemplate = {
    {                         // Hdr
      EFI_SMBIOS_TYPE_PROCESSOR_INFORMATION, // Type
      sizeof (SMBIOS_TABLE_TYPE4), // Length
      0                       // Handle
    },
    1,                        // Socket
    CentralProcessor,         // ProcessorType
    ProcessorFamilyIndicatorFamily2, // ProcessorFamily
    2,                        // ProcessorManufacture
    {                         // ProcessorId
      {                       // Signature
        0
      },
      {                       // FeatureFlags
        0
      }
    },
    3,                        // ProcessorVersion
    {                         // Voltage
      0
    },
    0,                        // ExternalClock
    0,                        // MaxSpeed
    0,                        // CurrentSpeed
    0,                        // Status
    ProcessorUpgradeUnknown,  // ProcessorUpgrade
    0xFFFF,                   // L1CacheHandle
    0xFFFF,                   // L2CacheHandle
    0xFFFF,                   // L3CacheHandle
    4,                        // SerialNumber
    5,                        // AssetTag
    6,                        // PartNumber
    0,                        // CoreCount
    0,                        //EnabledCoreCount
    0,                        // ThreadCount
    0,                        // ProcessorCharacteristics
    ProcessorFamilyARM,       // ProcessorFamily2
    0,                        // CoreCount2
    0,                        // EnabledCoreCount2
    0                         // ThreadCount2
};

/** Sets the HII variable `StringId` is `Pcd` isn't empty.

    @param Pcd       The FixedAtBuild PCD that contains the string to fetch.
    @param StringId  The string identifier to set.
**/
#define SET_HII_STRING_IF_PCD_NOT_EMPTY(Pcd, StringId) \
  do { \
    CHAR16 *Str; \
    Str = (CHAR16*)PcdGetPtr (Pcd); \
    if (StrLen (Str) > 0) { \
      HiiSetString (mHiiHandle, StringId, Str, NULL); \
    } \
  } while (0)

/** Fetches the specified processor's frequency in Hz.

  @param ProcessorNumber The processor number

  @return The clock frequency in MHz

**/
UINT16
GetCpuFrequency (
  IN  UINT8 ProcessorNumber
  )
{
  return (UINT16)(OemGetCpuFreq (ProcessorNumber) / 1000 / 1000);
}

/** Gets a description of the specified cache.

  @param[in] CacheLevel       Zero-based cache level (e.g. L1 cache is 0).
  @param[in] DataCache        Cache is a data cache.
  @param[in] UnifiedCache     Cache is a unified cache.
  @param[out] CacheSocketStr  The description of the specified cache

  @return The number of Unicode characters in CacheSocketStr not including the
          terminating NUL.
**/
UINTN
GetCacheSocketStr (
  IN  UINT8   CacheLevel,
  IN  BOOLEAN DataCache,
  IN  BOOLEAN UnifiedCache,
  OUT CHAR16  *CacheSocketStr
  )
{
  UINTN CacheSocketStrLen;

  if (CacheLevel == CpuCacheL1 && !DataCache && !UnifiedCache) {
    CacheSocketStrLen = UnicodeSPrint (
                          CacheSocketStr,
                          SMBIOS_STRING_MAX_LENGTH - 1,
                          L"L%x Instruction Cache",
                          CacheLevel);
  } else if (CacheLevel == CpuCacheL1 && DataCache) {
    CacheSocketStrLen = UnicodeSPrint (CacheSocketStr,
                          SMBIOS_STRING_MAX_LENGTH - 1,
                          L"L%x Data Cache",
                          CacheLevel);
  } else {
    CacheSocketStrLen = UnicodeSPrint (CacheSocketStr,
                          SMBIOS_STRING_MAX_LENGTH - 1,
                          L"L%x Cache",
                          CacheLevel);
  }

  return CacheSocketStrLen;
}

/** Fills in the Type 7 record with the cache architecture information
    read from the CPU registers.

  @param[in]  CacheLevel       Cache level (e.g. L1, L2).
  @param[in]  DataCache        Cache is a data cache.
  @param[in]  UnifiedCache     Cache is a unified cache.
  @param[out] Type7Record      The Type 7 record to fill in.

**/
VOID
ConfigureCacheArchitectureInformation (
  IN     UINT8                CacheLevel,
  IN     BOOLEAN              DataCache,
  IN     BOOLEAN              UnifiedCache,
  OUT    SMBIOS_TABLE_TYPE7   *Type7Record
  )
{
  UINT8        Associativity;
  UINT32       CacheSize32;
  UINT16       CacheSize16;
  UINT64       CacheSize64;

  if (!DataCache && !UnifiedCache) {
    Type7Record->SystemCacheType = CacheTypeInstruction;
  } else if (DataCache) {
    Type7Record->SystemCacheType = CacheTypeData;
  } else if (UnifiedCache) {
    Type7Record->SystemCacheType = CacheTypeUnified;
  } else {
    ASSERT(FALSE);
  }

  CacheSize64 = SmbiosProcessorGetCacheSize (CacheLevel,
                                             DataCache,
                                             UnifiedCache
                                             );

  Associativity = SmbiosProcessorGetCacheAssociativity (CacheLevel,
                                                        DataCache,
                                                        UnifiedCache
                                                        );

  CacheSize64 /= 1024; // Minimum granularity is 1K

  // Encode the cache size into the format SMBIOS wants
  if (CacheSize64 < MAX_INT16) {
    CacheSize16 = CacheSize64;
    CacheSize32 = CacheSize16;
  } else if ((CacheSize64 / 64) < MAX_INT16) {
    CacheSize16 = (1 << 15) | (CacheSize64 / 64);
    CacheSize32 = CacheSize16;
  } else {
    if ((CacheSize64 / 1024) <= 2047) {
      CacheSize32 = CacheSize64;
    } else {
      CacheSize32 = (1 << 31) | (CacheSize64 / 64);
    }

    CacheSize16 = -1;
  }

  Type7Record->MaximumCacheSize = CacheSize16;
  Type7Record->InstalledSize = CacheSize16;
  Type7Record->MaximumCacheSize2 = CacheSize32;
  Type7Record->InstalledSize2 = CacheSize32;

  switch (Associativity) {
    case 2:
      Type7Record->Associativity = CacheAssociativity2Way;
      break;
    case 4:
      Type7Record->Associativity = CacheAssociativity4Way;
      break;
    case 8:
      Type7Record->Associativity = CacheAssociativity8Way;
      break;
    case 12:
      Type7Record->Associativity = CacheAssociativity12Way;
      break;
    case 16:
      Type7Record->Associativity = CacheAssociativity16Way;
      break;
    case 20:
      Type7Record->Associativity = CacheAssociativity20Way;
      break;
    case 24:
      Type7Record->Associativity = CacheAssociativity24Way;
      break;
    case 32:
      Type7Record->Associativity = CacheAssociativity32Way;
      break;
    case 48:
      Type7Record->Associativity = CacheAssociativity48Way;
      break;
    case 64:
      Type7Record->Associativity = CacheAssociativity64Way;
      break;
    default:
      Type7Record->Associativity = CacheAssociativityOther;
      break;
  }

  Type7Record->CacheConfiguration = (CacheModeUnknown << CACHE_OPERATION_MODE_SHIFT) |
                                    (1 << CACHE_ENABLED_SHIFT) |
                                    (CacheLocationUnknown << CACHE_LOCATION_SHIFT) |
                                    (0 << CACHE_SOCKETED_SHIFT) |
                                    (CacheLevel - 1);
}


/** Allocates and initializes an SMBIOS_TABLE_TYPE7 structure.

  @param[in]  CacheLevel       The cache level (L1-L7).
  @param[in]  DataCache        Cache is a data cache.
  @param[in]  UnifiedCache     Cache is a unified cache.

  @return A pointer to the Type 7 structure. Returns NULL on failure.
**/
SMBIOS_TABLE_TYPE7 *
AllocateAndInitCacheInformation (
  IN UINT8   CacheLevel,
  IN BOOLEAN DataCache,
  IN BOOLEAN UnifiedCache
  )
{
  SMBIOS_TABLE_TYPE7  *Type7Record;
  EFI_STRING          CacheSocketStr;
  UINTN               CacheSocketStrLen;
  UINTN               StringBufferSize;
  CHAR8               *OptionalStrStart;
  UINTN               TableSize;

  // Allocate and fetch the cache description
  StringBufferSize = sizeof (CHAR16) * SMBIOS_STRING_MAX_LENGTH;
  CacheSocketStr = AllocateZeroPool (StringBufferSize);
  if (CacheSocketStr == NULL) {
    return NULL;
  }

  CacheSocketStrLen = GetCacheSocketStr (CacheLevel,
                                         DataCache,
                                         UnifiedCache,
                                         CacheSocketStr);

  TableSize = sizeof (SMBIOS_TABLE_TYPE7) + CacheSocketStrLen + 1 + 1;
  Type7Record = AllocateZeroPool (TableSize);
  if (Type7Record == NULL) {
    FreePool(CacheSocketStr);
    return NULL;
  }

  Type7Record->Hdr.Type = EFI_SMBIOS_TYPE_CACHE_INFORMATION;
  Type7Record->Hdr.Length = sizeof (SMBIOS_TABLE_TYPE7);
  Type7Record->Hdr.Handle = SMBIOS_HANDLE_PI_RESERVED;

  Type7Record->SocketDesignation = 1;

  Type7Record->SupportedSRAMType.Unknown = 1;
  Type7Record->CurrentSRAMType.Unknown = 1;
  Type7Record->CacheSpeed = 0;
  Type7Record->ErrorCorrectionType = CacheErrorUnknown;

  OptionalStrStart = (CHAR8 *)(Type7Record + 1);
  UnicodeStrToAsciiStrS (CacheSocketStr, OptionalStrStart, CacheSocketStrLen + 1);
  FreePool (CacheSocketStr);

  return Type7Record;
}

/**
  Add Type 7 SMBIOS Record for Cache Information.

  @param[in]    ProcessorIndex      Processor number of specified processor.
  @param[out]   L1CacheHandle       Pointer to the handle of the L1 Cache SMBIOS record.
  @param[out]   L2CacheHandle       Pointer to the handle of the L2 Cache SMBIOS record.
  @param[out]   L3CacheHandle       Pointer to the handle of the L3 Cache SMBIOS record.

**/
VOID
AddSmbiosCacheTypeTable (
  IN UINTN                  ProcessorIndex,
  OUT EFI_SMBIOS_HANDLE     *L1CacheHandle,
  OUT EFI_SMBIOS_HANDLE     *L2CacheHandle,
  OUT EFI_SMBIOS_HANDLE     *L3CacheHandle
  )
{
  EFI_STATUS                  Status;
  SMBIOS_TABLE_TYPE7          *Type7Record;
  EFI_SMBIOS_HANDLE           SmbiosHandle;
  UINT8                       CacheLevel;
  UINT8                       MaxCacheLevel;
  BOOLEAN                     DataCacheType;
  BOOLEAN                     SeparateCaches;

  Status = EFI_SUCCESS;

  MaxCacheLevel = 0;

  // See if there's an L1 cache present.
  MaxCacheLevel = SmbiosProcessorGetMaxCacheLevel ();

  if (MaxCacheLevel < 1) {
    return;
  }

  for (CacheLevel = 1; CacheLevel <= MaxCacheLevel; CacheLevel++) {
    Type7Record = NULL;

    SeparateCaches = SmbiosProcessorHasSeparateCaches (CacheLevel);

    // At each level of cache, we can have a single type (unified, instruction or data),
    // or two types - separate data and instruction caches. If we have separate
    // instruction and data caches, then on the first iteration (CacheSubLevel = 0)
    // process the instruction cache.
    for (DataCacheType = 0; DataCacheType <= 1; DataCacheType++) {
      // If there's no separate data/instruction cache, skip the second iteration
      if (DataCacheType == 1 && !SeparateCaches) {
        continue;
      }

      Type7Record = AllocateAndInitCacheInformation (CacheLevel,
                                                     DataCacheType,
                                                     !SeparateCaches
                                                     );
      if (Type7Record == NULL) {
        continue;
      }

      ConfigureCacheArchitectureInformation(CacheLevel,
                                            DataCacheType,
                                            !SeparateCaches,
                                            Type7Record
                                            );

      // Allow the platform to fill in other information such as speed, SRAM type etc.
      if (!OemGetCacheInformation (ProcessorIndex, CacheLevel,
                                   DataCacheType, !SeparateCaches, Type7Record)) {
        continue;
      }

      SmbiosHandle = SMBIOS_HANDLE_PI_RESERVED;
      // Finally, install the table
      Status = mSmbios->Add (mSmbios, NULL, &SmbiosHandle,
                             (EFI_SMBIOS_TABLE_HEADER *)Type7Record);
      if (EFI_ERROR (Status)) {
        continue;
      }

      // Config L1/L2/L3 Cache Handle
      switch (CacheLevel) {
        case CpuCacheL1:
          *L1CacheHandle = SmbiosHandle;
          break;
        case CpuCacheL2:
          *L2CacheHandle = SmbiosHandle;
          break;
        case CpuCacheL3:
          *L3CacheHandle = SmbiosHandle;
          break;
        default:
          break;
      }
    }
  }
}

/** Allocates a Type 4 Processor Information structure and sets the
    strings following the data fields.

  @param[out] Type4Record    The Type 4 structure to allocate and initialize
  @param[in]  ProcessorIndex The index of the processor
  @param[in]  Populated      Whether the specified processor is
                             populated.

  @retval EFI_SUCCESS          The Type 4 structure was successfully
                               allocated and the strings initialized.
  @retval EFI_OUT_OF_RESOURCES Could not allocate memory needed.
**/
EFI_STATUS
AllocateType4AndSetProcessorInformationStrings (
  SMBIOS_TABLE_TYPE4 **Type4Record,
  UINT8 ProcessorIndex,
  BOOLEAN Populated
  )
{
  EFI_STATUS      Status;
  EFI_STRING_ID   ProcessorManu;
  EFI_STRING_ID   ProcessorVersion;
  EFI_STRING_ID   SerialNumber;
  EFI_STRING_ID   AssetTag;
  EFI_STRING_ID   PartNumber;
  EFI_STRING      ProcessorStr;
  EFI_STRING      ProcessorManuStr;
  EFI_STRING      ProcessorVersionStr;
  EFI_STRING      SerialNumberStr;
  EFI_STRING      AssetTagStr;
  EFI_STRING      PartNumberStr;
  CHAR8           *OptionalStrStart;
  CHAR8           *StrStart;
  UINTN           ProcessorStrLen;
  UINTN           ProcessorManuStrLen;
  UINTN           ProcessorVersionStrLen;
  UINTN           SerialNumberStrLen;
  UINTN           AssetTagStrLen;
  UINTN           PartNumberStrLen;
  UINTN           TotalSize;
  UINTN           StringBufferSize;

  Status = EFI_SUCCESS;

  ProcessorManuStr    = NULL;
  ProcessorVersionStr = NULL;
  SerialNumberStr     = NULL;
  AssetTagStr         = NULL;
  PartNumberStr       = NULL;

  ProcessorManu       = STRING_TOKEN (STR_PROCESSOR_MANUFACTURE);
  ProcessorVersion    = STRING_TOKEN (STR_PROCESSOR_VERSION);
  SerialNumber        = STRING_TOKEN (STR_PROCESSOR_SERIAL_NUMBER);
  AssetTag            = STRING_TOKEN (STR_PROCESSOR_ASSET_TAG);
  PartNumber          = STRING_TOKEN (STR_PROCESSOR_PART_NUMBER);

  SET_HII_STRING_IF_PCD_NOT_EMPTY (PcdProcessorManufacturer, ProcessorManu);
  SET_HII_STRING_IF_PCD_NOT_EMPTY (PcdProcessorVersion, ProcessorVersion);
  SET_HII_STRING_IF_PCD_NOT_EMPTY (PcdProcessorSerialNumber, SerialNumber);
  SET_HII_STRING_IF_PCD_NOT_EMPTY (PcdProcessorAssetTag, AssetTag);
  SET_HII_STRING_IF_PCD_NOT_EMPTY (PcdProcessorPartNumber, PartNumber);

  // Processor Designation
  StringBufferSize = sizeof (CHAR16) * SMBIOS_STRING_MAX_LENGTH;
  ProcessorStr = AllocateZeroPool (StringBufferSize);
  if (ProcessorStr == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  ProcessorStrLen = UnicodeSPrint (ProcessorStr, StringBufferSize,
                                         L"CPU%02d", ProcessorIndex + 1);

  // Processor Manufacture
  ProcessorManuStr = HiiGetPackageString (&gEfiCallerIdGuid, ProcessorManu, NULL);
  ProcessorManuStrLen = StrLen (ProcessorManuStr);

  // Processor Version
  ProcessorVersionStr = HiiGetPackageString (&gEfiCallerIdGuid, ProcessorVersion, NULL);
  ProcessorVersionStrLen = StrLen (ProcessorVersionStr);

  // Serial Number
  SerialNumberStr = HiiGetPackageString (&gEfiCallerIdGuid, SerialNumber, NULL);
  SerialNumberStrLen = StrLen (SerialNumberStr);

  // Asset Tag
  AssetTagStr = HiiGetPackageString (&gEfiCallerIdGuid, AssetTag, NULL);
  AssetTagStrLen = StrLen (AssetTagStr);

  // Part Number
  PartNumberStr = HiiGetPackageString (&gEfiCallerIdGuid, PartNumber, NULL);
  PartNumberStrLen = StrLen (PartNumberStr);

  TotalSize = sizeof (SMBIOS_TABLE_TYPE4) +
              ProcessorStrLen        + 1 +
              ProcessorManuStrLen    + 1 +
              ProcessorVersionStrLen + 1 +
              SerialNumberStrLen     + 1 +
              AssetTagStrLen         + 1 +
              PartNumberStrLen       + 1 + 1;

  *Type4Record = AllocateZeroPool (TotalSize);
  if (*Type4Record == NULL) {
    Status = EFI_OUT_OF_RESOURCES;
    goto Exit;
  }

  CopyMem (*Type4Record, &mSmbiosProcessorTableTemplate, sizeof (SMBIOS_TABLE_TYPE4));

  OptionalStrStart = (CHAR8 *)(*Type4Record + 1);
  UnicodeStrToAsciiStrS (
    ProcessorStr,
    OptionalStrStart,
    ProcessorStrLen + 1
    );

  StrStart = OptionalStrStart + ProcessorStrLen + 1;
  UnicodeStrToAsciiStrS (
    ProcessorManuStr,
    StrStart,
    ProcessorManuStrLen + 1
    );

  StrStart += ProcessorManuStrLen + 1;
  UnicodeStrToAsciiStrS (
    ProcessorVersionStr,
    StrStart,
    ProcessorVersionStrLen + 1
    );

  StrStart += ProcessorVersionStrLen + 1;
  UnicodeStrToAsciiStrS (
    SerialNumberStr,
    StrStart,
    SerialNumberStrLen + 1
    );

  StrStart += SerialNumberStrLen + 1;
  UnicodeStrToAsciiStrS (
    AssetTagStr,
    StrStart,
    AssetTagStrLen + 1
    );

  StrStart += AssetTagStrLen + 1;
  UnicodeStrToAsciiStrS (
    PartNumberStr,
    StrStart,
    PartNumberStrLen + 1
    );

Exit:
  FreePool (ProcessorStr);
  FreePool (ProcessorManuStr);
  FreePool (ProcessorVersionStr);
  FreePool (SerialNumberStr);
  FreePool (AssetTagStr);
  FreePool (PartNumberStr);

  return Status;
}

/**
  Add Type 4 SMBIOS Record for Processor Information.

  @param[in]    ProcessorIndex     Processor index of specified processor.

**/
EFI_STATUS
AddSmbiosProcessorTypeTable (
  IN UINTN                  ProcessorIndex
  )
{
  EFI_STATUS                  Status;
  SMBIOS_TABLE_TYPE4          *Type4Record;
  EFI_SMBIOS_HANDLE           SmbiosHandle;
  EFI_SMBIOS_HANDLE           L1CacheHandle;
  EFI_SMBIOS_HANDLE           L2CacheHandle;
  EFI_SMBIOS_HANDLE           L3CacheHandle;
  UINT8                       *LegacyVoltage;
  PROCESSOR_STATUS_DATA       ProcessorStatus;
  UINT64                      *ProcessorId;
  PROCESSOR_CHARACTERISTIC_FLAGS ProcessorCharacteristics;
  OEM_MISC_PROCESSOR_DATA     MiscProcessorData;
  BOOLEAN                     ProcessorPopulated;

  Type4Record         = NULL;

  MiscProcessorData.Voltage         = 0;
  MiscProcessorData.CurrentSpeed    = 0;
  MiscProcessorData.CoreCount       = 0;
  MiscProcessorData.CoresEnabled    = 0;
  MiscProcessorData.ThreadCount     = 0;
  MiscProcessorData.MaxSpeed        = 0;
  L1CacheHandle       = 0xFFFF;
  L2CacheHandle       = 0xFFFF;
  L3CacheHandle       = 0xFFFF;

  ProcessorPopulated = OemIsProcessorPresent (ProcessorIndex);

  Status = AllocateType4AndSetProcessorInformationStrings (
             &Type4Record,
             ProcessorIndex,
             ProcessorPopulated
             );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  OemGetProcessorInformation (ProcessorIndex,
                              &ProcessorStatus,
                              (PROCESSOR_CHARACTERISTIC_FLAGS*)
                                &Type4Record->ProcessorCharacteristics,
                              &MiscProcessorData);

  if (ProcessorPopulated) {
    AddSmbiosCacheTypeTable (ProcessorIndex, &L1CacheHandle,
                             &L2CacheHandle, &L3CacheHandle);
  }

  LegacyVoltage = (UINT8*)&Type4Record->Voltage;

  *LegacyVoltage                    = MiscProcessorData.Voltage;
  Type4Record->CurrentSpeed         = MiscProcessorData.CurrentSpeed;
  Type4Record->MaxSpeed             = MiscProcessorData.MaxSpeed;
  Type4Record->Status               = ProcessorStatus.Data;
  Type4Record->L1CacheHandle        = L1CacheHandle;
  Type4Record->L2CacheHandle        = L2CacheHandle;
  Type4Record->L3CacheHandle        = L3CacheHandle;
  Type4Record->CoreCount            = MiscProcessorData.CoreCount;
  Type4Record->CoreCount2           = MiscProcessorData.CoreCount;
  Type4Record->EnabledCoreCount     = MiscProcessorData.CoresEnabled;
  Type4Record->EnabledCoreCount2    = MiscProcessorData.CoresEnabled;
  Type4Record->ThreadCount          = MiscProcessorData.ThreadCount;
  Type4Record->ThreadCount2         = MiscProcessorData.ThreadCount;

  Type4Record->CurrentSpeed = GetCpuFrequency (ProcessorIndex);
  Type4Record->ExternalClock =
    (UINT16)(SmbiosGetExternalClockFrequency () / 1000 / 1000);

  ProcessorId = (UINT64*)&Type4Record->ProcessorId;
  *ProcessorId = SmbiosGetProcessorId ();

  ProcessorCharacteristics = SmbiosGetProcessorCharacteristics ();
  Type4Record->ProcessorCharacteristics |= *((UINT64*)&ProcessorCharacteristics);

  Type4Record->ProcessorFamily = SmbiosGetProcessorFamily ();
  Type4Record->ProcessorFamily2 = SmbiosGetProcessorFamily2 ();

  SmbiosHandle = SMBIOS_HANDLE_PI_RESERVED;
  Status = mSmbios->Add (mSmbios, NULL, &SmbiosHandle,
                         (EFI_SMBIOS_TABLE_HEADER *)Type4Record);

  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "[%a]:[%dL] Smbios Type04 Table Log Failed! %r \n",
            __FUNCTION__, __LINE__, Status));
  }
  FreePool (Type4Record);

  return Status;
}

/**
   Standard EFI driver point.

  @param  ImageHandle     Handle for the image of this driver
  @param  SystemTable     Pointer to the EFI System Table

  @retval  EFI_SUCCESS    The data was successfully stored.

**/
EFI_STATUS
EFIAPI
ProcessorSubClassEntryPoint(
  IN EFI_HANDLE         ImageHandle,
  IN EFI_SYSTEM_TABLE   *SystemTable
  )
{
  EFI_STATUS    Status;
  UINT32        ProcessorIndex;

  //
  // Locate dependent protocols
  //
  Status = gBS->LocateProtocol (&gEfiSmbiosProtocolGuid, NULL, (VOID**)&mSmbios);
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "Could not locate SMBIOS protocol.  %r\n", Status));
    return Status;
  }

  //
  // Add our default strings to the HII database. They will be modified later.
  //
  mHiiHandle = HiiAddPackages (&gEfiCallerIdGuid,
                               NULL,
                               ProcessorSubClassStrings,
                               NULL,
                               NULL
                              );
  if (mHiiHandle == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  //
  // Add SMBIOS tables for populated sockets.
  //
  for (ProcessorIndex = 0; ProcessorIndex < OemGetMaxProcessors (); ProcessorIndex++) {
    Status = AddSmbiosProcessorTypeTable (ProcessorIndex);
    if (EFI_ERROR (Status)) {
      DEBUG ((DEBUG_ERROR, "Add Processor Type Table Failed!  %r.\n", Status));
      return Status;
    }
  }

  return Status;
}
