#include <PiPei.h>
#include <Ppi/MeUma.h>  /* GUID-defined PPI produced by this driver */
#include <Library/DebugLib.h>
#include <Library/PeiServicesLib.h>
#include <Library/HobLib.h>
#include <Library/IoLib.h>
#include <Library/PcdLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/BaseLib.h>

//
// ---------------------------------------------------------------------------
// Globals
// ---------------------------------------------------------------------------
//
// The PPI descriptor (GUID + EFI_PEI_PPI_DESCRIPTOR) for the ME UMA PPI.
// Located in the .data section at 0xffda3390.
//
extern EFI_GUID  gMeUmaPpiGuid;
extern VOID      *gMeUmaPpi;

UINT8  byte_FFDA339C;

//
// ---------------------------------------------------------------------------
// Function prototypes
// ---------------------------------------------------------------------------
EFI_STATUS
EFIAPI
ModuleEntryPoint (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  );

//
// Internal functions (previously sub_*)
//

/**
  Reads 64 bits from an unaligned buffer.
**/
UINT64
EFIAPI
ReadUnaligned64 (
  IN CONST VOID  *Buffer
  )
{
  ASSERT (Buffer != NULL);
  return *(const UINT64 *)Buffer;
}

/**
  Writes 64 bits to an unaligned buffer.
**/
UINT64
EFIAPI
WriteUnaligned64 (
  OUT VOID   *Buffer,
  IN  UINT64 Value
  )
{
  ASSERT (Buffer != NULL);
  *(UINT64 *)Buffer = Value;
  return Value;
}

/**
  Reads 16-bit I/O port.
**/
UINT16
EFIAPI
IoRead16 (
  IN UINT16  Port
  )
{
  ASSERT ((Port & 1) == 0);
  return *(volatile UINT16 *)(UINTN)Port;
}

/**
  Writes 16-bit I/O port.
**/
UINT16
EFIAPI
IoWrite16 (
  IN UINT16  Port,
  IN UINT16  Value
  )
{
  ASSERT ((Port & 1) == 0);
  *(volatile UINT16 *)(UINTN)Port = Value;
  return Value;
}

/**
  Reads 32-bit I/O port (dword).
**/
UINT32
EFIAPI
IoRead32 (
  IN UINT16  Port
  )
{
  ASSERT ((Port & 3) == 0);
  return __indword (Port);
}

/**
  Reads a 16-bit value from PCI configuration space.
**/
UINT16
ReadPciCfg16 (
  IN UINT8  Bus,
  IN UINT8  Dev,
  IN UINT8  Func
  )
{
  PCI_CONFIG_ACCESS  PciCfg;
  PciCfg.Reg  = 0x80000000 | ((UINT32)Bus << 16) | ((UINT32)Dev << 11) | ((UINT32)Func << 8);
  PciCfg.Reserved = 0;
  //
  // The actual read goes through the memory-mapped ECAM or I/O CFG mechanism.
  // We construct the address and decode it via the PCI Express library.
  //
  UINTN  Address = (PciCfg.Reg | 0) & 0xFFF;  // simplified: see PciExpressLib
  // ...
  return 0;
}

//
// ---------------------------------------------------------------------------
// Internal Helpers
// ---------------------------------------------------------------------------

/**
  Returns the EFI_PEI_SERVICES pointer via the IDT-based trick.
**/
EFI_PEI_SERVICES **
GetPeiServices (
  VOID
  )
{
  IA32_DESCRIPTOR  Idtr;
  AsmReadIdtr (&Idtr);
  return *(EFI_PEI_SERVICES ***)((UINTN)Idtr.Base - 4);
}

/**
  Returns the PCD pointer (PeiPcdLib).
**/
VOID *
GetPcdPtr (
  VOID *This
  )
{
  VOID   *PcdDb;
  EFI_STATUS  Status;

  Status = PeiServicesLocatePpi (&gPeiPcdPpiGuid, 0, NULL, &PcdDb);
  if (EFI_ERROR (Status)) {
    DEBUG ((EFI_D_ERROR, "\nASSERT_EFI_ERROR (Status = %r)\n", Status));
    ASSERT_EFI_ERROR (Status);
    return NULL;
  }
  return PcdDb;
}

