/** @file
  SlotDataUpdateDxeCLX64L - PCIe Slot Data Update Driver for CLX64L Platform

  This DXE driver provides platform-specific PCIe slot data configuration
  for the CLX64L (Cooper Lake Xeon 64L) platform. It installs PSLT
  (Platform Slot Table) entries via the UBA (Universal BIOS Architecture)
  protocol to describe the physical PCIe slot topology and capabilities.

  The driver locates the UBA Slot Data Update protocol by GUID, then
  installs two PSLT entries:
    - One for the physical slot table (40 bytes at 0xB80)
    - One for an extended slot configuration (40 bytes at 0xBB0)

  Build path:
    e:\hs\Build\HR6N0XMLK\DEBUG_VS2015\X64\PurleyRpPkg\Uba\UbaMain\Dxe\TypeCLX64L\SlotDataUpdateDxe

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

**/

#include "SlotDataUpdateDxeCLX64L.h"

//
// Global UEFI table pointers (set by _ModuleEntryPoint)
//
EFI_HANDLE             gImageHandle   = NULL;
EFI_SYSTEM_TABLE       *gSystemTable  = NULL;
EFI_BOOT_SERVICES      *gBootServices = NULL;
EFI_RUNTIME_SERVICES   *gRuntimeServices = NULL;

//
// Cached UBA debug protocol pointer
//
VOID                   *gUbaDebugProtocol = NULL;

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

//
// UBA Slot Data Update Protocol GUID
// {E03E0D46-5263-4845-B0A4-58D57B3177E2}
//
STATIC CONST EFI_GUID mSlotDataUpdateProtocolGuid = SLOT_DATA_UPDATE_PROTOCOL_GUID;

//
// Slot Data Entry Protocol GUID
// {B93613E1-48F0-4B32-B3A8-4FEDFC7C1365}
//
STATIC CONST EFI_GUID mSlotDataEntryGuid = SLOT_DATA_ENTRY_PROTOCOL_GUID;

//
// HOB List GUID
// {4CF27739-93D7-11D4-9A3A-0090273FC14D}
//
STATIC CONST EFI_GUID mGetHobListGuid = GET_HOB_LIST_PROTOCOL_GUID;

//
// First PSLT entry: physical slot configuration for CLX64L platform
// Format: PSLT header + slot mapping data
//   - Slot 0: Bus 0x00, Dev 0x02, Func 0x00, Port 0x01
//   - Slot 1: Bus 0x01, Dev 0x02, Func 0x01, Port 0x00
//   - Uplink: Bus 0x01, Dev 0x00 (bridge)
//
STATIC CONST PSLT_HEADER mSlotTableEntry1 = {
  PSLT_SIGNATURE,     // Signature = "PSLT"
  1,                  // Version
  0xBA0,              // TableOffset
  0x4B0,              // TableLength
  {                   // SlotData[24]:
    0x00, 0x02, 0x00, 0x01,   // Slot 0: B0:D2:F0 -> Port 1
    0x01, 0x02, 0x01, 0x00,   // Slot 1: B1:D2:F1 -> Port 0
    0x01, 0x00, 0x00, 0x00,   // Slot mapping attribute
    0x00, 0x00, 0x00, 0x00,   // Reserved
    0x00, 0x00, 0x00, 0x00,   // Reserved
    0x00, 0x00, 0x00, 0x00    // Reserved
  }
};

//
// Second PSLT entry: extended slot configuration
// Contains null table offset (placeholder/reserved entry)
//
STATIC CONST PSLT_HEADER mSlotTableEntry2 = {
  PSLT_SIGNATURE,     // Signature = "PSLT"
  1,                  // Version
  0xBA0,              // TableOffset
  0x4B0,              // TableLength
  {                   // SlotData[24]:
    0x00, 0x00, 0x00, 0x00,   // No slot mapping
    0x00, 0x00, 0x00, 0x00,   // Reserved
    0xB4, 0x04, 0x00, 0x00,   // Secondary table reference
    0x00, 0x00, 0x00, 0x00,   // Reserved
    0x00, 0x00, 0x00, 0x00,   // Reserved
    0x00, 0x00, 0x00, 0x00    // Reserved
  }
};

