/** @file
  Prints all the HOBs.

  Copyright (c) 2024, Intel Corporation. All rights reserved.<BR>

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

#include <Uefi.h>
#include <Pi/PiMultiPhase.h>
#include <Library/BaseMemoryLib.h>
#include <Library/DebugLib.h>
#include <Library/HobLib.h>
#include <Library/HobPrintLib.h>
#include <Guid/MemoryTypeInformation.h>
#include <Guid/MemoryAllocationHob.h>

#define ROW_LIMITER  16

typedef struct {
  UINT16               Type;
  CHAR8                *Name;
  HOB_PRINT_HANDLER    PrintHandler;
} HOB_PRINT_HANDLER_TABLE;

CHAR8  *mMemoryTypeStr[] = {
  "EfiReservedMemoryType",
  "EfiLoaderCode",
  "EfiLoaderData",
  "EfiBootServicesCode",
  "EfiBootServicesData",
  "EfiRuntimeServicesCode",
  "EfiRuntimeServicesData",
  "EfiConventionalMemory",
  "EfiUnusableMemory",
  "EfiACPIReclaimMemory",
  "EfiACPIMemoryNVS",
  "EfiMemoryMappedIO",
  "EfiMemoryMappedIOPortSpace",
  "EfiPalCode",
  "EfiPersistentMemory",
  "EfiMaxMemoryType"
};

CHAR8  *mResource_Type_List[] = {
  "EFI_RESOURCE_SYSTEM_MEMORY          ", // 0x00000000
  "EFI_RESOURCE_MEMORY_MAPPED_IO       ", // 0x00000001
  "EFI_RESOURCE_IO                     ", // 0x00000002
  "EFI_RESOURCE_FIRMWARE_DEVICE        ", // 0x00000003
  "EFI_RESOURCE_MEMORY_MAPPED_IO_PORT  ", // 0x00000004
  "EFI_RESOURCE_MEMORY_RESERVED        ", // 0x00000005
  "EFI_RESOURCE_IO_RESERVED            ", // 0x00000006
  "EFI_RESOURCE_MAX_MEMORY_TYPE        "  // 0x00000007
};

/**
  Print the Hex value of a given range.

  @param[in]  ErrorLevel     Error Level to print the Hex value.
  @param[in]  DataStart      A pointer to the start of data to be printed.
  @param[in]  DataSize       The length of the data to be printed.

  @retval EFI_SUCCESS        If it completed successfully.
**/
EFI_STATUS
PrintHex (
  IN  UINT32  ErrorLevel,
  IN  UINT8   *DataStart,
  IN  UINT16  DataSize
  )
{
  UINTN  Index1;
  UINTN  Index2;
  UINT8  *StartAddr;

  StartAddr = DataStart;
  for (Index1 = 0; Index1 * ROW_LIMITER < DataSize; Index1++) {
    DEBUG ((ErrorLevel, "   0x%04p:", (DataStart - StartAddr)));
    for (Index2 = 0; (Index2 < ROW_LIMITER) && (Index1 * ROW_LIMITER + Index2 < DataSize); Index2++) {
      DEBUG ((ErrorLevel, " %02x", *DataStart));
      DataStart++;
    }

    DEBUG ((ErrorLevel, "\n"));
  }

  return EFI_SUCCESS;
}

/**
  Print the Hex value of the Invalid HOB.

  @param[in]  HobStart       A pointer to the Invalid HOB.
  @param[in]  HobLength      The length in bytes of the Invalid HOB.

  @retval EFI_SUCCESS        If it completed successfully.
**/
EFI_STATUS
PrintInvalidHob (
  IN  VOID    *HobStart,
  IN  UINT16  HobLength
  )
{
  DEBUG ((DEBUG_ERROR, "   Invalid HOB. Full hex dump in below:\n"));
  PrintHex (DEBUG_ERROR, HobStart, HobLength);
  return RETURN_INVALID_PARAMETER;
}

