/**
 * @file SetupConfigUpdateDxeLightningRidgeEXECB3.c
 * @brief UEFI DXE driver for UBA setup configuration update on
 *        LightningRidgeEXECB3 platform.
 *
 * This module is structurally identical to the other SetupConfigUpdateDxe
 * variants (NeonCityFPGA, LightningRidgeEXECB1/2/4) with platform-specific
 * GUIDs substituted. The LightningRidgeEXECB3 variant uses:
 *   - Debug string: "UBA:SETUPConfigUpdate-TypeLightningRidgeEXECB3"
 *   - Board-type GUID: {E03E0D46-5263-4845-B0A4-58D57B3177E2}
 *   - Setup config protocol GUID: {CD1F9574-DD03-4196-96AD-4965146F9665}
 *
 * Build path:
 *   e:\hs\Build\HR6N0XMLK\DEBUG_VS2015\X64\PurleyRpPkg\Uba\UbaMain\Dxe\
 *   TypeLightningRidgeEXECB3\SetupCfgUpdateDxe\SetupCfgUpdateDxe\DEBUG\
 *   SetupConfigUpdateDxeLightningRidgeEXECB3.pdb
 */

#include "SetupConfigUpdateDxeLightningRidgeEXECB3.h"

//=============================================================================
// Module Global Variables
//=============================================================================

/// Image handle for this driver.
/// Set by ModuleEntryPoint from the ImageHandle parameter.
/// Asserted non-NULL before use.
EFI_HANDLE    gImageHandle       = NULL;   // Address: 0xBA8

/// System table pointer.
/// Set by ModuleEntryPoint from the SystemTable parameter.
/// Asserted non-NULL before use.
EFI_SYSTEM_TABLE *gSystemTable   = NULL;   // Address: 0xB98

/// Boot services pointer, extracted from gSystemTable->BootServices.
/// Asserted non-NULL before use.
EFI_BOOT_SERVICES *gBootServices = NULL;   // Address: 0xBA0

/// Runtime services pointer, extracted from gSystemTable->RuntimeServices.
/// Asserted non-NULL before use (cached but not called directly by this module).
EFI_RUNTIME_SERVICES *gRuntimeServices = NULL;  // Address: 0xBB0

/// Cached DebugLib protocol interface pointer.
/// Initialized on first call to GetDebugProtocol().
/// Address: 0xBB8
UBA_DEBUG_PROTOCOL *gDebugProtocol = NULL;

/// Cached HOB list pointer.
/// Initialized on first call to GetHobList().
/// Address: 0xBC0
VOID *gHobList = NULL;

/// Cached CMOS debug level byte from register 0x4B.
/// Address: 0xBC8
UINT8 gCmosDebugLevel = 0;

//=============================================================================
// Data Section Contents
//=============================================================================

/**
 * DebugLib protocol GUID at 0xB40.
 * Used with gBS->LocateProtocol() to resolve the UEFI debug library.
 * GUID: {36232936-0E76-31C8-A13A-3AF2FC1C3932}
 */
EFI_GUID gDebugProtocolGuid = DEBUG_LIB_PROTOCOL_GUID;

/**
 * UBA board-type protocol GUID at 0xB50.
 * Identifies the LightningRidgeEXECB3 platform-specific UBA protocol.
 * GUID: {E03E0D46-5263-4845-B0A4-58D57B3177E2}
 */
EFI_GUID gUbaBoardTypeProtocolGuid = UBA_BOARD_TYPE_PROTOCOL_GUID;

/**
 * Standard EFI HOB list GUID at 0xB60.
 * Used to scan the configuration table for the HOB list pointer.
 * Split into two 8-byte halves for fast comparison in IsGuidMatch().
 * GUID: {7739F24C-93D7-11D4-9A3A-0090273FC14D}
 */
EFI_GUID gEfiHobListGuid = EFI_HOB_LIST_GUID;

