/** @file
  Copyright (c) 2023, Intel Corporation. All rights reserved.<BR>
  SPDX-License-Identifier: BSD-2-Clause-Patent
**/

#include "UefiPayloadEntry.h"
#include <Library/FdtLib.h>
#include <Guid/UniversalPayloadBase.h>
#include <Guid/MemoryTypeInformation.h>
#include <Library/FdtParserLib.h>
#include <Library/HobParserLib.h>

#define MEMORY_ATTRIBUTE_MASK  (EFI_RESOURCE_ATTRIBUTE_PRESENT             |        \
                                       EFI_RESOURCE_ATTRIBUTE_INITIALIZED         | \
                                       EFI_RESOURCE_ATTRIBUTE_TESTED              | \
                                       EFI_RESOURCE_ATTRIBUTE_READ_PROTECTED      | \
                                       EFI_RESOURCE_ATTRIBUTE_WRITE_PROTECTED     | \
                                       EFI_RESOURCE_ATTRIBUTE_EXECUTION_PROTECTED | \
                                       EFI_RESOURCE_ATTRIBUTE_READ_ONLY_PROTECTED | \
                                       EFI_RESOURCE_ATTRIBUTE_16_BIT_IO           | \
                                       EFI_RESOURCE_ATTRIBUTE_32_BIT_IO           | \
                                       EFI_RESOURCE_ATTRIBUTE_64_BIT_IO           | \
                                       EFI_RESOURCE_ATTRIBUTE_PERSISTENT          )

#define TESTED_MEMORY_ATTRIBUTES  (EFI_RESOURCE_ATTRIBUTE_PRESENT     |     \
                                       EFI_RESOURCE_ATTRIBUTE_INITIALIZED | \
                                       EFI_RESOURCE_ATTRIBUTE_TESTED      )

GLOBAL_REMOVE_IF_UNREFERENCED EFI_MEMORY_TYPE_INFORMATION  mDefaultMemoryTypeInformation[] = {
  { EfiACPIReclaimMemory,   FixedPcdGet32 (PcdMemoryTypeEfiACPIReclaimMemory)   },
  { EfiACPIMemoryNVS,       FixedPcdGet32 (PcdMemoryTypeEfiACPIMemoryNVS)       },
  { EfiReservedMemoryType,  FixedPcdGet32 (PcdMemoryTypeEfiReservedMemoryType)  },
  { EfiRuntimeServicesData, FixedPcdGet32 (PcdMemoryTypeEfiRuntimeServicesData) },
  { EfiRuntimeServicesCode, FixedPcdGet32 (PcdMemoryTypeEfiRuntimeServicesCode) },
  { EfiMaxMemoryType,       0                                                   }
};

extern VOID  *mHobList;

CHAR8  *mLineBuffer = NULL;

/**
  Print all HOBs info from the HOB list.
  @return The pointer to the HOB list.
**/
VOID
PrintHob (
  IN CONST VOID  *HobStart
  );

VOID
EFIAPI
ProcessLibraryConstructorList (
  VOID
  );

/**
  Find the first substring.
  @param  String    Point to the string where to find the substring.
  @param  CharSet   Point to the string to be found.
**/
UINTN
EFIAPI
AsciiStrSpn (
  IN CHAR8  *String,
  IN CHAR8  *CharSet
  )
{
  UINTN  Count;
  CHAR8  *Str1;
  CHAR8  *Str2;

  Count = 0;

  for (Str1 = String; *Str1 != L'\0'; Str1++) {
    for (Str2 = CharSet; *Str2 != L'\0'; Str2++) {
      if (*Str1 == *Str2) {
        break;
      }
    }

    if (*Str2 == L'\0') {
      return Count;
    }

    Count++;
  }

  return Count;
}

