/** @file
  Setup Configuration Update driver for NeonCity EP EC B platform.

  This DXE driver implements the platform-specific setup configuration update
  for the NeonCity EP EC B platform. It registers a setup configuration
  protocol data structure via the UBA (Universal BIOS Architecture) board-type
  protocol.

  The driver follows the standard UBA SetupConfigUpdate pattern:
  1. Caches UEFI globals (ImageHandle, SystemTable, BootServices, RuntimeServices)
  2. Locates the HOB list for platform configuration
  3. Logs the driver start via UBA debug protocol
  4. Locates the UBA board-type protocol
  5. Calls the protocol's RegisterSetupConfig function to publish the
     platform-specific setup configuration data

  Copyright (c) 2020-2023, Intel Corporation. All rights reserved.
  SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include "SetupConfigUpdateDxeNeonCityEPECB.h"

//
// ============================================================================
// Module-Level Global Variables (.data section layout)
// ============================================================================
//
// These global variables occupy memory in the .data section at fixed offsets.
// They are accessed directly by the compiled code via RIP-relative addressing.
//
// Address   Name                        Description
// -------   ----                        -----------
// 0xB40     mDebugProtocolGuid          UBA Debug Protocol GUID
// 0xB50     mUbaBoardTypeProtocolGuid   UBA Board-Type Protocol GUID
// 0xB60     mEfiHobListGuid             EFI HOB List GUID
// 0xB68     mEfiHobListGuidSecondHalf   Second 8 bytes of HOB List GUID
// 0xB70     mUbaSetupConfigGuid         UBA Setup Config Protocol GUID
// 0xB80     mSetupConfigData            UBA_SETUP_CONFIG_DATA structure
// 0xBA0     gBS_global                  Cached BootServices pointer
// 0xBA8     gImageHandle_global         Cached ImageHandle
// 0xBB0     gRT_global                  Cached RuntimeServices pointer
// 0xBB8     mDebugProtocol              Cached Debug Protocol interface
// 0xBC0     mHobList                    Cached HOB List pointer
// 0xBC8     mCmosDebugLevel             Cached CMOS debug level
// 0xB98     gST_global                  Cached SystemTable pointer
//

//
// UBA Debug Protocol GUID
//
EFI_GUID  mDebugProtocolGuid = UBA_DEBUG_PROTOCOL_GUID;

//
// UBA Board-Type Protocol GUID for NeonCity EP EC B
//
EFI_GUID  mUbaBoardTypeProtocolGuid = UBA_BOARD_TYPE_PROTOCOL_GUID;

//
// EFI HOB List GUID - used to locate the HOB list from the system
// configuration table. The GUID is split into two 64-bit halves for
// the optimized comparison in IsHobListGuid().
//   First  8 bytes: 0x11D493D77739F24C (GUID.Data1 + Data2 + Data3 high)
//   Second 8 bytes: 0x4DC13F2700903A9A (GUID.Data3 low + Data4)
//
EFI_GUID  mEfiHobListGuid = EFI_HOB_LIST_GUID;

//
// UBA Setup Config Protocol GUID for NeonCity EP EC B
//
EFI_GUID  mUbaSetupConfigGuid = UBA_SETUP_CONFIG_PROTOCOL_GUID;

//
// Setup Configuration Data block.
// Signature "PSET" (Platform SETup), Version 1, DataSize 0x48C (1164 bytes).
// This data is provided to the setup engine via the board-type protocol.
//
UBA_SETUP_CONFIG_DATA  mSetupConfigData = {
  { 'P', 'S', 'E', 'T' },    // Signature
  1,                          // Version
  0x48C,                      // DataSize
  0x48C                       // DataSize2 (duplicate)
};

//
// Cached UEFI global pointers.
//
EFI_HANDLE             *gImageHandle_global = NULL;    // 0xBA8
EFI_SYSTEM_TABLE       *gST_global = NULL;             // 0xB98
EFI_BOOT_SERVICES      *gBS_global = NULL;             // 0xBA0
EFI_RUNTIME_SERVICES   *gRT_global = NULL;             // 0xBB0

//
// Cached protocol and data pointers.
//
VOID   *mDebugProtocol = NULL;   // 0xBB8 - Cached Debug Protocol
VOID   *mHobList       = NULL;   // 0xBC0 - Cached HOB List
UINT8  mCmosDebugLevel = 0;     // 0xBC8 - Cached CMOS Debug Level

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

/**
  Returns the UEFI status code for "Not Found".

  This is a simple helper that returns a constant. It is used in contexts
  where a function pointer returning EFI_STATUS is needed.

  @return EFI_NOT_FOUND (0x800000000000000E).

**/
UINT64
EFIAPI
ReturnNotFound (
  VOID
  )
{
  return EFI_NOT_FOUND;
}

