/**
 * @file SetupConfigUpdateDxeNeonCityEPRP.c
 * @brief UEFI DXE driver - SetupConfigUpdate for NeonCity EPRP platform
 *
 * This driver is a small UEFI DXE driver responsible for:
 * 1. Initializing UEFI boot services and runtime services globals
 * 2. Locating the HOB (Hand-off Block) list for firmware configuration
 * 3. Locating the SetupConfig protocol and registering a notification callback
 * 4. Providing debug output and assertion infrastructure
 *
 * The module is approximately 3.2 KB and contains 8 functions.
 */

#include "SetupConfigUpdateDxeNeonCityEPRP.h"

//
// ---------------------------------------------------------------------------
// Global data
// ---------------------------------------------------------------------------

EFI_GUID  gDebugProtocolGuid     = { 0x00000000, 0x0000, 0x0000,
                                      { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } };

EFI_GUID  gSetupConfigProtocolGuid = { 0x00000000, 0x0000, 0x0000,
                                        { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } };

EFI_GUID  gTargetHobGuid_lo      = { 0x00000000, 0x0000, 0x0000,
                                      { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } };

EFI_GUID  gTargetHobGuid_hi      = { 0x00000000, 0x0000, 0x0000,
                                      { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } };

EFI_GUID  gSetupConfigNotifyGuid = { 0x00000000, 0x0000, 0x0000,
                                      { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } };

VOID      *gDebugProtocolInterface = NULL;
VOID      *gHobList                 = NULL;
VOID      *gSetupConfigRegistration = NULL;

//
// CMOS debug-level storage (data segment at 0xBC8)
//
UINT8     gCmosDebugLevel;

//
// ---------------------------------------------------------------------------
// EfiGetLastErrorCode
// ---------------------------------------------------------------------------

/**
 * Returns the EFI_ALREADY_STARTED status code.
 *
 * This function is used internally to provide a well-known error status
 * value for assertion failure and debug reporting.
 *
 * @return  EFI_ALREADY_STARTED (0x800000000000000E)
 */
EFI_STATUS
EFIAPI
EfiGetLastErrorCode (
  VOID
  )
{
  return EFI_ALREADY_STARTED;
}

//
// ---------------------------------------------------------------------------
// GetDebugProtocol
// ---------------------------------------------------------------------------

/**
 * Retrieves the debug protocol interface.
 *
 * Uses a lazy initialization pattern: checks the cached pointer first;
 * if NULL, allocates memory and calls gBS->LocateProtocol to find the
 * debug protocol. The result is cached globally.
 *
 * @return  Pointer to the debug protocol interface, or NULL on failure.
 */
VOID *
GetDebugProtocol (
  VOID
  )
{
  VOID       *DebugProtocol;
  UINT64     PoolSize;
  EFI_STATUS Status;

  DebugProtocol = gDebugProtocolInterface;
  if (DebugProtocol == NULL) {
    //
    // Allocate pool header check - verify pool allocation size <= 0x10
    // This is a standard UEFI memory allocation guard
    //
    PoolSize = (UINT64)gBS->AllocatePool (EfiBootServicesData, 31);
    gBS->FreePool (PoolSize);

    if (PoolSize <= 0x10) {
      Status = gBS->LocateProtocol (
                      &gDebugProtocolGuid,
                      NULL,
                      &gDebugProtocolInterface
                      );
      DebugProtocol = gDebugProtocolInterface;
      if (EFI_ERROR (Status)) {
        DebugProtocol = NULL;
        gDebugProtocolInterface = NULL;
      }
    }
  }

  return DebugProtocol;
}

//
// ---------------------------------------------------------------------------
// DebugPrint
// ---------------------------------------------------------------------------

/**
 * Debug print with CMOS-based debug level filtering.
 *
 * Reads the CMOS diagnostic register (offset 0x4B) to determine the current
 * platform debug level. The debug level determines which error level masks
 * are enabled. Messages are only printed if the ErrorLevel matches the
 * current debug configuration:
 *
 *   Debug Level 1:  ErrorLevel 0x80000004 (EFI_D_ERROR | EFI_D_INIT)
 *   Debug Level 2+: ErrorLevel 0x80000006 (broader)
 *   Debug Level 0:  Falls back to hardware strap (FDAF0490 bit 1 | 1)
 *
 * @param[in] ErrorLevel  Debug error level mask.
 * @param[in] Format      Format string.
 * @param[in] ...         Variable arguments.
 */