/**
  Searches a string for the first occurrence of a character contained in a
  specified buffer.
  @param  String    Point to the string where to find the substring.
  @param  CharSet   Point to the string to be found.
**/
CHAR8 *
EFIAPI
AsciiStrBrk (
  IN CHAR8  *String,
  IN CHAR8  *CharSet
  )
{
  CHAR8  *Str1;
  CHAR8  *Str2;

  for (Str1 = String; *Str1 != L'\0'; Str1++) {
    for (Str2 = CharSet; *Str2 != L'\0'; Str2++) {
      if (*Str1 == *Str2) {
        return (CHAR8 *)Str1;
      }
    }
  }

  return NULL;
}

/**
  Find the next token after one or more specified characters.
  @param  String    Point to the string where to find the substring.
  @param  CharSet   Point to the string to be found.
**/
CHAR8 *
EFIAPI
AsciiStrTokenLine (
  IN CHAR8  *String OPTIONAL,
  IN CHAR8  *CharSet
  )
{
  CHAR8  *Begin;
  CHAR8  *End;

  Begin = (String == NULL) ? mLineBuffer : String;
  if (Begin == NULL) {
    return NULL;
  }

  Begin += AsciiStrSpn (Begin, CharSet);
  if (*Begin == L'\0') {
    mLineBuffer = NULL;
    return NULL;
  }

  End = AsciiStrBrk (Begin, CharSet);
  if ((End != NULL) && (*End != L'\0')) {
    *End = L'\0';
    End++;
  }

  mLineBuffer = End;
  return Begin;
}

/**
  Some bootloader may pass a pcd database, and UPL also contain a PCD database.
  Dxe PCD driver has the assumption that the two PCD database can be catenated and
  the local token number should be successive.
  This function will fix up the UPL PCD database to meet that assumption.
  @param[in]   DxeFv         The FV where to find the Universal PCD database.
  @retval EFI_SUCCESS        If it completed successfully.
  @retval other              Failed to fix up.
**/
EFI_STATUS
FixUpPcdDatabase (
  IN  EFI_FIRMWARE_VOLUME_HEADER  *DxeFv
  )
{
  EFI_STATUS           Status;
  EFI_FFS_FILE_HEADER  *FileHeader;
  VOID                 *PcdRawData;
  PEI_PCD_DATABASE     *PeiDatabase;
  PEI_PCD_DATABASE     *UplDatabase;
  EFI_HOB_GUID_TYPE    *GuidHob;
  DYNAMICEX_MAPPING    *ExMapTable;
  UINTN                Index;

  GuidHob = GetFirstGuidHob (&gPcdDataBaseHobGuid);
  if (GuidHob == NULL) {
    //
    // No fix-up is needed.
    //
    return EFI_SUCCESS;
  }

  PeiDatabase = (PEI_PCD_DATABASE *)GET_GUID_HOB_DATA (GuidHob);
  DEBUG ((DEBUG_INFO, "Find the Pei PCD data base, the total local token number is %d\n", PeiDatabase->LocalTokenCount));

  Status = FvFindFileByTypeGuid (DxeFv, EFI_FV_FILETYPE_DRIVER, PcdGetPtr (PcdPcdDriverFile), &FileHeader);
  ASSERT_EFI_ERROR (Status);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  Status = FileFindSection (FileHeader, EFI_SECTION_RAW, &PcdRawData);
  ASSERT_EFI_ERROR (Status);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  UplDatabase = (PEI_PCD_DATABASE *)PcdRawData;
  ExMapTable  = (DYNAMICEX_MAPPING *)(UINTN)((UINTN)PcdRawData + UplDatabase->ExMapTableOffset);

  for (Index = 0; Index < UplDatabase->ExTokenCount; Index++) {
    ExMapTable[Index].TokenNumber += PeiDatabase->LocalTokenCount;
  }

  DEBUG ((DEBUG_INFO, "Fix up UPL PCD database successfully\n"));
  return EFI_SUCCESS;
}

