/** @file
  SlotDataUpdateDxeNeonCityEPECB - PCIe Slot Data Update Driver for NeonCity EP EC B platform.

  This DXE driver registers PCIe slot configuration data for the NeonCity EP EC B
  platform via the Intel UBA (Universal BIOS Aggregator) protocol. It provides
  slot-specific data tables ("PSLT" - Platform Slot Table entries) defining PCIe
  slot identifiers for the platform's physical slot layout.

  This is one of many platform-type-specific slot data update drivers that are
  part of the Intel Purley UBA framework. Each platform type (e.g., NeonCity EP EC B)
  provides its own slot mapping table, and the UBA protocol aggregates them.

  Source: PurleyRpPkg/Uba/UbaMain/Dxe/TypeNeonCityEPECB/SlotDataUpdateDxe/
  Build: VS2015 x64 DEBUG on Intel Purley (Xeon Scalable) platform

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

#include <Uefi.h>
#include <Library/DebugLib.h>
#include <Library/IoLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/BaseMemoryLib.h>
#include <Protocol/UbaSlotDataUpdate.h>

//
// ---------------------------------------------------------------------------
// Local GUIDs
// ---------------------------------------------------------------------------
//
//
// UBA Debug Protocol GUID used by assertion handler.
// {36232936-0E76-31C8-A13A-3AF2FC1C3932}
//
EFI_GUID  gUbaDebugProtocolGuid =
  { 0x36232936, 0x0E76, 0x31C8, { 0xA1, 0x3A, 0x3A, 0xF2, 0xFC, 0x1C, 0x39, 0x32 } };

//
// UBA Slot Data Update Protocol GUID.
// {E03E0D46-5263-4845-B0A4-58D57B3177E2}
//
EFI_GUID  gUbaSlotDataUpdateProtocolGuid =
  { 0xE03E0D46, 0x5263, 0x4845, { 0xB0, 0xA4, 0x58, 0xD5, 0x7B, 0x31, 0x77, 0xE2 } };

//
// Platform Slot Config GUID 1 (maps to 40-byte PSLT).
// {226763AE-972C-4E3C-80D1-73B25E8CBBA3}
//
EFI_GUID  gSlotConfigGuid40 =
  { 0x226763AE, 0x972C, 0x4E3C, { 0x80, 0xD1, 0x73, 0xB2, 0x5E, 0x8C, 0xBB, 0xA3 } };

//
// Platform Slot Config GUID 2 (maps to 32-byte PSLT).
// {B93613E1-48F0-4B32-B3A8-4FEDFC7C1365}
//
EFI_GUID  gSlotConfigGuid32 =
  { 0xB93613E1, 0x48F0, 0x4B32, { 0xB3, 0xA8, 0x4F, 0xED, 0xFC, 0x7C, 0x13, 0x65 } };

//
// HOB List GUID (defined in MdePkg).
// gEfiHobListGuid = {0x7739F24C, 0x93D7, 0x11D4, {0x9A, 0x3A, 0x00, 0x90, 0x27, 0x3F, 0xC1, 0x4D}}
//
EFI_GUID  gEfiHobListGuid =
  { 0x7739F24C, 0x93D7, 0x11D4, { 0x9A, 0x3A, 0x00, 0x90, 0x27, 0x3F, 0xC1, 0x4D } };

//
// ---------------------------------------------------------------------------
// Global Variables
// ---------------------------------------------------------------------------
//
EFI_SYSTEM_TABLE       *gSystemTable       = NULL;
EFI_BOOT_SERVICES      *gBootServices      = NULL;
EFI_RUNTIME_SERVICES   *gRuntimeServices   = NULL;
EFI_HANDLE              gImageHandle       = NULL;

//
// Cached UBA debug protocol pointer (for ASSERT output).
//
VOID                    *gUbaDebugProtocol = NULL;

//
// Cached HOB list pointer.
//
VOID                    *gHobList          = NULL;

//
// ---------------------------------------------------------------------------
// Static data: Platform Slot Table (PSLT) entries
// ---------------------------------------------------------------------------
//
// PSLT entry 1 (40 bytes): Dual-slot configuration.
//   SlotIdentifier  = 0x4B0 -> SlotDataAccessor1
//   SlotIdentifier2 = 0x4B4 -> SlotDataAccessor2
//
GLOBAL_REMOVE_IF_UNREFERENCED
UINT64  mSlotConfig40[] = {
  SIGNATURE_64 ('P', 'S', 'L', 'T', 1, 0, 0, 0),   // +0x00: "PSLT" + version (8 bytes)
  0,                                                  // +0x08: Reserved (8 bytes)
  0x4B0,                                              // +0x10: SlotIdentifier1 (pointer to SlotDataAccessor1)
  0,                                                  // +0x18: Reserved / unused
  0x4B4                                               // +0x20: SlotIdentifier2 (pointer to SlotDataAccessor2)
};

//
// PSLT entry 2 (32 bytes): Single-slot configuration.
//   SlotIdentifier = 0x4B0 -> SlotDataAccessor1
//
GLOBAL_REMOVE_IF_UNREFERENCED
UINT64  mSlotConfig32[] = {
  SIGNATURE_64 ('P', 'S', 'L', 'T', 1, 0, 0, 0),   // +0x00: "PSLT" + version (8 bytes)
  0,                                                  // +0x08: Reserved (8 bytes)
  0x4B0,                                              // +0x10: SlotIdentifier (pointer to SlotDataAccessor1)
  0                                                   // +0x18: Reserved / unused
};

//
// ---------------------------------------------------------------------------
// Function Prototypes (forward declarations)
// ---------------------------------------------------------------------------
//
VOID    *LocateDebugProtocol     (VOID);
UINT64  ReadUnaligned64          (UINT64 *Buffer);
BOOLEAN CompareGuidValue         (UINT64 *Guid1, UINT64 *Guid2);
VOID    *LocateHobList           (VOID);
UINTN   PlatformSlotDataInit     (UINTN Flag, CHAR8 *PlatformString);
VOID    UbaDebugAssert           (CHAR8 *FileName, UINTN LineNumber, CHAR8 *Description);

//
// ---------------------------------------------------------------------------
// Slot Data Accessor Functions (referenced by PSLT entries)
// ---------------------------------------------------------------------------
//

/**
  Slot data accessor function 1.

  Returns the first parameter unchanged. This is a pass-through callback
  used as a slot identifier within the UBA framework. The address of this
  function (0x4B0) is stored in PSLT entries as a slot data reference.

  @param[in] Value  Input value to pass through.

  @return The same value as the input parameter.
**/
UINT8
SlotDataAccessor1 (
  UINT8  Value
  )
{
  return Value;
}

