/**@file
  WinNt emulator of pre-SEC phase. It's really a Win32 application, but this is
  Ok since all the other modules for NT32 are NOT Win32 applications.

  This program gets NT32 PCD setting and figures out what the memory layout
  will be, how may FD's will be loaded and also what the boot mode is.

  This code produces 128 K of temporary memory for the SEC stack by directly
  allocate memory space with ReadWrite and Execute attribute.

Copyright (c) 2006 - 2022, Intel Corporation. All rights reserved.<BR>
(C) Copyright 2016-2020 Hewlett Packard Enterprise Development LP<BR>
SPDX-License-Identifier: BSD-2-Clause-Patent
**/

#include "WinHost.h"

#ifndef SE_TIME_ZONE_NAME
#define SE_TIME_ZONE_NAME  TEXT("SeTimeZonePrivilege")
#endif

//
// The growth size for array of module handle entries
//
#define MAX_PDB_NAME_TO_MOD_HANDLE_ARRAY_SIZE  0x100

//
// Module handle entry structure
//
typedef struct {
  CHAR8    *PdbPointer;
  VOID     *ModHandle;
} PDB_NAME_TO_MOD_HANDLE;

//
// An Array to hold the module handles
//
PDB_NAME_TO_MOD_HANDLE  *mPdbNameModHandleArray    = NULL;
UINTN                   mPdbNameModHandleArraySize = 0;

//
// Default information about where the FD is located.
//  This array gets filled in with information from PcdWinNtFirmwareVolume
//  The number of array elements is allocated base on parsing
//  PcdWinNtFirmwareVolume and the memory is never freed.
//
UINTN       gFdInfoCount = 0;
NT_FD_INFO  *gFdInfo;

//
// Array that supports separate memory ranges.
//  The memory ranges are set by PcdWinNtMemorySizeForSecMain.
//  The number of array elements is allocated base on parsing
//  PcdWinNtMemorySizeForSecMain value and the memory is never freed.
//
UINTN             gSystemMemoryCount = 0;
NT_SYSTEM_MEMORY  *gSystemMemory;

BASE_LIBRARY_JUMP_BUFFER  mResetJumpBuffer;
CHAR8                     *mResetTypeStr[] = {
  "EfiResetCold",
  "EfiResetWarm",
  "EfiResetShutdown",
  "EfiResetPlatformSpecific"
};

/*++

Routine Description:
  This service is called from Index == 0 until it returns EFI_UNSUPPORTED.
  It allows discontinuous memory regions to be supported by the emulator.
  It uses gSystemMemory[] and gSystemMemoryCount that were created by
  parsing the host environment variable EFI_MEMORY_SIZE.
  The size comes from the varaible and the address comes from the call to
  UnixOpenFile.

Arguments:
  Index      - Which memory region to use
  MemoryBase - Return Base address of memory region
  MemorySize - Return size in bytes of the memory region

Returns:
  EFI_SUCCESS - If memory region was mapped
  EFI_UNSUPPORTED - If Index is not supported

**/
EFI_STATUS
WinPeiAutoScan (
  IN  UINTN                 Index,
  OUT EFI_PHYSICAL_ADDRESS  *MemoryBase,
  OUT UINT64                *MemorySize
  )
{
  if (Index >= gSystemMemoryCount) {
    return EFI_UNSUPPORTED;
  }

  *MemoryBase = gSystemMemory[Index].Memory;
  *MemorySize = gSystemMemory[Index].Size;

  return EFI_SUCCESS;
}

/*++

Routine Description:
  Return the FD Size and base address. Since the FD is loaded from a
  file into host memory only the SEC will know its address.

Arguments:
  Index  - Which FD, starts at zero.
  FdSize - Size of the FD in bytes
  FdBase - Start address of the FD. Assume it points to an FV Header
  FixUp  - Difference between actual FD address and build address

Returns:
  EFI_SUCCESS     - Return the Base address and size of the FV
  EFI_UNSUPPORTED - Index does nto map to an FD in the system

**/
EFI_STATUS
WinFdAddress (
  IN     UINTN                 Index,
  IN OUT EFI_PHYSICAL_ADDRESS  *FdBase,
  IN OUT UINT64                *FdSize,
  IN OUT EFI_PHYSICAL_ADDRESS  *FixUp
  )
{
  if (Index >= gFdInfoCount) {
    return EFI_UNSUPPORTED;
  }

  *FdBase = (EFI_PHYSICAL_ADDRESS)(UINTN)gFdInfo[Index].Address;
  *FdSize = (UINT64)gFdInfo[Index].Size;
  *FixUp  = 0;

  if ((*FdBase == 0) && (*FdSize == 0)) {
    return EFI_UNSUPPORTED;
  }

  if (Index == 0) {
    //
    // FD 0 has XIP code and well known PCD values
    // If the memory buffer could not be allocated at the FD build address
    // the Fixup is the difference.
    //
    *FixUp = *FdBase - PcdGet64 (PcdEmuFdBaseAddress);
  }

  return EFI_SUCCESS;
}

