/** @file
  OpromUpdateDxeNeonCityFPGA - UEFI DXE driver implementation.

  This DXE driver provides Option ROM (OpROM) update configuration for the
  NeonCityFPGA platform via the UBA (Unified Board Architecture) protocol.
  It registers callback functions that define PCIe slot-to-adapter mappings,
  enabling the platform to correctly configure option ROMs for installed
  PCIe devices.

  The driver follows the standard DXE initialization pattern:
  1. Cache system table pointers (gImageHandle, gST, gBS, gRT)
  2. Locate HOB list from configuration table
  3. Locate UBA NeonCityFPGA board-type protocol
  4. Register OpROM update configuration callbacks

  Build info:
    Source: e:\hs\Build\HR6N0XMLK\DEBUG_VS2015\X64\PurleyRpPkg\Uba\UbaMain\Dxe\TypeNeonCityFPGA\OpromUpdateDxe\OpromUpdateDxe\DEBUG\
    Toolchain: VS2015, X64 DEBUG

  Copyright (c) Lenovo. All rights reserved.
  SPDX-License-Identifier: BSD-2-Clause-Patent

  DECOMPILATION NOTE: This file is a reconstructed representation based on
  static analysis of the compiled binary. Some function signatures and types
  are inferred and may not match the original source exactly. The code uses
  MSVC extensions (__inbyte/__outbyte, VA_START/VA_END macros) and assumes
  a UEFI environment with DXE drivers.
**/

#include "OpromUpdateDxeNeonCityFPGA.h"

///
/// Global variables - cached UEFI system table pointers and protocol interfaces.
/// Initialized by _ModuleEntryPoint.
///
EFI_HANDLE              gImageHandle     = NULL; ///< @ 0xF20: Image handle from DXE dispatcher
EFI_SYSTEM_TABLE        *gST             = NULL; ///< @ 0xF10: System table pointer
EFI_BOOT_SERVICES       *gBS             = NULL; ///< @ 0xF18: Boot services pointer
EFI_RUNTIME_SERVICES    *gRT             = NULL; ///< @ 0xF28: Runtime services pointer
VOID                    *gDebugProtocol  = NULL; ///< @ 0xF30: Cached DebugLib protocol interface
VOID                    *gHobList        = NULL; ///< @ 0xF38: Cached HOB list pointer

///
/// Protocol GUIDs and configuration data (defined in .rdata section).
///

/// @ 0xCC0: EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_GUID
/// {BB7E702F-1A4A-11D4-9A38-0090273FC14D}
extern EFI_GUID  gPciRootBridgeIoProtocolGuid;

/// @ 0xCD0: DEBUG_LIB_PROTOCOL_GUID
/// {36232936-0E76-31C8-A13A-3AF2FC1C3932}
extern EFI_GUID  gDebugLibProtocolGuid;

/// @ 0xCE0: UBA_NEONCITY_FPGA_BOARD_TYPE_PROTOCOL_GUID
/// {E03E0D46-5263-4845-B0A4-58D57B3177E2}
extern EFI_GUID  gUbaBoardTypeProtocolGuid;

/// @ 0xCF0: EFI_HOB_LIST_GUID first half (8 bytes)
extern UINT64  gEfiHobListGuidFirstHalf;
/// @ 0xCF8: EFI_HOB_LIST_GUID second half (8 bytes)
extern UINT64  gEfiHobListGuidSecondHalf;

/// @ 0xD00: PCIe slot configuration protocol table 0 (8 QWORDs)
extern UINT64  gPcieSlotConfigTable0[8];

/// @ 0xD40: PCIe slot table 1 (6 entries, 40 bytes each)
extern UINT8  gPcieSlotTable1[240];

/// @ 0xE40: PCIe slot table 2 (10 entries, 40 bytes each)
extern UINT8  gPcieSlotTable2[400];

/// @ 0xEA8: OPCROM_UPDATE_CONFIG_GUID
/// {371BD79C-DE79-4C5F-AA2B-BC9EBEFA988F}
extern EFI_GUID  gOpromUpdateConfigGuid;

/// @ 0xEB8: UBA_OPROM_UPDATE_CONFIG
extern UBA_OPROM_UPDATE_CONFIG  gUbaOpromUpdateConfig;

