Newer
Older
AMI-Aptio-BIOS-Reversed / OemErrorLogDxe / OemErrorLogDxe.c
@Ajax Dong Ajax Dong 2 days ago 25 KB Init
/** @file
  OemErrorLogDxe - HR650X BIOS OEM Error Log DXE Driver

  This DXE driver registers to check CPU microcode version, stepping, and
  frequency consistency across all Application Processors (APs) vs. the
  Boot Strap Processor (BSP). When mismatches are detected, it sends an
  IPMI SEL (System Event Log) entry via the IPMI transport protocol.

  Copyright (C) Lenovo
  SPDX-License-Identifier: BSD-2-Clause-Patent

  File:   OemErrorLogDxe.c
  Binary: OemErrorLogDxe.efi (Index 0090)
**/

#include "OemErrorLogDxe.h"

//
// EFI global protocol/table pointers
//
EFI_HANDLE            gImageHandle   = NULL;
EFI_SYSTEM_TABLE      *gSystemTable  = NULL;
EFI_BOOT_SERVICES     *gBootServices = NULL;
EFI_RUNTIME_SERVICES  *gRuntimeServices = NULL;

//
// Module globals
//
EFI_MP_SERVICES_PROTOCOL  *gMpService          = NULL;   // qword_1698
UINT32                    *gMicrocodeVersions   = NULL;   // qword_1690
UINT32                    *gSteppingValues      = NULL;   // qword_1688
UINT32                    *gFrequencies         = NULL;   // qword_1680
VOID                      *gHobList             = NULL;   // qword_16C8
VOID                      *gIpmiTransport       = NULL;   // qword_16D0
VOID                      *gDebugPrintProtocol  = NULL;   // qword_16C0
UINT8                     gErrorLogBMCBus       = 0;      // n3 at 0x16D8

//
// GUID definitions (from .data section at 0x1640)
//
// gEfiMpServiceProtocolGuid      = { 3FDDA605-A76E-4F46-AD29-12F4531B3D08 }
// GUID at 0x1650                 = { 36232936-0E76-31C8-A13A-3AF2FC1C3932 } (EFI_HOB_LIST_GUID)
// GUID at 0x1660 (unk_1660)      = { 4A1D0E66-5271-4E22-83FE-90921B748213 } (gEfiIpmiTransportGuid)
//

/**
  CPUID wrapper.

  Executes the CPUID instruction with the given leaf and optionally stores
  the resulting register values.

  @param[in]  Leaf        CPUID leaf index.
  @param[out] Eax         Optional pointer to store EAX.
  @param[out] Ebx         Optional pointer to store EBX.
  @param[out] Ecx         Optional pointer to store ECX.
  @param[out] Edx         Optional pointer to store EDX.

  @return The leaf value.
**/
UINT64
EFIAPI
CpuId (
  IN  UINT32  Leaf,
  OUT UINT32  *Eax OPTIONAL,
  OUT UINT32  *Ebx OPTIONAL,
  OUT UINT32  *Ecx OPTIONAL,
  OUT UINT32  *Edx OPTIONAL
  )
{
  UINT32  LocalEax;
  UINT32  LocalEbx;
  UINT32  LocalEcx;
  UINT32  LocalEdx;

  AsmCpuid (Leaf, &LocalEax, &LocalEbx, &LocalEcx, &LocalEdx);

  if (Eax != NULL) {
    *Eax = LocalEax;
  }
  if (Ebx != NULL) {
    *Ebx = LocalEbx;
  }
  if (Ecx != NULL) {
    *Ecx = LocalEcx;
  }
  if (Edx != NULL) {
    *Edx = LocalEdx;
  }

  return Leaf;
}

/**
  Zero memory (replacement for ZeroMem with additional safety checks).

  @param[in] Buffer  Pointer to buffer to zero.
  @param[in] Size    Size in bytes.

  @return Pointer to the buffer.
**/
VOID *
EFIAPI
ZeroMemOrNull (
  OUT VOID    *Buffer,
  IN  UINTN   Size
  )
{
  ASSERT (Buffer != NULL);
  ASSERT (Size <= (MAX_UINTN - (UINTN)Buffer + 1));
  ZeroMem (Buffer, Size);
  return Buffer;
}

