/** @file

  Copyright (c) 2022, Arm Limited. All rights reserved.

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

**/

#include <PiPei.h>

#include <Base.h>
#include <libfdt.h>
#include <Library/ArmLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/DebugLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/PcdLib.h>

#include <Library/PrePiLib.h>

#include "CloudHvVirtMemInfoLib.h"

CLOUDHV_MEM_NODE_INFO  CloudHvMemNode[CLOUDHV_MAX_MEM_NODE_NUM];

/**
  Get all of memory nodes info from DT. Store all of them into
  CloudHvMemNode which will be consumed by ArmVirtGetMemoryMap.

  @retval RETURN_SUCCESS   Success.
  @retval EFI_NOT_FOUND    DT or the first memory node not found.

**/
RETURN_STATUS
EFIAPI
CloudHvVirtMemInfoPeiLibConstructor (
  VOID
  )
{
  VOID                         *DeviceTreeBase;
  EFI_RESOURCE_ATTRIBUTE_TYPE  ResourceAttributes;
  INT32                        Node, Prev;
  UINT64                       FirMemNodeBase, FirMemNodeSize;
  UINT64                       CurBase, MemBase;
  UINT64                       CurSize;
  CONST CHAR8                  *Type;
  INT32                        Len;
  CONST UINT64                 *RegProp;
  RETURN_STATUS                PcdStatus;
  UINT8                        Index;

  ZeroMem (CloudHvMemNode, sizeof (CloudHvMemNode));

  FirMemNodeBase     = 0;
  FirMemNodeSize     = 0;
  Index              = 0;
  MemBase            = FixedPcdGet64 (PcdSystemMemoryBase);
  ResourceAttributes = (
                        EFI_RESOURCE_ATTRIBUTE_PRESENT |
                        EFI_RESOURCE_ATTRIBUTE_INITIALIZED |
                        EFI_RESOURCE_ATTRIBUTE_WRITE_BACK_CACHEABLE |
                        EFI_RESOURCE_ATTRIBUTE_TESTED
                        );
  DeviceTreeBase = (VOID *)(UINTN)PcdGet64 (PcdDeviceTreeInitialBaseAddress);
  if (DeviceTreeBase == NULL) {
    return EFI_NOT_FOUND;
  }

  //
  // Make sure we have a valid device tree blob
  //
  if (fdt_check_header (DeviceTreeBase) != 0) {
    return EFI_NOT_FOUND;
  }

  //
  // Look for the lowest memory node
  //
  for (Prev = 0; ; Prev = Node) {
    Node = fdt_next_node (DeviceTreeBase, Prev, NULL);
    if (Node < 0) {
      break;
    }

    //
    // Check for memory node
    //
    Type = fdt_getprop (DeviceTreeBase, Node, "device_type", &Len);
    if ((Type != 0) && (AsciiStrnCmp (Type, "memory", Len) == 0)) {
      //
      // Get the 'reg' property of this node. For now, we will assume
      // two 8 byte quantities for base and size, respectively.
      //
      RegProp = fdt_getprop (DeviceTreeBase, Node, "reg", &Len);
      if ((RegProp != 0) && (Len == (2 * sizeof (UINT64)))) {
        CurBase = fdt64_to_cpu (ReadUnaligned64 (RegProp));
        CurSize = fdt64_to_cpu (ReadUnaligned64 (RegProp + 1));

        DEBUG ((
          DEBUG_INFO,
          "%a: System RAM @ 0x%lx - 0x%lx\n",
          __func__,
          CurBase,
          CurBase + CurSize - 1
          ));

        // We should build Hob seperately for the memory node except the first one
        if (CurBase != MemBase) {
          BuildResourceDescriptorHob (
            EFI_RESOURCE_SYSTEM_MEMORY,
            ResourceAttributes,
            CurBase,
            CurSize
            );
        } else {
          FirMemNodeBase = CurBase;
          FirMemNodeSize = CurSize;
        }

        CloudHvMemNode[Index].Base = CurBase;
        CloudHvMemNode[Index].Size = CurSize;
        Index++;

        if (Index >= CLOUDHV_MAX_MEM_NODE_NUM) {
          DEBUG ((
            DEBUG_WARN,
            "%a: memory node larger than %d will not be included into Memory System\n",
            __func__,
            CLOUDHV_MAX_MEM_NODE_NUM
            ));
          break;
        }
      } else {
        DEBUG ((
          DEBUG_ERROR,
          "%a: Failed to parse FDT memory node\n",
          __func__
          ));
      }
    }
  }

  //
  // Make sure the start of DRAM matches our expectation
  //
  if (FixedPcdGet64 (PcdSystemMemoryBase) != FirMemNodeBase) {
    return EFI_NOT_FOUND;
  }

  PcdStatus = PcdSet64S (PcdSystemMemorySize, FirMemNodeSize);
  ASSERT_RETURN_ERROR (PcdStatus);
  ASSERT (
    (((UINT64)PcdGet64 (PcdFdBaseAddress) +
      (UINT64)PcdGet32 (PcdFdSize)) <= FirMemNodeBase) ||
    ((UINT64)PcdGet64 (PcdFdBaseAddress) >= (FirMemNodeBase + FirMemNodeSize))
    );

  return RETURN_SUCCESS;
}