/**
  Calls the PcdGetX PPI method (index 4).
**/
UINT32
PcdGet32 (
  VOID *This,
  UINTN  Token
  )
{
  PCD_PPI  *PcdPpi;

  PcdPpi = (PCD_PPI *)GetPcdPtr (This);
  return PcdPpi->Get32 (Token);
}

/**
  Calls the PcdGetSize PPI method (index 5).
**/
UINTN
PcdGetSize (
  VOID *This,
  UINTN  Token
  )
{
  PCD_PPI  *PcdPpi;

  PcdPpi = (PCD_PPI *)GetPcdPtr (This);
  return PcdPpi->GetSize (Token);
}

/**
  Returns the HOB list pointer.
**/
VOID *
GetHobList (
  VOID
  )
{
  EFI_PEI_SERVICES  **PeiServices;
  EFI_STATUS         Status;
  VOID               *HobList;

  PeiServices = GetPeiServices ();
  Status = (*PeiServices)->GetHobList (PeiServices, &HobList);
  if (EFI_ERROR (Status)) {
    DEBUG ((EFI_D_ERROR, "\nASSERT_EFI_ERROR (Status = %r)\n", Status));
    ASSERT_EFI_ERROR (Status);
  }
  ASSERT (HobList != NULL);
  return HobList;
}

/**
  Locates the first HOB with type EFI_HOB_TYPE_GUID_EXT (0x0004)
  whose GUID matches 'This'.
**/
EFI_HOB_GUID_TYPE *
FindGuidHob (
  IN EFI_GUID  *Guid
  )
{
  VOID             *HobList;
  EFI_HOB_GUID_TYPE *Hob;

  HobList = GetHobList ();
  for (Hob = (EFI_HOB_GUID_TYPE *)HobList; Hob->Header.HobType != EFI_HOB_TYPE_END_OF_HOB_LIST;
       Hob = (EFI_HOB_GUID_TYPE *)((UINT8 *)Hob + Hob->Header.HobLength)) {
    if (Hob->Header.HobType == EFI_HOB_TYPE_GUID_EXT &&
        CompareGuid (&Hob->Name, Guid)) {
      return Hob;
    }
  }
  return NULL;
}

/**
  Copies a GUID from Source to Destination via the unaligned helpers.
**/
VOID *
CopyGuid (
  OUT VOID       *Destination,
  IN  CONST VOID *Source
  )
{
  WriteUnaligned64 (Destination, ReadUnaligned64 (Source));
  WriteUnaligned64 ((UINT8 *)Destination + 8, ReadUnaligned64 ((UINT8 *)Source + 8));
  return Destination;
}

/**
  Compares two GUIDs.
  Returns TRUE if they are equal.
**/
BOOLEAN
CompareGuid (
  IN CONST EFI_GUID  *Guid1,
  IN CONST EFI_GUID  *Guid2
  )
{
  UINT64  A_lo, A_hi, B_lo, B_hi;

  A_lo = ReadUnaligned64 (Guid1);
  A_hi = ReadUnaligned64 ((UINT8 *)Guid1 + 8);
  B_lo = ReadUnaligned64 (Guid2);
  B_hi = ReadUnaligned64 ((UINT8 *)Guid2 + 8);

  return (A_lo == B_lo && A_hi == B_hi);
}