/**
  It will build Fv HOBs based on information from bootloaders.
  @param[out] DxeFv          The pointer to the DXE FV in memory.
  @retval EFI_SUCCESS        If it completed successfully.
  @retval EFI_NOT_FOUND      If it failed to find node in fit image.
  @retval Others             If it failed to build required HOBs.
**/
EFI_STATUS
BuildFitLoadablesFvHob (
  OUT EFI_FIRMWARE_VOLUME_HEADER  **DxeFv
  )
{
  EFI_STATUS              Status;
  VOID                    *Fdt;
  UINT8                   *GuidHob;
  UNIVERSAL_PAYLOAD_BASE  *PayloadBase;
  INT32                   ConfigNode;
  INT32                   Config1Node;
  INT32                   ImageNode;
  INT32                   FvNode;
  INT32                   Depth;
  CONST FDT_PROPERTY      *PropertyPtr;
  INT32                   TempLen;
  CONST CHAR8             *Fvname;
  UINT32                  DataOffset;
  UINT32                  DataSize;
  UINT32                  *Data32;

  Fdt = NULL;

  GuidHob = GetFirstGuidHob (&gUniversalPayloadBaseGuid);
  if (GuidHob != NULL) {
    PayloadBase = (UNIVERSAL_PAYLOAD_BASE *)GET_GUID_HOB_DATA (GuidHob);
    Fdt         = (VOID *)(UINTN)PayloadBase->Entry;
    DEBUG ((DEBUG_INFO, "PayloadBase Entry = 0x%08x\n", PayloadBase->Entry));
  }

  if (Fdt == NULL) {
    return EFI_UNSUPPORTED;
  }

  Status = FdtCheckHeader (Fdt);
  if (EFI_ERROR (Status)) {
    return EFI_UNSUPPORTED;
  }

  ConfigNode = FdtSubnodeOffsetNameLen (Fdt, 0, "configurations", (INT32)AsciiStrLen ("configurations"));
  if (ConfigNode <= 0) {
    return EFI_NOT_FOUND;
  }

  Config1Node = FdtSubnodeOffsetNameLen (Fdt, ConfigNode, "conf-1", (INT32)AsciiStrLen ("conf-1"));
  if (Config1Node <= 0) {
    return EFI_NOT_FOUND;
  }

  ImageNode = FdtSubnodeOffsetNameLen (Fdt, 0, "images", (INT32)AsciiStrLen ("images"));
  if (ImageNode <= 0) {
    return EFI_NOT_FOUND;
  }

  FvNode = FdtSubnodeOffsetNameLen (Fdt, ImageNode, "tianocore", (INT32)AsciiStrLen ("tianocore"));
  Depth  = FdtNodeDepth (Fdt, FvNode);
  FvNode = FdtNextNode (Fdt, FvNode, &Depth);
  Fvname = FdtGetName (Fdt, FvNode, &TempLen);
  while ((AsciiStrCmp ((Fvname + AsciiStrLen (Fvname) - 2), "fv") == 0)) {
    if (FvNode <= 0) {
      return EFI_NOT_FOUND;
    }

    PropertyPtr = FdtGetProperty (Fdt, FvNode, "data-offset", &TempLen);
    Data32      = (UINT32 *)(PropertyPtr->Data);
    DataOffset  = SwapBytes32 (*Data32);

    PropertyPtr = FdtGetProperty (Fdt, FvNode, "data-size", &TempLen);
    Data32      = (UINT32 *)(PropertyPtr->Data);
    DataSize    = SwapBytes32 (*Data32);

    if (AsciiStrCmp (Fvname, "uefi-fv") == 0) {
      *DxeFv = (EFI_FIRMWARE_VOLUME_HEADER *)((UINTN)PayloadBase->Entry + (UINTN)DataOffset);
      ASSERT ((*DxeFv)->FvLength == DataSize);
    } else {
      BuildFvHob (((UINTN)PayloadBase->Entry + (UINTN)DataOffset), DataSize);
    }

    DEBUG ((
      DEBUG_INFO,
      "UPL Multiple fv[%a], Base=0x%08x, size=0x%08x\n",
      Fvname,
      ((UINTN)PayloadBase->Entry + (UINTN)DataOffset),
      DataSize
      ));
    Depth  = FdtNodeDepth (Fdt, FvNode);
    FvNode = FdtNextNode (Fdt, FvNode, &Depth);
    Fvname = FdtGetName (Fdt, FvNode, &TempLen);
  }

  return EFI_SUCCESS;
}