/**
  Read a 64-bit value from an unaligned pointer.

  @param[in] Buffer  Pointer to read from.

  @return The 64-bit value read.
**/
UINT64
EFIAPI
ReadUnaligned64Wrapper (
  IN VOID   *Buffer
  )
{
  ASSERT (Buffer != NULL);
  return ReadUnaligned64 (Buffer);
}

/**
  Compare two GUIDs by reading their first QWORD pair.

  @param[in] Guid1  First GUID.
  @param[in] Guid2  Second GUID.

  @retval TRUE   GUIDs match.
  @retval FALSE  GUIDs do not match.
**/
BOOLEAN
EFIAPI
CompareGuidByQwords (
  IN EFI_GUID  *Guid1,
  IN EFI_GUID  *Guid2
  )
{
  UINT64  Qword1a;
  UINT64  Qword1b;
  UINT64  Qword2a;
  UINT64  Qword2b;

  Qword1a = ReadUnaligned64Wrapper (Guid1);
  Qword1b = ReadUnaligned64Wrapper ((UINT8 *)Guid1 + 8);
  Qword2a = ReadUnaligned64Wrapper (Guid2);
  Qword2b = ReadUnaligned64Wrapper ((UINT8 *)Guid2 + 8);

  return (BOOLEAN)(Qword1a == Qword2a && Qword1b == Qword2b);
}

/**
  Allocate pool wrapper. Returns NULL on allocation failure.

  @param[in] AllocationSize  Size to allocate in bytes.

  @return Pointer to allocated buffer, or NULL on failure.
**/
VOID *
EFIAPI
AllocatePoolOrNull (
  IN UINTN   AllocationSize
  )
{
  VOID        *Buffer;
  EFI_STATUS  Status;

  Status = gBootServices->AllocatePool (EfiBootServicesData, AllocationSize, &Buffer);
  if (EFI_ERROR (Status)) {
    return NULL;
  }

  return Buffer;
}

/**
  Free pool with debug assertion.

  @param[in] Buffer  Pointer to buffer to free.

  @retval EFI_SUCCESS  Buffer was freed.
**/
EFI_STATUS
EFIAPI
FreePoolChecked (
  IN VOID   *Buffer
  )
{
  EFI_STATUS  Status;

  Status = gBootServices->FreePool (Buffer);
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "\nASSERT_EFI_ERROR (Status = %r)\n", Status));
    ASSERT (!EFI_ERROR (Status));
  }

  return Status;
}

/**
  Assertion failure handler (debug print).

  Prints the filename, line number, and assertion description via the
  debug print protocol.

  @param[in] FileName     Source file name.
  @param[in] LineNumber   Line number of the assertion.
  @param[in] Description  Assertion description string.
**/
VOID
EFIAPI
DebugAssertWorker (
  IN CONST CHAR8    *FileName,
  IN UINTN          LineNumber,
  IN CONST CHAR8    *Description
  )
{
  if (gDebugPrintProtocol != NULL) {
    ((DEBUG_PRINT_PROTOCOL *)gDebugPrintProtocol)->DebugAssert (FileName, LineNumber, Description);
  }
}

/**
  Initialize debug print protocol (locate gEfiDebugPrintProtocolGuid on demand).

  @retval Pointer to debug print protocol, or NULL if not found.
**/
VOID *
EFIAPI
GetDebugPrintProtocol (
  VOID
  )
{
  EFI_STATUS  Status;

  if (gDebugPrintProtocol == NULL) {
    //
    // Check if running in SMM or similar restricted context (< 0x10 pages free)
    //
    if (gBootServices->GetBootMode () == BOOT_ON_S3_RESUME) {
      return NULL;
    }

    Status = gBootServices->LocateProtocol (
                              &gEfiDebugPrintProtocolGuid,
                              NULL,
                              &gDebugPrintProtocol
                              );
    if (EFI_ERROR (Status)) {
      gDebugPrintProtocol = NULL;
    }
  }

  return gDebugPrintProtocol;
}

