/**@file
  Xen Platform PEI support

  Copyright (c) 2006 - 2016, Intel Corporation. All rights reserved.<BR>
  Copyright (c) 2011, Andrei Warkentin <andreiw@motorola.com>
  Copyright (c) 2019, Citrix Systems, Inc.

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

**/

//
// The package level header files this module uses
//
#include <PiPei.h>

//
// The Library classes this module consumes
//
#include <Library/BaseMemoryLib.h>
#include <Library/CpuLib.h>
#include <Library/DebugLib.h>
#include <Library/HobLib.h>
#include <Library/LocalApicLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/PcdLib.h>
#include <Library/SafeIntLib.h>
#include <Guid/XenInfo.h>
#include <IndustryStandard/E820.h>
#include <Library/ResourcePublicationLib.h>
#include <Library/MtrrLib.h>
#include <IndustryStandard/PageTable.h>
#include <IndustryStandard/Xen/arch-x86/hvm/start_info.h>
#include <Library/XenHypercallLib.h>
#include <IndustryStandard/Xen/memory.h>

#include "Platform.h"
#include "Xen.h"

STATIC UINT32  mXenLeaf = 0;

EFI_XEN_INFO  mXenInfo;

//
// Location of the firmware info struct setup by hvmloader.
// Only the E820 table is used by OVMF.
//
EFI_XEN_OVMF_INFO        *mXenHvmloaderInfo;
STATIC EFI_E820_ENTRY64  mE820Entries[128];
STATIC UINT32            mE820EntriesCount;

/**
  Returns E820 map provided by Xen

  @param Entries      Pointer to E820 map
  @param Count        Number of entries

  @return EFI_STATUS
**/
EFI_STATUS
XenGetE820Map (
  EFI_E820_ENTRY64  **Entries,
  UINT32            *Count
  )
{
  INTN              ReturnCode;
  xen_memory_map_t  Parameters;
  UINTN             LoopIndex;
  UINTN             Index;
  EFI_E820_ENTRY64  TmpEntry;

  //
  // Get E820 produced by hvmloader
  //
  if (mXenHvmloaderInfo != NULL) {
    ASSERT (mXenHvmloaderInfo->E820 < MAX_ADDRESS);
    *Entries = (EFI_E820_ENTRY64 *)(UINTN)mXenHvmloaderInfo->E820;
    *Count   = mXenHvmloaderInfo->E820EntriesCount;

    return EFI_SUCCESS;
  }

  //
  // Otherwise, get the E820 table from the Xen hypervisor
  //

  if (mE820EntriesCount > 0) {
    *Entries = mE820Entries;
    *Count   = mE820EntriesCount;
    return EFI_SUCCESS;
  }

  Parameters.nr_entries = 128;
  set_xen_guest_handle (Parameters.buffer, mE820Entries);

  // Returns a errno
  ReturnCode = XenHypercallMemoryOp (XENMEM_memory_map, &Parameters);
  ASSERT (ReturnCode == 0);

  mE820EntriesCount = Parameters.nr_entries;

  //
  // Sort E820 entries
  //
  for (LoopIndex = 1; LoopIndex < mE820EntriesCount; LoopIndex++) {
    for (Index = LoopIndex; Index < mE820EntriesCount; Index++) {
      if (mE820Entries[Index - 1].BaseAddr > mE820Entries[Index].BaseAddr) {
        TmpEntry                = mE820Entries[Index];
        mE820Entries[Index]     = mE820Entries[Index - 1];
        mE820Entries[Index - 1] = TmpEntry;
      }
    }
  }

  *Count   = mE820EntriesCount;
  *Entries = mE820Entries;

  return EFI_SUCCESS;
}