VOID
EFIAPI
DebugPrint (
  IN UINTN       ErrorLevel,
  IN CONST CHAR8 *Format,
  ...
  )
{
  VOID     *DebugProtocol;
  UINTN    EnabledMask;
  UINT8    DiagValue;
  UINT8    DebugLevel;
  VA_LIST  VaList;

  VA_START (VaList, Format);

  DebugProtocol = GetDebugProtocol ();
  EnabledMask = 0;

  if (DebugProtocol != NULL) {
    //
    // Read CMOS diagnostic register at index 0x4B via RTC ports 0x70/0x71
    //
    IoWrite8  (RTC_INDEX_PORT, IoRead8 (RTC_INDEX_PORT) & 0x80 | CMOS_DIAG_ADDR);
    DebugLevel = IoRead8 (RTC_DATA_PORT);

    //
    // Validate debug level; handle special values
    //
    if (DebugLevel > 3) {
      if (DebugLevel == 0) {
        //
        // Debug level 0: read hardware strap from memory-mapped GPIO
        //
        DebugLevel = (*(volatile UINT8 *)(UINTN)0xFDAF0490) & 2 | 1;
      }
    }

    //
    // Determine enabled error masks based on debug level
    //
    if ((DebugLevel - 1) <= 0xFD) {
      //
      // Debug level >= 1: standard masks apply
      //
      if (DebugLevel == 1) {
        EnabledMask = 0x80000004;  // EFI_D_ERROR | EFI_D_INIT
      } else {
        EnabledMask = 0x80000006;  // EFI_D_ERROR | EFI_D_INIT | broader
      }
    }

    //
    // Call the protocol's DebugPrint function if error level matches
    //
    if ((EnabledMask & ErrorLevel) != 0) {
      ((DEBUG_PROTOCOL_PRINT)DebugProtocol) (ErrorLevel, Format, VaList);
    }
  }
}

//
// ---------------------------------------------------------------------------
// DebugAssert
// ---------------------------------------------------------------------------

/**
 * Debug assertion handler.
 *
 * Retrieves the debug protocol and calls its assertion handler.
 * The assertion handler typically halts or reboots the system.
 *
 * @param[in] FileName     Source file name where the assertion occurred.
 * @param[in] LineNumber   Line number of the assertion.
 * @param[in] Description  Description of the assertion condition.
 */
VOID
EFIAPI
DebugAssert (
  IN CONST CHAR8 *FileName,
  IN UINTN       LineNumber,
  IN CONST CHAR8 *Description
  )
{
  VOID *DebugProtocol;

  DebugProtocol = GetDebugProtocol ();
  if (DebugProtocol != NULL) {
    ((DEBUG_PROTOCOL_ASSERT)DebugProtocol) (FileName, LineNumber, Description);
  }
}

//
// ---------------------------------------------------------------------------
// GetHobList
// ---------------------------------------------------------------------------

/**
 * Retrieves the HOB (Hand-off Block) list pointer by GUID matching.
 *
 * Walks the system table's HOB list (from SystemTable->HobList at offset
 * 0x68 = 104 decimal in x64). Each HOB entry is 24 bytes and contains
 * a GUID at offset +0 which is compared against the target GUID.
 *
 * On failure, issues an ASSERT_EFI_ERROR and falls back to cached value.
 *
 * @return  Pointer to the matching HOB, or NULL if the HOB list was empty
 *          or no matching HOB was found.
 */
VOID *
EFIAPI
GetHobList (
  VOID
  )
{
  VOID      *Hob;
  UINT64    HobCount;
  UINT64    Index;
  UINT64    Offset;

  Hob = (VOID *)gHobList;
  if (gHobList == NULL) {
    gHobList = NULL;
    HobCount = *(UINT64 *)(SystemTable + 104);

    if (HobCount != 0) {
      Offset = 0;
      for (Index = 0; Index < HobCount; Index++) {
        if (GuidCompare (
              (EFI_GUID *)(UINTN)(Offset + *(UINT64 *)(SystemTable + 112)),
              (EFI_GUID *)(UINTN)(Offset + *(UINT64 *)(SystemTable + 112))
              )) {
          //
          // Found matching HOB GUID
          //
          Hob = *(VOID **)(*(UINT64 *)(SystemTable + 112) + 24 * Index + 16);
          gHobList = (UINT64)Hob;
          return Hob;
        }
        Offset += 24;
      }

      //
      // No match found: ASSERT
      //
      DebugPrint (DEBUG_ERROR, L"\nASSERT_EFI_ERROR (Status = %r)\n", EFI_ALREADY_STARTED);
      DebugAssert (__FILE__, __LINE__, "!EFI_ERROR (Status)");
      Hob = (VOID *)gHobList;
    }

    if (Hob == NULL) {
      DebugAssert (__FILE__, __LINE__, "mHobList != ((void *) 0)");
    }
  }

  return Hob;
}