/**
  Print the information in HandOffHob.

  @param[in]  HobStart       A pointer to the HOB of type EFI_HOB_TYPE_HANDOFF.
  @param[in]  HobLength      The length in bytes of HOB of type EFI_HOB_TYPE_HANDOFF.
  @retval EFI_SUCCESS        If it completed successfully.
**/
EFI_STATUS
PrintHandOffHob (
  IN  VOID    *HobStart,
  IN  UINT16  HobLength
  )
{
  EFI_PEI_HOB_POINTERS  Hob;

  Hob.Raw = (UINT8 *)HobStart;
  if (HobLength < sizeof (*Hob.HandoffInformationTable)) {
    return PrintInvalidHob (HobStart, HobLength);
  }

  DEBUG ((DEBUG_INFO, "   BootMode            = 0x%x\n", Hob.HandoffInformationTable->BootMode));
  DEBUG ((DEBUG_INFO, "   EfiMemoryTop        = 0x%lx\n", Hob.HandoffInformationTable->EfiMemoryTop));
  DEBUG ((DEBUG_INFO, "   EfiMemoryBottom     = 0x%lx\n", Hob.HandoffInformationTable->EfiMemoryBottom));
  DEBUG ((DEBUG_INFO, "   EfiFreeMemoryTop    = 0x%lx\n", Hob.HandoffInformationTable->EfiFreeMemoryTop));
  DEBUG ((DEBUG_INFO, "   EfiFreeMemoryBottom = 0x%lx\n", Hob.HandoffInformationTable->EfiFreeMemoryBottom));
  DEBUG ((DEBUG_INFO, "   EfiEndOfHobList     = 0x%lx\n", Hob.HandoffInformationTable->EfiEndOfHobList));
  return EFI_SUCCESS;
}

/**
  Print the information in Memory Allocation Hob.
  @param[in] HobStart        A pointer to the HOB of type EFI_HOB_TYPE_MEMORY_ALLOCATION.
  @param[in] HobLength       The length in bytes of HOB of type EFI_HOB_TYPE_MEMORY_ALLOCATION.
  @retval EFI_SUCCESS        If it completed successfully.
**/
EFI_STATUS
PrintMemoryAllocationHob (
  IN  VOID    *HobStart,
  IN  UINT16  HobLength
  )
{
  EFI_PEI_HOB_POINTERS  Hob;

  Hob.Raw = (UINT8 *)HobStart;

  if (CompareGuid (&Hob.MemoryAllocation->AllocDescriptor.Name, &gEfiHobMemoryAllocStackGuid)) {
    if (HobLength < sizeof (*Hob.MemoryAllocationStack)) {
      return PrintInvalidHob (HobStart, HobLength);
    }

    DEBUG ((DEBUG_INFO, "   Type              = EFI_HOB_MEMORY_ALLOCATION_STACK\n"));
  } else if (CompareGuid (&Hob.MemoryAllocation->AllocDescriptor.Name, &gEfiHobMemoryAllocBspStoreGuid)) {
    if (HobLength < sizeof (*Hob.MemoryAllocationBspStore)) {
      return PrintInvalidHob (HobStart, HobLength);
    }

    DEBUG ((DEBUG_INFO, "   Type              = EFI_HOB_MEMORY_ALLOCATION_BSP_STORE\n"));
  } else if (CompareGuid (&Hob.MemoryAllocation->AllocDescriptor.Name, &gEfiHobMemoryAllocModuleGuid)) {
    if (HobLength < sizeof (*Hob.MemoryAllocationModule)) {
      return PrintInvalidHob (HobStart, HobLength);
    }

    DEBUG ((DEBUG_INFO, "   Type              = EFI_HOB_MEMORY_ALLOCATION_MODULE\n"));
    DEBUG ((DEBUG_INFO, "   ModuleName        = %g\n", &Hob.MemoryAllocationModule->ModuleName));
    DEBUG ((DEBUG_INFO, "   EntryPoint        = 0x%lx\n", Hob.MemoryAllocationModule->EntryPoint));
  } else {
    if (HobLength < sizeof (*Hob.MemoryAllocation)) {
      return PrintInvalidHob (HobStart, HobLength);
    }

    DEBUG ((DEBUG_INFO, "   Type              = EFI_HOB_TYPE_MEMORY_ALLOCATION\n"));
  }

  DEBUG ((DEBUG_INFO, "   Name              = %g\n", &Hob.MemoryAllocationStack->AllocDescriptor.Name));
  DEBUG ((DEBUG_INFO, "   MemoryBaseAddress = 0x%lx\n", Hob.MemoryAllocationStack->AllocDescriptor.MemoryBaseAddress));
  DEBUG ((DEBUG_INFO, "   MemoryLength      = 0x%lx\n", Hob.MemoryAllocationStack->AllocDescriptor.MemoryLength));
  DEBUG ((DEBUG_INFO, "   MemoryType        = %a \n", mMemoryTypeStr[Hob.MemoryAllocationStack->AllocDescriptor.MemoryType]));
  return EFI_SUCCESS;
}