/**
  Retrieves the UBA Debug Protocol interface.

  Attempts to locate the UBA Debug Protocol via gBS->LocateProtocol().
  Before doing so, performs a small pool allocation test to verify that
  the UEFI boot services are functioning correctly.

  The result is cached in mDebugProtocol for subsequent calls.

  @return Pointer to the UBA Debug Protocol interface, or NULL if not found.

**/
UINT64 *
GetDebugProtocol (
  VOID
  )
{
  UINTN   PoolSize;
  UINT64  *Protocol;

  //
  // Return cached value if already resolved.
  //
  Protocol = mDebugProtocol;
  if (Protocol != NULL) {
    return Protocol;
  }

  //
  // Allocate a small buffer (31 bytes = EfiBootServicesData pool type index)
  // and immediately free it. This validates that boot services are operational.
  // If the returned pointer value is > 0x10, the environment may not support
  // protocol lookup.
  //
  PoolSize = (UINTN)gBS_global->AllocatePool (EfiBootServicesData, 31);
  gBS_global->FreePool ((VOID *)PoolSize);

  if (PoolSize <= 0x10) {
    //
    // Valid UEFI environment. Locate the UBA Debug Protocol.
    //
    gBS_global->LocateProtocol (
                 &mDebugProtocolGuid,
                 NULL,
                 &mDebugProtocol
                 );

    //
    // Cache the result (or NULL if not found).
    //
    Protocol = mDebugProtocol;
    if (Protocol == NULL) {
      mDebugProtocol = NULL;
      return NULL;
    }
  } else {
    mDebugProtocol = NULL;
    return NULL;
  }

  return Protocol;
}

/**
  Debug assertion handler via UBA Debug Protocol.

  Calls the assertion function (at offset 0x08 in the protocol interface)
  of the UBA Debug Protocol to report a failed assertion.

  @param[in]  FileName     Pointer to the source file name string.
  @param[in]  LineNumber   Line number where the assertion occurred.
  @param[in]  Description  Pointer to the description of the failed assertion.

  @return The return value from the protocol assertion handler, or 0 if the
          protocol is not available.

**/
UINTN
DebugAssert (
  IN CONST CHAR8  *FileName,
  IN UINTN        LineNumber,
  IN CONST CHAR8  *Description
  )
{
  UINT64  *Protocol;

  Protocol = GetDebugProtocol ();
  if (Protocol != NULL) {
    //
    // Call the assertion handler at protocol offset 0x08.
    // Args: rcx = FileName, rdx = LineNumber, r8 = Description
    //
    return ((UBA_DEBUG_PROTOCOL *)Protocol)->DebugAssert (
             FileName,
             LineNumber,
             Description
             );
  }

  return 0;
}

