EFI driver implementing the MmCommunication2Protocol, but instead of trapping to MM mode (SMM on x86, el3 on arm) trap to the host, using a virtual device.
Moving the efi variable management to the host allows to have persistent efi variables without flash storage, and it also allows to have secure boot support without requiring MM mode to protect variable storage in flash. This patch brings the core code, and the x64 bits which talk to 'qemu -device uefi-vars-isa'. Signed-off-by: Gerd Hoffmann <kra...@redhat.com> --- .../VirtMmCommunication.inf | 50 +++ .../VirtMmCommunication.h | 37 ++ OvmfPkg/VirtMmCommunicationDxe/QemuX64.c | 106 +++++ .../VirtMmCommunication.c | 380 ++++++++++++++++++ 4 files changed, 573 insertions(+) create mode 100644 OvmfPkg/VirtMmCommunicationDxe/VirtMmCommunication.inf create mode 100644 OvmfPkg/VirtMmCommunicationDxe/VirtMmCommunication.h create mode 100644 OvmfPkg/VirtMmCommunicationDxe/QemuX64.c create mode 100644 OvmfPkg/VirtMmCommunicationDxe/VirtMmCommunication.c diff --git a/OvmfPkg/VirtMmCommunicationDxe/VirtMmCommunication.inf b/OvmfPkg/VirtMmCommunicationDxe/VirtMmCommunication.inf new file mode 100644 index 000000000000..a9998f3db8cd --- /dev/null +++ b/OvmfPkg/VirtMmCommunicationDxe/VirtMmCommunication.inf @@ -0,0 +1,50 @@ +#/** @file +# +# Virt MM Communicate driver +# +# SPDX-License-Identifier: BSD-2-Clause-Patent +# +#**/ + +[Defines] + INF_VERSION = 0x0001001A + BASE_NAME = VirtMmCommunication + FILE_GUID = 0B807404-2D1C-4066-95F4-F28A58800185 + MODULE_TYPE = DXE_RUNTIME_DRIVER + VERSION_STRING = 1.0 + ENTRY_POINT = VirtMmCommunication2Initialize + +[Sources] + VirtMmCommunication.c + +[Sources.X64] + QemuX64.c + +[Packages] + MdePkg/MdePkg.dec + MdeModulePkg/MdeModulePkg.dec + +[LibraryClasses] + BaseMemoryLib + DebugLib + DxeServicesTableLib + HobLib + MemoryAllocationLib + UefiDriverEntryPoint + +[FeaturePcd] + gEfiMdeModulePkgTokenSpaceGuid.PcdEnableVariableRuntimeCache ## CONSUMES + gEfiMdeModulePkgTokenSpaceGuid.PcdVariableCollectStatistics ## CONSUMES + +[Protocols] + gEfiMmCommunication2ProtocolGuid ## PRODUCES + gEfiSmmVariableProtocolGuid ## PRODUCES + +[Guids] + gEfiEndOfDxeEventGroupGuid + gEfiEventExitBootServicesGuid + gEfiEventReadyToBootGuid + gSmmVariableWriteGuid + +[Depex] + gEfiCpuArchProtocolGuid diff --git a/OvmfPkg/VirtMmCommunicationDxe/VirtMmCommunication.h b/OvmfPkg/VirtMmCommunicationDxe/VirtMmCommunication.h new file mode 100644 index 000000000000..32f9d16c42e3 --- /dev/null +++ b/OvmfPkg/VirtMmCommunicationDxe/VirtMmCommunication.h @@ -0,0 +1,37 @@ +/** @file + + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#ifndef _VIRT_MM_COMM_DXE_H_ +#define _VIRT_MM_COMM_DXE_H_ + +/* communication buffer */ + +#define MAX_BUFFER_SIZE (64 * 1024) + +extern VOID *mCommunicateBuffer; +extern EFI_PHYSICAL_ADDRESS mCommunicateBufferPhys; + +/* arch specific hooks */ + +EFI_STATUS +EFIAPI +VirtMmHwInit ( + VOID + ); + +EFI_STATUS +EFIAPI +VirtMmHwComm ( + VOID + ); + +EFI_STATUS +EFIAPI +VirtMmHwVirtMap ( + VOID + ); + +#endif /* _VIRT_MM_COMM_DXE_H_ */ diff --git a/OvmfPkg/VirtMmCommunicationDxe/QemuX64.c b/OvmfPkg/VirtMmCommunicationDxe/QemuX64.c new file mode 100644 index 000000000000..6470cfc7f73b --- /dev/null +++ b/OvmfPkg/VirtMmCommunicationDxe/QemuX64.c @@ -0,0 +1,106 @@ +/** @file + + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include <IndustryStandard/QemuUefiVars.h> + +#include <Library/BaseLib.h> +#include <Library/DebugLib.h> +#include <Library/IoLib.h> + +#include "VirtMmCommunication.h" + +STATIC +EFI_STATUS +EFIAPI +VirtMmHwCommand ( + UINT32 Cmd + ) +{ + UINT32 Count; + UINT32 Sts; + + IoWrite16 (UEFI_VARS_IO_BASE + UEFI_VARS_REG_CMD_STS, Cmd); + for (Count = 0; Count < 100; Count++) { + Sts = IoRead16 (UEFI_VARS_IO_BASE + UEFI_VARS_REG_CMD_STS); + DEBUG ((DEBUG_VERBOSE, "%a: Sts: 0x%x\n", __func__, Sts)); + switch (Sts) { + case UEFI_VARS_STS_SUCCESS: + return RETURN_SUCCESS; + case UEFI_VARS_STS_BUSY: + CpuPause (); + break; + case UEFI_VARS_STS_ERR_NOT_SUPPORTED: + return RETURN_UNSUPPORTED; + case UEFI_VARS_STS_ERR_BAD_BUFFER_SIZE: + return RETURN_BAD_BUFFER_SIZE; + default: + return RETURN_DEVICE_ERROR; + } + } + + return RETURN_TIMEOUT; +} + +EFI_STATUS +EFIAPI +VirtMmHwInit ( + VOID + ) +{ + UINT32 Magic, AddrLo, AddrHi; + EFI_STATUS Status; + + Magic = IoRead16 (UEFI_VARS_IO_BASE + UEFI_VARS_REG_MAGIC); + if (Magic != UEFI_VARS_MAGIC_VALUE) { + DEBUG (( + DEBUG_ERROR, + "%a: Magic value mismatch (0x%x != 0x%x)\n", + __func__, + Magic, + UEFI_VARS_MAGIC_VALUE + )); + return RETURN_DEVICE_ERROR; + } + + DEBUG ((DEBUG_INFO, "%a: Magic 0x%x, good\n", __func__, Magic)); + + Status = VirtMmHwCommand (UEFI_VARS_CMD_RESET); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "%a: Reset failed: %d\n", __func__, Status)); + return Status; + } + + AddrLo = (UINT32)mCommunicateBufferPhys; + AddrHi = (UINT32)RShiftU64 (mCommunicateBufferPhys, 32); + IoWrite32 (UEFI_VARS_IO_BASE + UEFI_VARS_REG_BUFFER_ADDR_LO, AddrLo); + IoWrite32 (UEFI_VARS_IO_BASE + UEFI_VARS_REG_BUFFER_ADDR_HI, AddrHi); + IoWrite32 (UEFI_VARS_IO_BASE + UEFI_VARS_REG_BUFFER_SIZE, MAX_BUFFER_SIZE); + + return RETURN_SUCCESS; +} + +EFI_STATUS +EFIAPI +VirtMmHwVirtMap ( + VOID + ) +{ + return RETURN_SUCCESS; +} + +EFI_STATUS +EFIAPI +VirtMmHwComm ( + VOID + ) +{ + EFI_STATUS Status; + + Status = VirtMmHwCommand (UEFI_VARS_CMD_MM); + DEBUG ((DEBUG_VERBOSE, "%a: Status: %r\n", __func__, Status)); + + return Status; +} diff --git a/OvmfPkg/VirtMmCommunicationDxe/VirtMmCommunication.c b/OvmfPkg/VirtMmCommunicationDxe/VirtMmCommunication.c new file mode 100644 index 000000000000..7b4ebe1257c6 --- /dev/null +++ b/OvmfPkg/VirtMmCommunicationDxe/VirtMmCommunication.c @@ -0,0 +1,380 @@ +/** @file + + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include <Library/BaseLib.h> +#include <Library/BaseMemoryLib.h> +#include <Library/DebugLib.h> +#include <Library/DxeServicesTableLib.h> +#include <Library/HobLib.h> +#include <Library/MemoryAllocationLib.h> +#include <Library/PcdLib.h> +#include <Library/UefiBootServicesTableLib.h> +#include <Library/UefiRuntimeServicesTableLib.h> + +#include <Protocol/MmCommunication2.h> + +#include "VirtMmCommunication.h" + +VOID *mCommunicateBuffer; +EFI_PHYSICAL_ADDRESS mCommunicateBufferPhys; + +// Notification event when virtual address map is set. +STATIC EFI_EVENT mSetVirtualAddressMapEvent; + +// Handle to install the MM Communication Protocol +STATIC EFI_HANDLE mMmCommunicateHandle; + +// Handle to install the EfiSmmVariableProtocol +STATIC EFI_HANDLE mSmmVariableHandle; + +// Handle to install the SmmVariableWrite +STATIC EFI_HANDLE mSmmVariableWriteHandle; + +/** + Communicates with a registered handler. + + This function provides a service to send and receive messages from a registered UEFI service. + + @param[in] This The EFI_MM_COMMUNICATION_PROTOCOL instance. + @param[in, out] CommBufferPhysical Physical address of the MM communication buffer + @param[in, out] CommBufferVirtual Virtual address of the MM communication buffer + @param[in, out] CommSize The size of the data buffer being passed in. On input, + when not omitted, the buffer should cover EFI_MM_COMMUNICATE_HEADER + and the value of MessageLength field. On exit, the size + of data being returned. Zero if the handler does not + wish to reply with any data. This parameter is optional + and may be NULL. + + @retval EFI_SUCCESS The message was successfully posted. + @retval EFI_INVALID_PARAMETER CommBufferPhysical or CommBufferVirtual was NULL, or + integer value pointed by CommSize does not cover + EFI_MM_COMMUNICATE_HEADER and the value of MessageLength + field. + @retval EFI_BAD_BUFFER_SIZE The buffer is too large for the MM implementation. + If this error is returned, the MessageLength field + in the CommBuffer header or the integer pointed by + CommSize, are updated to reflect the maximum payload + size the implementation can accommodate. + @retval EFI_ACCESS_DENIED The CommunicateBuffer parameter or CommSize parameter, + if not omitted, are in address range that cannot be + accessed by the MM environment. + +**/ +EFI_STATUS +EFIAPI +VirtMmCommunication2Communicate ( + IN CONST EFI_MM_COMMUNICATION2_PROTOCOL *This, + IN OUT VOID *CommBufferPhysical, + IN OUT VOID *CommBufferVirtual, + IN OUT UINTN *CommSize OPTIONAL + ) +{ + EFI_MM_COMMUNICATE_HEADER *CommunicateHeader; + EFI_STATUS Status; + UINTN BufferSize; + + // + // Check parameters + // + if ((CommBufferVirtual == NULL) || (CommBufferPhysical == NULL)) { + return EFI_INVALID_PARAMETER; + } + + Status = EFI_SUCCESS; + CommunicateHeader = CommBufferVirtual; + // CommBuffer is a mandatory parameter. Hence, Rely on + // MessageLength + Header to ascertain the + // total size of the communication payload rather than + // rely on optional CommSize parameter + BufferSize = CommunicateHeader->MessageLength + + sizeof (CommunicateHeader->HeaderGuid) + + sizeof (CommunicateHeader->MessageLength); + + DEBUG (( + DEBUG_VERBOSE, + "%a: %g msglen %ld, BufferSize %ld, CommSize %ld\n", + __func__, + &CommunicateHeader->HeaderGuid, + CommunicateHeader->MessageLength, + BufferSize, + *CommSize + )); + + // If CommSize is not omitted, perform size inspection before proceeding. + if (CommSize != NULL) { + // This case can be used by the consumer of this driver to find out the + // max size that can be used for allocating CommBuffer. + if ((*CommSize == 0) || + (*CommSize > MAX_BUFFER_SIZE)) + { + *CommSize = MAX_BUFFER_SIZE; + Status = EFI_BAD_BUFFER_SIZE; + } + + // + // CommSize should cover at least MessageLength + sizeof (EFI_MM_COMMUNICATE_HEADER); + // + if (*CommSize < BufferSize) { + Status = EFI_INVALID_PARAMETER; + } + } + + // + // If the message length is 0 or greater than what can be tolerated by the MM + // environment then return the expected size. + // + if ((CommunicateHeader->MessageLength == 0) || + (BufferSize > MAX_BUFFER_SIZE)) + { + CommunicateHeader->MessageLength = MAX_BUFFER_SIZE - + sizeof (CommunicateHeader->HeaderGuid) - + sizeof (CommunicateHeader->MessageLength); + Status = EFI_BAD_BUFFER_SIZE; + } + + // MessageLength or CommSize check has failed, return here. + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_WARN, "%a: check error: %r\n", __func__, Status)); + return Status; + } + + // Copy Communication Payload + CopyMem (mCommunicateBuffer, CommBufferVirtual, BufferSize); + + Status = VirtMmHwComm (); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_WARN, "%a: comm error: %r\n", __func__, Status)); + return Status; + } + + CopyMem (CommBufferVirtual, mCommunicateBuffer, BufferSize); + + DEBUG ((DEBUG_VERBOSE, "%a: success (%d)\n", __func__, BufferSize)); + return EFI_SUCCESS; +} + +// +// MM Communication Protocol instance +// +STATIC EFI_MM_COMMUNICATION2_PROTOCOL mMmCommunication2 = { + VirtMmCommunication2Communicate +}; + +/** + Notification callback on SetVirtualAddressMap event. + + This function notifies the MM communication protocol interface on + SetVirtualAddressMap event and converts pointers used in this driver + from physical to virtual address. + + @param Event SetVirtualAddressMap event. + @param Context A context when the SetVirtualAddressMap triggered. + + @retval EFI_SUCCESS The function executed successfully. + @retval Other Some error occurred when executing this function. + +**/ +STATIC +VOID +EFIAPI +VirtMmNotifySetVirtualAddressMap ( + IN EFI_EVENT Event, + IN VOID *Context + ) +{ + EFI_STATUS Status; + + DEBUG ((DEBUG_VERBOSE, "%a: << %p\n", __func__, mCommunicateBuffer)); + Status = gRT->ConvertPointer (EFI_OPTIONAL_PTR, &mCommunicateBuffer); + DEBUG ((DEBUG_VERBOSE, "%a: >> %p\n", __func__, mCommunicateBuffer)); + + if (EFI_ERROR (Status)) { + DEBUG (( + DEBUG_ERROR, + "%a: Unable to convert MM runtime pointer. Status: %r\n", + __func__, + Status + )); + } + + Status = VirtMmHwVirtMap (); + if (EFI_ERROR (Status)) { + DEBUG (( + DEBUG_ERROR, + "%a: VirtMmHwVirtMap failed. Status: %r\n", + __func__, + Status + )); + } +} + +STATIC EFI_GUID *CONST mGuidedEventGuid[] = { + &gEfiEndOfDxeEventGroupGuid, + &gEfiEventExitBootServicesGuid, + &gEfiEventReadyToBootGuid, +}; + +STATIC EFI_EVENT mGuidedEvent[ARRAY_SIZE (mGuidedEventGuid)]; + +/** + Event notification that is fired when GUIDed Event Group is signaled. + + @param Event The Event that is being processed, not used. + @param Context Event Context, not used. + +**/ +STATIC +VOID +EFIAPI +VirtMmGuidedEventNotify ( + IN EFI_EVENT Event, + IN VOID *Context + ) +{ + EFI_MM_COMMUNICATE_HEADER Header; + UINTN Size; + + // + // Use Guid to initialize EFI_SMM_COMMUNICATE_HEADER structure + // + CopyGuid (&Header.HeaderGuid, Context); + Header.MessageLength = 1; + Header.Data[0] = 0; + + Size = sizeof (Header); + VirtMmCommunication2Communicate (&mMmCommunication2, &Header, &Header, &Size); +} + +/** + The Entry Point for MM Communication + + This function installs the MM communication protocol interface and finds out + what type of buffer management will be required prior to invoking the + communication SMC. + + @param ImageHandle The firmware allocated handle for the EFI image. + @param SystemTable A pointer to the EFI System Table. + + @retval EFI_SUCCESS The entry point is executed successfully. + @retval Other Some error occurred when executing this entry point. + +**/ +EFI_STATUS +EFIAPI +VirtMmCommunication2Initialize ( + IN EFI_HANDLE ImageHandle, + IN EFI_SYSTEM_TABLE *SystemTable + ) +{ + EFI_STATUS Status; + UINTN Index; + + if (FeaturePcdGet (PcdEnableVariableRuntimeCache)) { + ASSERT (!"Variable driver runtime cache is not supported.\n"); + CpuDeadLoop (); + } + + if (FeaturePcdGet (PcdVariableCollectStatistics)) { + ASSERT (!"Variable driver statistics are not supported.\n"); + CpuDeadLoop (); + } + + mCommunicateBuffer = AllocateRuntimePages (EFI_SIZE_TO_PAGES (MAX_BUFFER_SIZE)); + if (!mCommunicateBuffer) { + Status = EFI_OUT_OF_RESOURCES; + goto ReturnErrorStatus; + } + + mCommunicateBufferPhys = (EFI_PHYSICAL_ADDRESS)(UINTN)(mCommunicateBuffer); + Status = VirtMmHwInit (); + if (EFI_ERROR (Status)) { + DEBUG (( + DEBUG_ERROR, + "%a: Failed to init HW: %r\n", + __func__, + Status + )); + goto FreeBufferPages; + } + + // Install the communication protocol + Status = gBS->InstallProtocolInterface ( + &mMmCommunicateHandle, + &gEfiMmCommunication2ProtocolGuid, + EFI_NATIVE_INTERFACE, + &mMmCommunication2 + ); + if (EFI_ERROR (Status)) { + DEBUG (( + DEBUG_ERROR, + "%a: Failed to install MM communication protocol\n", + __func__ + )); + goto FreeBufferPages; + } + + Status = gBS->InstallProtocolInterface ( + &mSmmVariableHandle, + &gEfiSmmVariableProtocolGuid, + EFI_NATIVE_INTERFACE, + NULL + ); + ASSERT_EFI_ERROR (Status); + + Status = gBS->InstallProtocolInterface ( + &mSmmVariableWriteHandle, + &gSmmVariableWriteGuid, + EFI_NATIVE_INTERFACE, + NULL + ); + ASSERT_EFI_ERROR (Status); + + // Register notification callback when virtual address is associated + // with the physical address. + // Create a Set Virtual Address Map event. + Status = gBS->CreateEvent ( + EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE, + TPL_NOTIFY, + VirtMmNotifySetVirtualAddressMap, + NULL, + &mSetVirtualAddressMapEvent + ); + ASSERT_EFI_ERROR (Status); + + for (Index = 0; Index < ARRAY_SIZE (mGuidedEventGuid); Index++) { + Status = gBS->CreateEventEx ( + EVT_NOTIFY_SIGNAL, + TPL_CALLBACK, + VirtMmGuidedEventNotify, + mGuidedEventGuid[Index], + mGuidedEventGuid[Index], + &mGuidedEvent[Index] + ); + ASSERT_EFI_ERROR (Status); + if (EFI_ERROR (Status)) { + while (Index-- > 0) { + gBS->CloseEvent (mGuidedEvent[Index]); + } + + goto UninstallProtocol; + } + } + + return EFI_SUCCESS; + +UninstallProtocol: + gBS->UninstallProtocolInterface ( + mMmCommunicateHandle, + &gEfiMmCommunication2ProtocolGuid, + &mMmCommunication2 + ); + +FreeBufferPages: + FreePages (mCommunicateBuffer, EFI_SIZE_TO_PAGES (MAX_BUFFER_SIZE)); + +ReturnErrorStatus: + return EFI_INVALID_PARAMETER; +} -- 2.42.0 -=-=-=-=-=-=-=-=-=-=-=- Groups.io Links: You receive all messages sent to this group. View/Reply Online (#111679): https://edk2.groups.io/g/devel/message/111679 Mute This Topic: https://groups.io/mt/102767936/21656 Group Owner: devel+ow...@edk2.groups.io Unsubscribe: https://edk2.groups.io/g/devel/unsub [arch...@mail-archive.com] -=-=-=-=-=-=-=-=-=-=-=-