/**
 *
  Create new HOB for new HOB list

  @param[in]  BootloaderParameter  The HOB to be added into the HOB list.
**/
VOID
CreatNewHobForHoblist (
  IN UINTN  BootloaderParameter
  )
{
  EFI_PEI_HOB_POINTERS         Hob;
  UINTN                        MinimalNeededSize;
  EFI_PHYSICAL_ADDRESS         FreeMemoryBottom;
  EFI_PHYSICAL_ADDRESS         FreeMemoryTop;
  EFI_PHYSICAL_ADDRESS         MemoryBottom;
  EFI_PHYSICAL_ADDRESS         MemoryTop;
  EFI_HOB_RESOURCE_DESCRIPTOR  *PhitResourceHob;
  EFI_HOB_RESOURCE_DESCRIPTOR  *ResourceHob;
  EFI_HOB_HANDOFF_INFO_TABLE   *HobInfo;

  Hob.Raw           = (UINT8 *)BootloaderParameter;
  MinimalNeededSize = FixedPcdGet32 (PcdSystemMemoryUefiRegionSize);

  ASSERT (Hob.Raw != NULL);
  ASSERT ((UINTN)Hob.HandoffInformationTable->EfiFreeMemoryTop == Hob.HandoffInformationTable->EfiFreeMemoryTop);
  ASSERT ((UINTN)Hob.HandoffInformationTable->EfiMemoryTop == Hob.HandoffInformationTable->EfiMemoryTop);
  ASSERT ((UINTN)Hob.HandoffInformationTable->EfiFreeMemoryBottom == Hob.HandoffInformationTable->EfiFreeMemoryBottom);
  ASSERT ((UINTN)Hob.HandoffInformationTable->EfiMemoryBottom == Hob.HandoffInformationTable->EfiMemoryBottom);

  //
  // Try to find Resource Descriptor HOB that contains Hob range EfiMemoryBottom..EfiMemoryTop
  //
  PhitResourceHob = FindResourceDescriptorByRange (Hob.Raw, Hob.HandoffInformationTable->EfiMemoryBottom, Hob.HandoffInformationTable->EfiMemoryTop);
  if (PhitResourceHob == NULL) {
    //
    // Boot loader's Phit Hob is not in an available Resource Descriptor, find another Resource Descriptor for new Phit Hob
    //
    ResourceHob = FindAnotherHighestBelow4GResourceDescriptor (Hob.Raw, MinimalNeededSize, NULL);
    if (ResourceHob == NULL) {
      return;
    }

    MemoryBottom     = ResourceHob->PhysicalStart + ResourceHob->ResourceLength - MinimalNeededSize;
    FreeMemoryBottom = MemoryBottom;
    FreeMemoryTop    = ResourceHob->PhysicalStart + ResourceHob->ResourceLength;
    MemoryTop        = FreeMemoryTop;
  } else if (PhitResourceHob->PhysicalStart + PhitResourceHob->ResourceLength - Hob.HandoffInformationTable->EfiMemoryTop >= MinimalNeededSize) {
    //
    // New availiable Memory range in new hob is right above memory top in old hob.
    //
    MemoryBottom     = Hob.HandoffInformationTable->EfiFreeMemoryTop;
    FreeMemoryBottom = Hob.HandoffInformationTable->EfiMemoryTop;
    FreeMemoryTop    = FreeMemoryBottom + MinimalNeededSize;
    MemoryTop        = FreeMemoryTop;
  } else if (Hob.HandoffInformationTable->EfiMemoryBottom - PhitResourceHob->PhysicalStart >= MinimalNeededSize) {
    //
    // New availiable Memory range in new hob is right below memory bottom in old hob.
    //
    MemoryBottom     = Hob.HandoffInformationTable->EfiMemoryBottom - MinimalNeededSize;
    FreeMemoryBottom = MemoryBottom;
    FreeMemoryTop    = Hob.HandoffInformationTable->EfiMemoryBottom;
    MemoryTop        = Hob.HandoffInformationTable->EfiMemoryTop;
  } else {
    //
    // In the Resource Descriptor HOB contains boot loader Hob, there is no enough free memory size for payload hob
    // Find another Resource Descriptor Hob
    //
    ResourceHob = FindAnotherHighestBelow4GResourceDescriptor (Hob.Raw, MinimalNeededSize, PhitResourceHob);
    if (ResourceHob == NULL) {
      return;
    }

    MemoryBottom     = ResourceHob->PhysicalStart + ResourceHob->ResourceLength - MinimalNeededSize;
    FreeMemoryBottom = MemoryBottom;
    FreeMemoryTop    = ResourceHob->PhysicalStart + ResourceHob->ResourceLength;
    MemoryTop        = FreeMemoryTop;
  }

  HobInfo           = HobConstructor ((VOID *)(UINTN)MemoryBottom, (VOID *)(UINTN)MemoryTop, (VOID *)(UINTN)FreeMemoryBottom, (VOID *)(UINTN)FreeMemoryTop);
  HobInfo->BootMode = Hob.HandoffInformationTable->BootMode;

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

    Hob.Raw = GET_NEXT_HOB (Hob);
  }

  return;
}

