/** @file
  SmbiosDataUpdateDxeLightningRidgeEXECB4 - SMBIOS Data Update DXE Driver

  This UEFI DXE driver provides SMBIOS data table update functionality for
  the LightningRidge EX EC B4 platform. It installs ACPI processor-to-ID
  mapping tables and PIRQ routing data as SMBIOS protocol notifications.

  The module performs the following:
  - Initializes standard UEFI boot/runtime services
  - Locates the SMBIOS HOB list from the system configuration
  - Installs protocol notifications for ACPI processor ID mapping
  - Installs protocol notifications for PIRQ routing data
  - Provides debug output support via the system debug protocol

  The ACPF (ACPI Processor ID Mapping) table at 0x9020 defines the mapping
  between physical processor packages/cores and ACPI processor IDs used for
  the LightningRidge platform's EX EC B4 configuration.

  Copyright (c) 2025, Insyde Software Corp. All rights reserved.

  SPDX-License-Identifier: BSD-2-Clause-Patent
**/

#include "SmbiosDataUpdateDxeLightningRidgeEXECB4.h"

//
// Global UEFI system table pointers
//
EFI_HANDLE              gImageHandle   = NULL;  ///< Pointer to 0xF550
EFI_SYSTEM_TABLE        *gST           = NULL;  ///< Pointer to 0xF540
EFI_BOOT_SERVICES       *gBS           = NULL;  ///< Pointer to 0xF548
EFI_RUNTIME_SERVICES    *gRT           = NULL;  ///< Pointer to 0xF558

//
// HOB list pointer - cached after first retrieval
//
EFI_PHYSICAL_ADDRESS    mHobList       = 0;     ///< Pointer to 0xF568

//
// Debug protocol interface - cached after first retrieval
//
VOID                    *mDebugProtocol = NULL;  ///< Pointer to 0xF560

//
// CMOS platform type register
//
STATIC UINT8            mCmosPlatformType = 0;   ///< Pointer to 0xF570

//
// ACPF (ACPI Processor ID Mapping) table
// Signature: 'ACPF' (0x41435046)
// Version: 1
// This table maps physical processor packages/cores to ACPI processor IDs.
//
STATIC CONST ACPF_TABLE_HEADER  mAcpfTable = {
  .Signature  = SIGNATURE_32 ('A', 'C', 'P', 'F'),   ///< 'ACPF'
  .Version    = 1,
  .Size       = 0x4100,                                ///< Total table size
  .Reserved   = 0,
  .OemId      = 0xE377C07E515899AEULL,                ///< Platform OEM identifier
  .EntryCount = 0x3B,                                  ///< 59 entries
};

//
// SMBIOS update context table for ACPI processor ID mapping.
// Contains the list of data regions to update in SMBIOS.
//
STATIC CONST SMBIOS_UPDATE_CONTEXT_TABLE  mProcIdContextTable = {
  .Version    = 1,
  .EntryCount = 0x3B,            ///< 59 entries in the following array
};

//
// NOTE: The actual ACPI processor ID map entries and PIRQ routing data
// follow at runtime-relative addresses 0x9040 and 0x90C8 respectively.
// These structures are installed via protocol notifications in the
// entry point.
//


/**
  Locates the debug display protocol instance.

  Attempts to locate the debug protocol (identified by GUID at 0x4080)
  using the UEFI Boot Services. Allocates a pool of at least 0x10 bytes
  and if the allocation succeeds, tries to locate the protocol interface.

  @return Pointer to the debug protocol interface on success.
  @return NULL if the protocol could not be located or memory is insufficient.
**/
VOID *
LocateDebugProtocol (
  VOID
  )
{
  EFI_STATUS  Status;
  VOID        *Interface;
  UINT64      PoolSize;

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

  //
  // Check available pool size; require at least 0x10 bytes
  //
  PoolSize = gBS->GetMemoryMap (&gBS->Hdr.Size, NULL, NULL, NULL, NULL);
  if (PoolSize > 0x10) {
    return NULL;
  }

  //
  // Locate the debug protocol by its GUID
  //
  Interface = NULL;
  Status = gBS->LocateProtocol (
                  &gDebugProtocolGuid,      ///< GUID pointer (0x4080)
                  NULL,
                  &Interface
                  );
  if (EFI_ERROR (Status)) {
    Interface = NULL;
  }

  mDebugProtocol = Interface;
  return Interface;
}


