mirror of
https://github.com/CloverHackyColor/CloverBootloader.git
synced 2024-12-01 12:53:27 +01:00
1406 lines
44 KiB
C
1406 lines
44 KiB
C
/** @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;
|
|
}
|