/** @file
  StaticSkuDataDxeLightningRidgeEXECB4 - Static SKU Data Driver

  Provides LightningRidge EX EC B4 platform-specific static data tables
  for the UBA (Unified BIOS Abstraction) protocol.  The driver registers
  ACPI name-to-path mappings for NVDIMM NVDR devices N011 and N020,
  enabling the platform policy layer to resolve control method paths at
  runtime under the _SB_.NVDR namespace.

  Each NVDR device publishes eight ACPI methods:
    FXCD  - Flex Capacity Data
    FXST  - Flex Status
    FXIN  - Flex Input
    FXOU  - Flex Output
    FXBS  - Flex Buffer Status
    FXFH  - Flex Flush
    CENA  - Capability Enable
    CFIS  - Capability Status

  Copyright (c) 2024, Dell Technologies. All rights reserved.
  SPDX-License-Identifier: BSD-2-Clause-Patent
**/

#include <Uefi.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiRuntimeServicesTableLib.h>
#include <Library/BaseLib.h>
#include <Library/DebugLib.h>
#include <Library/IoLib.h>

#include "StaticSkuDataDxeLightningRidgeEXECB4.h"

//
// ---------------------------------------------------------------------------
// GUID definitions for the UBA protocol and HOB matching.
// These GUIDs are consumed by the entry point but defined in the platform
// BDS / UBA DXE driver package.
// ---------------------------------------------------------------------------

extern EFI_GUID  gPlatformUbaProtocolGuid;
extern EFI_GUID  gUbaNvdrProtocolGuid;
extern EFI_GUID  gHobTargetGuidHi;
extern EFI_GUID  gHobTargetGuidLo;

//
// ---------------------------------------------------------------------------
// Module globals
// ---------------------------------------------------------------------------

EFI_HANDLE            gImageHandle     = NULL;
EFI_SYSTEM_TABLE     *gSystemTable      = NULL;
EFI_BOOT_SERVICES    *gBootServices     = NULL;
EFI_RUNTIME_SERVICES *gRuntimeServices  = NULL;

STATIC VOID          *mSkuProtocol      = NULL;   ///< Cached UBA protocol
STATIC VOID          *mHobList          = NULL;   ///< Cached HOB list

//
// ---------------------------------------------------------------------------
// UBA protocol function-table offsets
// ---------------------------------------------------------------------------

#define UBA_OFFSET_DEBUG_PRINT   0
#define UBA_OFFSET_ASSERT        8
#define UBA_OFFSET_INSTALL_DATA  16

//
// ---------------------------------------------------------------------------
// UBA protocol dispatch-table function type definitions
// ---------------------------------------------------------------------------

/**
  DebugPrint routine in the UBA protocol.
  @param[in]  ErrorLevel  Debug message error level.
  @param[in]  Format      Print format string.
  @param[in]  VaList      Variable argument list.
  @return  Number of characters printed, or implementation-defined.
**/
typedef
UINTN
(EFIAPI *UBA_DEBUG_PRINT)(
  IN UINTN        ErrorLevel,
  IN CONST CHAR8  *Format,
  VA_LIST         VaList
  );

/**
  Assert routine in the UBA protocol.
  @param[in]  FileName    Source file name.
  @param[in]  LineNumber  Assertion line number.
  @param[in]  Description Assertion description.
**/
typedef
VOID
(EFIAPI *UBA_ASSERT)(
  IN CONST CHAR8  *FileName,
  IN UINTN         LineNumber,
  IN CONST CHAR8  *Description
  );

//
// ---------------------------------------------------------------------------
// ACPI name-path entry layout
//
// Each entry occupies 32 bytes:
//   [0..3]  4-character ACPI method name
//   [4..7]  zero padding
//   [8..31] Full ACPI path string (null-terminated, zero-padded to 24 bytes)
// ---------------------------------------------------------------------------

#define ACPI_ENTRY_SIZE  32