/**
  Debug print wrapper using port 0x70/0x71 and serial.

  This function checks the error level against the platform's current
  debug level (read via CMOS ports 0x70/0x71 or a fixed memory address)
  and prints the debug message via the debug print protocol if the level
  is enabled.

  @param[in]  ErrorLevel  Debug error level.
  @param[in]  Format      Print format string.
  @param[in]  ...         Variable arguments for format string.

  @return 0 if message was not printed, non-zero if it was.
**/
UINT8
EFIAPI
DebugPrintWrapper (
  IN UINTN           ErrorLevel,
  IN CONST CHAR8     *Format,
  ...
  )
{
  UINT8       DebugLevel;
  UINTN       EnabledMask;
  VOID        *DebugProtocol;
  VA_LIST     VaList;
  UINT8       Result;

  DebugProtocol = GetDebugPrintProtocol ();
  if (DebugProtocol == NULL) {
    return 0;
  }

  //
  // Read current debug level from CMOS
  //
  IoWrite8 (0x70, (IoRead8 (0x70) & 0x80) | 0x4B);
  DebugLevel = IoRead8 (0x71);

  if (DebugLevel > 3) {
    if (DebugLevel == 0) {
      //
      // Fallback: read debug mask from fixed memory location
      //
      DebugLevel = (*(volatile UINT8 *)(UINTN)0xFDAF0490) & 2 | 1;
    }
  }

  EnabledMask = 0;
  if ((DebugLevel - 1) <= 0xFD) {
    EnabledMask = 0x80000006;
    if (DebugLevel == 1) {
      EnabledMask = 0x80000004;
    }
  }

  if ((EnabledMask & ErrorLevel) != 0) {
    VA_START (VaList, Format);
    Result = ((DEBUG_PRINT_PROTOCOL *)DebugProtocol)->DebugPrint (
                                                       ErrorLevel,
                                                       Format,
                                                       VaList
                                                       );
    VA_END (VaList);
    return Result;
  }

  return 0;
}

/**
  Override for DEBUG() macro via DebugPrintWrapper.

  We redefine the macro in the build system; this function exists so
  the original ASSERT_EFI_ERROR strings are redirected through the
  CMOS-aware print wrapper.
**/
VOID
EFIAPI
DebugPrintCmosAware (
  IN UINTN           ErrorLevel,
  IN CONST CHAR8     *Format,
  ...
  )
{
  VA_LIST  VaList;

  VA_START (VaList, Format);
  DebugPrintWrapper (ErrorLevel, Format, VaList);
  VA_END (VaList);
}

/**
  Get HOB list pointer (equivalent of GetHobList from DxeHobLib).

  Scans the system table's HOB list entries to find the HOB list pointer
  by matching the HOB list GUID.

  @return Pointer to the HOB list, or NULL if not found.
**/
VOID *
EFIAPI
GetHobList (
  VOID
  )
{
  UINTN   Index;
  UINTN   NumberOfTableEntries;
  EFI_CONFIGURATION_TABLE  *ConfigTable;
  UINT64   *HobGuid;

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

  gHobList = NULL;
  NumberOfTableEntries = gSystemTable->NumberOfTableEntries;
  ConfigTable          = gSystemTable->ConfigurationTable;

  for (Index = 0; Index < NumberOfTableEntries; Index++) {
    if (CompareGuidByQwords (
          &ConfigTable[Index].VendorGuid,
          (EFI_GUID *)(UINTN)0x1670   // gEfiHobListGuid
          ))
    {
      gHobList = ConfigTable[Index].VendorTable;
      break;
    }
  }

  if (gHobList == NULL) {
    DEBUG ((DEBUG_ERROR, "\nASSERT_EFI_ERROR (Status = %r)\n", EFI_NOT_FOUND));
    ASSERT (!EFI_ERROR (EFI_NOT_FOUND));
    ASSERT (gHobList != NULL);
  }

  return gHobList;
}

/**
  Locate IPMI transport protocol and store in gIpmiTransport.

  @retval EFI_SUCCESS  IPMI transport protocol located.
  @retval other        LocateProtocol failed status.
**/
EFI_STATUS
EFIAPI
LocateIpmiTransport (
  VOID
  )
{
  EFI_STATUS  Status;

  Status = gBootServices->LocateProtocol (
                            (EFI_GUID *)(UINTN)0x1660,   // gEfiIpmiTransportProtocolGuid
                            NULL,
                            &gIpmiTransport
                            );
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "\nASSERT_EFI_ERROR (Status = %r)\n", Status));
    ASSERT (!EFI_ERROR (Status));
  }

  return Status;
}

//
// IPMI command data structures for CPU mismatch SEL
//
#pragma pack(1)
typedef struct {
  UINT8   NetFn;
  UINT8   Cmd;
  UINT8   CompletionCode;
} IPMI_RESPONSE_HEADER;