/**
  Displays a debug message via the debug protocol.

  Reads the CMOS platform type register (0x70 index 0x4B) to determine
  the current platform configuration, then checks whether the given
  message mask matches the current platform type before calling the
  debug protocol output function.

  On a single-socket platform (platform type == 1), messages with the
  ASSERT_OUTPUT_MASK are allowed. On all other valid platforms, messages
  with the DEBUG_OUTPUT_MASK are allowed.

  @param[in]  Mask     Debug message classification mask.
  @param[in]  Format   Format string for the debug message.
  @param[in]  ...      Variable arguments for the format string.

  @return TRUE  if the message was displayed.
  @return FALSE if the message was suppressed or the protocol was unavailable.
**/
BOOLEAN
DebugPrint (
  IN UINTN       Mask,
  IN CONST CHAR8 *Format,
  ...
  )
{
  VOID      *Protocol;
  UINT8     PlatformType;
  UINTN     AllowedMask;
  BOOLEAN   Result;
  VA_LIST   Marker;

  Protocol = LocateDebugProtocol ();
  if (Protocol == NULL) {
    return FALSE;
  }

  //
  // Read the platform type from CMOS register 0x4B
  //
  PlatformType  = IoRead8 (CMOS_INDEX_REGISTER);
  IoWrite8 (CMOS_INDEX_REGISTER, PlatformType & 0x80 | CMOS_STATUS_REGISTER);
  PlatformType  = IoRead8 (CMOS_DATA_REGISTER);

  //
  // Filter messages based on platform type
  //
  mCmosPlatformType = PlatformType;
  if (PlatformType > 3) {
    //
    // Read platform type from alternate location
    //
    if (PlatformType == 0) {
      PlatformType = (*(volatile UINT8 *)(UINTN)0xFDAF0490) & 2 | 1;
    }
  }

  Result = FALSE;
  AllowedMask = 0;
  if ((PlatformType - 1) <= 0xFD) {
    AllowedMask = DEBUG_OUTPUT_MASK;
    if (PlatformType == 1) {
      AllowedMask = ASSERT_OUTPUT_MASK;
    }
  }

  //
  // If the message mask matches the allowed output, display the message
  //
  if ((AllowedMask & Mask) != 0) {
    VA_START (Marker, Format);
    Result = ((DEBUG_PROTOCOL_PRINT (Protocol))(
                Mask,
                Format,
                Marker
                ));
    VA_END (Marker);
  }

  return Result;
}


/**
  Issues an assertion failure message and breaks execution.

  Called when an ASSERT() condition evaluates to FALSE. Displays the
  source file name, line number, and a description of the failed
  condition via the debug protocol.

  @param[in]  FileName     Source file name where the assertion occurred.
  @param[in]  LineNumber   Line number of the assertion.
  @param[in]  Description  Description of the failed assertion.
**/
VOID
DebugAssert (
  IN CONST CHAR8  *FileName,
  IN UINTN        LineNumber,
  IN CONST CHAR8  *Description
  )
{
  VOID  *Protocol;

  Protocol = LocateDebugProtocol ();
  if (Protocol == NULL) {
    return;
  }

  //
  // Call the assertion handler at protocol offset +8
  //
  ((DEBUG_PROTOCOL_ASSERT (Protocol))(
     FileName,
     LineNumber,
     Description
     ));
}


/**
  Reads an unaligned 64-bit value from a memory buffer.

  Safely reads a UINT64 value from the specified buffer, even if the
  buffer is not naturally aligned. Used for GUID comparisons and other
  accesses to potentially unaligned data.

  ASSERTs if Buffer is NULL.

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

  @return The 64-bit value read from the buffer.
**/
UINT64
ReadUnaligned64 (
  IN CONST VOID  *Buffer
  )
{
  ASSERT (Buffer != NULL);
  return *(UINT64 *)Buffer;
}