//
// UBA debug protocol GUID (internal UBA protocol for debug services)
// This GUID varies by platform; located via gBS->LocateProtocol
//
STATIC CONST EFI_GUID mUbaDebugProtocolGuid = {
  0x7739F24C, 0x93D7, 0x11D4, { 0x9A, 0x3A, 0x00, 0x90, 0x27, 0x3F, 0xC1, 0x4D }
};

/**
  Read a 64-bit value from a buffer with NULL-pointer assertion.

  Wrapper around standard BaseLib ReadUnaligned64 with an additional
  NULL pointer check that triggers a UBA assert if the buffer is NULL.

  @param[in]  Buffer  Pointer to the buffer to read from.

  @return The 64-bit value at the buffer address.

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

  return *(UINT64 *)Buffer;
}

/**
  Get the UBA debug protocol instance.

  Allocates pool, locates the UBA debug protocol by GUID, and caches
  the result globally. The protocol provides DebugPrint and DebugAssert
  services used by the UBA framework.

  @return Pointer to the UBA_DEBUG_PROTOCOL, or NULL if the protocol
          could not be located or pool allocation failed.

**/
UBA_DEBUG_PROTOCOL *
EFIAPI
GetUbaDebugProtocol (
  VOID
  )
{
  EFI_STATUS            Status;
  UBA_DEBUG_PROTOCOL    *Protocol;

  //
  // Return cached protocol if already located
  //
  if (gUbaDebugProtocol != NULL) {
    return gUbaDebugProtocol;
  }

  //
  // Allocate pool for the protocol instance
  //
  Status = gBootServices->AllocatePool (
                            EfiBootServicesData,
                            31,
                            (VOID **)&Protocol
                            );
  if (EFI_ERROR (Status)) {
    return NULL;
  }

  //
  // Zero the allocated pool
  //
  gBootServices->SetMem (Protocol, 31, 0);

  //
  // Locate the UBA debug protocol
  //
  Status = gBootServices->LocateProtocol (
                            &mUbaDebugProtocolGuid,
                            NULL,
                            &gUbaDebugProtocol
                            );
  if (EFI_ERROR (Status)) {
    gUbaDebugProtocol = NULL;
    return NULL;
  }

  return gUbaDebugProtocol;
}

/**
  UBA debug print function.

  Queries the CMOS debug level (RTC index 0x4B) to determine if the
  requested severity level is enabled, and if so, forwards the message
  to the UBA debug protocol's DebugPrint function.

  The CMOS register at index 0x4B uses the following encoding:
    Bit 7 = NMI mask
    Bits 6-0 = Debug level:
      0 = Disabled
      1 = Errors only
      2 = Errors + Warnings
      3 = Full debug

  @param[in]  Severity      Debug message severity level.
  @param[in]  DebugString   Debug format string.
  @param[in]  ...           Variable arguments for format string.

  @return Status from the UBA debug protocol print function, or
          EFI_UNSUPPORTED if debug output is suppressed.

**/
EFI_STATUS
EFIAPI
LogUbaMessage (
  IN UINTN   Severity,
  IN CHAR8   *DebugString,
  ...
  )
{
  UBA_DEBUG_PROTOCOL    *Protocol;
  UINT64                SeverityLevel;
  UINT8                 CmosIndex;
  UINT8                 CmosValue;
  UINT8                 DebugLevel;

  Protocol = GetUbaDebugProtocol ();
  SeverityLevel = 0;

  if (Protocol != NULL) {
    //
    // Read CMOS debug level
    //
    CmosIndex = __inbyte (RTC_INDEX_PORT);
    __outbyte (RTC_INDEX_PORT, (CmosIndex & RTC_NMI_BIT) | RTC_DEBUG_INDEX);
    CmosValue = __inbyte (RTC_TARGET_PORT);
    DebugLevel = CmosValue;

    //
    // Determine if debug is enabled for this severity
    //
    if (DebugLevel > 3) {
      //
      // Non-standard debug level; check if CMOS was zero (disabled)
      //
      if (DebugLevel == 0) {
        DebugLevel = (UINT8)((UINTN)(MEMORY[0xFDAF0490] & 2) | 1);
      }
    }

    if ((DebugLevel - 1) <= 0xFD) {
      //
      // Map debug level to UBA severity mask
      //
      SeverityLevel = (DebugLevel == 1) ?
                      (UINT64)UBA_DEBUG_WARN :
                      (UINT64)UBA_DEBUG_ERROR;
    }

    //
    // If severity matches, forward to UBA debug print
    //
    if ((SeverityLevel & Severity) != 0) {
      return Protocol->DebugPrint (Severity, DebugString, (UINT64 *)&DebugString);
    }
  }

  return EFI_UNSUPPORTED;
}