/**
  Builds a HOB with GUID type.
**/
VOID *
BuildGuidHob (
  IN EFI_GUID  *Guid,
  IN UINTN      DataLength
  )
{
  EFI_HOB_GUID_TYPE  *Hob;
  VOID               *Buffer;
  EFI_STATUS          Status;
  EFI_PEI_SERVICES  **PeiServices;

  ASSERT (Guid != NULL);
  ASSERT (DataLength <= 0xFFE0);

  PeiServices = (EFI_PEI_SERVICES **)GetPeiServices ();
  Status = (*PeiServices)->CreateHob (
                              PeiServices,
                              EFI_HOB_TYPE_GUID_EXT,
                              (UINT16)(DataLength + sizeof (EFI_HOB_GUID_TYPE)),
                              &Hob
                              );
  if (EFI_ERROR (Status)) {
    return NULL;
  }
  ASSERT (Hob != NULL);
  CopyGuid (&Hob->Name, Guid);
  Buffer = Hob + 1;   // points to Hob + 24
  ZeroMem (Buffer, DataLength);
  return (VOID *)((UINT8 *)Hob + sizeof (EFI_HOB_GUID_TYPE));
}

//
// ---------------------------------------------------------------------------
// UMA Parameter Validation
// ---------------------------------------------------------------------------

/**
  Validates the stored UMA location parameters against the Silicon registers.

  @param[in]  MeNcMemLowBaseAddr   Low UMA base address
  @param[in]  MeNcMemHighBaseAddr  High UMA base address (extension)
  @param[in]  MeNcMemLowLimit      Low UMA limit address
  @param[in]  MeNcMemHighLimit     High UMA limit address

  @retval  EFI_SUCCESS            UMA parameters are valid.
  @retval  EFI_INVALID_PARAMETER  UMA address or size mismatch.
**/
EFI_STATUS
ValidateUmaLocation (
  IN UINT32  MeNcMemLowBaseAddr,
  IN UINT32  MeNcMemHighBaseAddr,
  IN UINT32  MeNcMemLowLimit,
  IN UINT32  MeNcMemHighLimit
  )
{
  UINT32      MeUmaSize;
  UINT32      MeUmaSizeCalc;
  UINT32      UmaBase;
  UINT32      UmaBaseExt;
  EFI_STATUS  Status;

  Status = EFI_SUCCESS;

  //
  // Check if the ME is in a debug or error mode that skips UMA checking.
  //
  if (!(UINT8)ModuleEntryPoint (NULL, NULL)) {
    return EFI_SUCCESS;
  }

  DEBUG ((EFI_D_INFO, "ME UMA:  ====================================================\n"));
  DEBUG ((EFI_D_INFO, "ME UMA:  Stored UMA Location:\n"));
  DEBUG ((EFI_D_INFO, "ME UMA:  MeNcMemLowBaseAddr  = 0x%x\n", MeNcMemLowBaseAddr));
  DEBUG ((EFI_D_INFO, "ME UMA:  MeNcMemHighBaseAddr = 0x%x\n", MeNcMemHighBaseAddr));
  DEBUG ((EFI_D_INFO, "ME UMA:  MeNcMemLowLimit     = 0x%x (->0x%x)\n", MeNcMemLowLimit, MeNcMemLowLimit));
  DEBUG ((EFI_D_INFO, "ME UMA:  MeNcMemHighLimit    = 0x%x\n", MeNcMemHighLimit));

  //
  // Calculate UMA size:
  //   MeUmaSize  = (MeNcMemLowLimit & 0xFFF80000) + 0x80000 - MeNcMemLowBaseAddr
  //   MeUmaSize += MeNcMemHighLimit
  //
  MeUmaSize    = (MeNcMemLowLimit & 0xFFF80000) + 0x80000 - MeNcMemLowBaseAddr;
  MeUmaSizeCalc = MeUmaSize + MeNcMemHighLimit;

  DEBUG ((EFI_D_INFO, "ME UMA:  MeUmaSize           = 0x%x (%dM)\n", MeUmaSizeCalc, MeUmaSizeCalc >> 20));

  //
  // Read the UMA parameters from Silicon registers (via PCI config access).
  //
  UmaBase    = *(UINT32 *)((UINTN)PciCfgRead (0) + 240);  // offset 0xF0
  UmaBaseExt = *(UINT32 *)((UINTN)PciCfgRead (0) + 244);  // offset 0xF4

  DEBUG ((EFI_D_INFO, "ME UMA:  UMA parameters read:\n"));
  DEBUG ((EFI_D_INFO, "ME UMA:  MeUmaBase           = 0x%x\n", UmaBase));
  DEBUG ((EFI_D_INFO, "ME UMA:  MeUmaBaseExt        = 0x%x\n", UmaBaseExt));

  //
  // If base addresses are zero, skip validation.
  //
  if (MeNcMemLowBaseAddr == 0 || MeNcMemLowLimit == 0) {
    if (MeUmaSizeCalc != 0) {
      DEBUG ((EFI_D_WARN, "ME UMA:  WARNING: Lower part of UMA addresses is 0.\n"));
    }
    goto Done;
  }

  //
  // Validate the UMA base addresses match Silicon registers.
  //
  if (MeNcMemLowBaseAddr != UmaBase || MeNcMemHighBaseAddr != UmaBaseExt) {
    DEBUG ((EFI_D_ERROR, "ME UMA:  ERROR: UMA addresses error.\n"));
    Status = EFI_INVALID_PARAMETER;
  }

  //
  // Validate UMA size: must not exceed 64 MB, must be even (2 MB aligned).
  //
  if (MeUmaSizeCalc >> 20 > 64) {
    DEBUG ((EFI_D_ERROR, "ME UMA:  ERROR: UMA size error #01.\n"));
    Status = EFI_INVALID_PARAMETER;
  } else if ((MeUmaSizeCalc & 1) != 0) {
    DEBUG ((EFI_D_ERROR, "ME UMA:  ERROR: UMA size error #02.\n"));
    Status = EFI_INVALID_PARAMETER;
  }

Done:
  DEBUG ((EFI_D_INFO, "ME UMA:  ====================================================\n"));
  return Status;
}