/**
  Print the information in Resource Discriptor Hob.
  @param[in]  HobStart       A pointer to HOB of type EFI_HOB_TYPE_RESOURCE_DESCRIPTOR.
  @param[in]  HobLength      The Length in bytes of HOB of type EFI_HOB_TYPE_RESOURCE_DESCRIPTOR.
  @retval EFI_SUCCESS        If it completed successfully.
**/
EFI_STATUS
PrintResourceDiscriptorHob (
  IN  VOID    *HobStart,
  IN  UINT16  HobLength
  )
{
  EFI_PEI_HOB_POINTERS  Hob;

  Hob.Raw = (UINT8 *)HobStart;
  ASSERT (HobLength >= sizeof (*Hob.ResourceDescriptor));

  DEBUG ((DEBUG_INFO, "   ResourceType      = %a\n", mResource_Type_List[Hob.ResourceDescriptor->ResourceType]));
  if (!IsZeroGuid (&Hob.ResourceDescriptor->Owner)) {
    DEBUG ((DEBUG_INFO, "   Owner             = %g\n", &Hob.ResourceDescriptor->Owner));
  }

  DEBUG ((DEBUG_INFO, "   ResourceAttribute = 0x%x\n", Hob.ResourceDescriptor->ResourceAttribute));
  DEBUG ((DEBUG_INFO, "   PhysicalStart     = 0x%lx\n", Hob.ResourceDescriptor->PhysicalStart));
  DEBUG ((DEBUG_INFO, "   ResourceLength    = 0x%lx\n", Hob.ResourceDescriptor->ResourceLength));
  return EFI_SUCCESS;
}

/**
  Print the Guid Hob using related print handle function.
  @param[in] HobStart        A pointer to the HOB of type EFI_HOB_TYPE_GUID_EXTENSION.
  @param[in] HobLength       The length in bytes of the HOB of type EFI_HOB_TYPE_GUID_EXTENSION.
  @retval EFI_SUCCESS        If it completed successfully.
**/
EFI_STATUS
PrintGuidHob (
  IN  VOID    *HobStart,
  IN  UINT16  HobLength
  )
{
  EFI_PEI_HOB_POINTERS  Hob;
  UINT16                DataLength;

  Hob.Raw = (UINT8 *)HobStart;
  ASSERT (HobLength >= sizeof (*Hob.Guid));

  DataLength = GET_GUID_HOB_DATA_SIZE (Hob.Raw);

  DEBUG ((DEBUG_INFO, "   Name       = %g\n", &Hob.Guid->Name));
  DEBUG ((DEBUG_INFO, "   DataLength = 0x%x\n", DataLength));
  PrintHex (DEBUG_VERBOSE, GET_GUID_HOB_DATA (Hob.Raw), DataLength);
  return EFI_SUCCESS;
}

/**
  Print the information in FV Hob.
  @param[in] HobStart        A pointer to the HOB of type EFI_HOB_TYPE_FV.
  @param[in] HobLength       The length in bytes of the HOB of type EFI_HOB_TYPE_FV.
  @retval EFI_SUCCESS        If it completed successfully.
**/
EFI_STATUS
PrintFvHob (
  IN  VOID    *HobStart,
  IN  UINT16  HobLength
  )
{
  EFI_PEI_HOB_POINTERS  Hob;

  Hob.Raw = (UINT8 *)HobStart;
  ASSERT (HobLength >= sizeof (*Hob.FirmwareVolume));

  DEBUG ((DEBUG_INFO, "   BaseAddress = 0x%lx\n", Hob.FirmwareVolume->BaseAddress));
  DEBUG ((DEBUG_INFO, "   Length      = 0x%lx\n", Hob.FirmwareVolume->Length));
  return EFI_SUCCESS;
}

/**
  Print the information in Cpu Hob.
  @param[in] HobStart        A pointer to the HOB of type EFI_HOB_TYPE_CPU.
  @param[in] HobLength       The length in bytes of the HOB of type EFI_HOB_TYPE_CPU.
  @retval EFI_SUCCESS        If it completed successfully.
**/
EFI_STATUS
PrintCpuHob (
  IN  VOID    *HobStart,
  IN  UINT16  HobLength
  )
{
  EFI_PEI_HOB_POINTERS  Hob;

  Hob.Raw = (UINT8 *)HobStart;
  ASSERT (HobLength >= sizeof (*Hob.Cpu));

  DEBUG ((DEBUG_INFO, "   SizeOfMemorySpace = 0x%lx\n", Hob.Cpu->SizeOfMemorySpace));
  DEBUG ((DEBUG_INFO, "   SizeOfIoSpace     = 0x%lx\n", Hob.Cpu->SizeOfIoSpace));
  return EFI_SUCCESS;
}