/**
 * UBA Setup Config protocol GUID at 0xB70.
 * The protocol located via RegisterProtocolNotify has its
 * RegisterSetupConfig callback at offset +0x10.
 * GUID: {CD1F9574-DD03-4196-96AD-4965146F9665}
 */
EFI_GUID gUbaSetupConfigProtocolGuid = UBA_SETUP_CONFIG_PROTOCOL_GUID;

/**
 * Setup Configuration Data descriptor at 0xB80.
 * 24-byte structure registered with the UBA board-type protocol.
 */
UBA_SETUP_CONFIG_DATA gSetupConfigData = {
    .Signature          = SETUP_CONFIG_SIGNATURE,    // "PSET"
    .Version            = SETUP_CONFIG_VERSION,      // 1
    .DataSize           = SETUP_CONFIG_DATA_SIZE,    // 0x48C
    .DataSizeDuplicate  = SETUP_CONFIG_DATA_SIZE     // 0x48C
};

//=============================================================================
// Function Implementations
//=============================================================================

/**
 * @brief Returns EFI_NOT_FOUND constant.
 *
 * Trivial leaf function that returns the EFI_NOT_FOUND status code.
 * This function has no callers within this module and appears to be
 * a dead code stub from library linkage.
 *
 * @return 0x800000000000000E (EFI_NOT_FOUND as unsigned 64-bit).
 */
UINT64
ReturnNotFound(
    VOID
    )
{
    return EFI_NOT_FOUND;
}

/**
 * @brief Reads a UINT64 from a pointer with NULL assertion.
 *
 * Simple aligned dereference of the input pointer as UINT64*.
 * Asserts (calls DebugAssert) if the input pointer is NULL.
 * Used by IsGuidMatch() to compare GUID halves.
 *
 * This function corresponds to a UEFI BaseLib ReadUnaligned64() inline
 * with an added NULL check assertion from the debug build.
 *
 * @param[in] Buffer  Pointer to read from. Must not be NULL.
 *
 * @return The UINT64 value at the pointer.
 */
UINT64
ReadUnaligned64(
    IN  VOID   *Buffer
    )
{
    if (Buffer == NULL) {
        DebugAssert(
            "e:\\hs\\MdePkg\\Library\\BaseLib\\Unaligned.c",
            192,
            "Buffer != ((void *) 0)"
            );
    }
    return *(UINT64 *)Buffer;
}

/**
 * @brief Compares two GUIDs using 64-bit unaligned reads.
 *
 * Splits each GUID into two 64-bit halves:
 *   - First half:  Data1 (4 bytes) + Data2 (2 bytes) + Data3_High (2 bytes)
 *   - Second half: Data3_Low (2 bytes) + Data4 (6 bytes)
 *
 * Compares each half independently. This avoids a full CompareGuid()
 * function call and is an optimization for inlined code size.
 *
 * @param[in] Guid1  Pointer to first EFI_GUID (from global data).
 * @param[in] Guid2  Pointer to second EFI_GUID (from ConfigTable entry).
 *
 * @return TRUE if both halves match, FALSE otherwise.
 */
BOOLEAN
IsGuidMatch(
    IN  EFI_GUID  *Guid1,
    IN  EFI_GUID  *Guid2
    )
{
    UINT64 FirstHalf1;
    UINT64 FirstHalf2;
    UINT64 SecondHalf1;
    UINT64 SecondHalf2;

    FirstHalf1  = ReadUnaligned64(Guid1);
    FirstHalf2  = ReadUnaligned64(Guid2);
    SecondHalf1 = ReadUnaligned64((UINT8 *)Guid1 + 8);
    SecondHalf2 = ReadUnaligned64((UINT8 *)Guid2 + 8);

    return (FirstHalf1 == FirstHalf2) && (SecondHalf1 == SecondHalf2);
}

