| /** @file | |
| OVMF ACPI support using QEMU's fw-cfg interface | |
| Copyright (c) 2008 - 2014, Intel Corporation. All rights reserved.<BR> | |
| Copyright (C) 2012-2014, Red Hat, Inc. | |
| SPDX-License-Identifier: BSD-2-Clause-Patent | |
| **/ | |
| #include <IndustryStandard/Acpi.h> // EFI_ACPI_DESCRIPTION_HEADER | |
| #include <IndustryStandard/QemuLoader.h> // QEMU_LOADER_FNAME_SIZE | |
| #include <IndustryStandard/UefiTcgPlatform.h> | |
| #include <Library/AcpiPlatformLib.h> | |
| #include <Library/BaseLib.h> // AsciiStrCmp() | |
| #include <Library/BaseMemoryLib.h> // CopyMem() | |
| #include <Library/DebugLib.h> // DEBUG() | |
| #include <Library/MemoryAllocationLib.h> // AllocatePool() | |
| #include <Library/OrderedCollectionLib.h> // OrderedCollectionMin() | |
| #include <Library/QemuFwCfgLib.h> // QemuFwCfgFindFile() | |
| #include <Library/QemuFwCfgS3Lib.h> // QemuFwCfgS3Enabled() | |
| #include <Library/UefiBootServicesTableLib.h> // gBS | |
| #include <Library/TpmMeasurementLib.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", __func__)); | |
| 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", | |
| __func__, | |
| 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", __func__)); | |
| return EFI_PROTOCOL_ERROR; | |
| } | |
| if (Allocate->Alignment > EFI_PAGE_SIZE) { | |
| DEBUG (( | |
| DEBUG_ERROR, | |
| "%a: unsupported alignment 0x%x\n", | |
| __func__, | |
| Allocate->Alignment | |
| )); | |
| return EFI_UNSUPPORTED; | |
| } | |
| Status = QemuFwCfgFindFile ((CHAR8 *)Allocate->File, &FwCfgItem, &FwCfgSize); | |
| if (EFI_ERROR (Status)) { | |
| DEBUG (( | |
| DEBUG_ERROR, | |
| "%a: QemuFwCfgFindFile(\"%a\"): %r\n", | |
| __func__, | |
| 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", | |
| __func__, | |
| 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", | |
| __func__, | |
| Allocate->File, | |
| Allocate->Alignment, | |
| Allocate->Zone, | |
| (UINT64)Blob->Size, | |
| (UINT64)(UINTN)Blob->Base | |
| )); | |
| // | |
| // Measure the data which is downloaded from QEMU. | |
| // It has to be done before it is consumed. Because the data will | |
| // be updated in the following operations. | |
| // | |
| TpmMeasureAndLogData ( | |
| 1, | |
| EV_PLATFORM_CONFIG_FLAGS, | |
| EV_POSTCODE_INFO_ACPI_DATA, | |
| ACPI_DATA_LEN, | |
| (VOID *)(UINTN)Blob->Base, | |
| Blob->Size | |
| ); | |
| 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", __func__)); | |
| 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", | |
| __func__, | |
| 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", | |
| __func__, | |
| 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", | |
| __func__, | |
| 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", | |
| __func__, | |
| 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", | |
| __func__, | |
| 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", __func__)); | |
| return EFI_PROTOCOL_ERROR; | |
| } | |
| TrackerEntry = OrderedCollectionFind (Tracker, AddChecksum->File); | |
| if (TrackerEntry == NULL) { | |
| DEBUG (( | |
| DEBUG_ERROR, | |
| "%a: invalid blob reference \"%a\"\n", | |
| __func__, | |
| 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", | |
| __func__, | |
| 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", | |
| __func__, | |
| 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", __func__)); | |
| 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", | |
| __func__, | |
| 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", | |
| __func__, | |
| WritePointer->PointerFile | |
| )); | |
| return EFI_PROTOCOL_ERROR; | |
| } | |
| PointeeBlob = OrderedCollectionUserStruct (PointeeEntry); | |
| PointerValue = WritePointer->PointeeOffset; | |
| if (PointerValue >= PointeeBlob->Size) { | |
| DEBUG ((DEBUG_ERROR, "%a: invalid PointeeOffset\n", __func__)); | |
| 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", | |
| __func__, | |
| 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", | |
| __func__, | |
| 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", | |
| __func__, | |
| 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", | |
| __func__, | |
| 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): ", | |
| __func__, | |
| 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", | |
| __func__, | |
| 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", | |
| __func__, | |
| 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; | |
| EFI_HANDLE QemuAcpiHandle; | |
| 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", | |
| __func__, | |
| (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); | |
| // | |
| // Measure the "etc/table-loader" which is downloaded from QEMU. | |
| // It has to be done before it is consumed. Because it would be | |
| // updated in the following operations. | |
| // | |
| TpmMeasureAndLogData ( | |
| 1, | |
| EV_PLATFORM_CONFIG_FLAGS, | |
| EV_POSTCODE_INFO_ACPI_DATA, | |
| ACPI_DATA_LEN, | |
| (VOID *)(UINTN)LoaderStart, | |
| FwCfgSize | |
| ); | |
| 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", | |
| __func__, | |
| 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; | |
| } | |
| } | |
| } | |
| // | |
| // Install a protocol to notify that the ACPI table provided by Qemu is | |
| // ready. | |
| // | |
| QemuAcpiHandle = NULL; | |
| Status = gBS->InstallProtocolInterface ( | |
| &QemuAcpiHandle, | |
| &gQemuAcpiTableNotifyProtocolGuid, | |
| EFI_NATIVE_INTERFACE, | |
| NULL | |
| ); | |
| 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 UninstallQemuAcpiTableNotifyProtocol; | |
| } | |
| // | |
| // Ownership of S3Context has been transferred. | |
| // | |
| S3Context = NULL; | |
| } | |
| DEBUG ((DEBUG_INFO, "%a: installed %d tables\n", __func__, Installed)); | |
| UninstallQemuAcpiTableNotifyProtocol: | |
| if (EFI_ERROR (Status)) { | |
| gBS->UninstallProtocolInterface ( | |
| QemuAcpiHandle, | |
| &gQemuAcpiTableNotifyProtocolGuid, | |
| NULL | |
| ); | |
| } | |
| UninstallAcpiTables: | |
| if (EFI_ERROR (Status)) { | |
| // | |
| // roll back partial installation | |
| // | |
| while (Installed > 0) { | |
| --Installed; | |
| AcpiProtocol->UninstallAcpiTable (AcpiProtocol, InstalledKey[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", | |
| __func__, | |
| 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; | |
| } |