/*++

Routine Description:
  Since the SEC is the only Unix program in stack it must export
  an interface to do POSIX calls.  gUnix is initialized in UnixThunk.c.

Arguments:
  InterfaceSize - sizeof (EFI_WIN_NT_THUNK_PROTOCOL);
  InterfaceBase - Address of the gUnix global

Returns:
  EFI_SUCCESS - Data returned

**/
VOID *
WinThunk (
  VOID
  )
{
  return &gEmuThunkProtocol;
}

EMU_THUNK_PPI  mSecEmuThunkPpi = {
  WinPeiAutoScan,
  WinFdAddress,
  WinThunk
};

VOID
SecPrint (
  CHAR8  *Format,
  ...
  )
{
  va_list  Marker;
  UINTN    CharCount;
  CHAR8    Buffer[0x1000];

  va_start (Marker, Format);

  _vsnprintf (Buffer, sizeof (Buffer), Format, Marker);

  va_end (Marker);

  CharCount = strlen (Buffer);
  WriteFile (
    GetStdHandle (STD_OUTPUT_HANDLE),
    Buffer,
    (DWORD)CharCount,
    (LPDWORD)&CharCount,
    NULL
    );
}

/**
  Resets the entire platform.

  @param[in] ResetType      The type of reset to perform.
  @param[in] ResetStatus    The status code for the reset.
  @param[in] DataSize       The size, in bytes, of ResetData.
  @param[in] ResetData      For a ResetType of EfiResetCold, EfiResetWarm, or EfiResetShutdown
                            the data buffer starts with a Null-terminated string, optionally
                            followed by additional binary data. The string is a description
                            that the caller may use to further indicate the reason for the
                            system reset.

**/
VOID
EFIAPI
WinReset (
  IN EFI_RESET_TYPE  ResetType,
  IN EFI_STATUS      ResetStatus,
  IN UINTN           DataSize,
  IN VOID            *ResetData OPTIONAL
  )
{
  UINTN  Index;

  ASSERT (ResetType <= EfiResetPlatformSpecific);
  SecPrint ("  Emu ResetSystem is called: ResetType = %s\n", mResetTypeStr[ResetType]);

  if (ResetType == EfiResetShutdown) {
    exit (0);
  } else {
    //
    // Unload all DLLs
    //
    for (Index = 0; Index < mPdbNameModHandleArraySize; Index++) {
      if (mPdbNameModHandleArray[Index].PdbPointer != NULL) {
        SecPrint ("  Emu Unload DLL: %s\n", mPdbNameModHandleArray[Index].PdbPointer);
        FreeLibrary (mPdbNameModHandleArray[Index].ModHandle);
        HeapFree (GetProcessHeap (), 0, mPdbNameModHandleArray[Index].PdbPointer);
        mPdbNameModHandleArray[Index].PdbPointer = NULL;
      }
    }

    //
    // Jump back to SetJump with jump code = ResetType + 1
    //
    LongJump (&mResetJumpBuffer, ResetType + 1);
  }
}

EFI_PEI_RESET2_PPI  mEmuReset2Ppi = {
  WinReset
};

/*++

Routine Description:
 Check to see if an address range is in the EFI GCD memory map.

 This is all of GCD for system memory passed to DXE Core. FV
 mapping and other device mapped into system memory are not
 inlcuded in the check.

Arguments:
  Index      - Which memory region to use
  MemoryBase - Return Base address of memory region
  MemorySize - Return size in bytes of the memory region

Returns:
  TRUE -  Address is in the EFI GCD memory map
  FALSE - Address is NOT in memory map

**/
BOOLEAN
EfiSystemMemoryRange (
  IN  VOID  *MemoryAddress
  )
{
  UINTN                 Index;
  EFI_PHYSICAL_ADDRESS  MemoryBase;

  MemoryBase = (EFI_PHYSICAL_ADDRESS)(UINTN)MemoryAddress;
  for (Index = 0; Index < gSystemMemoryCount; Index++) {
    if ((MemoryBase >= gSystemMemory[Index].Memory) &&
        (MemoryBase < (gSystemMemory[Index].Memory + gSystemMemory[Index].Size)))
    {
      return TRUE;
    }
  }

  return FALSE;
}

EFI_STATUS
WinNtOpenFile (
  IN  CHAR16    *FileName             OPTIONAL,
  IN  UINT32    MapSize,
  IN  DWORD     CreationDisposition,
  IN OUT  VOID  **BaseAddress,
  OUT UINTN     *Length
  )