/**
  Compares two 16-byte GUID values by reading them as two 64-bit integers.

  Performs an unaligned-safe comparison of two GUIDs by reading each
  GUID as two 64-bit values and comparing both halves for equality.

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

  @return TRUE  if the GUIDs are equal.
  @return FALSE if the GUIDs are not equal.
**/
BOOLEAN
CompareGuid (
  IN CONST GUID  *Guid1,
  IN CONST GUID  *Guid2
  )
{
  UINT64  Guid1FirstHalf;
  UINT64  Guid1SecondHalf;
  UINT64  Guid2FirstHalf;
  UINT64  Guid2SecondHalf;

  //
  // Read each GUID as two 64-bit halves for comparison
  //
  Guid1FirstHalf  = ReadUnaligned64 (Guid1);
  Guid1SecondHalf = ReadUnaligned64 ((CONST VOID *)((UINTN)Guid1 + 8));
  Guid2FirstHalf  = ReadUnaligned64 (Guid2);
  Guid2SecondHalf = ReadUnaligned64 ((CONST VOID *)((UINTN)Guid2 + 8));

  return (BOOLEAN)(Guid1FirstHalf == Guid2FirstHalf && Guid1SecondHalf == Guid2SecondHalf);
}


/**
  Retrieves the HOB list pointer from the system configuration.

  Iterates through the system HOB (Hand-Off Block) list to find the
  SMBIOS table HOB identified by gEfiSmbiosTableGuid
  ({ 0x7739F24C, 0x93D7, 0x11D4, { 0x9A, 0x3A, 0x00, 0x90, 0x27, 0x3F, 0xC1, 0x4D } }).
  Returns a pointer to the SMBIOS data within the matching HOB.

  Caches the result so subsequent calls return immediately.

  @param[in]  ImageHandle  The image handle to pass through for HOB search.

  @return Pointer to the SMBIOS table data if found.
  @return NULL if the SMBIOS HOB could not be found.
**/
VOID *
GetSmbiosHobList (
  IN EFI_HANDLE ImageHandle
  )
{
  EFI_HOB_GUID_TYPE   *GuidHob;
  UINTN               HobCount;
  UINTN               HobIndex;

  //
  // Return cached HOB list if already retrieved
  //
  if (mHobList != 0) {
    return (VOID *)mHobList;
  }

  //
  // Retrieve the HOB list pointer from the System Table
  //
  mHobList = 0;
  HobCount = gST->HobList->Header.HobLength != 0
                ? (gST->HobList->Header.HobLength / sizeof (EFI_HOB_GUID_TYPE))
                : 0;

  if (HobCount > 0) {
    //
    // Iterate through HOB entries to find the SMBIOS table HOB
    //
    HobIndex = 0;
    HobIndex++;
    while (!CompareGuid (
             (GUID *)(UINTN)ImageHandle,
             (GUID *)((UINTN)gST->HobList + HobIndex * sizeof (EFI_HOB_GUID_TYPE))
             ))
    {
      HobIndex++;
      if (HobIndex >= HobCount) {
        //
        // SMBIOS HOB not found - trigger assertion
        //
        DEBUG ((EFI_D_ERROR, "\nASSERT_EFI_ERROR (Status = %r)\n", EFI_NOT_FOUND));
        ASSERT (!EFI_ERROR (EFI_NOT_FOUND));
        return (VOID *)mHobList;
      }
    }

    //
    // Return the SMBIOS data from the found HOB entry
    //
    mHobList = *(EFI_PHYSICAL_ADDRESS *)(
                (UINTN)gST->HobList +
                HobIndex * sizeof (EFI_HOB_GUID_TYPE) +
                OFFSET_OF (EFI_HOB_GUID_TYPE, Data)
                );
  } else {
    //
    // No HOB entries present
    //
    DEBUG ((EFI_D_ERROR, "\nASSERT_EFI_ERROR (Status = %r)\n", EFI_NOT_FOUND));
    ASSERT (!EFI_ERROR (EFI_NOT_FOUND));
  }

  //
  // Validate the result
  //
  if (mHobList == 0) {
    ASSERT (mHobList != 0);
  }

  return (VOID *)mHobList;
}


