| /** @file | |
| Full functionality QemuFwCfgS3Lib instance, for DXE phase modules. | |
| Copyright (C) 2017, Red Hat, Inc. | |
| SPDX-License-Identifier: BSD-2-Clause-Patent | |
| **/ | |
| #include <Library/BaseLib.h> | |
| #include <Library/DebugLib.h> | |
| #include <Library/MemoryAllocationLib.h> | |
| #include <Library/QemuFwCfgLib.h> | |
| #include <Library/QemuFwCfgS3Lib.h> | |
| #include <Library/UefiBootServicesTableLib.h> | |
| #include <Protocol/S3SaveState.h> | |
| // | |
| // Event to signal when the S3SaveState protocol interface is installed. | |
| // | |
| STATIC EFI_EVENT mS3SaveStateInstalledEvent; | |
| // | |
| // Reference to the S3SaveState protocol interface, after it is installed. | |
| // | |
| STATIC EFI_S3_SAVE_STATE_PROTOCOL *mS3SaveState; | |
| // | |
| // The control structure is allocated in reserved memory, aligned at 8 bytes. | |
| // The client-requested ScratchBuffer will be allocated adjacently, also | |
| // aligned at 8 bytes. | |
| // | |
| #define RESERVED_MEM_ALIGNMENT 8 | |
| STATIC FW_CFG_DMA_ACCESS *mDmaAccess; | |
| STATIC VOID *mScratchBuffer; | |
| STATIC UINTN mScratchBufferSize; | |
| // | |
| // Callback provided by the client, for appending ACPI S3 Boot Script opcodes. | |
| // To be called from S3SaveStateInstalledNotify(). | |
| // | |
| STATIC FW_CFG_BOOT_SCRIPT_CALLBACK_FUNCTION *mCallback; | |
| /** | |
| Event notification function for mS3SaveStateInstalledEvent. | |
| **/ | |
| STATIC | |
| VOID | |
| EFIAPI | |
| S3SaveStateInstalledNotify ( | |
| IN EFI_EVENT Event, | |
| IN VOID *Context | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| ASSERT (Event == mS3SaveStateInstalledEvent); | |
| Status = gBS->LocateProtocol ( | |
| &gEfiS3SaveStateProtocolGuid, | |
| NULL /* Registration */, | |
| (VOID **)&mS3SaveState | |
| ); | |
| if (EFI_ERROR (Status)) { | |
| return; | |
| } | |
| ASSERT (mCallback != NULL); | |
| DEBUG (( | |
| DEBUG_INFO, | |
| "%a: %a: DmaAccess@0x%Lx ScratchBuffer@[0x%Lx+0x%Lx]\n", | |
| gEfiCallerBaseName, | |
| __func__, | |
| (UINT64)(UINTN)mDmaAccess, | |
| (UINT64)(UINTN)mScratchBuffer, | |
| (UINT64)mScratchBufferSize | |
| )); | |
| mCallback (Context, mScratchBuffer); | |
| gBS->CloseEvent (mS3SaveStateInstalledEvent); | |
| mS3SaveStateInstalledEvent = NULL; | |
| } | |
| /** | |
| Install the client module's FW_CFG_BOOT_SCRIPT_CALLBACK_FUNCTION callback for | |
| when the production of ACPI S3 Boot Script opcodes becomes possible. | |
| Take ownership of the client-provided Context, and pass it to the callback | |
| function, when the latter is invoked. | |
| Allocate scratch space for those ACPI S3 Boot Script opcodes to work upon | |
| that the client will produce in the callback function. | |
| @param[in] Callback FW_CFG_BOOT_SCRIPT_CALLBACK_FUNCTION to invoke | |
| when the production of ACPI S3 Boot Script | |
| opcodes becomes possible. Callback() may be | |
| called immediately from | |
| QemuFwCfgS3CallWhenBootScriptReady(). | |
| @param[in,out] Context Client-provided data structure for the | |
| Callback() callback function to consume. | |
| If Context points to dynamically allocated | |
| memory, then Callback() must release it. | |
| If Context points to dynamically allocated | |
| memory, and | |
| QemuFwCfgS3CallWhenBootScriptReady() returns | |
| successfully, then the caller of | |
| QemuFwCfgS3CallWhenBootScriptReady() must | |
| neither dereference nor even evaluate Context | |
| any longer, as ownership of the referenced area | |
| has been transferred to Callback(). | |
| @param[in] ScratchBufferSize The size of the scratch buffer that will hold, | |
| in reserved memory, all client data read, | |
| written, and checked by the ACPI S3 Boot Script | |
| opcodes produced by Callback(). | |
| @retval RETURN_UNSUPPORTED The library instance does not support this | |
| function. | |
| @retval RETURN_NOT_FOUND The fw_cfg DMA interface to QEMU is | |
| unavailable. | |
| @retval RETURN_BAD_BUFFER_SIZE ScratchBufferSize is too large. | |
| @retval RETURN_OUT_OF_RESOURCES Memory allocation failed. | |
| @retval RETURN_SUCCESS Callback() has been installed, and the | |
| ownership of Context has been transferred. | |
| Reserved memory has been allocated for the | |
| scratch buffer. | |
| A successful invocation of | |
| QemuFwCfgS3CallWhenBootScriptReady() cannot | |
| be rolled back. | |
| @return Error codes from underlying functions. | |
| **/ | |
| RETURN_STATUS | |
| EFIAPI | |
| QemuFwCfgS3CallWhenBootScriptReady ( | |
| IN FW_CFG_BOOT_SCRIPT_CALLBACK_FUNCTION *Callback, | |
| IN OUT VOID *Context OPTIONAL, | |
| IN UINTN ScratchBufferSize | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| VOID *Registration; | |
| // | |
| // Basic fw_cfg is certainly available, as we can only be here after a | |
| // successful call to QemuFwCfgS3Enabled(). Check fw_cfg DMA availability. | |
| // | |
| ASSERT (QemuFwCfgIsAvailable ()); | |
| QemuFwCfgSelectItem (QemuFwCfgItemInterfaceVersion); | |
| if ((QemuFwCfgRead32 () & FW_CFG_F_DMA) == 0) { | |
| DEBUG (( | |
| DEBUG_ERROR, | |
| "%a: %a: fw_cfg DMA unavailable\n", | |
| gEfiCallerBaseName, | |
| __func__ | |
| )); | |
| return RETURN_NOT_FOUND; | |
| } | |
| // | |
| // Allocate a reserved buffer for the DMA access control structure and the | |
| // client data together. | |
| // | |
| if (ScratchBufferSize > | |
| MAX_UINT32 - (RESERVED_MEM_ALIGNMENT - 1) - sizeof *mDmaAccess) | |
| { | |
| DEBUG (( | |
| DEBUG_ERROR, | |
| "%a: %a: ScratchBufferSize too big: %Lu\n", | |
| gEfiCallerBaseName, | |
| __func__, | |
| (UINT64)ScratchBufferSize | |
| )); | |
| return RETURN_BAD_BUFFER_SIZE; | |
| } | |
| mDmaAccess = AllocateReservedPool ( | |
| (RESERVED_MEM_ALIGNMENT - 1) + | |
| sizeof *mDmaAccess + ScratchBufferSize | |
| ); | |
| if (mDmaAccess == NULL) { | |
| DEBUG (( | |
| DEBUG_ERROR, | |
| "%a: %a: AllocateReservedPool(): out of resources\n", | |
| gEfiCallerBaseName, | |
| __func__ | |
| )); | |
| return RETURN_OUT_OF_RESOURCES; | |
| } | |
| mDmaAccess = ALIGN_POINTER (mDmaAccess, RESERVED_MEM_ALIGNMENT); | |
| // | |
| // Set up a protocol notify for EFI_S3_SAVE_STATE_PROTOCOL. Forward the | |
| // client's Context to the callback. | |
| // | |
| Status = gBS->CreateEvent ( | |
| EVT_NOTIFY_SIGNAL, | |
| TPL_CALLBACK, | |
| S3SaveStateInstalledNotify, | |
| Context, | |
| &mS3SaveStateInstalledEvent | |
| ); | |
| if (EFI_ERROR (Status)) { | |
| DEBUG (( | |
| DEBUG_ERROR, | |
| "%a: %a: CreateEvent(): %r\n", | |
| gEfiCallerBaseName, | |
| __func__, | |
| Status | |
| )); | |
| goto FreeDmaAccess; | |
| } | |
| Status = gBS->RegisterProtocolNotify ( | |
| &gEfiS3SaveStateProtocolGuid, | |
| mS3SaveStateInstalledEvent, | |
| &Registration | |
| ); | |
| if (EFI_ERROR (Status)) { | |
| DEBUG (( | |
| DEBUG_ERROR, | |
| "%a: %a: RegisterProtocolNotify(): %r\n", | |
| gEfiCallerBaseName, | |
| __func__, | |
| Status | |
| )); | |
| goto CloseEvent; | |
| } | |
| // | |
| // Set the remaining global variables. For the alignment guarantee on | |
| // mScratchBuffer, we rely on the fact that *mDmaAccess has a size that is an | |
| // integral multiple of RESERVED_MEM_ALIGNMENT. | |
| // | |
| ASSERT (sizeof *mDmaAccess % RESERVED_MEM_ALIGNMENT == 0); | |
| mScratchBuffer = mDmaAccess + 1; | |
| mScratchBufferSize = ScratchBufferSize; | |
| mCallback = Callback; | |
| // | |
| // Kick the event; EFI_S3_SAVE_STATE_PROTOCOL could be available already. | |
| // | |
| Status = gBS->SignalEvent (mS3SaveStateInstalledEvent); | |
| if (EFI_ERROR (Status)) { | |
| DEBUG (( | |
| DEBUG_ERROR, | |
| "%a: %a: SignalEvent(): %r\n", | |
| gEfiCallerBaseName, | |
| __func__, | |
| Status | |
| )); | |
| goto NullGlobals; | |
| } | |
| return RETURN_SUCCESS; | |
| NullGlobals: | |
| mScratchBuffer = NULL; | |
| mScratchBufferSize = 0; | |
| mCallback = NULL; | |
| CloseEvent: | |
| gBS->CloseEvent (mS3SaveStateInstalledEvent); | |
| mS3SaveStateInstalledEvent = NULL; | |
| FreeDmaAccess: | |
| FreePool (mDmaAccess); | |
| mDmaAccess = NULL; | |
| return (RETURN_STATUS)Status; | |
| } | |
| /** | |
| Produce ACPI S3 Boot Script opcodes that (optionally) select an fw_cfg item, | |
| and transfer data to it. | |
| The opcodes produced by QemuFwCfgS3ScriptWriteBytes() will first restore | |
| NumberOfBytes bytes in ScratchBuffer in-place, in reserved memory, then write | |
| them to fw_cfg using DMA. | |
| If the operation fails during S3 resume, the boot script will hang. | |
| This function may only be called from the client module's | |
| FW_CFG_BOOT_SCRIPT_CALLBACK_FUNCTION, which was passed to | |
| QemuFwCfgS3CallWhenBootScriptReady() as Callback. | |
| @param[in] FirmwareConfigItem The UINT16 selector key of the firmware config | |
| item to write, expressed as INT32. If | |
| FirmwareConfigItem is -1, no selection is | |
| made, the write will occur to the currently | |
| selected item, at its currently selected | |
| offset. Otherwise, the specified item will be | |
| selected, and the write will occur at offset | |
| 0. | |
| @param[in] NumberOfBytes Size of the data to restore in ScratchBuffer, | |
| and to write from ScratchBuffer, during S3 | |
| resume. NumberOfBytes must not exceed | |
| ScratchBufferSize, which was passed to | |
| QemuFwCfgS3CallWhenBootScriptReady(). | |
| @retval RETURN_SUCCESS The opcodes were appended to the ACPI S3 | |
| Boot Script successfully. There is no way | |
| to undo this action. | |
| @retval RETURN_INVALID_PARAMETER FirmwareConfigItem is invalid. | |
| @retval RETURN_BAD_BUFFER_SIZE NumberOfBytes is larger than | |
| ScratchBufferSize. | |
| @return Error codes from underlying functions. | |
| **/ | |
| RETURN_STATUS | |
| EFIAPI | |
| QemuFwCfgS3ScriptWriteBytes ( | |
| IN INT32 FirmwareConfigItem, | |
| IN UINTN NumberOfBytes | |
| ) | |
| { | |
| UINTN Count; | |
| EFI_STATUS Status; | |
| UINT64 AccessAddress; | |
| UINT32 ControlPollData; | |
| UINT32 ControlPollMask; | |
| ASSERT (mDmaAccess != NULL); | |
| ASSERT (mS3SaveState != NULL); | |
| if ((FirmwareConfigItem < -1) || (FirmwareConfigItem > MAX_UINT16)) { | |
| return RETURN_INVALID_PARAMETER; | |
| } | |
| if (NumberOfBytes > mScratchBufferSize) { | |
| return RETURN_BAD_BUFFER_SIZE; | |
| } | |
| // | |
| // Set up a write[+select] fw_cfg DMA command. | |
| // | |
| mDmaAccess->Control = FW_CFG_DMA_CTL_WRITE; | |
| if (FirmwareConfigItem != -1) { | |
| mDmaAccess->Control |= FW_CFG_DMA_CTL_SELECT; | |
| mDmaAccess->Control |= (UINT32)FirmwareConfigItem << 16; | |
| } | |
| mDmaAccess->Control = SwapBytes32 (mDmaAccess->Control); | |
| // | |
| // We ensured the following constraint via mScratchBufferSize in | |
| // QemuFwCfgS3CallWhenBootScriptReady(). | |
| // | |
| ASSERT (NumberOfBytes <= MAX_UINT32); | |
| mDmaAccess->Length = SwapBytes32 ((UINT32)NumberOfBytes); | |
| mDmaAccess->Address = SwapBytes64 ((UINTN)mScratchBuffer); | |
| // | |
| // Copy mDmaAccess and NumberOfBytes bytes from mScratchBuffer into the boot | |
| // script. When executed at S3 resume, this opcode will restore all of them | |
| // in-place. | |
| // | |
| Count = (UINTN)mScratchBuffer + NumberOfBytes - (UINTN)mDmaAccess; | |
| Status = mS3SaveState->Write ( | |
| mS3SaveState, // This | |
| EFI_BOOT_SCRIPT_MEM_WRITE_OPCODE, // OpCode | |
| EfiBootScriptWidthUint8, // Width | |
| (UINT64)(UINTN)mDmaAccess, // Address | |
| Count, // Count | |
| (VOID *)mDmaAccess // Buffer | |
| ); | |
| if (EFI_ERROR (Status)) { | |
| DEBUG (( | |
| DEBUG_ERROR, | |
| "%a: %a: EFI_BOOT_SCRIPT_MEM_WRITE_OPCODE: %r\n", | |
| gEfiCallerBaseName, | |
| __func__, | |
| Status | |
| )); | |
| return (RETURN_STATUS)Status; | |
| } | |
| // | |
| // Append an opcode that will write the address of the fw_cfg DMA command to | |
| // the fw_cfg DMA address register, which consists of two 32-bit IO ports. | |
| // The second (highest address, least significant) write will start the | |
| // transfer. | |
| // | |
| AccessAddress = SwapBytes64 ((UINTN)mDmaAccess); | |
| Status = mS3SaveState->Write ( | |
| mS3SaveState, // This | |
| EFI_BOOT_SCRIPT_IO_WRITE_OPCODE, // OpCode | |
| EfiBootScriptWidthUint32, // Width | |
| (UINT64)FW_CFG_IO_DMA_ADDRESS, // Address | |
| (UINTN)2, // Count | |
| (VOID *)&AccessAddress // Buffer | |
| ); | |
| if (EFI_ERROR (Status)) { | |
| DEBUG (( | |
| DEBUG_ERROR, | |
| "%a: %a: EFI_BOOT_SCRIPT_IO_WRITE_OPCODE: %r\n", | |
| gEfiCallerBaseName, | |
| __func__, | |
| Status | |
| )); | |
| return (RETURN_STATUS)Status; | |
| } | |
| // | |
| // The following opcode will wait until the Control word reads as zero | |
| // (transfer complete). As timeout we use MAX_UINT64 * 100ns, which is | |
| // approximately 58494 years. | |
| // | |
| ControlPollData = 0; | |
| ControlPollMask = MAX_UINT32; | |
| Status = mS3SaveState->Write ( | |
| mS3SaveState, // This | |
| EFI_BOOT_SCRIPT_MEM_POLL_OPCODE, // OpCode | |
| EfiBootScriptWidthUint32, // Width | |
| (UINT64)(UINTN)&mDmaAccess->Control, // Address | |
| (VOID *)&ControlPollData, // Data | |
| (VOID *)&ControlPollMask, // DataMask | |
| MAX_UINT64 // Delay | |
| ); | |
| if (EFI_ERROR (Status)) { | |
| DEBUG (( | |
| DEBUG_ERROR, | |
| "%a: %a: EFI_BOOT_SCRIPT_MEM_POLL_OPCODE: %r\n", | |
| gEfiCallerBaseName, | |
| __func__, | |
| Status | |
| )); | |
| return (RETURN_STATUS)Status; | |
| } | |
| return RETURN_SUCCESS; | |
| } | |
| /** | |
| Produce ACPI S3 Boot Script opcodes that (optionally) select an fw_cfg item, | |
| and transfer data from it. | |
| The opcodes produced by QemuFwCfgS3ScriptReadBytes() will read NumberOfBytes | |
| bytes from fw_cfg using DMA, storing the result in ScratchBuffer, in reserved | |
| memory. | |
| If the operation fails during S3 resume, the boot script will hang. | |
| This function may only be called from the client module's | |
| FW_CFG_BOOT_SCRIPT_CALLBACK_FUNCTION, which was passed to | |
| QemuFwCfgS3CallWhenBootScriptReady() as Callback. | |
| @param[in] FirmwareConfigItem The UINT16 selector key of the firmware config | |
| item to read, expressed as INT32. If | |
| FirmwareConfigItem is -1, no selection is | |
| made, the read will occur from the currently | |
| selected item, from its currently selected | |
| offset. Otherwise, the specified item will be | |
| selected, and the read will occur from offset | |
| 0. | |
| @param[in] NumberOfBytes Size of the data to read during S3 resume. | |
| NumberOfBytes must not exceed | |
| ScratchBufferSize, which was passed to | |
| QemuFwCfgS3CallWhenBootScriptReady(). | |
| @retval RETURN_SUCCESS The opcodes were appended to the ACPI S3 | |
| Boot Script successfully. There is no way | |
| to undo this action. | |
| @retval RETURN_INVALID_PARAMETER FirmwareConfigItem is invalid. | |
| @retval RETURN_BAD_BUFFER_SIZE NumberOfBytes is larger than | |
| ScratchBufferSize. | |
| @return Error codes from underlying functions. | |
| **/ | |
| RETURN_STATUS | |
| EFIAPI | |
| QemuFwCfgS3ScriptReadBytes ( | |
| IN INT32 FirmwareConfigItem, | |
| IN UINTN NumberOfBytes | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| UINT64 AccessAddress; | |
| UINT32 ControlPollData; | |
| UINT32 ControlPollMask; | |
| ASSERT (mDmaAccess != NULL); | |
| ASSERT (mS3SaveState != NULL); | |
| if ((FirmwareConfigItem < -1) || (FirmwareConfigItem > MAX_UINT16)) { | |
| return RETURN_INVALID_PARAMETER; | |
| } | |
| if (NumberOfBytes > mScratchBufferSize) { | |
| return RETURN_BAD_BUFFER_SIZE; | |
| } | |
| // | |
| // Set up a read[+select] fw_cfg DMA command. | |
| // | |
| mDmaAccess->Control = FW_CFG_DMA_CTL_READ; | |
| if (FirmwareConfigItem != -1) { | |
| mDmaAccess->Control |= FW_CFG_DMA_CTL_SELECT; | |
| mDmaAccess->Control |= (UINT32)FirmwareConfigItem << 16; | |
| } | |
| mDmaAccess->Control = SwapBytes32 (mDmaAccess->Control); | |
| // | |
| // We ensured the following constraint via mScratchBufferSize in | |
| // QemuFwCfgS3CallWhenBootScriptReady(). | |
| // | |
| ASSERT (NumberOfBytes <= MAX_UINT32); | |
| mDmaAccess->Length = SwapBytes32 ((UINT32)NumberOfBytes); | |
| mDmaAccess->Address = SwapBytes64 ((UINTN)mScratchBuffer); | |
| // | |
| // Copy mDmaAccess into the boot script. When executed at S3 resume, this | |
| // opcode will restore it in-place. | |
| // | |
| Status = mS3SaveState->Write ( | |
| mS3SaveState, // This | |
| EFI_BOOT_SCRIPT_MEM_WRITE_OPCODE, // OpCode | |
| EfiBootScriptWidthUint8, // Width | |
| (UINT64)(UINTN)mDmaAccess, // Address | |
| sizeof *mDmaAccess, // Count | |
| (VOID *)mDmaAccess // Buffer | |
| ); | |
| if (EFI_ERROR (Status)) { | |
| DEBUG (( | |
| DEBUG_ERROR, | |
| "%a: %a: EFI_BOOT_SCRIPT_MEM_WRITE_OPCODE: %r\n", | |
| gEfiCallerBaseName, | |
| __func__, | |
| Status | |
| )); | |
| return (RETURN_STATUS)Status; | |
| } | |
| // | |
| // Append an opcode that will write the address of the fw_cfg DMA command to | |
| // the fw_cfg DMA address register, which consists of two 32-bit IO ports. | |
| // The second (highest address, least significant) write will start the | |
| // transfer. | |
| // | |
| AccessAddress = SwapBytes64 ((UINTN)mDmaAccess); | |
| Status = mS3SaveState->Write ( | |
| mS3SaveState, // This | |
| EFI_BOOT_SCRIPT_IO_WRITE_OPCODE, // OpCode | |
| EfiBootScriptWidthUint32, // Width | |
| (UINT64)FW_CFG_IO_DMA_ADDRESS, // Address | |
| (UINTN)2, // Count | |
| (VOID *)&AccessAddress // Buffer | |
| ); | |
| if (EFI_ERROR (Status)) { | |
| DEBUG (( | |
| DEBUG_ERROR, | |
| "%a: %a: EFI_BOOT_SCRIPT_IO_WRITE_OPCODE: %r\n", | |
| gEfiCallerBaseName, | |
| __func__, | |
| Status | |
| )); | |
| return (RETURN_STATUS)Status; | |
| } | |
| // | |
| // The following opcode will wait until the Control word reads as zero | |
| // (transfer complete). As timeout we use MAX_UINT64 * 100ns, which is | |
| // approximately 58494 years. | |
| // | |
| ControlPollData = 0; | |
| ControlPollMask = MAX_UINT32; | |
| Status = mS3SaveState->Write ( | |
| mS3SaveState, // This | |
| EFI_BOOT_SCRIPT_MEM_POLL_OPCODE, // OpCode | |
| EfiBootScriptWidthUint32, // Width | |
| (UINT64)(UINTN)&mDmaAccess->Control, // Address | |
| (VOID *)&ControlPollData, // Data | |
| (VOID *)&ControlPollMask, // DataMask | |
| MAX_UINT64 // Delay | |
| ); | |
| if (EFI_ERROR (Status)) { | |
| DEBUG (( | |
| DEBUG_ERROR, | |
| "%a: %a: EFI_BOOT_SCRIPT_MEM_POLL_OPCODE: %r\n", | |
| gEfiCallerBaseName, | |
| __func__, | |
| Status | |
| )); | |
| return (RETURN_STATUS)Status; | |
| } | |
| return RETURN_SUCCESS; | |
| } | |
| /** | |
| Produce ACPI S3 Boot Script opcodes that (optionally) select an fw_cfg item, | |
| and increase its offset. | |
| If the operation fails during S3 resume, the boot script will hang. | |
| This function may only be called from the client module's | |
| FW_CFG_BOOT_SCRIPT_CALLBACK_FUNCTION, which was passed to | |
| QemuFwCfgS3CallWhenBootScriptReady() as Callback. | |
| @param[in] FirmwareConfigItem The UINT16 selector key of the firmware config | |
| item to advance the offset of, expressed as | |
| INT32. If FirmwareConfigItem is -1, no | |
| selection is made, and the offset for the | |
| currently selected item is increased. | |
| Otherwise, the specified item will be | |
| selected, and the offset increment will occur | |
| from offset 0. | |
| @param[in] NumberOfBytes The number of bytes to skip in the subject | |
| fw_cfg item. | |
| @retval RETURN_SUCCESS The opcodes were appended to the ACPI S3 | |
| Boot Script successfully. There is no way | |
| to undo this action. | |
| @retval RETURN_INVALID_PARAMETER FirmwareConfigItem is invalid. | |
| @retval RETURN_BAD_BUFFER_SIZE NumberOfBytes is too large. | |
| @return Error codes from underlying functions. | |
| **/ | |
| RETURN_STATUS | |
| EFIAPI | |
| QemuFwCfgS3ScriptSkipBytes ( | |
| IN INT32 FirmwareConfigItem, | |
| IN UINTN NumberOfBytes | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| UINT64 AccessAddress; | |
| UINT32 ControlPollData; | |
| UINT32 ControlPollMask; | |
| ASSERT (mDmaAccess != NULL); | |
| ASSERT (mS3SaveState != NULL); | |
| if ((FirmwareConfigItem < -1) || (FirmwareConfigItem > MAX_UINT16)) { | |
| return RETURN_INVALID_PARAMETER; | |
| } | |
| if (NumberOfBytes > MAX_UINT32) { | |
| return RETURN_BAD_BUFFER_SIZE; | |
| } | |
| // | |
| // Set up a skip[+select] fw_cfg DMA command. | |
| // | |
| mDmaAccess->Control = FW_CFG_DMA_CTL_SKIP; | |
| if (FirmwareConfigItem != -1) { | |
| mDmaAccess->Control |= FW_CFG_DMA_CTL_SELECT; | |
| mDmaAccess->Control |= (UINT32)FirmwareConfigItem << 16; | |
| } | |
| mDmaAccess->Control = SwapBytes32 (mDmaAccess->Control); | |
| mDmaAccess->Length = SwapBytes32 ((UINT32)NumberOfBytes); | |
| mDmaAccess->Address = 0; | |
| // | |
| // Copy mDmaAccess into the boot script. When executed at S3 resume, this | |
| // opcode will restore it in-place. | |
| // | |
| Status = mS3SaveState->Write ( | |
| mS3SaveState, // This | |
| EFI_BOOT_SCRIPT_MEM_WRITE_OPCODE, // OpCode | |
| EfiBootScriptWidthUint8, // Width | |
| (UINT64)(UINTN)mDmaAccess, // Address | |
| sizeof *mDmaAccess, // Count | |
| (VOID *)mDmaAccess // Buffer | |
| ); | |
| if (EFI_ERROR (Status)) { | |
| DEBUG (( | |
| DEBUG_ERROR, | |
| "%a: %a: EFI_BOOT_SCRIPT_MEM_WRITE_OPCODE: %r\n", | |
| gEfiCallerBaseName, | |
| __func__, | |
| Status | |
| )); | |
| return (RETURN_STATUS)Status; | |
| } | |
| // | |
| // Append an opcode that will write the address of the fw_cfg DMA command to | |
| // the fw_cfg DMA address register, which consists of two 32-bit IO ports. | |
| // The second (highest address, least significant) write will start the | |
| // transfer. | |
| // | |
| AccessAddress = SwapBytes64 ((UINTN)mDmaAccess); | |
| Status = mS3SaveState->Write ( | |
| mS3SaveState, // This | |
| EFI_BOOT_SCRIPT_IO_WRITE_OPCODE, // OpCode | |
| EfiBootScriptWidthUint32, // Width | |
| (UINT64)FW_CFG_IO_DMA_ADDRESS, // Address | |
| (UINTN)2, // Count | |
| (VOID *)&AccessAddress // Buffer | |
| ); | |
| if (EFI_ERROR (Status)) { | |
| DEBUG (( | |
| DEBUG_ERROR, | |
| "%a: %a: EFI_BOOT_SCRIPT_IO_WRITE_OPCODE: %r\n", | |
| gEfiCallerBaseName, | |
| __func__, | |
| Status | |
| )); | |
| return (RETURN_STATUS)Status; | |
| } | |
| // | |
| // The following opcode will wait until the Control word reads as zero | |
| // (transfer complete). As timeout we use MAX_UINT64 * 100ns, which is | |
| // approximately 58494 years. | |
| // | |
| ControlPollData = 0; | |
| ControlPollMask = MAX_UINT32; | |
| Status = mS3SaveState->Write ( | |
| mS3SaveState, // This | |
| EFI_BOOT_SCRIPT_MEM_POLL_OPCODE, // OpCode | |
| EfiBootScriptWidthUint32, // Width | |
| (UINT64)(UINTN)&mDmaAccess->Control, // Address | |
| (VOID *)&ControlPollData, // Data | |
| (VOID *)&ControlPollMask, // DataMask | |
| MAX_UINT64 // Delay | |
| ); | |
| if (EFI_ERROR (Status)) { | |
| DEBUG (( | |
| DEBUG_ERROR, | |
| "%a: %a: EFI_BOOT_SCRIPT_MEM_POLL_OPCODE: %r\n", | |
| gEfiCallerBaseName, | |
| __func__, | |
| Status | |
| )); | |
| return (RETURN_STATUS)Status; | |
| } | |
| return RETURN_SUCCESS; | |
| } | |
| /** | |
| Produce ACPI S3 Boot Script opcodes that check a value in ScratchBuffer. | |
| If the check fails during S3 resume, the boot script will hang. | |
| This function may only be called from the client module's | |
| FW_CFG_BOOT_SCRIPT_CALLBACK_FUNCTION, which was passed to | |
| QemuFwCfgS3CallWhenBootScriptReady() as Callback. | |
| @param[in] ScratchData Pointer to the UINT8, UINT16, UINT32 or UINT64 field | |
| in ScratchBuffer that should be checked. The caller | |
| is responsible for populating the field during S3 | |
| resume, by calling QemuFwCfgS3ScriptReadBytes() ahead | |
| of QemuFwCfgS3ScriptCheckValue(). | |
| ScratchData must point into ScratchBuffer, which was | |
| allocated, and passed to Callback(), by | |
| QemuFwCfgS3CallWhenBootScriptReady(). | |
| ScratchData must be aligned at ValueSize bytes. | |
| @param[in] ValueSize One of 1, 2, 4 or 8, specifying the size of the field | |
| to check. | |
| @param[in] ValueMask The value read from ScratchData is binarily AND-ed | |
| with ValueMask, and the result is compared against | |
| Value. If the masked data equals Value, the check | |
| passes, and the boot script can proceed. Otherwise, | |
| the check fails, and the boot script hangs. | |
| @param[in] Value Refer to ValueMask. | |
| @retval RETURN_SUCCESS The opcodes were appended to the ACPI S3 | |
| Boot Script successfully. There is no way | |
| to undo this action. | |
| @retval RETURN_INVALID_PARAMETER ValueSize is invalid. | |
| @retval RETURN_INVALID_PARAMETER ValueMask or Value cannot be represented in | |
| ValueSize bytes. | |
| @retval RETURN_INVALID_PARAMETER ScratchData is not aligned at ValueSize | |
| bytes. | |
| @retval RETURN_BAD_BUFFER_SIZE The ValueSize bytes at ScratchData aren't | |
| wholly contained in the ScratchBufferSize | |
| bytes at ScratchBuffer. | |
| @return Error codes from underlying functions. | |
| **/ | |
| RETURN_STATUS | |
| EFIAPI | |
| QemuFwCfgS3ScriptCheckValue ( | |
| IN VOID *ScratchData, | |
| IN UINT8 ValueSize, | |
| IN UINT64 ValueMask, | |
| IN UINT64 Value | |
| ) | |
| { | |
| EFI_BOOT_SCRIPT_WIDTH Width; | |
| EFI_STATUS Status; | |
| ASSERT (mS3SaveState != NULL); | |
| switch (ValueSize) { | |
| case 1: | |
| Width = EfiBootScriptWidthUint8; | |
| break; | |
| case 2: | |
| Width = EfiBootScriptWidthUint16; | |
| break; | |
| case 4: | |
| Width = EfiBootScriptWidthUint32; | |
| break; | |
| case 8: | |
| Width = EfiBootScriptWidthUint64; | |
| break; | |
| default: | |
| return RETURN_INVALID_PARAMETER; | |
| } | |
| if ((ValueSize < 8) && | |
| ((RShiftU64 (ValueMask, ValueSize * 8) > 0) || | |
| (RShiftU64 (Value, ValueSize * 8) > 0))) | |
| { | |
| return RETURN_INVALID_PARAMETER; | |
| } | |
| if ((UINTN)ScratchData % ValueSize > 0) { | |
| return RETURN_INVALID_PARAMETER; | |
| } | |
| if (((UINTN)ScratchData < (UINTN)mScratchBuffer) || | |
| ((UINTN)ScratchData > MAX_UINTN - ValueSize) || | |
| ((UINTN)ScratchData + ValueSize > | |
| (UINTN)mScratchBuffer + mScratchBufferSize)) | |
| { | |
| return RETURN_BAD_BUFFER_SIZE; | |
| } | |
| // | |
| // The following opcode will wait "until" (*ScratchData & ValueMask) reads as | |
| // Value, considering the least significant ValueSize bytes. As timeout we | |
| // use MAX_UINT64 * 100ns, which is approximately 58494 years. | |
| // | |
| Status = mS3SaveState->Write ( | |
| mS3SaveState, // This | |
| EFI_BOOT_SCRIPT_MEM_POLL_OPCODE, // OpCode | |
| Width, // Width | |
| (UINT64)(UINTN)ScratchData, // Address | |
| (VOID *)&Value, // Data | |
| (VOID *)&ValueMask, // DataMask | |
| MAX_UINT64 // Delay | |
| ); | |
| if (EFI_ERROR (Status)) { | |
| DEBUG (( | |
| DEBUG_ERROR, | |
| "%a: %a: EFI_BOOT_SCRIPT_MEM_POLL_OPCODE: %r\n", | |
| gEfiCallerBaseName, | |
| __func__, | |
| Status | |
| )); | |
| return (RETURN_STATUS)Status; | |
| } | |
| return RETURN_SUCCESS; | |
| } |