/** @file
*
*  Copyright (c) 2011-2015, ARM Limited. All rights reserved.
*  Copyright (c) 2017, Linaro, Ltd. All rights reserved.
*
*  SPDX-License-Identifier: BSD-2-Clause-Patent
*
**/

#include <PiPei.h>

#include <Library/ArmLib.h>
#include <Library/ArmMmuLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/DebugLib.h>
#include <Library/HobLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/PeiServicesLib.h>
#include <Library/PeiServicesTablePointerLib.h>

#include <Platform/MemoryMap.h>
#include <Platform/Pcie.h>

#include <Ppi/Capsule.h>
#include <Ppi/DramInfo.h>

#define ARM_MEMORY_REGION(Base, Size) \
  { (Base), (Base), (Size), ARM_MEMORY_REGION_ATTRIBUTE_WRITE_BACK }

#define ARM_UNCACHED_REGION(Base, Size) \
  { (Base), (Base), (Size), ARM_MEMORY_REGION_ATTRIBUTE_UNCACHED_UNBUFFERED }

#define ARM_DEVICE_REGION(Base, Size) \
  { (Base), (Base), (Size), ARM_MEMORY_REGION_ATTRIBUTE_DEVICE }

#define ARM_CACHED_DEVICE_REGION(Base, Size) \
  { (Base), (Base), (Size), ARM_MEMORY_REGION_ATTRIBUTE_WRITE_BACK_NONSHAREABLE }

VOID
BuildMemoryTypeInformationHob (
  VOID
  );

STATIC CONST EFI_RESOURCE_ATTRIBUTE_TYPE mDramResourceAttributes =
  EFI_RESOURCE_ATTRIBUTE_PRESENT |
  EFI_RESOURCE_ATTRIBUTE_INITIALIZED |
  EFI_RESOURCE_ATTRIBUTE_WRITE_COMBINEABLE |
  EFI_RESOURCE_ATTRIBUTE_WRITE_THROUGH_CACHEABLE |
  EFI_RESOURCE_ATTRIBUTE_WRITE_BACK_CACHEABLE |
  EFI_RESOURCE_ATTRIBUTE_TESTED;