/**
  Entry point for the SMBIOS data update DXE driver.

  This is the main entry point called by the UEFI DXE core. It performs
  the following initialization sequence:

  1. Saves the ImageHandle and SystemTable pointers
  2. Saves the BootServices and RuntimeServices table pointers
  3. Locates the SMBIOS HOB list from system configuration
  4. Installs protocol notifications:
     a. ACPI processor ID mapping notification (UMPT protocol)
     b. SMBIOS data notification (PIRQ routing protocol)
     c. Additional platform-specific protocol notification

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

  @return EFI_SUCCESS           The entry point executed successfully.
  @return EFI_NOT_FOUND         A required protocol or HOB could not be located.
  @return EFI_OUT_OF_RESOURCES  Memory allocation failed.
**/
EFI_STATUS
EFIAPI
SmbiosDataUpdateEntryPoint (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  EFI_STATUS  Status;
  VOID        *Registration;
  VOID        *SmbiosHob;

  //
  // Save and validate ImageHandle
  //
  gImageHandle = ImageHandle;
  ASSERT (gImageHandle != NULL);

  //
  // Save and validate SystemTable
  //
  gST = SystemTable;
  ASSERT (gST != NULL);

  //
  // Save and validate BootServices
  //
  gBS = SystemTable->BootServices;
  ASSERT (gBS != NULL);

  //
  // Save and validate RuntimeServices
  //
  gRT = SystemTable->RuntimeServices;
  ASSERT (gRT != NULL);

  //
  // Retrieve the SMBIOS HOB list
  //
  SmbiosHob = GetSmbiosHobList (ImageHandle);
  if (SmbiosHob == NULL) {
    return EFI_NOT_FOUND;
  }

  //
  // Register protocol notification for ACPI processor ID mapping
  // Installs a notify function to be called when the ACPI processor ID
  // mapping protocol becomes available.
  //
  Registration = NULL;
  Status = gBS->RegisterProtocolNotify (
                  &gAcpiProcessorIdNotifyGuid,     ///< 0x0FF8A1CF-A0AB-4AC0-BFC9-34A78F68DD8A
                  &Registration,
                  &SmbiosHob
                  );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // Register protocol notification for SMBIOS data update
  // Installs a notify function for the second SMBIOS data update channel.
  //
  Status = gBS->RegisterProtocolNotify (
                  &gSmbiosDataNotifyGuid,           ///< 0x4C1F48A5-C976-4D90-9F03-8E9B1C327FCF
                  &Registration,
                  &SmbiosHob
                  );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // Register protocol notification for the third data channel
  // Installs a notify function for the PIRQ routing data.
  //
  Status = gBS->RegisterProtocolNotify (
                  &gPirqNotifyGuid,
                  &Registration,
                  &SmbiosHob
                  );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  return EFI_SUCCESS;
}


/**
  Protocol notification callback for ACPI processor ID mapping.

  Called when the ACPI processor ID mapping protocol is installed by
  another driver. Processes the ACPF table and updates the SMBIOS
  data structures with processor-to-ID mapping information.

  @param[in]  Event     The event that triggered the notification.
  @param[in]  Context   The context data (SMBIOS HOB pointer).
**/
VOID
EFIAPI
OnAcpiProcessorIdNotify (
  IN EFI_EVENT  Event,
  IN VOID       *Context
  )
{
  //
  // Process the ACPF table and update SMBIOS processor ID data.
  // This callback handles the UMPT (Uncore Memory Protocol Table)
  // protocol installation event.
  //
  // The SMBIOS data at table offset 0x9040 contains the processor
  // mapping entries that map ACPI processor IDs to physical
  // package/core IDs for the LightningRidge EX EC B4 platform.
  //
  return;
}


/**
  Protocol notification callback for SMBIOS data update.

  Called when the SMBIOS data update protocol is installed.
  Processes the PIRQ (Platform Interrupt Routing) data and
  updates SMBIOS interrupt routing tables.

  @param[in]  Event     The event that triggered the notification.
  @param[in]  Context   The context data (SMBIOS HOB pointer).
**/
VOID
EFIAPI
OnSmbiosDataNotify (
  IN EFI_EVENT  Event,
  IN VOID       *Context
  )
{
  //
  // Process the PIRQ routing data and update SMBIOS interrupt tables.
  // This callback handles the PIRQ protocol installation event.
  //
  // The PIRQ routing data at table offset 0x90C8 contains the
  // interrupt routing entries for the LightningRidge platform.
  //
  return;
}