/**
  Slot data accessor function 2.

  Returns the second parameter unchanged. This is a pass-through callback
  used as a slot identifier within the UBA framework. The address of this
  function (0x4B4) is stored in PSLT entries as a secondary slot reference.

  @param[in] Value   First parameter (unused).
  @param[in] Value2  Second parameter to pass through.

  @return The value of the second parameter.
**/
UINT8
SlotDataAccessor2 (
  UINT64  Value,
  UINT8   Value2
  )
{
  return Value2;
}

//
// ---------------------------------------------------------------------------
// Helper Functions
// ---------------------------------------------------------------------------
//

/**
  Reads a UINT64 value from the specified buffer with a NULL pointer check.

  Performs an ASSERT that the buffer is not NULL before reading.

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

  @return The UINT64 value at the specified memory location.
**/
UINT64
ReadUnaligned64 (
  UINT64  *Buffer
  )
{
  ASSERT (Buffer != NULL);
  return *Buffer;
}

/**
  Compares two 16-byte GUID values by comparing them as two 8-byte halves.

  This avoids potential alignment faults when GUIDs are not naturally aligned.

  @param[in] Guid1  Pointer to first 16-byte GUID.
  @param[in] Guid2  Pointer to second 16-byte GUID.

  @retval TRUE   Both halves match.
  @retval FALSE  GUIDs differ.
**/
BOOLEAN
CompareGuidValue (
  UINT64  *Guid1,
  UINT64  *Guid2
  )
{
  return (ReadUnaligned64 (Guid1)     == ReadUnaligned64 (Guid2)) &&
         (ReadUnaligned64 (Guid1 + 1) == ReadUnaligned64 (Guid2 + 1));
}

//
// ---------------------------------------------------------------------------
// UBA Debug Protocol Support
// ---------------------------------------------------------------------------
//

/**
  Locates and caches the UBA debug protocol.

  This function is called by the assertion handler to find the
  UBA debug protocol (identified by gUbaDebugProtocolGuid).
  The protocol is cached after first lookup.

  Uses an interrupt-safe pattern: temporarily raises TPL to HIGH_LEVEL
  and restores to serialize initialization.

  @return Pointer to the UBA debug protocol, or NULL if not found.
**/
VOID *
LocateDebugProtocol (
  VOID
  )
{
  EFI_STATUS  Status;
  EFI_TPL     OldTpl;

  if (gUbaDebugProtocol == NULL) {
    //
    // Raise TPL to HIGH_LEVEL to serialize access during protocol lookup.
    //
    OldTpl = gBootServices->RaiseTPL (TPL_HIGH_LEVEL);
    gBootServices->RestoreTPL (OldTpl);

    if (OldTpl <= TPL_NOTIFY) {
      //
      // Locate the UBA debug protocol for assertion output.
      //
      Status = gBootServices->LocateProtocol (
                                &gUbaDebugProtocolGuid,
                                NULL,
                                &gUbaDebugProtocol
                                );
      if (EFI_ERROR (Status)) {
        gUbaDebugProtocol = NULL;
      }
    }
  }

  return gUbaDebugProtocol;
}

