/** @file
  Copyright (c) 2024, Intel Corporation. All rights reserved.<BR>
  SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include <PiPei.h>
#include <Pi/PiHob.h>
#include <Library/BaseLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/DebugLib.h>
#include <Library/IoLib.h>
#include <Library/PrintLib.h>
#include <Library/FdtLib.h>
#include <Library/HobLib.h>
#include <Library/PcdLib.h>

/**
  Add a new HOB to the HOB List.

  @param HobType            Type of the new HOB.
  @param HobLength          Length of the new HOB to allocate.

  @return  NULL if there is no space to create a hob.
  @return  The address point to the new created hob.

**/
VOID *
EFIAPI
CreateHob (
  IN  UINT16  HobType,
  IN  UINT16  HobLength
  );

/**
  Add HOB into HOB list
  @param[in]  Hob    The HOB to be added into the HOB list.
**/
VOID
AddNewHob (
  IN EFI_PEI_HOB_POINTERS  *Hob
  );

/**
  Check the HOB and decide if it is need inside Payload
  Payload maintainer may make decision which HOB is need or needn't
  Then add the check logic in the function.
  @param[in] Hob The HOB to check
  @retval TRUE  If HOB is need inside Payload
  @retval FALSE If HOB is needn't inside Payload
**/
BOOLEAN
EFIAPI
FitIsHobNeed (
  EFI_PEI_HOB_POINTERS  Hob
  )
{
  if (FixedPcdGetBool (PcdHandOffFdtEnable)) {
    if (Hob.Header->HobType == EFI_HOB_TYPE_HANDOFF) {
      return FALSE;
    }

    if (Hob.Header->HobType == EFI_HOB_TYPE_MEMORY_ALLOCATION) {
      if (CompareGuid (&Hob.MemoryAllocation->AllocDescriptor.Name, &gUniversalPayloadDeviceTreeGuid)) {
        return FALSE;
      }

      if (CompareGuid (&Hob.MemoryAllocationModule->MemoryAllocationHeader.Name, &gEfiHobMemoryAllocModuleGuid)) {
        return FALSE;
      }

      if ((Hob.MemoryAllocation->AllocDescriptor.MemoryType == EfiReservedMemoryType) ||
          (Hob.MemoryAllocation->AllocDescriptor.MemoryType == EfiBootServicesCode) ||
          (Hob.MemoryAllocation->AllocDescriptor.MemoryType == EfiBootServicesData) ||
          (Hob.MemoryAllocation->AllocDescriptor.MemoryType == EfiRuntimeServicesCode) ||
          (Hob.MemoryAllocation->AllocDescriptor.MemoryType == EfiRuntimeServicesData) ||
          (Hob.MemoryAllocation->AllocDescriptor.MemoryType == EfiACPIReclaimMemory) ||
          (Hob.MemoryAllocation->AllocDescriptor.MemoryType == EfiACPIMemoryNVS))
      {
        return FALSE;
      }
    }

    if (Hob.Header->HobType == EFI_HOB_TYPE_RESOURCE_DESCRIPTOR) {
      if (Hob.ResourceDescriptor->ResourceType == EFI_RESOURCE_SYSTEM_MEMORY) {
        return FALSE;
      }
    }

    if (Hob.Header->HobType == EFI_HOB_TYPE_GUID_EXTENSION) {
      if (CompareGuid (&Hob.Guid->Name, &gUniversalPayloadSmbios3TableGuid)) {
        return FALSE;
      }

      if (CompareGuid (&Hob.Guid->Name, &gUniversalPayloadSerialPortInfoGuid)) {
        return FALSE;
      }

      if (CompareGuid (&Hob.Guid->Name, &gUniversalPayloadAcpiTableGuid)) {
        return FALSE;
      }

      if (CompareGuid (&Hob.Guid->Name, &gUniversalPayloadPciRootBridgeInfoGuid)) {
        return FALSE;
      }
    }
  }

  // Arrive here mean the HOB is need
  return TRUE;
}

/**
  It will Parse FDT -custom node based on information from bootloaders.
  @param[in]  FdtBase The starting memory address of FdtBase
  @param[in]  HobList The starting memory address of New Hob list.

**/
UINTN
EFIAPI
CustomFdtNodeParser (
  IN VOID  *FdtBase,
  IN VOID  *HobList
  )
{
  INT32                 Node, CustomNode;
  INT32                 TempLen;
  UINT64                *Data64;
  UINTN                 CHobList;
  CONST FDT_PROPERTY    *PropertyPtr;
  EFI_PEI_HOB_POINTERS  Hob;

  CHobList = (UINTN)HobList;

  DEBUG ((DEBUG_INFO, "%a() #1 \n", __func__));

  //
  // Look for if exists hob list node
  //
  Node = FdtSubnodeOffsetNameLen (FdtBase, 0, "options", (INT32)AsciiStrLen ("options"));
  if (Node > 0) {
    DEBUG ((DEBUG_INFO, "  Found options node (%08X)", Node));
    CustomNode = FdtSubnodeOffsetNameLen (FdtBase, Node, "upl-custom", (INT32)AsciiStrLen ("upl-custom"));
    if (CustomNode > 0) {
      DEBUG ((DEBUG_INFO, "  Found upl-custom node (%08X)", CustomNode));
      PropertyPtr = FdtGetProperty (FdtBase, CustomNode, "hoblistptr", &TempLen);
      Data64      = (UINT64 *)(PropertyPtr->Data);
      CHobList    = (UINTN)Fdt64ToCpu (ReadUnaligned64 (Data64));
      DEBUG ((DEBUG_INFO, "  Found hob list node (%08X)", CustomNode));
      DEBUG ((DEBUG_INFO, " -pointer  %016lX\n", CHobList));
    }
  }

  Hob.Raw = (UINT8 *)CHobList;

  //
  // Since payload created new Hob, move all hobs except PHIT from boot loader hob list.
  //
  while (!END_OF_HOB_LIST (Hob)) {
    if (FitIsHobNeed (Hob)) {
      // Add this hob to payload HOB
      AddNewHob (&Hob);
    }

    Hob.Raw = GET_NEXT_HOB (Hob);
  }

  return CHobList;
}