typedef struct {
  UINT8   GeneratorId;        // 0x20 = BIOS
  UINT8   EvMRevision;        // 0x04
  UINT8   SensorType;         // 0x02 = Processor
  UINT8   SensorNumber;       // 0x72
  UINT8   EventDirType;       // Event Dir (bit 7) | Event Type
  UINT8   EventData1;         // 0x6F = OEM specific / Sensor-specific offset
  UINT8   EventData2;         // Flags: bit 4=microcode, bit 0=freq, bit 3=stepping
  UINT8   EventData3;         // 0x44 = OEM byte
} IPMI_CPU_MISMATCH_SEL_RECORD;
#pragma pack()

/**
  Check BMC bus number and determine whether to log the error.
  Sends a IPMI command to check the platform's current BMC channel
  or reads the "Setup" UEFI variable.

  @param[out] Bus  Pointer to receive the bus number.

  @retval EFI_SUCCESS           Bus number obtained.
  @retval EFI_NOT_FOUND         IPMI protocol not available.
  @retval EFI_UNSUPPORTED       Not supported.
**/
EFI_STATUS
EFIAPI
GetBmcLogBus (
  OUT UINT8   *Bus
  )
{
  EFI_STATUS              Status;
  IPMI_CPU_MISMATCH_SEL_RECORD  *IpmiCmd;
  UINT8                   ResponseData;
  UINT32                  ResponseSize;
  UINT8                   *VarData;
  UINTN                   VarDataSize;
  UINT8                   BusValue;

  *Bus = 0xFF;

  if (gIpmiTransport != NULL) {
    //
    // Send IPMI command 0x2E (0x46, 0x01) to IPMI transport
    //
    IpmiCmd = NULL;  // command data: {0x02}, length 1
    ResponseSize = sizeof (ResponseData);

    Status = ((IPMI_TRANSPORT_PROTOCOL *)gIpmiTransport)->SendIpmiCommand (
                                                            (IPMI_TRANSPORT_PROTOCOL *)gIpmiTransport,
                                                            0x2E,           // NetFn
                                                            0,              // LUN
                                                            0x01,           // Command
                                                            (UINT8 *)IpmiCmd,
                                                            1,              // Request length
                                                            &ResponseData,
                                                            &ResponseSize
                                                            );
    if (!EFI_ERROR (Status)) {
      BusValue = ResponseData;
      goto Done;
    }
  }

  //
  // Fallback: read "Setup" UEFI variable via RuntimeServices
  //
  VarData     = NULL;
  VarDataSize = 0;
  Status = gRuntimeServices->GetVariable (
                               L"Setup",
                               (EFI_GUID *)(UINTN)0x1678,   // Setup variable GUID
                               NULL,
                               &VarDataSize,
                               NULL
                               );

  if (Status == EFI_BUFFER_TOO_SMALL) {
    VarData = (UINT8 *)AllocatePoolOrNull (VarDataSize);
    if (VarData != NULL) {
      Status = gRuntimeServices->GetVariable (
                                   L"Setup",
                                   (EFI_GUID *)(UINTN)0x1678,
                                   NULL,
                                   &VarDataSize,
                                   VarData
                                   );
      if (!EFI_ERROR (Status)) {
        BusValue = VarData[284];   // Offset 284 in Setup variable = BMC bus
      }
      FreePool (VarData);
    }
  }

  if (EFI_ERROR (Status)) {
    *Bus = 0xFF;
    return EFI_UNSUPPORTED;
  }

Done:
  *Bus = BusValue;
  return EFI_SUCCESS;
}