//
// NVDR Device N011 -- Socket 0, Channel 3
//
STATIC CONST UINT8  mN011AcpiEntries[8 * ACPI_ENTRY_SIZE] = {
  'F','X','C','D', 0,0,0,0,  '_','S','B','_','.','N','V','D','R','.','N','0','1','1','.','F','X','C','D',0,0,0,0,0,
  'F','X','S','T', 0,0,0,0,  '_','S','B','_','.','N','V','D','R','.','N','0','1','1','.','F','X','S','T',0,0,0,0,0,
  'F','X','I','N', 0,0,0,0,  '_','S','B','_','.','N','V','D','R','.','N','0','1','1','.','F','X','I','N',0,0,0,0,0,
  'F','X','O','U', 0,0,0,0,  '_','S','B','_','.','N','V','D','R','.','N','0','1','1','.','F','X','O','U',0,0,0,0,0,
  'F','X','B','S', 0,0,0,0,  '_','S','B','_','.','N','V','D','R','.','N','0','1','1','.','F','X','B','S',0,0,0,0,0,
  'F','X','F','H', 0,0,0,0,  '_','S','B','_','.','N','V','D','R','.','N','0','1','1','.','F','X','F','H',0,0,0,0,0,
  'C','E','N','A', 0,0,0,0,  '_','S','B','_','.','N','V','D','R','.','N','0','1','1','.','C','E','N','A',0,0,0,0,
  'C','F','I','S', 0,0,0,0,  '_','S','B','_','.','N','V','D','R','.','N','0','1','1','.','C','F','I','S',0,0,0,0,
};

//
// NVDR Device N020 -- Socket 0, Channel 4
//
STATIC CONST UINT8  mN020AcpiEntries[8 * ACPI_ENTRY_SIZE] = {
  'F','X','C','D', 0,0,0,0,  '_','S','B','_','.','N','V','D','R','.','N','0','2','0','.','F','X','C','D',0,0,0,0,0,
  'F','X','S','T', 0,0,0,0,  '_','S','B','_','.','N','V','D','R','.','N','0','2','0','.','F','X','S','T',0,0,0,0,0,
  'F','X','I','N', 0,0,0,0,  '_','S','B','_','.','N','V','D','R','.','N','0','2','0','.','F','X','I','N',0,0,0,0,0,
  'F','X','O','U', 0,0,0,0,  '_','S','B','_','.','N','V','D','R','.','N','0','2','0','.','F','X','O','U',0,0,0,0,0,
  'F','X','B','S', 0,0,0,0,  '_','S','B','_','.','N','V','D','R','.','N','0','2','0','.','F','X','B','S',0,0,0,0,0,
  'F','X','F','H', 0,0,0,0,  '_','S','B','_','.','N','V','D','R','.','N','0','2','0','.','F','X','F','H',0,0,0,0,0,
  'C','E','N','A', 0,0,0,0,  '_','S','B','_','.','N','V','D','R','.','N','0','2','0','.','C','E','N','A',0,0,0,0,
  'C','F','I','S', 0,0,0,0,  '_','S','B','_','.','N','V','D','R','.','N','0','2','0','.','C','F','I','S',0,0,0,0,
};

//
// ---------------------------------------------------------------------------
// ReadUnaligned64
// ---------------------------------------------------------------------------

UINT64
EFIAPI
ReadUnaligned64 (
  IN CONST UINT64  *Buffer
  )
{
  ASSERT (Buffer != NULL);
  return *Buffer;
}

//
// ---------------------------------------------------------------------------
// GuidCompareByPair
//
// Compares two EFI_GUID values by splitting each into two UINT64 words.
// This matches the code pattern used in the original binary.
// ---------------------------------------------------------------------------

BOOLEAN
EFIAPI
GuidCompareByPair (
  IN EFI_GUID  *Guid1,
  IN EFI_GUID  *Guid2
  )
{
  UINT64  W1, W2, W3, W4;

  W1 = ReadUnaligned64 ((UINT64 *)Guid1);
  W2 = ReadUnaligned64 ((UINT64 *)((UINT8 *)Guid1 + 8));
  W3 = ReadUnaligned64 ((UINT64 *)Guid2);
  W4 = ReadUnaligned64 ((UINT64 *)((UINT8 *)Guid2 + 8));

  return (BOOLEAN)(W1 == W3 && W2 == W4);
}

//
// ---------------------------------------------------------------------------
// LocateUbaProtocol
//
// Finds the UBA protocol via BootServices->LocateProtocol and caches the
// result.  Before the lookup, the function probes memory availability by
// allocating and immediately freeing a small pool.
// ---------------------------------------------------------------------------