/**
 * @brief Resolves and caches the DebugLib protocol interface.
 *
 * This function lazily initializes the gDebugProtocol global pointer.
 * It performs a UEFI environment validation check first:
 *   1. Allocates POOL_ALLOC_SIZE_CHECK (31) bytes of EfiBootServicesData
 *   2. Frees the allocation immediately
 *   3. Checks if the returned pointer is <= MAX_VALID_ALLOC_PTR (0x10)
 *
 * If the allocation pointer is unexpectedly small or NULL, this indicates
 * a broken or missing UEFI boot services environment, and the function
 * returns NULL (no debug protocol).
 *
 * On passing the check, calls gBS->LocateProtocol() with the
 * DEBUG_LIB_PROTOCOL_GUID and caches the result in gDebugProtocol.
 * If LocateProtocol fails, gDebugProtocol is set to NULL.
 *
 * @return Pointer to the UBA_DEBUG_PROTOCOL interface, or NULL if not found.
 */
UBA_DEBUG_PROTOCOL *
GetDebugProtocol(
    VOID
    )
{
    if (gDebugProtocol == NULL) {
        VOID *PoolBuffer;
        EFI_STATUS Status;

        // Environment validation: allocate and free a small pool buffer
        // to verify boot services are responding.
        PoolBuffer = gBootServices->AllocatePool(EfiBootServicesData, POOL_ALLOC_SIZE_CHECK);
        gBootServices->FreePool(PoolBuffer);

        if ((UINTN)PoolBuffer <= MAX_VALID_ALLOC_PTR) {
            // Boot services appear broken -- return NULL
            return NULL;
        }

        // Locate the DebugLib protocol
        Status = gBootServices->LocateProtocol(
            &gDebugProtocolGuid,
            NULL,
            (VOID **)&gDebugProtocol
            );

        if (EFI_ERROR(Status)) {
            gDebugProtocol = NULL;
        }
    }

    return gDebugProtocol;
}

/**
 * @brief Handles assertion failures via the DebugLib protocol.
 *
 * Calls the DebugAssert function at offset +0x08 of the UBA_DEBUG_PROTOCOL
 * interface. If the debug protocol has not been resolved yet, GetDebugProtocol()
 * is called to lazily initialize it.
 *
 * @param[in] FileName    Source file name of the ASSERT.
 * @param[in] LineNumber  Source line number of the ASSERT.
 * @param[in] Description ASSERT description string.
 */
VOID
DebugAssert(
    IN  CHAR8   *FileName,
    IN  UINTN   LineNumber,
    IN  CHAR8   *Description
    )
{
    UBA_DEBUG_PROTOCOL *Protocol;

    Protocol = GetDebugProtocol();
    if (Protocol != NULL) {
        Protocol->DebugAssert(Protocol, FileName, LineNumber, Description);
    }
}

/**
 * @brief Debug print with platform-aware error level filtering.
 *
 * Before printing, the function reads the platform's debug verbosity level
 * from CMOS RTC register 0x4B:
 *   1. Save current NMI state (bit 7 of CMOS address port)
 *   2. Select CMOS register 0x4B
 *   3. Read the debug level value
 *   4. Restore NMI state
 *
 * The debug level value is interpreted as follows:
 *   - 0: Board is not in debug mode; fall back to the BOARD_CONFIG_REGISTER
 *        (MMIO 0xFDAF0490, bit 1). If bit 1 is set, use level 3; else level 1.
 *   - 1-3: Use the value as-is (1 = board type 1, 2 = board type 2, etc.)
 *   - >3: Use the value as-is
 *
 * The ErrorLevel is then filtered against a mask:
 *   - If board type is 1: ErrorLevel filter = 0x80000004 (error-only)
 *   - Otherwise: ErrorLevel filter = 0x80000006 (all debug)
 *
 * If the ErrorLevel matches the filter mask, the DebugPrint function is
 * called. Otherwise, the call is silently dropped.
 *
 * @param[in] ErrorLevel  Debug error level mask.
 * @param[in] Format      Format string.
 * @param[in] ...         Variable arguments.
 *
 * @return Non-zero if the message was printed, 0 if filtered or no protocol.
 */