/**
  AP worker function that collects CPU information.

  Called on each AP via EFI_MP_SERVICES_PROTOCOL.StartupAllAPs.
  Reads the microcode version (MSR 0x8B), stepping (CPUID), and
  frequency (MSR 0xCE) and stores them in the global arrays.

  @param[in] Buffer  Not used (required by MP Services API).
**/
VOID
EFIAPI
ApCollectCpuInfo (
  IN VOID  *Buffer
  )
{
  UINT32  ProcessorIndex;
  UINT32  Eax;
  UINT32  Ebx;
  UINT32  Ecx;
  UINT32  Edx;
  UINT64  MicrocodeMsr;
  UINT64  FreqMsr;

  //
  // Get current processor number from MP services
  //
  gMpService->WhoAmI (gMpService, &ProcessorIndex);

  //
  // Read microcode version from MSR 0x8B
  //
  AsmWriteMsr64 (0x8B, 0);
  CpuId (1, (UINT32 *)&Eax, (UINT32 *)&Ebx, (UINT32 *)&Ecx, (UINT32 *)&Edx);
  MicrocodeMsr = AsmReadMsr64 (0x8B);
  if (gMicrocodeVersions != NULL) {
    gMicrocodeVersions[ProcessorIndex] = (UINT32)(MicrocodeMsr >> 32);
  }

  //
  // Read stepping from CPUID (EAX[3:0] after CPUID leaf 1)
  //
  if (gSteppingValues != NULL) {
    CpuId (1, &Eax, NULL, NULL, NULL);
    gSteppingValues[ProcessorIndex] = Eax & 0xF;
  }

  //
  // Read frequency from MSR 0xCE (Platform Info MSR)
  //
  if (gFrequencies != NULL) {
    FreqMsr = AsmReadMsr64 (0xCE);
    //
    // Byte 1 of MSR 0xCE contains the max non-turbo ratio in 100 MHz units
    //
    gFrequencies[ProcessorIndex] = (UINT32)((10000 * (UINT8)((FreqMsr >> 8) & 0xFF)) / 100);
  }
}