/*++

Routine Description:
  Opens and memory maps a file using WinNt services. If *BaseAddress is non zero
  the process will try and allocate the memory starting at BaseAddress.

Arguments:
  FileName            - The name of the file to open and map
  MapSize             - The amount of the file to map in bytes
  CreationDisposition - The flags to pass to CreateFile().  Use to create new files for
                        memory emulation, and exiting files for firmware volume emulation
  BaseAddress         - The base address of the mapped file in the user address space.
                         If *BaseAddress is 0, the new memory region is used.
                         If *BaseAddress is not 0, the request memory region is used for
                          the mapping of the file into the process space.
  Length              - The size of the mapped region in bytes

Returns:
  EFI_SUCCESS      - The file was opened and mapped.
  EFI_NOT_FOUND    - FileName was not found in the current directory
  EFI_DEVICE_ERROR - An error occurred attempting to map the opened file

--*/
{
  HANDLE  NtFileHandle;
  HANDLE  NtMapHandle;
  VOID    *VirtualAddress;
  UINTN   FileSize;

  //
  // Use Win API to open/create a file
  //
  NtFileHandle = INVALID_HANDLE_VALUE;
  if (FileName != NULL) {
    NtFileHandle = CreateFile (
                     FileName,
                     GENERIC_READ | GENERIC_WRITE | GENERIC_EXECUTE,
                     FILE_SHARE_READ,
                     NULL,
                     CreationDisposition,
                     FILE_ATTRIBUTE_NORMAL,
                     NULL
                     );
    if (NtFileHandle == INVALID_HANDLE_VALUE) {
      return EFI_NOT_FOUND;
    }
  }

  //
  // Map the open file into a memory range
  //
  NtMapHandle = CreateFileMapping (
                  NtFileHandle,
                  NULL,
                  PAGE_EXECUTE_READWRITE,
                  0,
                  MapSize,
                  NULL
                  );
  if (NtMapHandle == NULL) {
    return EFI_DEVICE_ERROR;
  }

  //
  // Get the virtual address (address in the emulator) of the mapped file
  //
  VirtualAddress = MapViewOfFileEx (
                     NtMapHandle,
                     FILE_MAP_EXECUTE | FILE_MAP_ALL_ACCESS,
                     0,
                     0,
                     MapSize,
                     *BaseAddress
                     );
  if (VirtualAddress == NULL) {
    return EFI_DEVICE_ERROR;
  }

  if (MapSize == 0) {
    //
    // Seek to the end of the file to figure out the true file size.
    //
    FileSize = SetFilePointer (
                 NtFileHandle,
                 0,
                 NULL,
                 FILE_END
                 );
    if (FileSize == -1) {
      return EFI_DEVICE_ERROR;
    }

    *Length = FileSize;
  } else {
    *Length = MapSize;
  }

  *BaseAddress = VirtualAddress;

  return EFI_SUCCESS;
}

INTN
EFIAPI
main (
  IN  INT    Argc,
  IN  CHAR8  **Argv,
  IN  CHAR8  **Envp
  )