//
// ---------------------------------------------------------------------------
// GuidCompare
// ---------------------------------------------------------------------------

/**
 * Compares two 16-byte GUIDs by splitting into high/low QWORD pairs.
 *
 * Uses unaligned QWORD reads to compare both halves of the GUIDs.
 * The target GUID is stored split across gTargetHobGuid_lo and
 * gTargetHobGuid_hi.
 *
 * @param[in] Guid1  Pointer to first GUID (from HOB list).
 * @param[in] Guid2  Pointer to second GUID (target).
 *
 * @return TRUE if both halves match, FALSE otherwise.
 */
BOOLEAN
EFIAPI
GuidCompare (
  IN EFI_GUID *Guid1,
  IN EFI_GUID *Guid2
  )
{
  UINT64  Lo1, Lo2;
  UINT64  Hi1, Hi2;

  Lo1 = ReadUnalignedQword (&gTargetHobGuid_lo);
  Lo2 = ReadUnalignedQword (Guid2);
  Hi1 = ReadUnalignedQword (&gTargetHobGuid_hi);
  Hi2 = ReadUnalignedQword ((UINT8 *)Guid2 + 8);

  return (Lo1 == Lo2) && (Hi1 == Hi2);
}

//
// ---------------------------------------------------------------------------
// ReadUnalignedQword
// ---------------------------------------------------------------------------

/**
 * Reads an unaligned QWORD from memory.
 *
 * Performs a direct 8-byte read from the given address. Asserts if the
 * buffer pointer is NULL.
 *
 * @param[in] Buffer  Pointer to the data to read (must not be NULL).
 *
 * @return The QWORD value at Buffer.
 */
UINT64
EFIAPI
ReadUnalignedQword (
  IN CONST VOID *Buffer
  )
{
  ASSERT (Buffer != NULL);
  return *(volatile UINT64 *)Buffer;
}

//
// ---------------------------------------------------------------------------
// ModuleEntryPoint
// ---------------------------------------------------------------------------

/**
 * DXE module entry point.
 *
 * Initialization sequence:
 * 1. Save the ImageHandle and SystemTable to globals (via standard UEFI
 *    boot services library initialization with asserts).
 * 2. Locate the HOB list via GetHobList().
 * 3. Emit a debug message identifying this driver.
 * 4. Locate the SetupConfig protocol via gBS->LocateProtocol.
 * 5. Register a notification callback on the SetupConfig notify GUID.
 *
 * @param[in] ImageHandle  The firmware allocated handle for the EFI image.
 * @param[in] SystemTable  A pointer to the EFI System Table.
 *
 * @return EFI_SUCCESS if all initialization steps completed.
 */
EFI_STATUS
EFIAPI
ModuleEntryPoint (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  EFI_STATUS  Status;
  VOID        *SetupConfigProtocol;
  UINT64      RegistrationHandle;

  //
  // Standard UEFI driver initialization: save ImageHandle and SystemTable
  //
  gImageHandle = (UINT64)ImageHandle;
  ASSERT (ImageHandle != NULL);
  gST = (UINT64)SystemTable;
  ASSERT (SystemTable != NULL);
  gBS = (UINT64)SystemTable->BootServices;
  ASSERT (gBS != NULL);
  gRT = (UINT64)SystemTable->RuntimeServices;
  ASSERT (gRT != NULL);

  //
  // Initialize HOB list
  //
  GetHobList ();

  //
  // Register debug message for this driver
  //
  RegistrationHandle = 0;
  DebugPrint (DEBUG_ERROR, "UBA:SETUPConfigUpdate-TypeNeonCityEPRP\n");

  //
  // Locate the SetupConfig protocol
  //
  Status = gBS->LocateProtocol (
                  &gSetupConfigProtocolGuid,
                  NULL,
                  &SetupConfigProtocol
                  );

  if (!EFI_ERROR (Status)) {
    //
    // Register notification callback for SetupConfig updates
    // The protocol at offset +16 (notify function) is called with:
    //   - The SetupConfig protocol interface
    //   - The notification GUID
    //   - The registration handle
    //   - A 24-byte context/configuration block
    //
    return ((EFI_STATUS (EFIAPI *)(VOID *, VOID *, VOID *, UINTN))(
              SetupConfigProtocol + 16)) (
              SetupConfigProtocol,
              &gSetupConfigNotifyGuid,
              &gSetupConfigRegistration,
              24
              );
  }

  return Status;
}