/**
  Main CPU information check and notification handler.

  This function:
  1. Locates the MP Services protocol.
  2. Allocates arrays for microcode, stepping, and frequency data.
  3. Collects BSP data by calling ApCollectCpuInfo once.
  4. Starts APs via StartupAllAPs to collect their data.
  5. Compares all AP data against BSP.
  6. If mismatches found, sends an IPMI SEL.

  @param[in] Event    Event that triggered the notification.
  @param[in] Context  Context passed to the callback.

  @retval EFI_SUCCESS  Check completed.
  @retval other        Error from protocol or allocation.
**/
EFI_STATUS
EFIAPI
OemCheckCpuInfoNotify (
  IN EFI_EVENT    Event,
  IN VOID         *Context
  )
{
  EFI_STATUS              Status;
  EFI_MP_SERVICES_PROTOCOL  *MpService;
  UINTN                   NumberOfProcessors;
  UINTN                   NumberOfEnabledProcessors;
  UINTN                   Index;
  UINT32                  BspMicrocode;
  UINT32                  BspStepping;
  UINT32                  BspFrequency;
  BOOLEAN                 MicrocodeMismatch;
  BOOLEAN                 FrequencyMismatch;
  BOOLEAN                 SteppingMismatch;
  UINTN                   BufferSize;
  IPMI_CPU_MISMATCH_SEL_RECORD  SelRecord;
  IPMI_TRANSPORT_PROTOCOL       *IpmiTransport;
  UINT8                   ResponseData;
  UINT32                  ResponseSize;
  UINT8                   BusValue;

  DEBUG_PRINT_CMOS (DEBUG_INFO, "OemCheckCpuInfoNotify start ...\n");

  //
  // 1. Locate MP Services Protocol
  //
  Status = gBootServices->LocateProtocol (
                            &gEfiMpServiceProtocolGuid,
                            NULL,
                            (VOID **)&MpService
                            );
  if (EFI_ERROR (Status)) {
    DEBUG_PRINT_CMOS (DEBUG_INFO,
      "OemCheckCpuInfoNotify: locate gEfiMpServiceProtocolGuid fail Status: %r\n",
      Status);
    return Status;
  }
  gMpService = MpService;

  //
  // 2. Enable watchdog (optional)
  //
  gBootServices->Stall (1000);             // placeholder for watchdog timer

  //
  // 3. Get processor count
  //
  MpService->GetNumberOfProcessors (MpService, &NumberOfProcessors, &NumberOfEnabledProcessors);

  if (NumberOfProcessors <= 1) {
    return EFI_SUCCESS;
  }

  BufferSize = sizeof (UINT32) * NumberOfProcessors;

  //
  // 4. Allocate arrays
  //
  gMicrocodeVersions = AllocatePoolOrNull (BufferSize);
  if (gMicrocodeVersions != NULL) {
    ZeroMemOrNull (gMicrocodeVersions, BufferSize);
  }

  gSteppingValues = AllocatePoolOrNull (BufferSize);
  if (gSteppingValues != NULL) {
    ZeroMemOrNull (gSteppingValues, BufferSize);
  }

  gFrequencies = AllocatePoolOrNull (BufferSize);
  if (gFrequencies != NULL) {
    ZeroMemOrNull (gFrequencies, BufferSize);
  }

  //
  // 5. Collect BSP data first, then all APs
  //
  ApCollectCpuInfo (NULL);

  Status = MpService->StartupAllAPs (
                        MpService,
                        ApCollectCpuInfo,
                        FALSE,        // SingleThread
                        NULL,         // WaitEvent
                        0,            // TimeoutInMicroseconds (infinite)
                        NULL,         // ProcedureArgument
                        NULL          // FailedCpuList
                        );
  if (EFI_ERROR (Status)) {
    DEBUG_PRINT_CMOS (DEBUG_INFO,
      "OemCheckCpuInfoNotify: StartupAllAPs fail Status: %r\n",
      Status);
    return Status;
  }

  //
  // 6. Compare each AP against BSP
  //
  MicrocodeMismatch = FALSE;
  FrequencyMismatch = FALSE;
  SteppingMismatch  = FALSE;

  DEBUG_PRINT_CMOS (DEBUG_INFO, "BSP Information\n");
  DEBUG_PRINT_CMOS (DEBUG_INFO, "Microcode version: 0x%x\n", gMicrocodeVersions[0]);
  DEBUG_PRINT_CMOS (DEBUG_INFO, "Stepping:          0x%x\n", gSteppingValues[0]);
  DEBUG_PRINT_CMOS (DEBUG_INFO, "Frequency(MHz):    %d\n", gFrequencies[0]);

  for (Index = 0; Index < NumberOfProcessors; Index++) {
    if (gMicrocodeVersions[0] != gMicrocodeVersions[Index]) {
      DEBUG_PRINT_CMOS (DEBUG_INFO,
        "AP%d microcode version (0x%x) is different from BSP\n",
        Index,
        gMicrocodeVersions[Index]);
      MicrocodeMismatch = TRUE;
    }

    if (gFrequencies[0] != gFrequencies[Index]) {
      DEBUG_PRINT_CMOS (DEBUG_INFO,
        "AP%d frequency (%d MHz) is different from BSP\n",
        Index,
        gFrequencies[Index]);
      FrequencyMismatch = TRUE;
    }

    if (gSteppingValues[0] != gSteppingValues[Index]) {
      DEBUG_PRINT_CMOS (DEBUG_INFO,
        "AP%d stepping (0x%x) is different from BSP\n",
        Index,
        gSteppingValues[Index]);
      SteppingMismatch = TRUE;
    }
  }

  //
  // 7. Free allocated arrays
  //
  if (gMicrocodeVersions != NULL) {
    FreePoolChecked (gMicrocodeVersions);
    gMicrocodeVersions = NULL;
  }

  if (gFrequencies != NULL) {
    FreePoolChecked (gFrequencies);
    gFrequencies = NULL;
  }

  if (gSteppingValues != NULL) {
    FreePoolChecked (gSteppingValues);
    gSteppingValues = NULL;
  }

  //
  // 8. If any mismatch found, send IPMI SEL
  //
  if (MicrocodeMismatch || FrequencyMismatch || SteppingMismatch) {
    DEBUG_PRINT_CMOS (DEBUG_INFO, "OemCheckCpuInfoNotify: CPU Mismatch\n");

    Status = gBootServices->LocateProtocol (
                              (EFI_GUID *)(UINTN)0x1660,   // gEfiIpmiTransportProtocolGuid
                              NULL,
                              (VOID **)&IpmiTransport
                              );
    if (EFI_ERROR (Status)) {
      DEBUG_PRINT_CMOS (DEBUG_INFO,
        "OemCheckCpuInfoNotify: LocateProtocol fail (%r)\n",
        Status);
      return Status;
    }

    //
    // Determine BMC bus for SEL
    //
    Status = GetBmcLogBus (&BusValue);
    if (EFI_ERROR (Status)) {
      BusValue = 0xFF;
    }

    //
    // Build IPMI SEL record
    //
    SelRecord.GeneratorId    = 0x20;    // BIOS
    SelRecord.EvMRevision    = 0x04;
    SelRecord.SensorType     = 0x02;    // Processor
    SelRecord.SensorNumber   = 0x72;
    SelRecord.EventDirType   = (BusValue <= 1) ? 0x70 : 0x95;   // Event Dir | Event Type
    SelRecord.EventData1     = 0x6F;    // OEM specific
    SelRecord.EventData2     = 0;
    if (MicrocodeMismatch) {
      SelRecord.EventData2 |= 0x10;     // bit 4: microcode
    }
    if (FrequencyMismatch) {
      SelRecord.EventData2 |= 0x01;     // bit 0: frequency
    }
    if (SteppingMismatch) {
      SelRecord.EventData2 |= 0x08;     // bit 3: stepping
    }
    SelRecord.EventData3     = 0x44;    // OEM byte

    //
    // Send IPMI command (NetFn=0x0A, Cmd=0x44) to add SEL entry
    //
    ResponseSize = sizeof (ResponseData);
    Status = IpmiTransport->SendIpmiCommand (
                              IpmiTransport,
                              0x0A,              // NetFn: Application
                              0,                 // LUN
                              0x44,              // Command: Add SEL Entry
                              (UINT8 *)&SelRecord,
                              sizeof (SelRecord),
                              &ResponseData,
                              &ResponseSize
                              );
    if (EFI_ERROR (Status)) {
      DEBUG_PRINT_CMOS (DEBUG_INFO,
        "OemCheckCpuInfoNotify: IpmiTransportDxe->SendIpmiCommand fail Status: %r\n",
        Status);
      return Status;
    }

    DEBUG_PRINT_CMOS (DEBUG_INFO, "OemCheckCpuInfoNotify end ...\n");
  }

  return EFI_SUCCESS;
}