/// @ 0xEF1: PCIE_SLOT_BIT_CONFIG[8]
extern PCIE_SLOT_BIT_CONFIG  gPcieSlotBitConfig[8];

///
/// Forward declarations of internal functions
///
VOID    *EFIAPI GetHobList        (VOID);
VOID    *EFIAPI GetDebugProtocol  (VOID);
UINTN   EFIAPI DebugPrint         (UINTN ErrorLevel, CONST CHAR8 *Format, ...);
VOID    EFIAPI DebugAssert        (CONST CHAR8 *FileName, UINTN LineNumber, CONST CHAR8 *Description);

// ---------------------------------------------------------------------------
// Helper: ReadUnaligned64
// @address 0x8A8
// ---------------------------------------------------------------------------

/**
  Reads an unaligned 64-bit value from memory with NULL pointer assertion.

  @param[in]  Buffer  Pointer to the 64-bit value to read (must not be NULL).

  @return The 64-bit value at the given address.
**/
UINT64
EFIAPI
ReadUnaligned64 (
  IN CONST UINT64  *Buffer
  )
{
  if (Buffer == NULL) {
    DebugAssert (
      "e:\\hs\\MdePkg\\Library\\BaseLib\\Unaligned.c",
      192,
      "Buffer != ((void *) 0)"
      );
  }

  return *Buffer;
}

// ---------------------------------------------------------------------------
// Helper: IsGuidEqual
// @address 0x838
// ---------------------------------------------------------------------------

/**
  Compares two GUIDs by reading each as two 8-byte unaligned halves.

  @param[in]  Guid1  Pointer to the first GUID.
  @param[in]  Guid2  Pointer to the second GUID.

  @retval TRUE   GUIDs match.
  @retval FALSE  GUIDs differ.
**/
BOOLEAN
EFIAPI
IsGuidEqual (
  IN EFI_GUID  *Guid1,
  IN EFI_GUID  *Guid2
  )
{
  return (BOOLEAN)(
    ReadUnaligned64 ((UINT64 *)Guid1)       == ReadUnaligned64 ((UINT64 *)Guid2) &&
    ReadUnaligned64 ((UINT64 *)Guid1 + 1)   == ReadUnaligned64 ((UINT64 *)Guid2 + 1)
    );
}

// ---------------------------------------------------------------------------
// Internal: GetHobList
// @address 0x760
// ---------------------------------------------------------------------------

/**
  Scans SystemTable->ConfigurationTable[] for EFI_HOB_LIST_GUID and caches
  the result in gHobList.

  @return Pointer to the HOB list, or NULL if not found.
**/
VOID *
EFIAPI
GetHobList (
  VOID
  )
{
  UINTN   Index;
  UINTN   EntryCount;
  UINT64  EntryBase;

  if (gHobList != NULL) {
    return gHobList;
  }

  gHobList = NULL;
  //
  // SystemTable is at a fixed offset from gST.
  // gST + 0x68 (104) = NumberOfTableEntries
  // gST + 0x70 (112) = ConfigurationTable pointer
  //
  EntryCount = *(UINTN *)((UINT8 *)gST + 104);
  if (EntryCount != 0) {
    EntryBase = *(UINT64 *)((UINT8 *)gST + 112);
    for (Index = 0; Index < EntryCount; Index++) {
      //
      // Each config table entry: 16-byte GUID + 8-byte pointer (24 bytes total)
      //
      if (IsGuidEqual (
            (EFI_GUID *)(EntryBase + Index * 24),
            (EFI_GUID *)&gEfiHobListGuidFirstHalf
            ))
      {
        gHobList = *(VOID **)(EntryBase + Index * 24 + 16);
        break;
      }
    }
  }

  if (gHobList == NULL) {
    DebugPrint (0x80000000, "\nASSERT_EFI_ERROR (Status = %r)\n", 0x800000000000000EULL);
    DebugAssert (
      "e:\\hs\\MdePkg\\Library\\DxeHobLib\\HobLib.c",
      54,
      "!EFI_ERROR (Status)"
      );
  }

  if (gHobList == NULL) {
    DebugAssert (
      "e:\\hs\\MdePkg\\Library\\DxeHobLib\\HobLib.c",
      55,
      "mHobList != ((void *) 0)"
      );
  }

  return gHobList;
}

