In commit8057622527("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 commitdf73df138d("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 commit8057622527. Cc: Dhiru Kholia <dhiru.kholia@gmail.com> Cc: Jordan Justen <jordan.l.justen@intel.com> Fixes:df73df138dFixes:8057622527Reported-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>
		
			
				
	
	
		
			275 lines
		
	
	
		
			8.6 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			275 lines
		
	
	
		
			8.6 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /** @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.
 | |
| 
 | |
|   This program and the accompanying materials are licensed and made available
 | |
|   under the terms and conditions of the BSD License which accompanies this
 | |
|   distribution.  The full text of the license may be found at
 | |
|   http://opensource.org/licenses/bsd-license.php
 | |
| 
 | |
|   THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, WITHOUT
 | |
|   WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
 | |
| **/
 | |
| 
 | |
| #include <Library/MemoryAllocationLib.h>
 | |
| #include <Library/QemuFwCfgLib.h>
 | |
| #include <Library/QemuFwCfgS3Lib.h>
 | |
| 
 | |
| #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
 | |
|                         //   and QEMU_LOADER_WRITE_POINTER.PointeeOffset
 | |
| } 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
 | |
| // S3 Boot Script opcodes to work on.
 | |
| //
 | |
| #pragma pack (1)
 | |
| typedef union {
 | |
|   UINT64 PointerValue; // filled in from CONDENSED_WRITE_POINTER.PointerValue
 | |
| } 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
 | |
|                             QEMU_LOADER_WRITE_POINTER.PointeeFile, plus
 | |
|                             QEMU_LOADER_WRITE_POINTER.PointeeOffset.
 | |
| 
 | |
|   @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;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   FW_CFG_BOOT_SCRIPT_CALLBACK_FUNCTION provided to QemuFwCfgS3Lib.
 | |
| **/
 | |
| STATIC
 | |
| VOID
 | |
| EFIAPI
 | |
| AppendFwCfgBootScript (
 | |
|   IN OUT VOID *Context,              OPTIONAL
 | |
|   IN OUT VOID *ExternalScratchBuffer
 | |
|   )
 | |
| {
 | |
|   S3_CONTEXT     *S3Context;
 | |
|   SCRATCH_BUFFER *ScratchBuffer;
 | |
|   UINTN          Index;
 | |
| 
 | |
|   S3Context = Context;
 | |
|   ScratchBuffer = ExternalScratchBuffer;
 | |
| 
 | |
|   for (Index = 0; Index < S3Context->Used; ++Index) {
 | |
|     CONST CONDENSED_WRITE_POINTER *Condensed;
 | |
|     RETURN_STATUS                 Status;
 | |
| 
 | |
|     Condensed = &S3Context->WritePointers[Index];
 | |
| 
 | |
|     Status = QemuFwCfgS3ScriptSkipBytes (Condensed->PointerItem,
 | |
|                Condensed->PointerOffset);
 | |
|     if (RETURN_ERROR (Status)) {
 | |
|       goto FatalError;
 | |
|     }
 | |
| 
 | |
|     ScratchBuffer->PointerValue = Condensed->PointerValue;
 | |
|     Status = QemuFwCfgS3ScriptWriteBytes (-1, Condensed->PointerSize);
 | |
|     if (RETURN_ERROR (Status)) {
 | |
|       goto FatalError;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   DEBUG ((DEBUG_VERBOSE, "%a: boot script fragment saved\n", __FUNCTION__));
 | |
| 
 | |
|   ReleaseS3Context (S3Context);
 | |
|   return;
 | |
| 
 | |
| FatalError:
 | |
|   ASSERT (FALSE);
 | |
|   CpuDeadLoop ();
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   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
 | |
|                       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.)
 | |
| 
 | |
|   @return             Error codes from underlying functions.
 | |
| **/
 | |
| EFI_STATUS
 | |
| TransferS3ContextToBootScript (
 | |
|   IN S3_CONTEXT *S3Context
 | |
|   )
 | |
| {
 | |
|   RETURN_STATUS Status;
 | |
| 
 | |
|   if (S3Context->Used == 0) {
 | |
|     ReleaseS3Context (S3Context);
 | |
|     return EFI_SUCCESS;
 | |
|   }
 | |
| 
 | |
|   Status = QemuFwCfgS3CallWhenBootScriptReady (AppendFwCfgBootScript,
 | |
|              S3Context, sizeof (SCRATCH_BUFFER));
 | |
|   return (EFI_STATUS)Status;
 | |
| }
 |