/**
  Library constructor that saves UEFI global pointers and locates
  the IPMI transport protocol.

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

  @retval EFI_SUCCESS  The constructor always returns successfully.
**/
EFI_STATUS
EFIAPI
OemErrorLogDxeLibConstructor (
  IN EFI_HANDLE           ImageHandle,
  IN EFI_SYSTEM_TABLE     *SystemTable
  )
{
  EFI_STATUS  Status;

  //
  // Save global pointers
  //
  gImageHandle   = ImageHandle;
  gSystemTable   = SystemTable;
  gBootServices  = SystemTable->BootServices;
  gRuntimeServices = SystemTable->RuntimeServices;

  ASSERT (gImageHandle != NULL);
  ASSERT (gSystemTable != NULL);
  ASSERT (gBootServices != NULL);
  ASSERT (gRuntimeServices != NULL);

  //
  // Initialize HOB list
  //
  GetHobList ();

  //
  // Locate IPMI transport protocol to prepare for SEL logging
  //
  Status = gBootServices->LocateProtocol (
                            (EFI_GUID *)(UINTN)0x1660,   // gEfiIpmiTransportProtocolGuid
                            NULL,
                            &gIpmiTransport
                            );
  if (EFI_ERROR (Status)) {
    //
    // IPMI not available yet; register for protocol notification
    //
    Status = gBootServices->RegisterProtocolNotify (
                              (EFI_GUID *)(UINTN)0x1660,   // gEfiIpmiTransportProtocolGuid
                              (EFI_EVENT)(UINTN)LocateIpmiTransport,
                              NULL
                              );
    if (EFI_ERROR (Status)) {
      DEBUG_PRINT_CMOS (DEBUG_ERROR,
        "DxeLnvSendIpmiCmdLibConstructor Status = %r  \n",
        Status);
    }
  }

  return EFI_SUCCESS;
}

/**
  Driver entry point. Initializes globals, prints start/end messages,
  and registers the CPU info check notification.

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

  @return EFI status code.
**/
EFI_STATUS
EFIAPI
OemErrorLogDxeEntryPoint (
  IN EFI_HANDLE           ImageHandle,
  IN EFI_SYSTEM_TABLE     *SystemTable
  )
{
  EFI_STATUS  Status;

  //
  // Initialize UEFI globals and construct library
  //
  Status = OemErrorLogDxeLibConstructor (ImageHandle, SystemTable);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  DEBUG_PRINT_CMOS (DEBUG_INFO, "OemErrorLogDxeEntry\tstart ...\n");

  //
  // Register the CPU check notification via MP services protocol notify
  //
  Status = gBootServices->RegisterProtocolNotify (
                            &gEfiMpServiceProtocolGuid,
                            (EFI_EVENT)(UINTN)OemCheckCpuInfoNotify,
                            NULL
                            );

  if (!EFI_ERROR (Status)) {
    //
    // Check immediately after registration for already-present protocol
    //
    OemCheckCpuInfoNotify (NULL, NULL);
  }

  DEBUG_PRINT_CMOS (DEBUG_INFO, "OemErrorLogDxeEntry exit ...\n");
  return Status;
}