STATIC CONST ARM_MEMORY_REGION_DESCRIPTOR mVirtualMemoryTable[] = {
  // Memory mapped SPI NOR flash
  ARM_CACHED_DEVICE_REGION (FixedPcdGet64 (PcdFdBaseAddress),
                            FixedPcdGet32 (PcdFdSize)),

  // SynQuacer OnChip peripherals
  ARM_DEVICE_REGION (SYNQUACER_PERIPHERALS_BASE,
                     SYNQUACER_PERIPHERALS_SZ),

  // SynQuacer OnChip non-secure SRAM
  ARM_CACHED_DEVICE_REGION (SYNQUACER_NON_SECURE_SRAM_BASE,
                            SYNQUACER_NON_SECURE_SRAM_SZ),

  // SynQuacer GIC-500
  ARM_DEVICE_REGION (SYNQUACER_GIC500_DIST_BASE, SYNQUACER_GIC500_DIST_SIZE),
  ARM_DEVICE_REGION (SYNQUACER_GIC500_RDIST_BASE, SYNQUACER_GIC500_RDIST_SIZE),

  // SynQuacer eMMC(SDH30)
  ARM_DEVICE_REGION (SYNQUACER_EMMC_BASE, SYNQUACER_EMMC_BASE_SZ),

  // SynQuacer GPIO block
  ARM_DEVICE_REGION (SYNQUACER_GPIO_BASE, SYNQUACER_GPIO_SIZE),

  // SynQuacer EEPROM - could point to NOR flash as well
  ARM_CACHED_DEVICE_REGION (FixedPcdGet32 (PcdNetsecEepromBase),
                            SYNQUACER_EEPROM_BASE_SZ),

  // SynQuacer I2C
  ARM_DEVICE_REGION (SYNQUACER_I2C0_BASE, SYNQUACER_I2C0_SIZE),
  ARM_DEVICE_REGION (SYNQUACER_I2C1_BASE, SYNQUACER_I2C1_SIZE),

  // SynQuacer NETSEC
  ARM_DEVICE_REGION (SYNQUACER_NETSEC1_BASE, SYNQUACER_NETSEC1_BASE_SZ),

  // PCIe control registers
  ARM_DEVICE_REGION (SYNQUACER_PCIE_BASE, SYNQUACER_PCIE_SIZE),

  // PCIe config space
  ARM_DEVICE_REGION (SYNQUACER_PCI_SEG0_CONFIG_BASE,
                     SYNQUACER_PCI_SEG0_CONFIG_SIZE),
  ARM_DEVICE_REGION (SYNQUACER_PCI_SEG1_CONFIG_BASE,
                     SYNQUACER_PCI_SEG1_CONFIG_SIZE),

  // PCIe I/O space
  ARM_DEVICE_REGION (SYNQUACER_PCI_SEG0_PORTIO_MEMBASE,
                     SYNQUACER_PCI_SEG0_PORTIO_MEMSIZE),
  ARM_DEVICE_REGION (SYNQUACER_PCI_SEG1_PORTIO_MEMBASE,
                     SYNQUACER_PCI_SEG1_PORTIO_MEMSIZE),

  // variable store
  ARM_DEVICE_REGION (FixedPcdGet32 (PcdFip006DxeRegBaseAddress),
                     EFI_PAGE_SIZE),
  ARM_DEVICE_REGION (FixedPcdGet32 (PcdFip006DxeMemBaseAddress),
                     EFI_PAGE_SIZE),
  ARM_DEVICE_REGION (FixedPcdGet32 (PcdFlashNvStorageVariableBase),
                     FixedPcdGet32 (PcdFlashNvStorageVariableSize)),
  ARM_DEVICE_REGION (FixedPcdGet32 (PcdFlashNvStorageFtwWorkingBase),
                     FixedPcdGet32 (PcdFlashNvStorageFtwWorkingSize)),
  ARM_DEVICE_REGION (FixedPcdGet32 (PcdFlashNvStorageFtwSpareBase),
                     FixedPcdGet32 (PcdFlashNvStorageFtwSpareSize)),

  // NETSEC/eMMC SMMU
  ARM_DEVICE_REGION (SYNQUACER_SCB_SMMU_BASE, SYNQUACER_SCB_SMMU_SIZE),

  // DesignWare FUART
  ARM_DEVICE_REGION (SYNQUACER_UART1_BASE, SYNQUACER_UART1_SIZE),

  // SPI#1 MMIO
  ARM_DEVICE_REGION (SYNQUACER_SPI1_MMIO_BASE, SYNQUACER_SPI1_MMIO_SIZE),
};

STATIC
EFI_STATUS
DeclareDram (
  OUT ARM_MEMORY_REGION_DESCRIPTOR    **VirtualMemoryTable
  )
{
  SYNQUACER_DRAM_INFO_PPI       *DramInfo;
  EFI_STATUS                    Status;
  UINTN                         Idx;
  UINTN                         RegionCount;
  UINT64                        Base;
  UINT64                        Size;
  ARM_MEMORY_REGION_DESCRIPTOR  *DramDescriptor;

  Status = PeiServicesLocatePpi (&gSynQuacerDramInfoPpiGuid, 0, NULL,
             (VOID **)&DramInfo);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  Status = DramInfo->GetRegionCount (&RegionCount);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  *VirtualMemoryTable = AllocatePool (sizeof (mVirtualMemoryTable) +
                                      (RegionCount + 1) *
                                      sizeof (ARM_MEMORY_REGION_DESCRIPTOR));
  if (*VirtualMemoryTable == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }
  CopyMem (*VirtualMemoryTable, mVirtualMemoryTable,
    sizeof (mVirtualMemoryTable));

  DramDescriptor = *VirtualMemoryTable + ARRAY_SIZE (mVirtualMemoryTable);

  for (Idx = 0; Idx < RegionCount; Idx++) {
    Status = DramInfo->GetRegion (Idx, &Base, &Size);
    ASSERT_EFI_ERROR (Status);

    BuildResourceDescriptorHob (EFI_RESOURCE_SYSTEM_MEMORY,
      mDramResourceAttributes, Base, Size);

    if (Base > MAX_ALLOC_ADDRESS - Size + 1) {
      continue;
    }

    DramDescriptor->PhysicalBase = Base;
    DramDescriptor->VirtualBase  = Base;
    DramDescriptor->Length       = MIN (Size, MAX_ALLOC_ADDRESS - Base + 1);
    DramDescriptor->Attributes   = ARM_MEMORY_REGION_ATTRIBUTE_WRITE_BACK;
    DramDescriptor++;
  }

  DramDescriptor->PhysicalBase = 0;
  DramDescriptor->VirtualBase  = 0;
  DramDescriptor->Length       = 0;
  DramDescriptor->Attributes   = 0;

  return EFI_SUCCESS;
}