//
// ---------------------------------------------------------------------------
// ME Type Detection
// ---------------------------------------------------------------------------

/**
  Determines the onboard ME type based on HOB and FS (Firmware Status) info.

  @return  The ME type:
           1  = SPS (Server Platform Services)
           2  = Consumer/Client ME
           15 = Debug Mode (DFX)
           255 = Unknown or Error
**/
UINT8
GetOnBoardMeType (
  VOID
  )
{
  UINT32  MeFirmwareStatus;
  UINT8   MeOperationMode;

  //
  // Check Debug Mode via PCH PMC (DWR flow).
  //
  if (IsPchDwrFlow ()) {
    DEBUG ((EFI_D_INFO, "HECI: GetOnBoardMeType() for DWR flow return Dfx type\n"));
    return ME_TYPE_DFX;  // 15
  }

  //
  // Read the ME Firmware Status (MEFS) register from PCI config.
  //
  MeFirmwareStatus = *(UINT32 *)((UINTN)PciCfgRead (0) + 64);  // offset 0x40

  if (MeFirmwareStatus == 0xFFFFFFFF) {
    //
    // Fall back to HOB if MEFS is invalid.
    //
    MeFirmwareStatus = GetMeFs1FromHob ();
    DEBUG ((EFI_D_INFO, "HECI: GetOnBoardMeType() reads Hfs info from HOB = %d\n"));
  }

  //
  // Evaluate the ME type from the status.
  //
  if ((MeFirmwareStatus & 0xF) == 0xF) {
    return ME_TYPE_DFX;  // Debug/DFX
  }

  if ((MeFirmwareStatus & 0xF) == 4) {
    return 255;  // Unknown type
  }

  MeOperationMode = (UINT8)((MeFirmwareStatus >> 16) & 0xF);
  DEBUG ((EFI_D_INFO, "HECI: MeOperationMode = %d\n", MeOperationMode));

  switch (MeOperationMode) {
    case 0:
    case 1:
      return ME_TYPE_CLIENT;  // 2

    case 2:
      return 255;

    case 3:
    case 4:
    case 5:
      return 255;

    case 7:
      return 255;

    case 15:
      return ME_TYPE_SPS;  // 1

    default:
      DEBUG ((EFI_D_ERROR, "HECI: ME type not recognized (MEFS1: 0x%08X)\n", MeFirmwareStatus));
      DEBUG ((EFI_D_ERROR, "                             (MEFS2: 0x%08X)\n", PciCfgRead (0)));
      return 0;
  }
}