STATIC
VOID *
LocateUbaProtocol (
  VOID
  )
{
  EFI_STATUS  Status;
  UINT64      PoolSize;
  VOID       *Pool;

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

  //
  // Memory-probe: allocate and immediately free a small pool.
  //
  PoolSize = 0x1F;
  Pool     = NULL;
  gBootServices->AllocatePool (EfiBootServicesData, (UINTN)PoolSize, &Pool);
  if (Pool != NULL) {
    gBootServices->FreePool (Pool);
  }

  //
  // If the pool was small enough (<= 0x10), proceed to look up the protocol.
  //
  if (PoolSize <= 0x10) {
    Status = gBootServices->LocateProtocol (
                              &gPlatformUbaProtocolGuid,
                              NULL,
                              &mSkuProtocol
                              );
    if (EFI_ERROR (Status)) {
      mSkuProtocol = NULL;
    }
  }

  return mSkuProtocol;
}

//
// ---------------------------------------------------------------------------
// UbaAssert
//
// Dispatches an assertion through the UBA protocol's assert routine at
// offset +8.
// ---------------------------------------------------------------------------

VOID
EFIAPI
UbaAssert (
  IN CONST CHAR8  *FileName,
  IN UINTN         LineNumber,
  IN CONST CHAR8  *Description
  )
{
  VOID       *Protocol;
  UBA_ASSERT  AssertRoutine;

  Protocol = LocateUbaProtocol ();
  if (Protocol != NULL) {
    AssertRoutine = *(UBA_ASSERT *)((UINT8 *)Protocol + UBA_OFFSET_ASSERT);
    if (AssertRoutine != NULL) {
      AssertRoutine (FileName, LineNumber, Description);
    }
  }
}

//
// ---------------------------------------------------------------------------
// UbaDebugPrint
//
// Conditionally prints a debug message via the UBA protocol's debug print
// routine at offset +0.  The decision is based on the platform type read
// from CMOS scratch register 0x4B.
//
// CMOS 0x4B platform type detection:
//   - Values 1..3 are direct platform identifiers.
//   - Value 0 falls back to MMIO at 0xFDAF0490.
//   - Platform type 1 uses error mask 0x80000004; others use 0x80000006.
// ---------------------------------------------------------------------------

UINTN
EFIAPI
UbaDebugPrint (
  IN UINTN        ErrorLevel,
  IN CONST CHAR8  *Format,
  ...
  )
{
  VOID           *Protocol;
  UBA_DEBUG_PRINT DebugRoutine;
  UINT8           PlatformByte;
  UINTN           Result;
  VA_LIST         VaList;

  VA_START (VaList, Format);

  Result   = 0;
  Protocol = LocateUbaProtocol ();
  if (Protocol == NULL) {
    VA_END (VaList);
    return Result;
  }

  //
  // Select CMOS index 0x4B (preserve bit 7 of port 0x70).
  //
  IoWrite8 (0x70, (IoRead8 (0x70) & 0x80) | 0x4B);
  PlatformByte = IoRead8 (0x71);

  //
  // Normalise: values > 3 that are also 0 need the MMIO fallback.
  //
  if (PlatformByte > 3) {
    if (PlatformByte == 0) {
      PlatformByte = (MmioRead8 (0xFDAF0490) & 2) | 1;
    }
  }

  //
  // Map platform byte -> acceptable error-level mask.
  //
  if ((UINT8)(PlatformByte - 1) <= 0xFD) {
    if (PlatformByte == 1) {
      Result = 0x80000004;
    } else {
      Result = 0x80000006;
    }
  }

  //
  // Forward call if the mask matches the caller's ErrorLevel.
  //
  if ((Result & ErrorLevel) != 0) {
    DebugRoutine = *(UBA_DEBUG_PRINT *)((UINT8 *)Protocol + UBA_OFFSET_DEBUG_PRINT);
    if (DebugRoutine != NULL) {
      Result = DebugRoutine (ErrorLevel, Format, VaList);
    }
  }

  VA_END (VaList);
  return Result;
}

//
// ---------------------------------------------------------------------------
// FindHobList
//
// Scans the system table's configuration table for a GUID pair that matches
// the HOB list GUID.  When found, the pointer at offset +16 is returned as
// the HOB list.
// ---------------------------------------------------------------------------