/**
  Debug assertion handler using the UBA debug protocol.

  Reports assertion failures through the UBA debug protocol if available.
  Falls back silently if the protocol has not been located.

  @param[in] FileName     Source file name where the assertion occurred.
  @param[in] LineNumber   Line number of the assertion.
  @param[in] Description  Assertion failure description string.
**/
VOID
UbaDebugAssert (
  CHAR8  *FileName,
  UINTN  LineNumber,
  CHAR8  *Description
  )
{
  VOID    *Protocol;

  Protocol = LocateDebugProtocol ();
  if (Protocol != NULL) {
    //
    // Call the assertion function at protocol + 0x08 (index 1).
    //
    ((VOID (EFIAPI *)(CHAR8 *, UINTN, CHAR8 *))(*(UINT64 **)Protocol)[1])(
      FileName,
      LineNumber,
      Description
      );
  }
}

//
// ---------------------------------------------------------------------------
// HOB List Support
// ---------------------------------------------------------------------------
//

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

  Searches through the system table's configuration table entries for
  gEfiHobListGuid. The HOB list is created during PEI and consumed in DXE.

  @return Pointer to the HOB list, or NULL if not found.
**/
VOID *
LocateHobList (
  VOID
  )
{
  UINTN                     Index;
  EFI_CONFIGURATION_TABLE   *Entry;

  //
  // Return early if there are no configuration table entries.
  //
  if (gSystemTable->NumberOfTableEntries == 0) {
    DEBUG ((DEBUG_ERROR, "\nASSERT_EFI_ERROR (Status = %r)\n", EFI_NOT_FOUND));
    ASSERT (!EFI_ERROR (EFI_NOT_FOUND));
    return NULL;
  }

  //
  // Iterate through configuration table entries to find gEfiHobListGuid.
  //
  for (Index = 0; Index < gSystemTable->NumberOfTableEntries; Index++) {
    Entry = &gSystemTable->ConfigurationTable[Index];

    if (CompareGuidValue (
          (UINT64 *)&Entry->VendorGuid,
          (UINT64 *)&gEfiHobListGuid
          )) {
      //
      // Found the HOB list - cache and return the pointer.
      //
      gHobList = Entry->VendorTable;
      return gHobList;
    }
  }

  //
  // HOB list not found - report error and assert.
  //
  DEBUG ((DEBUG_ERROR, "\nASSERT_EFI_ERROR (Status = %r)\n", EFI_NOT_FOUND));
  ASSERT (!EFI_ERROR (EFI_NOT_FOUND));
  ASSERT (gHobList != NULL);

  return NULL;
}

//
// ---------------------------------------------------------------------------
// Platform Detection & Slot Data Initialization
// ---------------------------------------------------------------------------
//

/**
  Platform-specific slot data initialization.

  Reads CMOS register 0x4B to determine the platform configuration type,
  then dispatches the slot data update to the UBA protocol handler.

  CMOS register 0x4B encoding:
    Bits[1:0]:
      0 = Default/uninitialized - reads HW strap from MMIO 0xFDAF0490
      1 = Platform type A (maps to flag 0x80000004)
      2 = Platform type B (maps to flag 0x80000006)
      3+ = Platform type C (maps to flag 0x80000006)

  @param[in] Flag             Platform type filter (e.g., 0x80000000).
  @param[in] PlatformString   Platform identifier string for debug output.

  @retval Non-zero if slot data was initialized by the UBA protocol.
  @retval 0 if the platform type does not match or protocol unavailable.
**/
UINTN
PlatformSlotDataInit (
  UINTN       Flag,
  CHAR8       *PlatformString
  )
{
  UINTN       Result;
  UINT8       NmiSave;
  UINT8       CmosValue;

  Result = 0;

  if (LocateDebugProtocol () != NULL) {
    //
    // Save current NMI state and select CMOS register 0x4B.
    // Register 0x4B holds platform configuration data.
    //
    NmiSave   = IoRead8 (0x70);
    IoWrite8  (0x70, (NmiSave & 0x80) | 0x4B);
    CmosValue = IoRead8 (0x71);

    //
    // Handle special CMOS values:
    //   > 3 and != 0: treat as extended type
    //   = 0: read hardware strap register for actual type
    //
    if (CmosValue > 3) {
      if (CmosValue == 0) {
        //
        // Read hardware strap from fixed MMIO address.
        // Bit 1 selects platform type; OR with 1 for default.
        //
        CmosValue = (MmioRead32 (0xFDAF0490) & 2) | 1;
      }
    }

    //
    // Map CMOS value to platform type flags:
    //   Value 1 -> Flag | 0x00000004
    //   Other   -> Flag | 0x00000006
    //
    if ((CmosValue - 1) <= 0xFD) {
      if (CmosValue == 1) {
        Flag |= 0x00000004;
      } else {
        Flag |= 0x00000006;
      }

      //
      // Dispatch to UBA protocol slot data init handler (index 0 = first function pointer).
      // The protocol's first function handles platform-specific init dispatch:
      //   (*Protocol)(Flag, PlatformString, VarArgs)
      //
      Result = ((UINTN (EFIAPI *)(UINTN, CHAR8 *, UINTN))(
                 *(UINT64 **)LocateDebugProtocol ())[0])(
                 Flag,
                 PlatformString,
                 Flag
                 );
    }
  }

  return Result;
}

