/** @file
SmiFlashDxe.c - SMM SPI Flash Driver
HR650X (Xeon Scalable) BIOS
This module implements an SMM driver that:
1. Locates flash descriptor HOB via SMM configuration table
2. Registers an "SFPFREC" SMI handler for SPI flash protection
3. Provides debug print/assert through an SMM debug protocol
4. Supports 1MB (0x12) and 2MB (0x20) SPI flash parts
The SMI handler is registered with the GUID:
{01368881-C4AD-4B1D-B631-D57A8EC8DB6B}
and the handler name is "SFPFREC" (SMI Flash Protection / Firmware RECovery).
Copyright (c) Lenovo. All rights reserved.
**/
#include "SmiFlashDxe.h"
// ===========================================================================
// Global Variables (.data section, zero-initialized)
// ===========================================================================
/// @brief Function pointer table for init callbacks (null-terminated).
/// At 0x3030. Called in sequence from SmiFlashEntryPoint.
VOID (*gInitCallbacks[])() = { NULL };
/// @brief Cache of SMM System Table pointer (0x3038).
VOID *gSystemTableCopy = NULL;
/// @brief Cache of SMM Boot Services pointer (0x3040).
VOID *gBootServicesCopy = NULL;
/// @brief Image handle for this module (0x3048).
VOID *gImageHandle = NULL;
/// @brief Cache of SMM Runtime Services pointer (0x3050).
VOID *gRuntimeServicesCopy = NULL;
/// @brief Secondary cache of Runtime Services (0x3058), used for unregister.
VOID *gRuntimeServicesUnreg = NULL;
/// @brief Cleanup event for virtual address change (0x3060).
EFI_EVENT gCleanupEvent = NULL;
/// @brief Protocol notify event for SMM base protocol (0x3068).
EFI_EVENT gProtocolNotifyEvent = NULL;
/// @brief Cache of Boot Services for debug protocol access (0x3070).
VOID *gBootServicesDebug = NULL;
/// @brief Debug protocol interface pointer (0x3078), lazy-init.
SMM_DEBUG_PROTOCOL *gDebugProtocol = NULL;
/// @brief Cached HOB list pointer (0x3080).
VOID *gHobList = NULL;
/// @brief Local copy of SMM Boot Services (0x3088).
EFI_BOOT_SERVICES *gSmmBs = NULL;
/// @brief Local copy of SMM Runtime Services (0x3090).
EFI_RUNTIME_SERVICES *gSmmRt = NULL;
/// @brief Local copy of SMM System Table (0x3098).
EFI_SYSTEM_TABLE *gSmst = NULL;
/// @brief Debug level byte, read from CMOS 0x4B (0x30A0).
UINT8 gDebugLevel = 0;
// ===========================================================================
// .rdata Data (RO data placed at known addresses in .rdata)
// ===========================================================================
/// @brief Debug Protocol GUID (at 0x3000 in .rdata).
STATIC CONST EFI_GUID mDebugProtocolGuid = DEBUG_PROTOCOL_GUID;
/// @brief Flash descriptor HOB GUID - target for matching (at 0x3010).
STATIC CONST EFI_GUID mFlashHobGuid1 = FLASH_DESCRIPTOR_HOB_GUID;
/// @brief Flash descriptor HOB GUID - duplicate copy for alignment (at 0x3020).
STATIC CONST EFI_GUID mFlashHobGuid2 = FLASH_DESCRIPTOR_HOB_GUID;
/// @brief SMI handler GUID for "SFPFREC" handler registration.
STATIC CONST EFI_GUID mSpiFlashSmiGuid = SMI_HANDLER_SPI_FLASH_GUID;
/// @brief Wide string "SFPFREC" used as SMI handler name (at 0x2040).
STATIC CONST CHAR16 mSmiHandlerName[] = L"SFPFREC";
// ===========================================================================
// Library Support Functions (linked from BaseLib / BaseMemoryLib)
// ===========================================================================
/**
Read a potentially unaligned 64-bit value from memory.
@param[in] Buffer Pointer to read from.
@return The 64-bit value.
**/
UINT64
EFIAPI
ReadUnaligned64 (
IN CONST VOID *Buffer
)
{
ASSERT (Buffer != NULL);
return *(CONST UINT64 *)Buffer;
}
/**
Set memory buffer to a fill byte (memset).
Optimized using 32-bit stores when the buffer and size permit.
@param[out] Buffer Buffer to fill.
@param[in] Size Number of bytes to fill.
@param[in] Value Fill value.
@return Pointer to Buffer.
**/
VOID *
EFIAPI
SetMem (
OUT VOID *Buffer,
IN UINTN Size,
IN UINT8 Value
)
{
volatile UINT8 *Ptr8;
UINT32 Fill32;
UINT32 *Ptr32;
UINTN Count;
Fill32 = (Value << 24) | (Value << 16) | (Value << 8) | Value;
Ptr32 = (UINT32 *)Buffer;
Ptr8 = (volatile UINT8 *)Buffer;
// Align to 4-byte boundary
if (Size >= 4 && ((UINTN)Ptr32 & 3)) {
memset (Ptr8, Value, 4 - ((UINTN)Ptr8 & 3));
Ptr8 += 4 - ((UINTN)Ptr8 & 3);
Ptr32 = (UINT32 *)Ptr8;
Size -= 4 - ((UINTN)Ptr8 & 3);
}
// Fill 4-byte chunks
Count = Size >> 2;
while (Count--) {
*Ptr32++ = Fill32;
}
// Remaining bytes
memset (Ptr32, Value, Size & 3);
return Buffer;
}
/**
Copy memory with overlap handling (memcpy).
@param[out] Destination Target buffer.
@param[in] Source Source buffer.
@param[in] Length Number of bytes to copy.
@return Pointer to Destination.
**/
VOID *
EFIAPI
CopyMem (
OUT VOID *Destination,
IN CONST VOID *Source,
IN UINTN Length
)
{
UINT8 *Dest8;
CONST UINT8 *Src8;
UINTN AlignDelta;
BOOLEAN Backward;
Dest8 = (UINT8 *)Destination;
Src8 = (CONST UINT8 *)Source;
Backward = FALSE;
// Check for overlap: if Source < Destination and overlap exists,
// copy from end to avoid corruption.
if (Src8 < Dest8 && (Src8 + Length) > Dest8) {
Src8 += Length;
Dest8 += Length;
Backward = TRUE;
}
// For small copies or overlapping alignment, go byte-by-byte
if (Length < 8 || (UINTN)(Src8 - Dest8) < 8) {
goto FinalByteCopy;
}
// Align both pointers to 8-byte boundary
AlignDelta = (UINTN)Src8 & 7;
if (AlignDelta) {
if (!Backward) {
AlignDelta = 8 - AlignDelta;
}
qmemcpy (Dest8, Src8, AlignDelta);
Src8 += AlignDelta;
Dest8 += AlignDelta;
Length -= AlignDelta;
}
// Copy 8-byte aligned chunks
qmemcpy (Dest8, Src8, Length & ~7);
Src8 += Length & ~7;
Dest8 += Length & ~7;
Length &= 7;
FinalByteCopy:
if (Length) {
if (Backward) {
Src8 += 8;
Dest8 += 8;
}
qmemcpy (Dest8, Src8, Length);
}
return Destination;
}
// ===========================================================================
// Library Support: HobLib
// ===========================================================================
/**
Compare two EFI_GUIDs byte-by-byte. Handles unaligned comparisons
by processing in 8-byte words when alignment permits.
@param[in] Guid1 First GUID.
@param[in] Guid2 Second GUID.
@return 0 if equal, non-zero if different.
**/
INTN
EFIAPI
CompareGuid (
IN CONST GUID *Guid1,
IN CONST GUID *Guid2
)
{
CONST UINT64 *Left64;
CONST UINT64 *Right64;
UINTN Offset;
Left64 = (CONST UINT64 *)Guid1;
Right64 = (CONST UINT64 *)Guid2;
// Handle unaligned head
if (((UINTN)Guid1 & 7) != 0 && ((UINTN)Guid1 & 7) == ((UINTN)Guid2 & 7)) {
for (Offset = 8 - ((UINTN)Guid1 & 7); Offset > 0; Offset--) {
if (*(CONST UINT8 *)Guid1 != *(CONST UINT8 *)Guid2) {
return *(CONST UINT8 *)Guid1 - *(CONST UINT8 *)Guid2;
}
Guid1 = (CONST GUID *)((UINT8 *)Guid1 + 1);
Guid2 = (CONST GUID *)((UINT8 *)Guid2 + 1);
}
}
// Compare in 8-byte words
Left64 = (CONST UINT64 *)Guid1;
Right64 = (CONST UINT64 *)Guid2;
while ((UINTN)Left64 < (UINTN)Guid1 + sizeof (GUID) &&
*Left64 == *Right64) {
Left64++;
Right64++;
}
// Remaining byte comparison
Guid1 = (CONST GUID *)Left64;
Guid2 = (CONST GUID *)Right64;
while ((UINTN)Guid1 < (UINTN)Left64 + sizeof (GUID)) {
if (*(CONST UINT8 *)Guid1 != *(CONST UINT8 *)Guid2) {
return *(CONST UINT8 *)Guid1 - *(CONST UINT8 *)Guid2;
}
Guid1 = (CONST GUID *)((UINT8 *)Guid1 + 1);
Guid2 = (CONST GUID *)((UINT8 *)Guid2 + 1);
}
return 0;
}
/**
Check if a HOB entry matches the expected flash descriptor GUID.
Compares two 64-bit halves of the HOB entry GUID against the
cached target GUIDs at mFlashHobGuid1 and mFlashHobGuid2.
@param[in] HobStart Start of HOB region (unused).
@param[in] Buffer Pointer to HOB entry to check.
@retval TRUE The HOB entry GUID matches.
@retval FALSE The HOB entry GUID does not match.
**/
BOOLEAN
EFIAPI
IsHobGuidMatch (
IN VOID *HobStart,
IN VOID *Buffer
)
{
UINT64 TargetLow;
UINT64 TargetHigh;
UINT64 HobLow;
UINT64 HobHigh;
HobLow = ReadUnaligned64 (Buffer); // HOB GUID low half
HobHigh = ReadUnaligned64 ((CONST UINT64 *)Buffer + 1); // HOB GUID high half
TargetLow = ReadUnaligned64 (&mFlashHobGuid1); // Target low half
TargetHigh = ReadUnaligned64 (&mFlashHobGuid2); // Target high half (duplicate)
return (HobLow == TargetLow) && (HobHigh == TargetHigh);
}
/**
Walk the SMM System Table configuration table to find the flash
descriptor HOB entry matching FLASH_DESCRIPTOR_HOB_GUID.
The configuration table is at:
SystemTable_0 + 0x68 -> Number of entries (EntryCount)
SystemTable_0 + 0x70 -> Pointer to entry array
Each entry is 24 bytes: 16-byte GUID + 8-byte value.
@param[in] Unknown Context passed from constructor (unused).
@return Pointer to the matching HOB entry value, or NULL.
**/
VOID *
EFIAPI
GetHobList (
IN UINT64 Unknown
)
{
UINT64 EntryCount;
SMI_FLASH_HOB_ENTRY *Entries;
UINT64 Index;
if (gHobList != NULL) {
return gHobList;
}
gHobList = NULL;
EntryCount = *(UINT64 *)((UINT8 *)gSystemTableCopy + 0x68);
Entries = *(SMI_FLASH_HOB_ENTRY **)((UINT8 *)gSystemTableCopy + 0x70);
if (EntryCount == 0) {
DEBUG ((DEBUG_ERROR, "\nASSERT_EFI_ERROR (Status = %r)\n", EFI_NOT_FOUND));
ASSERT (!EFI_ERROR (EFI_NOT_FOUND));
if (gHobList == NULL) {
ASSERT (gHobList != NULL);
}
return gHobList;
}
for (Index = 0; Index < EntryCount; Index++) {
if (IsHobGuidMatch (NULL, &Entries[Index].Guid)) {
gHobList = (VOID *)Entries[Index].Value;
break;
}
}
if (gHobList == NULL) {
ASSERT (gHobList != NULL);
}
return gHobList;
}
// ===========================================================================
// SMM Debug Protocol (library-level support)
// ===========================================================================
/**
Initialize the debug protocol interface pointer (lazy-init).
Raises TPL to TPL_NOTIFY before calling LocateProtocol to ensure
thread safety in SMM. Caches the result in gDebugProtocol (0x3078).
@return Pointer to SMM_DEBUG_PROTOCOL interface, or NULL.
**/
SMM_DEBUG_PROTOCOL *
EFIAPI
GetDebugProtocolInterface (
VOID
)
{
if (gDebugProtocol != NULL) {
return gDebugProtocol;
}
if (gBootServicesDebug) {
gBootServicesDebug->RaiseTPL (TPL_NOTIFY);
gBootServicesDebug->RestoreTPL (TPL_NOTIFY);
if (gBootServicesDebug->LocateProtocol (
&mDebugProtocolGuid,
NULL,
(VOID **)&gDebugProtocol
) < 0) {
gDebugProtocol = NULL;
}
}
return gDebugProtocol;
}
/**
Debug print with level filtering.
Reads the current debug level from CMOS register 0x4B.
Only outputs if the message level passes the filter.
@param[in] ErrorLevel Error level bitmask.
@param[in] Format Format string.
@param[in] ... Variable arguments.
**/
UINTN
EFIAPI
DebugPrintWithLevel (
IN UINTN ErrorLevel,
IN CONST CHAR8 *Format,
...
)
{
SMM_DEBUG_PROTOCOL *Protocol;
UINT8 CmosLevel;
UINTN Filter;
VA_LIST Va;
VA_START (Va, Format);
Protocol = GetDebugProtocolInterface ();
Filter = 0;
if (Protocol != NULL) {
// Read debug level from CMOS
IoWrite8 (CMOS_INDEX_PORT, CMOS_DEBUG_LEVEL_REGISTER);
CmosLevel = IoRead8 (CMOS_DATA_PORT);
if (CmosLevel > CMOS_DEBUG_LEVEL_MAX) {
CmosLevel = CMOS_DEBUG_LEVEL_MAX;
}
if (CmosLevel > 0) {
Filter = DEBUG_LEVEL_ERROR;
if (CmosLevel == 1) {
Filter = DEBUG_LEVEL_ERROR;
}
}
if (Filter & ErrorLevel) {
Protocol->DebugPrint (ErrorLevel, Format, Va);
}
}
VA_END (Va);
return Filter;
}
/**
Assert handler. Calls the debug protocol assert function if available.
@param[in] FileName Source file name.
@param[in] LineNumber Source line number.
@param[in] Description Assert condition.
**/
VOID
EFIAPI
DebugAssert (
IN CONST CHAR8 *FileName,
IN UINTN LineNumber,
IN CONST CHAR8 *Description
)
{
SMM_DEBUG_PROTOCOL *Protocol;
Protocol = GetDebugProtocolInterface ();
if (Protocol != NULL) {
Protocol->DebugAssert (FileName, LineNumber, Description);
}
}
// ===========================================================================
// Boot Services Table Constructor
// ===========================================================================
/**
Library constructor for UefiBootServicesTableLib.
Caches ImageHandle, SystemTable, BootServices, and RuntimeServices.
Creates two events:
1. Cleanup event (EVT_NOTIFY_SIGNAL | EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE)
with notify function SmiHandlerFlashUnregister.
2. Protocol notify event registered for SMM base protocol
(0x60000002) with notify function SmiHandlerUnload.
Then calls GetHobList to initialize the HOB cache.
@param[in] ImageHandle Image handle.
@param[in] SystemTable System table.
@return NULL / 0 (status from GetHobList).
**/
VOID
EFIAPI
UefiBootServicesTableLibConstructor (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
gImageHandle = ImageHandle;
ASSERT (ImageHandle != NULL);
gSystemTableCopy = (VOID *)SystemTable;
ASSERT (SystemTable != NULL);
gBootServicesCopy = (VOID *)SystemTable->BootServices;
ASSERT (gBootServicesCopy != NULL);
gRuntimeServicesCopy = (VOID *)SystemTable->RuntimeServices;
ASSERT (gRuntimeServicesCopy != NULL);
// Save secondary copies for SMI handler management
gRuntimeServicesUnreg = (VOID *)SystemTable->RuntimeServices;
gBootServicesDebug = (VOID *)SystemTable->BootServices;
// Create cleanup event
SystemTable->BootServices->CreateEvent (
EVT_SMI_CLEANUP,
TPL_NOTIFY,
SmiHandlerFlashUnregister,
NULL,
&gCleanupEvent
);
// Register protocol notify for SMM base protocol
SystemTable->BootServices->RegisterProtocolNotify (
(EFI_GUID *)SMM_BASE2_PROTOCOL_NOTIFY,
gProtocolNotifyEvent,
&gProtocolNotifyEvent
);
GetHobList (0);
}
// ===========================================================================
// SMI Handler Entry Point
// ===========================================================================
/**
Main SMM flash driver entry point.
This function is called after the constructor has cached the service
table pointers. It:
1. Locates the flash descriptor HOB via the SMM configuration table
2. Checks HOB region type for 1MB (18) or 2MB (32) flash support
3. Allocates a 64-byte SMM communication buffer
4. Registers the "SFPFREC" SMI handler
5. Calls all registered init callbacks (gInitCallbacks[])
@param[in] ImageHandle EFI image handle.
@param[in] SystemTable SMM System Table.
@return EFI_SUCCESS or error status.
**/
EFI_STATUS
EFIAPI
SmiFlashEntryPoint (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status;
SMI_FLASH_HOB_TABLE *HobTable;
UINT64 Index;
UINT32 RegionType;
VOID *CommBuffer;
UINTN CallbackIndex;
// Store SystemTable locally if not already done
if (gSmst == NULL) {
gSmst = SystemTable;
gSmmBs = SystemTable->BootServices;
gSmmRt = SystemTable->RuntimeServices;
}
//
// Scan configuration table for flash descriptor HOB
// The HOB table is stored as a flat array accessible via the
// SMM System Table configuration table.
//
// SystemTable+0x68 = pointer to flat HOB table
// SystemTable+0x70 = pointer to HOB entry array
//
RegionType = 0;
if (gSystemTableCopy != NULL) {
// Walk HOB entries looking for FLASH_DESCRIPTOR_HOB_GUID
Index = *(UINT64 *)((UINT8 *)gSystemTableCopy + 0x68);
if (Index > 0) {
HobTable = (SMI_FLASH_HOB_TABLE *)gSystemTableCopy;
while (Index > 0) {
if (!CompareGuid (
&HobTable->Entries->Guid,
&mFlashHobGuid1
)) {
RegionType = *(UINT32 *)(HobTable->Entries->Value + 12);
break;
}
HobTable->Entries++;
Index--;
}
}
}
//
// Only proceed if flash region is 1MB (0x12) or 2MB (0x20)
//
if (RegionType == FLASH_REGION_SIZE_1MB ||
RegionType == FLASH_REGION_SIZE_2MB) {
//
// Allocate and zero SMM communication buffer
//
CommBuffer = NULL;
Status = gSmmBs->AllocatePool (
SMI_POOL_TYPE,
SMI_COMM_BUFFER_SIZE,
&CommBuffer
);
if (!EFI_ERROR (Status)) {
SetMem (CommBuffer, SMI_COMM_BUFFER_SIZE, 0);
//
// Register "SFPFREC" SMI handler
// RuntimeServices + 0x58 = SmiHandlerRegister
//
gSmmRt->SmiHandlerRegister (
&mSpiFlashSmiGuid,
(VOID **)&CommBuffer
);
//
// NOTE: The above call also passes the handler name "SFPFREC"
// internally, but the decompiler shows it as part of the
// SmiHandlerRegister varargs call chain.
//
}
}
//
// Call all registered init callbacks
//
CallbackIndex = 0;
while (gInitCallbacks[CallbackIndex] != NULL) {
gInitCallbacks[CallbackIndex] ();
CallbackIndex++;
}
return EFI_SUCCESS;
}
// ===========================================================================
// Entry Point (ModuleEntryPoint)
// ===========================================================================
/**
Module entry point. Called by the DXE/SMM dispatcher.
@param[in] ImageHandle Image handle.
@param[in] SystemTable System table.
@return EFI_SUCCESS or error status from SmiFlashEntryPoint.
**/
EFI_STATUS
EFIAPI
ModuleEntryPoint (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status;
VOID (*EntryFunc)(VOID);
//
// Call library constructor to initialize service table pointers
//
UefiBootServicesTableLibConstructor (ImageHandle, SystemTable);
//
// Call main SMM flash entry point
//
Status = SmiFlashEntryPoint (ImageHandle, SystemTable);
//
// If entry point failed, clean up events
//
if (EFI_ERROR (Status)) {
gSmmBs->CloseEvent (gCleanupEvent);
gSmmBs->CloseEvent (gProtocolNotifyEvent);
}
return Status;
}
// ===========================================================================
// SMI Handler Callbacks
// ===========================================================================
/**
Cleanup callback: clears the BootServices pointer.
Called on virtual address change notification.
@param[in] Event Event handle (unused).
@param[in] Context Context (unused).
**/
VOID
EFIAPI
SmiHandlerFlashUnregister (
IN EFI_EVENT Event,
IN VOID *Context
)
{
gBootServicesDebug = NULL;
}
/**
Unload callback: unregisters the SFPFREC SMI handler.
Called on ReadyToBoot or image unload.
@return EFI_SUCCESS or error from SmiHandlerUnRegister.
**/
EFI_STATUS
EFIAPI
SmiHandlerUnload (
VOID
)
{
//
// RuntimeServices + 0x40 = SmiHandlerUnRegister
//
if (gRuntimeServicesUnreg != NULL) {
return ((EFI_RUNTIME_SERVICES *)gRuntimeServicesUnreg)->SmiHandlerUnRegister (
&mSpiFlashSmiGuid
);
}
return EFI_SUCCESS;
}