/**
  Return the Virtual Memory Map of your platform

  This Virtual Memory Map is used by MemoryInitPei Module to initialize the MMU
  on your platform.

  @param[out]   VirtualMemoryMap    Array of ARM_MEMORY_REGION_DESCRIPTOR
                                    describing a Physical-to-Virtual Memory
                                    mapping. This array must be ended by a
                                    zero-filled entry. The allocated memory
                                    will not be freed.

**/
VOID
ArmVirtGetMemoryMap (
  OUT ARM_MEMORY_REGION_DESCRIPTOR  **VirtualMemoryMap
  )
{
  ARM_MEMORY_REGION_DESCRIPTOR  *VirtualMemoryTable;
  UINT8                         Index, MemNodeIndex;

  ASSERT (VirtualMemoryMap != NULL);

  VirtualMemoryTable = AllocatePool (
                         sizeof (ARM_MEMORY_REGION_DESCRIPTOR) *
                         MAX_VIRTUAL_MEMORY_MAP_DESCRIPTORS
                         );

  if (VirtualMemoryTable == NULL) {
    DEBUG ((DEBUG_ERROR, "%a: Error: Failed AllocatePool()\n", __func__));
    return;
  }

  Index        = 0;
  MemNodeIndex = 0;
  // System DRAM
  while ((MemNodeIndex < CLOUDHV_MAX_MEM_NODE_NUM) && (CloudHvMemNode[MemNodeIndex].Size != 0)) {
    VirtualMemoryTable[Index].PhysicalBase = CloudHvMemNode[MemNodeIndex].Base;
    VirtualMemoryTable[Index].VirtualBase  = CloudHvMemNode[MemNodeIndex].Base;
    VirtualMemoryTable[Index].Length       = CloudHvMemNode[MemNodeIndex].Size;
    VirtualMemoryTable[Index].Attributes   = ARM_MEMORY_REGION_ATTRIBUTE_WRITE_BACK;

    DEBUG ((
      DEBUG_INFO,
      "%a: Dumping System DRAM Memory Node%d Map:\n"
      "\tPhysicalBase: 0x%lX\n"
      "\tVirtualBase: 0x%lX\n"
      "\tLength: 0x%lX\n",
      __func__,
      MemNodeIndex,
      VirtualMemoryTable[Index].PhysicalBase,
      VirtualMemoryTable[Index].VirtualBase,
      VirtualMemoryTable[Index].Length
      ));
    Index++;
    MemNodeIndex++;
  }

  // Memory mapped peripherals (UART, RTC, GIC, virtio-mmio, etc)
  VirtualMemoryTable[Index].PhysicalBase = MACH_VIRT_PERIPH_BASE;
  VirtualMemoryTable[Index].VirtualBase  = MACH_VIRT_PERIPH_BASE;
  VirtualMemoryTable[Index].Length       = MACH_VIRT_PERIPH_SIZE;
  VirtualMemoryTable[Index].Attributes   = ARM_MEMORY_REGION_ATTRIBUTE_DEVICE;
  Index++;

  // Map the FV region as normal executable memory
  VirtualMemoryTable[Index].PhysicalBase = PcdGet64 (PcdFvBaseAddress);
  VirtualMemoryTable[Index].VirtualBase  = VirtualMemoryTable[Index].PhysicalBase;
  VirtualMemoryTable[Index].Length       = FixedPcdGet32 (PcdFvSize);
  VirtualMemoryTable[Index].Attributes   = ARM_MEMORY_REGION_ATTRIBUTE_WRITE_BACK;
  Index++;

  // Memory mapped for 32bit device (like TPM)
  VirtualMemoryTable[Index].PhysicalBase = TOP_32BIT_DEVICE_BASE;
  VirtualMemoryTable[Index].VirtualBase  = TOP_32BIT_DEVICE_BASE;
  VirtualMemoryTable[Index].Length       = TOP_32BIT_DEVICE_SIZE;
  VirtualMemoryTable[Index].Attributes   = ARM_MEMORY_REGION_ATTRIBUTE_DEVICE;
  Index++;

  // End of Table
  ZeroMem (&VirtualMemoryTable[Index], sizeof (ARM_MEMORY_REGION_DESCRIPTOR));

  *VirtualMemoryMap = VirtualMemoryTable;
}
