/** @file
  IioCfgUpdateDxeCLX64L - IIO Configuration Update DXE driver for CLX64L (Cascade Lake)

  This DXE driver is responsible for publishing IIO (Integrated I/O) configuration
  data for the Cascade Lake (CLX64L) platform via the UBA (Unified BIOS Architecture)
  protocol. It reads CMOS register 0x4B to determine the socket configuration type
  (single-socket vs. multi-socket) and registers the IIO platform configuration block
  under four distinct sub-system GUIDs.

  The driver follows a standard UEFI DXE driver pattern:
  1. Entry point caches UEFI Boot/Runtime Services pointers (via BootServicesTableLib).
  2. Retrieves the HOB (Hand-Off Block) list for system configuration data.
  3. Locates the UBA IIO Configuration Database protocol.
  4. Registers the CLX64L-specific IIO configuration data block four times,
     each under a different sub-system GUID.

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

**/

#include "IioCfgUpdateDxeCLX64L.h"

//
// Global caches for UEFI Boot/Runtime services and protocol pointers.
// These are populated by the entry point and used by helper functions.
//
EFI_BOOT_SERVICES     *gBS;
EFI_RUNTIME_SERVICES  *gRT;
EFI_SYSTEM_TABLE      *gST;
EFI_HANDLE             gImageHandle;

//
// Cached protocol pointers for UBA debug and UBA IIO configuration.
// Initialized on first use, then reused for subsequent calls.
//
UBA_DEBUG_PROTOCOL    *mUbaDebugProtocol   = NULL;   ///< Located by GetUbaDebugProtocol()
VOID                  *mHobEntry           = NULL;   ///< Located by GetHobList()

//
// CMOS register 0x4B stores the socket configuration type.
//
volatile UINT8        mSocketConfig;

///
/// IIO Platform Configuration Block for CLX64L (Cascade Lake)
///
/// This data block is registered under each of the four sub-system GUIDs.
/// The values represent platform-specific IIO configuration parameters.
///
/// Structure layout:
///   +0x00: Signature  "IIOP" (0x4F494950)
///   +0x04: Version    1
///   +0x08: TotalSize  0xD18 (3352 bytes)
///   +0x10: EntryCount 27
///   +0x18: Field3     0x4F8 (1272) - IIO topology or routing data size
///   +0x20: Field4     0xC00 (3072) - IIO register/data block size
///   +0x28: Field5     0x114 (276)  - IIO configuration table count
///
IIO_PLATFORM_CONFIG_BLOCK mIioPlatformConfig = {
  .Signature   = IIO_PLATFORM_CONFIG_SIGNATURE,
  .Version     = IIO_PLATFORM_CONFIG_VERSION,
  .TotalSize   = 0xD18,
  .EntryCount  = 27,
  .UnkField18  = 0x4F8,
  .UnkField20  = 0xC00,
  .UnkField28  = 0x114
};

