Recent changes in the QEMU ACPI table generator have shown that our
limited client for that interface is insufficient and/or brittle.

Implement the full interface utilizing OrderedCollectionLib for addressing
fw_cfg blobs by name.

In addition, we stop using EFI_ACPI_TABLE_PROTOCOL in the QEMU loader
client, because:
- splitting the blobs for EFI_ACPI_TABLE_PROTOCOL is just untenable short
  of a complete ACPI parser,
- and it is fully sufficient to install the RSD PTR as an EFI
  configuration table for the guest OS to see everything.

In short, for this interface, EFI_ACPI_TABLE_PROTOCOL is an unwanted and
restrictive convenience; let's stop using it.

Contributed-under: TianoCore Contribution Agreement 1.0
Signed-off-by: Laszlo Ersek <ler...@redhat.com>
---
 OvmfPkg/AcpiPlatformDxe/AcpiPlatformDxe.inf |   3 +
 OvmfPkg/AcpiPlatformDxe/Qemu.c              | 415 ++++++++++++++++++++++++++--
 2 files changed, 398 insertions(+), 20 deletions(-)

diff --git a/OvmfPkg/AcpiPlatformDxe/AcpiPlatformDxe.inf 
b/OvmfPkg/AcpiPlatformDxe/AcpiPlatformDxe.inf
index 90178e0..02eaf23 100644
--- a/OvmfPkg/AcpiPlatformDxe/AcpiPlatformDxe.inf
+++ b/OvmfPkg/AcpiPlatformDxe/AcpiPlatformDxe.inf
@@ -51,12 +51,15 @@
   MemoryAllocationLib
   BaseLib
   DxeServicesTableLib
+  OrderedCollectionLib
 
 [Protocols]
   gEfiAcpiTableProtocolGuid                     # PROTOCOL ALWAYS_CONSUMED
 
 [Guids]
   gEfiXenInfoGuid
+  gEfiAcpi10TableGuid
+  gEfiAcpi20TableGuid
 
 [Pcd]
   gEfiMdeModulePkgTokenSpaceGuid.PcdAcpiTableStorageFile
diff --git a/OvmfPkg/AcpiPlatformDxe/Qemu.c b/OvmfPkg/AcpiPlatformDxe/Qemu.c
index 333766e..4663ecb 100644
--- a/OvmfPkg/AcpiPlatformDxe/Qemu.c
+++ b/OvmfPkg/AcpiPlatformDxe/Qemu.c
@@ -17,12 +17,15 @@
 
 #include "AcpiPlatform.h"
 #include "QemuLoader.h"