// ---------------------------------------------------------------------------
// Internal: GetDebugProtocol
// @address 0x618
// ---------------------------------------------------------------------------

/**
  Resolves and caches the DebugLib protocol interface via gBS->LocateProtocol.

  Uses a pool alloc/free as a UEFI environment validity check. If the
  allocation returns a pointer <= 0x10, boot services are considered broken.

  @return Pointer to the DebugLib protocol interface, or NULL on failure.
**/
VOID *
EFIAPI
GetDebugProtocol (
  VOID
  )
{
  UINTN  PoolSize;

  if (gDebugProtocol != NULL) {
    return gDebugProtocol;
  }

  //
  // gBS->AllocatePool = BootServices + 24
  // gBS->FreePool     = BootServices + 32
  //
  PoolSize = (UINTN)gBS->AllocatePool (31);
  gBS->FreePool ((VOID *)PoolSize);

  if (PoolSize <= 0x10) {
    return NULL;
  }

  //
  // gBS->LocateProtocol = BootServices + 320 (0x140)
  //
  if (gBS->LocateProtocol (&gDebugLibProtocolGuid, NULL, &gDebugProtocol) < 0) {
    gDebugProtocol = NULL;
  }

  return gDebugProtocol;
}

// ---------------------------------------------------------------------------
// Internal: DebugPrint
// @address 0x698
// ---------------------------------------------------------------------------

/**
  Debug output with CMOS-based level filtering.

  Reads CMOS RTC register 0x4B via I/O ports 0x70/0x71 to determine the
  debug verbosity level. Falls back to MMIO 0xFDAF0490 if the CMOS level
  is 0. Calls the DebugLib protocol's DebugPrint if the ErrorLevel matches.

  @param[in]  ErrorLevel  Debug mask (typically 0x80000000 for errors).
  @param[in]  Format      Format string.
  @param[in]  ...         Variable arguments.
**/
UINTN
EFIAPI
DebugPrint (
  IN UINTN        ErrorLevel,
  IN CONST CHAR8  *Format,
  ...
  )
{
  VA_LIST             Va;
  UINTN               Result;
  DEBUG_LIB_PROTOCOL  *Protocol;
  UINT8               CmosNmiSave;
  UINT8               DebugLevel;

  Protocol = (DEBUG_LIB_PROTOCOL *)GetDebugProtocol ();
  Result   = 0;

  if (Protocol != NULL) {
    //
    // Read current CMOS index, preserving NMI enable bit
    //
    CmosNmiSave = __inbyte (0x70);
    __outbyte (0x70, CmosNmiSave & 0x80 | 0x4B);

    //
    // Read debug level from CMOS register 0x4B
    //
    DebugLevel = __inbyte (0x71);

    if (DebugLevel > 3) {
      if (DebugLevel == 0) {
        //
        // Fallback: read board config register at MMIO 0xFDAF0490
        // Bit 1 indicates debug capability
        //
        DebugLevel = (*(volatile UINT8 *)0xFDAF0490) & 2 | 1;
      }
    }

    //
    // Convert level 1..4 into ErrorLevel mask
    //
    if (DebugLevel - 1 <= 0xFD) {
      Result = 4;
      if (DebugLevel == 1) {
        ErrorLevel = 0x80000004;  // EFI_D_ERROR
      } else {
        ErrorLevel = 0x80000006;
      }
    }

    //
    // Check mask match and call DebugPrint if it matches
    //
    if ((ErrorLevel & 0x80000000) != 0) {
      VA_START (Va, Format);
      Result = Protocol->DebugPrint (ErrorLevel, Format, Va);
      VA_END (Va);
    }
  }

  return Result;
}

// ---------------------------------------------------------------------------
// Internal: DebugAssert
// @address 0x720
// ---------------------------------------------------------------------------