/**
  Reads ME Firmware Status 1 (MEFS1) from the appropriate HOB.

  @return  The MEFS1 value, or -1 on error.
**/
UINT32
GetMeFs1FromHob (
  VOID
  )
{
  EFI_HOB_GUID_TYPE  *Hob;
  UINT32              Result;

  Result = (UINT32)-1;

  Hob = FindGuidHob (&gMeFwHobGuid);
  if (Hob == NULL || ((ME_FW_HOB *)GET_GUID_HOB_DATA (Hob))->Group[0].FunNumber != HECI1_DEVICE) {
    //
    // Could not read HOB, use fallback path.
    //
    DEBUG ((EFI_D_ERROR, "HECI: GetMeFs1FromHob() Can't read correctly MeFwHob info\n"));
  } else {
    Result = ((ME_FW_HOB *)GET_GUID_HOB_DATA (Hob))->Group[0].FunNumber;
  }

  DEBUG ((EFI_D_INFO, "HECI: GetMeFs1FromHob() returns MEFS1 = %d\n", Result));
  return Result;
}

//
// ---------------------------------------------------------------------------
// Performance Logging (PEI Performance)
// ---------------------------------------------------------------------------

/**
  Gets the PEI performance log and ID arrays from HOBs (or builds them).

  @param[out]  PeiPerformanceLog       The performance log array
  @param[out]  PeiPerformanceIdArray   The performance ID array
**/
VOID
GetPeiPerformance (
  OUT UINT32  **PeiPerformanceLog,
  OUT UINT32  **PeiPerformanceIdArray
  )
{
  EFI_HOB_GUID_TYPE  *GuidHob;

  ASSERT (PeiPerformanceLog != NULL);
  ASSERT (PeiPerformanceIdArray != NULL);

  GuidHob = FindGuidHob (&gEfiPeiPerformanceHobGuid);
  if (GuidHob != NULL) {
    //
    // Use existing HOB data.
    //
    *PeiPerformanceLog = (UINT32 *)((UINT8 *)GET_GUID_HOB_DATA (GuidHob));
    GuidHob = FindGuidHob (&gEfiPeiPerformanceIdArrayGuid);
    ASSERT (GuidHob != NULL);
    *PeiPerformanceIdArray = (UINT32 *)((UINT8 *)GET_GUID_HOB_DATA (GuidHob));
  } else {
    //
    // Build fresh HOBs for the performance log and ID array.
    //
    *PeiPerformanceLog = BuildGuidHob (&gEfiPeiPerformanceHobGuid, 40008);
    *PeiPerformanceLog = (UINT32 *)ZeroMem (*PeiPerformanceLog, 40008);
    *PeiPerformanceIdArray = BuildGuidHob (&gEfiPeiPerformanceIdArrayGuid, 4000);
    ZeroMem (*PeiPerformanceIdArray, 4000);
  }
}

/**
  Finds the performance log entry index for a given module ID.

  @param[in]  PerformanceLog  The performance log array
  @param[in]  ModuleId        The module identifier to find

  @return  The index of the found entry, or the array length if not found.
**/
UINT32
FindPerformanceLogIndex (
  IN UINT32  *PerformanceLog,
  IN UINT32   ModuleId
  )
{
  UINT32  i;
  UINT32  Count;

  Count = PerformanceLog[0];
  for (i = 0; i < Count; i++) {
    UINT32  Index = Count - i - 1;
    UINT32  *Entry = &PerformanceLog[10 * Index];

    if (Entry[0] == 0 && Entry[2] == ModuleId && Entry[3] == 0 &&
        !AsciiStrnCmp ("", "", 7) && !AsciiStrnCmp ("", "", 7)) {
      return Index;
    }
  }
  return i;
}