STATIC
VOID *
FindHobList (
  IN EFI_HANDLE  ImageHandle
  )
{
  UINTN  Index;

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

  if (gSystemTable->NumberOfTableEntries == 0) {
    goto NoHob;
  }

  for (Index = 0; Index < gSystemTable->NumberOfTableEntries; Index++) {
    EFI_GUID  *EntryGuid;
    EFI_GUID   TargetGuid;

    EntryGuid = (EFI_GUID *)(
                  (UINT8 *)gSystemTable->ConfigurationTable +
                  Index * sizeof (EFI_CONFIGURATION_TABLE)
                  );

    //
    // Reconstruct the target GUID from the two module-embedded UINT64 values.
    //
    TargetGuid.Data1 = ReadUnaligned64 ((UINT64 *)&gHobTargetGuidHi);
    TargetGuid.Data2 = ReadUnaligned64 ((UINT64 *)&gHobTargetGuidLo);

    if (GuidCompareByPair (EntryGuid, &TargetGuid)) {
      mHobList = *(VOID **)((UINT8 *)EntryGuid + 16);
      break;
    }
  }

NoHob:
  if (mHobList == NULL) {
    DEBUG ((EFI_D_ERROR, "\nASSERT_EFI_ERROR (Status = %r)\n", EFI_NOT_FOUND));
    ASSERT (!EFI_ERROR (EFI_NOT_FOUND));
    ASSERT (mHobList != NULL);
  }

  return mHobList;
}

//
// ---------------------------------------------------------------------------
// GetAcpiPathPointers
//
// Returns three pointers into the ACPI path entry tables:
//   *N011CenaEntry -> mN011AcpiEntries[6 * 32]  ("_SB_.NVDR.N011.CENA")
//   *N011FxBsEntry -> mN011AcpiEntries[4 * 32]  ("_SB_.NVDR.N011.FXBS")
//   *N020FxStEntry -> mN020AcpiEntries[1 * 32]  ("_SB_.NVDR.N020.FXST")
// ---------------------------------------------------------------------------

EFI_STATUS
EFIAPI
GetAcpiPathPointers (
  OUT UINT8  **N011CenaEntry,
  OUT UINT8  **N011FxBsEntry,
  OUT UINT8  **N020FxStEntry
  )
{
  *N011CenaEntry = (UINT8 *)&mN011AcpiEntries[6 * ACPI_ENTRY_SIZE];
  *N011FxBsEntry = (UINT8 *)&mN011AcpiEntries[4 * ACPI_ENTRY_SIZE];
  *N020FxStEntry = (UINT8 *)&mN020AcpiEntries[1 * ACPI_ENTRY_SIZE];

  return EFI_SUCCESS;
}

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

EFI_STATUS
EFIAPI
StaticSkuDataDxeEntryPoint (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE *SystemTable
  )
{
  EFI_STATUS  Status;
  UINT64      Interface;
  UINT8      *Table0;
  UINT8      *Table1;
  UINT8      *Table2;

  //
  // 1.  Initialise global service table pointers.
  //
  gImageHandle = ImageHandle;
  ASSERT (gImageHandle != NULL);

  gSystemTable = SystemTable;
  ASSERT (gSystemTable != NULL);

  gBootServices = SystemTable->BootServices;
  ASSERT (gBootServices != NULL);

  gRuntimeServices = SystemTable->RuntimeServices;
  ASSERT (gRuntimeServices != NULL);

  //
  // 2.  Locate the HOB list.
  //
  FindHobList (ImageHandle);

  //
  // 3.  Register this platform by sending a debug-print via UBA.
  //
  UbaDebugPrint (0x80000000, "UBA:UsbOcUpdate-TypeLightningRidgeEXECB4\n");

  //
  // 4.  Locate the UBA protocol and install the ACPI path data.
  //
  Interface = 0;
  Status = gBootServices->LocateProtocol (
                            &gUbaNvdrProtocolGuid,
                            NULL,
                            (VOID **)&Interface
                            );
  if (!EFI_ERROR (Status)) {
    GetAcpiPathPointers (&Table0, &Table1, &Table2);

    //
    // Invoke InstallData at offset +16 in the protocol dispatch table.
    // Signature:  (This, Table0, Table1, Size = 16)
    //
    Status = ((EFI_STATUS (EFIAPI *)(UINT64, UINT8 *, UINT8 *, UINTN))(
               (UINT8 *)Interface + UBA_OFFSET_INSTALL_DATA
               ))(
               Interface,
               Table0,
               Table1,
               16
               );
  }

  return Status;
}