/**
  Debug assertion handler.

  @param[in]  FileName     Source file name.
  @param[in]  LineNumber   Line number.
  @param[in]  Description  Assertion description.
**/
VOID
EFIAPI
DebugAssert (
  IN CONST CHAR8  *FileName,
  IN UINTN        LineNumber,
  IN CONST CHAR8  *Description
  )
{
  DEBUG_LIB_PROTOCOL  *Protocol;

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

// ---------------------------------------------------------------------------
// Callback 0: PcieSlotChecker
// @address 0x48C
// ---------------------------------------------------------------------------

/**
  Checks whether a PCIe address falls within valid range for any slot whose
  bit is set in the bitmap.

  For each enabled slot bit, opens the PCI Root Bridge I/O Protocol to read
  configuration space registers at offsets 0x19 (sub-class) and 0x1A (base class),
  then checks if PcieAddress is within the [ConfigByte1, ConfigByte2] range.

  @param[in]  PcieAddress  PCIe address (segment:bus:device:function) to test.
  @param[in]  SlotBitmap   Bitmap of slots to test (bits 0..7 correspond to 8 slots).

  @retval TRUE   The address falls within a valid slot range.
  @retval FALSE  The address does not match any enabled slot.
**/
BOOLEAN
EFIAPI
PcieSlotChecker (
  IN UINT64   PcieAddress,
  IN UINT32   SlotBitmap
  )
{
  UINT32                Bitmap;
  PCIE_SLOT_BIT_CONFIG  *Entry;
  BOOLEAN               InRange;
  UINTN                 Index;
  EFI_STATUS            Status;
  VOID                  *RootBridgeInterface;
  UINT8                 ConfigByte1;
  UINT8                 ConfigByte2;
  UINT32                ConfigBase;

  Bitmap  = SlotBitmap;
  Entry   = &gPcieSlotBitConfig[0];
  InRange = FALSE;

  for (Index = 0; Index < 8; Index++) {
    if (((Bitmap >> Index) & 1) == 0) {
      //
      // This slot bit is not set - skip
      //
      Bitmap = SlotBitmap;
      Entry++;
      continue;
    }

    //
    // Open PCI Root Bridge I/O Protocol for PCI config access
    //
    gBS->LocateProtocol (&gPciRootBridgeIoProtocolGuid, NULL, &RootBridgeInterface);

    //
    // Build config address: ((Bus | ((Device | (Function << 8)) << 8)) << 8)
    //
    ConfigBase = (Entry->Bus | ((Entry->Device | (Entry->Function << 8)) << 8)) << 8;

    //
    // Read PCI config register at offset 0x19 (sub-class code)
    //
    ((EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL *)RootBridgeInterface)->Pci.Read (
      RootBridgeInterface,
      EfiPciWidthUint8,
      ConfigBase | 0x19,
      1,
      &ConfigByte1
      );

    //
    // Read PCI config register at offset 0x1A (base class code)
    //
    ((EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL *)RootBridgeInterface)->Pci.Read (
      RootBridgeInterface,
      EfiPciWidthUint8,
      ConfigBase | 0x1A,
      1,
      &ConfigByte2
      );

    //
    // Check if PcieAddress is in range [ConfigByte1, ConfigByte2]
    //
    InRange = FALSE;
    if (PcieAddress >= ConfigByte1) {
      InRange = (BOOLEAN)(PcieAddress <= ConfigByte2);
    }

    Bitmap = SlotBitmap;
    Entry++;
  }

  return InRange;
}

// ---------------------------------------------------------------------------
// Callback 1: GetPcieSlotConfigProtocol0
// @address 0x5B8
// ---------------------------------------------------------------------------

/**
  Returns the base configuration protocol pointer (table 0 at 0xD00).

  @param[out] Protocol  Receives the protocol/structure pointer.

  @return EFI_SUCCESS.
**/
EFI_STATUS
EFIAPI
GetPcieSlotConfigProtocol0 (
  OUT VOID  **Protocol
  )
{
  *Protocol = (VOID *)&gPcieSlotConfigTable0;
  return EFI_SUCCESS;
}

// ---------------------------------------------------------------------------
// Callback 2: GetPcieSlotTable1
// @address 0x5C8
// ---------------------------------------------------------------------------

/**
  Returns the first PCIe slot configuration table (6 entries at 0xD40).

  @param[out] Table  Receives the table base pointer.
  @param[out] Count  Receives the entry count (6).

  @return EFI_SUCCESS.
**/
EFI_STATUS
EFIAPI
GetPcieSlotTable1 (
  OUT VOID   **Table,
  OUT UINTN  *Count
  )
{
  *Table = (VOID *)&gPcieSlotTable1;
  *Count = 6;
  return EFI_SUCCESS;
}

// ---------------------------------------------------------------------------
// Callback 3: GetPcieSlotTable2
// @address 0x5DC
// ---------------------------------------------------------------------------

/**
  Returns the second PCIe slot configuration table (10 entries at 0xE40).

  @param[out] Table  Receives the table base pointer.
  @param[out] Count  Receives the entry count (10).

  @return EFI_SUCCESS.
**/
EFI_STATUS
EFIAPI
GetPcieSlotTable2 (
  OUT VOID   **Table,
  OUT UINTN  *Count
  )
{
  *Table = (VOID *)&gPcieSlotTable2;
  *Count = 10;
  return EFI_SUCCESS;
}

// ---------------------------------------------------------------------------
// Callback 4: SetPcieSlotNumberCallback
// @address 0x5F0
// ---------------------------------------------------------------------------

/**
  Sets the PCIe slot number to 2 and logs the callback invocation.

  @param[out] SlotNumber  Receives the slot number (2).

  @return EFI_SUCCESS.
**/
EFI_STATUS
EFIAPI
SetPcieSlotNumberCallback (
  OUT UINT8  *SlotNumber
  )
{
  *SlotNumber = 2;
  DebugPrint (0x80000000, "[UBA]:SetPcieSlotNumber callback - %d\n", *SlotNumber);
  return EFI_SUCCESS;
}

// ---------------------------------------------------------------------------
// Entry Point: _ModuleEntryPoint
// @address 0x390
// ---------------------------------------------------------------------------

/**
  DXE driver entry point.

  Initializes global pointers, locates the HOB list, and registers the
  OpROM update configuration with the UBA board-type protocol.

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

  @retval EFI_SUCCESS           Registration succeeded.
  @retval EFI_NOT_FOUND         UBA board-type protocol not found.
  @retval other                 Error from gBS->LocateProtocol or registration.
**/
EFI_STATUS
EFIAPI
_ModuleEntryPoint (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  EFI_STATUS                    Status;
  UBA_NEONCITY_BOARD_PROTOCOL   *BoardProtocol;

  //
  // Save image handle (assert if NULL)
  //
  gImageHandle = (UINT64)ImageHandle;
  if (ImageHandle == NULL) {
    DebugAssert (
      "e:\\hs\\MdePkg\\Library\\UefiBootServicesTableLib\\UefiBootServicesTableLib.c",
      51,
      "gImageHandle != ((void *) 0)"
      );
  }

  //
  // Save system table (assert if NULL)
  //
  gST = (UINT64)SystemTable;
  if (SystemTable == NULL) {
    DebugAssert (
      "e:\\hs\\MdePkg\\Library\\UefiBootServicesTableLib\\UefiBootServicesTableLib.c",
      57,
      "gST != ((void *) 0)"
      );
  }

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

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

  //
  // Locate HOB list
  //
  GetHobList ();

  //
  // Log driver type
  //
  DebugPrint (0x80000000, "UBA:OpromUpdate-TypeNeonCityFPGA\n");

  //
  // Locate UBA board-type protocol
  // gBS->LocateProtocol(...) = BootServices + 0x140 (320)
  //
  BoardProtocol = NULL;
  Status = gBS->LocateProtocol (
                  &gUbaBoardTypeProtocolGuid,
                  NULL,
                  (VOID **)&BoardProtocol
                  );

  //
  // Register OpROM update config: 5 callback functions + GUID + 48 bytes config
  //
  if (!EFI_ERROR (Status)) {
    return BoardProtocol->RegisterOpromUpdate (
                            BoardProtocol,
                            &gOpromUpdateConfigGuid,
                            &gUbaOpromUpdateConfig,
                            sizeof (UBA_OPROM_UPDATE_CONFIG)
                            );
  }

  return Status;
}