/** @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
#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;
STATIC EFI_EVENT mSmmStoreLibVirtualAddrChangeEvent;
/**
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 suppored.
@retval EFI_DEVICE_ERROR An error occured 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 call in virtual mode.
Call the passed in Child Notify event and convert any pointers in
lib to virtual mode.
@param[in] Event The Event that is being processed
@param[in] Context Event Context
**/
STATIC
VOID
EFIAPI
SmmStoreLibVirtualNotifyEvent (
IN EFI_EVENT Event,
IN VOID *Context
)
{
EfiConvertPointer (0x0, (VOID **)&mArgComBuf);
if (mSmmStoreInfo != NULL) {
EfiConvertPointer (0x0, (VOID **)&mSmmStoreInfo->ComBuffer);
EfiConvertPointer (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", __FUNCTION__));
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;
//
// Register for the virtual address change event
//
Status = gBS->CreateEventEx (
EVT_NOTIFY_SIGNAL,
TPL_NOTIFY,
SmmStoreLibVirtualNotifyEvent,
NULL,
&gEfiEventVirtualAddressChangeGuid,
&mSmmStoreLibVirtualAddrChangeEvent
);
ASSERT_EFI_ERROR (Status);
//
// 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",
__FUNCTION__
)
);
//
// 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",
__FUNCTION__
)
);
//
// 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;
}
/**
Denitializes SmmStore support by freeing allocated memory and unregistering
the virtual address change event.
**/
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;
}
if (mSmmStoreLibVirtualAddrChangeEvent != NULL) {
gBS->CloseEvent (mSmmStoreLibVirtualAddrChangeEvent);
mSmmStoreLibVirtualAddrChangeEvent = NULL;
}
}