/*++

Routine Description:
  Main entry point to SEC for WinNt. This is a Windows program

Arguments:
  Argc - Number of command line arguments
  Argv - Array of command line argument strings
  Envp - Array of environment variable strings

Returns:
  0 - Normal exit
  1 - Abnormal exit

--*/
{
  EFI_STATUS           Status;
  HANDLE               Token;
  TOKEN_PRIVILEGES     TokenPrivileges;
  VOID                 *TemporaryRam;
  UINT32               TemporaryRamSize;
  VOID                 *EmuMagicPage;
  UINTN                Index;
  UINTN                Index1;
  CHAR16               *FileName;
  CHAR16               *FileNamePtr;
  BOOLEAN              Done;
  EFI_PEI_FILE_HANDLE  FileHandle;
  VOID                 *SecFile;
  CHAR16               *MemorySizeStr;
  CHAR16               *FirmwareVolumesStr;
  UINTN                ProcessAffinityMask;
  UINTN                SystemAffinityMask;
  INT32                LowBit;
  UINTN                ResetJumpCode;
  EMU_THUNK_PPI        *SecEmuThunkPpi;

  //
  // Enable the privilege so that RTC driver can successfully run SetTime()
  //
  OpenProcessToken (GetCurrentProcess (), TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY, &Token);
  if (LookupPrivilegeValue (NULL, SE_TIME_ZONE_NAME, &TokenPrivileges.Privileges[0].Luid)) {
    TokenPrivileges.PrivilegeCount           = 1;
    TokenPrivileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
    AdjustTokenPrivileges (Token, FALSE, &TokenPrivileges, 0, (PTOKEN_PRIVILEGES)NULL, 0);
  }

  MemorySizeStr      = (CHAR16 *)PcdGetPtr (PcdEmuMemorySize);
  FirmwareVolumesStr = (CHAR16 *)PcdGetPtr (PcdEmuFirmwareVolume);

  SecPrint ("\n\rEDK II WIN Host Emulation Environment from http://www.tianocore.org/edk2/\n\r");

  //
  // Determine the first thread available to this process.
  //
  if (GetProcessAffinityMask (GetCurrentProcess (), &ProcessAffinityMask, &SystemAffinityMask)) {
    LowBit = (INT32)LowBitSet32 ((UINT32)ProcessAffinityMask);
    if (LowBit != -1) {
      //
      // Force the system to bind the process to a single thread to work
      // around odd semaphore type crashes.
      //
      SetProcessAffinityMask (GetCurrentProcess (), (INTN)(BIT0 << LowBit));
    }
  }

  //
  // Make some Windows calls to Set the process to the highest priority in the
  //  idle class. We need this to have good performance.
  //
  SetPriorityClass (GetCurrentProcess (), IDLE_PRIORITY_CLASS);
  SetThreadPriority (GetCurrentThread (), THREAD_PRIORITY_HIGHEST);

  SecInitializeThunk ();
  //
  // PPIs pased into PEI_CORE
  //
  SecEmuThunkPpi = AllocateZeroPool (sizeof (EMU_THUNK_PPI) + FixedPcdGet32 (PcdPersistentMemorySize));
  if (SecEmuThunkPpi == NULL) {
    SecPrint ("ERROR : Can not allocate memory for SecEmuThunkPpi.  Exiting.\n");
    exit (1);
  }

  CopyMem (SecEmuThunkPpi, &mSecEmuThunkPpi, sizeof (EMU_THUNK_PPI));
  SecEmuThunkPpi->Argc                 = Argc;
  SecEmuThunkPpi->Argv                 = Argv;
  SecEmuThunkPpi->Envp                 = Envp;
  SecEmuThunkPpi->PersistentMemorySize = FixedPcdGet32 (PcdPersistentMemorySize);
  AddThunkPpi (EFI_PEI_PPI_DESCRIPTOR_PPI, &gEmuThunkPpiGuid, SecEmuThunkPpi);
  AddThunkPpi (EFI_PEI_PPI_DESCRIPTOR_PPI, &gEfiPeiReset2PpiGuid, &mEmuReset2Ppi);

  //
  // Emulator Bus Driver Thunks
  //
  AddThunkProtocol (&mWinNtWndThunkIo, (CHAR16 *)PcdGetPtr (PcdEmuGop), TRUE);
  AddThunkProtocol (&mWinNtFileSystemThunkIo, (CHAR16 *)PcdGetPtr (PcdEmuFileSystem), TRUE);
  AddThunkProtocol (&mWinNtBlockIoThunkIo, (CHAR16 *)PcdGetPtr (PcdEmuVirtualDisk), TRUE);
  AddThunkProtocol (&mWinNtSnpThunkIo, (CHAR16 *)PcdGetPtr (PcdEmuNetworkInterface), TRUE);

  //
  // Allocate space for gSystemMemory Array
  //
  gSystemMemoryCount = CountSeparatorsInString (MemorySizeStr, '!') + 1;
  gSystemMemory      = calloc (gSystemMemoryCount, sizeof (NT_SYSTEM_MEMORY));
  if (gSystemMemory == NULL) {
    SecPrint ("ERROR : Can not allocate memory for %S.  Exiting.\n\r", MemorySizeStr);
    exit (1);
  }

  //
  // Allocate "physical" memory space for emulator. It will be reported out later throuth MemoryAutoScan()
  //
  for (Index = 0, Done = FALSE; !Done; Index++) {
    ASSERT (Index < gSystemMemoryCount);
    gSystemMemory[Index].Size   = ((UINT64)_wtoi (MemorySizeStr)) * ((UINT64)SIZE_1MB);
    gSystemMemory[Index].Memory = (EFI_PHYSICAL_ADDRESS)(UINTN)VirtualAlloc (NULL, (SIZE_T)(gSystemMemory[Index].Size), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    if (gSystemMemory[Index].Memory == 0) {
      return EFI_OUT_OF_RESOURCES;
    }

    //
    // Find the next region
    //
    for (Index1 = 0; MemorySizeStr[Index1] != '!' && MemorySizeStr[Index1] != 0; Index1++) {
    }

    if (MemorySizeStr[Index1] == 0) {
      Done = TRUE;
    }

    MemorySizeStr = MemorySizeStr + Index1 + 1;
  }

  //
  // Allocate space for gSystemMemory Array
  //
  gFdInfoCount = CountSeparatorsInString (FirmwareVolumesStr, '!') + 1;
  gFdInfo      = calloc (gFdInfoCount, sizeof (NT_FD_INFO));
  if (gFdInfo == NULL) {
    SecPrint ("ERROR : Can not allocate memory for %S.  Exiting.\n\r", FirmwareVolumesStr);
    exit (1);
  }

  //
  // Setup Boot Mode.
  //
  SecPrint ("  BootMode 0x%02x\n\r", PcdGet32 (PcdEmuBootMode));

  //
  //  Allocate 128K memory to emulate temp memory for PEI.
  //  on a real platform this would be SRAM, or using the cache as RAM.
  //  Set TemporaryRam to zero so WinNtOpenFile will allocate a new mapping
  //
  TemporaryRamSize = TEMPORARY_RAM_SIZE;
  TemporaryRam     = VirtualAlloc (NULL, (SIZE_T)(TemporaryRamSize), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
  if (TemporaryRam == NULL) {
    SecPrint ("ERROR : Can not allocate enough space for SecStack\n\r");
    exit (1);
  }

  //
  // If enabled use the magic page to communicate between modules
  // This replaces the PI PeiServicesTable pointer mechanism that
  // deos not work in the emulator. It also allows the removal of
  // writable globals from SEC, PEI_CORE (libraries), PEIMs
  //
  EmuMagicPage = (VOID *)(UINTN)(FixedPcdGet64 (PcdPeiServicesTablePage) & MAX_UINTN);
  if (EmuMagicPage != NULL) {
    UINT64  Size;
    Status = WinNtOpenFile (
               NULL,
               SIZE_4KB,
               0,
               &EmuMagicPage,
               &Size
               );
    if (EFI_ERROR (Status)) {
      SecPrint ("ERROR : Could not allocate PeiServicesTablePage @ %p\n\r", EmuMagicPage);
      return EFI_DEVICE_ERROR;
    }
  }

  //
  // Open All the firmware volumes and remember the info in the gFdInfo global
  // Meanwhile, find the SEC Core.
  //
  FileNamePtr = AllocateCopyPool (StrSize (FirmwareVolumesStr), FirmwareVolumesStr);
  if (FileNamePtr == NULL) {
    SecPrint ("ERROR : Can not allocate memory for firmware volume string\n\r");
    exit (1);
  }

  for (Done = FALSE, Index = 0, SecFile = NULL; !Done; Index++) {
    FileName = FileNamePtr;
    for (Index1 = 0; (FileNamePtr[Index1] != '!') && (FileNamePtr[Index1] != 0); Index1++) {
    }

    if (FileNamePtr[Index1] == 0) {
      Done = TRUE;
    } else {
      FileNamePtr[Index1] = '\0';
      FileNamePtr         = &FileNamePtr[Index1 + 1];
    }

    //
    // Open the FD and remember where it got mapped into our processes address space
    //
    Status = WinNtOpenFile (
               FileName,
               0,
               OPEN_EXISTING,
               &gFdInfo[Index].Address,
               &gFdInfo[Index].Size
               );
    if (EFI_ERROR (Status)) {
      SecPrint ("ERROR : Can not open Firmware Device File %S (0x%X).  Exiting.\n\r", FileName, Status);
      exit (1);
    }

    SecPrint ("  FD loaded from %S", FileName);

    if (SecFile == NULL) {
      //
      // Assume the beginning of the FD is an FV and look for the SEC Core.
      // Load the first one we find.
      //
      FileHandle = NULL;
      Status     = PeiServicesFfsFindNextFile (
                     EFI_FV_FILETYPE_SECURITY_CORE,
                     (EFI_PEI_FV_HANDLE)gFdInfo[Index].Address,
                     &FileHandle
                     );
      if (!EFI_ERROR (Status)) {
        Status = PeiServicesFfsFindSectionData (EFI_SECTION_PE32, FileHandle, &SecFile);
        if (!EFI_ERROR (Status)) {
          SecPrint (" contains SEC Core");
        }
      }
    }

    SecPrint ("\n\r");
  }

  ResetJumpCode = SetJump (&mResetJumpBuffer);

  //
  // Do not clear memory content for warm reset.
  //
  if (ResetJumpCode != EfiResetWarm + 1) {
    SecPrint ("  OS Emulator clearing temp RAM and physical RAM (to be discovered later)......\n\r");
    SetMem32 (TemporaryRam, TemporaryRamSize, PcdGet32 (PcdInitValueInTempStack));
    for (Index = 0; Index < gSystemMemoryCount; Index++) {
      SetMem32 ((VOID *)(UINTN)gSystemMemory[Index].Memory, (UINTN)gSystemMemory[Index].Size, PcdGet32 (PcdInitValueInTempStack));
    }
  }

  SecPrint (
    "  OS Emulator passing in %u KB of temp RAM at 0x%08lx to SEC\n\r",
    TemporaryRamSize / SIZE_1KB,
    TemporaryRam
    );
  //
  // Hand off to SEC Core
  //
  SecLoadSecCore ((UINTN)TemporaryRam, TemporaryRamSize, gFdInfo[0].Address, gFdInfo[0].Size, SecFile);

  //
  // If we get here, then the SEC Core returned. This is an error as SEC should
  //  always hand off to PEI Core and then on to DXE Core.
  //
  SecPrint ("ERROR : SEC returned\n\r");
  exit (1);
}

VOID
SecLoadSecCore (
  IN  UINTN  TemporaryRam,
  IN  UINTN  TemporaryRamSize,
  IN  VOID   *BootFirmwareVolumeBase,
  IN  UINTN  BootFirmwareVolumeSize,
  IN  VOID   *SecCorePe32File
  )

/*++

Routine Description:
  This is the service to load the SEC Core from the Firmware Volume

Arguments:
  TemporaryRam            - Memory to use for SEC.
  TemporaryRamSize        - Size of Memory to use for SEC
  BootFirmwareVolumeBase  - Start of the Boot FV
  SecCorePe32File         - SEC Core PE32

Returns:
  Success means control is transferred and thus we should never return

--*/
{
  EFI_STATUS            Status;
  VOID                  *TopOfStack;
  VOID                  *SecCoreEntryPoint;
  EFI_SEC_PEI_HAND_OFF  *SecCoreData;
  UINTN                 SecStackSize;

  //
  // Compute Top Of Memory for Stack and PEI Core Allocations
  //
  SecStackSize = TemporaryRamSize >> 1;

  //
  // |-----------| <---- TemporaryRamBase + TemporaryRamSize
  // |   Heap    |
  // |           |
  // |-----------| <---- StackBase / PeiTemporaryMemoryBase
  // |           |
  // |  Stack    |
  // |-----------| <---- TemporaryRamBase
  //
  TopOfStack = (VOID *)(TemporaryRam + SecStackSize);

  //
  // Reservet space for storing PeiCore's parament in stack.
  //
  TopOfStack = (VOID *)((UINTN)TopOfStack - sizeof (EFI_SEC_PEI_HAND_OFF) - CPU_STACK_ALIGNMENT);
  TopOfStack = ALIGN_POINTER (TopOfStack, CPU_STACK_ALIGNMENT);

  //
  // Bind this information into the SEC hand-off state
  //
  SecCoreData                         = (EFI_SEC_PEI_HAND_OFF *)(UINTN)TopOfStack;
  SecCoreData->DataSize               = sizeof (EFI_SEC_PEI_HAND_OFF);
  SecCoreData->BootFirmwareVolumeBase = BootFirmwareVolumeBase;
  SecCoreData->BootFirmwareVolumeSize = BootFirmwareVolumeSize;
  SecCoreData->TemporaryRamBase       = (VOID *)TemporaryRam;
  SecCoreData->TemporaryRamSize       = TemporaryRamSize;
  SecCoreData->StackBase              = SecCoreData->TemporaryRamBase;
  SecCoreData->StackSize              = SecStackSize;
  SecCoreData->PeiTemporaryRamBase    = (VOID *)((UINTN)SecCoreData->TemporaryRamBase + SecStackSize);
  SecCoreData->PeiTemporaryRamSize    = TemporaryRamSize - SecStackSize;

  //
  // Load the PEI Core from a Firmware Volume
  //
  Status = SecPeCoffGetEntryPoint (
             SecCorePe32File,
             &SecCoreEntryPoint
             );
  if (EFI_ERROR (Status)) {
    return;
  }

  //
  // Transfer control to the SEC Core
  //
  SwitchStack (
    (SWITCH_STACK_ENTRY_POINT)(UINTN)SecCoreEntryPoint,
    SecCoreData,
    GetThunkPpiList (),
    TopOfStack
    );
  //
  // If we get here, then the SEC Core returned.  This is an error
  //
  return;
}

RETURN_STATUS
EFIAPI
SecPeCoffGetEntryPoint (
  IN     VOID  *Pe32Data,
  IN OUT VOID  **EntryPoint
  )
{
  EFI_STATUS                    Status;
  PE_COFF_LOADER_IMAGE_CONTEXT  ImageContext;

  ZeroMem (&ImageContext, sizeof (ImageContext));
  ImageContext.Handle = Pe32Data;

  ImageContext.ImageRead = (PE_COFF_LOADER_READ_FILE)SecImageRead;

  Status = PeCoffLoaderGetImageInfo (&ImageContext);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // XIP for SEC and PEI_CORE
  //
  ImageContext.ImageAddress = (EFI_PHYSICAL_ADDRESS)(UINTN)Pe32Data;

  Status = PeCoffLoaderLoadImage (&ImageContext);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  Status = PeCoffLoaderRelocateImage (&ImageContext);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  *EntryPoint = (VOID *)(UINTN)ImageContext.EntryPoint;

  return EFI_SUCCESS;
}

EFI_STATUS
EFIAPI
SecImageRead (
  IN     VOID   *FileHandle,
  IN     UINTN  FileOffset,
  IN OUT UINTN  *ReadSize,
  OUT    VOID   *Buffer
  )

/*++

Routine Description:
  Support routine for the PE/COFF Loader that reads a buffer from a PE/COFF file

Arguments:
  FileHandle - The handle to the PE/COFF file
  FileOffset - The offset, in bytes, into the file to read
  ReadSize   - The number of bytes to read from the file starting at FileOffset
  Buffer     - A pointer to the buffer to read the data into.

Returns:
  EFI_SUCCESS - ReadSize bytes of data were read into Buffer from the PE/COFF file starting at FileOffset

--*/
{
  CHAR8  *Destination8;
  CHAR8  *Source8;
  UINTN  Length;

  Destination8 = Buffer;
  Source8      = (CHAR8 *)((UINTN)FileHandle + FileOffset);
  Length       = *ReadSize;
  while (Length--) {
    *(Destination8++) = *(Source8++);
  }

  return EFI_SUCCESS;
}

CHAR16 *
AsciiToUnicode (
  IN  CHAR8  *Ascii,
  IN  UINTN  *StrLen OPTIONAL
  )

/*++

Routine Description:
  Convert the passed in Ascii string to Unicode.
  Optionally return the length of the strings.

Arguments:
  Ascii   - Ascii string to convert
  StrLen  - Length of string

Returns:
  Pointer to malloc'ed Unicode version of Ascii

--*/
{
  UINTN   Index;
  CHAR16  *Unicode;

  //
  // Allocate a buffer for unicode string
  //
  for (Index = 0; Ascii[Index] != '\0'; Index++) {
  }

  Unicode = malloc ((Index + 1) * sizeof (CHAR16));
  if (Unicode == NULL) {
    return NULL;
  }

  for (Index = 0; Ascii[Index] != '\0'; Index++) {
    Unicode[Index] = (CHAR16)Ascii[Index];
  }

  Unicode[Index] = '\0';

  if (StrLen != NULL) {
    *StrLen = Index;
  }

  return Unicode;
}

UINTN
CountSeparatorsInString (
  IN  CONST CHAR16  *String,
  IN  CHAR16        Separator
  )

/*++

Routine Description:
  Count the number of separators in String

Arguments:
  String    - String to process
  Separator - Item to count

Returns:
  Number of Separator in String

--*/
{
  UINTN  Count;

  for (Count = 0; *String != '\0'; String++) {
    if (*String == Separator) {
      Count++;
    }
  }

  return Count;
}

/**
  Store the ModHandle in an array indexed by the Pdb File name.
  The ModHandle is needed to unload the image.
  @param ImageContext - Input data returned from PE Laoder Library. Used to find the
                 .PDB file name of the PE Image.
  @param ModHandle    - Returned from LoadLibraryEx() and stored for call to
                 FreeLibrary().
  @return   return EFI_SUCCESS when ModHandle was stored.
--*/
EFI_STATUS
AddModHandle (
  IN  PE_COFF_LOADER_IMAGE_CONTEXT  *ImageContext,
  IN  VOID                          *ModHandle
  )

{
  UINTN                   Index;
  PDB_NAME_TO_MOD_HANDLE  *Array;
  UINTN                   PreviousSize;
  PDB_NAME_TO_MOD_HANDLE  *TempArray;
  HANDLE                  Handle;
  UINTN                   Size;

  //
  // Return EFI_ALREADY_STARTED if this DLL has already been loaded
  //
  Array = mPdbNameModHandleArray;
  for (Index = 0; Index < mPdbNameModHandleArraySize; Index++, Array++) {
    if ((Array->PdbPointer != NULL) && (Array->ModHandle == ModHandle)) {
      return EFI_ALREADY_STARTED;
    }
  }

  Array = mPdbNameModHandleArray;
  for (Index = 0; Index < mPdbNameModHandleArraySize; Index++, Array++) {
    if (Array->PdbPointer == NULL) {
      //
      // Make a copy of the stirng and store the ModHandle
      //
      Handle            = GetProcessHeap ();
      Size              = AsciiStrLen (ImageContext->PdbPointer) + 1;
      Array->PdbPointer = HeapAlloc (Handle, HEAP_ZERO_MEMORY, Size);
      ASSERT (Array->PdbPointer != NULL);

      AsciiStrCpyS (Array->PdbPointer, Size, ImageContext->PdbPointer);
      Array->ModHandle = ModHandle;
      return EFI_SUCCESS;
    }
  }

  //
  // No free space in mPdbNameModHandleArray so grow it by
  // MAX_PDB_NAME_TO_MOD_HANDLE_ARRAY_SIZE entires.
  //
  PreviousSize                = mPdbNameModHandleArraySize * sizeof (PDB_NAME_TO_MOD_HANDLE);
  mPdbNameModHandleArraySize += MAX_PDB_NAME_TO_MOD_HANDLE_ARRAY_SIZE;
  //
  // re-allocate a new buffer and copy the old values to the new locaiton.
  //
  TempArray = HeapAlloc (
                GetProcessHeap (),
                HEAP_ZERO_MEMORY,
                mPdbNameModHandleArraySize * sizeof (PDB_NAME_TO_MOD_HANDLE)
                );

  CopyMem ((VOID *)(UINTN)TempArray, (VOID *)(UINTN)mPdbNameModHandleArray, PreviousSize);

  HeapFree (GetProcessHeap (), 0, mPdbNameModHandleArray);

  mPdbNameModHandleArray = TempArray;
  if (mPdbNameModHandleArray == NULL) {
    ASSERT (FALSE);
    return EFI_OUT_OF_RESOURCES;
  }

  return AddModHandle (ImageContext, ModHandle);
}

/**
  Return the ModHandle and delete the entry in the array.
   @param  ImageContext - Input data returned from PE Laoder Library. Used to find the
                 .PDB file name of the PE Image.
  @return
    ModHandle - ModHandle assoicated with ImageContext is returned
    NULL      - No ModHandle associated with ImageContext
**/
VOID *
RemoveModHandle (
  IN  PE_COFF_LOADER_IMAGE_CONTEXT  *ImageContext
  )
{
  UINTN                   Index;
  PDB_NAME_TO_MOD_HANDLE  *Array;

  if (ImageContext->PdbPointer == NULL) {
    //
    // If no PDB pointer there is no ModHandle so return NULL
    //
    return NULL;
  }

  Array = mPdbNameModHandleArray;
  for (Index = 0; Index < mPdbNameModHandleArraySize; Index++, Array++) {
    if ((Array->PdbPointer != NULL) && (AsciiStrCmp (Array->PdbPointer, ImageContext->PdbPointer) == 0)) {
      //
      // If you find a match return it and delete the entry
      //
      HeapFree (GetProcessHeap (), 0, Array->PdbPointer);
      Array->PdbPointer = NULL;
      return Array->ModHandle;
    }
  }

  return NULL;
}

VOID
EFIAPI
PeCoffLoaderRelocateImageExtraAction (
  IN OUT PE_COFF_LOADER_IMAGE_CONTEXT  *ImageContext
  )
{
  EFI_STATUS  Status;
  VOID        *DllEntryPoint;
  CHAR16      *DllFileName;
  HMODULE     Library;
  UINTN       Index;

  ASSERT (ImageContext != NULL);
  //
  // If we load our own PE COFF images the Windows debugger can not source
  //  level debug our code. If a valid PDB pointer exists use it to load
  //  the *.dll file as a library using Windows* APIs. This allows
  //  source level debug. The image is still loaded and relocated
  //  in the Framework memory space like on a real system (by the code above),
  //  but the entry point points into the DLL loaded by the code below.
  //

  DllEntryPoint = NULL;

  //
  // Load the DLL if it's not an EBC image.
  //
  if ((ImageContext->PdbPointer != NULL) &&
      (ImageContext->Machine != EFI_IMAGE_MACHINE_EBC))
  {
    //
    // Convert filename from ASCII to Unicode
    //
    DllFileName = AsciiToUnicode (ImageContext->PdbPointer, &Index);

    //
    // Check that we have a valid filename
    //
    if ((Index < 5) || (DllFileName[Index - 4] != '.')) {
      free (DllFileName);

      //
      // Never return an error if PeCoffLoaderRelocateImage() succeeded.
      // The image will run, but we just can't source level debug. If we
      // return an error the image will not run.
      //
      return;
    }

    //
    // Replace .PDB with .DLL on the filename
    //
    DllFileName[Index - 3] = 'D';
    DllFileName[Index - 2] = 'L';
    DllFileName[Index - 1] = 'L';

    //
    // Load the .DLL file into the user process's address space for source
    // level debug
    //
    Library = LoadLibraryEx (DllFileName, NULL, DONT_RESOLVE_DLL_REFERENCES);
    if (Library != NULL) {
      //
      // InitializeDriver is the entry point we put in all our EFI DLL's. The
      // DONT_RESOLVE_DLL_REFERENCES argument to LoadLIbraryEx() suppresses the
      // normal DLL entry point of DllMain, and prevents other modules that are
      // referenced in side the DllFileName from being loaded. There is no error
      // checking as the we can point to the PE32 image loaded by Tiano. This
      // step is only needed for source level debugging
      //
      DllEntryPoint = (VOID *)(UINTN)GetProcAddress (Library, "InitializeDriver");
    }

    if ((Library != NULL) && (DllEntryPoint != NULL)) {
      Status = AddModHandle (ImageContext, Library);
      if (Status == EFI_ALREADY_STARTED) {
        //
        // If the DLL has already been loaded before, then this instance of the DLL can not be debugged.
        //
        ImageContext->PdbPointer = NULL;
        SecPrint ("WARNING: DLL already loaded.  No source level debug %S.\n\r", DllFileName);
      } else {
        //
        // This DLL is not already loaded, so source level debugging is supported.
        //
        ImageContext->EntryPoint = (EFI_PHYSICAL_ADDRESS)(UINTN)DllEntryPoint;
        SecPrint ("LoadLibraryEx (\n\r  %S,\n\r  NULL, DONT_RESOLVE_DLL_REFERENCES)\n\r", DllFileName);
      }
    } else {
      SecPrint ("WARNING: No source level debug %S. \n\r", DllFileName);
    }

    free (DllFileName);
  }
}

VOID
EFIAPI
PeCoffLoaderUnloadImageExtraAction (
  IN PE_COFF_LOADER_IMAGE_CONTEXT  *ImageContext
  )
{
  VOID  *ModHandle;

  ASSERT (ImageContext != NULL);

  ModHandle = RemoveModHandle (ImageContext);
  if (ModHandle != NULL) {
    FreeLibrary (ModHandle);
    SecPrint ("FreeLibrary (\n\r  %s)\n\r", ImageContext->PdbPointer);
  } else {
    SecPrint ("WARNING: Unload image without source level debug\n\r");
  }
}

VOID
_ModuleEntryPoint (
  VOID
  )
{
}
