Newer
Older
AMI-Aptio-BIOS-Reversed / SmiFlashDxe / SmiFlashDxe.c
@Ajax Dong Ajax Dong 2 days ago 19 KB Init
/** @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;
}