/**
  Locates and caches the UBA debug protocol.

  Uses BootServices->LocateProtocol to find the UBA debug protocol interface.
  The protocol pointer is cached in mUbaDebugProtocol after the first successful
  lookup. Also checks that the HOB list size does not exceed the maximum expected
  size (0x10 entries) as a sanity check before proceeding.

  @return Pointer to the cached UBA debug protocol, or NULL if:
          - The HOB list has more than 16 entries (unexpected platform state)
          - The protocol could not be located
          - LocateProtocol returned an error

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

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

  //
  // Check the HOB list size as a platform sanity check.
  // The number of HOB entries should be <= 0x10 for this platform.
  //
  HobList = (EFI_HANDLE)gBS->GetHobList ();
  if ((UINTN)HobList > 0x10) {
    //
    // HOB list exceeds expected size; protocol lookup is skipped.
    //
    return NULL;
  }

  //
  // Locate the UBA debug protocol.
  //
  Status = gBS->LocateProtocol (
                  &gUbaDebugProtocolGuid,
                  NULL,
                  (VOID **)&Protocol
                  );
  if (EFI_ERROR (Status)) {
    //
    // Protocol not available; cache NULL to avoid repeated lookups.
    //
    mUbaDebugProtocol = NULL;
    return NULL;
  }

  //
  // Cache the protocol pointer for subsequent calls.
  //
  mUbaDebugProtocol = Protocol;
  return Protocol;
}

//
// GUID definitions for the UBA debug protocol.
// {36232936-0E76-31C8-A13A-3AF2FC1C3932}
//
EFI_GUID gUbaDebugProtocolGuid = UBA_DEBUG_PROTOCOL_GUID;

/**
  Reads CMOS register 0x4B to determine the IIO socket configuration type.

  Reads the current value from CMOS index 0x4B. The value indicates whether the
  platform is configured as single-socket or multi-socket. If the value read is
  greater than 3 and non-zero, the function checks the chipset configuration
  register at memory-mapped address 0xFDAF0490 to derive the socket type.

  @return Socket configuration type:
          1 = Single-socket configuration
          4 = Multi-socket configuration (default, or when value > 3)

**/
UINT8
ReadIioSocketConfig (
  VOID
  )
{
  UINT8   CmosValue;
  UINT8   SocketType;

  //
  // Read CMOS register 0x4B with NMI mask preserved.
  //
  CmosValue = IoRead8 (RTC_ADDRESS_REGISTER);
  IoWrite8 (RTC_ADDRESS_REGISTER, (CmosValue & CMOS_NMI_MASK) | CMOS_IIO_SOCKET_CONFIG);
  SocketType = IoRead8 (RTC_DATA_REGISTER);

  //
  // Decode the socket configuration.
  //
  if (SocketType > 3) {
    //
    // If CMOS value is out of expected range, check hardware register.
    // If SocketType is 0, fall through to hardware register check.
    //
    if (SocketType == 0) {
      //
      // Read chipset configuration at fixed MMIO address 0xFDAF0490.
      // Bit 1 indicates socket topology (0 = dual-socket, 1 = single-socket).
      //
      SocketType = (MmioRead8 (0xFDAF0490) & 0x02) | 0x01;
    }
  }

  return SocketType;
}

/**
  Prints a debug/trace message through the UBA debug protocol.

  This function formats and dispatches a debug message at the specified severity
  level via the UBA debug protocol's print function (vtable offset +8, function
  index 1). The socket configuration type is read from CMOS to determine the
  appropriate debug mask value.

  @param[in]  DebugLevel  Debug severity mask. Expected values include:
                           - 0x80000000 (EFI_D_ERROR / DEBUG level)
                           - 0x00400000 (EFI_D_VERBOSE)
  @param[in]  Format      A pointer to a Null-terminated Unicode format string.
  @param[in]  ...         Variable arguments to be formatted.

  @return The return value from the protocol print function, or 0 if the
          UBA debug protocol is not available.

**/
UINT8
UbaDebugPrint (
  IN  UINTN       DebugLevel,
  IN  CONST CHAR8 *Format,
  ...
  )
{
  UBA_DEBUG_PROTOCOL   *Protocol;
  UINT64                DebugMask;
  UINT8                 SocketType;
  VA_LIST               VaList;

  //
  // Get the cached UBA debug protocol.
  //
  Protocol = GetUbaDebugProtocol ();
  if (Protocol == NULL) {
    return 0;
  }

  //
  // Read the socket configuration to select the appropriate debug mask.
  //
  SocketType = ReadIioSocketConfig ();

  DebugMask = 0;
  if ((SocketType - 1) <= 0xFD) {
    //
    // Valid socket type: set default multi-socket mask.
    //
    DebugMask = 0x80000006;   // DEBUG_ERROR | 0x6 (platform-specific mask)
    if (SocketType == SOCKET_CONFIG_SINGLE) {
      DebugMask = 0x80000004; // Single-socket mask
    }
  }

  //
  // Only print if the requested debug level matches the current mask.
  //
  if ((DebugMask & DebugLevel) != 0) {
    VA_START (VaList, Format);
    Protocol->DebugPrint (DebugLevel, Format, VaList);
    VA_END (VaList);
  }

  return (UINT8)(UINTN)Protocol;
}

/**
  Compares two GUIDs by their first 8 bytes (two consecutive UINT64 values).

  This is an optimized GUID comparison that reads both halves of the GUID as
  QWORD values and compares them. Used primarily for matching HOB entry GUIDs.

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

  @retval TRUE   Both GUIDs are identical.
  @retval FALSE  GUIDs differ.

**/
BOOLEAN
CompareGuid (
  IN  EFI_GUID  *Guid1,
  IN  EFI_GUID  *Guid2
  )
{
  //
  // Read both QWORD halves of each GUID for comparison.
  // Use unaligned reads since GUID pointers in HOB entries may not be aligned.
  //
  return (ReadUnaligned64 ((UINT64 *)Guid1) == ReadUnaligned64 ((UINT64 *)Guid2)) &&
         (ReadUnaligned64 ((UINT64 *)((UINT8 *)Guid1 + 8)) == ReadUnaligned64 ((UINT64 *)((UINT8 *)Guid2 + 8)));
}