/**
  It will build HOBs based on information from bootloaders.
  @param[in]  NewFdtBase     The pointer to New FdtBase.
  @param[out] DxeFv          The pointer to the DXE FV in memory.
  @retval EFI_SUCCESS        If it completed successfully.
  @retval Others             If it failed to build required HOBs.
**/
EFI_STATUS
FitBuildHobs (
  IN  UINTN                       NewFdtBase,
  OUT EFI_FIRMWARE_VOLUME_HEADER  **DxeFv
  )
{
  UINT8                          *GuidHob;
  UINT32                         FdtSize;
  EFI_HOB_FIRMWARE_VOLUME        *FvHob;
  UNIVERSAL_PAYLOAD_ACPI_TABLE   *AcpiTable;
  ACPI_BOARD_INFO                *AcpiBoardInfo;
  UNIVERSAL_PAYLOAD_DEVICE_TREE  *Fdt;

  if (FixedPcdGetBool (PcdHandOffFdtEnable)) {
    //
    // Back up FDT in Reserved memory region
    //
    if (NewFdtBase != 0) {
      GuidHob = GetFirstGuidHob (&gUniversalPayloadDeviceTreeGuid);
      if (GuidHob != NULL) {
        Fdt =  (UNIVERSAL_PAYLOAD_DEVICE_TREE *)GET_GUID_HOB_DATA (GuidHob);
        if (Fdt != NULL) {
          DEBUG ((DEBUG_INFO, "Update FDT base to reserved memory\n"));
          FdtSize = PcdGet8 (PcdFDTPageSize) * EFI_PAGE_SIZE;
          CopyMem ((VOID *)NewFdtBase, (VOID *)(Fdt->DeviceTreeAddress), FdtSize);
          Fdt->DeviceTreeAddress = NewFdtBase;
        }
      }
    }
  }

  //
  // To create Memory Type Information HOB
  //
  GuidHob = GetFirstGuidHob (&gEfiMemoryTypeInformationGuid);
  if (GuidHob == NULL) {
    BuildGuidDataHob (
      &gEfiMemoryTypeInformationGuid,
      mDefaultMemoryTypeInformation,
      sizeof (mDefaultMemoryTypeInformation)
      );
  }

  //
  // Create guid hob for acpi board information
  //
  GuidHob = GetFirstGuidHob (&gUniversalPayloadAcpiTableGuid);
  if (GuidHob != NULL) {
    AcpiTable = (UNIVERSAL_PAYLOAD_ACPI_TABLE *)GET_GUID_HOB_DATA (GuidHob);
    GuidHob   = GetFirstGuidHob (&gUefiAcpiBoardInfoGuid);
    if (GuidHob == NULL) {
      AcpiBoardInfo = BuildHobFromAcpi ((UINT64)AcpiTable->Rsdp);
      ASSERT (AcpiBoardInfo != NULL);
    }
  }

  //
  // Create an empty FvHob for the DXE FV that contains DXE core.
  //
  BuildFvHob ((EFI_PHYSICAL_ADDRESS)0, 0);

  BuildFitLoadablesFvHob (DxeFv);
  //
  // Update DXE FV information to first fv hob in the hob list, which
  // is the empty FvHob created before.
  //
  FvHob              = GetFirstHob (EFI_HOB_TYPE_FV);
  FvHob->BaseAddress = (EFI_PHYSICAL_ADDRESS)(UINTN)*DxeFv;
  FvHob->Length      = (*DxeFv)->FvLength;
  return EFI_SUCCESS;
}