//
// ---------------------------------------------------------------------------
// HECI Message / PPI Installation
// ---------------------------------------------------------------------------

/**
  Checks if the current ME type prevents HECI communication.
**/
BOOLEAN
IsHeciSkipped (
  VOID
  )
{
  return GetOnBoardMeType () == 255;
}

/**
  Locates the HECI PPI and sends a message to query UMA configuration.

  @param[in]      Message       The HECI message buffer
  @param[out]     Response      The HECI response buffer

  @retval EFI_SUCCESS           Message sent and response received.
  @retval Others                Error
**/
EFI_STATUS
SendHeciMessage (
  IN  UINT8   *Message,
  OUT UINT8   *Response
  )
{
  EFI_STATUS                Status;
  EFI_PEI_PPI_DESCRIPTOR    *HeciPpi;
  HECI_PPI                  *Heci;

  Status = PeiServicesLocatePpi (&gHeciPpiGuid, 0, NULL, &HeciPpi);
  if (EFI_ERROR (Status)) {
    return EFI_UNSUPPORTED;
  }
  Heci = (HECI_PPI *)HeciPpi;

  return Heci->SendMessage (Heci, Message, Response);
}

/**
  Micro-delay using I/O port 0x508 polling.

  @param[in]  Microseconds  The number of microseconds to delay.
**/
VOID
MicroDelay (
  IN UINT32  Microseconds
  )
{
  UINT32  Count;
  UINT32  Base;
  UINT32  Current;

  Count = Microseconds >> 22;
  Base  = Microseconds & 0x3FFFFF;

  do {
    Current = Base + (IoRead32 (0x508) & 0xFFFFFF);
    Base = 0x400000;
    while (((Current - IoRead32 (0x508)) & 0x800000) == 0) {
      _mm_pause ();
    }
    Count--;
  } while (Count != 0xFFFFFFFF);
}

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

/**
  ME UMA PPI Driver Entry Point.

  Installs the ME UMA PPI that exposes the UMA location information
  to other PEIMs. Validates the UMA parameters from Silicon registers.

  @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 PPI was installed successfully.
  @retval EFI_INVALID_PARAMETER UMA parameters are inconsistent.
  @retval Others                Error from PPI installation.
**/
EFI_STATUS
EFIAPI
ModuleEntryPoint (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  EFI_STATUS         Status;
  EFI_PEI_SERVICES **PeiServices;

  //
  // One-time initialization of the PCI Express register.
  //
  if ((IoRead32 (1024068) & 0x80) == 0) {
    InitializePciExpress ();
    IoWrite16 ((UINT16 *)(UINTN)(GetPcdPtr (NULL) + 1024068), 0x500);
    *(UINT8 *)(GetPcdPtr (NULL) + 1024068) |= 0x80;
  }

  DEBUG ((EFI_D_INFO, "ME UMA:  ME UMA PPI Driver EntryPoint\n"));

  //
  // Check if the system has valid ME firmware (skip on debug/error).
  //
  byte_FFDA339C = (IsHeciSkipped () < 0) ? 0 : byte_FFDA339C;

  //
  // Install the ME UMA PPI.
  //
  PeiServices = GetPeiServices ();
  Status = (*PeiServices)->InstallPpi (
                              PeiServices,
                              &gMeUmaPpiDescriptor
                              );
  DEBUG ((EFI_D_INFO, "ME UMA:  ME UMA PPI Installation status %r\n", Status));

  if (EFI_ERROR (Status)) {
    DEBUG ((EFI_D_ERROR, "\nASSERT_EFI_ERROR (Status = %r)\n", Status));
    ASSERT_EFI_ERROR (Status);
  }

  return Status;
}