//
// GUID for the HOB Memory Allocation Module entry.
// {7739F24C-93D7-11D4-9A3A-0090273FC14D}
//
EFI_GUID gHobMemoryAllocModuleGuid = MEMORY_ALLOC_MODULE_HOB_GUID;

/**
  Retrieves the HOB list pointer for this module.

  Walks the HOB (Hand-Off Block) list from the System Table to find an entry
  whose GUID matches gEfiHobMemoryAllocModuleGuid. The HOB list pointer is
  cached in mHobEntry after the first successful lookup.

  @param[in]  ImageHandle  Unused; provided for API compatibility with the
                            standard UEFI driver entry point signature.

  @return Pointer to the matching HOB entry, or NULL if:
          - The HOB list is empty (SystemTable->HobListSize == 0)
          - No matching GUID entry was found
          - The ASSERT on !EFI_ERROR(Status) would fire

**/
VOID *
GetHobList (
  IN  EFI_HANDLE  ImageHandle
  )
{
  EFI_HOB_GUID_TYPE    *Hob;
  UINTN                 HobListSize;
  EFI_PHYSICAL_ADDRESS  HobListStart;
  UINTN                 Index;

  //
  // Return cached HOB entry if already located.
  //
  if (mHobEntry != NULL) {
    return mHobEntry;
  }

  //
  // Initialize cache to NULL to detect lookup failures.
  //
  mHobEntry = NULL;

  //
  // Get HOB list information from the System Table.
  // SystemTable + 104 = HobListSize (UINTN)
  // SystemTable + 112 = HobListStart (EFI_PHYSICAL_ADDRESS *)
  //
  HobListSize = *(UINTN *)((UINT8 *)gST + 104);
  if (HobListSize == 0) {
    //
    // HOB list is empty. This indicates a system configuration error.
    //
    DEBUG ((DEBUG_ERROR, "\nASSERT_EFI_ERROR (Status = %r)\n", EFI_INVALID_PARAMETER));
    ASSERT (!EFI_ERROR (EFI_INVALID_PARAMETER));
  }

  //
  // Walk the HOB list to find a matching GUID entry.
  //
  HobListStart = *(EFI_PHYSICAL_ADDRESS *)((UINT8 *)gST + 112);
  for (Index = 0; Index < HobListSize; Index++) {
    Hob = (EFI_HOB_GUID_TYPE *)(HobListStart + (Index * HOB_ENTRY_SIZE));
    if (CompareGuid (&Hob->Name, &gHobMemoryAllocModuleGuid)) {
      //
      // Found the matching HOB entry; cache and return it.
      // The entry data starts at Hob + 16 (after GUID and header).
      //
      mHobEntry = (VOID *)((UINT8 *)Hob + 16);
      return mHobEntry;
    }
  }

  //
  // No matching HOB entry found.
  //
  ASSERT (mHobEntry != NULL);
  return mHobEntry;
}

//
// GUIDs for the UBA IIO Configuration Database protocol and sub-system blocks.
//
// UBA IIO Config Protocol: {E03E0D46-5263-4845-B0A4-58D57B3177E2}
//
EFI_GUID gUbaIioConfigProtocolGuid = UBA_IIO_CONFIG_PROTOCOL_GUID;

//
// IIO sub-system configuration GUIDs
//
EFI_GUID gIioConfigSubGuid1 = IIO_CONFIG_SUBGUID_1;   // {6FE6C559-4F35-4111-98E1-332A251512F3}
EFI_GUID gIioConfigSubGuid2 = IIO_CONFIG_SUBGUID_2;   // {0F722F2A-650F-448A-ABB7-04EECD75BB30}
EFI_GUID gIioConfigSubGuid3 = IIO_CONFIG_SUBGUID_3;   // {EBD11A00-8C5C-4F71-BB9E-5394032B01F4}
EFI_GUID gIioConfigSubGuid4 = IIO_CONFIG_SUBGUID_4;   // {123BD082-3201-465C-B139-0CB8C77208F8}

