/** @file
DxeSelStatusCode - AMI IPMI SEL Status Code DXE Driver
This DXE driver maps UEFI status codes to IPMI SEL (System Event Log)
records. It reads configuration from the "ServerSetup" UEFI variable,
locates the IPMI transport protocol, and registers event notification
callbacks to write SEL records when status codes are reported during
boot and runtime.
Copyright (c) AMI. All rights reserved.
**/
#include <Uefi.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiRuntimeServicesTableLib.h>
#include <Library/UefiRuntimeLib.h>
#include <Library/DxeHobLib.h>
#include <Library/BaseLib.h>
#include "DxeSelStatusCode.h"
//
// Global Variables
//
EFI_HANDLE gImageHandle = NULL;
EFI_SYSTEM_TABLE *gSystemTable = NULL;
EFI_BOOT_SERVICES *gBootServices = NULL;
EFI_RUNTIME_SERVICES *gRuntimeServices = NULL;
EFI_RUNTIME_SERVICES *gRuntimeServicesCopy = NULL; // RuntimeServices_0
EFI_RUNTIME_SERVICES *gRuntimeServices2 = NULL; // RuntimeServices_1
VOID *gIpmiTransport = NULL; // qword_4418
// SEL configuration state
UINT8 gSelRedundancy = 0; // byte_4370
UINT8 gEnableRedundancy = 0; // byte_4410
UINT8 gSelRedundancyMode = 0; // byte_4421
UINT8 gSelOperationMode = 0; // n2 (0x4420)
VOID *gDebugPrintProtocol = NULL; // qword_43C0
VOID *gHobList = NULL; // qword_43C8
VOID *gSmbiosProtocol = NULL; // qword_43D8
UINT8 gSmbiosInitialized = 0; // byte_43D0
EFI_EVENT gVirtualAddressChangeEvent = NULL; // qword_4408
EFI_EVENT gBootServicesCleanupEvent = NULL; // qword_43B0
EFI_EVENT gRuntimeCleanupEvent = NULL; // qword_43F0
EFI_EVENT gSmbiosCleanupEvent = NULL; // qword_43E8
EFI_EVENT gSmmCommEvent = NULL; // qword_43F8
EFI_EVENT gRuntimeProtocolCleanupEvent = NULL; // qword_43A8
VOID *gBootServicesCopy = NULL; // BootServices_0 at qword_43B8
//
// SEL Event Record Mapping Table
//
// 61 entries x 12 bytes mapping UEFI status code classes to IPMI
// sensor number, sensor type, event direction, and event data.
//
SEL_EVENT_MAP_ENTRY gSelEventMapTable[] = {
// {StatusCodeValue, StatusCodeInstance, SensorNum, SensorType, EvDir, EvData2}
{0x00051005, 0x00000001, 0x0F, 0xC2, 0x01, 0xFF},
{0x02080000, 0x00000001, 0x0F, 0xC2, 0x02, 0xFF},
{0x00011007, 0x00000001, 0x0F, 0xC2, 0x03, 0xFF},
{0x03051006, 0x00000001, 0x0F, 0xC2, 0x04, 0xFF},
{0x03050007, 0x00000001, 0x0F, 0xC2, 0x05, 0xFF},
{0x02020000, 0x00000001, 0x0F, 0xC2, 0x06, 0xFF},
{0x02011001, 0x00000001, 0x0F, 0xC2, 0x07, 0xFF},
{0x03051000, 0x00000001, 0x0F, 0xC2, 0x08, 0xFF},
{0x01030000, 0x00000001, 0x0F, 0xC2, 0x09, 0xFF},
{0x00011001, 0x00000001, 0x0F, 0xC2, 0x0A, 0xFF},
{0x020B0000, 0x00000001, 0x0F, 0xC2, 0x0B, 0xFF},
{0x01010000, 0x00000001, 0x0F, 0xC2, 0x0C, 0xFF},
{0x00020000, 0x00000001, 0x0F, 0xC2, 0x0D, 0xFF},
{0x03031006, 0x00000001, 0x0F, 0xC2, 0x12, 0xFF},
{0x03051002, 0x00000001, 0x0F, 0xC2, 0x13, 0xFF},
{0x01011001, 0x00000001, 0x0F, 0xC2, 0x17, 0xFF},
{0x01021000, 0x00000001, 0x0F, 0xC2, 0x18, 0xFF},
{0x00011000, 0x00000001, 0x0F, 0xC2, 0x19, 0xFF},
{0x03101019, 0x00000001, 0x0F, 0xC2, 0x13, 0xFF},
{0x00000000, 0x00000002, 0x0F, 0xC0, 0x00, 0xFF},
{0x00051009, 0x00000002, 0x0F, 0xC0, 0x01, 0xFF},
{0x0005100A, 0x00000002, 0x0F, 0xC0, 0x02, 0xFF},
{0x02081002, 0x00000002, 0x0D, 0x02, 0xFF, 0xFF},
{0x02080005, 0x00000002, 0x0D, 0x01, 0xFF, 0xFF},
{0x90000000, 0x00000002, 0x0F, 0xC0, 0x04, 0xFF},
{0x02080006, 0x00000002, 0x0F, 0xC0, 0x06, 0xFF},
{0x01010006, 0x00000002, 0x0F, 0xC0, 0x07, 0xFF},
{0x01080006, 0x00000002, 0x0F, 0xC0, 0x08, 0xFF},
{0x01030006, 0x00000002, 0x0F, 0xC0, 0x09, 0xFF},
{0x01030003, 0x00000002, 0x0F, 0xC0, 0x0A, 0xFF},
{0x00020006, 0x00000002, 0x0F, 0xC0, 0x0B, 0xFF},
{0x00011007, 0x00000002, 0x0F, 0xC0, 0x0C, 0xFF},
{0x00010001, 0x00000002, 0x0F, 0xC0, 0x0D, 0xFF},
{0x00010001, 0x00000002, 0x07, 0x08, 0xFF, 0xFF},
{0x00011002, 0x00000002, 0x07, 0x05, 0xFF, 0xFF},
{0x00011006, 0x00000002, 0x07, 0x01, 0xFF, 0xFF},
{0x00011004, 0x00000002, 0x07, 0x02, 0xFF, 0xFF},
{0x00011005, 0x00000002, 0x07, 0x00, 0xFF, 0xFF},
{0x03150011, 0x00000002, 0x10, 0x04, 0xFF, 0xFF},
{0x02011000, 0x00000002, 0x13, 0x04, 0xFF, 0xFF},
{0x02011001, 0x00000002, 0x13, 0x05, 0xFF, 0xFF},
{0x00050004, 0x00000002, 0x0C, 0x07, 0xFF, 0xFF},
{0x00050001, 0x00000002, 0x0C, 0x24, 0xFF, 0x00},
{0x00050011, 0x00000002, 0x0C, 0x24, 0xFF, 0x01},
{0x00050021, 0x00000002, 0x0C, 0x24, 0xFF, 0x02},
{0x00050031, 0x00000002, 0x0C, 0x24, 0xFF, 0x03},
{0x00050041, 0x00000002, 0x0C, 0x24, 0xFF, 0x04},
{0x00050051, 0x00000002, 0x0C, 0x24, 0xFF, 0x05},
{0x00050061, 0x00000002, 0x0C, 0x24, 0xFF, 0x06},
{0x00050071, 0x00000002, 0x0C, 0x24, 0xFF, 0x07},
{0x00051002, 0x00000002, 0x0C, 0x00, 0xFF, 0xFF},
{0x00051003, 0x00000002, 0x0C, 0x01, 0xFF, 0xFF},
{0x00050005, 0x00000001, 0x0C, 0x28, 0xFF, 0x00},
{0x00050015, 0x00000001, 0x0C, 0x28, 0xFF, 0x01},
{0x00050025, 0x00000001, 0x0C, 0x28, 0xFF, 0x02},
{0x00050035, 0x00000001, 0x0C, 0x28, 0xFF, 0x03},
{0x00050045, 0x00000001, 0x0C, 0x28, 0xFF, 0x04},
{0x00050055, 0x00000001, 0x0C, 0x28, 0xFF, 0x05},
{0x00050065, 0x00000001, 0x0C, 0x28, 0xFF, 0x06},
{0x00050075, 0x00000001, 0x0C, 0x28, 0xFF, 0x07},
{0x00051001, 0x00000001, 0x0C, 0x06, 0xFF, 0xFF},
};
//
// Function Prototypes
//
EFI_STATUS
EFIAPI
UefiBootServicesTableLibConstructor (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
);
EFI_STATUS
ReportStatusCodeLibDestructor (
VOID
);
VOID
EFIAPI
BootServicesCleanup (
VOID
);
VOID
EFIAPI
RuntimeProtocolCleanup (
VOID
);
//----------------------------------------------------------------------
// Module Entry Point
//----------------------------------------------------------------------
EFI_STATUS
EFIAPI
ModuleEntryPoint (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status;
UefiBootServicesTableLibConstructor (ImageHandle, SystemTable);
Status = SelStatusCodeEntryInit ();
if (EFI_ERROR (Status)) {
ReportStatusCodeLibDestructor ();
}
return Status;
}
//----------------------------------------------------------------------
// Initializes UEFI boot services table library state. Registers
// exit boot services and set virtual address map events. Locates
// runtime protocol and debug print protocol instances.
//----------------------------------------------------------------------
EFI_STATUS
EFIAPI
UefiBootServicesTableLibConstructor (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status;
// Cache global pointers
gImageHandle = ImageHandle;
ASSERT (gImageHandle != NULL);
gSystemTable = SystemTable;
ASSERT (gSystemTable != NULL);
gBootServices = SystemTable->BootServices;
ASSERT (gBootServices != NULL);
gRuntimeServices = SystemTable->RuntimeServices;
ASSERT (gRuntimeServices != NULL);
gRuntimeServicesCopy = gRuntimeServices;
// Register BootServices cleanup event
Status = gBootServices->CreateEvent (
EVT_SIGNAL_EXIT_BOOT_SERVICES,
TPL_CALLBACK,
BootServicesCleanup,
NULL,
&gBootServicesCleanupEvent
);
ASSERT_EFI_ERROR (Status);
// Register runtime protocol cleanup event
Status = gBootServices->CreateEvent (
EVT_SIGNAL_EXIT_BOOT_SERVICES,
TPL_CALLBACK,
RuntimeProtocolCleanup,
NULL,
&gBootServicesCleanupEvent
);
ASSERT_EFI_ERROR (Status);
HobGetGuidEntry ();
// Register SetVirtualAddressMap notify for runtime library
Status = EfiCreateEventVirtualAddressMap (
TPL_NOTIFY,
SetVirtualAddressMapHandler
);
ASSERT_EFI_ERROR (Status);
// Register runtime protocol notify
Status = gBootServices->CreateEvent (
EVT_NOTIFY_SIGNAL,
TPL_NOTIFY,
DxeSelStatusCodeNoOpNotify,
NULL,
&gRuntimeProtocolCleanupEvent
);
ASSERT_EFI_ERROR (Status);
SmbiosProtocolInit ();
// Register SMBIOS cleanup
Status = gBootServices->CreateEvent (
EVT_NOTIFY_SIGNAL,
TPL_NOTIFY,
SmbiosProtocolCleanup,
NULL,
&gSmbiosCleanupEvent
);
ASSERT_EFI_ERROR (Status);
// Register ReportStatusCode shutdown
Status = gBootServices->CreateEvent (
EVT_NOTIFY_SIGNAL,
TPL_NOTIFY,
ReportStatusCodeNotifyShutdown,
NULL,
&gSmbiosCleanupEvent
);
ASSERT_EFI_ERROR (Status);
return Status;
}
//----------------------------------------------------------------------
// Closes all registered events and frees protocol resources.
// Called on error unwind or module unload.
//----------------------------------------------------------------------
EFI_STATUS
ReportStatusCodeLibDestructor (
VOID
)
{
ASSERT (gBootServices != NULL);
if (gSmbiosCleanupEvent != NULL) {
gBootServices->CloseEvent (gSmbiosCleanupEvent);
ASSERT_EFI_ERROR (Status);
}
if (gRuntimeCleanupEvent != NULL) {
gBootServices->CloseEvent (gRuntimeCleanupEvent);
ASSERT_EFI_ERROR (Status);
}
if (gSmmCommEvent != NULL) {
gBootServices->CloseEvent (gSmmCommEvent);
ASSERT_EFI_ERROR (Status);
}
if (gVirtualAddressChangeEvent != NULL) {
gBootServices->CloseEvent (gVirtualAddressChangeEvent);
ASSERT_EFI_ERROR (Status);
}
gBootServices->CloseEvent (gRuntimeProtocolCleanupEvent);
gBootServices->CloseEvent (gBootServicesCleanupEvent);
return EFI_SUCCESS;
}
//----------------------------------------------------------------------
// Converts gRuntimeServicesCopy and gIpmiTransport for virtual
// addressing after SetVirtualAddressMap.
//----------------------------------------------------------------------
VOID
EFIAPI
SetVirtualAddressMapHandler (
VOID
)
{
EfiConvertPointer (0, (VOID **)&gRuntimeServicesCopy);
EfiConvertPointer (0, (VOID **)&gIpmiTransport);
}
//----------------------------------------------------------------------
// Searches the SEL event mapping table for an entry matching the
// given status code value and instance. If found, builds a 16-byte
// IPMI SEL event record data buffer.
//
// @param StatusCodeValue UEFI status code value
// @param StatusCodeInstance Instance or sub-classifier
// @param RecordData Output buffer for record data
//
// @retval EFI_SUCCESS Record data constructed
// @retval EFI_NOT_FOUND No matching mapping
//----------------------------------------------------------------------
EFI_STATUS
SelMapStatusCodeToEventData (
IN UINT32 StatusCodeValue,
IN UINT32 StatusCodeInstance,
OUT UINT8 *RecordData
)
{
UINTN Index;
SEL_EVENT_MAP_ENTRY *Entry;
Index = 0;
for (Index = 0; Index < ARRAY_SIZE (gSelEventMapTable); Index++) {
if (gSelEventMapTable[Index].StatusCodeValue == StatusCodeValue &&
gSelEventMapTable[Index].StatusCodeInstance == StatusCodeInstance) {
Entry = &gSelEventMapTable[Index];
break;
}
}
if (Entry == NULL) {
return EFI_NOT_FOUND;
}
// Check redundancy status
if (SelCheckRedundancyStatus (StatusCodeValue, StatusCodeInstance, Entry) == 1) {
if (gSelOperationMode == 1) {
// Clear the record first
SelManageEventLog (StatusCodeValue, 1);
} else if (gSelRedundancy && gSelOperationMode == 2) {
// Add with redundancy
SelManageEventLog (StatusCodeValue, 2);
}
}
// Build record data structure
*(UINT16 *)(RecordData + 0) = 0; // Record ID = 0
*(UINT8 *)(RecordData + 2) = 2; // Record Type = 2 (SEL)
*(UINT32 *)(RecordData + 3) = 0; // Timestamp = 0
*(UINT16 *)(RecordData + 7) = 1; // Generator ID
*(UINT8 *)(RecordData + 9) = 4; // Event Message Format Version
*(UINT8 *)(RecordData + 10) = Entry->SensorNumber;
*(UINT16 *)(RecordData + 11) = 0x6F00; // Event Type + Event Direction
*(UINT8 *)(RecordData + 13) = Entry->SensorType;
*(UINT8 *)(RecordData + 14) = Entry->EventDirType;
*(UINT8 *)(RecordData + 15) = Entry->EventData2;
return EFI_SUCCESS;
}
//----------------------------------------------------------------------
// Writes an IPMI SEL event record using the transport protocol.
// Handles reserve and append operations.
//----------------------------------------------------------------------
EFI_STATUS
SelWriteEventToRecord (
IN VOID *Transport,
IN UINT8 OperationMode,
IN UINT32 StatusCodeValue,
IN UINT32 StatusCodeInstance
)
{
EFI_STATUS Status;
UINT8 RecordData[16];
UINT16 RecordId;
INTN N16;
UINT8 ResponseSize;
UINT8 CompletionCode;
RecordId = 0;
// Skip if redundancy not needed
if (OperationMode == 1 && !gSelRedundancyMode) return EFI_SUCCESS;
if (OperationMode == 2 && !gEnableRedundancy) return EFI_SUCCESS;
// Map status code to SEL event data
Status = SelMapStatusCodeToEventData (StatusCodeValue, StatusCodeInstance, RecordData);
if (EFI_ERROR (Status)) {
return EFI_SUCCESS;
}
ResponseSize = 2;
N16 = 16;
// IPMI transport write command
return gIpmiTransport->Write (
gIpmiTransport,
IPMI_SEL_CMD_RESERVE, // NetFn 0x0A
0,
IPMI_SEL_CMD_ADD, // Command 0x44
RecordData,
N16,
&RecordId,
&ResponseSize
);
}
//----------------------------------------------------------------------
// Clears an IPMI SEL event record. Validates sensor type.
//----------------------------------------------------------------------
EFI_STATUS
SelClearEventRecord (
IN UINT32 StatusCodeType,
IN UINT32 StatusCodeValue
)
{
UINTN Index;
SEL_EVENT_MAP_ENTRY *Entry;
// Validate type
if ((UINT8)StatusCodeType < 1 || (UINT8)StatusCodeType > 2) {
return EFI_SUCCESS;
}
Index = 0;
Entry = NULL;
for (Index = 0; Index < ARRAY_SIZE (gSelEventMapTable); Index++) {
if (gSelEventMapTable[Index].StatusCodeValue == StatusCodeValue &&
(UINT8)StatusCodeType == gSelEventMapTable[Index].EventDirType) {
Entry = &gSelEventMapTable[Index];
break;
}
}
if (Entry == NULL) {
return EFI_NOT_FOUND;
}
// Call IPMI transport to clear SEL
return gRuntimeServicesCopy->SetVariable (
L"ServerSetup",
&gServerSetupVariableGuid,
(UINTN)StatusCodeValue,
sizeof(SERVER_SETUP_VARIABLE),
&gServerSetupVariable
);
}
//----------------------------------------------------------------------
// Main initialization for the SEL Status Code driver. Reads platform
// configuration from the "ServerSetup" UEFI variable, locates the IPMI
// transport protocol, registers SetVirtualAddressMap notification, and
// installs the SEL event clearing/smm notification protocol.
//----------------------------------------------------------------------
EFI_STATUS
SelStatusCodeEntryInit (
VOID
)
{
EFI_STATUS Status;
SERVER_SETUP_VARIABLE ServerSetup;
UINTN DataSize;
IPMI_Transport *Transport;
VOID *Interface;
ServerSetup.Value = 33554433; // Default: 0x2000001
DataSize = 1072;
Interface = NULL;
// Get IPMI transport protocol
Status = gBootServices->LocateProtocol (
&gIpmiTransportProtocolGuid,
NULL,
&gIpmiTransport
);
if (EFI_ERROR (Status)) {
return Status;
}
// Read ServerSetup variable
Status = gRuntimeServices->GetVariable (
L"ServerSetup",
&gServerSetupVariableGuid,
&ServerSetup.Attributes,
&DataSize,
&ServerSetup
);
if (EFI_ERROR (Status)) {
ServerSetup.Value = 33554433;
ServerSetup.Bits.SelEnabled = 0;
ServerSetup.Bits.SelOperation = 1;
}
if (!ServerSetup.Bits.SelEnabled) {
return EFI_UNSUPPORTED;
}
// Handle SEL clear operation
if (ServerSetup.Bits.SelOperation == 1 || ServerSetup.Bits.SelOperation == 2) {
SelManageEventLog (ServerSetup.Value, 1);
if (ServerSetup.Bits.SelOperation == 1) {
ServerSetup.Bits.SelOperation = 0;
Status = gRuntimeServices->SetVariable (
L"ServerSetup",
&gServerSetupVariableGuid,
ServerSetup.Attributes,
DataSize,
&ServerSetup
);
ASSERT_EFI_ERROR (Status);
}
}
gSelOperationMode = ServerSetup.Bits.ReservePolicy;
// Check redundancy
if (SelCheckRedundancyStatus (0, 0, 0) == 1 && gSelOperationMode == 1) {
SelManageEventLog (0, 1);
}
// Configure SEL event handling
if (ServerSetup.Bits.EventHandling != 0) {
gSelRedundancyMode = (ServerSetup.Bits.EventHandling == 1 || ServerSetup.Bits.EventHandling == 3);
gEnableRedundancy = (ServerSetup.Bits.EventHandling <= 2);
// Register SMM communication protocol
Status = gBootServices->InstallProtocolInterface (
&Interface,
&gSmmCommunicationProtocolGuid,
EFI_NATIVE_INTERFACE,
SelWriteEventToRecord
);
if (!EFI_ERROR (Status)) {
// Register SetVirtualAddressMap event
Status = gBootServices->CreateEvent (
EVT_NOTIFY_SIGNAL,
TPL_NOTIFY,
SetVirtualAddressMapHandler
);
if (!EFI_ERROR (Status)) {
// Locate SMM communication/SMBIOS dispatch protocol
Status = gBootServices->LocateProtocol (
&gSmbiosDispatchProtocolGuid,
NULL,
&v16
);
if (!EFI_ERROR (Status)) {
// Register clear handler
(*v16) (SelClearEventRecord, 31);
}
ASSERT_EFI_ERROR (Status);
}
return EFI_SUCCESS;
}
}
return EFI_UNSUPPORTED;
}
//----------------------------------------------------------------------
// Allocates memory and locates the debug print protocol. Used for
// ASSERT and debug output.
//----------------------------------------------------------------------
VOID *
DebugPrintProtocolInit (
VOID
)
{
VOID *Protocol;
UINTN PoolSize;
if (gDebugPrintProtocol != NULL) {
return gDebugPrintProtocol;
}
if (gBootServicesCopy != NULL) {
PoolSize = gBootServicesCopy->GetPoolSize (NULL);
if (PoolSize <= 16) {
Status = gBootServicesCopy->LocateProtocol (
&gDebugPortProtocolGuid,
NULL,
&gDebugPrintProtocol
);
if (EFI_ERROR (Status)) {
gDebugPrintProtocol = NULL;
}
}
}
return gDebugPrintProtocol;
}
//----------------------------------------------------------------------
// Checks current debug verbosity level (via CMOS) and prints message
// if level matches. Used by ASSERT_EFI_ERROR macro output.
//----------------------------------------------------------------------
EFI_STATUS
DebugPrintWithLevel (
IN UINTN ErrorLevel,
IN CONST CHAR8 *Format,
...
)
{
VA_LIST Marker;
EFI_STATUS Status;
DEBUG_PRINT_PROTOCOL *Protocol;
UINT8 DebugLevel;
UINT8 CmosValue;
UINT8 EffectiveLevel;
Protocol = DebugPrintProtocolInit ();
if (Protocol != NULL) {
// Read debug level from CMOS (offset 0x4B)
CmosValue = IoRead8 (0x70);
CmosValue = (CmosValue & 0x80) | 0x4B;
IoWrite8 (0x70, CmosValue);
DebugLevel = IoRead8 (0x71);
if (DebugLevel > 3) {
DebugLevel = 3;
}
EffectiveLevel = DebugLevel - 1;
if (EffectiveLevel <= 0xFD) {
Status = 0x7FFFFFF4;
if (DebugLevel == 1) {
Status = 0x7FFFFFF4;
}
}
if (Status & ErrorLevel) {
VA_START (Marker, Format);
Status = Protocol->Print (ErrorLevel, Format, &Marker);
VA_END (Marker);
}
}
return Status;
}
//----------------------------------------------------------------------
// Standard EDK2 ASSERT handler. Prints file/line/expression and
// enters dead loop.
//----------------------------------------------------------------------
VOID
DebugAssert (
IN CONST CHAR8 *FileName,
IN UINTN LineNumber,
IN CONST CHAR8 *Description
)
{
DEBUG_PRINT_PROTOCOL *Protocol;
Protocol = DebugPrintProtocolInit ();
if (Protocol != NULL) {
Protocol->Assert (FileName, LineNumber, Description);
}
}
//----------------------------------------------------------------------
// ExitBootServices callback. Nullifies the BootServices_0 pointer
// to prevent use after boot services exit.
//----------------------------------------------------------------------
VOID
EFIAPI
BootServicesCleanup (
VOID
)
{
gBootServicesCopy = NULL;
}
//----------------------------------------------------------------------
// SetVirtualAddressMap callback. Converts the debug print protocol
// pointer for runtime virtual addressing.
//----------------------------------------------------------------------
EFI_STATUS
EFIAPI
RuntimeProtocolCleanup (
VOID
)
{
if (gDebugPrintProtocol != NULL) {
return EfiConvertPointer (0, &gDebugPrintProtocol);
}
return EFI_SUCCESS;
}
//----------------------------------------------------------------------
// Locates the GUIDed HOB entry in the system HOB list. Used to find
// the SMBIOS table or other platform configuration tables.
//----------------------------------------------------------------------
VOID *
HobGetGuidEntry (
VOID
)
{
EFI_STATUS Status;
EFI_HOB_HANDOFF_INFO_TABLE *HobList;
EFI_GUID *Guid;
UINTN HobCount;
if (gHobList != NULL) {
return gHobList;
}
// Get HOB list from SystemTable
HobList = (EFI_HOB_HANDOFF_INFO_TABLE *)gSystemTable->BootServices->GetHobList ();
Status = EFI_NOT_FOUND;
if (HobList->EfiMemoryTop != NULL) {
for (HobCount = 0; HobCount < HobList->EfiFreeMemoryBottom; HobCount++) {
Guid = (EFI_GUID *)((UINT8 *)HobList + HobCount * sizeof(EFI_HOB_GUID_TYPE));
if (HobGuidCompare (Guid, &gSmbiosTableGuid)) {
gHobList = *(VOID **)((UINT8 *)Guid + 16);
break;
}
}
}
if (gHobList == NULL) {
ASSSERT_EFI_ERROR (EFI_NOT_FOUND);
ASSERT (gHobList != NULL);
}
return gHobList;
}
//----------------------------------------------------------------------
// No-op callback. Used as placeholder for events that don't require
// action but need a registered callback.
//----------------------------------------------------------------------
VOID
DxeSelStatusCodeNoOpNotify (
VOID
)
{
return;
}
//----------------------------------------------------------------------
// SetVirtualAddressMap notification to convert the gRuntimeServices2
// pointer to virtual addressing.
//----------------------------------------------------------------------
VOID
EFIAPI
RuntimeProtocolNotifyCleanup (
VOID
)
{
return EfiConvertPointer (0, &gRuntimeServices2);
}
//----------------------------------------------------------------------
// Locates the SMBIOS protocol instance. Used for SMBIOS table access
// during SEL event logging.
//----------------------------------------------------------------------
VOID
SmbiosProtocolInit (
VOID
)
{
EFI_STATUS Status;
if (gSmbiosProtocol != NULL || gSmbiosInitialized) {
return;
}
if (gBootServices != NULL) {
Status = gBootServices->LocateProtocol (
&gSmbiosProtocolGuid,
NULL,
&gSmbiosProtocol
);
if (EFI_ERROR (Status)) {
gSmbiosProtocol = NULL;
}
}
}
//----------------------------------------------------------------------
// SetVirtualAddressMap callback to convert the SMBIOS protocol
// pointer for runtime virtual addressing.
//----------------------------------------------------------------------
VOID
EFIAPI
SmbiosProtocolCleanup (
VOID
)
{
if (gSmbiosProtocol != NULL) {
return EfiConvertPointer (0, &gSmbiosProtocol);
}
return EFI_SUCCESS;
}
//----------------------------------------------------------------------
// ExitBootServices notification. Initializes SMBIOS protocol for
// runtime and marks initialization complete.
//----------------------------------------------------------------------
VOID
EFIAPI
ReportStatusCodeNotifyShutdown (
VOID
)
{
SmbiosProtocolInit ();
gSmbiosInitialized = 1;
}
//----------------------------------------------------------------------
// Adds a SEL event record via the IPMI transport. Retries up to
// 512 times to handle the reserve-then-add race condition with
// the BMC.
//
// @param Transport IPMI transport protocol
// @param RecordId Record ID bytes
//
// @retval EFI_SUCCESS Record added
// @retval EFI_INVALID_PARAMETER Invalid input
// @retval EFI_TIMEOUT Max retries exceeded
//----------------------------------------------------------------------
EFI_STATUS
SelAddEventRecord (
IN VOID *Transport,
IN UINT8 *RecordId,
...
)
{
UINTN Retries;
EFI_STATUS Status;
UINT8 ResponseCode;
UINT8 RequestData[6];
UINT8 ResponseData[2];
UINT8 CompletionCode;
if (RecordId == NULL) {
return EFI_INVALID_PARAMETER;
}
Retries = 512;
do {
// Send reserve SEL command
RequestData[0] = RecordId[0];
RequestData[1] = RecordId[1];
RequestData[2] = 0x34; // IPMI SEL reserve
RequestData[3] = 0x52; // IPMI SEL add
RequestData[4] = 6; // Data length
RequestData[5] = 1; // Set reserve flag
Status = ((IPMI_TRANSPORT_PROTOCOL *)Transport)->SendCommand (
Transport,
IPMI_NETFN_STORAGE,
0,
IPMI_SEL_CMD_RESERVE,
RequestData,
sizeof(RequestData),
&ResponseData,
&ResponseCode
);
if (EFI_ERROR (Status)) {
if (ResponseCode == 0xC5) {
// Reservation lost/gained - need to retry
Status = ((IPMI_TRANSPORT_PROTOCOL *)Transport)->SendCommand (
Transport,
IPMI_NETFN_STORAGE,
0,
IPMI_SEL_CMD_RESERVE,
NULL,
0,
RecordId,
&ResponseCode
);
if (EFI_ERROR (Status)) {
return Status;
}
}
} else {
if ((ResponseData[0] & 0x0F) == 1) {
return EFI_SUCCESS;
}
}
--Retries;
} while (Retries > 0);
return EFI_TIMEOUT;
}
//----------------------------------------------------------------------
// Checks the IPMI SEL status via the transport protocol to determine
// if the SEL is operational, has available space, and redundancy is
// active.
//
// @retval 1 SEL is operational with space and redundancy
// @retval 0 SEL not available or no redundancy
//----------------------------------------------------------------------
UINT8
SelCheckRedundancyStatus (
VOID
)
{
EFI_STATUS Status;
UINT8 SelStatus[13];
UINT8 DataSize;
UINT64 CommandData;
if (gIpmiTransport == NULL) {
return 0;
}
CommandData = 14; // NetFn 0x0A, Cmd 0x40 (Get SEL Info)
DataSize = 0;
Status = gIpmiTransport->Write (
gIpmiTransport,
0x0A, // IPMI NetFn Storage
0,
0x40, // Get SEL Info
0,
DataSize,
SelStatus,
&CommandData
);
if (EFI_ERROR (Status)) {
return 0;
}
// Byte 7: SEL State (bit 3 = Redundancy, bit 7 = Operational)
gSelRedundancy = SelStatus[7] & 8;
return SelStatus[7] >> 7;
}
//----------------------------------------------------------------------
// Manages the IPMI SEL event log. Supports two operations:
// Mode 1: Clear SEL entries matching the status code
// Mode 2: Add SEL entries for status codes
//
// For mode 2, performs a reserve + add sequence using the IPMI
// transport protocol.
//----------------------------------------------------------------------
EFI_STATUS
SelManageEventLog (
IN UINT64 ServerSetupData,
IN UINT8 OperationMode
)
{
EFI_STATUS Status;
IPMI_TRANSPORT *Transport;
UINT8 RecordId[2];
UINT8 ResponseData[4];
UINT8 SelStatus[13];
UINT8 DataSize;
UINT8 ResponseSize;
UINTN EventType;
Transport = gIpmiTransport;
RecordId[0] = 0;
RecordId[1] = 0;
ResponseSize = 1;
DataSize = 0;
if (gIpmiTransport == NULL || OperationMode > 2) {
return EFI_INVALID_PARAMETER;
}
// Get SEL info to check state
Status = gIpmiTransport->Write (
gIpmiTransport,
0x0A, // IPMI NetFn Storage
0,
0x40, // Get SEL Info
0,
0,
SelStatus,
(UINT8 *)&DataSize
);
if (EFI_ERROR (Status)) {
return Status;
}
if ((SelStatus[13] & 2) != 0) {
// SEL has entries - clear or process
if (OperationMode == 1) {
// Clear: reserve + clear
Status = gIpmiTransport->Write (
gIpmiTransport,
0x0A,
0,
0x44, // Reserve SEL
0,
0,
RecordId,
&ResponseSize
);
if (EFI_ERROR (Status)) {
return Status;
}
EventType = HIBYTE(RecordId[0]);
// Clear SEL entries
Status = gIpmiTransport->Write (
gIpmiTransport,
0x0A,
0,
0x47, // Clear SEL
&EventType,
6,
ResponseData,
&ResponseSize
);
if (EFI_ERROR (Status)) {
return Status;
}
if ((ResponseData[0] & 0x0F) == 1) {
return EFI_SUCCESS;
}
return SelAddEventRecord (Transport, RecordId);
} else if ((SelStatus[13] & 8) == 0) {
// Deleting single record not supported
DebugPrintWithLevel (EFI_D_ERROR, "Deleting Single record is not supported. Status: EFI_UNSUPPORTED\n");
return EFI_UNSUPPORTED;
}
// Add entry
ResponseSize = 4;
Status = gIpmiTransport->Write (
gIpmiTransport,
0x0A,
0,
0x44, // Reserve + Add
ResponseData,
4,
StatusData,
&ResponseSize
);
if (EFI_ERROR (Status)) {
return Status;
}
// Check response byte 8 for status
if (Transport->CompletionCode == 0x80) {
return EFI_UNSUPPORTED;
}
if (Transport->CompletionCode == 0x81) {
return EFI_UNSUPPORTED;
}
}
return Status;
}
//----------------------------------------------------------------------
// Compares two GUIDs by reading their QWORD pairs via unaligned
// access. Compares GUID at unk_4060 (SMBIOS_TABLE_GUID).
//----------------------------------------------------------------------
BOOLEAN
HobGuidCompare (
IN CONST EFI_GUID *Guid1,
IN CONST EFI_GUID *Guid2
)
{
EFI_GUID TargetGuid;
CopyMem (&TargetGuid, &gSmbiosTableGuid, sizeof(EFI_GUID));
return (ReadUnaligned64 (&TargetGuid) == ReadUnaligned64 (Guid1) &&
ReadUnaligned64 ((UINT8 *)&TargetGuid + 8) == ReadUnaligned64 (((UINT8 *)Guid2 + 8)));
}
//----------------------------------------------------------------------
// Reads a 64-bit value from potentially unaligned memory.
//
// @param Buffer Pointer to data (may be unaligned)
//
// @return The 64-bit value read.
//----------------------------------------------------------------------
UINT64
EFIAPI
ReadUnaligned64 (
IN CONST VOID *Buffer
)
{
ASSERT (Buffer != NULL);
return *(UINT64 *)Buffer;
}