/**@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); | |
} |