UINT8
DebugPrint(
    IN  UINTN   ErrorLevel,
    IN  CHAR8   *Format,
    ...
    )
{
    UBA_DEBUG_PROTOCOL *Protocol;
    UINT64 LevelFilter;
    UINT8 NmiSave;
    UINT8 BoardType;
    VA_LIST Args;

    Protocol = GetDebugProtocol();
    if (Protocol == NULL) {
        return 0;
    }

    // Read CMOS register 0x4B for board type / debug level
    NmiSave = __inbyte(CMOS_ADDRESS_PORT);
    __outbyte(CMOS_ADDRESS_PORT, NmiSave & 0x80 | CMOS_REGISTER_DEBUG);
    BoardType = __inbyte(CMOS_DATA_PORT);

    // Interpret the board type value
    if (BoardType > 3) {
        // Value > 3: if zero, look up fallback
        if (BoardType == 0) {
            // Use MMIO board configuration register as fallback
            BoardType = (*(volatile UINT32 *)BOARD_CONFIG_REGISTER & 2) | 1;
        }
    }

    // Set the error level filter based on board type
    LevelFilter = 0;
    if ((BoardType - 1) <= 0xFD) {
        LevelFilter = 0x80000006;  // Error + Warning + Info
        if (BoardType == 1) {
            LevelFilter = 0x80000004;  // Error only
        }
    }

    // If the ErrorLevel matches the filter, call the protocol
    if ((LevelFilter & ErrorLevel) != 0) {
        VA_START(Args, Format);
        Protocol->DebugPrint(Protocol, Format, Args);
        VA_END(Args);
    }

    return (LevelFilter & ErrorLevel) != 0 ? 1 : 0;
}

/**
 * @brief Locates and caches the HOB list pointer.
 *
 * Scans SystemTable->ConfigurationTable[] for the EFI_HOB_LIST_GUID entry.
 * The configuration table is an array of CONFIG_TABLE_ENTRY structures
 * (24 bytes each: 16-byte GUID + 8-byte pointer).
 *
 * Entry in the ConfigurationTable is found by comparing the GUID using
 * the fast-path IsGuidMatch() function. If found, the pointer at offset
 * +0x10 within the matching entry is cached in gHobList.
 *
 * If the HOB list is not found (empty configuration table or matching
 * entry not present), an ASSERT is raised and gHobList remains NULL.
 *
 * @return Pointer to the HOB list, or NULL if not found.
 */
VOID *
GetHobList(
    VOID
    )
{
    if (gHobList == NULL) {
        UINTN Index;
        UINTN TableCount;
        CONFIG_TABLE_ENTRY *Table;

        // Initialize
        gHobList = NULL;
        TableCount = gSystemTable->NumberOfTableEntries;
        Table = gSystemTable->ConfigurationTable;

        if (TableCount > 0) {
            // Scan the configuration table for the HOB list GUID
            for (Index = 0; Index < TableCount; Index++) {
                if (IsGuidMatch(&gEfiHobListGuid, &Table[Index].VendorGuid)) {
                    gHobList = Table[Index].VendorTable;
                    break;
                }
            }
        }

        // Assert if HOB list was not found
        if (gHobList == NULL) {
            DebugPrint(DEBUG_ERROR_LEVEL_NOT_FOUND, "\nASSERT_EFI_ERROR (Status = %r)\n", EFI_NOT_FOUND);
            DebugAssert(
                "e:\\hs\\MdePkg\\Library\\DxeHobLib\\HobLib.c",
                54,
                "!EFI_ERROR (Status)"
                );
        }

        // Assert if HOB list pointer is NULL after attempted resolution
        if (gHobList == NULL) {
            DebugAssert(
                "e:\\hs\\MdePkg\\Library\\DxeHobLib\\HobLib.c",
                55,
                "mHobList != ((void *) 0)"
                );
        }
    }

    return gHobList;
}

