/** @file
  Generic SEC driver for ARM platforms

  Copyright (c) 2011 - 2022, ARM Limited. All rights reserved.

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

**/

#include "Sec.h"

/**
  This service of the EFI_PEI_TEMPORARY_RAM_SUPPORT_PPI that migrates temporary
  RAM into permanent memory.

  @param PeiServices            Pointer to the PEI Services Table.
  @param TemporaryMemoryBase    Source Address in temporary memory from which
                                the SEC or PEIM will copy the Temporary RAM
                                contents.
  @param PermanentMemoryBase    Destination Address in permanent memory into
                                which the SEC or PEIM will copy the Temporary
                                RAM contents.
  @param CopySize               Amount of memory to migrate from temporary to
                                permanent memory.

  @retval EFI_SUCCESS           The data was successfully returned.
  @retval EFI_INVALID_PARAMETER PermanentMemoryBase + CopySize >
                                TemporaryMemoryBase when TemporaryMemoryBase >
                                PermanentMemoryBase.

**/
STATIC
EFI_STATUS
EFIAPI
SecTemporaryRamSupport (
  IN CONST EFI_PEI_SERVICES  **PeiServices,
  IN EFI_PHYSICAL_ADDRESS    TemporaryMemoryBase,
  IN EFI_PHYSICAL_ADDRESS    PermanentMemoryBase,
  IN UINTN                   CopySize
  )
{
  VOID   *OldHeap;
  VOID   *NewHeap;
  VOID   *OldStack;
  VOID   *NewStack;
  UINTN  HeapSize;

  HeapSize = ALIGN_VALUE (CopySize / 2, CPU_STACK_ALIGNMENT);

  OldHeap = (VOID *)(UINTN)TemporaryMemoryBase;
  NewHeap = (VOID *)((UINTN)PermanentMemoryBase + (CopySize - HeapSize));

  OldStack = (VOID *)((UINTN)TemporaryMemoryBase + HeapSize);
  NewStack = (VOID *)(UINTN)PermanentMemoryBase;

  //
  // Migrate the temporary memory stack to permanent memory stack.
  //
  CopyMem (NewStack, OldStack, CopySize - HeapSize);

  //
  // Migrate the temporary memory heap to permanent memory heap.
  //
  CopyMem (NewHeap, OldHeap, HeapSize);

  SecSwitchStack ((UINTN)NewStack - (UINTN)OldStack);

  return EFI_SUCCESS;
}

STATIC CONST EFI_PEI_TEMPORARY_RAM_SUPPORT_PPI  mTemporaryRamSupportPpi = {
  SecTemporaryRamSupport
};

STATIC CONST EFI_PEI_PPI_DESCRIPTOR  gCommonPpiTable[] = {
  {
    EFI_PEI_PPI_DESCRIPTOR_PPI,
    &gEfiTemporaryRamSupportPpiGuid,
    (VOID *)&mTemporaryRamSupportPpi
  }
};

/**
  Construct a PPI list from the PPIs provided in this file and the ones
  provided by the platform code.

  @param[out]   PpiListSize   Size of the PPI list in bytes
  @param[out]   PpiList       Pointer to the constructed PPI list
  @param[in]    TransferListBase  Pointer to the Transfer List base
**/
STATIC
VOID
CreatePpiList (
  OUT UINTN                   *PpiListSize,
  OUT EFI_PEI_PPI_DESCRIPTOR  **PpiList,
  IN  VOID                    *TransferListBase
  )
{
  EFI_PEI_PPI_DESCRIPTOR  *PlatformPpiList;
  UINTN                   PlatformPpiListSize;
  UINTN                   ListBase;
  EFI_PEI_PPI_DESCRIPTOR  *LastPpi;

  // Get the Platform PPIs
  PlatformPpiListSize = 0;
  ArmPlatformGetPlatformPpiList (&PlatformPpiListSize, &PlatformPpiList);

  // Copy the Common and Platform PPis in Temporary Memory
  ListBase = PcdGet64 (PcdCPUCoresStackBase);
  CopyMem ((VOID *)ListBase, gCommonPpiTable, sizeof (gCommonPpiTable));
  CopyMem ((VOID *)(ListBase + sizeof (gCommonPpiTable)), PlatformPpiList, PlatformPpiListSize);

  // Set the Terminate flag on the last PPI entry
  LastPpi = (EFI_PEI_PPI_DESCRIPTOR *)ListBase +
            ((sizeof (gCommonPpiTable) + PlatformPpiListSize) / sizeof (EFI_PEI_PPI_DESCRIPTOR)) - 1;
  *PpiListSize = sizeof (gCommonPpiTable) + PlatformPpiListSize;

  // Add the Transfer List PPI after the Common and Platform PPIs
  if (TransferListBase != NULL) {
    LastPpi++;
    LastPpi->Flags = EFI_PEI_PPI_DESCRIPTOR_PPI;
    LastPpi->Guid  = &gArmTransferListPpiGuid;
    LastPpi->Ppi   = TransferListBase;
    *PpiListSize  += sizeof (EFI_PEI_PPI_DESCRIPTOR);
  }

  LastPpi->Flags |= EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST;

  *PpiList = (EFI_PEI_PPI_DESCRIPTOR *)ListBase;
}

/**

 Prints firmware version and build time to serial console.

**/
STATIC
VOID
PrintFirmwareVersion (
  VOID
  )
{
  CHAR8  Buffer[100];
  UINTN  CharCount;

  CharCount = AsciiSPrint (
                Buffer,
                sizeof (Buffer),
                "UEFI firmware (version %s built at %a on %a)\n\r",
                (CHAR16 *)PcdGetPtr (PcdFirmwareVersionString),
                __TIME__,
                __DATE__
                );

  // Because we are directly bit banging the serial port instead of going through the DebugLib, we need to make sure
  // the serial port is initialized before we write to it
  SerialPortInitialize ();
  SerialPortWrite ((UINT8 *)Buffer, CharCount);
}