/**
  Debug print function via UBA Debug Protocol.

  This function resolves the UBA Debug Protocol and, if the requested
  error level is enabled by the CMOS debug level configuration, calls
  the protocol's print function.

  The CMOS debug level is read from RTC port 0x70/0x71 at register index
  0x4B. The level determines which debug messages are filtered:
  - Level 0:  Fallback to MMIO board config register 0xFDAF0490
  - Level 1:  Filter mask = 0x80000004 (DEBUG_INIT | DEBUG_INFO)
  - Level >1: Filter mask = 0x80000046 (DEBUG_INIT | DEBUG_WARN |
              DEBUG_ERROR | DEBUG_INFO)
  - Level >3: Use cached value or board config fallback

  @param[in]  ErrorLevel  Debug error level mask.
  @param[in]  Format      Format string for the debug message.
  @param[in]  ...         Variable arguments for the format string.

  @return The return value from the protocol print function, or 0.

**/
UINTN
EFIAPI
DebugPrint (
  IN UINTN       ErrorLevel,
  IN CONST CHAR8 *Format,
  ...
  )
{
  UINT64  *Protocol;
  UINTN   Result;
  UINT8   CmosValue;
  UINT8   DebugLevel;
  UINT32  DebugMask;
  VA_LIST VaList;

  VA_START (VaList, Format);

  //
  // Get the UBA Debug Protocol interface.
  //
  Protocol = GetDebugProtocol ();
  Result   = 0;

  if (Protocol != NULL) {
    //
    // Read the current CMOS index register, preserving bit 7 (NMI enable).
    // Select CMOS register index 0x4B for the debug level.
    //
    CmosValue  = IoRead8 (RTC_INDEX_PORT);
    CmosValue  = (CmosValue & 0x80) | CMOS_DEBUG_LEVEL_REGISTER;
    IoWrite8  (RTC_INDEX_PORT, CmosValue);

    //
    // Read the debug level from CMOS data port 0x71.
    //
    DebugLevel = IoRead8 (RTC_DATA_PORT);

    //
    // Determine the effective debug level.
    //
    if (DebugLevel > 3) {
      //
      // Level > 3 means the raw value is not a simple level encoding.
      // Use the cached value.
      //
      DebugLevel = mCmosDebugLevel;
      if (DebugLevel == 0) {
        //
        // Fallback: read board configuration from MMIO register 0xFDAF0490.
        // Bit 1 indicates the board type, and bit 0 is always set.
        //
        DebugLevel = (MmioRead32 (BOARD_CONFIG_MMIO_ADDR) & 2) | 1;
      }
    }

    //
    // Calculate the debug mask from the level.
    // Level must be >= 1 and level - 1 <= 0xFD.
    //
    if ((DebugLevel > 0) && ((DebugLevel - 1) <= 0xFD)) {
      //
      // Map platform ID to debug filter mask.
      // Level 1:  mask = 0x80000004 (DEBUG_INIT | DEBUG_INFO)
      // Level >1: mask = 0x80000046 (multiple debug flags)
      //
      if (DebugLevel == 1) {
        DebugMask = 0x80000004;
      } else {
        DebugMask = 0x80000046;
      }

      //
      // If the requested ErrorLevel is enabled by the mask, call the
      // protocol's print function at offset 0x00.
      //
      if ((DebugMask & ErrorLevel) != 0) {
        Result = ((UBA_DEBUG_PROTOCOL *)Protocol)->DebugPrint (
                   ErrorLevel,
                   Format,
                   VaList
                   );
      }
    }
  }

  return Result;
}

/**
  Locates the HOB (Hand-Off Block) list from the UEFI System Table.

  Scans SystemTable->ConfigurationTable[] for an entry whose VendorGuid
  matches EFI_HOB_LIST_GUID. The comparison is done by splitting the
  16-byte GUID into two 8-byte halves.

  The result is cached in mHobList for subsequent calls.

  @return Pointer to the HOB list, or NULL if not found.

**/
VOID *
GetHobList (
  VOID
  )
{
  UINTN                  Index;
  UINTN                  TableCount;
  EFI_CONFIGURATION_TABLE  *ConfigTable;

  //
  // Return cached value if already resolved.
  //
  if (mHobList != NULL) {
    return mHobList;
  }

  //
  // Initialize to NULL.
  //
  mHobList = NULL;

  //
  // Check if there are configuration table entries.
  //
  TableCount = gST_global->NumberOfTableEntries;
  if (TableCount > 0) {
    //
    // Get pointer to the configuration table array.
    //
    ConfigTable = gST_global->ConfigurationTable;

    for (Index = 0; Index < TableCount; Index++) {
      //
      // Compare the current entry's VendorGuid against EFI_HOB_LIST_GUID.
      //
      if (IsHobListGuid (&ConfigTable[Index].VendorGuid)) {
        //
        // Found the HOB list. Extract the VendorTable pointer.
        //
        mHobList = ConfigTable[Index].VendorTable;
        return mHobList;
      }
    }
  }

  //
  // HOB list not found. Raise ASSERT_EFI_ERROR.
  //
  DebugPrint (0x80000000, L"\nASSERT_EFI_ERROR (Status = %r)\n", EFI_NOT_FOUND);
  DebugAssert (
    L"e:\\hs\\MdePkg\\Library\\DxeHobLib\\HobLib.c",
    54,
    L"!EFI_ERROR (Status)"
    );

  //
  // If mHobList is still NULL, raise another assertion.
  //
  if (mHobList == NULL) {
    DebugAssert (
      L"e:\\hs\\MdePkg\\Library\\DxeHobLib\\HobLib.c",
      55,
      L"mHobList != ((void *) 0)"
      );
  }

  return mHobList;
}

/**
  Compares a given GUID against the EFI HOB List GUID.

  The comparison uses two 8-byte unaligned reads (via ReadUnaligned64)
  to compare the GUID halves independently, rather than a full 16-byte
  EFI_GUID comparison.

  @param[in]  GuidPtr  Pointer to the EFI_GUID to compare.

  @retval TRUE   The GUID matches EFI_HOB_LIST_GUID.
  @retval FALSE  The GUID does not match.

**/
BOOLEAN
IsHobListGuid (
  IN EFI_GUID  *GuidPtr
  )
{
  //
  // Compare first 8 bytes of the GUID against the first half of the
  // cached EFI_HOB_LIST_GUID.
  //
  if (ReadUnaligned64 (&mEfiHobListGuid) != ReadUnaligned64 (GuidPtr)) {
    return FALSE;
  }

  //
  // Compare second 8 bytes of the GUID (at offset 8).
  //
  return ReadUnaligned64 ((UINT8 *)&mEfiHobListGuid + 8) ==
         ReadUnaligned64 ((UINT8 *)GuidPtr + 8);
}

