2017-02-09 17:32:40 +01:00
|
|
|
/** @file
|
|
|
|
Append an ACPI S3 Boot Script fragment from the QEMU_LOADER_WRITE_POINTER
|
|
|
|
commands of QEMU's fully processed table linker/loader script.
|
|
|
|
|
|
|
|
Copyright (C) 2017, Red Hat, Inc.
|
|
|
|
|
2019-04-04 01:06:33 +02:00
|
|
|
SPDX-License-Identifier: BSD-2-Clause-Patent
|
2017-02-09 17:32:40 +01:00
|
|
|
**/
|
|
|
|
|
|
|
|
#include <Library/MemoryAllocationLib.h>
|
|
|
|
#include <Library/QemuFwCfgLib.h>
|
2017-02-23 00:22:17 +01:00
|
|
|
#include <Library/QemuFwCfgS3Lib.h>
|
2017-02-09 17:32:40 +01:00
|
|
|
|
|
|
|
#include "AcpiPlatform.h"
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
// Condensed structure for capturing the fw_cfg operations -- select, skip,
|
|
|
|
// write -- inherent in executing a QEMU_LOADER_WRITE_POINTER command.
|
|
|
|
//
|
|
|
|
typedef struct {
|
|
|
|
UINT16 PointerItem; // resolved from QEMU_LOADER_WRITE_POINTER.PointerFile
|
|
|
|
UINT8 PointerSize; // copied as-is from QEMU_LOADER_WRITE_POINTER
|
|
|
|
UINT32 PointerOffset; // copied as-is from QEMU_LOADER_WRITE_POINTER
|
|
|
|
UINT64 PointerValue; // resolved from QEMU_LOADER_WRITE_POINTER.PointeeFile
|
2017-02-21 13:56:48 +01:00
|
|
|
// and QEMU_LOADER_WRITE_POINTER.PointeeOffset
|
2017-02-09 17:32:40 +01:00
|
|
|
} CONDENSED_WRITE_POINTER;
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
// Context structure to accumulate CONDENSED_WRITE_POINTER objects from
|
|
|
|
// QEMU_LOADER_WRITE_POINTER commands.
|
|
|
|
//
|
|
|
|
// Any pointers in this structure own the pointed-to objects; that is, when the
|
|
|
|
// context structure is released, all pointed-to objects must be released too.
|
|
|
|
//
|
|
|
|
struct S3_CONTEXT {
|
|
|
|
CONDENSED_WRITE_POINTER *WritePointers; // one array element per processed
|
|
|
|
// QEMU_LOADER_WRITE_POINTER
|
|
|
|
// command
|
|
|
|
UINTN Allocated; // number of elements allocated for
|
|
|
|
// WritePointers
|
|
|
|
UINTN Used; // number of elements populated in
|
|
|
|
// WritePointers
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
// Scratch buffer, allocated in EfiReservedMemoryType type memory, for the ACPI
|
2017-02-23 00:22:17 +01:00
|
|
|
// S3 Boot Script opcodes to work on.
|
2017-02-09 17:32:40 +01:00
|
|
|
//
|
|
|
|
#pragma pack (1)
|
2017-02-23 00:22:17 +01:00
|
|
|
typedef union {
|
|
|
|
UINT64 PointerValue; // filled in from CONDENSED_WRITE_POINTER.PointerValue
|
2017-02-09 17:32:40 +01:00
|
|
|
} SCRATCH_BUFFER;
|
|
|
|
#pragma pack ()
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
Allocate an S3_CONTEXT object.
|
|
|
|
|
|
|
|
@param[out] S3Context The allocated S3_CONTEXT object is returned
|
|
|
|
through this parameter.
|
|
|
|
|
|
|
|
@param[in] WritePointerCount Number of CONDENSED_WRITE_POINTER elements to
|
|
|
|
allocate room for. WritePointerCount must be
|
|
|
|
positive.
|
|
|
|
|
|
|
|
@retval EFI_SUCCESS Allocation successful.
|
|
|
|
|
|
|
|
@retval EFI_OUT_OF_RESOURCES Out of memory.
|
|
|
|
|
|
|
|
@retval EFI_INVALID_PARAMETER WritePointerCount is zero.
|
|
|
|
**/
|
|
|
|
EFI_STATUS
|
|
|
|
AllocateS3Context (
|
|
|
|
OUT S3_CONTEXT **S3Context,
|
|
|
|
IN UINTN WritePointerCount
|
|
|
|
)
|
|
|
|
{
|
|
|
|
EFI_STATUS Status;
|
|
|
|
S3_CONTEXT *Context;
|
|
|
|
|
|
|
|
if (WritePointerCount == 0) {
|
|
|
|
return EFI_INVALID_PARAMETER;
|
|
|
|
}
|
|
|
|
|
|
|
|
Context = AllocateZeroPool (sizeof *Context);
|
|
|
|
if (Context == NULL) {
|
|
|
|
return EFI_OUT_OF_RESOURCES;
|
|
|
|
}
|
|
|
|
|
|
|
|
Context->WritePointers = AllocatePool (WritePointerCount *
|
|
|
|
sizeof *Context->WritePointers);
|
|
|
|
if (Context->WritePointers == NULL) {
|
|
|
|
Status = EFI_OUT_OF_RESOURCES;
|
|
|
|
goto FreeContext;
|
|
|
|
}
|
|
|
|
|
|
|
|
Context->Allocated = WritePointerCount;
|
|
|
|
*S3Context = Context;
|
|
|
|
return EFI_SUCCESS;
|
|
|
|
|
|
|
|
FreeContext:
|
|
|
|
FreePool (Context);
|
|
|
|
|
|
|
|
return Status;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
Release an S3_CONTEXT object.
|
|
|
|
|
|
|
|
@param[in] S3Context The object to release.
|
|
|
|
**/
|
|
|
|
VOID
|
|
|
|
ReleaseS3Context (
|
|
|
|
IN S3_CONTEXT *S3Context
|
|
|
|
)
|
|
|
|
{
|
|
|
|
FreePool (S3Context->WritePointers);
|
|
|
|
FreePool (S3Context);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
Save the information necessary to replicate a QEMU_LOADER_WRITE_POINTER
|
|
|
|
command during S3 resume, in condensed format.
|
|
|
|
|
|
|
|
This function is to be called from ProcessCmdWritePointer(), after all the
|
|
|
|
sanity checks have passed, and before the fw_cfg operations are performed.
|
|
|
|
|
|
|
|
@param[in,out] S3Context The S3_CONTEXT object into which the caller wants
|
|
|
|
to save the information that was derived from
|
|
|
|
QEMU_LOADER_WRITE_POINTER.
|
|
|
|
|
|
|
|
@param[in] PointerItem The FIRMWARE_CONFIG_ITEM that
|
|
|
|
QEMU_LOADER_WRITE_POINTER.PointerFile was resolved
|
|
|
|
to, expressed as a UINT16 value.
|
|
|
|
|
|
|
|
@param[in] PointerSize Copied directly from
|
|
|
|
QEMU_LOADER_WRITE_POINTER.PointerSize.
|
|
|
|
|
|
|
|
@param[in] PointerOffset Copied directly from
|
|
|
|
QEMU_LOADER_WRITE_POINTER.PointerOffset.
|
|
|
|
|
|
|
|
@param[in] PointerValue The base address of the allocated / downloaded
|
|
|
|
fw_cfg blob that is identified by
|
2017-02-21 13:56:48 +01:00
|
|
|
QEMU_LOADER_WRITE_POINTER.PointeeFile, plus
|
|
|
|
QEMU_LOADER_WRITE_POINTER.PointeeOffset.
|
2017-02-09 17:32:40 +01:00
|
|
|
|
|
|
|
@retval EFI_SUCCESS The information derived from
|
|
|
|
QEMU_LOADER_WRITE_POINTER has been successfully
|
|
|
|
absorbed into S3Context.
|
|
|
|
|
|
|
|
@retval EFI_OUT_OF_RESOURCES No room available in S3Context.
|
|
|
|
**/
|
|
|
|
EFI_STATUS
|
|
|
|
SaveCondensedWritePointerToS3Context (
|
|
|
|
IN OUT S3_CONTEXT *S3Context,
|
|
|
|
IN UINT16 PointerItem,
|
|
|
|
IN UINT8 PointerSize,
|
|
|
|
IN UINT32 PointerOffset,
|
|
|
|
IN UINT64 PointerValue
|
|
|
|
)
|
|
|
|
{
|
|
|
|
CONDENSED_WRITE_POINTER *Condensed;
|
|
|
|
|
|
|
|
if (S3Context->Used == S3Context->Allocated) {
|
|
|
|
return EFI_OUT_OF_RESOURCES;
|
|
|
|
}
|
|
|
|
Condensed = S3Context->WritePointers + S3Context->Used;
|
|
|
|
Condensed->PointerItem = PointerItem;
|
|
|
|
Condensed->PointerSize = PointerSize;
|
|
|
|
Condensed->PointerOffset = PointerOffset;
|
|
|
|
Condensed->PointerValue = PointerValue;
|
|
|
|
DEBUG ((DEBUG_VERBOSE, "%a: 0x%04x/[0x%08x+%d] := 0x%Lx (%Lu)\n",
|
|
|
|
__FUNCTION__, PointerItem, PointerOffset, PointerSize, PointerValue,
|
|
|
|
(UINT64)S3Context->Used));
|
|
|
|
++S3Context->Used;
|
|
|
|
return EFI_SUCCESS;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2017-02-23 00:22:17 +01:00
|
|
|
FW_CFG_BOOT_SCRIPT_CALLBACK_FUNCTION provided to QemuFwCfgS3Lib.
|
2017-02-09 17:32:40 +01:00
|
|
|
**/
|
2017-02-23 00:22:17 +01:00
|
|
|
STATIC
|
|
|
|
VOID
|
|
|
|
EFIAPI
|
|
|
|
AppendFwCfgBootScript (
|
|
|
|
IN OUT VOID *Context, OPTIONAL
|
|
|
|
IN OUT VOID *ExternalScratchBuffer
|
2017-02-09 17:32:40 +01:00
|
|
|
)
|
|
|
|
{
|
2017-02-23 00:22:17 +01:00
|
|
|
S3_CONTEXT *S3Context;
|
|
|
|
SCRATCH_BUFFER *ScratchBuffer;
|
|
|
|
UINTN Index;
|
2017-02-09 17:32:40 +01:00
|
|
|
|
2017-02-23 00:22:17 +01:00
|
|
|
S3Context = Context;
|
|
|
|
ScratchBuffer = ExternalScratchBuffer;
|
2017-02-09 17:32:40 +01:00
|
|
|
|
|
|
|
for (Index = 0; Index < S3Context->Used; ++Index) {
|
|
|
|
CONST CONDENSED_WRITE_POINTER *Condensed;
|
2017-02-23 00:22:17 +01:00
|
|
|
RETURN_STATUS Status;
|
2017-02-09 17:32:40 +01:00
|
|
|
|
|
|
|
Condensed = &S3Context->WritePointers[Index];
|
|
|
|
|
2017-02-23 00:22:17 +01:00
|
|
|
Status = QemuFwCfgS3ScriptSkipBytes (Condensed->PointerItem,
|
|
|
|
Condensed->PointerOffset);
|
|
|
|
if (RETURN_ERROR (Status)) {
|
2017-02-09 17:32:40 +01:00
|
|
|
goto FatalError;
|
|
|
|
}
|
|
|
|
|
|
|
|
ScratchBuffer->PointerValue = Condensed->PointerValue;
|
2017-02-23 00:22:17 +01:00
|
|
|
Status = QemuFwCfgS3ScriptWriteBytes (-1, Condensed->PointerSize);
|
|
|
|
if (RETURN_ERROR (Status)) {
|
2017-02-09 17:32:40 +01:00
|
|
|
goto FatalError;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-23 00:22:17 +01:00
|
|
|
DEBUG ((DEBUG_VERBOSE, "%a: boot script fragment saved\n", __FUNCTION__));
|
|
|
|
|
|
|
|
ReleaseS3Context (S3Context);
|
|
|
|
return;
|
2017-02-09 17:32:40 +01:00
|
|
|
|
|
|
|
FatalError:
|
|
|
|
ASSERT (FALSE);
|
|
|
|
CpuDeadLoop ();
|
2017-02-23 00:22:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
Translate and append the information from an S3_CONTEXT object to the ACPI S3
|
|
|
|
Boot Script.
|
|
|
|
|
|
|
|
The effects of a successful call to this function cannot be undone.
|
|
|
|
|
|
|
|
@param[in] S3Context The S3_CONTEXT object to translate to ACPI S3 Boot
|
|
|
|
Script opcodes. If the function returns successfully,
|
|
|
|
the caller must set the S3Context pointer -- originally
|
|
|
|
returned by AllocateS3Context() -- immediately to NULL,
|
|
|
|
because the ownership of S3Context has been transfered.
|
|
|
|
|
|
|
|
@retval EFI_SUCCESS The translation of S3Context to ACPI S3 Boot Script
|
OvmfPkg/AcpiPlatformDxe: short-circuit the transfer of an empty S3_CONTEXT
In commit 805762252733 ("OvmfPkg/AcpiPlatformDxe: save fw_cfg boot script
with QemuFwCfgS3Lib", 2017-02-23), we replaced the explicit S3 boot script
manipulation in TransferS3ContextToBootScript() with a call to
QemuFwCfgS3CallWhenBootScriptReady(). (Passing AppendFwCfgBootScript() as
callback.)
QemuFwCfgS3CallWhenBootScriptReady() checks for fw_cfg DMA up-front, and
bails with RETURN_NOT_FOUND if fw_cfg DMA is missing.
(This is justified as the goal of QemuFwCfgS3Lib is to "enable[] driver
modules [...] to produce fw_cfg DMA operations that are to be replayed at
S3 resume time".)
In turn, if QemuFwCfgS3CallWhenBootScriptReady() fails, then
OvmfPkg/AcpiPlatformDxe rolls back any earlier linker/loader script
processing, and falls back to the built-in ACPI tables.
(This is also justified because failure to save WRITE_POINTER commands for
replaying at S3 resume implies failure to process the linker/loader script
comprehensively.)
Calling QemuFwCfgS3CallWhenBootScriptReady() from
TransferS3ContextToBootScript() *unconditionally* is wrong however. For
the case when the linker/loader script contains no WRITE_POINTER commands,
the call perpetuated an earlier side effect, and introduced another one:
(1) On machine types that provide fw_cfg DMA (i.e., 2.5+),
QemuFwCfgS3CallWhenBootScriptReady() would succeed, and allocate
workspace for the boot script opcodes in reserved memory. However, no
opcodes would actually be produced in the AppendFwCfgBootScript()
callback, due to lack of any WRITE_POINTER commands.
This waste of reserved memory had been introduced in earlier commit
df73df138d9d ("OvmfPkg/AcpiPlatformDxe: replay
QEMU_LOADER_WRITE_POINTER commands at S3", 2017-02-09).
(2) On machine types that lack fw_cfg DMA (i.e., 2.4 and earlier),
TransferS3ContextToBootScript() would now fail the linker/loader
script for no reason.
(Note that QEMU itself prevents adding devices that depend on
WRITE_POINTER if the machine type lacks fw_cfg DMA:
$ qemu-system-x86_64 -M pc-q35-2.4 -device vmgenid
qemu-system-x86_64: -device vmgenid: vmgenid requires DMA write
support in fw_cfg, which this machine type does not provide)
Short-circuit an empty S3_CONTEXT in TransferS3ContextToBootScript() by
dropping S3_CONTEXT on the floor. This is compatible with the current
contract of the function as it constitutes a transfer of ownership.
Regression (2) was found and reported by Dhiru Kholia as an OSX guest boot
failure on the "pc-q35-2.4" machine type:
http://mid.mail-archive.com/CANO7a6x6EaWNZ8y=MvLU=w_LjRLXserO3NmsgHvaYE0aUCCWzg@mail.gmail.com
Dhiru bisected the issue to commit 805762252733.
Cc: Dhiru Kholia <dhiru.kholia@gmail.com>
Cc: Jordan Justen <jordan.l.justen@intel.com>
Fixes: df73df138d9d53f7f7570f4fe97a6cde941a2656
Fixes: 805762252733bb67bc5157f0137c64e010724c77
Reported-by: Dhiru Kholia <dhiru.kholia@gmail.com>
Contributed-under: TianoCore Contribution Agreement 1.1
Signed-off-by: Laszlo Ersek <lersek@redhat.com>
Tested-by: Dhiru Kholia <dhiru.kholia@gmail.com>
Reviewed-by: Jordan Justen <jordan.l.justen@intel.com>
2017-08-06 11:20:02 +02:00
|
|
|
opcodes has been successfully executed or queued. (This
|
|
|
|
includes the case when S3Context was empty on input and
|
|
|
|
no ACPI S3 Boot Script opcodes have been necessary to
|
|
|
|
produce.)
|
2017-02-23 00:22:17 +01:00
|
|
|
|
|
|
|
@return Error codes from underlying functions.
|
|
|
|
**/
|
|
|
|
EFI_STATUS
|
|
|
|
TransferS3ContextToBootScript (
|
|
|
|
IN S3_CONTEXT *S3Context
|
|
|
|
)
|
|
|
|
{
|
|
|
|
RETURN_STATUS Status;
|
|
|
|
|
OvmfPkg/AcpiPlatformDxe: short-circuit the transfer of an empty S3_CONTEXT
In commit 805762252733 ("OvmfPkg/AcpiPlatformDxe: save fw_cfg boot script
with QemuFwCfgS3Lib", 2017-02-23), we replaced the explicit S3 boot script
manipulation in TransferS3ContextToBootScript() with a call to
QemuFwCfgS3CallWhenBootScriptReady(). (Passing AppendFwCfgBootScript() as
callback.)
QemuFwCfgS3CallWhenBootScriptReady() checks for fw_cfg DMA up-front, and
bails with RETURN_NOT_FOUND if fw_cfg DMA is missing.
(This is justified as the goal of QemuFwCfgS3Lib is to "enable[] driver
modules [...] to produce fw_cfg DMA operations that are to be replayed at
S3 resume time".)
In turn, if QemuFwCfgS3CallWhenBootScriptReady() fails, then
OvmfPkg/AcpiPlatformDxe rolls back any earlier linker/loader script
processing, and falls back to the built-in ACPI tables.
(This is also justified because failure to save WRITE_POINTER commands for
replaying at S3 resume implies failure to process the linker/loader script
comprehensively.)
Calling QemuFwCfgS3CallWhenBootScriptReady() from
TransferS3ContextToBootScript() *unconditionally* is wrong however. For
the case when the linker/loader script contains no WRITE_POINTER commands,
the call perpetuated an earlier side effect, and introduced another one:
(1) On machine types that provide fw_cfg DMA (i.e., 2.5+),
QemuFwCfgS3CallWhenBootScriptReady() would succeed, and allocate
workspace for the boot script opcodes in reserved memory. However, no
opcodes would actually be produced in the AppendFwCfgBootScript()
callback, due to lack of any WRITE_POINTER commands.
This waste of reserved memory had been introduced in earlier commit
df73df138d9d ("OvmfPkg/AcpiPlatformDxe: replay
QEMU_LOADER_WRITE_POINTER commands at S3", 2017-02-09).
(2) On machine types that lack fw_cfg DMA (i.e., 2.4 and earlier),
TransferS3ContextToBootScript() would now fail the linker/loader
script for no reason.
(Note that QEMU itself prevents adding devices that depend on
WRITE_POINTER if the machine type lacks fw_cfg DMA:
$ qemu-system-x86_64 -M pc-q35-2.4 -device vmgenid
qemu-system-x86_64: -device vmgenid: vmgenid requires DMA write
support in fw_cfg, which this machine type does not provide)
Short-circuit an empty S3_CONTEXT in TransferS3ContextToBootScript() by
dropping S3_CONTEXT on the floor. This is compatible with the current
contract of the function as it constitutes a transfer of ownership.
Regression (2) was found and reported by Dhiru Kholia as an OSX guest boot
failure on the "pc-q35-2.4" machine type:
http://mid.mail-archive.com/CANO7a6x6EaWNZ8y=MvLU=w_LjRLXserO3NmsgHvaYE0aUCCWzg@mail.gmail.com
Dhiru bisected the issue to commit 805762252733.
Cc: Dhiru Kholia <dhiru.kholia@gmail.com>
Cc: Jordan Justen <jordan.l.justen@intel.com>
Fixes: df73df138d9d53f7f7570f4fe97a6cde941a2656
Fixes: 805762252733bb67bc5157f0137c64e010724c77
Reported-by: Dhiru Kholia <dhiru.kholia@gmail.com>
Contributed-under: TianoCore Contribution Agreement 1.1
Signed-off-by: Laszlo Ersek <lersek@redhat.com>
Tested-by: Dhiru Kholia <dhiru.kholia@gmail.com>
Reviewed-by: Jordan Justen <jordan.l.justen@intel.com>
2017-08-06 11:20:02 +02:00
|
|
|
if (S3Context->Used == 0) {
|
|
|
|
ReleaseS3Context (S3Context);
|
|
|
|
return EFI_SUCCESS;
|
|
|
|
}
|
|
|
|
|
2017-02-23 00:22:17 +01:00
|
|
|
Status = QemuFwCfgS3CallWhenBootScriptReady (AppendFwCfgBootScript,
|
|
|
|
S3Context, sizeof (SCRATCH_BUFFER));
|
|
|
|
return (EFI_STATUS)Status;
|
2017-02-09 17:32:40 +01:00
|
|
|
}
|