diff --git a/OvmfPkg/AcpiPlatformDxe/AcpiPlatform.h b/OvmfPkg/AcpiPlatformDxe/AcpiPlatform.h index 08dd7f8f7d..0f035a0d57 100644 --- a/OvmfPkg/AcpiPlatformDxe/AcpiPlatform.h +++ b/OvmfPkg/AcpiPlatformDxe/AcpiPlatform.h @@ -33,6 +33,8 @@ typedef struct { UINT64 PciAttributes; } ORIGINAL_ATTRIBUTES; +typedef struct S3_CONTEXT S3_CONTEXT; + EFI_STATUS EFIAPI InstallAcpiTable ( @@ -91,5 +93,30 @@ RestorePciDecoding ( IN UINTN Count ); +EFI_STATUS +AllocateS3Context ( + OUT S3_CONTEXT **S3Context, + IN UINTN WritePointerCount + ); + +VOID +ReleaseS3Context ( + IN S3_CONTEXT *S3Context + ); + +EFI_STATUS +SaveCondensedWritePointerToS3Context ( + IN OUT S3_CONTEXT *S3Context, + IN UINT16 PointerItem, + IN UINT8 PointerSize, + IN UINT32 PointerOffset, + IN UINT64 PointerValue + ); + +EFI_STATUS +TransferS3ContextToBootScript ( + IN CONST S3_CONTEXT *S3Context + ); + #endif diff --git a/OvmfPkg/AcpiPlatformDxe/AcpiPlatformDxe.inf b/OvmfPkg/AcpiPlatformDxe/AcpiPlatformDxe.inf index 654d3a0390..bb5f14e0fc 100644 --- a/OvmfPkg/AcpiPlatformDxe/AcpiPlatformDxe.inf +++ b/OvmfPkg/AcpiPlatformDxe/AcpiPlatformDxe.inf @@ -33,6 +33,7 @@ Xen.c EntryPoint.c PciDecoding.c + BootScript.c [Packages] MdePkg/MdePkg.dec @@ -59,6 +60,7 @@ [Protocols] gEfiAcpiTableProtocolGuid # PROTOCOL ALWAYS_CONSUMED gEfiPciIoProtocolGuid # PROTOCOL SOMETIMES_CONSUMED + gEfiS3SaveStateProtocolGuid # PROTOCOL SOMETIMES_CONSUMED [Guids] gEfiXenInfoGuid diff --git a/OvmfPkg/AcpiPlatformDxe/BootScript.c b/OvmfPkg/AcpiPlatformDxe/BootScript.c new file mode 100644 index 0000000000..b7a7f270f2 --- /dev/null +++ b/OvmfPkg/AcpiPlatformDxe/BootScript.c @@ -0,0 +1,414 @@ +/** @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 +#include +#include + +#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 +} 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. We use the buffer to compose and to +// replay several fw_cfg select+skip and write operations, using the DMA access +// method. The fw_cfg operations will implement the actions dictated by +// CONDENSED_WRITE_POINTER objects. +// +#pragma pack (1) +typedef struct { + FW_CFG_DMA_ACCESS Access; // filled in from + // CONDENSED_WRITE_POINTER.PointerItem, + // CONDENSED_WRITE_POINTER.PointerSize, + // CONDENSED_WRITE_POINTER.PointerOffset + 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. + + @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; +} + + +/** + 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. + + @retval EFI_OUT_OF_RESOURCES Out of memory. + + @retval EFI_SUCCESS The translation of S3Context to ACPI S3 Boot + Script opcodes has been successful. + + @return Error codes from underlying functions. +**/ +EFI_STATUS +TransferS3ContextToBootScript ( + IN CONST S3_CONTEXT *S3Context + ) +{ + EFI_STATUS Status; + EFI_S3_SAVE_STATE_PROTOCOL *S3SaveState; + SCRATCH_BUFFER *ScratchBuffer; + FW_CFG_DMA_ACCESS *Access; + UINT64 BigEndianAddressOfAccess; + UINT32 ControlPollData; + UINT32 ControlPollMask; + UINTN Index; + + // + // If the following protocol lookup fails, it shall not happen due to an + // unexpected DXE driver dispatch order. + // + // Namely, this function is only invoked on QEMU. Therefore it is only + // reached after Platform BDS signals gRootBridgesConnectedEventGroupGuid + // (see OnRootBridgesConnected() in "EntryPoint.c"). Hence, because + // TransferS3ContextToBootScript() is invoked in BDS, all DXE drivers, + // including S3SaveStateDxe (producing EFI_S3_SAVE_STATE_PROTOCOL), have been + // dispatched by the time we get here. (S3SaveStateDxe is not expected to + // have any stricter-than-TRUE DEPEX -- not a DEPEX that gets unblocked only + // within BDS anyway.) + // + // Reaching this function also depends on QemuFwCfgS3Enabled(). That implies + // S3SaveStateDxe has not exited immediately due to S3 being disabled. Thus + // EFI_S3_SAVE_STATE_PROTOCOL can only be missing for genuinely unforeseeable + // reasons. + // + Status = gBS->LocateProtocol (&gEfiS3SaveStateProtocolGuid, + NULL /* Registration */, (VOID **)&S3SaveState); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "%a: LocateProtocol(): %r\n", __FUNCTION__, Status)); + return Status; + } + + ScratchBuffer = AllocateReservedPool (sizeof *ScratchBuffer); + if (ScratchBuffer == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + // + // Set up helper variables that we'll use identically for all + // CONDENSED_WRITE_POINTER elements. + // + Access = &ScratchBuffer->Access; + BigEndianAddressOfAccess = SwapBytes64 ((UINTN)Access); + ControlPollData = 0; + ControlPollMask = MAX_UINT32; + + // + // For each CONDENSED_WRITE_POINTER, we need six ACPI S3 Boot Script opcodes: + // (1) restore an FW_CFG_DMA_ACCESS object in reserved memory that selects + // the writeable fw_cfg file PointerFile (through PointerItem), and skips + // to PointerOffset in it, + // (2) call QEMU with the FW_CFG_DMA_ACCESS object, + // (3) wait for the select+skip to finish, + // (4) restore a SCRATCH_BUFFER object in reserved memory that writes + // PointerValue (base address of the allocated / downloaded PointeeFile), + // of size PointerSize, into the fw_cfg file selected in (1), at the + // offset sought to in (1), + // (5) call QEMU with the FW_CFG_DMA_ACCESS object, + // (6) wait for the write to finish. + // + // EFI_S3_SAVE_STATE_PROTOCOL does not allow rolling back opcode additions, + // therefore we treat any failure here as fatal. + // + for (Index = 0; Index < S3Context->Used; ++Index) { + CONST CONDENSED_WRITE_POINTER *Condensed; + + Condensed = &S3Context->WritePointers[Index]; + + // + // (1) restore an FW_CFG_DMA_ACCESS object in reserved memory that selects + // the writeable fw_cfg file PointerFile (through PointerItem), and + // skips to PointerOffset in it, + // + Access->Control = SwapBytes32 ((UINT32)Condensed->PointerItem << 16 | + FW_CFG_DMA_CTL_SELECT | FW_CFG_DMA_CTL_SKIP); + Access->Length = SwapBytes32 (Condensed->PointerOffset); + Access->Address = 0; + Status = S3SaveState->Write ( + S3SaveState, // This + EFI_BOOT_SCRIPT_MEM_WRITE_OPCODE, // OpCode + EfiBootScriptWidthUint8, // Width + (UINT64)(UINTN)Access, // Address + sizeof *Access, // Count + Access // Buffer + ); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "%a: Index %Lu opcode 1: %r\n", __FUNCTION__, + (UINT64)Index, Status)); + goto FatalError; + } + + // + // (2) call QEMU with the FW_CFG_DMA_ACCESS object, + // + Status = S3SaveState->Write ( + S3SaveState, // This + EFI_BOOT_SCRIPT_IO_WRITE_OPCODE, // OpCode + EfiBootScriptWidthUint32, // Width + (UINT64)0x514, // Address + (UINTN)2, // Count + &BigEndianAddressOfAccess // Buffer + ); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "%a: Index %Lu opcode 2: %r\n", __FUNCTION__, + (UINT64)Index, Status)); + goto FatalError; + } + + // + // (3) wait for the select+skip to finish, + // + Status = S3SaveState->Write ( + S3SaveState, // This + EFI_BOOT_SCRIPT_MEM_POLL_OPCODE, // OpCode + EfiBootScriptWidthUint32, // Width + (UINT64)(UINTN)&Access->Control, // Address + &ControlPollData, // Data + &ControlPollMask, // DataMask + MAX_UINT64 // Delay + ); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "%a: Index %Lu opcode 3: %r\n", __FUNCTION__, + (UINT64)Index, Status)); + goto FatalError; + } + + // + // (4) restore a SCRATCH_BUFFER object in reserved memory that writes + // PointerValue (base address of the allocated / downloaded + // PointeeFile), of size PointerSize, into the fw_cfg file selected in + // (1), at the offset sought to in (1), + // + Access->Control = SwapBytes32 (FW_CFG_DMA_CTL_WRITE); + Access->Length = SwapBytes32 (Condensed->PointerSize); + Access->Address = SwapBytes64 ((UINTN)&ScratchBuffer->PointerValue); + ScratchBuffer->PointerValue = Condensed->PointerValue; + Status = S3SaveState->Write ( + S3SaveState, // This + EFI_BOOT_SCRIPT_MEM_WRITE_OPCODE, // OpCode + EfiBootScriptWidthUint8, // Width + (UINT64)(UINTN)ScratchBuffer, // Address + sizeof *ScratchBuffer, // Count + ScratchBuffer // Buffer + ); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "%a: Index %Lu opcode 4: %r\n", __FUNCTION__, + (UINT64)Index, Status)); + goto FatalError; + } + + // + // (5) call QEMU with the FW_CFG_DMA_ACCESS object, + // + Status = S3SaveState->Write ( + S3SaveState, // This + EFI_BOOT_SCRIPT_IO_WRITE_OPCODE, // OpCode + EfiBootScriptWidthUint32, // Width + (UINT64)0x514, // Address + (UINTN)2, // Count + &BigEndianAddressOfAccess // Buffer + ); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "%a: Index %Lu opcode 5: %r\n", __FUNCTION__, + (UINT64)Index, Status)); + goto FatalError; + } + + // + // (6) wait for the write to finish. + // + Status = S3SaveState->Write ( + S3SaveState, // This + EFI_BOOT_SCRIPT_MEM_POLL_OPCODE, // OpCode + EfiBootScriptWidthUint32, // Width + (UINT64)(UINTN)&Access->Control, // Address + &ControlPollData, // Data + &ControlPollMask, // DataMask + MAX_UINT64 // Delay + ); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "%a: Index %Lu opcode 6: %r\n", __FUNCTION__, + (UINT64)Index, Status)); + goto FatalError; + } + } + + DEBUG ((DEBUG_VERBOSE, "%a: boot script fragment saved, ScratchBuffer=%p\n", + __FUNCTION__, (VOID *)ScratchBuffer)); + return EFI_SUCCESS; + +FatalError: + ASSERT (FALSE); + CpuDeadLoop (); + return Status; +} diff --git a/OvmfPkg/AcpiPlatformDxe/QemuFwCfgAcpi.c b/OvmfPkg/AcpiPlatformDxe/QemuFwCfgAcpi.c index de827c2df2..eadd690bef 100644 --- a/OvmfPkg/AcpiPlatformDxe/QemuFwCfgAcpi.c +++ b/OvmfPkg/AcpiPlatformDxe/QemuFwCfgAcpi.c @@ -360,6 +360,11 @@ ProcessCmdAddChecksum ( @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 @@ -369,13 +374,21 @@ ProcessCmdAddChecksum ( does not fit in the given pointer size. @retval EFI_SUCCESS The pointer object inside the writeable fw_cfg - file has been written. + 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 CONST ORDERED_COLLECTION *Tracker, + IN OUT S3_CONTEXT *S3Context OPTIONAL ) { RETURN_STATUS Status; @@ -432,6 +445,25 @@ ProcessCmdWritePointer ( 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); @@ -701,6 +733,7 @@ InstallQemuFwCfgTables ( CONST QEMU_LOADER_ENTRY *WritePointerSubsetEnd; ORIGINAL_ATTRIBUTES *OriginalPciAttributes; UINTN OriginalPciAttributesCount; + S3_CONTEXT *S3Context; ORDERED_COLLECTION *Tracker; UINTN *InstalledKey; INT32 Installed; @@ -726,10 +759,22 @@ InstallQemuFwCfgTables ( RestorePciDecoding (OriginalPciAttributes, OriginalPciAttributesCount); LoaderEnd = LoaderStart + FwCfgSize / sizeof *LoaderEntry; + 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 FreeLoader; + } + } + Tracker = OrderedCollectionInit (BlobCompare, BlobKeyCompare); if (Tracker == NULL) { Status = EFI_OUT_OF_RESOURCES; - goto FreeLoader; + goto FreeS3Context; } // @@ -758,7 +803,7 @@ InstallQemuFwCfgTables ( case QemuLoaderCmdWritePointer: Status = ProcessCmdWritePointer (&LoaderEntry->Command.WritePointer, - Tracker); + Tracker, S3Context); if (!EFI_ERROR (Status)) { WritePointerSubsetEnd = LoaderEntry + 1; } @@ -790,11 +835,21 @@ InstallQemuFwCfgTables ( Status = Process2ndPassCmdAddPointer (&LoaderEntry->Command.AddPointer, Tracker, AcpiProtocol, InstalledKey, &Installed); if (EFI_ERROR (Status)) { - break; + 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); + } + +UninstallAcpiTables: if (EFI_ERROR (Status)) { // // roll back partial installation @@ -847,6 +902,11 @@ RollbackWritePointersAndFreeTracker: } OrderedCollectionUninit (Tracker); +FreeS3Context: + if (S3Context != NULL) { + ReleaseS3Context (S3Context); + } + FreeLoader: FreePool (LoaderStart); diff --git a/OvmfPkg/AcpiPlatformDxe/QemuFwCfgAcpiPlatformDxe.inf b/OvmfPkg/AcpiPlatformDxe/QemuFwCfgAcpiPlatformDxe.inf index d99f2d5a95..e550ff5a47 100644 --- a/OvmfPkg/AcpiPlatformDxe/QemuFwCfgAcpiPlatformDxe.inf +++ b/OvmfPkg/AcpiPlatformDxe/QemuFwCfgAcpiPlatformDxe.inf @@ -31,6 +31,7 @@ QemuFwCfgAcpi.c EntryPoint.c PciDecoding.c + BootScript.c [Packages] MdePkg/MdePkg.dec @@ -49,6 +50,7 @@ [Protocols] gEfiAcpiTableProtocolGuid # PROTOCOL ALWAYS_CONSUMED gEfiPciIoProtocolGuid # PROTOCOL SOMETIMES_CONSUMED + gEfiS3SaveStateProtocolGuid # PROTOCOL SOMETIMES_CONSUMED [Guids] gRootBridgesConnectedEventGroupGuid