/**
  Reads an unaligned 64-bit value from memory.

  Wraps a simple volatile memory read with a NULL pointer check.

  @param[in]  Buffer  Pointer to the memory to read. Must not be NULL.

  @return The 64-bit value read from the given address.

**/
UINT64
ReadUnaligned64 (
  IN CONST VOID  *Buffer
  )
{
  if (Buffer == NULL) {
    DebugAssert (
      L"e:\\hs\\MdePkg\\Library\\BaseLib\\Unaligned.c",
      192,
      L"Buffer != ((void *) 0)"
      );
  }

  return *(volatile UINT64 *)Buffer;
}

/**
  Module entry point for SetupConfigUpdateDxeNeonCityEPECB.

  This function initializes the driver by:
  1. Caching UEFI global pointers with assertion checks.
  2. Locating the HOB list.
  3. Logging driver initialization via UBA debug protocol.
  4. Locating the UBA board-type protocol for NeonCity EP EC B.
  5. Calling RegisterSetupConfig to publish the setup configuration data.

  @param[in]  ImageHandle  The firmware-allocated handle for this driver image.
  @param[in]  SystemTable  A pointer to the EFI System Table.

  @retval EFI_SUCCESS           The setup config data was registered.
  @retval EFI_INVALID_PARAMETER A required parameter was NULL.
  @retval Others                Error from LocateProtocol if the UBA
                                board-type protocol is not available.

**/
EFI_STATUS
EFIAPI
SetupConfigUpdateDxeNeonCityEPECBEntryPoint (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  EFI_STATUS                Status;
  UBA_BOARD_TYPE_PROTOCOL   *BoardProtocol;

  //
  // Cache ImageHandle with assertion check.
  //
  gImageHandle_global = (EFI_HANDLE *)ImageHandle;
  if (ImageHandle == NULL) {
    DebugAssert (
      L"e:\\hs\\MdePkg\\Library\\UefiBootServicesTableLib\\UefiBootServicesTableLib.c",
      51,
      L"gImageHandle != ((void *) 0)"
      );
  }

  //
  // Cache SystemTable with assertion check.
  //
  gST_global = SystemTable;
  if (SystemTable == NULL) {
    DebugAssert (
      L"e:\\hs\\MdePkg\\Library\\UefiBootServicesTableLib\\UefiBootServicesTableLib.c",
      57,
      L"gST != ((void *) 0)"
      );
  }

  //
  // Cache BootServices from SystemTable with assertion check.
  //
  gBS_global = SystemTable->BootServices;
  if (gBS_global == NULL) {
    DebugAssert (
      L"e:\\hs\\MdePkg\\Library\\UefiBootServicesTableLib\\UefiBootServicesTableLib.c",
      63,
      L"gBS != ((void *) 0)"
      );
  }

  //
  // Cache RuntimeServices from SystemTable with assertion check.
  //
  gRT_global = SystemTable->RuntimeServices;
  if (gRT_global == NULL) {
    DebugAssert (
      L"e:\\hs\\MdePkg\\Library\\UefiRuntimeServicesTableLib\\UefiRuntimeServicesTableLib.c",
      47,
      L"gRT != ((void *) 0)"
      );
  }

  //
  // Locate the HOB list from the system configuration table.
  //
  GetHobList ();

  //
  // Print debug banner.
  //
  DebugPrint (0x80000000, L"UBA:SETUPConfigUpdate-TypeNeonCityEPECB\n");

  //
  // Locate the UBA board-type protocol.
  //
  BoardProtocol = NULL;
  Status = gBS_global->LocateProtocol (
                         &mUbaBoardTypeProtocolGuid,
                         NULL,
                         (VOID **)&BoardProtocol
                         );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // Call the board-type protocol's RegisterSetupConfig function.
  // This registers the setup configuration GUID and data block for
  // the NeonCity EP EC B platform.
  //
  return BoardProtocol->RegisterSetupConfig (
                          BoardProtocol,
                          &mUbaSetupConfigGuid,
                          &mSetupConfigData,
                          sizeof (UBA_SETUP_CONFIG_DATA)
                          );
}