| /**@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 XenVersion; | |
| EFI_XEN_OVMF_INFO *Info; | |
| CHAR8 Sig[sizeof (Info->Signature) + 1]; | |
| UINT32 *PVHResetVectorData; | |
| RETURN_STATUS Status; | |
| ASSERT (mXenLeaf != 0); | |
| // | |
| // 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.VersionMajor); | |
| 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); | |
| } |