/**@file
  Xen Platform PEI support
  Copyright (c) 2006 - 2016, Intel Corporation. All rights reserved.
  Copyright (c) 2011, Andrei Warkentin 
  Copyright (c) 2019, Citrix Systems, Inc.
  SPDX-License-Identifier: BSD-2-Clause-Patent
**/
//
// The package level header files this module uses
//
#include 
//
// The Library classes this module consumes
//
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#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*)((1ULL << mPhysMemAddressWidth) - EFI_PAGE_SIZE);
  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);
}