/**
  Connects to the Hypervisor.

  @return EFI_STATUS

**/
EFI_STATUS
XenConnect (
  )
{
  UINT32             Index;
  UINT32             TransferReg;
  UINT32             TransferPages;
  UINT32             XenVersion;
  EFI_XEN_OVMF_INFO  *Info;
  CHAR8              Sig[sizeof (Info->Signature) + 1];
  UINT32             *PVHResetVectorData;
  RETURN_STATUS      Status;

  ASSERT (mXenLeaf != 0);

  //
  // Prepare HyperPages to be able to make hypercalls
  //

  AsmCpuid (mXenLeaf + 2, &TransferPages, &TransferReg, NULL, NULL);
  mXenInfo.HyperPages = AllocatePages (TransferPages);
  if (!mXenInfo.HyperPages) {
    return EFI_OUT_OF_RESOURCES;
  }

  for (Index = 0; Index < TransferPages; Index++) {
    AsmWriteMsr64 (
      TransferReg,
      (UINTN)mXenInfo.HyperPages +
      (Index << EFI_PAGE_SHIFT) + Index
      );
  }

  //
  // Find out the Xen version
  //

  AsmCpuid (mXenLeaf + 1, &XenVersion, NULL, NULL, NULL);
  DEBUG ((
    DEBUG_ERROR,
    "Detected Xen version %d.%d\n",
    XenVersion >> 16,
    XenVersion & 0xFFFF
    ));
  mXenInfo.VersionMajor = (UINT16)(XenVersion >> 16);
  mXenInfo.VersionMinor = (UINT16)(XenVersion & 0xFFFF);

  //
  // Check if there are information left by hvmloader
  //

  Info = (EFI_XEN_OVMF_INFO *)(UINTN)OVMF_INFO_PHYSICAL_ADDRESS;
  //
  // Copy the signature, and make it null-terminated.
  //
  AsciiStrnCpyS (
    Sig,
    sizeof (Sig),
    (CHAR8 *)&Info->Signature,
    sizeof (Info->Signature)
    );
  if (AsciiStrCmp (Sig, "XenHVMOVMF") == 0) {
    mXenHvmloaderInfo = Info;
  } else {
    mXenHvmloaderInfo = NULL;
  }

  mXenInfo.RsdpPvh = NULL;

  //
  // Locate and use information from the start of day structure if we have
  // booted via the PVH entry point.
  //

  PVHResetVectorData = (VOID *)(UINTN)PcdGet32 (PcdXenPvhStartOfDayStructPtr);
  //
  // That magic value is written in XenResetVector/Ia32/XenPVHMain.asm
  //
  if (PVHResetVectorData[1] == SIGNATURE_32 ('X', 'P', 'V', 'H')) {
    struct hvm_start_info  *HVMStartInfo;

    HVMStartInfo = (VOID *)(UINTN)PVHResetVectorData[0];
    if (HVMStartInfo->magic == XEN_HVM_START_MAGIC_VALUE) {
      ASSERT (HVMStartInfo->rsdp_paddr != 0);
      if (HVMStartInfo->rsdp_paddr != 0) {
        mXenInfo.RsdpPvh = (VOID *)(UINTN)HVMStartInfo->rsdp_paddr;
      }
    }
  }

  BuildGuidDataHob (
    &gEfiXenInfoGuid,
    &mXenInfo,
    sizeof (mXenInfo)
    );

  //
  // Initialize the XenHypercall library, now that the XenInfo HOB is
  // available
  //
  Status = XenHypercallLibInit ();
  ASSERT_RETURN_ERROR (Status);

  return EFI_SUCCESS;
}

/**
  Figures out if we are running inside Xen HVM.

  @retval TRUE   Xen was detected
  @retval FALSE  Xen was not detected

**/
BOOLEAN
XenDetect (
  VOID
  )
{
  UINT8  Signature[13];

  if (mXenLeaf != 0) {
    return TRUE;
  }

  Signature[12] = '\0';
  for (mXenLeaf = 0x40000000; mXenLeaf < 0x40010000; mXenLeaf += 0x100) {
    AsmCpuid (
      mXenLeaf,
      NULL,
      (UINT32 *)&Signature[0],
      (UINT32 *)&Signature[4],
      (UINT32 *)&Signature[8]
      );

    if (!AsciiStrCmp ((CHAR8 *)Signature, "XenVMMXenVMM")) {
      return TRUE;
    }
  }

  mXenLeaf = 0;
  return FALSE;
}