/**
  Entry point to the C language phase of UEFI payload.
  @param[in]   BootloaderParameter  The starting address of FDT .
  @retval      It will not return if SUCCESS, and return error when passing bootloader parameter.
**/
EFI_STATUS
EFIAPI
FitUplEntryPoint (
  IN UINTN  BootloaderParameter
  )
{
  EFI_STATUS                  Status;
  PHYSICAL_ADDRESS            DxeCoreEntryPoint;
  EFI_PEI_HOB_POINTERS        Hob;
  EFI_FIRMWARE_VOLUME_HEADER  *DxeFv;

 #if FixedPcdGetBool (PcdHandOffFdtEnable) == 1
  PHYSICAL_ADDRESS  HobListPtr;
  VOID              *FdtBase;
 #endif
  VOID  *FdtBaseResvd;

  if (FixedPcdGetBool (PcdHandOffFdtEnable)) {
    mHobList = (VOID *)NULL;
  } else {
    mHobList = (VOID *)BootloaderParameter;
  }

  DxeFv        = NULL;
  FdtBaseResvd = 0;
  // Call constructor for all libraries
  ProcessLibraryConstructorList ();

  DEBUG ((DEBUG_INFO, "Entering Universal Payload...\n"));
  DEBUG ((DEBUG_INFO, "sizeof(UINTN) = 0x%x\n", sizeof (UINTN)));
  DEBUG ((DEBUG_INFO, "BootloaderParameter = 0x%x\n", BootloaderParameter));

  DEBUG ((DEBUG_INFO, "Start init Hobs...\n"));
 #if FixedPcdGetBool (PcdHandOffFdtEnable) == 1
  HobListPtr = UplInitHob ((VOID *)BootloaderParameter);

  //
  // Found hob list node
  //
  if (HobListPtr != 0) {
    FdtBase = (VOID *)BootloaderParameter;
    if (FdtCheckHeader (FdtBase) == 0) {
      CustomFdtNodeParser ((VOID *)FdtBase, (VOID *)HobListPtr);
      FdtBaseResvd = PayloadAllocatePages (PcdGet8 (PcdFDTPageSize), EfiReservedMemoryType);
    }
  }

 #else
  CreatNewHobForHoblist (BootloaderParameter);
 #endif

  // Build HOB based on information from Bootloader
  Status = FitBuildHobs ((UINTN)FdtBaseResvd, &DxeFv);

  // Call constructor for all libraries again since hobs were built
  ProcessLibraryConstructorList ();

  DEBUG_CODE (
    //
    // Dump the Hobs from boot loader
    //
    PrintHob (mHobList);
    );

  FixUpPcdDatabase (DxeFv);
  Status = UniversalLoadDxeCore (DxeFv, &DxeCoreEntryPoint);
  ASSERT_EFI_ERROR (Status);

  Hob.HandoffInformationTable = (EFI_HOB_HANDOFF_INFO_TABLE *)GetFirstHob (EFI_HOB_TYPE_HANDOFF);
  HandOffToDxeCore (DxeCoreEntryPoint, Hob);

  // Should not get here
  CpuDeadLoop ();
  return EFI_SUCCESS;
}