/**
  ASSERT helper via UBA debug protocol.

  Triggers a debug assert through the UBA debug protocol's DebugAssert
  function. Used as the platform assert handler for UBA-based modules.

  @param[in]  FileName      Source file name of the assert.
  @param[in]  LineNumber    Line number of the assert.
  @param[in]  AssertString  Assertion expression string.

**/
VOID
EFIAPI
UbaAssert (
  IN CHAR8   *FileName,
  IN UINTN   LineNumber,
  IN CHAR8   *AssertString
  )
{
  UBA_DEBUG_PROTOCOL    *Protocol;

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

/**
  Check if a HOB entry matches the expected GUIDs.

  Compares the HOB entry's GUID fields against the context GUID and
  the known HOB list GUID to determine if this is the target HOB.

  @param[in]  ContextGuid   The context/expected GUID value.
  @param[in]  HobEntry      Pointer to a HOB entry structure.

  @retval TRUE   The HOB entry matches both context and HOB list GUID.
  @retval FALSE  The HOB entry does not match.

**/
BOOLEAN
EFIAPI
MatchHobGuid (
  IN UINT64      ContextGuid,
  IN VOID        *HobEntry
  )
{
  UINT64    Guid1;
  UINT64    Guid2;

  //
  // Read the first 8 bytes of the HOB header
  //
  Guid1 = ReadUnaligned64 (HobEntry);
  Guid2 = ReadUnaligned64 ((VOID *)((UINTN)HobEntry + 8));

  return (ReadUnaligned64 (&mSlotDataEntryGuid) == Guid1) &&
         (ReadUnaligned64 (&mGetHobListGuid) == Guid2);
}

/**
  Retrieve the HOB list pointer.

  Iterates through the HOB entries in the system configuration HOB
  list to find the one matching the slot data entry GUID. The result
  is cached globally.

  @param[in]  Context  Optional context value (currently unused).

  @return Pointer to the matching HOB list entry data field, or NULL
          if no matching HOB was found.

**/
UINT64
EFIAPI
GetHobList (
  IN UINT64  Context
  )
{
  UINT64    Result;
  UINTN     Index;
  UINTN     EntryOffset;

  //
  // Return cached HOB list if already located
  //
  if (gHobList != NULL) {
    return (UINT64)(UINTN)gHobList;
  }

  //
  // Initialize
  //
  gHobList = NULL;

  //
  // Iterate through HOB entries from the system table
  //
  if (gSystemTable->NumberOfTableEntries != 0) {
    Index = 0;
    EntryOffset = 0;

    while (Index < gSystemTable->NumberOfTableEntries) {
      if (MatchHobGuid (
            Context,
            (VOID *)((UINTN)gSystemTable->ConfigurationTable + EntryOffset)
            )) {
        //
        // Found matching HOB; return the data field at offset 16
        //
        Result = *(UINT64 *)((UINTN)gSystemTable->ConfigurationTable +
                             EntryOffset + 16);
        gHobList = (VOID *)(UINTN)Result;
        return Result;
      }

      Index++;
      EntryOffset += sizeof (EFI_CONFIGURATION_TABLE);
    }

    //
    // No matching HOB found - assert
    //
    LogUbaMessage (
      UBA_DEBUG_ERROR,
      "\nASSERT_EFI_ERROR (Status = %r)\n",
      (UINT64)0x800000000000000E
      );
    UbaAssert (
      "e:\\hs\\MdePkg\\Library\\DxeHobLib\\HobLib.c",
      54,
      "!EFI_ERROR (Status)"
      );
  }

  //
  // Verify HOB list was found
  //
  if (gHobList == NULL) {
    UbaAssert (
      "e:\\hs\\MdePkg\\Library\\DxeHobLib\\HobLib.c",
      55,
      "mHobList != ((void *) 0)"
      );
  }

  return (UINT64)(UINTN)gHobList;
}

/**
  Platform-specific slot data update entry point for CLX64L.

  This is the main entry point called after UEFI core initialization.
  It performs the following:
    1. Locates the UBA Slot Data Update protocol
    2. Installs the first PSLT entry (physical slot mapping)
    3. Installs the second PSLT entry (extended slot config)

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

  @return EFI_SUCCESS           Slot data updated successfully.
  @return EFI_UNSUPPORTED       Slot data update protocol not found.
  @return EFI_INVALID_PARAMETER Invalid slot data provided.

**/
EFI_STATUS
EFIAPI
_ModuleEntryPoint (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  EFI_STATUS                    Status;
  SLOT_DATA_UPDATE_PROTOCOL     *SlotProtocol;

  //
  // Save global UEFI table pointers
  //
  gImageHandle = ImageHandle;

  if (ImageHandle == NULL) {
    UbaAssert (
      "e:\\hs\\MdePkg\\Library\\UefiBootServicesTableLib\\UefiBootServicesTableLib.c",
      51,
      "gImageHandle != ((void *) 0)"
      );
  }

  gSystemTable = SystemTable;

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

  //
  // Cache boot services pointer from system table (offset 0x60)
  //
  gBootServices = SystemTable->BootServices;

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

  //
  // Cache runtime services pointer from system table (offset 0x58)
  //
  gRuntimeServices = SystemTable->RuntimeServices;

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

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

  //
  // Log initialization message
  //
  LogUbaMessage (UBA_DEBUG_ERROR, "UBA:SlotDataUpdate-TypeClx64L\n");

  //
  // Locate the UBA Slot Data Update protocol (BootServices->LocateProtocol)
  // BootServices[0x140] = LocateProtocol function pointer
  //
  Status = gBootServices->LocateProtocol (
                            &mSlotDataUpdateProtocolGuid,
                            NULL,
                            (VOID **)&SlotProtocol
                            );

  if (!EFI_ERROR (Status)) {
    //
    // Install first PSLT entry: physical slot table (40 bytes)
    // SlotProtocol[0x10] = GetSlotData function pointer
    //
    Status = SlotProtocol->GetSlotData (
                             SlotProtocol,
                             &mSlotDataEntryGuid,
                             (VOID *)&mSlotTableEntry1,
                             sizeof (PSLT_HEADER) + 24
                             );

    if (!EFI_ERROR (Status)) {
      //
      // Install second PSLT entry: extended slot config (40 bytes)
      //
      return SlotProtocol->GetSlotData (
                             SlotProtocol,
                             &mSlotDataEntryGuid,
                             (VOID *)&mSlotTableEntry2,
                             sizeof (PSLT_HEADER) + 24
                             );
    }
  }

  return Status;
}