/**
  SEC main routine.

  @param[in]  PeiCoreEntryPoint   Address in ram of the entrypoint of the PEI
                                  core
  @param[in]  TransferListBaseAddr Address of the Transfer List base address

**/
STATIC
VOID
EFIAPI
SecMain (
  IN  EFI_PEI_CORE_ENTRY_POINT  PeiCoreEntryPoint,
  IN  UINTN                     TransferListBaseAddr
  )
{
  EFI_SEC_PEI_HAND_OFF    SecCoreData;
  UINTN                   PpiListSize;
  EFI_PEI_PPI_DESCRIPTOR  *PpiList;
  UINTN                   TemporaryRamBase;
  UINTN                   TemporaryRamSize;
  VOID                    *TransferListBase;

  // Dump the Transfer List
  TransferListBase = (VOID *)TransferListBaseAddr;
  if (TransferListBase != NULL) {
    if (TransferListCheckHeader (TransferListBase) != TRANSFER_LIST_OPS_INVALID) {
      DEBUG_CODE_BEGIN ();
      TransferListDump (TransferListBase);
      DEBUG_CODE_END ();
    } else {
      TransferListBase = NULL;
      DEBUG ((DEBUG_ERROR, "%a: No valid operations possible on TransferList found @ 0x%p\n", __func__, TransferListBase));
    }
  } else {
    DEBUG ((DEBUG_INFO, "%a: No TransferList found, continuing boot\n", __func__));
  }

  CreatePpiList (&PpiListSize, &PpiList, TransferListBase);

  // Adjust the Temporary Ram as the new Ppi List (Common + Platform Ppi Lists) is created at
  // the base of the primary core stack
  PpiListSize      = ALIGN_VALUE (PpiListSize, CPU_STACK_ALIGNMENT);
  TemporaryRamBase = (UINTN)PcdGet64 (PcdCPUCoresStackBase) + PpiListSize;
  TemporaryRamSize = (UINTN)PcdGet32 (PcdCPUCorePrimaryStackSize) - PpiListSize;

  //
  // Bind this information into the SEC hand-off state
  // Note: this must be in sync with the stuff in the asm file
  // Note also:  HOBs (pei temp ram) MUST be above stack
  //
  SecCoreData.DataSize               = sizeof (EFI_SEC_PEI_HAND_OFF);
  SecCoreData.BootFirmwareVolumeBase = (VOID *)(UINTN)PcdGet64 (PcdFvBaseAddress);
  SecCoreData.BootFirmwareVolumeSize = PcdGet32 (PcdFvSize);
  SecCoreData.TemporaryRamBase       = (VOID *)TemporaryRamBase; // We run on the primary core (and so we use the first stack)
  SecCoreData.TemporaryRamSize       = TemporaryRamSize;
  SecCoreData.PeiTemporaryRamBase    = SecCoreData.TemporaryRamBase;
  SecCoreData.PeiTemporaryRamSize    = ALIGN_VALUE (SecCoreData.TemporaryRamSize / 2, CPU_STACK_ALIGNMENT);
  SecCoreData.StackBase              = (VOID *)((UINTN)SecCoreData.TemporaryRamBase + SecCoreData.PeiTemporaryRamSize);
  SecCoreData.StackSize              = (TemporaryRamBase + TemporaryRamSize) - (UINTN)SecCoreData.StackBase;

  // Jump to PEI core entry point
  (PeiCoreEntryPoint)(&SecCoreData, PpiList);
}

/**
  Module C entrypoint.

  @param[in]  PeiCoreEntryPoint   Address in ram of the entrypoint of the PEI
                                  core
  @param[in]  TransferListBaseAddr Address of the Transfer List base address

**/
VOID
CEntryPoint (
  IN  EFI_PEI_CORE_ENTRY_POINT  PeiCoreEntryPoint,
  IN  UINTN                     TransferListBaseAddr
  )
{
  if (!ArmMmuEnabled ()) {
    // Data Cache enabled on Primary core when MMU is enabled.
    ArmDisableDataCache ();
    // Invalidate instruction cache
    ArmInvalidateInstructionCache ();
    // Enable Instruction Caches on all cores.
    ArmEnableInstructionCache ();

    InvalidateDataCacheRange (
      (VOID *)(UINTN)PcdGet64 (PcdCPUCoresStackBase),
      PcdGet32 (PcdCPUCorePrimaryStackSize)
      );
  }

  // Write VBAR - The Exception Vector table must be aligned to its requirement
  // Note: The AArch64 Vector table must be 2k-byte aligned - if this assertion fails ensure
  // 'Align=4K' is defined into your FDF for this module.
  ASSERT (((UINTN)PeiVectorTable & ARM_VECTOR_TABLE_ALIGNMENT) == 0);
  ArmWriteVBar ((UINTN)PeiVectorTable);

  // Invoke "ProcessLibraryConstructorList" to have all library constructors
  // called.
  ProcessLibraryConstructorList ();

  PrintFirmwareVersion ();

  // Initialize the Debug Agent for Source Level Debugging
  InitializeDebugAgent (DEBUG_AGENT_INIT_POSTMEM_SEC, NULL, NULL);
  SaveAndSetDebugTimerInterrupt (TRUE);

  // Initialize the platform specific controllers
  ArmPlatformInitialize (ArmReadMpidr ());

  // Goto primary Main.
  SecMain (PeiCoreEntryPoint, TransferListBaseAddr);

  // PEI Core should always load and never return
  ASSERT (FALSE);
}
