Newer
Older
AMI-Aptio-BIOS-Reversed / AmiModulePkg / SmmS3SaveState / S3SaveStateDxe / S3SaveStateDxe.c
@Ajax Dong Ajax Dong 2 days ago 57 KB Full restructure
/**
 * 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
 */