BOOLEAN
XenHvmloaderDetected (
  VOID
  )
{
  return (mXenHvmloaderInfo != NULL);
}

BOOLEAN
XenPvhDetected (
  VOID
  )
{
  //
  // This function should only be used after XenConnect
  //
  ASSERT (mXenInfo.HyperPages != NULL);

  return mXenHvmloaderInfo == NULL;
}

VOID
XenPublishRamRegions (
  VOID
  )
{
  EFI_E820_ENTRY64  *E820Map;
  UINT32            E820EntriesCount;
  EFI_STATUS        Status;
  EFI_E820_ENTRY64  *Entry;
  UINTN             Index;
  UINT64            LapicBase;
  UINT64            LapicEnd;

  DEBUG ((DEBUG_INFO, "Using memory map provided by Xen\n"));

  //
  // Parse RAM in E820 map
  //
  E820EntriesCount = 0;
  Status           = XenGetE820Map (&E820Map, &E820EntriesCount);
  ASSERT_EFI_ERROR (Status);

  AddMemoryBaseSizeHob (0, 0xA0000);
  //
  // Video memory + Legacy BIOS region, to allow Linux to boot.
  //
  AddReservedMemoryBaseSizeHob (0xA0000, BASE_1MB - 0xA0000, TRUE);

  LapicBase = PcdGet32 (PcdCpuLocalApicBaseAddress);
  LapicEnd  = LapicBase + SIZE_1MB;
  AddIoMemoryRangeHob (LapicBase, LapicEnd);

  for (Index = 0; Index < E820EntriesCount; Index++) {
    UINT64  Base;
    UINT64  End;
    UINT64  ReservedBase;
    UINT64  ReservedEnd;

    Entry = &E820Map[Index];

    //
    // Round up the start address, and round down the end address.
    //
    Base = ALIGN_VALUE (Entry->BaseAddr, (UINT64)EFI_PAGE_SIZE);
    End  = (Entry->BaseAddr + Entry->Length) & ~(UINT64)EFI_PAGE_MASK;

    //
    // Ignore the first 1MB, this is handled before the loop.
    //
    if (Base < BASE_1MB) {
      Base = BASE_1MB;
    }

    if (Base >= End) {
      continue;
    }

    switch (Entry->Type) {
      case EfiAcpiAddressRangeMemory:
        AddMemoryRangeHob (Base, End);
        break;
      case EfiAcpiAddressRangeACPI:
        AddReservedMemoryRangeHob (Base, End, FALSE);
        break;
      case EfiAcpiAddressRangeReserved:
        //
        // hvmloader marks a range that overlaps with the local APIC memory
        // mapped region as reserved, but CpuDxe wants it as mapped IO. We
        // have already added it as mapped IO, so skip it here.
        //

        //
        // add LAPIC predecessor range, if any
        //
        ReservedBase = Base;
        ReservedEnd  = MIN (End, LapicBase);
        if (ReservedBase < ReservedEnd) {
          AddReservedMemoryRangeHob (ReservedBase, ReservedEnd, FALSE);
        }

        //
        // add LAPIC successor range, if any
        //
        ReservedBase = MAX (Base, LapicEnd);
        ReservedEnd  = End;
        if (ReservedBase < ReservedEnd) {
          AddReservedMemoryRangeHob (ReservedBase, ReservedEnd, FALSE);
        }

        break;
      default:
        break;
    }
  }
}

