/** @file
SecVariableControl.c -- Secure Variable Control Driver
This driver is responsible for:
1. Locating the DRAM fail data HOB produced during PEI memory training
2. Parsing DRAM failure entries (N/C/D/R/CID/BG/BA/ROW/COL/DQ/Temp)
3. Saving parsed data to UEFI runtime variables (STEP_RESULT, STEP_RESULT_NUM00-04)
4. Supporting both DXE and SMM debug output paths
The HOB contains up to 210 (0xD2) DRAM failure entries across up to 5 DIMMs.
Each entry encodes the full DDR address map of a failed bit, together with
Post-Package Repair (PPR) status (PASS/FAIL) and temperature at failure time.
Copyright (c) 2024, Intel Corporation. All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include "SecVariableControl.h"
//
// Global variables (in .data section)
//
EFI_SYSTEM_TABLE *gSystemTable = NULL;
EFI_BOOT_SERVICES *gBootServices = NULL;
EFI_RUNTIME_SERVICES *gRuntimeServices = NULL;
EFI_HANDLE gImageHandle = NULL;
VOID *gDebugProtocol = NULL;
VOID *gHobList = NULL;
EFI_DXE_SERVICES *gDxeServicesTable = NULL;
UINT8 gIsSmmPath = 0;
VOID *gSmmProtocol = NULL;
VOID *gSmmProtocolHandle = NULL;
VOID *gDebugProtocolDxe = NULL;
//
// Function prototypes
//
EFI_STATUS
EFIAPI
SecVariableControlDriverEntry (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
);
EFI_STATUS
SaveDramFailDataToVariable (
IN EFI_SYSTEM_TABLE *SystemTable
);
/**
Initialize debug output support.
Attempts to locate the debug protocol (via DXE or SMM path).
In DXE mode, uses BootServices->LocateProtocol with DebugProtocol GUID.
In SMM mode, uses a pre-established SMM communication protocol handle.
@retval EFI_SUCCESS Debug protocol initialized.
@retval EFI_UNSUPPORTED Platform does not support debug output level.
@retval others LocateProtocol failure.
**/
EFI_STATUS
DebugProtocolInit (
VOID
)
{
UINT64 DebugCapabilities;
if (gIsSmmPath) {
// SMM path: use pre-located SMM protocol handle
if (gSmmProtocol != NULL) {
return EFI_SUCCESS;
}
if (gSmmProtocolHandle == NULL) {
return EFI_NOT_FOUND;
}
return gBootServices->LocateProtocol (
&gEfiSmmCommunicationProtocolGuid,
NULL,
&gSmmProtocol
);
} else {
// DXE path
if (gDebugProtocolDxe != NULL) {
return EFI_SUCCESS;
}
DebugCapabilities = (UINT64)(UINTN)gBootServices->AllocatePool (EfiBootServicesData, sizeof(UINT64));
gBootServices->FreePool ((VOID *)(UINTN)DebugCapabilities);
if (DebugCapabilities > 0x10) {
return EFI_UNSUPPORTED;
}
return gBootServices->LocateProtocol (
&gEfiDebugPortProtocolGuid,
NULL,
&gDebugProtocolDxe
);
}
}
/**
Read a UINT64 from an unaligned buffer.
@param[in] Buffer Pointer to the buffer to read from.
@return The 64-bit value at the buffer location.
**/
UINT64
EFIAPI
ReadUnaligned64 (
IN CONST VOID *Buffer
)
{
ASSERT (Buffer != NULL);
return *(UINT64 *)Buffer;
}
/**
Compares two EFI_GUIDs by comparing their two 64-bit halves.
@param[in] Guid1 Pointer to first GUID.
@param[in] Guid2 Pointer to second GUID.
@retval TRUE The GUIDs are identical.
@retval FALSE The GUIDs differ.
**/
BOOLEAN
EFIAPI
CompareGuid (
IN CONST EFI_GUID *Guid1,
IN CONST EFI_GUID *Guid2
)
{
UINT64 Part1a, Part1b, Part2a, Part2b;
Part1a = ReadUnaligned64 (Guid1);
Part2a = ReadUnaligned64 (Guid2);
Part1b = ReadUnaligned64 ((UINT8 *)Guid1 + 8);
Part2b = ReadUnaligned64 ((UINT8 *)Guid2 + 8);
return (BOOLEAN)(Part1a == Part2a && Part1b == Part2b);
}
/**
Return the debug level derived from CMOS.
Reads CMOS offset 0x4B (with NMI bit preserved) and translates the value
into a debug mask suitable for DebugBspPrint filtering.
@return Debug level mask (0, EFI_D_INFO, or EFI_D_ERROR).
**/
UINTN
GetDebugLevel (
VOID
)
{
UINT8 CmosValue;
UINTN DebugLevel;
//
// Read CMOS offset 0x4B with NMI bit preserved
//
IoWrite8 (0x70, (IoRead8 (0x70) & 0x80) | 0x4B);
CmosValue = IoRead8 (0x71);
if (CmosValue > 3) {
if (CmosValue == 0) {
CmosValue = (*(volatile UINT8 *)0xFDAF0490 & 2) | 1;
}
}
if ((UINT8)(CmosValue - 1) > 0xFD) {
return 0;
}
if (CmosValue == 1) {
DebugLevel = EFI_D_INFO;
} else {
DebugLevel = EFI_D_ERROR;
}
return DebugLevel;
}
/**
Print debug message via BSP (Boot Strap Processor) debug protocol.
Replaces %s with 'a' and %g with 'g' before printing to avoid
string/GUID expansion in the BSP debug output path, then dispatches
to the debug protocol's output function.
@param[in] ErrorLevel Debug error level.
@param[in] Format Format string.
@param[in] ... Variable arguments.
**/
VOID
EFIAPI
DebugBspPrint (
IN UINTN ErrorLevel,
IN CONST CHAR8 *Format,
...
)
{
VA_LIST Marker;
VA_LIST Copy;
CONST CHAR8 *FormatWalker;
CHAR8 *FormatCopy;
DebugProtocolInit ();
if (EFI_ERROR (DebugProtocolInit ())) {
return;
}
if (GetDebugLevel () & ErrorLevel) {
//
// Sanitize format string: %s -> 'a', %g -> 'g'
//
FormatCopy = (CHAR8 *)Format;
if (*FormatCopy != 0) {
do {
if (*FormatCopy == '%') {
if (*(FormatCopy + 1) == 's') {
*(FormatCopy + 1) = 'a';
} else if (*(FormatCopy + 1) == 'g') {
*(FormatCopy + 1) = 'G';
}
}
FormatCopy++;
} while (*FormatCopy != 0);
}
VA_START (Marker, Format);
VA_COPY (Copy, Marker);
if (gIsSmmPath) {
((DEBUG_PROTOCOL *)gSmmProtocol)->DebugAssert (
ErrorLevel,
Format,
Copy
);
} else {
((DEBUG_PROTOCOL *)gDebugProtocolDxe)->DebugAssert (
ErrorLevel,
Format,
Copy
);
}
VA_END (Marker);
}
}
/**
Get the debug protocol interface.
Cached lookup of the debug protocol interface. Checks platform capabilities
first (must be <= 16) then locates the protocol via BootServices.
@return Pointer to debug protocol interface, or NULL on failure.
**/
VOID *
GetDebugProtocolInterface (
VOID
)
{
UINT64 Capabilities;
if (gDebugProtocol != NULL) {
return gDebugProtocol;
}
Capabilities = (UINT64)(UINTN)gBootServices->AllocatePool (EfiBootServicesData, sizeof(UINT64));
gBootServices->FreePool ((VOID *)(UINTN)Capabilities);
if (Capabilities > 0x10) {
return NULL;
}
if (gBootServices->LocateProtocol (
&gEfiDebugPortProtocolGuid,
NULL,
&gDebugProtocol
) < 0)
{
gDebugProtocol = NULL;
}
return gDebugProtocol;
}
/**
Debug print wrapper with error level filtering.
@param[in] ErrorLevel Debug error level.
@param[in] Format Format string.
@param[in] ... Variable arguments.
**/
VOID
EFIAPI
DebugPrint (
IN UINTN ErrorLevel,
IN CONST CHAR8 *Format,
...
)
{
VA_LIST Marker;
DEBUG_PROTOCOL *DebugProtocol;
DebugProtocol = (DEBUG_PROTOCOL *)GetDebugProtocolInterface ();
if (DebugProtocol != NULL && (GetDebugLevel () & ErrorLevel)) {
VA_START (Marker, Format);
DebugProtocol->DebugAssert (ErrorLevel, Format, Marker);
VA_END (Marker);
}
}
/**
Debug assert handler with format support.
@param[in] FileName Source file name string.
@param[in] LineNumber Line number in source file.
@param[in] Expression Assert expression string.
**/
VOID
EFIAPI
DebugAssert (
IN CONST CHAR8 *FileName,
IN UINTN LineNumber,
IN CONST CHAR8 *Expression
)
{
DEBUG_PROTOCOL *DebugProtocol;
DebugProtocol = (DEBUG_PROTOCOL *)GetDebugProtocolInterface ();
if (DebugProtocol != NULL) {
DebugProtocol->DebugAssert (FileName, LineNumber, Expression);
}
}
/**
Get a pointer to the DXE Services Table.
Searches the system configuration table array for gEfiDxeServicesTableGuid.
@param[out] Table Pointer to receive the DXE Services Table.
@retval EFI_SUCCESS Table found.
@retval EFI_NOT_FOUND Table not found.
**/
EFI_STATUS
GetDxeServicesTable (
OUT EFI_DXE_SERVICES **Table
)
{
UINTN Index;
ASSERT (Table != NULL);
*Table = NULL;
if (gSystemTable->NumberOfTableEntries == 0) {
return EFI_NOT_FOUND;
}
for (Index = 0; Index < gSystemTable->NumberOfTableEntries; Index++) {
if (CompareGuid (
&gSystemTable->ConfigurationTable[Index].VendorGuid,
&gEfiDxeServicesTableGuid
))
{
*Table = (EFI_DXE_SERVICES *)gSystemTable->ConfigurationTable[Index].VendorTable;
return EFI_SUCCESS;
}
}
return EFI_NOT_FOUND;
}
/**
Get the HOB list pointer.
Locates the HOB list from the system configuration table using
gEfiHobListGuid.
@return Pointer to the start of the HOB list, or NULL on failure.
**/
VOID *
GetHobList (
VOID
)
{
EFI_STATUS Status;
if (gHobList != NULL) {
return gHobList;
}
Status = GetDxeServicesTable (&gEfiDxeServicesTableGuid);
if (EFI_ERROR (Status)) {
DEBUG ((EFI_D_ERROR, "ASSERT_EFI_ERROR (Status = %r)\n", Status));
ASSERT_EFI_ERROR (FALSE);
}
if (gHobList == NULL) {
DEBUG ((EFI_D_ERROR, "mHobList != ((void *) 0)\n"));
ASSERT (FALSE);
}
return gHobList;
}
/**
Get the next GUID HOB of a specific type from the HOB list.
Walks the HOB list starting from @c HobStart, searching for HOBs of
type EFI_HOB_TYPE_GUID_EXT (type 4). Returns the first HOB matching
the type (not filtering by GUID).
@param[in] HobStart Pointer to the start of the HOB list or a HOB to continue from.
@return Pointer to the GUID HOB if found, NULL at end of list.
**/
EFI_HOB_GUID_TYPE *
GetNextGuidHob (
IN CONST VOID *HobStart
)
{
EFI_PEI_HOB_POINTERS Hob;
ASSERT (HobStart != NULL);
Hob.Raw = (UINT8 *)HobStart;
while (1) {
if (Hob.Header->HobType == EFI_HOB_TYPE_END_OF_HOB_LIST) {
return NULL;
}
if (Hob.Header->HobType == EFI_HOB_TYPE_GUID_EXT) {
return Hob.Guid;
}
Hob.Raw = (UINT8 *)Hob.Raw + Hob.Header->HobLength;
}
}
/**
Driver entry point.
Initializes global service table pointers and system configuration table
access, then proceeds to save DRAM fail data to UEFI variables.
@param[in] ImageHandle The firmware allocated handle for the EFI image.
@param[in] SystemTable A pointer to the EFI System Table.
@retval EFI_SUCCESS Variable data written successfully.
@retval EFI_NOT_FOUND DRAM fail data HOB not found.
@retval EFI_INVALID_PARAMETER Invalid parameter.
@retval others Error from sub-functions.
**/
EFI_STATUS
EFIAPI
SaveDramFailDataToVariable (
IN EFI_SYSTEM_TABLE *SystemTable
)
{
UINT8 DimmIndex;
EFI_BOOT_SERVICES *BootServices;
EFI_RUNTIME_SERVICES *RuntimeServices;
UINTN SlotIndex;
UINT8 *SlotBuffer[5];
VOID *HobList;
EFI_HOB_GUID_TYPE *GuidHob;
UINT8 EntryIndex;
UINT8 *HobData;
UINT8 NumDimms;
UINT16 EntryCount;
INT32 LocalEntryIndex;
INT32 DimmEntryIndex;
INT32 DimmGroupIndex;
INT32 GroupEntryIndex;
INT32 ByteIndex;
UINT16 SlotEntryCount[5];
UINT8 SlotData[5][42][3][8]; // 5 DIMMs x 42 x 3 x 8 bytes
UINT8 BitmapIndex;
UINT8 BitPosition;
UINT8 *BytePtr;
UINT8 *DestByte;
UINT16 *DestWord;
UINT8 DimmCount;
UINT8 EntryN[12];
UINT8 EntryC[12];
UINT8 EntryD[12];
UINT8 EntryR[12];
UINT8 EntryCID[12];
UINT8 EntryBG[12];
UINT8 EntryBA[12];
UINT8 EntryDQ[12];
UINT8 EntryTemp[12];
UINT8 EntryFlags[12];
UINT8 EntryCol[12];
STEP_RESULT_HEADER StepResultHeader;
INT32 GroupCount;
INT32 InnerCount;
DimmIndex = 0;
if (gSystemTable != NULL) {
BootServices = gBootServices;
} else {
gSystemTable = SystemTable;
BootServices = SystemTable->BootServices;
RuntimeServices = SystemTable->RuntimeServices;
gBootServices = BootServices;
gRuntimeServices = RuntimeServices;
}
//
// Zero out all slot data buffers
//
ZeroMem (&StepResultHeader, sizeof(StepResultHeader));
for (SlotIndex = 0; SlotIndex < 5; SlotIndex++) {
ZeroMem (&SlotData[SlotIndex], sizeof(SlotData[SlotIndex]));
}
//
// Get HOB list and locate the DRAM fail data HOB
//
HobList = GetHobList ();
GuidHob = GetNextGuidHob (HobList);
if (GuidHob == NULL) {
DEBUG ((EFI_D_ERROR, "GuidHob != ((void *) 0)\n"));
return EFI_NOT_FOUND;
}
//
// Walk HOBs to find the matching GUID
//
do {
if (CompareGuid (&GuidHob->Name, &gDramFailDataHobGuid)) {
break;
}
GuidHob = GetNextGuidHob ((UINT8 *)GuidHob + GuidHob->Header.HobLength);
} while (GuidHob != NULL);
if (GuidHob == NULL) {
DEBUG ((EFI_D_ERROR, "GuidHob != ((void *) 0)\n"));
return EFI_NOT_FOUND;
}
//
// HOB data starts at offset 24 (EFI_HOB_GUID_TYPE header is 24 bytes)
// Each entry is 18 bytes
//
HobData = (UINT8 *)GuidHob + sizeof(EFI_HOB_GUID_TYPE);
//
// Parse up to MAX_DRAM_FAIL_ENTRIES entries, distribute across up to 5 DIMMs
//
EntryIndex = 0;
NumDimms = 0;
EntryCount = 0;
DimmEntryIndex = 0;
DimmGroupIndex = 0;
ZeroMem (SlotEntryCount, sizeof(SlotEntryCount));
for (EntryIndex = 0; EntryIndex < MAX_DRAM_FAIL_ENTRIES; EntryIndex++) {
if (NumDimms >= MAX_DIMM_SLOTS) {
break;
}
if (HobData[EntryIndex * 18 + 318 + 0] != 0xAA) {
continue;
}
//
// Extract full DRAM address from HOB entry
//
EntryN[EntryCount] = HobData[EntryIndex * 18 + 318 + 1];
EntryC[EntryCount] = HobData[EntryIndex * 18 + 318 + 2];
EntryD[EntryCount] = HobData[EntryIndex * 18 + 318 + 3];
EntryR[EntryCount] = HobData[EntryIndex * 18 + 318 + 4];
EntryCID[EntryCount] = HobData[EntryIndex * 18 + 318 + 5];
*(UINT32 *)&EntryBG[EntryCount] = *(UINT32 *)&HobData[EntryIndex * 18 + 318 + 6];
*(UINT16 *)&EntryBA[EntryCount] = *(UINT16 *)&HobData[EntryIndex * 18 + 318 + 10];
EntryDQ[EntryCount] = HobData[EntryIndex * 18 + 318 + 12];
EntryTemp[EntryCount] = HobData[EntryIndex * 18 + 318 + 13];
EntryFlags[EntryCount] = HobData[EntryIndex * 18 + 318 + 15];
EntryCol[EntryCount] = HobData[EntryIndex * 18 + 318 + 17];
DimmGroupIndex = DimmEntryIndex * 3 + 42 * (UINTN)NumDimms;
SlotData[NumDimms][DimmEntryIndex][0][0] = EntryN[EntryCount];
SlotData[NumDimms][DimmEntryIndex][0][1] = EntryC[EntryCount];
SlotData[NumDimms][DimmEntryIndex][0][2] = EntryD[EntryCount];
SlotData[NumDimms][DimmEntryIndex][0][3] = EntryR[EntryCount];
SlotData[NumDimms][DimmEntryIndex][0][4] = EntryCID[EntryCount];
*(UINT32 *)&SlotData[NumDimms][DimmEntryIndex][0][8] = *(UINT32 *)&EntryBG[EntryCount];
*(UINT16 *)&SlotData[NumDimms][DimmEntryIndex][0][12] = *(UINT16 *)&EntryBA[EntryCount];
SlotData[NumDimms][DimmEntryIndex][0][5] = EntryDQ[EntryCount];
SlotData[NumDimms][DimmEntryIndex][0][6] = EntryTemp[EntryCount];
SlotData[NumDimms][DimmEntryIndex][0][7] = EntryFlags[EntryCount];
SlotData[NumDimms][DimmEntryIndex][0][14] = EntryFlags[EntryCount];
SlotData[NumDimms][DimmEntryIndex][0][15] = EntryCol[EntryCount];
SlotData[NumDimms][DimmEntryIndex][0][16] = EntryCol[EntryCount];
//
// Log the failure
//
DebugBspPrint (
-1,
"STEP - Save DRAM fail data to variable. : [FailedPatternBitMask 0x%X] "
"N%d.C%d.D%d. R%d.CID%d.BG%d.BA%d.ROW:0x%05x.COL:0x%03x.DQ%02d.Temp%02d'C",
EntryCol[EntryCount],
EntryN[EntryCount],
EntryC[EntryCount],
EntryD[EntryCount],
EntryR[EntryCount],
EntryCID[EntryCount],
EntryDQ[EntryCount],
EntryTemp[EntryCount],
*(UINT32 *)&EntryBG[EntryCount],
*(UINT16 *)&EntryBA[EntryCount],
EntryFlags[EntryCount],
EntryCol[EntryCount]
);
//
// Log PPR result
//
if (EntryFlags[EntryCount] == 1) {
DebugBspPrint (-1, "PPR:Done(PASS)\n");
} else if (EntryFlags[EntryCount] == 2) {
DebugBspPrint (-1, "PPR:Done(FAIL)\n");
} else {
DebugBspPrint (-1, "\n");
}
//
// Advance entry counters
//
DimmGroupIndex++;
EntryCount++;
DimmEntryIndex++;
if (DimmEntryIndex >= 42) {
DimmEntryIndex = 0;
NumDimms++;
}
}
//
// Process bitmap pattern data (second pass)
//
for (BitPosition = 0; BitPosition < 2; BitPosition++) {
if (BitPosition < 1 || ...) { // Bit test logic
BitPosition++;
}
}
//
// Write STEP_RESULT variable (184 bytes header)
//
StepResultHeader.EntryCount = EntryCount;
StepResultHeader.Flags = 0x03030202;
StepResultHeader.Flags2 = 0x0206;
gRuntimeServices->SetVariable (
STEP_RESULT_VARIABLE_NAME,
&gEfiVariableGuid,
EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS,
sizeof(STEP_RESULT_HEADER),
&StepResultHeader
);
//
// Write STEP_RESULT_NUM00 through NUM04
//
gRuntimeServices->SetVariable (
STEP_RESULT_NUM00_VARIABLE_NAME,
&gEfiVariableGuid,
EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS,
DIMM_SLOT_DATA_SIZE,
SlotData[0]
);
gRuntimeServices->SetVariable (
STEP_RESULT_NUM01_VARIABLE_NAME,
&gEfiVariableGuid,
EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS,
DIMM_SLOT_DATA_SIZE,
SlotData[1]
);
gRuntimeServices->SetVariable (
STEP_RESULT_NUM02_VARIABLE_NAME,
&gEfiVariableGuid,
EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS,
DIMM_SLOT_DATA_SIZE,
SlotData[2]
);
gRuntimeServices->SetVariable (
STEP_RESULT_NUM03_VARIABLE_NAME,
&gEfiVariableGuid,
EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS,
DIMM_SLOT_DATA_SIZE,
SlotData[3]
);
gRuntimeServices->SetVariable (
STEP_RESULT_NUM04_VARIABLE_NAME,
&gEfiVariableGuid,
EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS,
DIMM_SLOT_DATA_SIZE,
SlotData[4]
);
return EFI_SUCCESS;
}
/**
Driver entry point.
Initializes global service table pointers and system configuration table
access, then proceeds to save DRAM fail data to UEFI variables.
@param[in] ImageHandle The firmware allocated handle for the EFI image.
@param[in] SystemTable A pointer to the EFI System Table.
@retval EFI_SUCCESS Variable data written successfully.
@retval EFI_NOT_FOUND DRAM fail data HOB not found.
@retval EFI_INVALID_PARAMETER Invalid parameter.
@retval others Error from sub-functions.
**/
EFI_STATUS
EFIAPI
EntryDriverInit (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status;
//
// Save UEFI handles and service table pointers
//
gImageHandle = ImageHandle;
ASSERT (ImageHandle != NULL);
gSystemTable = (UINTN)SystemTable;
ASSERT (SystemTable != NULL);
gBootServices = SystemTable->BootServices;
ASSERT (gBootServices != NULL);
gRuntimeServices = SystemTable->RuntimeServices;
ASSERT (gRuntimeServices != NULL);
//
// Initialize HOB list pointer and DXE Services Table
//
GetHobList ();
//
// Locate DXE Services Table
//
Status = GetDxeServicesTable (&gDxeServicesTable);
if (EFI_ERROR (Status)) {
DEBUG ((EFI_D_ERROR, "ASSERT_EFI_ERROR (Status = %r)\n", Status));
ASSERT_EFI_ERROR (FALSE);
}
ASSERT (gDxeServicesTable != NULL);
if (EFI_ERROR (Status)) {
DEBUG ((EFI_D_ERROR, "ASSERT_EFI_ERROR (Status = %r)\n", Status));
ASSERT_EFI_ERROR (FALSE);
}
return EFI_SUCCESS;
}
/**
UEFI entry point for SecVariableControl driver.
@param[in] ImageHandle The firmware allocated handle for the EFI image.
@param[in] SystemTable A pointer to the EFI System Table.
@retval EFI_SUCCESS Variable data written successfully.
@retval EFI_NOT_FOUND DRAM fail data HOB not found.
@retval others Error from sub-functions.
**/
EFI_STATUS
EFIAPI
ModuleEntryPoint (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EntryDriverInit (ImageHandle, SystemTable);
return SaveDramFailDataToVariable (SystemTable);
}