//
// ---------------------------------------------------------------------------
// Entry Point
// ---------------------------------------------------------------------------
//

/**
  Entry point for the SlotDataUpdate DXE driver for NeonCity EP EC B.

  Initializes UEFI boot/runtime service table pointer globals, locates the
  HOB list, performs CMOS-based platform detection, and registers PCIe slot
  configuration data via the UBA slot data update protocol.

  Two PSLT entries are registered:
    1. 32-byte entry (GUID {B93613E1-48F0-4B32-B3A8-4FEDFC7C1365}) -
       Single-slot configuration referencing SlotDataAccessor1 (0x4B0).
    2. 40-byte entry (GUID {226763AE-972C-4E3C-80D1-73B25E8CBBA3}) -
       Dual-slot configuration referencing both SlotDataAccessor1 (0x4B0)
       and SlotDataAccessor2 (0x4B4).

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

  @retval EFI_SUCCESS           Slot data was successfully registered.
  @retval EFI_INVALID_PARAMETER ImageHandle or SystemTable was NULL.
  @retval EFI_NOT_FOUND         The UBA protocol or HOB list was not found.
**/
EFI_STATUS
EFIAPI
SlotDataUpdateEntryPoint (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  EFI_STATUS  Status;
  VOID        *Protocol;
  UINT64      LocalVariable;

  //
  // Store global EFI handles.
  //
  gImageHandle    = ImageHandle;
  gSystemTable    = SystemTable;
  gBootServices   = SystemTable->BootServices;
  gRuntimeServices = SystemTable->RuntimeServices;

  //
  // Validate input parameters (uses UBA assert for output).
  //
  ASSERT (ImageHandle != NULL);
  ASSERT (SystemTable != NULL);
  ASSERT (gBootServices != NULL);
  ASSERT (gRuntimeServices != NULL);

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

  //
  // Perform platform detection and slot data initialization
  // via the UBA framework. This checks CMOS register 0x4B
  // and dispatches to the appropriate platform type handler.
  //
  PlatformSlotDataInit (0x80000000, "UBA:SlotDataUpdate-TypeNeonCityEPECB\n");

  //
  // Locate the UBA slot data update protocol.
  //
  LocalVariable = 0;
  Status = gBootServices->LocateProtocol (
                            &gUbaSlotDataUpdateProtocolGuid,
                            NULL,
                            (VOID **)&LocalVariable
                            );
  if (!EFI_ERROR (Status)) {
    Protocol = (VOID *)(UINTN)LocalVariable;

    //
    // Register 32-byte slot configuration (single slot, referenced by gSlotConfigGuid32).
    //
    Status = ((EFI_STATUS (EFIAPI *)(VOID *, EFI_GUID *, VOID *, UINTN))(
               *(UINT64 **)Protocol)[2])(
               Protocol,
               &gSlotConfigGuid32,
               &mSlotConfig32,
               sizeof (mSlotConfig32)
               );
    if (!EFI_ERROR (Status)) {
      //
      // Register 40-byte slot configuration (dual slot, referenced by gSlotConfigGuid40).
      //
      Status = ((EFI_STATUS (EFIAPI *)(VOID *, EFI_GUID *, VOID *, UINTN))(
                 *(UINT64 **)Protocol)[2])(
                 Protocol,
                 &gSlotConfigGuid40,
                 &mSlotConfig40,
                 sizeof (mSlotConfig40)
                 );
    }
  }

  return Status;
}