EFI_STATUS
PhysicalAddressIdentityMapping (
  IN EFI_PHYSICAL_ADDRESS  AddressToMap
  )
{
  INTN                            Index;
  PAGE_MAP_AND_DIRECTORY_POINTER  *L4, *L3;
  PAGE_TABLE_ENTRY                *PageTable;

  DEBUG ((DEBUG_INFO, "Mapping 1:1 of address 0x%lx\n", (UINT64)AddressToMap));

  // L4 / Top level Page Directory Pointers

  L4    = (VOID *)(UINTN)PcdGet32 (PcdOvmfSecPageTablesBase);
  Index = PML4_OFFSET (AddressToMap);

  if (!L4[Index].Bits.Present) {
    L3 = AllocatePages (1);
    if (L3 == NULL) {
      return EFI_OUT_OF_RESOURCES;
    }

    ZeroMem (L3, EFI_PAGE_SIZE);

    L4[Index].Bits.ReadWrite            = 1;
    L4[Index].Bits.Accessed             = 1;
    L4[Index].Bits.PageTableBaseAddress = (EFI_PHYSICAL_ADDRESS)L3 >> 12;
    L4[Index].Bits.Present              = 1;
  }

  // L3 / Next level Page Directory Pointers

  L3    = (VOID *)(EFI_PHYSICAL_ADDRESS)(L4[Index].Bits.PageTableBaseAddress << 12);
  Index = PDP_OFFSET (AddressToMap);

  if (!L3[Index].Bits.Present) {
    PageTable = AllocatePages (1);
    if (PageTable == NULL) {
      return EFI_OUT_OF_RESOURCES;
    }

    ZeroMem (PageTable, EFI_PAGE_SIZE);

    L3[Index].Bits.ReadWrite            = 1;
    L3[Index].Bits.Accessed             = 1;
    L3[Index].Bits.PageTableBaseAddress = (EFI_PHYSICAL_ADDRESS)PageTable >> 12;
    L3[Index].Bits.Present              = 1;
  }

  // L2 / Page Table Entries

  PageTable = (VOID *)(EFI_PHYSICAL_ADDRESS)(L3[Index].Bits.PageTableBaseAddress << 12);
  Index     = PDE_OFFSET (AddressToMap);

  if (!PageTable[Index].Bits.Present) {
    PageTable[Index].Bits.ReadWrite            = 1;
    PageTable[Index].Bits.Accessed             = 1;
    PageTable[Index].Bits.Dirty                = 1;
    PageTable[Index].Bits.MustBe1              = 1;
    PageTable[Index].Bits.PageTableBaseAddress = AddressToMap >> 21;
    PageTable[Index].Bits.Present              = 1;
  }

  CpuFlushTlb ();

  return EFI_SUCCESS;
}

STATIC
EFI_STATUS
MapSharedInfoPage (
  IN VOID  *PagePtr
  )
{
  xen_add_to_physmap_t  Parameters;
  INTN                  ReturnCode;

  Parameters.domid = DOMID_SELF;
  Parameters.space = XENMAPSPACE_shared_info;
  Parameters.idx   = 0;
  Parameters.gpfn  = (UINTN)PagePtr >> EFI_PAGE_SHIFT;
  ReturnCode       = XenHypercallMemoryOp (XENMEM_add_to_physmap, &Parameters);
  if (ReturnCode != 0) {
    return EFI_NO_MAPPING;
  }

  return EFI_SUCCESS;
}

STATIC
VOID
UnmapXenPage (
  IN VOID  *PagePtr
  )
{
  xen_remove_from_physmap_t  Parameters;
  INTN                       ReturnCode;

  Parameters.domid = DOMID_SELF;
  Parameters.gpfn  = (UINTN)PagePtr >> EFI_PAGE_SHIFT;
  ReturnCode       = XenHypercallMemoryOp (XENMEM_remove_from_physmap, &Parameters);
  ASSERT (ReturnCode == 0);
}

STATIC
UINT64
GetCpuFreq (
  IN XEN_VCPU_TIME_INFO  *VcpuTime
  )
{
  UINT32  Version;
  UINT32  TscToSystemMultiplier;
  INT8    TscShift;
  UINT64  CpuFreq;

  do {
    Version = VcpuTime->Version;
    MemoryFence ();
    TscToSystemMultiplier = VcpuTime->TscToSystemMultiplier;
    TscShift              = VcpuTime->TscShift;
    MemoryFence ();
  } while (((Version & 1) != 0) && (Version != VcpuTime->Version));

  CpuFreq = DivU64x32 (LShiftU64 (1000000000ULL, 32), TscToSystemMultiplier);
  if (TscShift >= 0) {
    CpuFreq = RShiftU64 (CpuFreq, TscShift);
  } else {
    CpuFreq = LShiftU64 (CpuFreq, -TscShift);
  }

  return CpuFreq;
}