/**
  Print the information in MemoryPoolHob.
  @param[in] HobStart        A pointer to the HOB of type EFI_HOB_TYPE_MEMORY_POOL.
  @param[in] HobLength       The length in bytes of the HOB of type EFI_HOB_TYPE_MEMORY_POOL.
  @retval EFI_SUCCESS        If it completed successfully.
**/
EFI_STATUS
PrintMemoryPoolHob (
  IN  VOID    *HobStart,
  IN  UINT16  HobLength
  )
{
  EFI_PEI_HOB_POINTERS  Hob;
  UINT16                AllocationSize;

  Hob.Raw = (UINT8 *)HobStart;
  ASSERT (HobLength >= sizeof (*Hob.Pool));

  AllocationSize = HobLength - sizeof (EFI_HOB_GENERIC_HEADER);
  DEBUG ((DEBUG_INFO, "   AllocationSize    = 0x%lx\n", AllocationSize));

  PrintHex (DEBUG_VERBOSE, Hob.Raw + sizeof (EFI_HOB_GENERIC_HEADER), AllocationSize);

  return EFI_SUCCESS;
}

/**
  Print the information in Fv2Hob.
  @param[in] HobStart        A pointer to the HOB of type EFI_HOB_TYPE_FV2.
  @param[in] HobLength       The length in bytes of the HOB of type EFI_HOB_TYPE_FV2.
  @retval EFI_SUCCESS        If it completed successfully.
**/
EFI_STATUS
PrintFv2Hob (
  IN  VOID    *HobStart,
  IN  UINT16  HobLength
  )
{
  EFI_PEI_HOB_POINTERS  Hob;

  Hob.Raw = (UINT8 *)HobStart;
  ASSERT (HobLength >= sizeof (*Hob.FirmwareVolume2));

  DEBUG ((DEBUG_INFO, "   BaseAddress = 0x%lx\n", Hob.FirmwareVolume2->BaseAddress));
  DEBUG ((DEBUG_INFO, "   Length      = 0x%lx\n", Hob.FirmwareVolume2->Length));
  DEBUG ((DEBUG_INFO, "   FvName      = %g\n", &Hob.FirmwareVolume2->FvName));
  DEBUG ((DEBUG_INFO, "   FileName    = %g\n", &Hob.FirmwareVolume2->FileName));
  return EFI_SUCCESS;
}

/**
  Print the information in Capsule Hob.
  @param[in] HobStart        A pointer to the HOB of type EFI_HOB_TYPE_UEFI_CAPSULE.
  @param[in] HobLength       The length in bytes of the HOB of type EFI_HOB_TYPE_UEFI_CAPSULE.
  @retval EFI_SUCCESS        If it completed successfully.
**/
EFI_STATUS
PrintCapsuleHob (
  IN  VOID    *HobStart,
  IN  UINT16  HobLength
  )
{
  EFI_PEI_HOB_POINTERS  Hob;

  Hob.Raw = (UINT8 *)HobStart;
  ASSERT (HobLength >= sizeof (*Hob.Capsule));

  DEBUG ((DEBUG_INFO, "   BaseAddress = 0x%lx\n", Hob.Capsule->BaseAddress));
  DEBUG ((DEBUG_INFO, "   Length = 0x%lx\n", Hob.Capsule->Length));
  return EFI_SUCCESS;
}

/**
  Print the information in Fv3 Hob.
  @param[in] HobStart        A pointer to the HOB of type EFI_HOB_TYPE_FV3.
  @param[in] HobLength       The length in bytes of the HOB of type EFI_HOB_TYPE_FV3.
  @retval EFI_SUCCESS        If it completed successfully.
**/
EFI_STATUS
PrintFv3Hob (
  IN  VOID    *HobStart,
  IN  UINT16  HobLength
  )
{
  EFI_PEI_HOB_POINTERS  Hob;

  Hob.Raw = (UINT8 *)HobStart;
  ASSERT (HobLength >= sizeof (*Hob.FirmwareVolume3));

  DEBUG ((DEBUG_INFO, "   BaseAddress          = 0x%lx\n", Hob.FirmwareVolume3->BaseAddress));
  DEBUG ((DEBUG_INFO, "   Length               = 0x%lx\n", Hob.FirmwareVolume3->Length));
  DEBUG ((DEBUG_INFO, "   AuthenticationStatus = 0x%x\n", Hob.FirmwareVolume3->AuthenticationStatus));
  DEBUG ((DEBUG_INFO, "   ExtractedFv          = %a\n", (Hob.FirmwareVolume3->ExtractedFv ? "True" : "False")));
  DEBUG ((DEBUG_INFO, "   FvName               = %g\n", &Hob.FirmwareVolume3->FvName));
  DEBUG ((DEBUG_INFO, "   FileName             = %g\n", &Hob.FirmwareVolume3->FileName));
  return EFI_SUCCESS;
}