STATIC
BOOLEAN
CheckCapsule (
  IN  EFI_PEI_SERVICES              **PeiServices,
  IN  PEI_CAPSULE_PPI               *Capsule,
  IN  EFI_PHYSICAL_ADDRESS          UefiMemoryBase,
  OUT VOID                          **CapsuleBuffer,
  OUT UINTN                         *CapsuleBufferLength
  )
{
  EFI_STATUS        Status;

  Status = Capsule->CheckCapsuleUpdate (PeiServices);
  if (!EFI_ERROR (Status)) {

    //
    // Coalesce the capsule into unused memory. CreateState() below will copy
    // it to a properly allocated buffer.
    //
    *CapsuleBuffer = (VOID *)(UINTN)PcdGet64 (PcdSystemMemoryBase);
    *CapsuleBufferLength = UefiMemoryBase - PcdGet64 (PcdSystemMemoryBase);

    PeiServicesSetBootMode (BOOT_ON_FLASH_UPDATE);

    Status = Capsule->Coalesce (PeiServices, CapsuleBuffer,
                        CapsuleBufferLength);
    if (!EFI_ERROR (Status)) {
      DEBUG ((DEBUG_INFO, "%a: Coalesced capsule @ %p (0x%lx)\n",
        __FUNCTION__, *CapsuleBuffer, *CapsuleBufferLength));
      return TRUE;
    } else {
      DEBUG ((DEBUG_WARN, "%a: failed to coalesce() capsule (Status == %r)\n",
        __FUNCTION__, Status));
    }
  }
  return FALSE;
}

EFI_STATUS
EFIAPI
MemoryPeim (
  IN EFI_PHYSICAL_ADDRESS       UefiMemoryBase,
  IN UINT64                     UefiMemorySize
  )
{
  EFI_STATUS                    Status;
  ARM_MEMORY_REGION_DESCRIPTOR  *VirtualMemoryTable;
  EFI_PEI_SERVICES              **PeiServices;
  PEI_CAPSULE_PPI               *Capsule;
  VOID                          *CapsuleBuffer;
  UINTN                         CapsuleBufferLength;
  BOOLEAN                       HaveCapsule;
  EFI_BOOT_MODE                 BootMode;

  Status = DeclareDram (&VirtualMemoryTable);
  ASSERT_EFI_ERROR (Status);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  PeiServices = (EFI_PEI_SERVICES **) GetPeiServicesTablePointer ();
  ASSERT (PeiServices != NULL);

  Status = PeiServicesLocatePpi (&gPeiCapsulePpiGuid, 0, NULL,
             (VOID **)&Capsule);
  ASSERT_EFI_ERROR (Status);

  //
  // Check for persistent capsules, unless we are booting with default
  // settings.
  //
  Status = PeiServicesGetBootMode (&BootMode);
  if (!EFI_ERROR (Status) && BootMode != BOOT_WITH_DEFAULT_SETTINGS) {
    HaveCapsule = CheckCapsule (PeiServices, Capsule, UefiMemoryBase,
                    &CapsuleBuffer, &CapsuleBufferLength);
  } else {
    HaveCapsule = FALSE;
  }

  Status = ArmConfigureMmu (VirtualMemoryTable, NULL, NULL);
  ASSERT_EFI_ERROR (Status);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  if (HaveCapsule) {
    Status = Capsule->CreateState (PeiServices, CapsuleBuffer,
                        CapsuleBufferLength);

    if (EFI_ERROR (Status)) {
      DEBUG ((DEBUG_WARN, "%a: Capsule->CreateState failed (Status == %r)\n",
        __FUNCTION__, Status));
    }
  }

  if (FeaturePcdGet (PcdPrePiProduceMemoryTypeInformationHob)) {
    // Optional feature that helps prevent EFI memory map fragmentation.
    BuildMemoryTypeInformationHob ();
  }
  return EFI_SUCCESS;
}
