/**
* S3SaveStateDxe.c
* Lenovo HR650X BIOS - S3 Save State DXE Driver
*
* Full decompilation of S3SaveStateDxe.efi from Lenovo HR650X BIOS.
* SHA256: 807650fb83ebcd72a3d8c5b2c4e201dbcf5af25150a6d0fe62296f1030255b8e
*
* This module implements:
* - ACPI S3 Context Save (AcpiS3ContextSave.c)
* - PiDxeS3BootScriptLib (BootScriptSave.c)
* - SmmLockBoxDxeLib
* - Various UEFI library stubs (PCD, HOB, PCIe, debug)
*
* The driver saves ACPI S3 resume context (FACS, IDTR, page tables, boot script
* stack) into the SMM LockBox and provides EFI_S3_SAVE_STATE protocol for
* recording boot script entries in the pre-SMM phase. On SmmReadyToLock, it
* transitions the boot script buffer to SMM-owned memory.
*/
#include <Uefi.h>
#include <Protocol/SmmCommunication.h>
#include <Protocol/S3SaveState.h>
#include <Protocol/S3SmmSaveState.h>
#include <Guid/Acpi.h>
#include <Guid/S3SaveState.h>
#include <Library/BaseMemoryLib.h>
#include <Library/BaseLib.h>
#include <Library/DebugLib.h>
/*===========================================================================
* Global Data
*===========================================================================*/
// EFI System Table (from UefiBootServicesTableLib)
EFI_SYSTEM_TABLE *gSystemTable = NULL; // 0x5458
EFI_BOOT_SERVICES *gBootServices = NULL; // 0x5460
EFI_HANDLE gImageHandle = NULL; // 0x5468
EFI_RUNTIME_SERVICES *gRuntimeServices = NULL; // 0x5470
// Cached protocol pointers
VOID *gDebugPortProtocol = NULL; // 0x5478 (DebugPort)
VOID *gPcdProtocol = NULL; // 0x5480 (PCD)
VOID *gHobList = NULL; // 0x5488 (HOB list)
UINT64 gPciExpressBaseAddress = 0; // 0x5490 (PCIe MMIO base)
VOID *gSmmCommRegion = NULL; // 0x5498 (SMM Comm Region)
VOID *gSmmCommunication = NULL; // 0x54A0 (SMM Comm protocol)
VOID *gS3SaveStateReg = NULL; // 0x54A8
UINT8 gBootScriptInSmm = 0; // 0x54B0
VOID *mEventDxeSmmReadyToLock = NULL; // 0x54B8
VOID *gS3SmmSaveStateProtocol = NULL; // 0x54C0
VOID *gS3SaveStateNotify = NULL; // 0x54C8
VOID *gS3SmmSaveStateNotify = NULL; // 0x54D0
UINT8 gBootScriptInSmm2 = 0; // 0x54D8
BOOT_SCRIPT_TABLE *gBootScriptTable = NULL; // 0x54E0
BOOT_SCRIPT_TABLE *gBootScriptTableSmm = NULL; // 0x54E8
/*===========================================================================
* GUID references (from .rdata) -- symbol names only; actual GUIDs in raw bytes
*===========================================================================*/
extern EFI_GUID gEfiSmmCommunicationRegionTableGuid; // unk_52C0
extern EFI_GUID gEfiDebugPortProtocolGuid; // unk_52D0
extern EFI_GUID gEfiSmmCommunicationProtocolGuid; // unk_52E0
extern EFI_GUID gAcpiS3ContextGuid; // unk_52F0, 0x5360, 0x5400
extern EFI_GUID gEfiDxeSmmReadyToLockProtocolGuid; // unk_5300
extern EFI_GUID gEfiLockBoxProtocolGuid; // unk_5310
extern EFI_GUID gEfiPcdProtocolGuid; // unk_5320
extern EFI_GUID gEfiS3SaveStateProtocolGuid; // unk_5330, 0x5340
extern EFI_GUID gEfiAcpiTableGuid; // unk_5350
extern EFI_GUID gEfiAcpiTableGuid2; // unk_53B0
extern EFI_GUID gEfiS3BootScriptGuid; // unk_5380, 0x5410, 0x5420, 0x5430, 0x5440
extern EFI_GUID gEfiS3SmmSaveStateProtocolGuid; // unk_5390
extern EFI_GUID gEfiHobListGuid; // unk_53A0
/*===========================================================================
* Driver Entry Point
*===========================================================================*/
/**
* _ModuleEntryPoint (0x404)
*
* Standard UEFI DXE driver entry point. Calls ProcessLibraryConstructorList
* (sub_4B4) to initialize library globals, then registers the main driver
* callback (sub_1C5C / AcpiS3ContextInitialize) as a timer event.
* Finally installs the EFI_S3_SAVE_STATE protocol interface.
*/
EFI_STATUS
EFIAPI
DriverEntryPoint (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status;
// Initialize library globals (gImageHandle, gST, gBS, gRT, etc.)
ProcessLibraryConstructorList (ImageHandle, SystemTable);
// Schedule AcpiS3ContextInitialize as a timer event (10ms, TPL_CALLBACK)
Status = gBootServices->SetTimer (
gBootServices->CreateEvent (EVT_NOTIFY_SIGNAL, TPL_CALLBACK, AcpiS3ContextInitialize, NULL, NULL),
TimerRelative,
10000 // 10 ms
);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_ERROR, "\nASSERT_EFI_ERROR (Status = %r)\n", Status));
ASSERT_EFI_ERROR (FALSE);
}
// Install the EFI S3 Save State protocol
Status = gBootServices->InstallProtocolInterface (
&gImageHandle,
&gEfiS3SaveStateProtocolGuid,
EFI_NATIVE_INTERFACE,
&mS3SaveStateProtocol
);
if (EFI_ERROR (Status)) {
CpuDeadLoop ();
}
return Status;
}
/*===========================================================================
* Library Constructor (ProcessLibraryConstructorList / sub_4B4)
*===========================================================================*/
/**
* ProcessLibraryConstructorList (sub_4B4)
*
* Initializes all UEFI library globals used by this module:
* - Saves ImageHandle, gST, gBS, gRT from entry params
* - Initializes HOB list pointer
* - Locates PCD protocol, reads PcdPciExpressBaseAddress
* - Configures PCI Express MMIO via I/O port writes
* - Handles PCIe enable/disable based on PCD
* - Calls BootScriptTableInit() to set up the boot script buffer
*/
EFI_STATUS
ProcessLibraryConstructorList (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
UINTN PciExBarLength;
BOOLEAN PciExpressEnable;
UINT32 Reg32;
gImageHandle = ImageHandle;
ASSERT (gImageHandle != NULL);
gSystemTable = SystemTable;
ASSERT (gSystemTable != NULL);
gBootServices = SystemTable->BootServices;
ASSERT (gBootServices != NULL);
gRuntimeServices = SystemTable->RuntimeServices;
ASSERT (gRuntimeServices != NULL);
// Initialize HOB list pointer
GetHobList ();
// Get PCD protocol
gPcdProtocol = GetPcdProtocol ();
// Get PCI Express base address from PCD
gPciExpressBaseAddress = PcdGet64 (PcdPciExpressBaseAddress);
// Check if PCIe is enabled (bit 7 of PcdPciExpressEnable)
PciExpressEnable = (PcdGet8 (PcdPciExpressEnable) & 0x80) != 0;
// Read PCIe extended config BAR length
Reg32 = IoRead32 (PCI_EXPRESS_CAPABILITY_BAR);
// Wait for PCIe bus master enable (poll bit)
while ((Reg32 + 357 - IoRead32 (PCI_EXPRESS_CAPABILITY_BAR)) & 0x800000) {
// spin
}
// Enable or disable PCIe based on platform config
if (PciExpressEnable) {
IoOr32 (PCI_EXPRESS_CAPABILITY_BAR, 0x200);
} else {
IoAnd32 (PCI_EXPRESS_CAPABILITY_BAR, ~0x200);
}
return BootScriptTableInit ();
}
/*===========================================================================
* ACPI S3 Context Save (from AcpiS3ContextSave.c)
*===========================================================================*/
/**
* AcpiS3ContextInitialize (sub_1C5C)
*
* Called at end-of-DXE (via timer event). This function:
* 1. Checks if LockBox protocol is available; skips context save if not.
* 2. Allocates and populates the ACPI_S3_CONTEXT structure.
* 3. Locates the FACS table via ACPI config table GUIDs.
* 4. Builds an IDTR profile buffer (4096-byte IDT descriptor).
* 5. Allocates S3 NVS page tables (NX-aware, 4KB-aligned).
* 6. Allocates boot script stack (0x8000 bytes).
* 7. Allocates S3 debug buffer (4096 bytes, filled with 0xFF).
* 8. Saves everything via SaveLockBox / SetLockBoxAttributes.
* 9. Calls gBS->Stall(0) to finalize.
*
* If LockBox protocol is unavailable, prints a warning but continues
* (boot script functions still work without ACPI context).
*/
EFI_STATUS
AcpiS3ContextInitialize (
VOID
)
{
EFI_LOCK_BOX_PROTOCOL *LockBox;
ACPI_S3_CONTEXT *AcpiS3Context;
EFI_ACPI_TABLE *FacsTable;
EFI_STATUS Status;
DEBUG ((DEBUG_INFO, "AcpiS3ContextSave!\n"));
// Check if LockBox is available
Status = gBootServices->LocateProtocol (&gEfiLockBoxProtocolGuid, NULL, (VOID **)&LockBox);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_WARN, "ACPI S3 context can't be saved without LockBox!\n"));
return EFI_SUCCESS; // continue; boot script still works
}
// Allocate the ACPI S3 context structure (48 bytes)
AcpiS3Context = AllocateZeroPool (sizeof (ACPI_S3_CONTEXT));
ASSERT (AcpiS3Context != NULL);
// Locate FACS table via ACPI config tables
FacsTable = (EFI_ACPI_TABLE *)LocateAcpiFacsTable (&gEfiAcpiTableGuid);
if (FacsTable == NULL) {
FacsTable = (EFI_ACPI_TABLE *)LocateAcpiFacsTable (&gEfiAcpiTableGuid2);
}
AcpiS3Context->AcpiFacsTable = (UINT64)FacsTable;
ASSERT (AcpiS3Context->AcpiFacsTable != 0);
// Build IDTR profile buffer
// This is a self-referencing 4096-byte structure:
// Bytes[4096-2..4096-1] = 0x0FFF (limit)
// AcpiS3Context->IdtrProfile points to the last 2 bytes (the IDT limit word)
{
UINT8 *IdtrBuffer;
IdtrBuffer = AllocatePagesOrCrash (4106);
// Self-reference: the pointer address is stored at offset 4098
*(UINT64 *)(IdtrBuffer + 4098) = (UINT64)IdtrBuffer;
// Limit field at buffer[4096..4097] = 4095
*(UINT16 *)(IdtrBuffer + 4096) = 4095;
AcpiS3Context->IdtrProfile = (UINT64)(IdtrBuffer + 4096);
}
// Save IDTR profile into LockBox (10 bytes)
Status = SaveLockBox (&gAcpiS3ContextGuid, (VOID *)AcpiS3Context->IdtrProfile, 10);
ASSERT_EFI_ERROR (Status);
Status = SetLockBoxAttributes (&gAcpiS3ContextGuid, LOCK_BOX_ATTRIBUTE_RESTORE_IN_PLACE);
ASSERT_EFI_ERROR (Status);
// Determine NX page table requirements
{
BOOLEAN EnableNX = FALSE;
if (FacsTable != NULL &&
FacsTable->Signature == EFI_ACPI_5_0_FACS_SIGNATURE &&
FacsTable->Version == EFI_ACPI_5_0_FACS_VERSION &&
(FacsTable->Flags & EFI_ACPI_5_0_FACS_FLAGS_S4B_S3B) != 0)
{
EnableNX = TRUE;
}
AcpiS3Context->S3NvsPageTableAddress = (UINT64)AllocateS3NvsPageTable (EnableNX);
}
// Boot script stack
AcpiS3Context->BootScriptStackSize = 0x8000;
AcpiS3Context->BootScriptStackBase = (UINT64)AllocateZeroPool (0x8000);
ASSERT (AcpiS3Context->BootScriptStackBase != 0);
// Debug buffer (4KB, filled with 0xFF)
AcpiS3Context->S3DebugBufferAddress = (UINT64)AllocateZeroPool (4096);
SetMem ((VOID *)(UINTN)AcpiS3Context->S3DebugBufferAddress, 4096, 0xFF);
// Debug prints of AcpiS3Context fields
DEBUG ((DEBUG_INFO, "AcpiS3Context: AcpiFacsTable is 0x%8x\n", AcpiS3Context->AcpiFacsTable));
DEBUG ((DEBUG_INFO, "AcpiS3Context: IdtrProfile is 0x%8x\n", AcpiS3Context->IdtrProfile));
DEBUG ((DEBUG_INFO, "AcpiS3Context: S3NvsPageTableAddress is 0x%8x\n", AcpiS3Context->S3NvsPageTableAddress));
DEBUG ((DEBUG_INFO, "AcpiS3Context: S3DebugBufferAddress is 0x%8x\n", AcpiS3Context->S3DebugBufferAddress));
DEBUG ((DEBUG_INFO, "AcpiS3Context: BootScriptStackBase is 0x%8x\n", AcpiS3Context->BootScriptStackBase));
DEBUG ((DEBUG_INFO, "AcpiS3Context: BootScriptStackSize is 0x%8x\n", AcpiS3Context->BootScriptStackSize));
// Save ACPI S3 context into lockbox
Status = SaveLockBox (&gAcpiS3ContextGuid, AcpiS3Context, sizeof (ACPI_S3_CONTEXT));
ASSERT_EFI_ERROR (Status);
// Mark as restore-in-place
Status = SetLockBoxAttributes (&gAcpiS3ContextGuid, LOCK_BOX_ATTRIBUTE_RESTORE_IN_PLACE);
ASSERT_EFI_ERROR (Status);
// Finalize boot script initialization
Status = gBootServices->Stall (0);
if (EFI_ERROR (Status)) {
ASSERT_EFI_ERROR (FALSE);
}
return Status;
}
/**
* AllocatePagesOrCrash (sub_18A4)
*
* Allocates N bytes of physically contiguous pages.
* Rounds size up to page granularity.
* Zeroes memory if size is non-zero.
* Asserts on allocation failure.
*/
VOID *
AllocatePagesOrCrash (
IN UINTN Size
)
{
EFI_STATUS Status;
UINT64 Pages;
VOID *Buffer;
Pages = Size >> 12;
if ((Size & 0xFFF) != 0) {
Pages++;
}
Buffer = NULL;
Status = gBootServices->AllocatePages (AllocateAnyPages, EfiRuntimeServicesData, (UINTN)Pages, &Buffer);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_ERROR, "\nASSERT_EFI_ERROR (Status = %r)\n", Status));
ASSERT_EFI_ERROR (FALSE);
}
if (Size != 0) {
ZeroMem (Buffer, Size);
}
return Buffer;
}
/**
* AllocateS3NvsPageTable (sub_1AB0)
*
* Calculates the page table size needed for S3 resume based on:
* - Physical address width (from CPUID or HOB)
* - Whether NX is enabled (requires additional page table levels)
*
* With NX enabled: allocates 4-level paging tables (PML4, PDPT, PD, PT)
* matching the physical address space width.
* Without NX: allocates 32-bit PAE paging tables.
*
* Returns a physically contiguous zeroed buffer large enough for
* all page table levels.
*/
UINT64
AllocateS3NvsPageTable (
IN BOOLEAN EnableNX
)
{
UINT8 PhysicalAddressBits = 36; // default: 4-level paging
UINT32 TotalPages;
UINT32 NumberOfPml4;
UINT32 NumberOfPdpt;
UINT32 NumberOfExtraPages;
BOOLEAN Enable5LevelPaging = FALSE;
// Determine physical address width from CPUID
if (PcdGetBool (PcdUse5LevelPageTable)) {
UINT32 Regs[4];
AsmCpuid (CPUID_EXTENDED_STATE, &Regs[0], &Regs[1], &Regs[2], &Regs[3]);
if (Regs[0] >= 0x80000001) {
AsmCpuid (CPUID_EXTENDED_CPU_SIG, &Regs[0], &Regs[1], &Regs[2], &Regs[3]);
Enable5LevelPaging = (Regs[3] & BIT26) != 0; // ECX[5] or EDX[26]
}
}
// Get physical address bits from HOB or CPUID
{
VOID *Hob;
UINT32 Regs[4];
Hob = GetNextHob (EFI_HOB_TYPE_PHYSICAL_ADDRESS_BITS, gHobList);
if (Hob != NULL) {
PhysicalAddressBits = ((EFI_HOB_PHYSICAL_ADDRESS_BITS *)Hob)->PhysicalAddressBits;
} else {
AsmCpuid (CPUID_EXTENDED_STATE, &Regs[0], &Regs[1], &Regs[2], &Regs[3]);
if (Regs[0] < 0x80000008) {
PhysicalAddressBits = 36;
} else {
AsmCpuid (CPUID_ADDRESS_WIDTH, &Regs[0], &Regs[1], &Regs[2], &Regs[3]);
PhysicalAddressBits = Regs[0] & 0xFF;
}
}
}
ASSERT (PhysicalAddressBits <= 52);
// Clamp to 48 bits for 4-level paging
if (PhysicalAddressBits > 48) {
PhysicalAddressBits = 48;
}
// Non-NX mode uses 32-bit PAE only
if (!EnableNX) {
PhysicalAddressBits = 32;
}
// Calculate page table levels
if (Enable5LevelPaging && PhysicalAddressBits > 39) {
NumberOfPml4 = LShiftU64 (1, PhysicalAddressBits - 39);
NumberOfPdpt = NumberOfPml4 * LShiftU64 (1, PhysicalAddressBits - 30);
} else {
NumberOfPml4 = 1;
NumberOfPdpt = LShiftU64 (1, PhysicalAddressBits - 30);
}
if (Enable5LevelPaging) {
NumberOfExtraPages = NumberOfPml4 + NumberOfPml4 + NumberOfPml4;
} else {
NumberOfExtraPages = NumberOfPml4 * (NumberOfPdpt + 1);
}
TotalPages = (EnableNX ? 0 : 8) + NumberOfExtraPages + 1;
DEBUG ((DEBUG_INFO, "AcpiS3ContextSave TotalPageTableSize - 0x%x pages\n", TotalPages));
{
VOID *Table;
Table = AllocatePagesOrCrash (TotalPages << 12);
ASSERT (Table != 0);
return (UINT64)Table;
}
}
/**
* LocateAcpiFacsTable (sub_1940)
*
* Searches for the FACS table in the ACPI configuration tables.
* Takes an ACPI table GUID (either RSDT or XSDT) and walks
* ACPI table entries looking for FACS signature (0x53434146).
*
* Supports both 32-bit (RSDT) and 64-bit (XSDT) table entry formats.
* For XSDT (revision >= 2), uses the 64-bit entries.
* For RSDT (revision < 2), uses the 32-bit entries.
*/
UINTN
LocateAcpiFacsTable (
IN EFI_GUID *TableGuid
)
{
EFI_ACPI_5_0_ROOT_SYSTEM_DESCRIPTION_POINTER *Rsdp;
UINTN i;
UINTN TableCount;
// Find ACPI RSDP from system configuration tables
Rsdp = NULL;
for (i = 0; i < gSystemTable->NumberOfTableEntries; i++) {
if (CompareGuid (TableGuid, &gSystemTable->ConfigurationTable[i].VendorGuid)) {
Rsdp = (EFI_ACPI_5_0_ROOT_SYSTEM_DESCRIPTION_POINTER *)gSystemTable->ConfigurationTable[i].VendorTable;
break;
}
}
if (Rsdp == NULL) {
return 0;
}
// Use XSDT if revision >= 2 (ACPI 3.0+)
if (Rsdp->Revision >= 2) {
EFI_ACPI_5_0_EXTENDED_SYSTEM_DESCRIPTION_TABLE *Xsdt;
Xsdt = (EFI_ACPI_5_0_EXTENDED_SYSTEM_DESCRIPTION_TABLE *)(UINTN)Rsdp->XsdtAddress;
if (Xsdt == NULL) {
goto use_rsdt;
}
TableCount = (Xsdt->Header.Length - sizeof (EFI_ACPI_DESCRIPTION_HEADER)) / sizeof (UINT64);
// Search for either FACS or FADT (from FADT we get the FACS address)
for (i = 0; i < TableCount; i++) {
EFI_ACPI_COMMON_HEADER *Table;
Table = (EFI_ACPI_COMMON_HEADER *)(UINTN)Xsdt->TableOffsetEntry[i];
if (Table == NULL) {
continue;
}
// Direct FACS match (rare — usually FACS is pointed to by FADT)
if (Table->Signature == EFI_ACPI_5_0_FIRMWARE_CTRL_SIGNATURE) {
if (Table->Revision >= 3 ||
*(UINT32 *)((UINTN)Table + EFI_ACPI_5_0_FACS_OFFSET_X_FIRMWARE_CTRL) != 0)
{
return (UINTN)Table;
}
}
// Found FADT — extract FACS address
if (Table->Signature == EFI_ACPI_5_0_FIXED_HARDWARE_DESCRIPTION_SIGNATURE) {
EFI_ACPI_5_0_FIXED_HARDWARE_DESCRIPTION *Fadt;
UINTN FacsAddr;
Fadt = (EFI_ACPI_5_0_FIXED_HARDWARE_DESCRIPTION *)Table;
if (Table->Revision >= 3 && Fadt->XFirmwareCtrl != 0) {
FacsAddr = (UINTN)Fadt->XFirmwareCtrl;
} else {
FacsAddr = Fadt->FirmwareCtrl;
}
if (FacsAddr != 0) {
return FacsAddr;
}
}
}
use_rsdt:
// Fall through to RSDT if XSDT didn't help
}
// Use RSDT (32-bit entries)
{
EFI_ACPI_5_0_ROOT_SYSTEM_DESCRIPTION_TABLE *Rsdt;
UINT32 *EntryArray;
Rsdt = (EFI_ACPI_5_0_ROOT_SYSTEM_DESCRIPTION_TABLE *)(UINTN)Rsdp->RsdtAddress;
if (Rsdt == NULL) {
return 0;
}
TableCount = (Rsdt->Header.Length - sizeof (EFI_ACPI_DESCRIPTION_HEADER)) / sizeof (UINT32);
EntryArray = (UINT32 *)((UINTN)Rsdt + sizeof (EFI_ACPI_DESCRIPTION_HEADER));
for (i = 0; i < TableCount; i++) {
EFI_ACPI_COMMON_HEADER *Table;
Table = (EFI_ACPI_COMMON_HEADER *)(UINTN)EntryArray[i];
if (Table == NULL) {
continue;
}
if (Table->Signature == EFI_ACPI_5_0_FIXED_HARDWARE_DESCRIPTION_SIGNATURE) {
EFI_ACPI_5_0_FIXED_HARDWARE_DESCRIPTION *Fadt;
UINTN FacsAddr;
Fadt = (EFI_ACPI_5_0_FIXED_HARDWARE_DESCRIPTION *)Table;
if (Table->Revision >= 3 && Fadt->XFirmwareCtrl != 0) {
FacsAddr = (UINTN)Fadt->XFirmwareCtrl;
} else {
FacsAddr = Fadt->FirmwareCtrl;
}
if (FacsAddr != 0) {
return FacsAddr;
}
}
}
}
return 0;
}
/*===========================================================================
* Boot Script Table Management (from PiDxeS3BootScriptLib/BootScriptSave.c)
*===========================================================================*/
/**
* BootScriptTableInit (sub_339C)
*
* Initializes the boot script buffer and registers protocol callbacks.
* Called from ProcessLibraryConstructorList after library constructors.
*
* Steps:
* 1. Reads PcdS3BootScriptTablePrivateDataEntries for initial count.
* 2. Allocates BOOT_SCRIPT_TABLE (32 bytes + context).
* 3. Sets up SMM Communication protocol (gEfiSmmCommunicationProtocolGuid).
* 4. If SMM communication works, gets the SMM-side boot script buffer
* and registers notifications for:
* - gEfiPcdProtocolGuid for S3 Save State protocol
* - gEfiDxeSmmReadyToLockProtocolGuid
* - gEfiS3SmmSaveStateProtocolGuid
*
* Sets global state:
* gBootScriptTable = DXE-side boot script buffer descriptor
* gBootScriptTableSmm = SMM-side copy (if SMM comm is available)
* gBootScriptInSmm = flag (byte_54B0)
* gBootScriptInSmm2 = flag (byte_54D8)
*/
EFI_STATUS
BootScriptTableInit (
VOID
)
{
EFI_STATUS Status;
UINT64 NumberOfEntries;
// Get PCD value for initial number of entries
NumberOfEntries = PcdGet32 (PcdS3BootScriptTablePrivateDataEntries);
// Allocate boot script descriptor table
gBootScriptTable = AllocateZeroPool (sizeof (BOOT_SCRIPT_TABLE));
if (gBootScriptTable == NULL) {
// Fallback: allocate from SMM communication region
EFI_PHYSICAL_ADDRESS TableBuffer;
TableBuffer = 0xFFFFFFFFLL;
Status = gBootServices->AllocatePages (AllocateAnyPages, EfiBootServicesData, 1, &TableBuffer);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_ERROR, "\nASSERT_EFI_ERROR (Status = %r)\n", Status));
ASSERT_EFI_ERROR (FALSE);
}
gBootScriptTable = (BOOT_SCRIPT_TABLE *)(UINTN)TableBuffer;
Status = gBootServices->SetMem ((VOID *)(UINTN)gBootScriptTable, sizeof (*gBootScriptTable), 0);
ASSERT_EFI_ERROR (Status);
gBootScriptInSmm = 1;
gPcdProtocol = GetPcdProtocol ();
Status = gPcdProtocol->SetPcd (1, (UINT64)gBootScriptTable);
ASSERT_EFI_ERROR (Status);
}
gBootScriptTable->BufferAddress = NumberOfEntries;
gBootScriptTable->BufferPages = 4; // initial 4 pages = 16KB
gBootScriptTable->Field0E = 0;
gBootScriptTable->Field0F = 0;
gBootScriptTable->TotalLength = 0;
// Set up SMM communication if available
Status = gBootServices->LocateProtocol (&gEfiSmmCommunicationProtocolGuid, NULL, &gSmmCommunication);
if (!EFI_ERROR (Status)) {
VOID *SmmCommBuffer;
UINT64 CommSize;
// Communicate with SMM: share boot script table descriptor
CommSize = 32;
Status = SmmCommCommunicate (&gBootScriptTable, &CommSize);
if (!EFI_ERROR (Status) && CommSize >= 8) {
gBootScriptTableSmm = *(BOOT_SCRIPT_TABLE **)gBootScriptTable;
}
// If SMM side exists, allocate a second buffer for SMM boot script
if (gBootScriptTableSmm != NULL) {
// Second buffer in SMM for S3 save state
Status = SmmCommCommunicate (&gS3SmmSaveStateProtocol, &CommSize);
if (!EFI_ERROR (Status)) {
// Register S3 Save State protocol notification
Status = RegisterProtocolNotify (&gEfiS3SaveStateProtocolGuid, ScriptCopyTableToSmm, &gS3SaveStateNotify);
ASSERT_EFI_ERROR (Status);
// Register SMM ready-to-lock notification
Status = RegisterProtocolNotify (&gEfiDxeSmmReadyToLockProtocolGuid, ScriptCopyTableToSmm, &gS3SmmSaveStateNotify);
ASSERT_EFI_ERROR (Status);
// Register S3 SMM Save State notification
Status = RegisterProtocolNotify (&gEfiS3SmmSaveStateProtocolGuid, ScriptCopyTableToSmm, &gS3SmmSaveStateNotify);
ASSERT_EFI_ERROR (Status);
}
}
}
return EFI_SUCCESS;
}
/*===========================================================================
* Boot Script Entry Management
*===========================================================================*/
/**
* ScriptAllocateEntry (sub_3B64)
*
* Allocates space in the boot script buffer for an entry of EntrySize bytes.
*
* Two modes:
* 1) Pre-SMM (Table->SmmReadyLock=0): calls BootScriptBufferGrow()
* to allocate/grow the buffer. Data is written to DXE-owned memory.
* 2) Post-SMM (Table->SmmReadyLock=1, Table->Field0E=1): allocates
* directly from the SMM-owned buffer. Writes terminator marker.
* If byte 0x15 is set, calls BootScriptResync() first to restore
* the buffer from LockBox.
*
* Returns pointer to entry location, or NULL if buffer is full.
* On FATAL ERROR (post-lock write without SMM), prints message and
* returns NULL.
*/
VOID *
ScriptAllocateEntry (
IN UINT8 EntrySize
)
{
BOOT_SCRIPT_TABLE *Table;
VOID *Buffer;
Table = gBootScriptTable;
// Mode 1: Pre-SMM (DXE-owned buffer)
if (!Table->SmmReadyLock) {
return BootScriptBufferGrow (EntrySize);
}
// Mode 2: Post-SMM (SMM-owned buffer)
if (!Table->Field0E) {
DEBUG ((DEBUG_ERROR, "FATAL ERROR: Set boot script outside SMM after SmmReadyToLock!!!\n"));
return NULL;
}
// Resync from LockBox if needed
if (Table->Field0E) {
BootScriptResync ();
}
// Check available space
if (Table->CurrentOffset + EntrySize + 3 <= (UINT64)Table->BufferPages << 12) {
Buffer = (VOID *)(UINTN)(Table->BufferAddress + Table->CurrentOffset);
Table->CurrentOffset += EntrySize;
ScriptWriteTerminator (Buffer);
return Buffer;
}
return NULL;
}
/**
* ScriptWriteTerminator (sub_2C10)
*
* Writes the 0xFF 0xFF 0xFF terminator at position [Table->CurrentOffset]
* in the buffer and updates the TotalLength field at buffer[5].
*/
VOID
ScriptWriteTerminator (
IN VOID *Buffer OPTIONAL
)
{
BOOT_SCRIPT_TABLE *Table;
UINT8 Terminator[3] = {0xFF, 0xFF, 0xFF};
Table = gBootScriptTable;
if (Table == NULL || Table->BufferAddress == 0) {
return;
}
CopyMem (
(VOID *)(UINTN)(Table->BufferAddress + Table->CurrentOffset),
Terminator,
3
);
*(UINT32 *)(UINTN)(Table->BufferAddress + 5) = Table->CurrentOffset + 3;
}
/**
* ScriptFinalizeEntry (sub_3BF8)
*
* After writing an entry to the boot script buffer in SMM mode,
* this function updates the SMM LockBox with the new content range.
*
* Calls ScriptUpdateLockBox twice:
* 1. To save the data from entry start through end of buffer.
* 2. To save the updated total length field (offset 5, 4 bytes).
*/
VOID
ScriptFinalizeEntry (
IN VOID *Entry
)
{
BOOT_SCRIPT_TABLE *Table;
UINT32 DataOffset;
UINT32 FinalLength;
Table = gBootScriptTable;
if (!Table->SmmReadyLock || !Table->Field0E) {
return;
}
// Update the boot script data in LockBox
DataOffset = (UINT32)((UINT8 *)Entry - (UINT8 *)(UINTN)Table->BufferAddress);
FinalLength = Table->CurrentOffset + 3;
ScriptUpdateLockBox (
&gEfiS3BootScriptGuid,
DataOffset,
(VOID *)(UINTN)(Table->BufferAddress + DataOffset),
FinalLength - DataOffset
);
ASSERT_EFI_ERROR (0);
// Update the total length field in LockBox
ScriptUpdateLockBox (
&gEfiS3BootScriptGuid,
5,
&FinalLength,
sizeof (FinalLength)
);
ASSERT_EFI_ERROR (0);
}
/**
* BootScriptBufferGrow (sub_3918)
*
* Allocates or grows the boot script buffer. Called from ScriptAllocateEntry
* when there's insufficient space.
*
* First-call:
* Allocates 4 pages (16KB), initializes header {0xAAAA, length=13, version=1}
* Sets CurrentOffset = 13 (past header).
*
* Subsequent growth:
* When current usage exceeds (BufferPages - 2) << 12, doubles pages by +2.
* Allocates new pages, copies old data, releases old pages.
*
* Returns pointer to free space in buffer, or NULL on allocation failure.
*/
VOID *
BootScriptBufferGrow (
IN UINT8 EntrySize
)
{
BOOT_SCRIPT_TABLE *Table;
UINT64 CurrentOffset;
UINT16 NewPages;
UINT16 BufferPages;
UINT64 Buffer;
Table = gBootScriptTable;
// First-time allocation
if (Table->BufferAddress == 0) {
EFI_STATUS Status;
EFI_PHYSICAL_ADDRESS PhysBuffer;
PhysBuffer = 0xFFFFFFFFLL;
Status = gBootServices->AllocatePages (AllocateAnyPages, EfiBootServicesData, 4, &PhysBuffer);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_ERROR, "\nASSERT_EFI_ERROR (Status = %r)\n", Status));
ASSERT_EFI_ERROR (FALSE);
return NULL;
}
Buffer = (UINT64)PhysBuffer;
// Initialize buffer header:
// [0..1] 0xAAAA (signature/marker)
// [2] 13 (header length)
// [3..4] 1 (version)
*(UINT16 *)(UINTN)Buffer = 0xAAAA;
*(UINT8 *)(UINTN)(Buffer + 2) = 13;
*(UINT16 *)(UINTN)(Buffer + 3) = 1;
Table->CurrentOffset = 13;
Table->BufferAddress = Buffer;
Table->BufferPages = 4;
CurrentOffset = 13;
} else {
// Check if we need to grow
CurrentOffset = Table->CurrentOffset;
BufferPages = Table->BufferPages;
Buffer = Table->BufferAddress;
if ((UINT64)(BufferPages - 2) << 12 < CurrentOffset + EntrySize + 3) {
EFI_PHYSICAL_ADDRESS NewBuffer;
EFI_STATUS Status;
NewPages = BufferPages + 2;
NewBuffer = 0xFFFFFFFFLL;
Status = gBootServices->AllocatePages (AllocateAnyPages, EfiBootServicesData, (UINTN)NewPages, &NewBuffer);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_ERROR, "\nASSERT_EFI_ERROR (Status = %r)\n", Status));
ASSERT_EFI_ERROR (FALSE);
return NULL;
}
// Copy old data to new buffer
CopyMem ((VOID *)(UINTN)NewBuffer, (VOID *)(UINTN)Buffer, CurrentOffset);
gBootServices->FreePages ((EFI_PHYSICAL_ADDRESS)Buffer, BufferPages);
Table->BufferAddress = (UINT64)NewBuffer;
Table->BufferPages = NewPages;
Buffer = (UINT64)NewBuffer;
}
}
Table->CurrentOffset = (UINT32)(CurrentOffset + EntrySize);
return (VOID *)(UINTN)(Table->BufferAddress + CurrentOffset);
}
/**
* BootScriptResync (sub_3AAC)
*
* Restores the boot script buffer from LockBox after SMM has modified it.
* This is a synchronization step between SMM communication calls.
*
* Calls:
* - ScriptRestoreLockBox to get the SMM-updated buffer
* - ScriptUpdateLockBox to notify SMM of new writes
*/
VOID
BootScriptResync (
VOID
)
{
EFI_STATUS Status;
UINTN BufferSize;
UINT64 LockBoxBuffer;
UINTN LockBoxSize;
// First call to RestoreLockBox retrieves the current data size
LockBoxSize = sizeof (LockBoxBuffer);
Status = ScriptRestoreLockBox (
&gEfiS3BootScriptGuid,
(VOID *)(UINTN)gBootScriptTable->BufferAddress,
&LockBoxSize
);
// Then we need to get the actual data
LockBoxSize = sizeof (UINT32);
ScriptUpdateLockBox (
&gEfiS3BootScriptGuid,
0,
(VOID *)(UINTN)gBootScriptTable->BufferAddress,
gBootScriptTable->CurrentOffset
);
ASSERT_EFI_ERROR (Status);
// Read back the total length from LockBox
gBootScriptTable->CurrentOffset = *(UINT32 *)(UINTN)(gBootScriptTable->BufferAddress + 16) - 3;
}
/*===========================================================================
* SMM Communication LockBox Wrappers (from SmmLockBoxDxeLib)
*===========================================================================*/
/**
* GetSmmCommunicationProtocol (sub_2610)
*
* Locates and caches the SMM Communication protocol via UEFI
* boot services LocateProtocol.
*
* Returns the protocol pointer, or NULL if not available (pre-SMM).
*/
EFI_SMM_COMMUNICATION_PROTOCOL *
GetSmmCommunicationProtocol (
VOID
)
{
EFI_STATUS Status;
if (gSmmCommunication == NULL) {
Status = gBootServices->LocateProtocol (
&gEfiSmmCommunicationProtocolGuid,
NULL,
&gSmmCommunication
);
if (EFI_ERROR (Status)) {
gSmmCommunication = NULL;
}
}
return gSmmCommunication;
}
/**
* SmmCommunicationGetBuffer (sub_2660)
*
* Reads the SMM Communication Region Table from system configuration
* and finds the entry with Type == 7 (SMM_COMMUNICATE_HEADER_VARIABLE_SIZE)
* that has >= 0x50 (80) 4K pages of buffer space.
*
* Returns the physical address of the communication buffer, or NULL on failure.
*
* Caches result in gSmmCommRegion.
*/
VOID *
SmmCommunicationGetBuffer (
VOID
)
{
EFI_SMM_COMMUNICATION_REGION_TABLE *RegionTable;
VOID *Buffer;
Buffer = NULL;
if (gSmmCommRegion == NULL) {
// Locate the SMM Communication Region Table via system config table
if (GetSystemConfigurationTable (&gEfiSmmCommunicationRegionTableGuid, (VOID **)&RegionTable) >= 0) {
UINTN i;
ASSERT (RegionTable != NULL);
for (i = 0; i < RegionTable->NumberOfEntries; i++) {
SMM_COMMUNICATION_REGION_ENTRY *Entry;
Entry = (SMM_COMMUNICATION_REGION_ENTRY *)((UINTN)RegionTable +
sizeof (*RegionTable) +
i * RegionTable->EntrySize);
if (Entry->Type == 7 && (Entry->PhysicalStart << 12) >= 0x50) {
Buffer = (VOID *)(UINTN)(Entry->CpuStart << 12);
break;
}
}
}
gSmmCommRegion = Buffer;
}
return gSmmCommRegion;
}
/**
* SaveLockBox (sub_2700)
*
* Sends a SaveLockBox command via SMM Communication protocol.
* Copies the GUID + buffer data into the shared communication buffer
* and calls SmmCommunication->Communicate().
*
* @param[in] Guid LockBox GUID
* @param[in] Buffer Data to save
* @param[in] Length Size of data (max = 0x8000)
*
* @retval EFI_SUCCESS Data saved to LockBox
* @retval EFI_INVALID_PARAMETER Guid or Buffer or Length is NULL
* @retval EFI_OUT_OF_RESOURCES SMM Communication protocol not available
*/
EFI_STATUS
SaveLockBox (
IN EFI_GUID *Guid,
IN VOID *Buffer,
IN UINTN Length
)
{
EFI_SMM_COMMUNICATION_PROTOCOL *SmmComm;
VOID *CommBuffer;
UINT64 CommSize;
DEBUG ((DEBUG_INFO, "SmmLockBoxDxeLib SaveLockBox - Enter\n"));
if (Guid == NULL || Buffer == NULL || Length == 0) {
return EFI_INVALID_PARAMETER;
}
SmmComm = GetSmmCommunicationProtocol ();
if (SmmComm == NULL) {
return EFI_OUT_OF_RESOURCES;
}
CommBuffer = SmmCommunicationGetBuffer ();
if (CommBuffer == NULL) {
CommBuffer = (VOID *)&Guid; // fallback to stack-local buffer
}
// Build the SMM communication buffer:
// +0 EFI_GUID (LockBox GUID identifying the operation)
// +16 UINT64 MessageLength (size of LockBox command)
// +24 UINT64 ReturnStatus (filled by SMM)
// +32 UINT32 LockBoxFunction (1 = Save)
// +36 UINT32 HeaderSize (48)
// +40 EFI_GUID Target LockBox GUID (16 bytes)
// +56 UINT64 Buffer pointer
// +64 UINT64 Buffer length
// Total: 72 bytes
CopyMem (CommBuffer, &gEfiSmmCommunicationProtocolGuid, sizeof (EFI_GUID));
*(UINT64 *)((UINTN)CommBuffer + sizeof (EFI_GUID)) = (UINT64)(sizeof (UINT64) + sizeof (UINTN) + Length);
*(UINT64 *)((UINTN)CommBuffer + sizeof (EFI_GUID) + sizeof (UINT64)) = (UINT64)-1;
*(UINT32 *)((UINTN)CommBuffer + sizeof (EFI_GUID) + sizeof (UINT64) + sizeof (UINT32)) = 1; // Function 1 = SaveLockBox
*(UINT32 *)((UINTN)CommBuffer + sizeof (EFI_GUID) + sizeof (UINT64) + sizeof (UINT64)) = 48; // HeaderSize
CopyMem ((VOID *)((UINTN)CommBuffer + sizeof (EFI_GUID) + sizeof (UINT64) + sizeof (UINT64) + sizeof (UINT64)),
Guid,
16); // Target LockBox GUID
*(UINT64 *)((UINTN)CommBuffer + sizeof (EFI_GUID) + sizeof (UINT64) + sizeof (UINT64) + sizeof (UINT64) + sizeof (UINT64)) = (UINT64)Buffer;
*(UINT64 *)((UINTN)CommBuffer + sizeof (EFI_GUID) + sizeof (UINT64) + sizeof (UINT64) + sizeof (UINT64) + sizeof (UINT64) + sizeof (UINT64)) = (UINT64)Length;
CommSize = 72;
SmmComm->Communicate (SmmComm, CommBuffer, &CommSize);
if (*(UINT64 *)((UINTN)CommBuffer + sizeof (EFI_GUID) + sizeof (UINT64)) < 0) {
DEBUG ((DEBUG_ERROR, "\nASSERT_EFI_ERROR (Status = %r)\n",
*(UINT64 *)((UINTN)CommBuffer + sizeof (EFI_GUID) + sizeof (UINT64))));
ASSERT_EFI_ERROR (FALSE);
}
DEBUG ((DEBUG_INFO, "SmmLockBoxDxeLib SaveLockBox - Exit (%r)\n",
*(UINT64 *)((UINTN)CommBuffer + sizeof (EFI_GUID) + sizeof (UINT64))));
return *(UINT64 *)((UINTN)CommBuffer + sizeof (EFI_GUID) + sizeof (UINT64));
}
/**
* SetLockBoxAttributes (sub_2844)
*
* Sends a SetLockBoxAttributes command via SMM Communication protocol.
*
* @param[in] Guid LockBox GUID
* @param[in] Attributes Attributes to set (e.g., LOCK_BOX_ATTRIBUTE_RESTORE_IN_PLACE)
*
* @retval EFI_SUCCESS Attributes set
* @retval EFI_INVALID_PARAMETER Guid is NULL
* @retval EFI_OUT_OF_RESOURCES SMM Communication protocol not available
*/
EFI_STATUS
SetLockBoxAttributes (
IN EFI_GUID *Guid,
IN UINT64 Attributes
)
{
EFI_SMM_COMMUNICATION_PROTOCOL *SmmComm;
VOID *CommBuffer;
UINT64 CommSize;
DEBUG ((DEBUG_INFO, "SmmLockBoxDxeLib SetLockBoxAttributes - Enter\n"));
if (Guid == NULL) {
return EFI_INVALID_PARAMETER;
}
SmmComm = GetSmmCommunicationProtocol ();
if (SmmComm == NULL) {
return EFI_OUT_OF_RESOURCES;
}
CommBuffer = SmmCommunicationGetBuffer ();
if (CommBuffer == NULL) {
CommBuffer = (VOID *)&Guid; // fallback
}
// Build SMM communication buffer for SetAttributes:
// Function = 4 (SetLockBoxAttributes)
CopyMem (CommBuffer, &gEfiSmmCommunicationProtocolGuid, sizeof (EFI_GUID));
*(UINT64 *)((UINTN)CommBuffer + sizeof (EFI_GUID)) = 64; // MessageLength
*(UINT64 *)((UINTN)CommBuffer + sizeof (EFI_GUID) + sizeof (UINT64)) = (UINT64)-1;
*(UINT32 *)((UINTN)CommBuffer + sizeof (EFI_GUID) + sizeof (UINT64) + sizeof (UINT32)) = 4; // Function
*(UINT32 *)((UINTN)CommBuffer + sizeof (EFI_GUID) + sizeof (UINT64) + sizeof (UINT64)) = 40; // HeaderSize
CopyMem ((VOID *)((UINTN)CommBuffer + sizeof (EFI_GUID) + sizeof (UINT64) + sizeof (UINT64) + sizeof (UINT64)),
Guid,
16);
*(UINT64 *)((UINTN)CommBuffer + sizeof (EFI_GUID) + sizeof (UINT64) + sizeof (UINT64) + sizeof (UINT64) + sizeof (UINT64)) = Attributes;
CommSize = 64;
SmmComm->Communicate (SmmComm, CommBuffer, &CommSize);
if (*(UINT64 *)((UINTN)CommBuffer + sizeof (EFI_GUID) + sizeof (UINT64)) < 0) {
DEBUG ((DEBUG_ERROR, "\nASSERT_EFI_ERROR (Status = %r)\n",
*(UINT64 *)((UINTN)CommBuffer + sizeof (EFI_GUID) + sizeof (UINT64))));
ASSERT_EFI_ERROR (FALSE);
}
DEBUG ((DEBUG_INFO, "SmmLockBoxDxeLib SetLockBoxAttributes - Exit (%r)\n",
*(UINT64 *)((UINTN)CommBuffer + sizeof (EFI_GUID) + sizeof (UINT64))));
return *(UINT64 *)((UINTN)CommBuffer + sizeof (EFI_GUID) + sizeof (UINT64));
}
/**
* UpdateLockBox (sub_2970)
*
* Sends an UpdateLockBox command via SMM Communication protocol.
* Updates data at a specific offset within an existing LockBox entry.
*
* @param[in] Guid LockBox GUID
* @param[in] Offset Offset from start of LockBox data
* @param[in] Buffer New data
* @param[in] Length Length of new data
*/
EFI_STATUS
UpdateLockBox (
IN EFI_GUID *Guid,
IN UINTN Offset,
IN VOID *Buffer,
IN UINTN Length
)
{
EFI_SMM_COMMUNICATION_PROTOCOL *SmmComm;
VOID *CommBuffer;
UINT64 CommSize;
DEBUG ((DEBUG_INFO, "SmmLockBoxDxeLib UpdateLockBox - Enter\n"));
if (Buffer == NULL || Length == 0) {
return EFI_INVALID_PARAMETER;
}
SmmComm = GetSmmCommunicationProtocol ();
if (SmmComm == NULL) {
return EFI_OUT_OF_RESOURCES;
}
CommBuffer = SmmCommunicationGetBuffer ();
if (CommBuffer == NULL) {
CommBuffer = (VOID *)&Guid;
}
CopyMem (CommBuffer, &gEfiSmmCommunicationProtocolGuid, sizeof (EFI_GUID));
*(UINT64 *)((UINTN)CommBuffer + sizeof (EFI_GUID)) = 56;
*(UINT64 *)((UINTN)CommBuffer + sizeof (EFI_GUID) + sizeof (UINT64)) = (UINT64)-1;
*(UINT32 *)((UINTN)CommBuffer + sizeof (EFI_GUID) + sizeof (UINT64) + sizeof (UINT32)) = 2; // Function 2 = UpdateLockBox
*(UINT32 *)((UINTN)CommBuffer + sizeof (EFI_GUID) + sizeof (UINT64) + sizeof (UINT64)) = 56; // HeaderSize
CopyMem ((VOID *)((UINTN)CommBuffer + sizeof (EFI_GUID) + sizeof (UINT64) + sizeof (UINT64) + sizeof (UINT64)),
&gEfiS3BootScriptGuid,
16);
*(UINT64 *)((UINTN)CommBuffer + sizeof (EFI_GUID) + sizeof (UINT64) + sizeof (UINT64) + sizeof (UINT64) + sizeof (UINT64)) = (UINT64)Offset;
*(UINT64 *)((UINTN)CommBuffer + sizeof (EFI_GUID) + sizeof (UINT64) + sizeof (UINT64) + sizeof (UINT64) + sizeof (UINT64) + sizeof (UINT64)) = (UINT64)Buffer;
*(UINT64 *)((UINTN)CommBuffer + sizeof (EFI_GUID) + sizeof (UINT64) + sizeof (UINT64) + sizeof (UINT64) + sizeof (UINT64) + sizeof (UINT64) + sizeof (UINT64)) = (UINT64)Length;
CommSize = 80;
SmmComm->Communicate (SmmComm, CommBuffer, &CommSize);
if (*(UINT64 *)((UINTN)CommBuffer + sizeof (EFI_GUID) + sizeof (UINT64)) < 0) {
DEBUG ((DEBUG_ERROR, "\nASSERT_EFI_ERROR (Status = %r)\n",
*(UINT64 *)((UINTN)CommBuffer + sizeof (EFI_GUID) + sizeof (UINT64))));
ASSERT_EFI_ERROR (FALSE);
}
DEBUG ((DEBUG_INFO, "SmmLockBoxDxeLib UpdateLockBox - Exit (%r)\n",
*(UINT64 *)((UINTN)CommBuffer + sizeof (EFI_GUID) + sizeof (UINT64))));
return *(UINT64 *)((UINTN)CommBuffer + sizeof (EFI_GUID) + sizeof (UINT64));
}
/**
* RestoreLockBox (sub_2AB8)
*
* Sends a RestoreLockBox command via SMM Communication protocol.
*
* @param[in] Guid LockBox GUID
* @param[out] Buffer Buffer to receive data (NULL to query size)
* @param[in] Length On input: buffer size; on output: actual data size
*
* @retval EFI_SUCCESS Data restored
* @retval EFI_INVALID_PARAMETER Guid is NULL, or Buffer/Length mismatch
* @retval EFI_OUT_OF_RESOURCES SMM Communication not available
*/
EFI_STATUS
RestoreLockBox (
IN EFI_GUID *Guid,
OUT VOID *Buffer OPTIONAL,
IN OUT UINTN *Length OPTIONAL
)
{
EFI_SMM_COMMUNICATION_PROTOCOL *SmmComm;
VOID *CommBuffer;
UINT64 CommSize;
DEBUG ((DEBUG_INFO, "SmmLockBoxDxeLib RestoreLockBox - Enter\n"));
if (Guid == NULL) {
return EFI_INVALID_PARAMETER;
}
if (Buffer != NULL && Length == NULL) {
return EFI_INVALID_PARAMETER;
}
if (Buffer == NULL && Length != NULL) {
return EFI_INVALID_PARAMETER;
}
SmmComm = GetSmmCommunicationProtocol ();
if (SmmComm == NULL) {
return EFI_OUT_OF_RESOURCES;
}
CommBuffer = SmmCommunicationGetBuffer ();
if (CommBuffer == NULL) {
CommBuffer = (VOID *)&Guid;
}
CopyMem (CommBuffer, &gEfiSmmCommunicationProtocolGuid, sizeof (EFI_GUID));
*(UINT64 *)((UINTN)CommBuffer + sizeof (EFI_GUID)) = 48;
*(UINT64 *)((UINTN)CommBuffer + sizeof (EFI_GUID) + sizeof (UINT64)) = (UINT64)-1;
*(UINT32 *)((UINTN)CommBuffer + sizeof (EFI_GUID) + sizeof (UINT64) + sizeof (UINT32)) = 3; // Function 3 = RestoreLockBox
*(UINT32 *)((UINTN)CommBuffer + sizeof (EFI_GUID) + sizeof (UINT64) + sizeof (UINT64)) = 48; // HeaderSize
CopyMem ((VOID *)((UINTN)CommBuffer + sizeof (EFI_GUID) + sizeof (UINT64) + sizeof (UINT64) + sizeof (UINT64)),
Guid,
16);
if (Buffer != NULL) {
*(UINT64 *)((UINTN)CommBuffer + sizeof (EFI_GUID) + sizeof (UINT64) + sizeof (UINT64) + sizeof (UINT64) + sizeof (UINT64)) = (UINT64)*Length;
} else {
*(UINT64 *)((UINTN)CommBuffer + sizeof (EFI_GUID) + sizeof (UINT64) + sizeof (UINT64) + sizeof (UINT64) + sizeof (UINT64)) = 0;
}
CommSize = 72;
SmmComm->Communicate (SmmComm, CommBuffer, &CommSize);
if (Length != NULL) {
*Length = *(UINT64 *)((UINTN)CommBuffer + sizeof (EFI_GUID) + sizeof (UINT64) + sizeof (UINT64) + sizeof (UINT64) + sizeof (UINT64));
}
if (*(UINT64 *)((UINTN)CommBuffer + sizeof (EFI_GUID) + sizeof (UINT64)) < 0) {
DEBUG ((DEBUG_ERROR, "\nASSERT_EFI_ERROR (Status = %r)\n",
*(UINT64 *)((UINTN)CommBuffer + sizeof (EFI_GUID) + sizeof (UINT64))));
ASSERT_EFI_ERROR (FALSE);
}
DEBUG ((DEBUG_INFO, "SmmLockBoxDxeLib RestoreLockBox - Exit (%r)\n",
*(UINT64 *)((UINTN)CommBuffer + sizeof (EFI_GUID) + sizeof (UINT64))));
return *(UINT64 *)((UINTN)CommBuffer + sizeof (EFI_GUID) + sizeof (UINT64));
}
/*===========================================================================
* Library Helpers (library stubs linked into this module)
*===========================================================================*/
/**
* GetPcdProtocol (sub_20D4)
*
* Lazily locates and caches the PCD protocol (gEfiPcdProtocolGuid).
*/
VOID *
GetPcdProtocol (
VOID
)
{
EFI_STATUS Status;
if (gPcdProtocol == NULL) {
Status = gBootServices->LocateProtocol (&gEfiPcdProtocolGuid, NULL, &gPcdProtocol);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_ERROR, "\nASSERT_EFI_ERROR (Status = %r)\n", Status));
ASSERT_EFI_ERROR (FALSE);
}
if (gPcdProtocol == NULL) {
DEBUG ((DEBUG_ERROR, "mPcd != ((void *) 0)\n"));
ASSERT (FALSE);
}
}
return gPcdProtocol;
}
/**
* GetHobList (sub_2500)
*
* Lazily locates and caches the HOB list pointer (gEfiHobListGuid).
*/
VOID *
GetHobList (
VOID
)
{
EFI_STATUS Status;
if (gHobList == NULL) {
Status = GetSystemConfigurationTable (&gEfiHobListGuid, &gHobList);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_ERROR, "\nASSERT_EFI_ERROR (Status = %r)\n", Status));
ASSERT_EFI_ERROR (FALSE);
}
if (gHobList == NULL) {
DEBUG ((DEBUG_ERROR, "mHobList != ((void *) 0)\n"));
ASSERT (FALSE);
}
}
return gHobList;
}
/**
* GetSystemConfigurationTable (sub_2350)
*
* Searches gST->ConfigurationTable[] by GUID and returns the table pointer.
*/
EFI_STATUS
GetSystemConfigurationTable (
IN EFI_GUID *TableGuid,
OUT VOID **Table
)
{
UINTN i;
ASSERT (TableGuid != NULL);
ASSERT (Table != NULL);
*Table = NULL;
if (gSystemTable->NumberOfTableEntries == 0) {
return EFI_NOT_FOUND;
}
for (i = 0; i < gSystemTable->NumberOfTableEntries; i++) {
if (CompareGuid (TableGuid, &gSystemTable->ConfigurationTable[i].VendorGuid)) {
*Table = gSystemTable->ConfigurationTable[i].VendorTable;
return EFI_SUCCESS;
}
}
return EFI_NOT_FOUND;
}
/**
* RegisterProtocolNotify (sub_2414)
*
* Creates a protocol notification event. When the specified protocol
* is installed, the notification function is called.
* Returns the event handle.
*/
VOID *
RegisterProtocolNotify (
IN EFI_GUID *ProtocolGuid,
IN EFI_EVENT_NOTIFY NotifyFunction,
IN VOID *Registration
)
{
EFI_STATUS Status;
VOID *Event;
ASSERT (Registration != NULL);
Status = gBootServices->CreateEvent (EVT_NOTIFY_SIGNAL, TPL_CALLBACK, NotifyFunction, NULL, &Event);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_ERROR, "\nASSERT_EFI_ERROR (Status = %r)\n", Status));
ASSERT_EFI_ERROR (FALSE);
}
Status = gBootServices->RegisterProtocolNotify (ProtocolGuid, Event, Registration);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_ERROR, "\nASSERT_EFI_ERROR (Status = %r)\n", Status));
ASSERT_EFI_ERROR (FALSE);
}
// Signal once to catch already-installed protocols
gBootServices->SignalEvent (Event);
return Event;
}
/*===========================================================================
* Boot Script Write Dispatch helpers
*===========================================================================*/
/**
* ScriptIoWriteEntry (sub_680)
*
* Writes an I/O boot script entry (type 0). The entry encodes:
* - I/O port address, count, data width, and data buffer.
*/
EFI_STATUS
ScriptIoWriteEntry (
IN UINT32 Address,
IN UINT32 Count,
IN UINT8 Width,
IN VOID *Buffer
);
/**
* ScriptIoReadWriteEntry (sub_750)
*
* Writes an I/O read-write (mask) boot script entry (type 1).
* Stores both data and mask for read-modify-write during S3 resume.
*/
EFI_STATUS
ScriptIoReadWriteEntry (
IN UINT32 Address,
IN UINT32 Count,
IN UINT8 Width,
IN VOID *Data,
IN VOID *Mask
);
/**
* ScriptMemWriteEntry (sub_824)
*
* Writes a memory write boot script entry (type 2).
*/
EFI_STATUS
ScriptMemWriteEntry (
IN UINT64 Address,
IN UINT32 Count,
IN UINT8 Width,
IN VOID *Buffer
);
/**
* ScriptMemReadWriteEntry (sub_8F8)
*
* Writes a memory read-write (mask) boot script entry (type 3).
*/
EFI_STATUS
ScriptMemReadWriteEntry (
IN UINT64 Address,
IN UINT32 Count,
IN UINT8 Width,
IN VOID *Data,
IN VOID *Mask
);
/**
* ScriptPciCfgWriteEntry (sub_9D0)
*
* Writes a PCI config write boot script entry (type 4).
*/
EFI_STATUS
ScriptPciCfgWriteEntry (
IN UINT64 Address,
IN UINT32 Count,
IN UINT8 Width,
IN VOID *Buffer
);
/**
* ScriptPciCfgReadWriteEntry (sub_AC0)
*
* Writes a PCI config read-write (mask) boot script entry (type 5).
*/
EFI_STATUS
ScriptPciCfgReadWriteEntry (
IN UINT64 Address,
IN UINT32 Count,
IN UINT8 Width,
IN VOID *Data,
IN VOID *Mask
);
/**
* ScriptSmbusExecuteEntry (sub_DC0)
*
* Writes an SMBus execute boot script entry (type 6).
* Encodes slave address, command, operation type, and data pointers.
*/
EFI_STATUS
ScriptSmbusExecuteEntry (
IN UINT64 SlaveAddress,
IN UINT8 Command,
IN UINT8 Operation,
IN BOOLEAN PecCheck,
IN VOID *Buffer,
IN OUT UINTN *BufferLength
);
/**
* ScriptStallEntry (sub_F74)
*
* Writes a microsecond delay boot script entry (type 7).
*/
EFI_STATUS
ScriptStallEntry (
IN UINT32 Microseconds
);
/**
* ScriptDispatchEntry (sub_FE4)
*
* Writes a dispatch function call entry (type 8).
* The function address is called during S3 resume replay.
*/
EFI_STATUS
ScriptDispatchEntry (
IN VOID *EntryPoint
);
/**
* ScriptInformationEntry (sub_1164)
*
* Writes informational ASCII data (type 9).
* Non-executed; used for debugging/logging.
*/
EFI_STATUS
ScriptInformationEntry (
IN CHAR8 *Info,
...
);
/**
* ScriptPciCfg2WriteEntry (sub_3CB8)
*
* Writes a PCI config 2 write entry (type 10). Address is in
* PCIe extended config format (segment/bus/device/function/register).
*/
EFI_STATUS
ScriptPciCfg2WriteEntry (
IN UINT32 Address,
IN VOID *Buffer,
IN UINTN BufferLength
);
/**
* ScriptPollInsert (sub_3D40)
*
* Helper used by ScriptPollEntry. Moves boot script data to make
* room for a new poll entry at a given position. Handles the
* three cases:
* - Insert at target (with offset calculation)
* - Insert at end (no offset)
* - Insert at end with no target
*/
VOID
ScriptPollInsert (
IN UINT8 EntrySize,
IN VOID *Target OPTIONAL,
IN BOOLEAN AdjustFlag,
IN OUT UINT64 *Position
);
// Forward declarations for functions referenced solely via function pointers:
EFI_STATUS
BootScriptClose (
VOID
);
/*===========================================================================
* Data Flow: Boot Script Buffer Lifecycle
*===========================================================================
*
* 1. BootScriptTableInit() allocates the BOOT_SCRIPT_TABLE.
* For first-call: buffer is 4 pages with header {0xAAAA, length=13}.
*
* 2. Drivers write entries via S3BootScriptWrite().
* Entries are appended to buffer at CurrentOffset.
* Entry types 0-16 define I/O, memory, PCI, SMBus operations.
*
* 3. On SmmReadyToLock:
* ScriptNotifySmmReady() is called.
* ScriptWriteTerminator() marks the end.
* gBootScriptTable->SmmReadyLock = 1.
* All subsequent writes go through SMM (via LockBox).
*
* 4. On ExitBootServices:
* ScriptNotifyBootScriptDone() saves the finalized boot script
* to LockBox and marks Table->Field0F = 1.
*
* 5. During S3 resume (SMM phase):
* The boot script is replayed from LockBox.
* Each entry's Type determines the replay action:
* 0: OUT instruction
* 1: OUT with previous read
* 2: Memory write
* 4: PCI config write
* 6: SMBus execute
* 7: Stall (delay)
* 8: Function dispatch
* 10: PCIe extended config write
* 254: Label (for GOTO)
*
* ACPI S3 Context Flow:
* - AcpiS3ContextInitialize() builds the context
* - Context is saved into LockBox
* - S3 resume boot script (in SMM) restores the context
* - FACS, IDTR, page tables, stack are restored by the
* S3 resume path in SMM.
*
* LockBox GUIDs used:
* gAcpiS3ContextGuid - ACPI S3 Context (unk_52F0, 0x5360, 0x5400)
* gEfiS3BootScriptGuid - Boot Script Table (unk_5380, 0x5410, 0x5420, 0x5430, 0x5440)
*/
/*===========================================================================
* Global Data Layout
*===========================================================================
*
* 0x5458: gSystemTable (8 bytes) - EFI System Table
* 0x5460: gBootServices (8 bytes) - EFI Boot Services
* 0x5468: gImageHandle (8 bytes) - Driver image handle
* 0x5470: gRuntimeServices (8 bytes) - EFI Runtime Services
* 0x5478: gDebugPortProtocol (8 bytes) - DebugPort protocol (cached)
* 0x5480: gPcdProtocol (8 bytes) - PCD protocol (cached)
* 0x5488: gHobList (8 bytes) - HOB list pointer (cached)
* 0x5490: gPciExpressBaseAddress (8 bytes) - PCIe MMIO base address
* 0x5498: gSmmCommRegion (8 bytes) - SMM communication region buffer pointer
* 0x54A0: gSmmCommunication (8 bytes) - SMM Communication protocol (cached)
* 0x54A8: gS3SaveStateReg (8 bytes) - S3 Save State registration handle
* 0x54B0: gBootScriptInSmm (1 byte) - Boot script buffer is in SMM
* 0x54B8: mEventDxeSmmReadyToLock (8 bytes) - ReadyToLock event
* 0x54C0: gS3SmmSaveStateProtocol (8 bytes) - S3 SMM Save State protocol pointer
* 0x54C8: gS3SaveStateNotify (8 bytes) - S3 Save State protocol registration
* 0x54D0: gS3SmmSaveStateNotify (8 bytes) - S3 SMM Save State notify registration
* 0x54D8: gBootScriptInSmm2 (1 byte) - Secondary SMM boot script flag
* 0x54E0: gBootScriptTable (8 bytes) - Primary boot script table pointer
* 0x54E8: gBootScriptTableSmm (8 bytes) - SMM boot script table pointer
*
* Total .data size: 0x240 bytes (0x52C0 - 0x5500)
*/
/*===========================================================================
* Source Files (from debug paths)
*===========================================================================
*
* These are the original source files that comprise this module:
*
* MdeModulePkg\Universal\Acpi\S3SaveStateDxe\S3SaveState.c
* MdeModulePkg\Universal\Acpi\S3SaveStateDxe\AcpiS3ContextSave.c
* MdeModulePkg\Library\PiDxeS3BootScriptLib\BootScriptSave.c
* MdeModulePkg\Library\SmmLockBoxLib\SmmLockBoxDxeLib.c
* MdeModulePkg\Library\SmmLockBoxLib\SmmLockBoxDxeLib.c
* MdePkg\Library\UefiLib\UefiLib.c
* MdePkg\Library\DxeHobLib\HobLib.c
* MdePkg\Library\DxePcdLib\DxePcdLib.c
* MdePkg\Library\BaseMemoryLibRepStr\CopyMemWrapper.c
* MdePkg\Library\BaseMemoryLibRepStr\ZeroMemWrapper.c
* MdePkg\Library\BaseMemoryLibRepStr\SetMemWrapper.c
* MdePkg\Library\BaseLib\String.c
* MdePkg\Library\BaseLib\Unaligned.c
* MdePkg\Library\BaseLib\LShiftU64.c
* MdePkg\Library\BaseIoLibIntrinsic\IoLib.c
* MdePkg\Library\BaseIoLibIntrinsic\IoLibMsc.c
* MdePkg\Library\UefiBootServicesTableLib\UefiBootServicesTableLib.c
* MdePkg\Library\UefiRuntimeServicesTableLib\UefiRuntimeServicesTableLib.c
*
* Build config: HR6N0XMLK, DEBUG_VS2015, X64
*/