STATIC
VOID
XenDelay (
  IN XEN_VCPU_TIME_INFO  *VcpuTimeInfo,
  IN UINT64              DelayNs
  )
{
  UINT64         Tick;
  UINT64         CpuFreq;
  UINT64         Delay;
  UINT64         DelayTick;
  UINT64         NewTick;
  RETURN_STATUS  Status;

  Tick = AsmReadTsc ();

  CpuFreq = GetCpuFreq (VcpuTimeInfo);
  Status  = SafeUint64Mult (DelayNs, CpuFreq, &Delay);
  if (EFI_ERROR (Status)) {
    DEBUG ((
      DEBUG_ERROR,
      "XenDelay (%lu ns): delay too big in relation to CPU freq %lu Hz\n",
      DelayNs,
      CpuFreq
      ));
    ASSERT_EFI_ERROR (Status);
    CpuDeadLoop ();
  }

  DelayTick = DivU64x32 (Delay, 1000000000);

  NewTick = Tick + DelayTick;

  //
  // Check for overflow
  //
  if (NewTick < Tick) {
    //
    // Overflow, wait for TSC to also overflow
    //
    while (AsmReadTsc () >= Tick) {
      CpuPause ();
    }
  }

  while (AsmReadTsc () <= NewTick) {
    CpuPause ();
  }
}

/**
  Calculate the frequency of the Local Apic Timer
**/
VOID
CalibrateLapicTimer (
  VOID
  )
{
  XEN_SHARED_INFO     *SharedInfo;
  XEN_VCPU_TIME_INFO  *VcpuTimeInfo;
  UINT32              TimerTick, TimerTick2, DiffTimer;
  UINT64              TscTick, TscTick2;
  UINT64              Freq;
  UINT64              Dividend;
  EFI_STATUS          Status;

  SharedInfo = (VOID *)((UINTN)PcdGet32 (PcdCpuLocalApicBaseAddress) + SIZE_1MB);
  Status     = PhysicalAddressIdentityMapping ((EFI_PHYSICAL_ADDRESS)SharedInfo);
  if (EFI_ERROR (Status)) {
    DEBUG ((
      DEBUG_ERROR,
      "Failed to add page table entry for Xen shared info page: %r\n",
      Status
      ));
    ASSERT_EFI_ERROR (Status);
    return;
  }

  Status = MapSharedInfoPage (SharedInfo);
  if (EFI_ERROR (Status)) {
    DEBUG ((
      DEBUG_ERROR,
      "Failed to map Xen's shared info page: %r\n",
      Status
      ));
    ASSERT_EFI_ERROR (Status);
    return;
  }

  VcpuTimeInfo = &SharedInfo->VcpuInfo[0].Time;

  InitializeApicTimer (1, MAX_UINT32, TRUE, 0);
  DisableApicTimerInterrupt ();

  TimerTick = GetApicTimerCurrentCount ();
  TscTick   = AsmReadTsc ();
  XenDelay (VcpuTimeInfo, 1000000ULL);
  TimerTick2 = GetApicTimerCurrentCount ();
  TscTick2   = AsmReadTsc ();

  DiffTimer = TimerTick - TimerTick2;
  Status    = SafeUint64Mult (GetCpuFreq (VcpuTimeInfo), DiffTimer, &Dividend);
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "overflow while calculating APIC frequency\n"));
    DEBUG ((
      DEBUG_ERROR,
      "CPU freq: %lu Hz; APIC timer tick count for 1 ms: %u\n",
      GetCpuFreq (VcpuTimeInfo),
      DiffTimer
      ));
    ASSERT_EFI_ERROR (Status);
    CpuDeadLoop ();
  }

  Freq = DivU64x64Remainder (Dividend, TscTick2 - TscTick, NULL);
  DEBUG ((DEBUG_INFO, "APIC Freq % 8lu Hz\n", Freq));

  ASSERT (Freq <= MAX_UINT32);
  Status = PcdSet32S (PcdFSBClock, (UINT32)Freq);
  ASSERT_EFI_ERROR (Status);

  UnmapXenPage (SharedInfo);
}