/**
  Registers the IIO platform configuration blocks via the UBA protocol.

  Locates the UBA IIO Configuration Database protocol and calls its SetData
  function (vtable offset +16, function index 2) four times to register the
  same IIO_PLATFORM_CONFIG_BLOCK under four different sub-system GUIDs.

  The four GUIDs correspond to different IIO sub-systems within the CLX64L
  platform (e.g., PCIe root ports, UPI links, memory controllers).

  @retval EFI_SUCCESS            All four IIO configuration blocks were
                                 successfully registered.
  @retval EFI_UNSUPPORTED        The UBA IIO configuration protocol could not
                                 be located on this platform.
  @retval EFI_INVALID_PARAMETER  One or more parameters were invalid.
  @retval other                  Error status returned by LocateProtocol.

**/
EFI_STATUS
RegisterIioPlatformConfig (
  VOID
  )
{
  EFI_STATUS          Status;
  UBA_IIO_CONFIG_DB   *UbaIioConfigDb;

  //
  // Print trace message indicating IIO config update for CLX64L.
  //
  UbaDebugPrint (DEBUG_VERBOSE, "UBA:IioCfgUpdate-TypeClx64L\n");

  //
  // Locate the UBA IIO Configuration Database protocol.
  //
  Status = gBS->LocateProtocol (
                  &gUbaIioConfigProtocolGuid,
                  NULL,
                  (VOID **)&UbaIioConfigDb
                  );
  if (EFI_ERROR (Status)) {
    //
    // Protocol not available on this platform.
    //
    return Status;
  }

  //
  // Register the IIO platform configuration block under each sub-system GUID.
  // The same configuration block is registered four times, once per sub-system.
  // Each call uses the protocol's SetData function with size 48 bytes.
  //
  Status  = UbaIioConfigDb->SetData (&gIioConfigSubGuid1, &mIioPlatformConfig, sizeof (mIioPlatformConfig));
  Status |= UbaIioConfigDb->SetData (&gIioConfigSubGuid2, &mIioPlatformConfig, sizeof (mIioPlatformConfig));
  Status |= UbaIioConfigDb->SetData (&gIioConfigSubGuid3, &mIioPlatformConfig, sizeof (mIioPlatformConfig));
  Status |= UbaIioConfigDb->SetData (&gIioConfigSubGuid4, &mIioPlatformConfig, sizeof (mIioPlatformConfig));

  return Status;
}

/**
  DXE Driver Entry Point for IioCfgUpdateDxeCLX64L.

  This is the main entry point called by the UEFI DXE core. It initializes the
  UEFI system table pointers via gBS, gRT, gST, and gImageHandle (provided by
  the UefiBootServicesTableLib and UefiRuntimeServicesTableLib libraries), then
  retrieves the HOB list and registers IIO platform configuration data.

  Execution flow:
  1. Validate and cache ImageHandle and SystemTable.
  2. Validate and cache gBS (BootServices) and gRT (RuntimeServices).
  3. Call GetHobList() to retrieve the HOB list for memory allocation data.
  4. Call RegisterIioPlatformConfig() to publish IIO config blocks.

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

  @retval EFI_SUCCESS           The driver entry point completed successfully.
  @retval EFI_INVALID_PARAMETER ImageHandle or SystemTable was NULL.
  @retval other                 Error status from RegisterIioPlatformConfig().

**/
EFI_STATUS
EFIAPI
IioCfgUpdateDxeEntry (
  IN EFI_HANDLE         ImageHandle,
  IN EFI_SYSTEM_TABLE   *SystemTable
  )
{
  EFI_STATUS  Status;
  VOID        *HobList;

  //
  // === Phase 1: Initialize UEFI Service Pointers ===
  //
  // The UefiBootServicesTableLib constructor sets:
  //   gImageHandle = ImageHandle
  //   gST          = SystemTable
  //   gBS          = SystemTable->BootServices
  // The UefiRuntimeServicesTableLib constructor sets:
  //   gRT          = SystemTable->RuntimeServices
  //
  // Each pointer is validated against NULL with ASSERT.
  //

  //
  // === Phase 2: Retrieve HOB List ===
  //
  // Walk the System Table's HOB database to locate the memory allocation
  // module HOB entry (gEfiHobMemoryAllocModuleGuid).
  //
  HobList = GetHobList (ImageHandle);
  if (HobList == NULL) {
    //
    // HOB list not found - system configuration error.
    // An ASSERT will have already been triggered inside GetHobList().
    //
    return EFI_NOT_FOUND;
  }

  //
  // === Phase 3: Register IIO Configuration Blocks ===
  //
  // Locate the UBA IIO Configuration Database protocol and register the
  // CLX64L-specific IIO platform configuration data under four GUIDs.
  //
  Status = RegisterIioPlatformConfig ();
  return Status;
}