2 OVMF ACPI support using QEMU's fw-cfg interface
4 Copyright (c) 2008 - 2014, Intel Corporation. All rights reserved.<BR>
5 Copyright (C) 2012-2014, Red Hat, Inc.
7 SPDX-License-Identifier: BSD-2-Clause-Patent
11 #include <IndustryStandard/Acpi.h> // EFI_ACPI_DESCRIPTION_HEADER
12 #include <IndustryStandard/QemuLoader.h> // QEMU_LOADER_FNAME_SIZE
13 #include <Library/BaseLib.h> // AsciiStrCmp()
14 #include <Library/BaseMemoryLib.h> // CopyMem()
15 #include <Library/DebugLib.h> // DEBUG()
16 #include <Library/MemoryAllocationLib.h> // AllocatePool()
17 #include <Library/OrderedCollectionLib.h> // OrderedCollectionMin()
18 #include <Library/QemuFwCfgLib.h> // QemuFwCfgFindFile()
19 #include <Library/QemuFwCfgS3Lib.h> // QemuFwCfgS3Enabled()
20 #include <Library/UefiBootServicesTableLib.h> // gBS
22 #include <Protocol/QemuAcpiTableNotify.h>
23 #include "AcpiPlatform.h"
24 EFI_HANDLE mQemuAcpiHandle
= NULL
;
25 QEMU_ACPI_TABLE_NOTIFY_PROTOCOL mAcpiNotifyProtocol
;
28 // The user structure for the ordered collection that will track the fw_cfg
29 // blobs under processing.
32 UINT8 File
[QEMU_LOADER_FNAME_SIZE
]; // NUL-terminated name of the fw_cfg
33 // blob. This is the ordering / search
35 UINTN Size
; // The number of bytes in this blob.
36 UINT8
*Base
; // Pointer to the blob data.
37 BOOLEAN HostsOnlyTableData
; // TRUE iff the blob has been found to
38 // only contain data that is directly
39 // part of ACPI tables.
43 Compare a standalone key against a user structure containing an embedded key.
45 @param[in] StandaloneKey Pointer to the bare key.
47 @param[in] UserStruct Pointer to the user structure with the embedded
50 @retval <0 If StandaloneKey compares less than UserStruct's key.
52 @retval 0 If StandaloneKey compares equal to UserStruct's key.
54 @retval >0 If StandaloneKey compares greater than UserStruct's key.
60 IN CONST VOID
*StandaloneKey
,
61 IN CONST VOID
*UserStruct
67 return AsciiStrCmp (StandaloneKey
, (CONST CHAR8
*)Blob
->File
);
71 Comparator function for two user structures.
73 @param[in] UserStruct1 Pointer to the first user structure.
75 @param[in] UserStruct2 Pointer to the second user structure.
77 @retval <0 If UserStruct1 compares less than UserStruct2.
79 @retval 0 If UserStruct1 compares equal to UserStruct2.
81 @retval >0 If UserStruct1 compares greater than UserStruct2.
87 IN CONST VOID
*UserStruct1
,
88 IN CONST VOID
*UserStruct2
94 return BlobKeyCompare (Blob1
->File
, UserStruct2
);
98 Comparator function for two opaque pointers, ordering on (unsigned) pointer
100 Can be used as both Key and UserStruct comparator.
102 @param[in] Pointer1 First pointer.
104 @param[in] Pointer2 Second pointer.
106 @retval <0 If Pointer1 compares less than Pointer2.
108 @retval 0 If Pointer1 compares equal to Pointer2.
110 @retval >0 If Pointer1 compares greater than Pointer2.
116 IN CONST VOID
*Pointer1
,
117 IN CONST VOID
*Pointer2
120 if (Pointer1
== Pointer2
) {
124 if ((UINTN
)Pointer1
< (UINTN
)Pointer2
) {
132 Comparator function for two ASCII strings. Can be used as both Key and
133 UserStruct comparator.
135 This function exists solely so we can avoid casting &AsciiStrCmp to
136 ORDERED_COLLECTION_USER_COMPARE and ORDERED_COLLECTION_KEY_COMPARE.
138 @param[in] AsciiString1 Pointer to the first ASCII string.
140 @param[in] AsciiString2 Pointer to the second ASCII string.
142 @return The return value of AsciiStrCmp (AsciiString1, AsciiString2).
148 IN CONST VOID
*AsciiString1
,
149 IN CONST VOID
*AsciiString2
152 return AsciiStrCmp (AsciiString1
, AsciiString2
);
156 Release the ORDERED_COLLECTION structure populated by
157 CollectAllocationsRestrictedTo32Bit() (below).
159 This function may be called by CollectAllocationsRestrictedTo32Bit() itself,
162 @param[in] AllocationsRestrictedTo32Bit The ORDERED_COLLECTION structure to
167 ReleaseAllocationsRestrictedTo32Bit (
168 IN ORDERED_COLLECTION
*AllocationsRestrictedTo32Bit
171 ORDERED_COLLECTION_ENTRY
*Entry
, *Entry2
;
173 for (Entry
= OrderedCollectionMin (AllocationsRestrictedTo32Bit
);
177 Entry2
= OrderedCollectionNext (Entry
);
178 OrderedCollectionDelete (AllocationsRestrictedTo32Bit
, Entry
, NULL
);
181 OrderedCollectionUninit (AllocationsRestrictedTo32Bit
);
185 Iterate over the linker/loader script, and collect the names of the fw_cfg
186 blobs that are referenced by QEMU_LOADER_ADD_POINTER.PointeeFile fields, such
187 that QEMU_LOADER_ADD_POINTER.PointerSize is less than 8. This means that the
188 pointee blob's address will have to be patched into a narrower-than-8 byte
189 pointer field, hence the pointee blob must not be allocated from 64-bit
192 @param[out] AllocationsRestrictedTo32Bit The ORDERED_COLLECTION structure
193 linking (not copying / owning) such
194 QEMU_LOADER_ADD_POINTER.PointeeFile
195 fields that name the blobs
196 restricted from 64-bit allocation.
198 @param[in] LoaderStart Points to the first entry in the
199 linker/loader script.
201 @param[in] LoaderEnd Points one past the last entry in
202 the linker/loader script.
204 @retval EFI_SUCCESS AllocationsRestrictedTo32Bit has been
207 @retval EFI_OUT_OF_RESOURCES Memory allocation failed.
209 @retval EFI_PROTOCOL_ERROR Invalid linker/loader script contents.
213 CollectAllocationsRestrictedTo32Bit (
214 OUT ORDERED_COLLECTION
**AllocationsRestrictedTo32Bit
,
215 IN CONST QEMU_LOADER_ENTRY
*LoaderStart
,
216 IN CONST QEMU_LOADER_ENTRY
*LoaderEnd
219 ORDERED_COLLECTION
*Collection
;
220 CONST QEMU_LOADER_ENTRY
*LoaderEntry
;
223 Collection
= OrderedCollectionInit (AsciiStringCompare
, AsciiStringCompare
);
224 if (Collection
== NULL
) {
225 return EFI_OUT_OF_RESOURCES
;
228 for (LoaderEntry
= LoaderStart
; LoaderEntry
< LoaderEnd
; ++LoaderEntry
) {
229 CONST QEMU_LOADER_ADD_POINTER
*AddPointer
;
231 if (LoaderEntry
->Type
!= QemuLoaderCmdAddPointer
) {
235 AddPointer
= &LoaderEntry
->Command
.AddPointer
;
237 if (AddPointer
->PointerSize
>= 8) {
241 if (AddPointer
->PointeeFile
[QEMU_LOADER_FNAME_SIZE
- 1] != '\0') {
242 DEBUG ((DEBUG_ERROR
, "%a: malformed file name\n", __FUNCTION__
));
243 Status
= EFI_PROTOCOL_ERROR
;
247 Status
= OrderedCollectionInsert (
250 (VOID
*)AddPointer
->PointeeFile
256 "%a: restricting blob \"%a\" from 64-bit allocation\n",
258 AddPointer
->PointeeFile
261 case EFI_ALREADY_STARTED
:
263 // The restriction has been recorded already.
266 case EFI_OUT_OF_RESOURCES
:
273 *AllocationsRestrictedTo32Bit
= Collection
;
277 ReleaseAllocationsRestrictedTo32Bit (Collection
);
282 Process a QEMU_LOADER_ALLOCATE command.
284 @param[in] Allocate The QEMU_LOADER_ALLOCATE command to
287 @param[in,out] Tracker The ORDERED_COLLECTION tracking the
288 BLOB user structures created thus
291 @param[in] AllocationsRestrictedTo32Bit The ORDERED_COLLECTION populated by
293 CollectAllocationsRestrictedTo32Bit,
294 naming the fw_cfg blobs that must
295 not be allocated from 64-bit address
298 @retval EFI_SUCCESS An area of whole AcpiNVS pages has been
299 allocated for the blob contents, and the
300 contents have been saved. A BLOB object (user
301 structure) has been allocated from pool memory,
302 referencing the blob contents. The BLOB user
303 structure has been linked into Tracker.
305 @retval EFI_PROTOCOL_ERROR Malformed fw_cfg file name has been found in
306 Allocate, or the Allocate command references a
307 file that is already known by Tracker.
309 @retval EFI_UNSUPPORTED Unsupported alignment request has been found in
312 @retval EFI_OUT_OF_RESOURCES Pool allocation failed.
314 @return Error codes from QemuFwCfgFindFile() and
315 gBS->AllocatePages().
321 IN CONST QEMU_LOADER_ALLOCATE
*Allocate
,
322 IN OUT ORDERED_COLLECTION
*Tracker
,
323 IN ORDERED_COLLECTION
*AllocationsRestrictedTo32Bit
326 FIRMWARE_CONFIG_ITEM FwCfgItem
;
330 EFI_PHYSICAL_ADDRESS Address
;
333 if (Allocate
->File
[QEMU_LOADER_FNAME_SIZE
- 1] != '\0') {
334 DEBUG ((DEBUG_ERROR
, "%a: malformed file name\n", __FUNCTION__
));
335 return EFI_PROTOCOL_ERROR
;
338 if (Allocate
->Alignment
> EFI_PAGE_SIZE
) {
341 "%a: unsupported alignment 0x%x\n",
345 return EFI_UNSUPPORTED
;
348 Status
= QemuFwCfgFindFile ((CHAR8
*)Allocate
->File
, &FwCfgItem
, &FwCfgSize
);
349 if (EFI_ERROR (Status
)) {
352 "%a: QemuFwCfgFindFile(\"%a\"): %r\n",
360 NumPages
= EFI_SIZE_TO_PAGES (FwCfgSize
);
361 Address
= MAX_UINT64
;
362 if (OrderedCollectionFind (
363 AllocationsRestrictedTo32Bit
,
367 Address
= MAX_UINT32
;
370 Status
= gBS
->AllocatePages (
376 if (EFI_ERROR (Status
)) {
380 Blob
= AllocatePool (sizeof *Blob
);
382 Status
= EFI_OUT_OF_RESOURCES
;
386 CopyMem (Blob
->File
, Allocate
->File
, QEMU_LOADER_FNAME_SIZE
);
387 Blob
->Size
= FwCfgSize
;
388 Blob
->Base
= (VOID
*)(UINTN
)Address
;
389 Blob
->HostsOnlyTableData
= TRUE
;
391 Status
= OrderedCollectionInsert (Tracker
, NULL
, Blob
);
392 if (Status
== RETURN_ALREADY_STARTED
) {
395 "%a: duplicated file \"%a\"\n",
399 Status
= EFI_PROTOCOL_ERROR
;
402 if (EFI_ERROR (Status
)) {
406 QemuFwCfgSelectItem (FwCfgItem
);
407 QemuFwCfgReadBytes (FwCfgSize
, Blob
->Base
);
408 ZeroMem (Blob
->Base
+ Blob
->Size
, EFI_PAGES_TO_SIZE (NumPages
) - Blob
->Size
);
412 "%a: File=\"%a\" Alignment=0x%x Zone=%d Size=0x%Lx "
419 (UINT64
)(UINTN
)Blob
->Base
427 gBS
->FreePages (Address
, NumPages
);
433 Process a QEMU_LOADER_ADD_POINTER command.
435 @param[in] AddPointer The QEMU_LOADER_ADD_POINTER command to process.
437 @param[in] Tracker The ORDERED_COLLECTION tracking the BLOB user
438 structures created thus far.
440 @retval EFI_PROTOCOL_ERROR Malformed fw_cfg file name(s) have been found in
441 AddPointer, or the AddPointer command references
442 a file unknown to Tracker, or the pointer to
443 relocate has invalid location, size, or value, or
444 the relocated pointer value is not representable
445 in the given pointer size.
447 @retval EFI_SUCCESS The pointer field inside the pointer blob has
453 ProcessCmdAddPointer (
454 IN CONST QEMU_LOADER_ADD_POINTER
*AddPointer
,
455 IN CONST ORDERED_COLLECTION
*Tracker
458 ORDERED_COLLECTION_ENTRY
*TrackerEntry
, *TrackerEntry2
;
463 if ((AddPointer
->PointerFile
[QEMU_LOADER_FNAME_SIZE
- 1] != '\0') ||
464 (AddPointer
->PointeeFile
[QEMU_LOADER_FNAME_SIZE
- 1] != '\0'))
466 DEBUG ((DEBUG_ERROR
, "%a: malformed file name\n", __FUNCTION__
));
467 return EFI_PROTOCOL_ERROR
;
470 TrackerEntry
= OrderedCollectionFind (Tracker
, AddPointer
->PointerFile
);
471 TrackerEntry2
= OrderedCollectionFind (Tracker
, AddPointer
->PointeeFile
);
472 if ((TrackerEntry
== NULL
) || (TrackerEntry2
== NULL
)) {
475 "%a: invalid blob reference(s) \"%a\" / \"%a\"\n",
477 AddPointer
->PointerFile
,
478 AddPointer
->PointeeFile
480 return EFI_PROTOCOL_ERROR
;
483 Blob
= OrderedCollectionUserStruct (TrackerEntry
);
484 Blob2
= OrderedCollectionUserStruct (TrackerEntry2
);
485 if (((AddPointer
->PointerSize
!= 1) && (AddPointer
->PointerSize
!= 2) &&
486 (AddPointer
->PointerSize
!= 4) && (AddPointer
->PointerSize
!= 8)) ||
487 (Blob
->Size
< AddPointer
->PointerSize
) ||
488 (Blob
->Size
- AddPointer
->PointerSize
< AddPointer
->PointerOffset
))
492 "%a: invalid pointer location or size in \"%a\"\n",
494 AddPointer
->PointerFile
496 return EFI_PROTOCOL_ERROR
;
499 PointerField
= Blob
->Base
+ AddPointer
->PointerOffset
;
501 CopyMem (&PointerValue
, PointerField
, AddPointer
->PointerSize
);
502 if (PointerValue
>= Blob2
->Size
) {
505 "%a: invalid pointer value in \"%a\"\n",
507 AddPointer
->PointerFile
509 return EFI_PROTOCOL_ERROR
;
513 // The memory allocation system ensures that the address of the byte past the
514 // last byte of any allocated object is expressible (no wraparound).
516 ASSERT ((UINTN
)Blob2
->Base
<= MAX_ADDRESS
- Blob2
->Size
);
518 PointerValue
+= (UINT64
)(UINTN
)Blob2
->Base
;
519 if ((AddPointer
->PointerSize
< 8) &&
520 (RShiftU64 (PointerValue
, AddPointer
->PointerSize
* 8) != 0))
524 "%a: relocated pointer value unrepresentable in "
527 AddPointer
->PointerFile
529 return EFI_PROTOCOL_ERROR
;
532 CopyMem (PointerField
, &PointerValue
, AddPointer
->PointerSize
);
536 "%a: PointerFile=\"%a\" PointeeFile=\"%a\" "
537 "PointerOffset=0x%x PointerSize=%d\n",
539 AddPointer
->PointerFile
,
540 AddPointer
->PointeeFile
,
541 AddPointer
->PointerOffset
,
542 AddPointer
->PointerSize
548 Process a QEMU_LOADER_ADD_CHECKSUM command.
550 @param[in] AddChecksum The QEMU_LOADER_ADD_CHECKSUM command to process.
552 @param[in] Tracker The ORDERED_COLLECTION tracking the BLOB user
553 structures created thus far.
555 @retval EFI_PROTOCOL_ERROR Malformed fw_cfg file name has been found in
556 AddChecksum, or the AddChecksum command
557 references a file unknown to Tracker, or the
558 range to checksum is invalid.
560 @retval EFI_SUCCESS The requested range has been checksummed.
565 ProcessCmdAddChecksum (
566 IN CONST QEMU_LOADER_ADD_CHECKSUM
*AddChecksum
,
567 IN CONST ORDERED_COLLECTION
*Tracker
570 ORDERED_COLLECTION_ENTRY
*TrackerEntry
;
573 if (AddChecksum
->File
[QEMU_LOADER_FNAME_SIZE
- 1] != '\0') {
574 DEBUG ((DEBUG_ERROR
, "%a: malformed file name\n", __FUNCTION__
));
575 return EFI_PROTOCOL_ERROR
;
578 TrackerEntry
= OrderedCollectionFind (Tracker
, AddChecksum
->File
);
579 if (TrackerEntry
== NULL
) {
582 "%a: invalid blob reference \"%a\"\n",
586 return EFI_PROTOCOL_ERROR
;
589 Blob
= OrderedCollectionUserStruct (TrackerEntry
);
590 if ((Blob
->Size
<= AddChecksum
->ResultOffset
) ||
591 (Blob
->Size
< AddChecksum
->Length
) ||
592 (Blob
->Size
- AddChecksum
->Length
< AddChecksum
->Start
))
596 "%a: invalid checksum range in \"%a\"\n",
600 return EFI_PROTOCOL_ERROR
;
603 Blob
->Base
[AddChecksum
->ResultOffset
] = CalculateCheckSum8 (
604 Blob
->Base
+ AddChecksum
->Start
,
609 "%a: File=\"%a\" ResultOffset=0x%x Start=0x%x "
613 AddChecksum
->ResultOffset
,
621 Process a QEMU_LOADER_WRITE_POINTER command.
623 @param[in] WritePointer The QEMU_LOADER_WRITE_POINTER command to process.
625 @param[in] Tracker The ORDERED_COLLECTION tracking the BLOB user
626 structures created thus far.
628 @param[in,out] S3Context The S3_CONTEXT object capturing the fw_cfg actions
629 of successfully processed QEMU_LOADER_WRITE_POINTER
630 commands, to be replayed at S3 resume. S3Context
631 may be NULL if S3 is disabled.
633 @retval EFI_PROTOCOL_ERROR Malformed fw_cfg file name(s) have been found in
634 WritePointer. Or, the WritePointer command
635 references a file unknown to Tracker or the
636 fw_cfg directory. Or, the pointer object to
637 rewrite has invalid location, size, or initial
638 relative value. Or, the pointer value to store
639 does not fit in the given pointer size.
641 @retval EFI_SUCCESS The pointer object inside the writeable fw_cfg
642 file has been written. If S3Context is not NULL,
643 then WritePointer has been condensed into
646 @return Error codes propagated from
647 SaveCondensedWritePointerToS3Context(). The
648 pointer object inside the writeable fw_cfg file
649 has not been written.
653 ProcessCmdWritePointer (
654 IN CONST QEMU_LOADER_WRITE_POINTER
*WritePointer
,
655 IN CONST ORDERED_COLLECTION
*Tracker
,
656 IN OUT S3_CONTEXT
*S3Context OPTIONAL
659 RETURN_STATUS Status
;
660 FIRMWARE_CONFIG_ITEM PointerItem
;
661 UINTN PointerItemSize
;
662 ORDERED_COLLECTION_ENTRY
*PointeeEntry
;
666 if ((WritePointer
->PointerFile
[QEMU_LOADER_FNAME_SIZE
- 1] != '\0') ||
667 (WritePointer
->PointeeFile
[QEMU_LOADER_FNAME_SIZE
- 1] != '\0'))
669 DEBUG ((DEBUG_ERROR
, "%a: malformed file name\n", __FUNCTION__
));
670 return EFI_PROTOCOL_ERROR
;
673 Status
= QemuFwCfgFindFile (
674 (CONST CHAR8
*)WritePointer
->PointerFile
,
678 PointeeEntry
= OrderedCollectionFind (Tracker
, WritePointer
->PointeeFile
);
679 if (RETURN_ERROR (Status
) || (PointeeEntry
== NULL
)) {
682 "%a: invalid fw_cfg file or blob reference \"%a\" / \"%a\"\n",
684 WritePointer
->PointerFile
,
685 WritePointer
->PointeeFile
687 return EFI_PROTOCOL_ERROR
;
690 if (((WritePointer
->PointerSize
!= 1) && (WritePointer
->PointerSize
!= 2) &&
691 (WritePointer
->PointerSize
!= 4) && (WritePointer
->PointerSize
!= 8)) ||
692 (PointerItemSize
< WritePointer
->PointerSize
) ||
693 (PointerItemSize
- WritePointer
->PointerSize
<
694 WritePointer
->PointerOffset
))
698 "%a: invalid pointer location or size in \"%a\"\n",
700 WritePointer
->PointerFile
702 return EFI_PROTOCOL_ERROR
;
705 PointeeBlob
= OrderedCollectionUserStruct (PointeeEntry
);
706 PointerValue
= WritePointer
->PointeeOffset
;
707 if (PointerValue
>= PointeeBlob
->Size
) {
708 DEBUG ((DEBUG_ERROR
, "%a: invalid PointeeOffset\n", __FUNCTION__
));
709 return EFI_PROTOCOL_ERROR
;
713 // The memory allocation system ensures that the address of the byte past the
714 // last byte of any allocated object is expressible (no wraparound).
716 ASSERT ((UINTN
)PointeeBlob
->Base
<= MAX_ADDRESS
- PointeeBlob
->Size
);
718 PointerValue
+= (UINT64
)(UINTN
)PointeeBlob
->Base
;
719 if ((WritePointer
->PointerSize
< 8) &&
720 (RShiftU64 (PointerValue
, WritePointer
->PointerSize
* 8) != 0))
724 "%a: pointer value unrepresentable in \"%a\"\n",
726 WritePointer
->PointerFile
728 return EFI_PROTOCOL_ERROR
;
732 // If S3 is enabled, we have to capture the below fw_cfg actions in condensed
733 // form, to be replayed during S3 resume.
735 if (S3Context
!= NULL
) {
736 EFI_STATUS SaveStatus
;
738 SaveStatus
= SaveCondensedWritePointerToS3Context (
741 WritePointer
->PointerSize
,
742 WritePointer
->PointerOffset
,
745 if (EFI_ERROR (SaveStatus
)) {
750 QemuFwCfgSelectItem (PointerItem
);
751 QemuFwCfgSkipBytes (WritePointer
->PointerOffset
);
752 QemuFwCfgWriteBytes (WritePointer
->PointerSize
, &PointerValue
);
755 // Because QEMU has now learned PointeeBlob->Base, we must mark PointeeBlob
756 // as unreleasable, for the case when the whole linker/loader script is
757 // handled successfully.
759 PointeeBlob
->HostsOnlyTableData
= FALSE
;
763 "%a: PointerFile=\"%a\" PointeeFile=\"%a\" "
764 "PointerOffset=0x%x PointeeOffset=0x%x PointerSize=%d\n",
766 WritePointer
->PointerFile
,
767 WritePointer
->PointeeFile
,
768 WritePointer
->PointerOffset
,
769 WritePointer
->PointeeOffset
,
770 WritePointer
->PointerSize
776 Undo a QEMU_LOADER_WRITE_POINTER command.
778 This function revokes (zeroes out) a guest memory reference communicated to
779 QEMU earlier. The caller is responsible for invoking this function only on
780 such QEMU_LOADER_WRITE_POINTER commands that have been successfully processed
781 by ProcessCmdWritePointer().
783 @param[in] WritePointer The QEMU_LOADER_WRITE_POINTER command to undo.
787 UndoCmdWritePointer (
788 IN CONST QEMU_LOADER_WRITE_POINTER
*WritePointer
791 RETURN_STATUS Status
;
792 FIRMWARE_CONFIG_ITEM PointerItem
;
793 UINTN PointerItemSize
;
796 Status
= QemuFwCfgFindFile (
797 (CONST CHAR8
*)WritePointer
->PointerFile
,
801 ASSERT_RETURN_ERROR (Status
);
804 QemuFwCfgSelectItem (PointerItem
);
805 QemuFwCfgSkipBytes (WritePointer
->PointerOffset
);
806 QemuFwCfgWriteBytes (WritePointer
->PointerSize
, &PointerValue
);
810 "%a: PointerFile=\"%a\" PointerOffset=0x%x PointerSize=%d\n",
812 WritePointer
->PointerFile
,
813 WritePointer
->PointerOffset
,
814 WritePointer
->PointerSize
819 // We'll be saving the keys of installed tables so that we can roll them back
820 // in case of failure. 128 tables should be enough for anyone (TM).
822 #define INSTALLED_TABLES_MAX 128
825 Process a QEMU_LOADER_ADD_POINTER command in order to see if its target byte
826 array is an ACPI table, and if so, install it.
828 This function assumes that the entire QEMU linker/loader command file has
829 been processed successfully in a prior first pass.
831 @param[in] AddPointer The QEMU_LOADER_ADD_POINTER command to process.
833 @param[in] Tracker The ORDERED_COLLECTION tracking the BLOB user
836 @param[in] AcpiProtocol The ACPI table protocol used to install tables.
838 @param[in,out] InstalledKey On input, an array of INSTALLED_TABLES_MAX UINTN
839 elements, allocated by the caller. On output,
840 the function will have stored (appended) the
841 AcpiProtocol-internal key of the ACPI table that
842 the function has installed, if the AddPointer
843 command identified an ACPI table that is
844 different from RSDT and XSDT.
846 @param[in,out] NumInstalled On input, the number of entries already used in
847 InstalledKey; it must be in [0,
848 INSTALLED_TABLES_MAX] inclusive. On output, the
849 parameter is incremented if the AddPointer
850 command identified an ACPI table that is
851 different from RSDT and XSDT.
853 @param[in,out] SeenPointers The ORDERED_COLLECTION tracking the absolute
854 target addresses that have been pointed-to by
855 QEMU_LOADER_ADD_POINTER commands thus far. If a
856 target address is encountered for the first
857 time, and it identifies an ACPI table that is
858 different from RDST and XSDT, the table is
859 installed. If a target address is seen for the
860 second or later times, it is skipped without
863 @retval EFI_INVALID_PARAMETER NumInstalled was outside the allowed range on
866 @retval EFI_OUT_OF_RESOURCES The AddPointer command identified an ACPI
867 table different from RSDT and XSDT, but there
868 was no more room in InstalledKey.
870 @retval EFI_SUCCESS AddPointer has been processed. Either its
871 absolute target address has been encountered
872 before, or an ACPI table different from RSDT
873 and XSDT has been installed (reflected by
874 InstalledKey and NumInstalled), or RSDT or
875 XSDT has been identified but not installed, or
876 the fw_cfg blob pointed-into by AddPointer has
877 been marked as hosting something else than
878 just direct ACPI table contents.
880 @return Error codes returned by
881 AcpiProtocol->InstallAcpiTable().
886 Process2ndPassCmdAddPointer (
887 IN CONST QEMU_LOADER_ADD_POINTER
*AddPointer
,
888 IN CONST ORDERED_COLLECTION
*Tracker
,
889 IN EFI_ACPI_TABLE_PROTOCOL
*AcpiProtocol
,
890 IN OUT UINTN InstalledKey
[INSTALLED_TABLES_MAX
],
891 IN OUT INT32
*NumInstalled
,
892 IN OUT ORDERED_COLLECTION
*SeenPointers
895 CONST ORDERED_COLLECTION_ENTRY
*TrackerEntry
;
896 CONST ORDERED_COLLECTION_ENTRY
*TrackerEntry2
;
897 ORDERED_COLLECTION_ENTRY
*SeenPointerEntry
;
900 CONST UINT8
*PointerField
;
902 UINTN Blob2Remaining
;
904 CONST EFI_ACPI_1_0_FIRMWARE_ACPI_CONTROL_STRUCTURE
*Facs
;
905 CONST EFI_ACPI_DESCRIPTION_HEADER
*Header
;
908 if ((*NumInstalled
< 0) || (*NumInstalled
> INSTALLED_TABLES_MAX
)) {
909 return EFI_INVALID_PARAMETER
;
912 TrackerEntry
= OrderedCollectionFind (Tracker
, AddPointer
->PointerFile
);
913 TrackerEntry2
= OrderedCollectionFind (Tracker
, AddPointer
->PointeeFile
);
914 Blob
= OrderedCollectionUserStruct (TrackerEntry
);
915 Blob2
= OrderedCollectionUserStruct (TrackerEntry2
);
916 PointerField
= Blob
->Base
+ AddPointer
->PointerOffset
;
918 CopyMem (&PointerValue
, PointerField
, AddPointer
->PointerSize
);
921 // We assert that PointerValue falls inside Blob2's contents. This is ensured
922 // by the Blob2->Size check and later checks in ProcessCmdAddPointer().
924 Blob2Remaining
= (UINTN
)Blob2
->Base
;
925 ASSERT (PointerValue
>= Blob2Remaining
);
926 Blob2Remaining
+= Blob2
->Size
;
927 ASSERT (PointerValue
< Blob2Remaining
);
929 Status
= OrderedCollectionInsert (
931 &SeenPointerEntry
, // for reverting insertion in error case
932 (VOID
*)(UINTN
)PointerValue
934 if (EFI_ERROR (Status
)) {
935 if (Status
== RETURN_ALREADY_STARTED
) {
937 // Already seen this pointer, don't try to process it again.
941 "%a: PointerValue=0x%Lx already processed, skipping.\n",
945 Status
= EFI_SUCCESS
;
951 Blob2Remaining
-= (UINTN
)PointerValue
;
954 "%a: checking for ACPI header in \"%a\" at 0x%Lx "
955 "(remaining: 0x%Lx): ",
957 AddPointer
->PointeeFile
,
959 (UINT64
)Blob2Remaining
965 // To make our job simple, the FACS has a custom header. Sigh.
967 if (sizeof *Facs
<= Blob2Remaining
) {
968 Facs
= (EFI_ACPI_1_0_FIRMWARE_ACPI_CONTROL_STRUCTURE
*)(UINTN
)PointerValue
;
970 if ((Facs
->Length
>= sizeof *Facs
) &&
971 (Facs
->Length
<= Blob2Remaining
) &&
973 EFI_ACPI_1_0_FIRMWARE_ACPI_CONTROL_STRUCTURE_SIGNATURE
))
977 "found \"%-4.4a\" size 0x%x\n",
978 (CONST CHAR8
*)&Facs
->Signature
,
981 TableSize
= Facs
->Length
;
986 // check for the uniform tables
988 if ((TableSize
== 0) && (sizeof *Header
<= Blob2Remaining
)) {
989 Header
= (EFI_ACPI_DESCRIPTION_HEADER
*)(UINTN
)PointerValue
;
991 if ((Header
->Length
>= sizeof *Header
) &&
992 (Header
->Length
<= Blob2Remaining
) &&
993 (CalculateSum8 ((CONST UINT8
*)Header
, Header
->Length
) == 0))
996 // This looks very much like an ACPI table from QEMU:
997 // - Length field consistent with both ACPI and containing blob size
998 // - checksum is correct
1002 "found \"%-4.4a\" size 0x%x\n",
1003 (CONST CHAR8
*)&Header
->Signature
,
1006 TableSize
= Header
->Length
;
1009 // Skip RSDT and XSDT because those are handled by
1010 // EFI_ACPI_TABLE_PROTOCOL automatically.
1011 if ((Header
->Signature
==
1012 EFI_ACPI_1_0_ROOT_SYSTEM_DESCRIPTION_TABLE_SIGNATURE
) ||
1013 (Header
->Signature
==
1014 EFI_ACPI_2_0_EXTENDED_SYSTEM_DESCRIPTION_TABLE_SIGNATURE
))
1021 if (TableSize
== 0) {
1022 DEBUG ((DEBUG_VERBOSE
, "not found; marking fw_cfg blob as opaque\n"));
1023 Blob2
->HostsOnlyTableData
= FALSE
;
1027 if (*NumInstalled
== INSTALLED_TABLES_MAX
) {
1030 "%a: can't install more than %d tables\n",
1032 INSTALLED_TABLES_MAX
1034 Status
= EFI_OUT_OF_RESOURCES
;
1035 goto RollbackSeenPointer
;
1038 Status
= AcpiProtocol
->InstallAcpiTable (
1040 (VOID
*)(UINTN
)PointerValue
,
1042 &InstalledKey
[*NumInstalled
]
1044 if (EFI_ERROR (Status
)) {
1047 "%a: InstallAcpiTable(): %r\n",
1051 goto RollbackSeenPointer
;
1057 RollbackSeenPointer
:
1058 OrderedCollectionDelete (SeenPointers
, SeenPointerEntry
, NULL
);
1063 Download, process, and install ACPI table data from the QEMU loader
1066 @param[in] AcpiProtocol The ACPI table protocol used to install tables.
1068 @retval EFI_UNSUPPORTED Firmware configuration is unavailable, or QEMU
1069 loader command with unsupported parameters
1072 @retval EFI_NOT_FOUND The host doesn't export the required fw_cfg
1075 @retval EFI_OUT_OF_RESOURCES Memory allocation failed, or more than
1076 INSTALLED_TABLES_MAX tables found.
1078 @retval EFI_PROTOCOL_ERROR Found invalid fw_cfg contents.
1080 @return Status codes returned by
1081 AcpiProtocol->InstallAcpiTable().
1086 InstallQemuFwCfgTables (
1087 IN EFI_ACPI_TABLE_PROTOCOL
*AcpiProtocol
1091 FIRMWARE_CONFIG_ITEM FwCfgItem
;
1093 QEMU_LOADER_ENTRY
*LoaderStart
;
1094 CONST QEMU_LOADER_ENTRY
*LoaderEntry
, *LoaderEnd
;
1095 CONST QEMU_LOADER_ENTRY
*WritePointerSubsetEnd
;
1096 ORIGINAL_ATTRIBUTES
*OriginalPciAttributes
;
1097 UINTN OriginalPciAttributesCount
;
1098 ORDERED_COLLECTION
*AllocationsRestrictedTo32Bit
;
1099 S3_CONTEXT
*S3Context
;
1100 ORDERED_COLLECTION
*Tracker
;
1101 UINTN
*InstalledKey
;
1103 ORDERED_COLLECTION_ENTRY
*TrackerEntry
, *TrackerEntry2
;
1104 ORDERED_COLLECTION
*SeenPointers
;
1105 ORDERED_COLLECTION_ENTRY
*SeenPointerEntry
, *SeenPointerEntry2
;
1107 Status
= QemuFwCfgFindFile ("etc/table-loader", &FwCfgItem
, &FwCfgSize
);
1108 if (EFI_ERROR (Status
)) {
1112 if (FwCfgSize
% sizeof *LoaderEntry
!= 0) {
1115 "%a: \"etc/table-loader\" has invalid size 0x%Lx\n",
1119 return EFI_PROTOCOL_ERROR
;
1122 LoaderStart
= AllocatePool (FwCfgSize
);
1123 if (LoaderStart
== NULL
) {
1124 return EFI_OUT_OF_RESOURCES
;
1127 EnablePciDecoding (&OriginalPciAttributes
, &OriginalPciAttributesCount
);
1128 QemuFwCfgSelectItem (FwCfgItem
);
1129 QemuFwCfgReadBytes (FwCfgSize
, LoaderStart
);
1130 RestorePciDecoding (OriginalPciAttributes
, OriginalPciAttributesCount
);
1131 LoaderEnd
= LoaderStart
+ FwCfgSize
/ sizeof *LoaderEntry
;
1133 AllocationsRestrictedTo32Bit
= NULL
;
1134 Status
= CollectAllocationsRestrictedTo32Bit (
1135 &AllocationsRestrictedTo32Bit
,
1139 if (EFI_ERROR (Status
)) {
1144 if (QemuFwCfgS3Enabled ()) {
1146 // Size the allocation pessimistically, assuming that all commands in the
1147 // script are QEMU_LOADER_WRITE_POINTER commands.
1149 Status
= AllocateS3Context (&S3Context
, LoaderEnd
- LoaderStart
);
1150 if (EFI_ERROR (Status
)) {
1151 goto FreeAllocationsRestrictedTo32Bit
;
1155 Tracker
= OrderedCollectionInit (BlobCompare
, BlobKeyCompare
);
1156 if (Tracker
== NULL
) {
1157 Status
= EFI_OUT_OF_RESOURCES
;
1162 // first pass: process the commands
1164 // "WritePointerSubsetEnd" points one past the last successful
1165 // QEMU_LOADER_WRITE_POINTER command. Now when we're about to start the first
1166 // pass, no such command has been encountered yet.
1168 WritePointerSubsetEnd
= LoaderStart
;
1169 for (LoaderEntry
= LoaderStart
; LoaderEntry
< LoaderEnd
; ++LoaderEntry
) {
1170 switch (LoaderEntry
->Type
) {
1171 case QemuLoaderCmdAllocate
:
1172 Status
= ProcessCmdAllocate (
1173 &LoaderEntry
->Command
.Allocate
,
1175 AllocationsRestrictedTo32Bit
1179 case QemuLoaderCmdAddPointer
:
1180 Status
= ProcessCmdAddPointer (
1181 &LoaderEntry
->Command
.AddPointer
,
1186 case QemuLoaderCmdAddChecksum
:
1187 Status
= ProcessCmdAddChecksum (
1188 &LoaderEntry
->Command
.AddChecksum
,
1193 case QemuLoaderCmdWritePointer
:
1194 Status
= ProcessCmdWritePointer (
1195 &LoaderEntry
->Command
.WritePointer
,
1199 if (!EFI_ERROR (Status
)) {
1200 WritePointerSubsetEnd
= LoaderEntry
+ 1;
1208 "%a: unknown loader command: 0x%x\n",
1215 if (EFI_ERROR (Status
)) {
1216 goto RollbackWritePointersAndFreeTracker
;
1220 InstalledKey
= AllocatePool (INSTALLED_TABLES_MAX
* sizeof *InstalledKey
);
1221 if (InstalledKey
== NULL
) {
1222 Status
= EFI_OUT_OF_RESOURCES
;
1223 goto RollbackWritePointersAndFreeTracker
;
1226 SeenPointers
= OrderedCollectionInit (PointerCompare
, PointerCompare
);
1227 if (SeenPointers
== NULL
) {
1228 Status
= EFI_OUT_OF_RESOURCES
;
1233 // second pass: identify and install ACPI tables
1236 for (LoaderEntry
= LoaderStart
; LoaderEntry
< LoaderEnd
; ++LoaderEntry
) {
1237 if (LoaderEntry
->Type
== QemuLoaderCmdAddPointer
) {
1238 Status
= Process2ndPassCmdAddPointer (
1239 &LoaderEntry
->Command
.AddPointer
,
1246 if (EFI_ERROR (Status
)) {
1247 goto UninstallAcpiTables
;
1253 // Translating the condensed QEMU_LOADER_WRITE_POINTER commands to ACPI S3
1254 // Boot Script opcodes has to be the last operation in this function, because
1255 // if it succeeds, it cannot be undone.
1257 if (S3Context
!= NULL
) {
1258 Status
= TransferS3ContextToBootScript (S3Context
);
1259 if (EFI_ERROR (Status
)) {
1260 goto UninstallAcpiTables
;
1264 // Ownership of S3Context has been transferred.
1269 UninstallAcpiTables
:
1270 if (EFI_ERROR (Status
)) {
1272 // roll back partial installation
1274 while (Installed
> 0) {
1276 AcpiProtocol
->UninstallAcpiTable (AcpiProtocol
, InstalledKey
[Installed
]);
1280 // Install a protocol to notify that the ACPI table provided by Qemu is
1283 gBS
->InstallProtocolInterface (
1285 &gQemuAcpiTableNotifyProtocolGuid
,
1286 EFI_NATIVE_INTERFACE
,
1287 &mAcpiNotifyProtocol
1291 for (SeenPointerEntry
= OrderedCollectionMin (SeenPointers
);
1292 SeenPointerEntry
!= NULL
;
1293 SeenPointerEntry
= SeenPointerEntry2
)
1295 SeenPointerEntry2
= OrderedCollectionNext (SeenPointerEntry
);
1296 OrderedCollectionDelete (SeenPointers
, SeenPointerEntry
, NULL
);
1299 OrderedCollectionUninit (SeenPointers
);
1302 FreePool (InstalledKey
);
1304 RollbackWritePointersAndFreeTracker
:
1306 // In case of failure, revoke any allocation addresses that were communicated
1307 // to QEMU previously, before we release all the blobs.
1309 if (EFI_ERROR (Status
)) {
1310 LoaderEntry
= WritePointerSubsetEnd
;
1311 while (LoaderEntry
> LoaderStart
) {
1313 if (LoaderEntry
->Type
== QemuLoaderCmdWritePointer
) {
1314 UndoCmdWritePointer (&LoaderEntry
->Command
.WritePointer
);
1320 // Tear down the tracker infrastructure. Each fw_cfg blob will be left in
1321 // place only if we're exiting with success and the blob hosts data that is
1322 // not directly part of some ACPI table.
1324 for (TrackerEntry
= OrderedCollectionMin (Tracker
); TrackerEntry
!= NULL
;
1325 TrackerEntry
= TrackerEntry2
)
1330 TrackerEntry2
= OrderedCollectionNext (TrackerEntry
);
1331 OrderedCollectionDelete (Tracker
, TrackerEntry
, &UserStruct
);
1334 if (EFI_ERROR (Status
) || Blob
->HostsOnlyTableData
) {
1337 "%a: freeing \"%a\"\n",
1341 gBS
->FreePages ((UINTN
)Blob
->Base
, EFI_SIZE_TO_PAGES (Blob
->Size
));
1347 OrderedCollectionUninit (Tracker
);
1350 if (S3Context
!= NULL
) {
1351 ReleaseS3Context (S3Context
);
1354 FreeAllocationsRestrictedTo32Bit
:
1355 ReleaseAllocationsRestrictedTo32Bit (AllocationsRestrictedTo32Bit
);
1358 FreePool (LoaderStart
);