-#include <Library/BaseMemoryLib.h>
-#include <Library/MemoryAllocationLib.h>
-#include <Library/QemuFwCfgLib.h>
-#include <Library/DxeServicesTableLib.h>
-#include <Library/PcdLib.h>
-#include <IndustryStandard/Acpi.h>
+#include <Library/BaseMemoryLib.h>        // CopyMem()
+#include <Library/MemoryAllocationLib.h>  // AllocatePool()
+#include <Library/QemuFwCfgLib.h>         // QemuFwCfgFindFile()
+#include <Library/DxeServicesTableLib.h>  // gDS
+#include <Library/PcdLib.h>               // PcdGet16()
+#include <Library/UefiLib.h>              // EfiGetSystemConfigurationTable()
+#include <Library/OrderedCollectionLib.h> // OrderedCollectionInit()
+#include <IndustryStandard/Acpi.h>        // EFI_ACPI_DESCRIPTION_HEADER
+#include <Guid/Acpi.h>                    // gEfiAcpi10TableGuid
 
 BOOLEAN
 QemuDetected (
@@ -518,7 +521,8 @@ QemuInstallAcpiTable (
 
 
 /**
-  Check if an array of bytes starts with an RSD PTR structure.
+  Check if an array of bytes starts with an RSD PTR structure, and if so,
+  return the EFI ACPI table GUID that corresponds to its version.
 
   Checksum is ignored.
 
@@ -526,8 +530,9 @@ QemuInstallAcpiTable (
 
   @param[in] Size       Number of bytes in Buffer.
 
-  @param[out] RsdpSize  If the function returns EFI_SUCCESS, this parameter
-                        contains the size of the detected RSD PTR structure.
+  @param[out] AcpiTableGuid  On successful exit, pointer to the EFI ACPI table
+                             GUID in statically allocated storage that
+                             corresponds to the detected RSD PTR version.
 
   @retval  EFI_SUCCESS         RSD PTR structure detected at the beginning of
                                Buffer, and its advertised size does not exceed
@@ -545,7 +550,7 @@ EFI_STATUS
 CheckRsdp (
   IN  CONST VOID *Buffer,
   IN  UINTN      Size,
-  OUT UINTN      *RsdpSize
+  OUT EFI_GUID   **AcpiTableGuid
   )
 {
   CONST UINT64                                       *Signature;
@@ -574,7 +579,7 @@ CheckRsdp (
     //
     // ACPI 1.0 doesn't include the Length field
     //
-    *RsdpSize = sizeof *Rsdp1;
+    *AcpiTableGuid = &gEfiAcpi10TableGuid;
     return EFI_SUCCESS;
   }
 
@@ -587,27 +592,99 @@ CheckRsdp (
     return EFI_PROTOCOL_ERROR;
   }
 
-  *RsdpSize = Rsdp2->Length;
+  *AcpiTableGuid = &gEfiAcpi20TableGuid;
   return EFI_SUCCESS;
 }
 
+//
+// 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.
+} 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);
+}
+
 /**
-  Download all ACPI table data files from QEMU and interpret them.
+  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.
 
-  @param[in] AcpiProtocol  The ACPI table protocol used to install tables.
+  @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);
+}
+
+/**
+  Download, process, and install ACPI table data from the QEMU loader
+  interface.
 
-  @retval  EFI_UNSUPPORTED       Firmware configuration is unavailable.
+  @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_OUT_OF_RESOURCES  Memory allocation failed.
 
   @retval  EFI_PROTOCOL_ERROR    Found invalid fw_cfg contents.
 
-  @return                        Status codes returned by
-                                 AcpiProtocol->InstallAcpiTable().
+  @retval  EFI_ALREADY_STARTED   One of the ACPI TABLE GUIDs has been found in
+                                 the EFI Configuration Table, indicating the
+                                 presence of a preexistent RSD PTR table, and
+                                 therefore that of another module installing
+                                 ACPI tables.
+
+  @retval  EFI_SUCCESS           Installation of ACPI tables succeeded.
 
 **/
 
@@ -617,5 +694,303 @@ InstallAllQemuLinkedTables (
   VOID
   )
 {
-  return CheckRsdp (NULL, 0, NULL);
+  EFI_STATUS               Status;
+  FIRMWARE_CONFIG_ITEM     FwCfgItem;
+  UINTN                    FwCfgSize;
+  VOID                     *Rsdp;
+  UINTN                    RsdpBufferSize;
+  UINT8                    *Loader;
+  CONST QEMU_LOADER_ENTRY  *LoaderEntry, *LoaderEnd;
+  ORDERED_COLLECTION       *Tracker;
+  ORDERED_COLLECTION_ENTRY *TrackerEntry, *TrackerEntry2;
+  BLOB                     *Blob;
+  EFI_GUID                 *AcpiTableGuid;
+
+  //
+  // This function allocates memory on four levels. From lowest to highest:
+  // - Areas consisting of whole pages, of type EfiACPIMemoryNVS, for
+  //   (processed) ACPI payload,
+  // - BLOB structures referencing the above, tracking their names, sizes, and
+  //   addresses,
+  // - ORDERED_COLLECTION_ENTRY objects internal to OrderedCollectionLib,
+  //   linking the BLOB structures,
+  // - an ORDERED_COLLECTION organizing the ORDERED_COLLECTION_ENTRY entries.
+  //
+  // On exit, the last three levels are torn down unconditionally. If we exit
+  // with success, then the first (lowest) level is left in place, constituting
+  // the ACPI tables for the guest. If we exit with error, then even the first
+  // (lowest) level is torn down.
+  //
+
+  Status = QemuFwCfgFindFile ("etc/table-loader", &FwCfgItem, &FwCfgSize);
+  if (EFI_ERROR (Status)) {
+    return Status;
+  }
+  if (FwCfgSize % sizeof *LoaderEntry != 0) {
+    DEBUG ((EFI_D_ERROR, "%a: \"etc/table-loader\" has invalid size 0x%Lx\n",
+      __FUNCTION__, (UINT64)FwCfgSize));
+    return EFI_PROTOCOL_ERROR;
+  }
+
+  if (!EFI_ERROR (EfiGetSystemConfigurationTable (
+                    &gEfiAcpi10TableGuid, &Rsdp)) ||
+      !EFI_ERROR (EfiGetSystemConfigurationTable (
+                    &gEfiAcpi20TableGuid, &Rsdp))) {
+    DEBUG ((EFI_D_ERROR, "%a: RSD PTR already present\n", __FUNCTION__));
+    return EFI_ALREADY_STARTED;
+  }
+
+  Loader = AllocatePool (FwCfgSize);
+  if (Loader == NULL) {
+    return EFI_OUT_OF_RESOURCES;
+  }
+
+  QemuFwCfgSelectItem (FwCfgItem);
+  QemuFwCfgReadBytes (FwCfgSize, Loader);
+
+  Tracker = OrderedCollectionInit (BlobCompare, BlobKeyCompare);
+  if (Tracker == NULL) {
+    Status = EFI_OUT_OF_RESOURCES;
+    goto FreeLoader;
+  }
+
+  Rsdp           = NULL;
+  RsdpBufferSize = 0;
+
+  LoaderEntry = (QEMU_LOADER_ENTRY *)Loader;
+  LoaderEnd   = (QEMU_LOADER_ENTRY *)(Loader + FwCfgSize);
+  while (LoaderEntry < LoaderEnd) {
+    switch (LoaderEntry->Type) {
+    case QemuLoaderCmdAllocate: {
+      CONST QEMU_LOADER_ALLOCATE *Allocate;
+      UINTN                      NumPages;
+      EFI_PHYSICAL_ADDRESS       Address;
+
+      Allocate = &LoaderEntry->Command.Allocate;
+      if (Allocate->File[QEMU_LOADER_FNAME_SIZE - 1] != '\0') {
+        DEBUG ((EFI_D_ERROR, "%a: malformed file name in Allocate command\n",
+          __FUNCTION__));
+        Status = EFI_PROTOCOL_ERROR;
+        goto FreeTracker;
+      }
+      if (Allocate->Alignment > EFI_PAGE_SIZE) {
+        DEBUG ((EFI_D_ERROR, "%a: unsupported alignment 0x%x in Allocate "
+          "command\n", __FUNCTION__, Allocate->Alignment));
+        Status = EFI_UNSUPPORTED;
+        goto FreeTracker;
+      }
+      Status = QemuFwCfgFindFile ((CHAR8 *)Allocate->File, &FwCfgItem,
+                 &FwCfgSize);
+      if (EFI_ERROR (Status)) {
+        DEBUG ((EFI_D_ERROR, "%a: nonexistent file \"%a\" in Allocate "
+          "command\n", __FUNCTION__, Allocate->File));
+        goto FreeTracker;
+      }
+
+      NumPages = EFI_SIZE_TO_PAGES (FwCfgSize);
+      Address = 0xFFFFFFFF;
+      Status = gBS->AllocatePages (AllocateMaxAddress, EfiACPIMemoryNVS,
+                      NumPages, &Address);
+      if (EFI_ERROR (Status)) {
+        goto FreeTracker;
+      }
+
+      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;
+
+      if (Allocate->Zone == QemuLoaderAllocFSeg) {
+        if (Rsdp == NULL) {
+          Rsdp = Blob->Base;
+          RsdpBufferSize = Blob->Size;
+        } else {
+          DEBUG ((EFI_D_ERROR, "%a: duplicate RSD PTR candidate in Allocate "
+            "command\n", __FUNCTION__));
+          Status = EFI_PROTOCOL_ERROR;
+          goto FreeBlob;
+        }
+      }
+
+      Status = OrderedCollectionInsert (Tracker, NULL, Blob);
+      if (Status == RETURN_ALREADY_STARTED) {
+        DEBUG ((EFI_D_ERROR, "%a: duplicated file \"%a\" in Allocate "
+          "command\n", __FUNCTION__, Allocate->File));
+        Status = EFI_PROTOCOL_ERROR;
+        goto FreeBlob;
+      }
+      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 ((EFI_D_VERBOSE, "%a: Allocate: 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));
+      break;
+
+FreeBlob:
+      FreePool (Blob);
+FreePages:
+      gBS->FreePages (Address, NumPages);
+      goto FreeTracker;
+    }
+
+    case QemuLoaderCmdAddPointer: {
+      CONST QEMU_LOADER_ADD_POINTER *AddPointer;
+      CONST BLOB                    *Blob2;
+      UINT8                         *PointerField;
+
+      AddPointer = &LoaderEntry->Command.AddPointer;
+      if (AddPointer->PointerFile[QEMU_LOADER_FNAME_SIZE - 1] != '\0' ||
+          AddPointer->PointeeFile[QEMU_LOADER_FNAME_SIZE - 1] != '\0') {
+        DEBUG ((EFI_D_ERROR, "%a: malformed file name in AddPointer command\n",
+          __FUNCTION__));
+        Status = EFI_PROTOCOL_ERROR;
+        goto FreeTracker;
+      }
+
+      TrackerEntry = OrderedCollectionFind (Tracker, AddPointer->PointerFile);
+      TrackerEntry2 = OrderedCollectionFind (Tracker, AddPointer->PointeeFile);
+      if (TrackerEntry == NULL || TrackerEntry2 == NULL) {
+        DEBUG ((EFI_D_ERROR, "%a: invalid blob reference(s) \"%a\" / \"%a\" "
+          "in AddPointer command\n", __FUNCTION__, AddPointer->PointerFile,
+          AddPointer->PointeeFile));
+        Status = EFI_PROTOCOL_ERROR;
+        goto FreeTracker;
+      }
+
+      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 ((EFI_D_ERROR, "%a: invalid pointer location in \"%a\" in "
+          "AddPointer command\n", __FUNCTION__, AddPointer->PointerFile));
+        Status = EFI_PROTOCOL_ERROR;
+        goto FreeTracker;
+      }
+
+      PointerField = Blob->Base + AddPointer->PointerOffset;
+      switch (AddPointer->PointerSize) {
+      case 1:
+        *PointerField += (UINT8)(UINTN)Blob2->Base;
+        break;
+
+      case 2:
+        *(UINT16 *)PointerField += (UINT16)(UINTN)Blob2->Base;
+        break;
+
+      case 4:
+        *(UINT32 *)PointerField += (UINT32)(UINTN)Blob2->Base;
+        break;
+
+      case 8:
+        *(UINT64 *)PointerField += (UINT64)(UINTN)Blob2->Base;
+        break;
+      }
+
+      DEBUG ((EFI_D_VERBOSE, "%a: AddPointer: PointerFile=\"%a\" "
+        "PointeeFile=\"%a\" PointerOffset=0x%x PointerSize=%d\n", __FUNCTION__,
+        AddPointer->PointerFile, AddPointer->PointeeFile,
+        AddPointer->PointerOffset, AddPointer->PointerSize));
+      break;
+    }
+
+    case QemuLoaderCmdAddChecksum: {
+      CONST QEMU_LOADER_ADD_CHECKSUM *AddChecksum;
+
+      AddChecksum = &LoaderEntry->Command.AddChecksum;
+      if (AddChecksum->File[QEMU_LOADER_FNAME_SIZE - 1] != '\0') {
+        DEBUG ((EFI_D_ERROR, "%a: malformed file name in AddChecksum "
+          "command\n", __FUNCTION__));
+        Status = EFI_PROTOCOL_ERROR;
+        goto FreeTracker;
+      }
+
+      TrackerEntry = OrderedCollectionFind (Tracker, AddChecksum->File);
+      if (TrackerEntry == NULL) {
+        DEBUG ((EFI_D_ERROR, "%a: invalid blob reference \"%a\" in "
+          "AddChecksum command\n", __FUNCTION__, AddChecksum->File));
+        Status = EFI_PROTOCOL_ERROR;
+        goto FreeTracker;
+      }
+
+      Blob = OrderedCollectionUserStruct (TrackerEntry);
+      if (Blob->Size <= AddChecksum->ResultOffset ||
+          Blob->Size < AddChecksum->Length ||
+          Blob->Size - AddChecksum->Length < AddChecksum->Start) {
+        DEBUG ((EFI_D_ERROR, "%a: invalid checksum location or range in "
+          "\"%a\" in AddChecksum command\n", __FUNCTION__, AddChecksum->File));
+        Status = EFI_PROTOCOL_ERROR;
+        goto FreeTracker;
+      }
+
+      Blob->Base[AddChecksum->ResultOffset] = CalculateCheckSum8 (
+                                               Blob->Base + AddChecksum->Start,
+                                               AddChecksum->Length
+                                               );
+      DEBUG ((EFI_D_VERBOSE, "%a: AddChecksum: File=\"%a\" ResultOffset=0x%x "
+        "Start=0x%x Length=0x%x\n", __FUNCTION__, AddChecksum->File,
+        AddChecksum->ResultOffset, AddChecksum->Start, AddChecksum->Length));
+      break;
+    }
+
+    default:
+      DEBUG ((EFI_D_VERBOSE, "%a: unknown loader command: 0x%x\n",
+        __FUNCTION__, LoaderEntry->Type));
+      break;
+    }
+
+    ++LoaderEntry;
+  }
+
+  if (Rsdp == NULL) {
+    DEBUG ((EFI_D_ERROR, "%a: no RSD PTR candidate\n", __FUNCTION__));
+    Status = EFI_PROTOCOL_ERROR;
+    goto FreeTracker;
+  }
+
+  AcpiTableGuid = NULL;
+  if (EFI_ERROR (CheckRsdp (Rsdp, RsdpBufferSize, &AcpiTableGuid))) {
+    DEBUG ((EFI_D_ERROR, "%a: RSD PTR not found in candidate\n",
+      __FUNCTION__));
+    Status = EFI_PROTOCOL_ERROR;
+    goto FreeTracker;
+  }
+
+  Status = gBS->InstallConfigurationTable (AcpiTableGuid, Rsdp);
+
+FreeTracker:
+  //
+  // Tear down the tracker structure, and if we're exiting with an error, the
+  // pages holding the blob data (ie. the processed ACPI payload) as well.
+  //
+  for (TrackerEntry = OrderedCollectionMin (Tracker); TrackerEntry != NULL;
+       TrackerEntry = TrackerEntry2) {
+    VOID *UserStruct;
+
+    TrackerEntry2 = OrderedCollectionNext (TrackerEntry);
+    OrderedCollectionDelete (Tracker, TrackerEntry, &UserStruct);
+    if (EFI_ERROR (Status)) {
+      Blob = UserStruct;
+      gBS->FreePages ((UINTN)Blob->Base, EFI_SIZE_TO_PAGES (Blob->Size));
+    }
+    FreePool (UserStruct);
+  }
+  OrderedCollectionUninit (Tracker);
+
+FreeLoader:
+  FreePool (Loader);
+  return Status;
 }
-- 
1.8.3.1


------------------------------------------------------------------------------
_______________________________________________
edk2-devel mailing list
edk2-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/edk2-devel

Reply via email to