diff --git a/UefiPayloadPkg/Include/Library/SmmStoreLib.h b/UefiPayloadPkg/Include/Library/SmmStoreLib.h new file mode 100644 index 0000000000..bf0b8a01b8 --- /dev/null +++ b/UefiPayloadPkg/Include/Library/SmmStoreLib.h @@ -0,0 +1,146 @@ +/** @file SmmStoreLib.h + + Copyright (c) 2022, 9elements GmbH
+ + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#ifndef SMM_STORE_LIB_H_ +#define SMM_STORE_LIB_H_ + +#include +#include +#include + +#define SMMSTORE_COMBUF_SIZE 16 + +/** + Get the SmmStore block size + + @param BlockSize The pointer to store the block size in. + +**/ +EFI_STATUS +SmmStoreLibGetBlockSize ( + OUT UINTN *BlockSize + ); + +/** + Get the SmmStore number of blocks + + @param NumBlocks The pointer to store the number of blocks in. + +**/ +EFI_STATUS +SmmStoreLibGetNumBlocks ( + OUT UINTN *NumBlocks + ); + +/** + Get the SmmStore MMIO address + + @param MmioAddress The pointer to store the address in. + +**/ +EFI_STATUS +SmmStoreLibGetMmioAddress ( + OUT EFI_PHYSICAL_ADDRESS *MmioAddress + ); + +/** + Read from SmmStore + + @param[in] Lba The starting logical block index to read from. + @param[in] Offset Offset into the block at which to begin reading. + @param[in] NumBytes On input, indicates the requested read size. On + output, indicates the actual number of bytes read. + @param[in] Buffer Pointer to the buffer to read into. + +**/ +EFI_STATUS +SmmStoreLibRead ( + IN EFI_LBA Lba, + IN UINTN Offset, + IN UINTN *NumBytes, + IN UINT8 *Buffer + ); + +/** + Write to SmmStore + + @param[in] Lba The starting logical block index to write to. + @param[in] Offset Offset into the block at which to begin writing. + @param[in] NumBytes On input, indicates the requested write size. On + output, indicates the actual number of bytes written. + @param[in] Buffer Pointer to the data to write. + +**/ +EFI_STATUS +SmmStoreLibWrite ( + IN EFI_LBA Lba, + IN UINTN Offset, + IN UINTN *NumBytes, + IN UINT8 *Buffer + ); + +/** + Erase a block using the SmmStore + + @param Lba The logical block index to erase. + +**/ +EFI_STATUS +SmmStoreLibEraseBlock ( + IN EFI_LBA Lba + ); + +/** + Function to update a pointer on virtual address change. Matches the signature + and operation of EfiConvertPointer. + +**/ +typedef EFI_STATUS EFIAPI (*CONVERT_POINTER_CALLBACK) ( + IN UINTN DebugDisposition, + IN OUT VOID **Address + ); + +/** + Initializes SmmStore support + + @retval EFI_WRITE_PROTECTED The SmmStore is not present. + @retval EFI_UNSUPPORTED The SmmStoreInfo HOB wasn't found. + @retval EFI_SUCCESS The SmmStore is supported. + +**/ +EFI_STATUS +SmmStoreLibInitialize ( + VOID + ); + +/** + Fixup internal data so that EFI can be called in virtual mode. + Converts any pointers in lib to virtual mode. This function is meant to + be invoked on gEfiEventVirtualAddressChangeGuid event when the library is + used at run-time. + + @param[in] ConvertPointer Function to switch virtual address space. + +**/ +VOID +EFIAPI +SmmStoreLibVirtualAddressChange ( + IN CONVERT_POINTER_CALLBACK ConvertPointer + ); + +/** + Deinitializes SmmStore support + +**/ +VOID +EFIAPI +SmmStoreLibDeinitialize ( + VOID + ); + +#endif /* SMM_STORE_LIB_H_ */ diff --git a/UefiPayloadPkg/Library/SmmStoreLib/SmmStore.c b/UefiPayloadPkg/Library/SmmStoreLib/SmmStore.c new file mode 100644 index 0000000000..690bae0f1c --- /dev/null +++ b/UefiPayloadPkg/Library/SmmStoreLib/SmmStore.c @@ -0,0 +1,447 @@ +/** @file SmmStore.c + + Copyright (c) 2022, 9elements GmbH
+ + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include "SmmStore.h" + +/* + * A memory buffer to place arguments in. + */ +STATIC SMM_STORE_COM_BUF *mArgComBuf; +STATIC EFI_PHYSICAL_ADDRESS mArgComBufPhys; + +/* + * Metadata provided by the first stage bootloader. + */ +STATIC SMMSTORE_INFO *mSmmStoreInfo; + +/** + Calls into SMM to use the SMMSTOREv2 implementation for persistent storage. + + @param Cmd The command to write into the APM port. This allows to enter the + Smi special command handler. + @param SubCmd The subcommand to execute in the Smi handler. + @param Arg Optional argument to pass to the Smi handler. Typically a pointer + in 'flat' memory mode, which points to read only memory. + + @retval EFI_NO_RESPONSE The SmmStore is not present or didn't response. + @retval EFI_UNSUPPORTED The request isn't supported. + @retval EFI_DEVICE_ERROR An error occurred while executing the request. + @retval EFI_SUCCESS The operation was executed successfully. +**/ +STATIC +EFI_STATUS +CallSmm ( + UINT8 Cmd, + UINT8 SubCmd, + UINTN Arg + ) +{ + CONST UINTN Rax = ((SubCmd << 8) | Cmd); + CONST UINTN Rbx = Arg; + UINTN Result; + + Result = TriggerSmi (Rax, Rbx, 5); + if (Result == Rax) { + return EFI_NO_RESPONSE; + } else if (Result == SMMSTORE_RET_SUCCESS) { + return EFI_SUCCESS; + } else if (Result == SMMSTORE_RET_UNSUPPORTED) { + return EFI_UNSUPPORTED; + } + + return EFI_DEVICE_ERROR; +} + +/** + Get the SmmStore block size + + @param BlockSize The pointer to store the block size in. + +**/ +EFI_STATUS +SmmStoreLibGetBlockSize ( + OUT UINTN *BlockSize + ) +{ + if (mSmmStoreInfo == NULL) { + return EFI_NO_MEDIA; + } + + if (BlockSize == NULL) { + return EFI_INVALID_PARAMETER; + } + + *BlockSize = mSmmStoreInfo->BlockSize; + + return EFI_SUCCESS; +} + +/** + Get the SmmStore number of blocks + + @param NumBlocks The pointer to store the number of blocks in. + +**/ +EFI_STATUS +SmmStoreLibGetNumBlocks ( + OUT UINTN *NumBlocks + ) +{ + if (mSmmStoreInfo == NULL) { + return EFI_NO_MEDIA; + } + + if (NumBlocks == NULL) { + return EFI_INVALID_PARAMETER; + } + + *NumBlocks = mSmmStoreInfo->NumBlocks; + + return EFI_SUCCESS; +} + +/** + Get the SmmStore MMIO address + + @param MmioAddress The pointer to store the address in. + +**/ +EFI_STATUS +SmmStoreLibGetMmioAddress ( + OUT EFI_PHYSICAL_ADDRESS *MmioAddress + ) +{ + if (mSmmStoreInfo == NULL) { + return EFI_NO_MEDIA; + } + + if (MmioAddress == NULL) { + return EFI_INVALID_PARAMETER; + } + + *MmioAddress = mSmmStoreInfo->MmioAddress; + + return EFI_SUCCESS; +} + +/** + Read from SmmStore + + @param[in] Lba The starting logical block index to read from. + @param[in] Offset Offset into the block at which to begin reading. + @param[in] NumBytes On input, indicates the requested read size. On + output, indicates the actual number of bytes read. + @param[in] Buffer Pointer to the buffer to read into. + +**/ +EFI_STATUS +SmmStoreLibRead ( + IN EFI_LBA Lba, + IN UINTN Offset, + IN UINTN *NumBytes, + IN UINT8 *Buffer + ) +{ + EFI_STATUS Status; + + if (mSmmStoreInfo == NULL) { + return EFI_NO_MEDIA; + } + + if (Lba >= mSmmStoreInfo->NumBlocks) { + return EFI_INVALID_PARAMETER; + } + + if (((*NumBytes + Offset) > mSmmStoreInfo->BlockSize) || + ((*NumBytes + Offset) > mSmmStoreInfo->ComBufferSize)) + { + return EFI_INVALID_PARAMETER; + } + + mArgComBuf->Read.BufSize = *NumBytes; + mArgComBuf->Read.BufOffset = Offset; + mArgComBuf->Read.BlockId = Lba; + + Status = CallSmm (mSmmStoreInfo->ApmCmd, SMMSTORE_CMD_RAW_READ, mArgComBufPhys); + if (EFI_ERROR (Status)) { + return Status; + } + + CopyMem (Buffer, (VOID *)(UINTN)(mSmmStoreInfo->ComBuffer + Offset), *NumBytes); + + return EFI_SUCCESS; +} + +/** + Write to SmmStore + + @param[in] Lba The starting logical block index to write to. + @param[in] Offset Offset into the block at which to begin writing. + @param[in] NumBytes On input, indicates the requested write size. On + output, indicates the actual number of bytes written. + @param[in] Buffer Pointer to the data to write. + +**/ +EFI_STATUS +SmmStoreLibWrite ( + IN EFI_LBA Lba, + IN UINTN Offset, + IN UINTN *NumBytes, + IN UINT8 *Buffer + ) +{ + if (mSmmStoreInfo == NULL) { + return EFI_NO_MEDIA; + } + + if (Lba >= mSmmStoreInfo->NumBlocks) { + return EFI_INVALID_PARAMETER; + } + + if (((*NumBytes + Offset) > mSmmStoreInfo->BlockSize) || + ((*NumBytes + Offset) > mSmmStoreInfo->ComBufferSize)) + { + return EFI_INVALID_PARAMETER; + } + + mArgComBuf->Write.BufSize = *NumBytes; + mArgComBuf->Write.BufOffset = Offset; + mArgComBuf->Write.BlockId = Lba; + + CopyMem ((VOID *)(UINTN)(mSmmStoreInfo->ComBuffer + Offset), Buffer, *NumBytes); + + return CallSmm (mSmmStoreInfo->ApmCmd, SMMSTORE_CMD_RAW_WRITE, mArgComBufPhys); +} + +/** + Erase a SmmStore block + + @param Lba The logical block index to erase. + +**/ +EFI_STATUS +SmmStoreLibEraseBlock ( + IN EFI_LBA Lba + ) +{ + if (mSmmStoreInfo == NULL) { + return EFI_NO_MEDIA; + } + + if (Lba >= mSmmStoreInfo->NumBlocks) { + return EFI_INVALID_PARAMETER; + } + + mArgComBuf->Clear.BlockId = Lba; + + return CallSmm (mSmmStoreInfo->ApmCmd, SMMSTORE_CMD_RAW_CLEAR, mArgComBufPhys); +} + +/** + Fixup internal data so that EFI can be called in virtual mode. + Converts any pointers in lib to virtual mode. This function is meant to + be invoked on gEfiEventVirtualAddressChangeGuid event when the library is + used at run-time. + + @param[in] ConvertPointer Function to switch virtual address space. + +**/ +VOID +EFIAPI +SmmStoreLibVirtualAddressChange ( + IN CONVERT_POINTER_CALLBACK ConvertPointer + ) +{ + ConvertPointer (0x0, (VOID **)&mArgComBuf); + if (mSmmStoreInfo != NULL) { + ConvertPointer (0x0, (VOID **)&mSmmStoreInfo->ComBuffer); + ConvertPointer (0x0, (VOID **)&mSmmStoreInfo); + } + + return; +} + +/** + Initializes SmmStore support + + @retval EFI_WRITE_PROTECTED The SmmStore is not present. + @retval EFI_OUT_OF_RESOURCES Run out of memory. + @retval EFI_SUCCESS The SmmStore is supported. + +**/ +EFI_STATUS +SmmStoreLibInitialize ( + VOID + ) +{ + EFI_STATUS Status; + VOID *GuidHob; + EFI_GCD_MEMORY_SPACE_DESCRIPTOR GcdDescriptor; + + // + // Find the SmmStore information guid hob + // + GuidHob = GetFirstGuidHob (&gEfiSmmStoreInfoHobGuid); + if (GuidHob == NULL) { + DEBUG ((DEBUG_WARN, "SmmStore not supported! Skipping driver init.\n")); + return EFI_UNSUPPORTED; + } + + // + // Place SmmStore information hob in a runtime buffer + // + mSmmStoreInfo = AllocateRuntimePool (GET_GUID_HOB_DATA_SIZE (GuidHob)); + if (mSmmStoreInfo == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + CopyMem (mSmmStoreInfo, GET_GUID_HOB_DATA (GuidHob), GET_GUID_HOB_DATA_SIZE (GuidHob)); + + // + // Validate input + // + if ((mSmmStoreInfo->MmioAddress == 0) || + (mSmmStoreInfo->ComBuffer == 0) || + (mSmmStoreInfo->BlockSize == 0) || + (mSmmStoreInfo->NumBlocks == 0)) + { + DEBUG ((DEBUG_ERROR, "%a: Invalid data in SmmStore Info hob\n", __func__)); + FreePool (mSmmStoreInfo); + mSmmStoreInfo = NULL; + return EFI_WRITE_PROTECTED; + } + + // + // Allocate Communication Buffer for arguments to pass to SMM. + // The argument com buffer is only read by SMM, but never written. + // The FVB data send/retrieved will be placed in a separate bootloader + // pre-allocated memory region, the ComBuffer. + // + if (mSmmStoreInfo->ComBuffer < BASE_4GB) { + // + // Assume that SMM handler is running in 32-bit mode when ComBuffer is + // is placed below BASE_4GB. + // + mArgComBufPhys = BASE_4GB - 1; + } else { + mArgComBufPhys = BASE_8EB - 1; + } + + Status = gBS->AllocatePages ( + AllocateMaxAddress, + EfiRuntimeServicesData, + EFI_SIZE_TO_PAGES (sizeof (SMM_STORE_COM_BUF)), + &mArgComBufPhys + ); + + if (EFI_ERROR (Status)) { + FreePool (mSmmStoreInfo); + mSmmStoreInfo = NULL; + return EFI_OUT_OF_RESOURCES; + } + + mArgComBuf = (VOID *)mArgComBufPhys; + + // + // Finally mark the SMM communication buffer provided by CB or SBL as runtime memory + // + Status = gDS->GetMemorySpaceDescriptor (mSmmStoreInfo->ComBuffer, &GcdDescriptor); + if (EFI_ERROR (Status) || (GcdDescriptor.GcdMemoryType != EfiGcdMemoryTypeReserved)) { + DEBUG (( + DEBUG_INFO, + "%a: No memory space descriptor for com buffer found\n", + __func__ + )); + + // + // Add a new entry if not covered by existing mapping + // + Status = gDS->AddMemorySpace ( + EfiGcdMemoryTypeReserved, + mSmmStoreInfo->ComBuffer, + mSmmStoreInfo->ComBufferSize, + EFI_MEMORY_WB | EFI_MEMORY_RUNTIME + ); + ASSERT_EFI_ERROR (Status); + } + + // + // Mark as runtime service + // + Status = gDS->SetMemorySpaceAttributes ( + mSmmStoreInfo->ComBuffer, + mSmmStoreInfo->ComBufferSize, + EFI_MEMORY_RUNTIME + ); + ASSERT_EFI_ERROR (Status); + + // + // Mark the memory mapped store as MMIO memory + // + Status = gDS->GetMemorySpaceDescriptor (mSmmStoreInfo->MmioAddress, &GcdDescriptor); + if (EFI_ERROR (Status) || (GcdDescriptor.GcdMemoryType != EfiGcdMemoryTypeMemoryMappedIo)) { + DEBUG (( + DEBUG_INFO, + "%a: No memory space descriptor for com buffer found\n", + __func__ + )); + + // + // Add a new entry if not covered by existing mapping + // + Status = gDS->AddMemorySpace ( + EfiGcdMemoryTypeMemoryMappedIo, + mSmmStoreInfo->MmioAddress, + mSmmStoreInfo->NumBlocks * mSmmStoreInfo->BlockSize, + EFI_MEMORY_UC | EFI_MEMORY_RUNTIME + ); + ASSERT_EFI_ERROR (Status); + } + + // + // Mark as runtime service + // + Status = gDS->SetMemorySpaceAttributes ( + mSmmStoreInfo->MmioAddress, + mSmmStoreInfo->NumBlocks * mSmmStoreInfo->BlockSize, + EFI_MEMORY_RUNTIME + ); + ASSERT_EFI_ERROR (Status); + + return EFI_SUCCESS; +} + +/** + Deinitializes SmmStore support by freeing allocated memory. + +**/ +VOID +EFIAPI +SmmStoreLibDeinitialize ( + VOID + ) +{ + if (mArgComBuf != NULL) { + gBS->FreePages (mArgComBufPhys, EFI_SIZE_TO_PAGES (sizeof (SMM_STORE_COM_BUF))); + mArgComBuf = NULL; + } + + if (mSmmStoreInfo != NULL) { + FreePool (mSmmStoreInfo); + mSmmStoreInfo = NULL; + } +} diff --git a/UefiPayloadPkg/Library/SmmStoreLib/SmmStore.h b/UefiPayloadPkg/Library/SmmStoreLib/SmmStore.h new file mode 100644 index 0000000000..d91b8e13c6 --- /dev/null +++ b/UefiPayloadPkg/Library/SmmStoreLib/SmmStore.h @@ -0,0 +1,82 @@ +/** @file SmmStore.h + + Copyright (c) 2022, 9elements GmbH
+ + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#ifndef COREBOOT_SMMSTORE_H_ +#define COREBOOT_SMMSTORE_H_ + +#define SMMSTORE_RET_SUCCESS 0 +#define SMMSTORE_RET_FAILURE 1 +#define SMMSTORE_RET_UNSUPPORTED 2 + +/* Version 2 only */ +#define SMMSTORE_CMD_INIT 4 +#define SMMSTORE_CMD_RAW_READ 5 +#define SMMSTORE_CMD_RAW_WRITE 6 +#define SMMSTORE_CMD_RAW_CLEAR 7 + +/* + * This allows the payload to store raw data in the flash regions. + * This can be used by a FaultTolerantWrite implementation, that uses at least + * two regions in an A/B update scheme. + */ + +#pragma pack(1) + +/* + * Reads a chunk of raw data with size BufSize from the block specified by + * block_id starting at BufOffset. + * The read data is placed in buf. + * + * block_id must be less than num_blocks + * BufOffset + BufSize must be less than block_size + */ +typedef struct { + UINT32 BufSize; + UINT32 BufOffset; + UINT32 BlockId; +} SMM_STORE_PARAMS_WRITE; + +/* + * Writes a chunk of raw data with size BufSize to the block specified by + * block_id starting at BufOffset. + * + * block_id must be less than num_blocks + * BufOffset + BufSize must be less than block_size + */ +typedef struct { + UINT32 BufSize; + UINT32 BufOffset; + UINT32 BlockId; +} SMM_STORE_PARAMS_READ; + +/* + * Erases the specified block. + * + * block_id must be less than num_blocks + */ +typedef struct { + UINT32 BlockId; +} SMM_STORE_PARAMS_CLEAR; + +typedef union { + SMM_STORE_PARAMS_WRITE Write; + SMM_STORE_PARAMS_READ Read; + SMM_STORE_PARAMS_CLEAR Clear; +} SMM_STORE_COM_BUF; + +#pragma pack(0) + +UINTN +EFIAPI +TriggerSmi ( + IN UINTN Cmd, + IN UINTN Arg, + IN UINTN Retry + ); + +#endif // COREBOOT_SMMSTORE_H_ diff --git a/UefiPayloadPkg/Library/SmmStoreLib/SmmStoreLib.inf b/UefiPayloadPkg/Library/SmmStoreLib/SmmStoreLib.inf new file mode 100644 index 0000000000..8111100f46 --- /dev/null +++ b/UefiPayloadPkg/Library/SmmStoreLib/SmmStoreLib.inf @@ -0,0 +1,40 @@ +## @file +# SmmStore library for coreboot +# +# Copyright (c) 2022 9elements GmbH.
+# +# SPDX-License-Identifier: BSD-2-Clause-Patent +# +## + +[Defines] + INF_VERSION = 0x00010005 + BASE_NAME = SmmStoreLib + FILE_GUID = 40A2CBC6-CFB8-447b-A90E-298E88FD345E + MODULE_TYPE = DXE_DRIVER + VERSION_STRING = 1.0 + LIBRARY_CLASS = SmmStoreLib + +[Sources] + SmmStore.c + SmmStore.h + +[Sources.X64] + X64/SmmStore.nasm + +[LibraryClasses] + BaseMemoryLib + DebugLib + DxeServicesTableLib + HobLib + MemoryAllocationLib + UefiBootServicesTableLib + +[Guids] + gEfiSmmStoreInfoHobGuid ## CONSUMES + gEfiEventVirtualAddressChangeGuid ## CONSUMES + +[Packages] + MdePkg/MdePkg.dec + MdeModulePkg/MdeModulePkg.dec + UefiPayloadPkg/UefiPayloadPkg.dec diff --git a/UefiPayloadPkg/Library/SmmStoreLib/X64/SmmStore.nasm b/UefiPayloadPkg/Library/SmmStoreLib/X64/SmmStore.nasm new file mode 100644 index 0000000000..ee20c1fae8 --- /dev/null +++ b/UefiPayloadPkg/Library/SmmStoreLib/X64/SmmStore.nasm @@ -0,0 +1,48 @@ +;------------------------------------------------------------------------------ ; +; Copyright (c) 2022, 9elements GmbH. All rights reserved.
+; SPDX-License-Identifier: BSD-2-Clause-Patent +; +;------------------------------------------------------------------------------- + +%include "Nasm.inc" + +DEFAULT REL +SECTION .text + +;UINTN +;EFIAPI +;TriggerSmi ( +; UINTN Cmd, +; UINTN Arg, +; UINTN Retry +; ) + +global ASM_PFX(TriggerSmi) +ASM_PFX(TriggerSmi): + push rbx + mov rax, rcx ; Smi handler expect Cmd in RAX + mov rbx, rdx ; Smi handler expect Argument in RBX +@Trigger: + out 0b2h, al ; write to APM port to trigger SMI + +; There might be a delay between writing the Smi trigger register and +; entering SMM, in which case the Smi handler will do nothing as only +; synchronous Smis are handled. In addition when there's no Smi handler +; or the SmmStore feature isn't compiled in, no register will be modified. + +; As there's no livesign from SMM, just wait a bit for the handler to fire, +; and then try again. + + cmp rax, rcx ; Check if rax was modified by SMM + jne @Return ; SMM modified rax, return now + push rcx ; save rcx to stack + mov rcx, 10000 + rep pause ; add a small delay + pop rcx ; restore rcx + cmp r8, 0 + je @Return + dec r8 + jmp @Trigger +@Return: + pop rbx + ret diff --git a/UefiPayloadPkg/UefiPayloadPkg.dsc b/UefiPayloadPkg/UefiPayloadPkg.dsc index 7cb5f83dd1..80e738dc66 100644 --- a/UefiPayloadPkg/UefiPayloadPkg.dsc +++ b/UefiPayloadPkg/UefiPayloadPkg.dsc @@ -341,6 +341,8 @@ AuthVariableLib|MdeModulePkg/Library/AuthVariableLibNull/AuthVariableLibNull.inf !endif + SmmStoreLib|UefiPayloadPkg/Library/SmmStoreLib/SmmStoreLib.inf + !if $(VARIABLE_SUPPORT) == "EMU" TpmMeasurementLib|MdeModulePkg/Library/TpmMeasurementLibNull/TpmMeasurementLibNull.inf !elseif $(VARIABLE_SUPPORT) == "SPI"