/**
 * @brief Module entry point. Called by the DXE dispatcher.
 *
 * The entry point performs all module work inline -- no registration of
 * timer callbacks or protocol notifications. The flow is:
 *
 * 1. Initialize global UEFI pointers (with assertions):
 *     - gImageHandle = ImageHandle
 *     - gSystemTable = SystemTable
 *     - gBootServices = SystemTable->BootServices
 *     - gRuntimeServices = SystemTable->RuntimeServices
 *
 * 2. Call GetHobList() to locate the HOB list from the configuration table
 *    (asserts if not found)
 *
 * 3. Log platform identification via DebugPrint():
 *     "UBA:SETUPConfigUpdate-TypeLightningRidgeEXECB3"
 *
 * 4. Call gBS->LocateProtocol() with the UBA board-type GUID to resolve
 *    the UBA_BOARD_TYPE_PROTOCOL interface
 *
 * 5. Call RegisterSetupConfig() on the protocol interface at offset +0x10,
 *    passing the UBA Setup Config protocol GUID, the UBA_SETUP_CONFIG_DATA
 *    descriptor, and the data size (24 bytes)
 *
 * @param[in] ImageHandle  Handle for this driver image.
 * @param[in] SystemTable  Pointer to the UEFI system table.
 *
 * @return EFI_STATUS from LocateProtocol if it fails, or from
 *         RegisterSetupConfig on success.
 */
EFI_STATUS
EFIAPI
ModuleEntryPoint(
    IN  EFI_HANDLE        ImageHandle,
    IN  EFI_SYSTEM_TABLE  *SystemTable
    )
{
    EFI_STATUS Status;
    UBA_BOARD_TYPE_PROTOCOL *BoardProtocol;

    // Step 1: Initialize global pointers
    gImageHandle = ImageHandle;
    if (ImageHandle == NULL) {
        DebugAssert(
            "e:\\hs\\MdePkg\\Library\\UefiBootServicesTableLib\\UefiBootServicesTableLib.c",
            51,
            "gImageHandle != ((void *) 0)"
            );
    }

    gSystemTable = SystemTable;
    if (SystemTable == NULL) {
        DebugAssert(
            "e:\\hs\\MdePkg\\Library\\UefiBootServicesTableLib\\UefiBootServicesTableLib.c",
            57,
            "gST != ((void *) 0)"
            );
    }

    gBootServices = SystemTable->BootServices;
    if (gBootServices == NULL) {
        DebugAssert(
            "e:\\hs\\MdePkg\\Library\\UefiBootServicesTableLib\\UefiBootServicesTableLib.c",
            63,
            "gBS != ((void *) 0)"
            );
    }

    gRuntimeServices = SystemTable->RuntimeServices;
    if (gRuntimeServices == NULL) {
        DebugAssert(
            "e:\\hs\\MdePkg\\Library\\UefiRuntimeServicesTableLib\\UefiRuntimeServicesTableLib.c",
            47,
            "gRT != ((void *) 0)"
            );
    }

    // Step 2: Locate the HOB list
    GetHobList();

    // Step 3: Log platform identification string
    DebugPrint(
        DEBUG_ERROR_LEVEL_NOT_FOUND,
        "UBA:SETUPConfigUpdate-TypeLightningRidgeEXECB3\n"
        );

    // Step 4: Locate the UBA board-type protocol
    Status = gBootServices->LocateProtocol(
        &gUbaBoardTypeProtocolGuid,
        NULL,          // No registration -- just locate existing protocol
        (VOID **)&BoardProtocol
        );

    if (!EFI_ERROR(Status)) {
        // Step 5: Register the setup configuration data
        // The RegisterSetupConfig callback is at offset +0x10 in the protocol
        Status = BoardProtocol->RegisterSetupConfig(
            BoardProtocol,
            &gUbaSetupConfigProtocolGuid,
            &gSetupConfigData,
            sizeof(gSetupConfigData)   // 24 bytes
            );
    }

    return Status;
}