//
// Mapping table from Hob type to Hob print function.
//
HOB_PRINT_HANDLER_TABLE  mHobHandles[] = {
  { EFI_HOB_TYPE_HANDOFF,             "EFI_HOB_TYPE_HANDOFF",             PrintHandOffHob            },
  { EFI_HOB_TYPE_MEMORY_ALLOCATION,   "EFI_HOB_TYPE_MEMORY_ALLOCATION",   PrintMemoryAllocationHob   },
  { EFI_HOB_TYPE_RESOURCE_DESCRIPTOR, "EFI_HOB_TYPE_RESOURCE_DESCRIPTOR", PrintResourceDiscriptorHob },
  { EFI_HOB_TYPE_GUID_EXTENSION,      "EFI_HOB_TYPE_GUID_EXTENSION",      PrintGuidHob               },
  { EFI_HOB_TYPE_FV,                  "EFI_HOB_TYPE_FV",                  PrintFvHob                 },
  { EFI_HOB_TYPE_CPU,                 "EFI_HOB_TYPE_CPU",                 PrintCpuHob                },
  { EFI_HOB_TYPE_MEMORY_POOL,         "EFI_HOB_TYPE_MEMORY_POOL",         PrintMemoryPoolHob         },
  { EFI_HOB_TYPE_FV2,                 "EFI_HOB_TYPE_FV2",                 PrintFv2Hob                },
  { EFI_HOB_TYPE_UEFI_CAPSULE,        "EFI_HOB_TYPE_UEFI_CAPSULE",        PrintCapsuleHob            },
  { EFI_HOB_TYPE_FV3,                 "EFI_HOB_TYPE_FV3",                 PrintFv3Hob                }
};

/**
  Print all HOBs info from the HOB list.

  @param[in] HobStart       A pointer to the HOB list.
  @param[in] PrintHandler   A custom handler to print HOB info.

**/
VOID
EFIAPI
PrintHobList (
  IN CONST VOID         *HobStart,
  IN HOB_PRINT_HANDLER  PrintHandler
  )
{
  EFI_STATUS            Status;
  EFI_PEI_HOB_POINTERS  Hob;
  UINTN                 Count;
  UINTN                 Index;

  ASSERT (HobStart != NULL);

  Hob.Raw = (UINT8 *)HobStart;
  DEBUG ((DEBUG_INFO, "Print all Hob information from Hob 0x%p\n", Hob.Raw));

  Status = EFI_SUCCESS;
  Count  = 0;
  //
  // Parse the HOB list to see which type it is, and print the information.
  //
  while (!END_OF_HOB_LIST (Hob)) {
    //
    // Print HOB generic information
    //
    for (Index = 0; Index < ARRAY_SIZE (mHobHandles); Index++) {
      if (Hob.Header->HobType == mHobHandles[Index].Type) {
        DEBUG ((DEBUG_INFO, "HOB[%d]: Type = %a, Offset = 0x%p, Length = 0x%x\n", Count, mHobHandles[Index].Name, (Hob.Raw - (UINT8 *)HobStart), Hob.Header->HobLength));
        break;
      }
    }

    if (Index == ARRAY_SIZE (mHobHandles)) {
      DEBUG ((DEBUG_INFO, "HOB[%d]: Type = %d, Offset = 0x%p, Length = 0x%x\n", Count, Hob.Header->HobType, (Hob.Raw - (UINT8 *)HobStart), Hob.Header->HobLength));
    }

    //
    // Process custom HOB print handler first
    //
    if (PrintHandler != NULL) {
      Status = PrintHandler (Hob.Raw, Hob.Header->HobLength);
    }

    //
    // Process internal HOB print handler
    //
    if ((PrintHandler == NULL) || EFI_ERROR (Status)) {
      if (Index < ARRAY_SIZE (mHobHandles)) {
        mHobHandles[Index].PrintHandler (Hob.Raw, Hob.Header->HobLength);
      } else {
        DEBUG ((DEBUG_INFO, "   Unkown Hob type, full hex dump in below:\n"));
        PrintHex (DEBUG_INFO, Hob.Raw, Hob.Header->HobLength);
      }
    }

    Count++;
    Hob.Raw = GET_NEXT_HOB (Hob);
  }

  DEBUG ((DEBUG_INFO, "There are totally %d Hobs, the End Hob address is %p\n", Count, Hob.Raw));
}
