From c9bba52fc7312e8ea9071f9a4b7005954e36e0c1 Mon Sep 17 00:00:00 2001 From: Laszlo Ersek Date: Wed, 26 May 2021 22:14:14 +0200 Subject: [PATCH] OvmfPkg/XenAcpiPlatformDxe: create from AcpiPlatformDxe MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Create an almost verbatim copy of the "OvmfPkg/AcpiPlatformDxe/AcpiPlatformDxe.inf" driver for the OvmfXen platform. We're going to trim the driver in subsequent patches. Ultimately, the XenAcpiPlatformDxe driver will duplicate a negligible amount of code that is currently present in the QemuFwCfgAcpiPlatformDxe driver. List the new driver in "Maintainers.txt", in the "OvmfPkg: Xen-related modules" section. Switch the OvmfXen platform to the new driver at once. This patch should be reviewed with "git show --find-copies-harder". Cc: Andrew Fish Cc: Anthony Perard Cc: Ard Biesheuvel Cc: Jordan Justen Cc: Julien Grall Cc: Leif Lindholm Cc: Michael D Kinney Cc: Philippe Mathieu-Daudé Ref: https://bugzilla.tianocore.org/show_bug.cgi?id=2122 Signed-off-by: Laszlo Ersek Message-Id: <20210526201446.12554-12-lersek@redhat.com> Reviewed-by: Ard Biesheuvel Reviewed-by: Philippe Mathieu-Daudé Reviewed-by: Leif Lindholm --- Maintainers.txt | 1 + OvmfPkg/OvmfXen.dsc | 2 +- OvmfPkg/OvmfXen.fdf | 2 +- OvmfPkg/XenAcpiPlatformDxe/AcpiPlatform.c | 268 ++++ OvmfPkg/XenAcpiPlatformDxe/AcpiPlatform.h | 102 ++ OvmfPkg/XenAcpiPlatformDxe/BootScript.c | 269 ++++ OvmfPkg/XenAcpiPlatformDxe/EntryPoint.c | 95 ++ OvmfPkg/XenAcpiPlatformDxe/PciDecoding.c | 194 +++ OvmfPkg/XenAcpiPlatformDxe/Qemu.c | 511 +++++++ OvmfPkg/XenAcpiPlatformDxe/QemuFwCfgAcpi.c | 1196 +++++++++++++++++ OvmfPkg/XenAcpiPlatformDxe/Xen.c | 316 +++++ .../XenAcpiPlatformDxe/XenAcpiPlatformDxe.inf | 70 + 12 files changed, 3024 insertions(+), 2 deletions(-) create mode 100644 OvmfPkg/XenAcpiPlatformDxe/AcpiPlatform.c create mode 100644 OvmfPkg/XenAcpiPlatformDxe/AcpiPlatform.h create mode 100644 OvmfPkg/XenAcpiPlatformDxe/BootScript.c create mode 100644 OvmfPkg/XenAcpiPlatformDxe/EntryPoint.c create mode 100644 OvmfPkg/XenAcpiPlatformDxe/PciDecoding.c create mode 100644 OvmfPkg/XenAcpiPlatformDxe/Qemu.c create mode 100644 OvmfPkg/XenAcpiPlatformDxe/QemuFwCfgAcpi.c create mode 100644 OvmfPkg/XenAcpiPlatformDxe/Xen.c create mode 100644 OvmfPkg/XenAcpiPlatformDxe/XenAcpiPlatformDxe.inf diff --git a/Maintainers.txt b/Maintainers.txt index 1ec9185e70..23cb66383a 100644 --- a/Maintainers.txt +++ b/Maintainers.txt @@ -497,6 +497,7 @@ F: OvmfPkg/PlatformPei/MemDetect.c F: OvmfPkg/PlatformPei/Platform.* F: OvmfPkg/PlatformPei/Xen.* F: OvmfPkg/SmbiosPlatformDxe/*Xen.c +F: OvmfPkg/XenAcpiPlatformDxe/ F: OvmfPkg/XenBusDxe/ F: OvmfPkg/XenIoPciDxe/ F: OvmfPkg/XenIoPvhDxe/ diff --git a/OvmfPkg/OvmfXen.dsc b/OvmfPkg/OvmfXen.dsc index e535503e38..4d5171cd43 100644 --- a/OvmfPkg/OvmfXen.dsc +++ b/OvmfPkg/OvmfXen.dsc @@ -652,7 +652,7 @@ # ACPI Support # MdeModulePkg/Universal/Acpi/AcpiTableDxe/AcpiTableDxe.inf - OvmfPkg/AcpiPlatformDxe/AcpiPlatformDxe.inf + OvmfPkg/XenAcpiPlatformDxe/XenAcpiPlatformDxe.inf OvmfPkg/AcpiTables/AcpiTables.inf MdeModulePkg/Universal/Acpi/S3SaveStateDxe/S3SaveStateDxe.inf MdeModulePkg/Universal/Acpi/BootScriptExecutorDxe/BootScriptExecutorDxe.inf diff --git a/OvmfPkg/OvmfXen.fdf b/OvmfPkg/OvmfXen.fdf index c7d4d18530..85306245b5 100644 --- a/OvmfPkg/OvmfXen.fdf +++ b/OvmfPkg/OvmfXen.fdf @@ -352,7 +352,7 @@ INF MdeModulePkg/Universal/SmbiosDxe/SmbiosDxe.inf INF OvmfPkg/SmbiosPlatformDxe/SmbiosPlatformDxe.inf INF MdeModulePkg/Universal/Acpi/AcpiTableDxe/AcpiTableDxe.inf -INF OvmfPkg/AcpiPlatformDxe/AcpiPlatformDxe.inf +INF OvmfPkg/XenAcpiPlatformDxe/XenAcpiPlatformDxe.inf INF RuleOverride=ACPITABLE OvmfPkg/AcpiTables/AcpiTables.inf INF MdeModulePkg/Universal/Acpi/S3SaveStateDxe/S3SaveStateDxe.inf INF MdeModulePkg/Universal/Acpi/BootScriptExecutorDxe/BootScriptExecutorDxe.inf diff --git a/OvmfPkg/XenAcpiPlatformDxe/AcpiPlatform.c b/OvmfPkg/XenAcpiPlatformDxe/AcpiPlatform.c new file mode 100644 index 0000000000..2b2dc57675 --- /dev/null +++ b/OvmfPkg/XenAcpiPlatformDxe/AcpiPlatform.c @@ -0,0 +1,268 @@ +/** @file + OVMF ACPI Platform Driver for Xen guests + + Copyright (C) 2021, Red Hat, Inc. + Copyright (c) 2008 - 2012, Intel Corporation. All rights reserved.
+ SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include // ASSERT_EFI_ERROR() +#include // gBS +#include // XenDetected() +#include // gEfiFirmwareVolume2Protocol... + +#include "AcpiPlatform.h" + +EFI_STATUS +EFIAPI +InstallAcpiTable ( + IN EFI_ACPI_TABLE_PROTOCOL *AcpiProtocol, + IN VOID *AcpiTableBuffer, + IN UINTN AcpiTableBufferSize, + OUT UINTN *TableKey + ) +{ + return AcpiProtocol->InstallAcpiTable ( + AcpiProtocol, + AcpiTableBuffer, + AcpiTableBufferSize, + TableKey + ); +} + + +/** + Locate the first instance of a protocol. If the protocol requested is an + FV protocol, then it will return the first FV that contains the ACPI table + storage file. + + @param Instance Return pointer to the first instance of the protocol + + @return EFI_SUCCESS The function completed successfully. + @return EFI_NOT_FOUND The protocol could not be located. + @return EFI_OUT_OF_RESOURCES There are not enough resources to find the protocol. + +**/ +EFI_STATUS +LocateFvInstanceWithTables ( + OUT EFI_FIRMWARE_VOLUME2_PROTOCOL **Instance + ) +{ + EFI_STATUS Status; + EFI_HANDLE *HandleBuffer; + UINTN NumberOfHandles; + EFI_FV_FILETYPE FileType; + UINT32 FvStatus; + EFI_FV_FILE_ATTRIBUTES Attributes; + UINTN Size; + UINTN Index; + EFI_FIRMWARE_VOLUME2_PROTOCOL *FvInstance; + + FvStatus = 0; + + // + // Locate protocol. + // + Status = gBS->LocateHandleBuffer ( + ByProtocol, + &gEfiFirmwareVolume2ProtocolGuid, + NULL, + &NumberOfHandles, + &HandleBuffer + ); + if (EFI_ERROR (Status)) { + // + // Defined errors at this time are not found and out of resources. + // + return Status; + } + + // + // Looking for FV with ACPI storage file + // + for (Index = 0; Index < NumberOfHandles; Index++) { + // + // Get the protocol on this handle + // This should not fail because of LocateHandleBuffer + // + Status = gBS->HandleProtocol ( + HandleBuffer[Index], + &gEfiFirmwareVolume2ProtocolGuid, + (VOID**) &FvInstance + ); + ASSERT_EFI_ERROR (Status); + + // + // See if it has the ACPI storage file + // + Status = FvInstance->ReadFile ( + FvInstance, + (EFI_GUID*)PcdGetPtr (PcdAcpiTableStorageFile), + NULL, + &Size, + &FileType, + &Attributes, + &FvStatus + ); + + // + // If we found it, then we are done + // + if (Status == EFI_SUCCESS) { + *Instance = FvInstance; + break; + } + } + + // + // Our exit status is determined by the success of the previous operations + // If the protocol was found, Instance already points to it. + // + + // + // Free any allocated buffers + // + gBS->FreePool (HandleBuffer); + + return Status; +} + + +/** + Find ACPI tables in an FV and install them. + + This is now a fall-back path. Normally, we will search for tables provided + by the VMM first. + + If that fails, we use this function to load the ACPI tables from an FV. The + sources for the FV based tables is located under OvmfPkg/AcpiTables. + + @param AcpiTable Protocol instance pointer + +**/ +EFI_STATUS +EFIAPI +InstallOvmfFvTables ( + IN EFI_ACPI_TABLE_PROTOCOL *AcpiTable + ) +{ + EFI_STATUS Status; + EFI_FIRMWARE_VOLUME2_PROTOCOL *FwVol; + INTN Instance; + EFI_ACPI_COMMON_HEADER *CurrentTable; + UINTN TableHandle; + UINT32 FvStatus; + UINTN TableSize; + UINTN Size; + EFI_ACPI_TABLE_INSTALL_ACPI_TABLE TableInstallFunction; + + Instance = 0; + CurrentTable = NULL; + TableHandle = 0; + + if (QemuDetected ()) { + TableInstallFunction = QemuInstallAcpiTable; + } else { + TableInstallFunction = InstallAcpiTable; + } + + // + // set FwVol (and use an ASSERT() below) to suppress incorrect + // compiler/analyzer warnings + // + FwVol = NULL; + // + // Locate the firmware volume protocol + // + Status = LocateFvInstanceWithTables (&FwVol); + if (EFI_ERROR (Status)) { + return EFI_ABORTED; + } + ASSERT (FwVol != NULL); + + // + // Read tables from the storage file. + // + while (Status == EFI_SUCCESS) { + + Status = FwVol->ReadSection ( + FwVol, + (EFI_GUID*)PcdGetPtr (PcdAcpiTableStorageFile), + EFI_SECTION_RAW, + Instance, + (VOID**) &CurrentTable, + &Size, + &FvStatus + ); + if (!EFI_ERROR (Status)) { + // + // Add the table + // + TableHandle = 0; + + TableSize = ((EFI_ACPI_DESCRIPTION_HEADER *) CurrentTable)->Length; + ASSERT (Size >= TableSize); + + // + // Install ACPI table + // + Status = TableInstallFunction ( + AcpiTable, + CurrentTable, + TableSize, + &TableHandle + ); + + // + // Free memory allocated by ReadSection + // + gBS->FreePool (CurrentTable); + + if (EFI_ERROR (Status)) { + return EFI_ABORTED; + } + + // + // Increment the instance + // + Instance++; + CurrentTable = NULL; + } + } + + return EFI_SUCCESS; +} + +/** + Effective entrypoint of Acpi Platform driver. + + @param ImageHandle + @param SystemTable + + @return EFI_SUCCESS + @return EFI_LOAD_ERROR + @return EFI_OUT_OF_RESOURCES + +**/ +EFI_STATUS +EFIAPI +InstallAcpiTables ( + IN EFI_ACPI_TABLE_PROTOCOL *AcpiTable + ) +{ + EFI_STATUS Status; + + if (XenDetected ()) { + Status = InstallXenTables (AcpiTable); + } else { + Status = InstallQemuFwCfgTables (AcpiTable); + } + + if (EFI_ERROR (Status)) { + Status = InstallOvmfFvTables (AcpiTable); + } + + return Status; +} + diff --git a/OvmfPkg/XenAcpiPlatformDxe/AcpiPlatform.h b/OvmfPkg/XenAcpiPlatformDxe/AcpiPlatform.h new file mode 100644 index 0000000000..6259697c4b --- /dev/null +++ b/OvmfPkg/XenAcpiPlatformDxe/AcpiPlatform.h @@ -0,0 +1,102 @@ +/** @file + OVMF ACPI Platform Driver for Xen guests + + Copyright (C) 2021, Red Hat, Inc. + Copyright (c) 2008 - 2012, Intel Corporation. All rights reserved.
+ SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#ifndef ACPI_PLATFORM_H_ +#define ACPI_PLATFORM_H_ + +#include // EFI_ACPI_TABLE_PROTOCOL +#include // EFI_PCI_IO_PROTOCOL + +typedef struct { + EFI_PCI_IO_PROTOCOL *PciIo; + UINT64 PciAttributes; +} ORIGINAL_ATTRIBUTES; + +typedef struct S3_CONTEXT S3_CONTEXT; + +EFI_STATUS +EFIAPI +InstallAcpiTable ( + IN EFI_ACPI_TABLE_PROTOCOL *AcpiProtocol, + IN VOID *AcpiTableBuffer, + IN UINTN AcpiTableBufferSize, + OUT UINTN *TableKey + ); + +BOOLEAN +QemuDetected ( + VOID + ); + +EFI_STATUS +EFIAPI +QemuInstallAcpiTable ( + IN EFI_ACPI_TABLE_PROTOCOL *AcpiProtocol, + IN VOID *AcpiTableBuffer, + IN UINTN AcpiTableBufferSize, + OUT UINTN *TableKey + ); + +EFI_STATUS +EFIAPI +InstallXenTables ( + IN EFI_ACPI_TABLE_PROTOCOL *AcpiProtocol + ); + +EFI_STATUS +EFIAPI +InstallQemuFwCfgTables ( + IN EFI_ACPI_TABLE_PROTOCOL *AcpiProtocol + ); + +EFI_STATUS +EFIAPI +InstallAcpiTables ( + IN EFI_ACPI_TABLE_PROTOCOL *AcpiTable + ); + +VOID +EnablePciDecoding ( + OUT ORIGINAL_ATTRIBUTES **OriginalAttributes, + OUT UINTN *Count + ); + +VOID +RestorePciDecoding ( + IN ORIGINAL_ATTRIBUTES *OriginalAttributes, + IN UINTN Count + ); + +EFI_STATUS +AllocateS3Context ( + OUT S3_CONTEXT **S3Context, + IN UINTN WritePointerCount + ); + +VOID +ReleaseS3Context ( + IN S3_CONTEXT *S3Context + ); + +EFI_STATUS +SaveCondensedWritePointerToS3Context ( + IN OUT S3_CONTEXT *S3Context, + IN UINT16 PointerItem, + IN UINT8 PointerSize, + IN UINT32 PointerOffset, + IN UINT64 PointerValue + ); + +EFI_STATUS +TransferS3ContextToBootScript ( + IN S3_CONTEXT *S3Context + ); + +#endif + diff --git a/OvmfPkg/XenAcpiPlatformDxe/BootScript.c b/OvmfPkg/XenAcpiPlatformDxe/BootScript.c new file mode 100644 index 0000000000..14d1e68694 --- /dev/null +++ b/OvmfPkg/XenAcpiPlatformDxe/BootScript.c @@ -0,0 +1,269 @@ +/** @file + Append an ACPI S3 Boot Script fragment from the QEMU_LOADER_WRITE_POINTER + commands of QEMU's fully processed table linker/loader script. + + Copyright (C) 2017-2021, Red Hat, Inc. + + SPDX-License-Identifier: BSD-2-Clause-Patent +**/ + +#include // CpuDeadLoop() +#include // DEBUG() +#include // AllocatePool() +#include // QemuFwCfgS3ScriptSkipBytes() + +#include "AcpiPlatform.h" + + +// +// Condensed structure for capturing the fw_cfg operations -- select, skip, +// write -- inherent in executing a QEMU_LOADER_WRITE_POINTER command. +// +typedef struct { + UINT16 PointerItem; // resolved from QEMU_LOADER_WRITE_POINTER.PointerFile + UINT8 PointerSize; // copied as-is from QEMU_LOADER_WRITE_POINTER + UINT32 PointerOffset; // copied as-is from QEMU_LOADER_WRITE_POINTER + UINT64 PointerValue; // resolved from QEMU_LOADER_WRITE_POINTER.PointeeFile + // and QEMU_LOADER_WRITE_POINTER.PointeeOffset +} CONDENSED_WRITE_POINTER; + + +// +// Context structure to accumulate CONDENSED_WRITE_POINTER objects from +// QEMU_LOADER_WRITE_POINTER commands. +// +// Any pointers in this structure own the pointed-to objects; that is, when the +// context structure is released, all pointed-to objects must be released too. +// +struct S3_CONTEXT { + CONDENSED_WRITE_POINTER *WritePointers; // one array element per processed + // QEMU_LOADER_WRITE_POINTER + // command + UINTN Allocated; // number of elements allocated for + // WritePointers + UINTN Used; // number of elements populated in + // WritePointers +}; + + +// +// Scratch buffer, allocated in EfiReservedMemoryType type memory, for the ACPI +// S3 Boot Script opcodes to work on. +// +#pragma pack (1) +typedef union { + UINT64 PointerValue; // filled in from CONDENSED_WRITE_POINTER.PointerValue +} SCRATCH_BUFFER; +#pragma pack () + + +/** + Allocate an S3_CONTEXT object. + + @param[out] S3Context The allocated S3_CONTEXT object is returned + through this parameter. + + @param[in] WritePointerCount Number of CONDENSED_WRITE_POINTER elements to + allocate room for. WritePointerCount must be + positive. + + @retval EFI_SUCCESS Allocation successful. + + @retval EFI_OUT_OF_RESOURCES Out of memory. + + @retval EFI_INVALID_PARAMETER WritePointerCount is zero. +**/ +EFI_STATUS +AllocateS3Context ( + OUT S3_CONTEXT **S3Context, + IN UINTN WritePointerCount + ) +{ + EFI_STATUS Status; + S3_CONTEXT *Context; + + if (WritePointerCount == 0) { + return EFI_INVALID_PARAMETER; + } + + Context = AllocateZeroPool (sizeof *Context); + if (Context == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + Context->WritePointers = AllocatePool (WritePointerCount * + sizeof *Context->WritePointers); + if (Context->WritePointers == NULL) { + Status = EFI_OUT_OF_RESOURCES; + goto FreeContext; + } + + Context->Allocated = WritePointerCount; + *S3Context = Context; + return EFI_SUCCESS; + +FreeContext: + FreePool (Context); + + return Status; +} + + +/** + Release an S3_CONTEXT object. + + @param[in] S3Context The object to release. +**/ +VOID +ReleaseS3Context ( + IN S3_CONTEXT *S3Context + ) +{ + FreePool (S3Context->WritePointers); + FreePool (S3Context); +} + + +/** + Save the information necessary to replicate a QEMU_LOADER_WRITE_POINTER + command during S3 resume, in condensed format. + + This function is to be called from ProcessCmdWritePointer(), after all the + sanity checks have passed, and before the fw_cfg operations are performed. + + @param[in,out] S3Context The S3_CONTEXT object into which the caller wants + to save the information that was derived from + QEMU_LOADER_WRITE_POINTER. + + @param[in] PointerItem The FIRMWARE_CONFIG_ITEM that + QEMU_LOADER_WRITE_POINTER.PointerFile was resolved + to, expressed as a UINT16 value. + + @param[in] PointerSize Copied directly from + QEMU_LOADER_WRITE_POINTER.PointerSize. + + @param[in] PointerOffset Copied directly from + QEMU_LOADER_WRITE_POINTER.PointerOffset. + + @param[in] PointerValue The base address of the allocated / downloaded + fw_cfg blob that is identified by + QEMU_LOADER_WRITE_POINTER.PointeeFile, plus + QEMU_LOADER_WRITE_POINTER.PointeeOffset. + + @retval EFI_SUCCESS The information derived from + QEMU_LOADER_WRITE_POINTER has been successfully + absorbed into S3Context. + + @retval EFI_OUT_OF_RESOURCES No room available in S3Context. +**/ +EFI_STATUS +SaveCondensedWritePointerToS3Context ( + IN OUT S3_CONTEXT *S3Context, + IN UINT16 PointerItem, + IN UINT8 PointerSize, + IN UINT32 PointerOffset, + IN UINT64 PointerValue + ) +{ + CONDENSED_WRITE_POINTER *Condensed; + + if (S3Context->Used == S3Context->Allocated) { + return EFI_OUT_OF_RESOURCES; + } + Condensed = S3Context->WritePointers + S3Context->Used; + Condensed->PointerItem = PointerItem; + Condensed->PointerSize = PointerSize; + Condensed->PointerOffset = PointerOffset; + Condensed->PointerValue = PointerValue; + DEBUG ((DEBUG_VERBOSE, "%a: 0x%04x/[0x%08x+%d] := 0x%Lx (%Lu)\n", + __FUNCTION__, PointerItem, PointerOffset, PointerSize, PointerValue, + (UINT64)S3Context->Used)); + ++S3Context->Used; + return EFI_SUCCESS; +} + + +/** + FW_CFG_BOOT_SCRIPT_CALLBACK_FUNCTION provided to QemuFwCfgS3Lib. +**/ +STATIC +VOID +EFIAPI +AppendFwCfgBootScript ( + IN OUT VOID *Context, OPTIONAL + IN OUT VOID *ExternalScratchBuffer + ) +{ + S3_CONTEXT *S3Context; + SCRATCH_BUFFER *ScratchBuffer; + UINTN Index; + + S3Context = Context; + ScratchBuffer = ExternalScratchBuffer; + + for (Index = 0; Index < S3Context->Used; ++Index) { + CONST CONDENSED_WRITE_POINTER *Condensed; + RETURN_STATUS Status; + + Condensed = &S3Context->WritePointers[Index]; + + Status = QemuFwCfgS3ScriptSkipBytes (Condensed->PointerItem, + Condensed->PointerOffset); + if (RETURN_ERROR (Status)) { + goto FatalError; + } + + ScratchBuffer->PointerValue = Condensed->PointerValue; + Status = QemuFwCfgS3ScriptWriteBytes (-1, Condensed->PointerSize); + if (RETURN_ERROR (Status)) { + goto FatalError; + } + } + + DEBUG ((DEBUG_VERBOSE, "%a: boot script fragment saved\n", __FUNCTION__)); + + ReleaseS3Context (S3Context); + return; + +FatalError: + ASSERT (FALSE); + CpuDeadLoop (); +} + + +/** + Translate and append the information from an S3_CONTEXT object to the ACPI S3 + Boot Script. + + The effects of a successful call to this function cannot be undone. + + @param[in] S3Context The S3_CONTEXT object to translate to ACPI S3 Boot + Script opcodes. If the function returns successfully, + the caller must set the S3Context pointer -- originally + returned by AllocateS3Context() -- immediately to NULL, + because the ownership of S3Context has been transferred. + + @retval EFI_SUCCESS The translation of S3Context to ACPI S3 Boot Script + opcodes has been successfully executed or queued. (This + includes the case when S3Context was empty on input and + no ACPI S3 Boot Script opcodes have been necessary to + produce.) + + @return Error codes from underlying functions. +**/ +EFI_STATUS +TransferS3ContextToBootScript ( + IN S3_CONTEXT *S3Context + ) +{ + RETURN_STATUS Status; + + if (S3Context->Used == 0) { + ReleaseS3Context (S3Context); + return EFI_SUCCESS; + } + + Status = QemuFwCfgS3CallWhenBootScriptReady (AppendFwCfgBootScript, + S3Context, sizeof (SCRATCH_BUFFER)); + return (EFI_STATUS)Status; +} diff --git a/OvmfPkg/XenAcpiPlatformDxe/EntryPoint.c b/OvmfPkg/XenAcpiPlatformDxe/EntryPoint.c new file mode 100644 index 0000000000..b6d0835fe3 --- /dev/null +++ b/OvmfPkg/XenAcpiPlatformDxe/EntryPoint.c @@ -0,0 +1,95 @@ +/** @file + Entry point of OVMF ACPI Platform Driver for Xen guests + + Copyright (C) 2015-2021, Red Hat, Inc. + Copyright (c) 2008 - 2015, Intel Corporation. All rights reserved.
+ + SPDX-License-Identifier: BSD-2-Clause-Patent +**/ + +#include // gRootBridgesConnectedEve... +#include // DEBUG() +#include // PcdGetBool() +#include // gBS +#include // EFI_ACPI_TABLE_PROTOCOL + +#include "AcpiPlatform.h" + +STATIC +EFI_ACPI_TABLE_PROTOCOL * +FindAcpiTableProtocol ( + VOID + ) +{ + EFI_STATUS Status; + EFI_ACPI_TABLE_PROTOCOL *AcpiTable; + + Status = gBS->LocateProtocol ( + &gEfiAcpiTableProtocolGuid, + NULL, + (VOID**)&AcpiTable + ); + ASSERT_EFI_ERROR (Status); + return AcpiTable; +} + + +STATIC +VOID +EFIAPI +OnRootBridgesConnected ( + IN EFI_EVENT Event, + IN VOID *Context + ) +{ + EFI_STATUS Status; + + DEBUG ((DEBUG_INFO, + "%a: root bridges have been connected, installing ACPI tables\n", + __FUNCTION__)); + Status = InstallAcpiTables (FindAcpiTableProtocol ()); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "%a: InstallAcpiTables: %r\n", __FUNCTION__, Status)); + } + gBS->CloseEvent (Event); +} + + +EFI_STATUS +EFIAPI +AcpiPlatformEntryPoint ( + IN EFI_HANDLE ImageHandle, + IN EFI_SYSTEM_TABLE *SystemTable + ) +{ + EFI_STATUS Status; + EFI_EVENT RootBridgesConnected; + + // + // If the platform doesn't support PCI, or PCI enumeration has been disabled, + // install the tables at once, and let the entry point's return code reflect + // the full functionality. + // + if (PcdGetBool (PcdPciDisableBusEnumeration)) { + DEBUG ((DEBUG_INFO, "%a: PCI or its enumeration disabled, installing " + "ACPI tables\n", __FUNCTION__)); + return InstallAcpiTables (FindAcpiTableProtocol ()); + } + + // + // Otherwise, delay installing the ACPI tables until root bridges are + // connected. The entry point's return status will only reflect the callback + // setup. (Note that we're a DXE_DRIVER; our entry point function is invoked + // strictly before BDS is entered and can connect the root bridges.) + // + Status = gBS->CreateEventEx (EVT_NOTIFY_SIGNAL, TPL_CALLBACK, + OnRootBridgesConnected, NULL /* Context */, + &gRootBridgesConnectedEventGroupGuid, &RootBridgesConnected); + if (!EFI_ERROR (Status)) { + DEBUG ((DEBUG_INFO, + "%a: waiting for root bridges to be connected, registered callback\n", + __FUNCTION__)); + } + + return Status; +} diff --git a/OvmfPkg/XenAcpiPlatformDxe/PciDecoding.c b/OvmfPkg/XenAcpiPlatformDxe/PciDecoding.c new file mode 100644 index 0000000000..00fc57eb13 --- /dev/null +++ b/OvmfPkg/XenAcpiPlatformDxe/PciDecoding.c @@ -0,0 +1,194 @@ +/** @file + Temporarily enable IO and MMIO decoding for all PCI devices while QEMU + regenerates the ACPI tables. + + Copyright (C) 2016-2021, Red Hat, Inc. + + SPDX-License-Identifier: BSD-2-Clause-Patent +**/ + +#include // DEBUG() +#include // AllocatePool() +#include // gBS + +#include "AcpiPlatform.h" + + +/** + Collect all PciIo protocol instances in the system. Save their original + attributes, and enable IO and MMIO decoding for each. + + This is a best effort function; it doesn't return status codes. Its + caller is supposed to proceed even if this function fails. + + @param[out] OriginalAttributes On output, a dynamically allocated array of + ORIGINAL_ATTRIBUTES elements. The array lists + the PciIo protocol instances found in the + system at the time of the call, plus the + original PCI attributes for each. + + Before returning, the function enables IO and + MMIO decoding for each PciIo instance it + finds. + + On error, or when no such instances are + found, OriginalAttributes is set to NULL. + + @param[out] Count On output, the number of elements in + OriginalAttributes. On error it is set to + zero. +**/ +VOID +EnablePciDecoding ( + OUT ORIGINAL_ATTRIBUTES **OriginalAttributes, + OUT UINTN *Count + ) +{ + EFI_STATUS Status; + UINTN NoHandles; + EFI_HANDLE *Handles; + ORIGINAL_ATTRIBUTES *OrigAttrs; + UINTN Idx; + + *OriginalAttributes = NULL; + *Count = 0; + + if (PcdGetBool (PcdPciDisableBusEnumeration)) { + // + // The platform downloads ACPI tables from QEMU in general, but there are + // no root bridges in this execution. We're done. + // + return; + } + + Status = gBS->LocateHandleBuffer (ByProtocol, &gEfiPciIoProtocolGuid, + NULL /* SearchKey */, &NoHandles, &Handles); + if (Status == EFI_NOT_FOUND) { + // + // No PCI devices were found on either of the root bridges. We're done. + // + return; + } + + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_WARN, "%a: LocateHandleBuffer(): %r\n", __FUNCTION__, + Status)); + return; + } + + OrigAttrs = AllocatePool (NoHandles * sizeof *OrigAttrs); + if (OrigAttrs == NULL) { + DEBUG ((DEBUG_WARN, "%a: AllocatePool(): out of resources\n", + __FUNCTION__)); + goto FreeHandles; + } + + for (Idx = 0; Idx < NoHandles; ++Idx) { + EFI_PCI_IO_PROTOCOL *PciIo; + UINT64 Attributes; + + // + // Look up PciIo on the handle and stash it + // + Status = gBS->HandleProtocol (Handles[Idx], &gEfiPciIoProtocolGuid, + (VOID**)&PciIo); + ASSERT_EFI_ERROR (Status); + OrigAttrs[Idx].PciIo = PciIo; + + // + // Stash the current attributes + // + Status = PciIo->Attributes (PciIo, EfiPciIoAttributeOperationGet, 0, + &OrigAttrs[Idx].PciAttributes); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_WARN, "%a: EfiPciIoAttributeOperationGet: %r\n", + __FUNCTION__, Status)); + goto RestoreAttributes; + } + + // + // Retrieve supported attributes + // + Status = PciIo->Attributes (PciIo, EfiPciIoAttributeOperationSupported, 0, + &Attributes); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_WARN, "%a: EfiPciIoAttributeOperationSupported: %r\n", + __FUNCTION__, Status)); + goto RestoreAttributes; + } + + // + // Enable IO and MMIO decoding + // + Attributes &= EFI_PCI_IO_ATTRIBUTE_IO | EFI_PCI_IO_ATTRIBUTE_MEMORY; + Status = PciIo->Attributes (PciIo, EfiPciIoAttributeOperationEnable, + Attributes, NULL); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_WARN, "%a: EfiPciIoAttributeOperationEnable: %r\n", + __FUNCTION__, Status)); + goto RestoreAttributes; + } + } + + // + // Success + // + FreePool (Handles); + *OriginalAttributes = OrigAttrs; + *Count = NoHandles; + return; + +RestoreAttributes: + while (Idx > 0) { + --Idx; + OrigAttrs[Idx].PciIo->Attributes (OrigAttrs[Idx].PciIo, + EfiPciIoAttributeOperationSet, + OrigAttrs[Idx].PciAttributes, + NULL + ); + } + FreePool (OrigAttrs); + +FreeHandles: + FreePool (Handles); +} + + +/** + Restore the original PCI attributes saved with EnablePciDecoding(). + + @param[in] OriginalAttributes The array allocated and populated by + EnablePciDecoding(). This parameter may be + NULL. If OriginalAttributes is NULL, then the + function is a no-op; otherwise the PciIo + attributes will be restored, and the + OriginalAttributes array will be freed. + + @param[in] Count The Count value stored by EnablePciDecoding(), + the number of elements in OriginalAttributes. + Count may be zero if and only if + OriginalAttributes is NULL. +**/ +VOID +RestorePciDecoding ( + IN ORIGINAL_ATTRIBUTES *OriginalAttributes, + IN UINTN Count + ) +{ + UINTN Idx; + + ASSERT ((OriginalAttributes == NULL) == (Count == 0)); + if (OriginalAttributes == NULL) { + return; + } + + for (Idx = 0; Idx < Count; ++Idx) { + OriginalAttributes[Idx].PciIo->Attributes ( + OriginalAttributes[Idx].PciIo, + EfiPciIoAttributeOperationSet, + OriginalAttributes[Idx].PciAttributes, + NULL + ); + } + FreePool (OriginalAttributes); +} diff --git a/OvmfPkg/XenAcpiPlatformDxe/Qemu.c b/OvmfPkg/XenAcpiPlatformDxe/Qemu.c new file mode 100644 index 0000000000..b4a407c41b --- /dev/null +++ b/OvmfPkg/XenAcpiPlatformDxe/Qemu.c @@ -0,0 +1,511 @@ +/** @file + OVMF ACPI QEMU support + + Copyright (C) 2012-2021, Red Hat, Inc. + Copyright (c) 2008 - 2014, Intel Corporation. All rights reserved.
+ + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include // EFI_ACPI_1_0_IO_APIC_STRUCTURE +#include // CopyMem() +#include // DEBUG() +#include // gDS +#include // AllocatePool() +#include // PcdGet16() +#include // QemuFwCfgIsAvailable() + +#include "AcpiPlatform.h" + +BOOLEAN +QemuDetected ( + VOID + ) +{ + if (!QemuFwCfgIsAvailable ()) { + return FALSE; + } + + return TRUE; +} + + +STATIC +UINTN +CountBits16 ( + UINT16 Mask + ) +{ + // + // For all N >= 1, N bits are enough to represent the number of bits set + // among N bits. It's true for N == 1. When adding a new bit (N := N+1), + // the maximum number of possibly set bits increases by one, while the + // representable maximum doubles. + // + Mask = ((Mask & 0xAAAA) >> 1) + (Mask & 0x5555); + Mask = ((Mask & 0xCCCC) >> 2) + (Mask & 0x3333); + Mask = ((Mask & 0xF0F0) >> 4) + (Mask & 0x0F0F); + Mask = ((Mask & 0xFF00) >> 8) + (Mask & 0x00FF); + + return Mask; +} + + +STATIC +EFI_STATUS +EFIAPI +QemuInstallAcpiMadtTable ( + IN EFI_ACPI_TABLE_PROTOCOL *AcpiProtocol, + IN VOID *AcpiTableBuffer, + IN UINTN AcpiTableBufferSize, + OUT UINTN *TableKey + ) +{ + UINTN CpuCount; + UINTN PciLinkIsoCount; + UINTN NewBufferSize; + EFI_ACPI_1_0_MULTIPLE_APIC_DESCRIPTION_TABLE_HEADER *Madt; + EFI_ACPI_1_0_PROCESSOR_LOCAL_APIC_STRUCTURE *LocalApic; + EFI_ACPI_1_0_IO_APIC_STRUCTURE *IoApic; + EFI_ACPI_1_0_INTERRUPT_SOURCE_OVERRIDE_STRUCTURE *Iso; + EFI_ACPI_1_0_LOCAL_APIC_NMI_STRUCTURE *LocalApicNmi; + VOID *Ptr; + UINTN Loop; + EFI_STATUS Status; + + ASSERT (AcpiTableBufferSize >= sizeof (EFI_ACPI_DESCRIPTION_HEADER)); + + QemuFwCfgSelectItem (QemuFwCfgItemSmpCpuCount); + CpuCount = QemuFwCfgRead16 (); + ASSERT (CpuCount >= 1); + + // + // Set Level-tiggered, Active High for these identity mapped IRQs. The bitset + // corresponds to the union of all possible interrupt assignments for the LNKA, + // LNKB, LNKC, LNKD PCI interrupt lines. See the DSDT. + // + PciLinkIsoCount = CountBits16 (PcdGet16 (Pcd8259LegacyModeEdgeLevel)); + + NewBufferSize = 1 * sizeof (*Madt) + + CpuCount * sizeof (*LocalApic) + + 1 * sizeof (*IoApic) + + (1 + PciLinkIsoCount) * sizeof (*Iso) + + 1 * sizeof (*LocalApicNmi); + + Madt = AllocatePool (NewBufferSize); + if (Madt == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + CopyMem (&(Madt->Header), AcpiTableBuffer, sizeof (EFI_ACPI_DESCRIPTION_HEADER)); + Madt->Header.Length = (UINT32) NewBufferSize; + Madt->LocalApicAddress = PcdGet32 (PcdCpuLocalApicBaseAddress); + Madt->Flags = EFI_ACPI_1_0_PCAT_COMPAT; + Ptr = Madt + 1; + + LocalApic = Ptr; + for (Loop = 0; Loop < CpuCount; ++Loop) { + LocalApic->Type = EFI_ACPI_1_0_PROCESSOR_LOCAL_APIC; + LocalApic->Length = sizeof (*LocalApic); + LocalApic->AcpiProcessorId = (UINT8) Loop; + LocalApic->ApicId = (UINT8) Loop; + LocalApic->Flags = 1; // enabled + ++LocalApic; + } + Ptr = LocalApic; + + IoApic = Ptr; + IoApic->Type = EFI_ACPI_1_0_IO_APIC; + IoApic->Length = sizeof (*IoApic); + IoApic->IoApicId = (UINT8) CpuCount; + IoApic->Reserved = EFI_ACPI_RESERVED_BYTE; + IoApic->IoApicAddress = 0xFEC00000; + IoApic->SystemVectorBase = 0x00000000; + Ptr = IoApic + 1; + + // + // IRQ0 (8254 Timer) => IRQ2 (PIC) Interrupt Source Override Structure + // + Iso = Ptr; + Iso->Type = EFI_ACPI_1_0_INTERRUPT_SOURCE_OVERRIDE; + Iso->Length = sizeof (*Iso); + Iso->Bus = 0x00; // ISA + Iso->Source = 0x00; // IRQ0 + Iso->GlobalSystemInterruptVector = 0x00000002; + Iso->Flags = 0x0000; // Conforms to specs of the bus + ++Iso; + + // + // Set Level-triggered, Active High for all possible PCI link targets. + // + for (Loop = 0; Loop < 16; ++Loop) { + if ((PcdGet16 (Pcd8259LegacyModeEdgeLevel) & (1 << Loop)) == 0) { + continue; + } + Iso->Type = EFI_ACPI_1_0_INTERRUPT_SOURCE_OVERRIDE; + Iso->Length = sizeof (*Iso); + Iso->Bus = 0x00; // ISA + Iso->Source = (UINT8) Loop; + Iso->GlobalSystemInterruptVector = (UINT32) Loop; + Iso->Flags = 0x000D; // Level-triggered, Active High + ++Iso; + } + ASSERT ( + (UINTN) (Iso - (EFI_ACPI_1_0_INTERRUPT_SOURCE_OVERRIDE_STRUCTURE *)Ptr) == + 1 + PciLinkIsoCount + ); + Ptr = Iso; + + LocalApicNmi = Ptr; + LocalApicNmi->Type = EFI_ACPI_1_0_LOCAL_APIC_NMI; + LocalApicNmi->Length = sizeof (*LocalApicNmi); + LocalApicNmi->AcpiProcessorId = 0xFF; // applies to all processors + // + // polarity and trigger mode of the APIC I/O input signals conform to the + // specifications of the bus + // + LocalApicNmi->Flags = 0x0000; + // + // Local APIC interrupt input LINTn to which NMI is connected. + // + LocalApicNmi->LocalApicInti = 0x01; + Ptr = LocalApicNmi + 1; + + ASSERT ((UINTN) ((UINT8 *)Ptr - (UINT8 *)Madt) == NewBufferSize); + Status = InstallAcpiTable (AcpiProtocol, Madt, NewBufferSize, TableKey); + + FreePool (Madt); + + return Status; +} + + +#pragma pack(1) + +typedef struct { + UINT64 Base; + UINT64 End; + UINT64 Length; +} PCI_WINDOW; + +typedef struct { + PCI_WINDOW PciWindow32; + PCI_WINDOW PciWindow64; +} FIRMWARE_DATA; + +typedef struct { + UINT8 BytePrefix; + UINT8 ByteValue; +} AML_BYTE; + +typedef struct { + UINT8 NameOp; + UINT8 RootChar; + UINT8 NameChar[4]; + UINT8 PackageOp; + UINT8 PkgLength; + UINT8 NumElements; + AML_BYTE Pm1aCntSlpTyp; + AML_BYTE Pm1bCntSlpTyp; + AML_BYTE Reserved[2]; +} SYSTEM_STATE_PACKAGE; + +#pragma pack() + + +STATIC +EFI_STATUS +EFIAPI +PopulateFwData( + OUT FIRMWARE_DATA *FwData + ) +{ + EFI_STATUS Status; + UINTN NumDesc; + EFI_GCD_MEMORY_SPACE_DESCRIPTOR *AllDesc; + + Status = gDS->GetMemorySpaceMap (&NumDesc, &AllDesc); + if (Status == EFI_SUCCESS) { + UINT64 NonMmio32MaxExclTop; + UINT64 Mmio32MinBase; + UINT64 Mmio32MaxExclTop; + UINTN CurDesc; + + Status = EFI_UNSUPPORTED; + + NonMmio32MaxExclTop = 0; + Mmio32MinBase = BASE_4GB; + Mmio32MaxExclTop = 0; + + for (CurDesc = 0; CurDesc < NumDesc; ++CurDesc) { + CONST EFI_GCD_MEMORY_SPACE_DESCRIPTOR *Desc; + UINT64 ExclTop; + + Desc = &AllDesc[CurDesc]; + ExclTop = Desc->BaseAddress + Desc->Length; + + if (ExclTop <= (UINT64) PcdGet32 (PcdOvmfFdBaseAddress)) { + switch (Desc->GcdMemoryType) { + case EfiGcdMemoryTypeNonExistent: + break; + + case EfiGcdMemoryTypeReserved: + case EfiGcdMemoryTypeSystemMemory: + if (NonMmio32MaxExclTop < ExclTop) { + NonMmio32MaxExclTop = ExclTop; + } + break; + + case EfiGcdMemoryTypeMemoryMappedIo: + if (Mmio32MinBase > Desc->BaseAddress) { + Mmio32MinBase = Desc->BaseAddress; + } + if (Mmio32MaxExclTop < ExclTop) { + Mmio32MaxExclTop = ExclTop; + } + break; + + default: + ASSERT(0); + } + } + } + + if (Mmio32MinBase < NonMmio32MaxExclTop) { + Mmio32MinBase = NonMmio32MaxExclTop; + } + + if (Mmio32MinBase < Mmio32MaxExclTop) { + FwData->PciWindow32.Base = Mmio32MinBase; + FwData->PciWindow32.End = Mmio32MaxExclTop - 1; + FwData->PciWindow32.Length = Mmio32MaxExclTop - Mmio32MinBase; + + FwData->PciWindow64.Base = 0; + FwData->PciWindow64.End = 0; + FwData->PciWindow64.Length = 0; + + Status = EFI_SUCCESS; + } + + FreePool (AllDesc); + } + + DEBUG (( + DEBUG_INFO, + "ACPI PciWindow32: Base=0x%08lx End=0x%08lx Length=0x%08lx\n", + FwData->PciWindow32.Base, + FwData->PciWindow32.End, + FwData->PciWindow32.Length + )); + DEBUG (( + DEBUG_INFO, + "ACPI PciWindow64: Base=0x%08lx End=0x%08lx Length=0x%08lx\n", + FwData->PciWindow64.Base, + FwData->PciWindow64.End, + FwData->PciWindow64.Length + )); + + return Status; +} + + +STATIC +VOID +EFIAPI +GetSuspendStates ( + UINTN *SuspendToRamSize, + SYSTEM_STATE_PACKAGE *SuspendToRam, + UINTN *SuspendToDiskSize, + SYSTEM_STATE_PACKAGE *SuspendToDisk + ) +{ + STATIC CONST SYSTEM_STATE_PACKAGE Template = { + 0x08, // NameOp + '\\', // RootChar + { '_', 'S', 'x', '_' }, // NameChar[4] + 0x12, // PackageOp + 0x0A, // PkgLength + 0x04, // NumElements + { 0x0A, 0x00 }, // Pm1aCntSlpTyp + { 0x0A, 0x00 }, // Pm1bCntSlpTyp -- we don't support it + { // Reserved[2] + { 0x0A, 0x00 }, + { 0x0A, 0x00 } + } + }; + RETURN_STATUS Status; + FIRMWARE_CONFIG_ITEM FwCfgItem; + UINTN FwCfgSize; + UINT8 SystemStates[6]; + + // + // configure defaults + // + *SuspendToRamSize = sizeof Template; + CopyMem (SuspendToRam, &Template, sizeof Template); + SuspendToRam->NameChar[2] = '3'; // S3 + SuspendToRam->Pm1aCntSlpTyp.ByteValue = 1; // PIIX4: STR + + *SuspendToDiskSize = sizeof Template; + CopyMem (SuspendToDisk, &Template, sizeof Template); + SuspendToDisk->NameChar[2] = '4'; // S4 + SuspendToDisk->Pm1aCntSlpTyp.ByteValue = 2; // PIIX4: POSCL + + // + // check for overrides + // + Status = QemuFwCfgFindFile ("etc/system-states", &FwCfgItem, &FwCfgSize); + if (Status != RETURN_SUCCESS || FwCfgSize != sizeof SystemStates) { + DEBUG ((DEBUG_INFO, "ACPI using S3/S4 defaults\n")); + return; + } + QemuFwCfgSelectItem (FwCfgItem); + QemuFwCfgReadBytes (sizeof SystemStates, SystemStates); + + // + // Each byte corresponds to a system state. In each byte, the MSB tells us + // whether the given state is enabled. If so, the three LSBs specify the + // value to be written to the PM control register's SUS_TYP bits. + // + if (SystemStates[3] & BIT7) { + SuspendToRam->Pm1aCntSlpTyp.ByteValue = + SystemStates[3] & (BIT2 | BIT1 | BIT0); + DEBUG ((DEBUG_INFO, "ACPI S3 value: %d\n", + SuspendToRam->Pm1aCntSlpTyp.ByteValue)); + } else { + *SuspendToRamSize = 0; + DEBUG ((DEBUG_INFO, "ACPI S3 disabled\n")); + } + + if (SystemStates[4] & BIT7) { + SuspendToDisk->Pm1aCntSlpTyp.ByteValue = + SystemStates[4] & (BIT2 | BIT1 | BIT0); + DEBUG ((DEBUG_INFO, "ACPI S4 value: %d\n", + SuspendToDisk->Pm1aCntSlpTyp.ByteValue)); + } else { + *SuspendToDiskSize = 0; + DEBUG ((DEBUG_INFO, "ACPI S4 disabled\n")); + } +} + + +STATIC +EFI_STATUS +EFIAPI +QemuInstallAcpiSsdtTable ( + IN EFI_ACPI_TABLE_PROTOCOL *AcpiProtocol, + IN VOID *AcpiTableBuffer, + IN UINTN AcpiTableBufferSize, + OUT UINTN *TableKey + ) +{ + EFI_STATUS Status; + FIRMWARE_DATA *FwData; + + Status = EFI_OUT_OF_RESOURCES; + + FwData = AllocateReservedPool (sizeof (*FwData)); + if (FwData != NULL) { + UINTN SuspendToRamSize; + SYSTEM_STATE_PACKAGE SuspendToRam; + UINTN SuspendToDiskSize; + SYSTEM_STATE_PACKAGE SuspendToDisk; + UINTN SsdtSize; + UINT8 *Ssdt; + + GetSuspendStates (&SuspendToRamSize, &SuspendToRam, + &SuspendToDiskSize, &SuspendToDisk); + SsdtSize = AcpiTableBufferSize + 17 + SuspendToRamSize + SuspendToDiskSize; + Ssdt = AllocatePool (SsdtSize); + + if (Ssdt != NULL) { + Status = PopulateFwData (FwData); + + if (Status == EFI_SUCCESS) { + UINT8 *SsdtPtr; + + SsdtPtr = Ssdt; + + CopyMem (SsdtPtr, AcpiTableBuffer, AcpiTableBufferSize); + SsdtPtr += AcpiTableBufferSize; + + // + // build "OperationRegion(FWDT, SystemMemory, 0x12345678, 0x87654321)" + // + *(SsdtPtr++) = 0x5B; // ExtOpPrefix + *(SsdtPtr++) = 0x80; // OpRegionOp + *(SsdtPtr++) = 'F'; + *(SsdtPtr++) = 'W'; + *(SsdtPtr++) = 'D'; + *(SsdtPtr++) = 'T'; + *(SsdtPtr++) = 0x00; // SystemMemory + *(SsdtPtr++) = 0x0C; // DWordPrefix + + // + // no virtual addressing yet, take the four least significant bytes + // + CopyMem(SsdtPtr, &FwData, 4); + SsdtPtr += 4; + + *(SsdtPtr++) = 0x0C; // DWordPrefix + + *(UINT32*) SsdtPtr = sizeof (*FwData); + SsdtPtr += 4; + + // + // add suspend system states + // + CopyMem (SsdtPtr, &SuspendToRam, SuspendToRamSize); + SsdtPtr += SuspendToRamSize; + CopyMem (SsdtPtr, &SuspendToDisk, SuspendToDiskSize); + SsdtPtr += SuspendToDiskSize; + + ASSERT((UINTN) (SsdtPtr - Ssdt) == SsdtSize); + ((EFI_ACPI_DESCRIPTION_HEADER *) Ssdt)->Length = (UINT32) SsdtSize; + Status = InstallAcpiTable (AcpiProtocol, Ssdt, SsdtSize, TableKey); + } + + FreePool(Ssdt); + } + + if (Status != EFI_SUCCESS) { + FreePool(FwData); + } + } + + return Status; +} + + +EFI_STATUS +EFIAPI +QemuInstallAcpiTable ( + IN EFI_ACPI_TABLE_PROTOCOL *AcpiProtocol, + IN VOID *AcpiTableBuffer, + IN UINTN AcpiTableBufferSize, + OUT UINTN *TableKey + ) +{ + EFI_ACPI_DESCRIPTION_HEADER *Hdr; + EFI_ACPI_TABLE_INSTALL_ACPI_TABLE TableInstallFunction; + + Hdr = (EFI_ACPI_DESCRIPTION_HEADER*) AcpiTableBuffer; + switch (Hdr->Signature) { + case EFI_ACPI_1_0_APIC_SIGNATURE: + TableInstallFunction = QemuInstallAcpiMadtTable; + break; + case EFI_ACPI_1_0_SECONDARY_SYSTEM_DESCRIPTION_TABLE_SIGNATURE: + TableInstallFunction = QemuInstallAcpiSsdtTable; + break; + default: + TableInstallFunction = InstallAcpiTable; + } + + return TableInstallFunction ( + AcpiProtocol, + AcpiTableBuffer, + AcpiTableBufferSize, + TableKey + ); +} diff --git a/OvmfPkg/XenAcpiPlatformDxe/QemuFwCfgAcpi.c b/OvmfPkg/XenAcpiPlatformDxe/QemuFwCfgAcpi.c new file mode 100644 index 0000000000..521c06cf54 --- /dev/null +++ b/OvmfPkg/XenAcpiPlatformDxe/QemuFwCfgAcpi.c @@ -0,0 +1,1196 @@ +/** @file + OVMF ACPI support using QEMU's fw-cfg interface + + Copyright (C) 2012-2021, Red Hat, Inc. + Copyright (c) 2008 - 2014, Intel Corporation. All rights reserved.
+ + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include // EFI_ACPI_DESCRIPTION_HEADER +#include // QEMU_LOADER_FNAME_SIZE +#include // AsciiStrCmp() +#include // CopyMem() +#include // DEBUG() +#include // AllocatePool() +#include // OrderedCollectionMin() +#include // QemuFwCfgFindFile() +#include // QemuFwCfgS3Enabled() +#include // gBS + +#include "AcpiPlatform.h" + +// +// The user structure for the ordered collection that will track the fw_cfg +// blobs under processing. +// +typedef struct { + UINT8 File[QEMU_LOADER_FNAME_SIZE]; // NUL-terminated name of the fw_cfg + // blob. This is the ordering / search + // key. + UINTN Size; // The number of bytes in this blob. + UINT8 *Base; // Pointer to the blob data. + BOOLEAN HostsOnlyTableData; // TRUE iff the blob has been found to + // only contain data that is directly + // part of ACPI tables. +} BLOB; + + +/** + Compare a standalone key against a user structure containing an embedded key. + + @param[in] StandaloneKey Pointer to the bare key. + + @param[in] UserStruct Pointer to the user structure with the embedded + key. + + @retval <0 If StandaloneKey compares less than UserStruct's key. + + @retval 0 If StandaloneKey compares equal to UserStruct's key. + + @retval >0 If StandaloneKey compares greater than UserStruct's key. +**/ +STATIC +INTN +EFIAPI +BlobKeyCompare ( + IN CONST VOID *StandaloneKey, + IN CONST VOID *UserStruct + ) +{ + CONST BLOB *Blob; + + Blob = UserStruct; + return AsciiStrCmp (StandaloneKey, (CONST CHAR8 *)Blob->File); +} + + +/** + Comparator function for two user structures. + + @param[in] UserStruct1 Pointer to the first user structure. + + @param[in] UserStruct2 Pointer to the second user structure. + + @retval <0 If UserStruct1 compares less than UserStruct2. + + @retval 0 If UserStruct1 compares equal to UserStruct2. + + @retval >0 If UserStruct1 compares greater than UserStruct2. +**/ +STATIC +INTN +EFIAPI +BlobCompare ( + IN CONST VOID *UserStruct1, + IN CONST VOID *UserStruct2 + ) +{ + CONST BLOB *Blob1; + + Blob1 = UserStruct1; + return BlobKeyCompare (Blob1->File, UserStruct2); +} + + +/** + Comparator function for two opaque pointers, ordering on (unsigned) pointer + value itself. + Can be used as both Key and UserStruct comparator. + + @param[in] Pointer1 First pointer. + + @param[in] Pointer2 Second pointer. + + @retval <0 If Pointer1 compares less than Pointer2. + + @retval 0 If Pointer1 compares equal to Pointer2. + + @retval >0 If Pointer1 compares greater than Pointer2. +**/ +STATIC +INTN +EFIAPI +PointerCompare ( + IN CONST VOID *Pointer1, + IN CONST VOID *Pointer2 + ) +{ + if (Pointer1 == Pointer2) { + return 0; + } + if ((UINTN)Pointer1 < (UINTN)Pointer2) { + return -1; + } + return 1; +} + + +/** + Comparator function for two ASCII strings. Can be used as both Key and + UserStruct comparator. + + This function exists solely so we can avoid casting &AsciiStrCmp to + ORDERED_COLLECTION_USER_COMPARE and ORDERED_COLLECTION_KEY_COMPARE. + + @param[in] AsciiString1 Pointer to the first ASCII string. + + @param[in] AsciiString2 Pointer to the second ASCII string. + + @return The return value of AsciiStrCmp (AsciiString1, AsciiString2). +**/ +STATIC +INTN +EFIAPI +AsciiStringCompare ( + IN CONST VOID *AsciiString1, + IN CONST VOID *AsciiString2 + ) +{ + return AsciiStrCmp (AsciiString1, AsciiString2); +} + + +/** + Release the ORDERED_COLLECTION structure populated by + CollectAllocationsRestrictedTo32Bit() (below). + + This function may be called by CollectAllocationsRestrictedTo32Bit() itself, + on the error path. + + @param[in] AllocationsRestrictedTo32Bit The ORDERED_COLLECTION structure to + release. +**/ +STATIC +VOID +ReleaseAllocationsRestrictedTo32Bit ( + IN ORDERED_COLLECTION *AllocationsRestrictedTo32Bit +) +{ + ORDERED_COLLECTION_ENTRY *Entry, *Entry2; + + for (Entry = OrderedCollectionMin (AllocationsRestrictedTo32Bit); + Entry != NULL; + Entry = Entry2) { + Entry2 = OrderedCollectionNext (Entry); + OrderedCollectionDelete (AllocationsRestrictedTo32Bit, Entry, NULL); + } + OrderedCollectionUninit (AllocationsRestrictedTo32Bit); +} + + +/** + Iterate over the linker/loader script, and collect the names of the fw_cfg + blobs that are referenced by QEMU_LOADER_ADD_POINTER.PointeeFile fields, such + that QEMU_LOADER_ADD_POINTER.PointerSize is less than 8. This means that the + pointee blob's address will have to be patched into a narrower-than-8 byte + pointer field, hence the pointee blob must not be allocated from 64-bit + address space. + + @param[out] AllocationsRestrictedTo32Bit The ORDERED_COLLECTION structure + linking (not copying / owning) such + QEMU_LOADER_ADD_POINTER.PointeeFile + fields that name the blobs + restricted from 64-bit allocation. + + @param[in] LoaderStart Points to the first entry in the + linker/loader script. + + @param[in] LoaderEnd Points one past the last entry in + the linker/loader script. + + @retval EFI_SUCCESS AllocationsRestrictedTo32Bit has been + populated. + + @retval EFI_OUT_OF_RESOURCES Memory allocation failed. + + @retval EFI_PROTOCOL_ERROR Invalid linker/loader script contents. +**/ +STATIC +EFI_STATUS +CollectAllocationsRestrictedTo32Bit ( + OUT ORDERED_COLLECTION **AllocationsRestrictedTo32Bit, + IN CONST QEMU_LOADER_ENTRY *LoaderStart, + IN CONST QEMU_LOADER_ENTRY *LoaderEnd +) +{ + ORDERED_COLLECTION *Collection; + CONST QEMU_LOADER_ENTRY *LoaderEntry; + EFI_STATUS Status; + + Collection = OrderedCollectionInit (AsciiStringCompare, AsciiStringCompare); + if (Collection == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + for (LoaderEntry = LoaderStart; LoaderEntry < LoaderEnd; ++LoaderEntry) { + CONST QEMU_LOADER_ADD_POINTER *AddPointer; + + if (LoaderEntry->Type != QemuLoaderCmdAddPointer) { + continue; + } + AddPointer = &LoaderEntry->Command.AddPointer; + + if (AddPointer->PointerSize >= 8) { + continue; + } + + if (AddPointer->PointeeFile[QEMU_LOADER_FNAME_SIZE - 1] != '\0') { + DEBUG ((DEBUG_ERROR, "%a: malformed file name\n", __FUNCTION__)); + Status = EFI_PROTOCOL_ERROR; + goto RollBack; + } + + Status = OrderedCollectionInsert ( + Collection, + NULL, // Entry + (VOID *)AddPointer->PointeeFile + ); + switch (Status) { + case EFI_SUCCESS: + DEBUG (( + DEBUG_VERBOSE, + "%a: restricting blob \"%a\" from 64-bit allocation\n", + __FUNCTION__, + AddPointer->PointeeFile + )); + break; + case EFI_ALREADY_STARTED: + // + // The restriction has been recorded already. + // + break; + case EFI_OUT_OF_RESOURCES: + goto RollBack; + default: + ASSERT (FALSE); + } + } + + *AllocationsRestrictedTo32Bit = Collection; + return EFI_SUCCESS; + +RollBack: + ReleaseAllocationsRestrictedTo32Bit (Collection); + return Status; +} + + +/** + Process a QEMU_LOADER_ALLOCATE command. + + @param[in] Allocate The QEMU_LOADER_ALLOCATE command to + process. + + @param[in,out] Tracker The ORDERED_COLLECTION tracking the + BLOB user structures created thus + far. + + @param[in] AllocationsRestrictedTo32Bit The ORDERED_COLLECTION populated by + the function + CollectAllocationsRestrictedTo32Bit, + naming the fw_cfg blobs that must + not be allocated from 64-bit address + space. + + @retval EFI_SUCCESS An area of whole AcpiNVS pages has been + allocated for the blob contents, and the + contents have been saved. A BLOB object (user + structure) has been allocated from pool memory, + referencing the blob contents. The BLOB user + structure has been linked into Tracker. + + @retval EFI_PROTOCOL_ERROR Malformed fw_cfg file name has been found in + Allocate, or the Allocate command references a + file that is already known by Tracker. + + @retval EFI_UNSUPPORTED Unsupported alignment request has been found in + Allocate. + + @retval EFI_OUT_OF_RESOURCES Pool allocation failed. + + @return Error codes from QemuFwCfgFindFile() and + gBS->AllocatePages(). +**/ +STATIC +EFI_STATUS +EFIAPI +ProcessCmdAllocate ( + IN CONST QEMU_LOADER_ALLOCATE *Allocate, + IN OUT ORDERED_COLLECTION *Tracker, + IN ORDERED_COLLECTION *AllocationsRestrictedTo32Bit + ) +{ + FIRMWARE_CONFIG_ITEM FwCfgItem; + UINTN FwCfgSize; + EFI_STATUS Status; + UINTN NumPages; + EFI_PHYSICAL_ADDRESS Address; + BLOB *Blob; + + if (Allocate->File[QEMU_LOADER_FNAME_SIZE - 1] != '\0') { + DEBUG ((DEBUG_ERROR, "%a: malformed file name\n", __FUNCTION__)); + return EFI_PROTOCOL_ERROR; + } + + if (Allocate->Alignment > EFI_PAGE_SIZE) { + DEBUG ((DEBUG_ERROR, "%a: unsupported alignment 0x%x\n", __FUNCTION__, + Allocate->Alignment)); + return EFI_UNSUPPORTED; + } + + Status = QemuFwCfgFindFile ((CHAR8 *)Allocate->File, &FwCfgItem, &FwCfgSize); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "%a: QemuFwCfgFindFile(\"%a\"): %r\n", __FUNCTION__, + Allocate->File, Status)); + return Status; + } + + NumPages = EFI_SIZE_TO_PAGES (FwCfgSize); + Address = MAX_UINT64; + if (OrderedCollectionFind ( + AllocationsRestrictedTo32Bit, + Allocate->File + ) != NULL) { + Address = MAX_UINT32; + } + Status = gBS->AllocatePages (AllocateMaxAddress, EfiACPIMemoryNVS, NumPages, + &Address); + if (EFI_ERROR (Status)) { + return Status; + } + + Blob = AllocatePool (sizeof *Blob); + if (Blob == NULL) { + Status = EFI_OUT_OF_RESOURCES; + goto FreePages; + } + CopyMem (Blob->File, Allocate->File, QEMU_LOADER_FNAME_SIZE); + Blob->Size = FwCfgSize; + Blob->Base = (VOID *)(UINTN)Address; + Blob->HostsOnlyTableData = TRUE; + + Status = OrderedCollectionInsert (Tracker, NULL, Blob); + if (Status == RETURN_ALREADY_STARTED) { + DEBUG ((DEBUG_ERROR, "%a: duplicated file \"%a\"\n", __FUNCTION__, + Allocate->File)); + Status = EFI_PROTOCOL_ERROR; + } + if (EFI_ERROR (Status)) { + goto FreeBlob; + } + + QemuFwCfgSelectItem (FwCfgItem); + QemuFwCfgReadBytes (FwCfgSize, Blob->Base); + ZeroMem (Blob->Base + Blob->Size, EFI_PAGES_TO_SIZE (NumPages) - Blob->Size); + + DEBUG ((DEBUG_VERBOSE, "%a: File=\"%a\" Alignment=0x%x Zone=%d Size=0x%Lx " + "Address=0x%Lx\n", __FUNCTION__, Allocate->File, Allocate->Alignment, + Allocate->Zone, (UINT64)Blob->Size, (UINT64)(UINTN)Blob->Base)); + return EFI_SUCCESS; + +FreeBlob: + FreePool (Blob); + +FreePages: + gBS->FreePages (Address, NumPages); + + return Status; +} + + +/** + Process a QEMU_LOADER_ADD_POINTER command. + + @param[in] AddPointer The QEMU_LOADER_ADD_POINTER command to process. + + @param[in] Tracker The ORDERED_COLLECTION tracking the BLOB user + structures created thus far. + + @retval EFI_PROTOCOL_ERROR Malformed fw_cfg file name(s) have been found in + AddPointer, or the AddPointer command references + a file unknown to Tracker, or the pointer to + relocate has invalid location, size, or value, or + the relocated pointer value is not representable + in the given pointer size. + + @retval EFI_SUCCESS The pointer field inside the pointer blob has + been relocated. +**/ +STATIC +EFI_STATUS +EFIAPI +ProcessCmdAddPointer ( + IN CONST QEMU_LOADER_ADD_POINTER *AddPointer, + IN CONST ORDERED_COLLECTION *Tracker + ) +{ + ORDERED_COLLECTION_ENTRY *TrackerEntry, *TrackerEntry2; + BLOB *Blob, *Blob2; + UINT8 *PointerField; + UINT64 PointerValue; + + if (AddPointer->PointerFile[QEMU_LOADER_FNAME_SIZE - 1] != '\0' || + AddPointer->PointeeFile[QEMU_LOADER_FNAME_SIZE - 1] != '\0') { + DEBUG ((DEBUG_ERROR, "%a: malformed file name\n", __FUNCTION__)); + return EFI_PROTOCOL_ERROR; + } + + TrackerEntry = OrderedCollectionFind (Tracker, AddPointer->PointerFile); + TrackerEntry2 = OrderedCollectionFind (Tracker, AddPointer->PointeeFile); + if (TrackerEntry == NULL || TrackerEntry2 == NULL) { + DEBUG ((DEBUG_ERROR, "%a: invalid blob reference(s) \"%a\" / \"%a\"\n", + __FUNCTION__, AddPointer->PointerFile, AddPointer->PointeeFile)); + return EFI_PROTOCOL_ERROR; + } + + Blob = OrderedCollectionUserStruct (TrackerEntry); + Blob2 = OrderedCollectionUserStruct (TrackerEntry2); + if ((AddPointer->PointerSize != 1 && AddPointer->PointerSize != 2 && + AddPointer->PointerSize != 4 && AddPointer->PointerSize != 8) || + Blob->Size < AddPointer->PointerSize || + Blob->Size - AddPointer->PointerSize < AddPointer->PointerOffset) { + DEBUG ((DEBUG_ERROR, "%a: invalid pointer location or size in \"%a\"\n", + __FUNCTION__, AddPointer->PointerFile)); + return EFI_PROTOCOL_ERROR; + } + + PointerField = Blob->Base + AddPointer->PointerOffset; + PointerValue = 0; + CopyMem (&PointerValue, PointerField, AddPointer->PointerSize); + if (PointerValue >= Blob2->Size) { + DEBUG ((DEBUG_ERROR, "%a: invalid pointer value in \"%a\"\n", __FUNCTION__, + AddPointer->PointerFile)); + return EFI_PROTOCOL_ERROR; + } + + // + // The memory allocation system ensures that the address of the byte past the + // last byte of any allocated object is expressible (no wraparound). + // + ASSERT ((UINTN)Blob2->Base <= MAX_ADDRESS - Blob2->Size); + + PointerValue += (UINT64)(UINTN)Blob2->Base; + if (AddPointer->PointerSize < 8 && + RShiftU64 (PointerValue, AddPointer->PointerSize * 8) != 0) { + DEBUG ((DEBUG_ERROR, "%a: relocated pointer value unrepresentable in " + "\"%a\"\n", __FUNCTION__, AddPointer->PointerFile)); + return EFI_PROTOCOL_ERROR; + } + + CopyMem (PointerField, &PointerValue, AddPointer->PointerSize); + + DEBUG ((DEBUG_VERBOSE, "%a: PointerFile=\"%a\" PointeeFile=\"%a\" " + "PointerOffset=0x%x PointerSize=%d\n", __FUNCTION__, + AddPointer->PointerFile, AddPointer->PointeeFile, + AddPointer->PointerOffset, AddPointer->PointerSize)); + return EFI_SUCCESS; +} + + +/** + Process a QEMU_LOADER_ADD_CHECKSUM command. + + @param[in] AddChecksum The QEMU_LOADER_ADD_CHECKSUM command to process. + + @param[in] Tracker The ORDERED_COLLECTION tracking the BLOB user + structures created thus far. + + @retval EFI_PROTOCOL_ERROR Malformed fw_cfg file name has been found in + AddChecksum, or the AddChecksum command + references a file unknown to Tracker, or the + range to checksum is invalid. + + @retval EFI_SUCCESS The requested range has been checksummed. +**/ +STATIC +EFI_STATUS +EFIAPI +ProcessCmdAddChecksum ( + IN CONST QEMU_LOADER_ADD_CHECKSUM *AddChecksum, + IN CONST ORDERED_COLLECTION *Tracker + ) +{ + ORDERED_COLLECTION_ENTRY *TrackerEntry; + BLOB *Blob; + + if (AddChecksum->File[QEMU_LOADER_FNAME_SIZE - 1] != '\0') { + DEBUG ((DEBUG_ERROR, "%a: malformed file name\n", __FUNCTION__)); + return EFI_PROTOCOL_ERROR; + } + + TrackerEntry = OrderedCollectionFind (Tracker, AddChecksum->File); + if (TrackerEntry == NULL) { + DEBUG ((DEBUG_ERROR, "%a: invalid blob reference \"%a\"\n", __FUNCTION__, + AddChecksum->File)); + return EFI_PROTOCOL_ERROR; + } + + Blob = OrderedCollectionUserStruct (TrackerEntry); + if (Blob->Size <= AddChecksum->ResultOffset || + Blob->Size < AddChecksum->Length || + Blob->Size - AddChecksum->Length < AddChecksum->Start) { + DEBUG ((DEBUG_ERROR, "%a: invalid checksum range in \"%a\"\n", + __FUNCTION__, AddChecksum->File)); + return EFI_PROTOCOL_ERROR; + } + + Blob->Base[AddChecksum->ResultOffset] = CalculateCheckSum8 ( + Blob->Base + AddChecksum->Start, + AddChecksum->Length + ); + DEBUG ((DEBUG_VERBOSE, "%a: File=\"%a\" ResultOffset=0x%x Start=0x%x " + "Length=0x%x\n", __FUNCTION__, AddChecksum->File, + AddChecksum->ResultOffset, AddChecksum->Start, AddChecksum->Length)); + return EFI_SUCCESS; +} + + +/** + Process a QEMU_LOADER_WRITE_POINTER command. + + @param[in] WritePointer The QEMU_LOADER_WRITE_POINTER command to process. + + @param[in] Tracker The ORDERED_COLLECTION tracking the BLOB user + structures created thus far. + + @param[in,out] S3Context The S3_CONTEXT object capturing the fw_cfg actions + of successfully processed QEMU_LOADER_WRITE_POINTER + commands, to be replayed at S3 resume. S3Context + may be NULL if S3 is disabled. + + @retval EFI_PROTOCOL_ERROR Malformed fw_cfg file name(s) have been found in + WritePointer. Or, the WritePointer command + references a file unknown to Tracker or the + fw_cfg directory. Or, the pointer object to + rewrite has invalid location, size, or initial + relative value. Or, the pointer value to store + does not fit in the given pointer size. + + @retval EFI_SUCCESS The pointer object inside the writeable fw_cfg + file has been written. If S3Context is not NULL, + then WritePointer has been condensed into + S3Context. + + @return Error codes propagated from + SaveCondensedWritePointerToS3Context(). The + pointer object inside the writeable fw_cfg file + has not been written. +**/ +STATIC +EFI_STATUS +ProcessCmdWritePointer ( + IN CONST QEMU_LOADER_WRITE_POINTER *WritePointer, + IN CONST ORDERED_COLLECTION *Tracker, + IN OUT S3_CONTEXT *S3Context OPTIONAL + ) +{ + RETURN_STATUS Status; + FIRMWARE_CONFIG_ITEM PointerItem; + UINTN PointerItemSize; + ORDERED_COLLECTION_ENTRY *PointeeEntry; + BLOB *PointeeBlob; + UINT64 PointerValue; + + if (WritePointer->PointerFile[QEMU_LOADER_FNAME_SIZE - 1] != '\0' || + WritePointer->PointeeFile[QEMU_LOADER_FNAME_SIZE - 1] != '\0') { + DEBUG ((DEBUG_ERROR, "%a: malformed file name\n", __FUNCTION__)); + return EFI_PROTOCOL_ERROR; + } + + Status = QemuFwCfgFindFile ((CONST CHAR8 *)WritePointer->PointerFile, + &PointerItem, &PointerItemSize); + PointeeEntry = OrderedCollectionFind (Tracker, WritePointer->PointeeFile); + if (RETURN_ERROR (Status) || PointeeEntry == NULL) { + DEBUG ((DEBUG_ERROR, + "%a: invalid fw_cfg file or blob reference \"%a\" / \"%a\"\n", + __FUNCTION__, WritePointer->PointerFile, WritePointer->PointeeFile)); + return EFI_PROTOCOL_ERROR; + } + + if ((WritePointer->PointerSize != 1 && WritePointer->PointerSize != 2 && + WritePointer->PointerSize != 4 && WritePointer->PointerSize != 8) || + (PointerItemSize < WritePointer->PointerSize) || + (PointerItemSize - WritePointer->PointerSize < + WritePointer->PointerOffset)) { + DEBUG ((DEBUG_ERROR, "%a: invalid pointer location or size in \"%a\"\n", + __FUNCTION__, WritePointer->PointerFile)); + return EFI_PROTOCOL_ERROR; + } + + PointeeBlob = OrderedCollectionUserStruct (PointeeEntry); + PointerValue = WritePointer->PointeeOffset; + if (PointerValue >= PointeeBlob->Size) { + DEBUG ((DEBUG_ERROR, "%a: invalid PointeeOffset\n", __FUNCTION__)); + return EFI_PROTOCOL_ERROR; + } + + // + // The memory allocation system ensures that the address of the byte past the + // last byte of any allocated object is expressible (no wraparound). + // + ASSERT ((UINTN)PointeeBlob->Base <= MAX_ADDRESS - PointeeBlob->Size); + + PointerValue += (UINT64)(UINTN)PointeeBlob->Base; + if (WritePointer->PointerSize < 8 && + RShiftU64 (PointerValue, WritePointer->PointerSize * 8) != 0) { + DEBUG ((DEBUG_ERROR, "%a: pointer value unrepresentable in \"%a\"\n", + __FUNCTION__, WritePointer->PointerFile)); + return EFI_PROTOCOL_ERROR; + } + + // + // If S3 is enabled, we have to capture the below fw_cfg actions in condensed + // form, to be replayed during S3 resume. + // + if (S3Context != NULL) { + EFI_STATUS SaveStatus; + + SaveStatus = SaveCondensedWritePointerToS3Context ( + S3Context, + (UINT16)PointerItem, + WritePointer->PointerSize, + WritePointer->PointerOffset, + PointerValue + ); + if (EFI_ERROR (SaveStatus)) { + return SaveStatus; + } + } + + QemuFwCfgSelectItem (PointerItem); + QemuFwCfgSkipBytes (WritePointer->PointerOffset); + QemuFwCfgWriteBytes (WritePointer->PointerSize, &PointerValue); + + // + // Because QEMU has now learned PointeeBlob->Base, we must mark PointeeBlob + // as unreleasable, for the case when the whole linker/loader script is + // handled successfully. + // + PointeeBlob->HostsOnlyTableData = FALSE; + + DEBUG ((DEBUG_VERBOSE, "%a: PointerFile=\"%a\" PointeeFile=\"%a\" " + "PointerOffset=0x%x PointeeOffset=0x%x PointerSize=%d\n", __FUNCTION__, + WritePointer->PointerFile, WritePointer->PointeeFile, + WritePointer->PointerOffset, WritePointer->PointeeOffset, + WritePointer->PointerSize)); + return EFI_SUCCESS; +} + + +/** + Undo a QEMU_LOADER_WRITE_POINTER command. + + This function revokes (zeroes out) a guest memory reference communicated to + QEMU earlier. The caller is responsible for invoking this function only on + such QEMU_LOADER_WRITE_POINTER commands that have been successfully processed + by ProcessCmdWritePointer(). + + @param[in] WritePointer The QEMU_LOADER_WRITE_POINTER command to undo. +**/ +STATIC +VOID +UndoCmdWritePointer ( + IN CONST QEMU_LOADER_WRITE_POINTER *WritePointer + ) +{ + RETURN_STATUS Status; + FIRMWARE_CONFIG_ITEM PointerItem; + UINTN PointerItemSize; + UINT64 PointerValue; + + Status = QemuFwCfgFindFile ((CONST CHAR8 *)WritePointer->PointerFile, + &PointerItem, &PointerItemSize); + ASSERT_RETURN_ERROR (Status); + + PointerValue = 0; + QemuFwCfgSelectItem (PointerItem); + QemuFwCfgSkipBytes (WritePointer->PointerOffset); + QemuFwCfgWriteBytes (WritePointer->PointerSize, &PointerValue); + + DEBUG ((DEBUG_VERBOSE, + "%a: PointerFile=\"%a\" PointerOffset=0x%x PointerSize=%d\n", __FUNCTION__, + WritePointer->PointerFile, WritePointer->PointerOffset, + WritePointer->PointerSize)); +} + + +// +// We'll be saving the keys of installed tables so that we can roll them back +// in case of failure. 128 tables should be enough for anyone (TM). +// +#define INSTALLED_TABLES_MAX 128 + +/** + Process a QEMU_LOADER_ADD_POINTER command in order to see if its target byte + array is an ACPI table, and if so, install it. + + This function assumes that the entire QEMU linker/loader command file has + been processed successfully in a prior first pass. + + @param[in] AddPointer The QEMU_LOADER_ADD_POINTER command to process. + + @param[in] Tracker The ORDERED_COLLECTION tracking the BLOB user + structures. + + @param[in] AcpiProtocol The ACPI table protocol used to install tables. + + @param[in,out] InstalledKey On input, an array of INSTALLED_TABLES_MAX UINTN + elements, allocated by the caller. On output, + the function will have stored (appended) the + AcpiProtocol-internal key of the ACPI table that + the function has installed, if the AddPointer + command identified an ACPI table that is + different from RSDT and XSDT. + + @param[in,out] NumInstalled On input, the number of entries already used in + InstalledKey; it must be in [0, + INSTALLED_TABLES_MAX] inclusive. On output, the + parameter is incremented if the AddPointer + command identified an ACPI table that is + different from RSDT and XSDT. + + @param[in,out] SeenPointers The ORDERED_COLLECTION tracking the absolute + target addresses that have been pointed-to by + QEMU_LOADER_ADD_POINTER commands thus far. If a + target address is encountered for the first + time, and it identifies an ACPI table that is + different from RDST and XSDT, the table is + installed. If a target address is seen for the + second or later times, it is skipped without + taking any action. + + @retval EFI_INVALID_PARAMETER NumInstalled was outside the allowed range on + input. + + @retval EFI_OUT_OF_RESOURCES The AddPointer command identified an ACPI + table different from RSDT and XSDT, but there + was no more room in InstalledKey. + + @retval EFI_SUCCESS AddPointer has been processed. Either its + absolute target address has been encountered + before, or an ACPI table different from RSDT + and XSDT has been installed (reflected by + InstalledKey and NumInstalled), or RSDT or + XSDT has been identified but not installed, or + the fw_cfg blob pointed-into by AddPointer has + been marked as hosting something else than + just direct ACPI table contents. + + @return Error codes returned by + AcpiProtocol->InstallAcpiTable(). +**/ +STATIC +EFI_STATUS +EFIAPI +Process2ndPassCmdAddPointer ( + IN CONST QEMU_LOADER_ADD_POINTER *AddPointer, + IN CONST ORDERED_COLLECTION *Tracker, + IN EFI_ACPI_TABLE_PROTOCOL *AcpiProtocol, + IN OUT UINTN InstalledKey[INSTALLED_TABLES_MAX], + IN OUT INT32 *NumInstalled, + IN OUT ORDERED_COLLECTION *SeenPointers + ) +{ + CONST ORDERED_COLLECTION_ENTRY *TrackerEntry; + CONST ORDERED_COLLECTION_ENTRY *TrackerEntry2; + ORDERED_COLLECTION_ENTRY *SeenPointerEntry; + CONST BLOB *Blob; + BLOB *Blob2; + CONST UINT8 *PointerField; + UINT64 PointerValue; + UINTN Blob2Remaining; + UINTN TableSize; + CONST EFI_ACPI_1_0_FIRMWARE_ACPI_CONTROL_STRUCTURE *Facs; + CONST EFI_ACPI_DESCRIPTION_HEADER *Header; + EFI_STATUS Status; + + if (*NumInstalled < 0 || *NumInstalled > INSTALLED_TABLES_MAX) { + return EFI_INVALID_PARAMETER; + } + + TrackerEntry = OrderedCollectionFind (Tracker, AddPointer->PointerFile); + TrackerEntry2 = OrderedCollectionFind (Tracker, AddPointer->PointeeFile); + Blob = OrderedCollectionUserStruct (TrackerEntry); + Blob2 = OrderedCollectionUserStruct (TrackerEntry2); + PointerField = Blob->Base + AddPointer->PointerOffset; + PointerValue = 0; + CopyMem (&PointerValue, PointerField, AddPointer->PointerSize); + + // + // We assert that PointerValue falls inside Blob2's contents. This is ensured + // by the Blob2->Size check and later checks in ProcessCmdAddPointer(). + // + Blob2Remaining = (UINTN)Blob2->Base; + ASSERT(PointerValue >= Blob2Remaining); + Blob2Remaining += Blob2->Size; + ASSERT (PointerValue < Blob2Remaining); + + Status = OrderedCollectionInsert ( + SeenPointers, + &SeenPointerEntry, // for reverting insertion in error case + (VOID *)(UINTN)PointerValue + ); + if (EFI_ERROR (Status)) { + if (Status == RETURN_ALREADY_STARTED) { + // + // Already seen this pointer, don't try to process it again. + // + DEBUG (( + DEBUG_VERBOSE, + "%a: PointerValue=0x%Lx already processed, skipping.\n", + __FUNCTION__, + PointerValue + )); + Status = EFI_SUCCESS; + } + return Status; + } + + Blob2Remaining -= (UINTN) PointerValue; + DEBUG ((DEBUG_VERBOSE, "%a: checking for ACPI header in \"%a\" at 0x%Lx " + "(remaining: 0x%Lx): ", __FUNCTION__, AddPointer->PointeeFile, + PointerValue, (UINT64)Blob2Remaining)); + + TableSize = 0; + + // + // To make our job simple, the FACS has a custom header. Sigh. + // + if (sizeof *Facs <= Blob2Remaining) { + Facs = (EFI_ACPI_1_0_FIRMWARE_ACPI_CONTROL_STRUCTURE *)(UINTN)PointerValue; + + if (Facs->Length >= sizeof *Facs && + Facs->Length <= Blob2Remaining && + Facs->Signature == + EFI_ACPI_1_0_FIRMWARE_ACPI_CONTROL_STRUCTURE_SIGNATURE) { + DEBUG ((DEBUG_VERBOSE, "found \"%-4.4a\" size 0x%x\n", + (CONST CHAR8 *)&Facs->Signature, Facs->Length)); + TableSize = Facs->Length; + } + } + + // + // check for the uniform tables + // + if (TableSize == 0 && sizeof *Header <= Blob2Remaining) { + Header = (EFI_ACPI_DESCRIPTION_HEADER *)(UINTN)PointerValue; + + if (Header->Length >= sizeof *Header && + Header->Length <= Blob2Remaining && + CalculateSum8 ((CONST UINT8 *)Header, Header->Length) == 0) { + // + // This looks very much like an ACPI table from QEMU: + // - Length field consistent with both ACPI and containing blob size + // - checksum is correct + // + DEBUG ((DEBUG_VERBOSE, "found \"%-4.4a\" size 0x%x\n", + (CONST CHAR8 *)&Header->Signature, Header->Length)); + TableSize = Header->Length; + + // + // Skip RSDT and XSDT because those are handled by + // EFI_ACPI_TABLE_PROTOCOL automatically. + if (Header->Signature == + EFI_ACPI_1_0_ROOT_SYSTEM_DESCRIPTION_TABLE_SIGNATURE || + Header->Signature == + EFI_ACPI_2_0_EXTENDED_SYSTEM_DESCRIPTION_TABLE_SIGNATURE) { + return EFI_SUCCESS; + } + } + } + + if (TableSize == 0) { + DEBUG ((DEBUG_VERBOSE, "not found; marking fw_cfg blob as opaque\n")); + Blob2->HostsOnlyTableData = FALSE; + return EFI_SUCCESS; + } + + if (*NumInstalled == INSTALLED_TABLES_MAX) { + DEBUG ((DEBUG_ERROR, "%a: can't install more than %d tables\n", + __FUNCTION__, INSTALLED_TABLES_MAX)); + Status = EFI_OUT_OF_RESOURCES; + goto RollbackSeenPointer; + } + + Status = AcpiProtocol->InstallAcpiTable (AcpiProtocol, + (VOID *)(UINTN)PointerValue, TableSize, + &InstalledKey[*NumInstalled]); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "%a: InstallAcpiTable(): %r\n", __FUNCTION__, + Status)); + goto RollbackSeenPointer; + } + ++*NumInstalled; + return EFI_SUCCESS; + +RollbackSeenPointer: + OrderedCollectionDelete (SeenPointers, SeenPointerEntry, NULL); + return Status; +} + + +/** + Download, process, and install ACPI table data from the QEMU loader + interface. + + @param[in] AcpiProtocol The ACPI table protocol used to install tables. + + @retval EFI_UNSUPPORTED Firmware configuration is unavailable, or QEMU + loader command with unsupported parameters + has been found. + + @retval EFI_NOT_FOUND The host doesn't export the required fw_cfg + files. + + @retval EFI_OUT_OF_RESOURCES Memory allocation failed, or more than + INSTALLED_TABLES_MAX tables found. + + @retval EFI_PROTOCOL_ERROR Found invalid fw_cfg contents. + + @return Status codes returned by + AcpiProtocol->InstallAcpiTable(). + +**/ +EFI_STATUS +EFIAPI +InstallQemuFwCfgTables ( + IN EFI_ACPI_TABLE_PROTOCOL *AcpiProtocol + ) +{ + EFI_STATUS Status; + FIRMWARE_CONFIG_ITEM FwCfgItem; + UINTN FwCfgSize; + QEMU_LOADER_ENTRY *LoaderStart; + CONST QEMU_LOADER_ENTRY *LoaderEntry, *LoaderEnd; + CONST QEMU_LOADER_ENTRY *WritePointerSubsetEnd; + ORIGINAL_ATTRIBUTES *OriginalPciAttributes; + UINTN OriginalPciAttributesCount; + ORDERED_COLLECTION *AllocationsRestrictedTo32Bit; + S3_CONTEXT *S3Context; + ORDERED_COLLECTION *Tracker; + UINTN *InstalledKey; + INT32 Installed; + ORDERED_COLLECTION_ENTRY *TrackerEntry, *TrackerEntry2; + ORDERED_COLLECTION *SeenPointers; + ORDERED_COLLECTION_ENTRY *SeenPointerEntry, *SeenPointerEntry2; + + Status = QemuFwCfgFindFile ("etc/table-loader", &FwCfgItem, &FwCfgSize); + if (EFI_ERROR (Status)) { + return Status; + } + if (FwCfgSize % sizeof *LoaderEntry != 0) { + DEBUG ((DEBUG_ERROR, "%a: \"etc/table-loader\" has invalid size 0x%Lx\n", + __FUNCTION__, (UINT64)FwCfgSize)); + return EFI_PROTOCOL_ERROR; + } + + LoaderStart = AllocatePool (FwCfgSize); + if (LoaderStart == NULL) { + return EFI_OUT_OF_RESOURCES; + } + EnablePciDecoding (&OriginalPciAttributes, &OriginalPciAttributesCount); + QemuFwCfgSelectItem (FwCfgItem); + QemuFwCfgReadBytes (FwCfgSize, LoaderStart); + RestorePciDecoding (OriginalPciAttributes, OriginalPciAttributesCount); + LoaderEnd = LoaderStart + FwCfgSize / sizeof *LoaderEntry; + + AllocationsRestrictedTo32Bit = NULL; + Status = CollectAllocationsRestrictedTo32Bit ( + &AllocationsRestrictedTo32Bit, + LoaderStart, + LoaderEnd + ); + if (EFI_ERROR (Status)) { + goto FreeLoader; + } + + S3Context = NULL; + if (QemuFwCfgS3Enabled ()) { + // + // Size the allocation pessimistically, assuming that all commands in the + // script are QEMU_LOADER_WRITE_POINTER commands. + // + Status = AllocateS3Context (&S3Context, LoaderEnd - LoaderStart); + if (EFI_ERROR (Status)) { + goto FreeAllocationsRestrictedTo32Bit; + } + } + + Tracker = OrderedCollectionInit (BlobCompare, BlobKeyCompare); + if (Tracker == NULL) { + Status = EFI_OUT_OF_RESOURCES; + goto FreeS3Context; + } + + // + // first pass: process the commands + // + // "WritePointerSubsetEnd" points one past the last successful + // QEMU_LOADER_WRITE_POINTER command. Now when we're about to start the first + // pass, no such command has been encountered yet. + // + WritePointerSubsetEnd = LoaderStart; + for (LoaderEntry = LoaderStart; LoaderEntry < LoaderEnd; ++LoaderEntry) { + switch (LoaderEntry->Type) { + case QemuLoaderCmdAllocate: + Status = ProcessCmdAllocate ( + &LoaderEntry->Command.Allocate, + Tracker, + AllocationsRestrictedTo32Bit + ); + break; + + case QemuLoaderCmdAddPointer: + Status = ProcessCmdAddPointer (&LoaderEntry->Command.AddPointer, + Tracker); + break; + + case QemuLoaderCmdAddChecksum: + Status = ProcessCmdAddChecksum (&LoaderEntry->Command.AddChecksum, + Tracker); + break; + + case QemuLoaderCmdWritePointer: + Status = ProcessCmdWritePointer (&LoaderEntry->Command.WritePointer, + Tracker, S3Context); + if (!EFI_ERROR (Status)) { + WritePointerSubsetEnd = LoaderEntry + 1; + } + break; + + default: + DEBUG ((DEBUG_VERBOSE, "%a: unknown loader command: 0x%x\n", + __FUNCTION__, LoaderEntry->Type)); + break; + } + + if (EFI_ERROR (Status)) { + goto RollbackWritePointersAndFreeTracker; + } + } + + InstalledKey = AllocatePool (INSTALLED_TABLES_MAX * sizeof *InstalledKey); + if (InstalledKey == NULL) { + Status = EFI_OUT_OF_RESOURCES; + goto RollbackWritePointersAndFreeTracker; + } + + SeenPointers = OrderedCollectionInit (PointerCompare, PointerCompare); + if (SeenPointers == NULL) { + Status = EFI_OUT_OF_RESOURCES; + goto FreeKeys; + } + + // + // second pass: identify and install ACPI tables + // + Installed = 0; + for (LoaderEntry = LoaderStart; LoaderEntry < LoaderEnd; ++LoaderEntry) { + if (LoaderEntry->Type == QemuLoaderCmdAddPointer) { + Status = Process2ndPassCmdAddPointer ( + &LoaderEntry->Command.AddPointer, + Tracker, + AcpiProtocol, + InstalledKey, + &Installed, + SeenPointers + ); + if (EFI_ERROR (Status)) { + goto UninstallAcpiTables; + } + } + } + + // + // Translating the condensed QEMU_LOADER_WRITE_POINTER commands to ACPI S3 + // Boot Script opcodes has to be the last operation in this function, because + // if it succeeds, it cannot be undone. + // + if (S3Context != NULL) { + Status = TransferS3ContextToBootScript (S3Context); + if (EFI_ERROR (Status)) { + goto UninstallAcpiTables; + } + // + // Ownership of S3Context has been transferred. + // + S3Context = NULL; + } + +UninstallAcpiTables: + if (EFI_ERROR (Status)) { + // + // roll back partial installation + // + while (Installed > 0) { + --Installed; + AcpiProtocol->UninstallAcpiTable (AcpiProtocol, InstalledKey[Installed]); + } + } else { + DEBUG ((DEBUG_INFO, "%a: installed %d tables\n", __FUNCTION__, Installed)); + } + + for (SeenPointerEntry = OrderedCollectionMin (SeenPointers); + SeenPointerEntry != NULL; + SeenPointerEntry = SeenPointerEntry2) { + SeenPointerEntry2 = OrderedCollectionNext (SeenPointerEntry); + OrderedCollectionDelete (SeenPointers, SeenPointerEntry, NULL); + } + OrderedCollectionUninit (SeenPointers); + +FreeKeys: + FreePool (InstalledKey); + +RollbackWritePointersAndFreeTracker: + // + // In case of failure, revoke any allocation addresses that were communicated + // to QEMU previously, before we release all the blobs. + // + if (EFI_ERROR (Status)) { + LoaderEntry = WritePointerSubsetEnd; + while (LoaderEntry > LoaderStart) { + --LoaderEntry; + if (LoaderEntry->Type == QemuLoaderCmdWritePointer) { + UndoCmdWritePointer (&LoaderEntry->Command.WritePointer); + } + } + } + + // + // Tear down the tracker infrastructure. Each fw_cfg blob will be left in + // place only if we're exiting with success and the blob hosts data that is + // not directly part of some ACPI table. + // + for (TrackerEntry = OrderedCollectionMin (Tracker); TrackerEntry != NULL; + TrackerEntry = TrackerEntry2) { + VOID *UserStruct; + BLOB *Blob; + + TrackerEntry2 = OrderedCollectionNext (TrackerEntry); + OrderedCollectionDelete (Tracker, TrackerEntry, &UserStruct); + Blob = UserStruct; + + if (EFI_ERROR (Status) || Blob->HostsOnlyTableData) { + DEBUG ((DEBUG_VERBOSE, "%a: freeing \"%a\"\n", __FUNCTION__, + Blob->File)); + gBS->FreePages ((UINTN)Blob->Base, EFI_SIZE_TO_PAGES (Blob->Size)); + } + FreePool (Blob); + } + OrderedCollectionUninit (Tracker); + +FreeS3Context: + if (S3Context != NULL) { + ReleaseS3Context (S3Context); + } + +FreeAllocationsRestrictedTo32Bit: + ReleaseAllocationsRestrictedTo32Bit (AllocationsRestrictedTo32Bit); + +FreeLoader: + FreePool (LoaderStart); + + return Status; +} diff --git a/OvmfPkg/XenAcpiPlatformDxe/Xen.c b/OvmfPkg/XenAcpiPlatformDxe/Xen.c new file mode 100644 index 0000000000..e8395db548 --- /dev/null +++ b/OvmfPkg/XenAcpiPlatformDxe/Xen.c @@ -0,0 +1,316 @@ +/** @file + OVMF ACPI Xen support + + Copyright (C) 2021, Red Hat, Inc. + Copyright (c) 2008 - 2012, Intel Corporation. All rights reserved.
+ Copyright (c) 2012, Bei Guan + + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include // CpuDeadLoop() +#include // DEBUG() +#include // XenGetInfoHOB() + +#include "AcpiPlatform.h" + +#define XEN_ACPI_PHYSICAL_ADDRESS 0x000EA020 +#define XEN_BIOS_PHYSICAL_END 0x000FFFFF + +EFI_ACPI_2_0_ROOT_SYSTEM_DESCRIPTION_POINTER *XenAcpiRsdpStructurePtr = NULL; + +/** + Get the address of Xen ACPI Root System Description Pointer (RSDP) + structure. + + @param RsdpStructurePtr Return pointer to RSDP structure + + @return EFI_SUCCESS Find Xen RSDP structure successfully. + @return EFI_NOT_FOUND Don't find Xen RSDP structure. + @return EFI_ABORTED Find Xen RSDP structure, but it's not integrated. + +**/ +EFI_STATUS +EFIAPI +GetXenAcpiRsdp ( + OUT EFI_ACPI_2_0_ROOT_SYSTEM_DESCRIPTION_POINTER **RsdpPtr + ) +{ + EFI_ACPI_2_0_ROOT_SYSTEM_DESCRIPTION_POINTER *RsdpStructurePtr; + UINT8 *XenAcpiPtr; + UINT8 Sum; + EFI_XEN_INFO *XenInfo; + + // + // Detect the RSDP structure + // + + // + // First look for PVH one + // + XenInfo = XenGetInfoHOB (); + ASSERT (XenInfo != NULL); + if (XenInfo->RsdpPvh != NULL) { + DEBUG ((DEBUG_INFO, "%a: Use ACPI RSDP table at 0x%p\n", + gEfiCallerBaseName, XenInfo->RsdpPvh)); + *RsdpPtr = XenInfo->RsdpPvh; + return EFI_SUCCESS; + } + + // + // Otherwise, look for the HVM one + // + for (XenAcpiPtr = (UINT8*)(UINTN) XEN_ACPI_PHYSICAL_ADDRESS; + XenAcpiPtr < (UINT8*)(UINTN) XEN_BIOS_PHYSICAL_END; + XenAcpiPtr += 0x10) { + + RsdpStructurePtr = (EFI_ACPI_2_0_ROOT_SYSTEM_DESCRIPTION_POINTER *) + (UINTN) XenAcpiPtr; + + if (!AsciiStrnCmp ((CHAR8 *) &RsdpStructurePtr->Signature, "RSD PTR ", 8)) { + // + // RSDP ACPI 1.0 checksum for 1.0/2.0/3.0 table. + // This is only the first 20 bytes of the structure + // + Sum = CalculateSum8 ( + (CONST UINT8 *)RsdpStructurePtr, + sizeof (EFI_ACPI_1_0_ROOT_SYSTEM_DESCRIPTION_POINTER) + ); + if (Sum != 0) { + return EFI_ABORTED; + } + + if (RsdpStructurePtr->Revision >= 2) { + // + // RSDP ACPI 2.0/3.0 checksum, this is the entire table + // + Sum = CalculateSum8 ( + (CONST UINT8 *)RsdpStructurePtr, + sizeof (EFI_ACPI_2_0_ROOT_SYSTEM_DESCRIPTION_POINTER) + ); + if (Sum != 0) { + return EFI_ABORTED; + } + } + *RsdpPtr = RsdpStructurePtr; + return EFI_SUCCESS; + } + } + + return EFI_NOT_FOUND; +} + +/** + Get Xen Acpi tables from the RSDP structure. And installs Xen ACPI tables + into the RSDT/XSDT using InstallAcpiTable. Some signature of the installed + ACPI tables are: FACP, APIC, HPET, WAET, SSDT, FACS, DSDT. + + @param AcpiProtocol Protocol instance pointer. + + @return EFI_SUCCESS The table was successfully inserted. + @return EFI_INVALID_PARAMETER Either AcpiTableBuffer is NULL, TableHandle is + NULL, or AcpiTableBufferSize and the size + field embedded in the ACPI table pointed to + by AcpiTableBuffer are not in sync. + @return EFI_OUT_OF_RESOURCES Insufficient resources exist to complete the request. + +**/ +EFI_STATUS +EFIAPI +InstallXenTables ( + IN EFI_ACPI_TABLE_PROTOCOL *AcpiProtocol + ) +{ + EFI_STATUS Status; + UINTN TableHandle; + + EFI_ACPI_DESCRIPTION_HEADER *Rsdt; + EFI_ACPI_DESCRIPTION_HEADER *Xsdt; + VOID *CurrentTableEntry; + UINTN CurrentTablePointer; + EFI_ACPI_DESCRIPTION_HEADER *CurrentTable; + UINTN Index; + UINTN NumberOfTableEntries; + EFI_ACPI_2_0_FIXED_ACPI_DESCRIPTION_TABLE *Fadt2Table; + EFI_ACPI_1_0_FIXED_ACPI_DESCRIPTION_TABLE *Fadt1Table; + EFI_ACPI_2_0_FIRMWARE_ACPI_CONTROL_STRUCTURE *Facs2Table; + EFI_ACPI_1_0_FIRMWARE_ACPI_CONTROL_STRUCTURE *Facs1Table; + EFI_ACPI_DESCRIPTION_HEADER *DsdtTable; + + Fadt2Table = NULL; + Fadt1Table = NULL; + Facs2Table = NULL; + Facs1Table = NULL; + DsdtTable = NULL; + TableHandle = 0; + NumberOfTableEntries = 0; + + // + // Try to find Xen ACPI tables + // + Status = GetXenAcpiRsdp (&XenAcpiRsdpStructurePtr); + if (EFI_ERROR (Status)) { + return Status; + } + + // + // If XSDT table is find, just install its tables. + // Otherwise, try to find and install the RSDT tables. + // + if (XenAcpiRsdpStructurePtr->XsdtAddress) { + // + // Retrieve the addresses of XSDT and + // calculate the number of its table entries. + // + Xsdt = (EFI_ACPI_DESCRIPTION_HEADER *) (UINTN) + XenAcpiRsdpStructurePtr->XsdtAddress; + NumberOfTableEntries = (Xsdt->Length - + sizeof (EFI_ACPI_DESCRIPTION_HEADER)) / + sizeof (UINT64); + + // + // Install ACPI tables found in XSDT. + // + for (Index = 0; Index < NumberOfTableEntries; Index++) { + // + // Get the table entry from XSDT + // + CurrentTableEntry = (VOID *) ((UINT8 *) Xsdt + + sizeof (EFI_ACPI_DESCRIPTION_HEADER) + + Index * sizeof (UINT64)); + CurrentTablePointer = (UINTN) *(UINT64 *)CurrentTableEntry; + CurrentTable = (EFI_ACPI_DESCRIPTION_HEADER *) CurrentTablePointer; + + // + // Install the XSDT tables + // + Status = InstallAcpiTable ( + AcpiProtocol, + CurrentTable, + CurrentTable->Length, + &TableHandle + ); + + if (EFI_ERROR (Status)) { + return Status; + } + + // + // Get the FACS and DSDT table address from the table FADT + // + if (!AsciiStrnCmp ((CHAR8 *) &CurrentTable->Signature, "FACP", 4)) { + Fadt2Table = (EFI_ACPI_2_0_FIXED_ACPI_DESCRIPTION_TABLE *) + (UINTN) CurrentTablePointer; + Facs2Table = (EFI_ACPI_2_0_FIRMWARE_ACPI_CONTROL_STRUCTURE *) + (UINTN) Fadt2Table->FirmwareCtrl; + DsdtTable = (EFI_ACPI_DESCRIPTION_HEADER *) (UINTN) Fadt2Table->Dsdt; + } + } + } + else if (XenAcpiRsdpStructurePtr->RsdtAddress) { + // + // Retrieve the addresses of RSDT and + // calculate the number of its table entries. + // + Rsdt = (EFI_ACPI_DESCRIPTION_HEADER *) (UINTN) + XenAcpiRsdpStructurePtr->RsdtAddress; + NumberOfTableEntries = (Rsdt->Length - + sizeof (EFI_ACPI_DESCRIPTION_HEADER)) / + sizeof (UINT32); + + // + // Install ACPI tables found in XSDT. + // + for (Index = 0; Index < NumberOfTableEntries; Index++) { + // + // Get the table entry from RSDT + // + CurrentTableEntry = (UINT32 *) ((UINT8 *) Rsdt + + sizeof (EFI_ACPI_DESCRIPTION_HEADER) + + Index * sizeof (UINT32)); + CurrentTablePointer = *(UINT32 *)CurrentTableEntry; + CurrentTable = (EFI_ACPI_DESCRIPTION_HEADER *) CurrentTablePointer; + + // + // Install the RSDT tables + // + Status = InstallAcpiTable ( + AcpiProtocol, + CurrentTable, + CurrentTable->Length, + &TableHandle + ); + + if (EFI_ERROR (Status)) { + return Status; + } + + // + // Get the FACS and DSDT table address from the table FADT + // + if (!AsciiStrnCmp ((CHAR8 *) &CurrentTable->Signature, "FACP", 4)) { + Fadt1Table = (EFI_ACPI_1_0_FIXED_ACPI_DESCRIPTION_TABLE *) + (UINTN) CurrentTablePointer; + Facs1Table = (EFI_ACPI_1_0_FIRMWARE_ACPI_CONTROL_STRUCTURE *) + (UINTN) Fadt1Table->FirmwareCtrl; + DsdtTable = (EFI_ACPI_DESCRIPTION_HEADER *) (UINTN) Fadt1Table->Dsdt; + } + } + } + + // + // Install the FACS table. + // + if (Fadt2Table) { + // + // FACS 2.0 + // + Status = InstallAcpiTable ( + AcpiProtocol, + Facs2Table, + Facs2Table->Length, + &TableHandle + ); + if (EFI_ERROR (Status)) { + return Status; + } + } + else if (Fadt1Table) { + // + // FACS 1.0 + // + Status = InstallAcpiTable ( + AcpiProtocol, + Facs1Table, + Facs1Table->Length, + &TableHandle + ); + if (EFI_ERROR (Status)) { + return Status; + } + } + + // + // Install DSDT table. If we reached this point without finding the DSDT, + // then we're out of sync with the hypervisor, and cannot continue. + // + if (DsdtTable == NULL) { + DEBUG ((DEBUG_ERROR, "%a: no DSDT found\n", __FUNCTION__)); + ASSERT (FALSE); + CpuDeadLoop (); + } + + Status = InstallAcpiTable ( + AcpiProtocol, + DsdtTable, + DsdtTable->Length, + &TableHandle + ); + if (EFI_ERROR (Status)) { + return Status; + } + + return EFI_SUCCESS; +} + diff --git a/OvmfPkg/XenAcpiPlatformDxe/XenAcpiPlatformDxe.inf b/OvmfPkg/XenAcpiPlatformDxe/XenAcpiPlatformDxe.inf new file mode 100644 index 0000000000..379b5d56d5 --- /dev/null +++ b/OvmfPkg/XenAcpiPlatformDxe/XenAcpiPlatformDxe.inf @@ -0,0 +1,70 @@ +## @file +# OVMF ACPI Platform Driver for Xen guests +# +# Copyright (C) 2021, Red Hat, Inc. +# Copyright (c) 2008 - 2019, Intel Corporation. All rights reserved.
+# SPDX-License-Identifier: BSD-2-Clause-Patent +# +## + +[Defines] + INF_VERSION = 0x00010005 + BASE_NAME = XenAcpiPlatform + FILE_GUID = fa0a48ac-767d-4c88-b70c-ec54c8b900c4 + MODULE_TYPE = DXE_DRIVER + VERSION_STRING = 1.0 + ENTRY_POINT = AcpiPlatformEntryPoint + +# +# The following information is for reference only and not required by the build tools. +# +# VALID_ARCHITECTURES = IA32 X64 EBC +# + +[Sources] + AcpiPlatform.c + AcpiPlatform.h + BootScript.c + EntryPoint.c + PciDecoding.c + Qemu.c + QemuFwCfgAcpi.c + Xen.c + +[Packages] + MdeModulePkg/MdeModulePkg.dec + MdePkg/MdePkg.dec + OvmfPkg/OvmfPkg.dec + UefiCpuPkg/UefiCpuPkg.dec + +[LibraryClasses] + BaseLib + BaseMemoryLib + DebugLib + DxeServicesTableLib + MemoryAllocationLib + OrderedCollectionLib + PcdLib + QemuFwCfgLib + QemuFwCfgS3Lib + UefiBootServicesTableLib + UefiDriverEntryPoint + XenPlatformLib + +[Protocols] + gEfiAcpiTableProtocolGuid # PROTOCOL ALWAYS_CONSUMED + gEfiFirmwareVolume2ProtocolGuid # PROTOCOL SOMETIMES_CONSUMED + gEfiPciIoProtocolGuid # PROTOCOL SOMETIMES_CONSUMED + +[Guids] + gRootBridgesConnectedEventGroupGuid + +[Pcd] + gEfiMdeModulePkgTokenSpaceGuid.PcdAcpiTableStorageFile + gEfiMdeModulePkgTokenSpaceGuid.PcdPciDisableBusEnumeration + gUefiCpuPkgTokenSpaceGuid.PcdCpuLocalApicBaseAddress + gUefiOvmfPkgTokenSpaceGuid.Pcd8259LegacyModeEdgeLevel + gUefiOvmfPkgTokenSpaceGuid.PcdOvmfFdBaseAddress + +[Depex] + gEfiAcpiTableProtocolGuid -- 2.39.2