/** @file
This file contains the implementation for a Platform Runtime Mechanism (PRM) configuration driver.
Copyright (c) Microsoft Corporation
Copyright (c) 2020, Intel Corporation. All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define _DBGMSGID_ "[PRMCONFIG]"
STATIC UINTN mMaxRuntimeMmioRangeCount;
GLOBAL_REMOVE_IF_UNREFERENCED STATIC PRM_RUNTIME_MMIO_RANGES **mRuntimeMmioRanges;
/**
Converts the runtime memory range physical addresses to virtual addresses.
@param[in] RuntimeMmioRanges A pointer to a PRM_RUNTIME_MMIO_RANGES buffer.
**/
VOID
ConvertRuntimeMemoryRangeAddresses (
IN PRM_RUNTIME_MMIO_RANGES *RuntimeMmioRanges
)
{
UINTN Index;
if ((RuntimeMmioRanges == NULL) || (RuntimeMmioRanges->Count == 0)) {
return;
}
for (Index = 0; Index < (UINTN)RuntimeMmioRanges->Count; Index++) {
RuntimeMmioRanges->Range[Index].VirtualBaseAddress = RuntimeMmioRanges->Range[Index].PhysicalBaseAddress;
gRT->ConvertPointer (0x0, (VOID **)&(RuntimeMmioRanges->Range[Index].VirtualBaseAddress));
}
}
/**
Sets the runtime memory range attributes.
The EFI_MEMORY_RUNTIME attribute is set for each PRM_RUNTIME_MMIO_RANGE present
in the buffer provided.
@param[in] RuntimeMmioRanges A pointer to a PRM_RUNTIME_MMIO_RANGES buffer.
**/
VOID
SetRuntimeMemoryRangeAttributes (
IN PRM_RUNTIME_MMIO_RANGES *RuntimeMmioRanges
)
{
EFI_STATUS Status;
EFI_STATUS Status2;
UINTN Index;
EFI_GCD_MEMORY_SPACE_DESCRIPTOR Descriptor;
DEBUG ((DEBUG_INFO, "%a %a - Entry.\n", _DBGMSGID_, __func__));
if ((RuntimeMmioRanges == NULL) || (RuntimeMmioRanges->Count == 0)) {
return;
}
for (Index = 0; Index < (UINTN)RuntimeMmioRanges->Count; Index++) {
DEBUG ((
DEBUG_INFO,
" %a %a: Runtime MMIO Range [%d].\n",
_DBGMSGID_,
__func__,
Index
));
DEBUG ((
DEBUG_INFO,
" %a %a: Physical address = 0x%016x. Length = 0x%x.\n",
_DBGMSGID_,
__func__,
RuntimeMmioRanges->Range[Index].PhysicalBaseAddress,
RuntimeMmioRanges->Range[Index].Length
));
// Runtime memory ranges should cover ranges on a page boundary
ASSERT ((RuntimeMmioRanges->Range[Index].PhysicalBaseAddress & EFI_PAGE_MASK) == 0);
ASSERT ((RuntimeMmioRanges->Range[Index].Length & EFI_PAGE_MASK) == 0);
Status2 = EFI_NOT_FOUND;
Status = gDS->GetMemorySpaceDescriptor (RuntimeMmioRanges->Range[Index].PhysicalBaseAddress, &Descriptor);
if (!EFI_ERROR (Status) &&
(
((Descriptor.GcdMemoryType != EfiGcdMemoryTypeMemoryMappedIo) && (Descriptor.GcdMemoryType != EfiGcdMemoryTypeReserved)) ||
((Descriptor.Length & EFI_PAGE_MASK) != 0)
)
)
{
Status2 = gDS->RemoveMemorySpace (
RuntimeMmioRanges->Range[Index].PhysicalBaseAddress,
Descriptor.Length
);
}
if ((Status == EFI_NOT_FOUND) || !EFI_ERROR (Status2)) {
Status = gDS->AddMemorySpace (
EfiGcdMemoryTypeMemoryMappedIo,
RuntimeMmioRanges->Range[Index].PhysicalBaseAddress,
(UINT64)RuntimeMmioRanges->Range[Index].Length,
EFI_MEMORY_UC | EFI_MEMORY_RUNTIME
);
ASSERT_EFI_ERROR (Status);
Status = gDS->AllocateMemorySpace (
EfiGcdAllocateAddress,
EfiGcdMemoryTypeMemoryMappedIo,
0,
(UINT64)RuntimeMmioRanges->Range[Index].Length,
&RuntimeMmioRanges->Range[Index].PhysicalBaseAddress,
gImageHandle,
NULL
);
ASSERT_EFI_ERROR (Status);
}
Status = gDS->GetMemorySpaceDescriptor (RuntimeMmioRanges->Range[Index].PhysicalBaseAddress, &Descriptor);
ASSERT_EFI_ERROR (Status);
if (EFI_ERROR (Status)) {
DEBUG ((
DEBUG_ERROR,
" %a %a: Error [%r] finding descriptor for runtime memory range 0x%016x.\n",
_DBGMSGID_,
__func__,
Status,
RuntimeMmioRanges->Range[Index].PhysicalBaseAddress
));
continue;
}
if ((Descriptor.Attributes & EFI_MEMORY_RUNTIME) != 0) {
continue;
}
Status = gDS->SetMemorySpaceAttributes (
RuntimeMmioRanges->Range[Index].PhysicalBaseAddress,
(UINT64)RuntimeMmioRanges->Range[Index].Length,
Descriptor.Attributes | EFI_MEMORY_RUNTIME
);
ASSERT_EFI_ERROR (Status);
if (EFI_ERROR (Status)) {
DEBUG ((
DEBUG_ERROR,
" %a %a: Error [%r] setting descriptor for runtime memory range 0x%016x.\n",
_DBGMSGID_,
__func__,
Status,
RuntimeMmioRanges->Range[Index].PhysicalBaseAddress
));
} else {
DEBUG ((DEBUG_INFO, " %a %a: Successfully set runtime attribute for the MMIO range.\n", _DBGMSGID_, __func__));
}
}
}
/**
Stores pointers or pointer to resources that should be converted in the virtual address change event.
**/
VOID
StoreVirtualMemoryAddressChangePointers (
VOID
)
{
EFI_STATUS Status;
UINTN HandleCount;
UINTN HandleIndex;
UINTN RangeIndex;
EFI_HANDLE *HandleBuffer;
PRM_CONFIG_PROTOCOL *PrmConfigProtocol;
DEBUG ((DEBUG_INFO, "%a %a - Entry.\n", _DBGMSGID_, __func__));
RangeIndex = 0;
mRuntimeMmioRanges = AllocateRuntimeZeroPool (sizeof (*mRuntimeMmioRanges) * mMaxRuntimeMmioRangeCount);
if ((mRuntimeMmioRanges == NULL) && (mMaxRuntimeMmioRangeCount > 0)) {
DEBUG ((
DEBUG_ERROR,
" %a %a: Memory allocation for runtime MMIO pointer array failed.\n",
_DBGMSGID_,
__func__
));
ASSERT (FALSE);
return;
}
HandleBuffer = NULL;
Status = gBS->LocateHandleBuffer (
ByProtocol,
&gPrmConfigProtocolGuid,
NULL,
&HandleCount,
&HandleBuffer
);
if (!EFI_ERROR (Status)) {
for (HandleIndex = 0; HandleIndex < HandleCount; HandleIndex++) {
Status = gBS->HandleProtocol (
HandleBuffer[HandleIndex],
&gPrmConfigProtocolGuid,
(VOID **)&PrmConfigProtocol
);
ASSERT_EFI_ERROR (Status);
if (EFI_ERROR (Status) || (PrmConfigProtocol == NULL)) {
continue;
}
if (PrmConfigProtocol->ModuleContextBuffers.RuntimeMmioRanges != NULL) {
if (RangeIndex >= mMaxRuntimeMmioRangeCount) {
Status = EFI_BUFFER_TOO_SMALL;
DEBUG ((
DEBUG_ERROR,
" %a %a: Index out of bounds - Actual count (%d) of runtime MMIO ranges exceeds maximum count (%d).\n",
_DBGMSGID_,
__func__,
RangeIndex + 1,
mMaxRuntimeMmioRangeCount
));
ASSERT_EFI_ERROR (Status);
return;
}
mRuntimeMmioRanges[RangeIndex++] = PrmConfigProtocol->ModuleContextBuffers.RuntimeMmioRanges;
}
}
DEBUG ((
DEBUG_INFO,
" %a %a: %d MMIO ranges buffers saved for future virtual memory conversion.\n",
_DBGMSGID_,
__func__,
RangeIndex
));
}
}
/**
Validates a data buffer for a PRM module.
Verifies the buffer header signature is valid and the length meets the minimum size.
@param[in] PrmDataBuffer A pointer to the data buffer for this PRM module.
@retval EFI_SUCCESS The data buffer was validated successfully.
@retval EFI_INVALID_PARAMETER The pointer given for PrmDataBuffer is NULL.
@retval EFI_NOT_FOUND The data buffer signature is not valid.
@retval EFI_BUFFER_TOO_SMALL The buffer size is too small.
**/
EFI_STATUS
ValidatePrmDataBuffer (
IN CONST PRM_DATA_BUFFER *PrmDataBuffer
)
{
if (PrmDataBuffer == NULL) {
return EFI_INVALID_PARAMETER;
}
if (PrmDataBuffer->Header.Signature != PRM_DATA_BUFFER_HEADER_SIGNATURE) {
DEBUG ((DEBUG_ERROR, " %a %a: The PRM data buffer signature is invalid. PRM module.\n", _DBGMSGID_, __func__));
return EFI_NOT_FOUND;
}
if (PrmDataBuffer->Header.Length < sizeof (PRM_DATA_BUFFER_HEADER)) {
DEBUG ((DEBUG_ERROR, " %a %a: The PRM data buffer length is invalid.\n", _DBGMSGID_, __func__));
return EFI_BUFFER_TOO_SMALL;
}
return EFI_SUCCESS;
}
/**
Validates a PRM context buffer.
Verifies the buffer header signature is valid and the GUID is set to a non-zero value.
@param[in] PrmContextBuffer A pointer to the context buffer for this PRM handler.
@retval EFI_SUCCESS The context buffer was validated successfully.
@retval EFI_INVALID_PARAMETER The pointer given for ContextBuffer is NULL.
@retval EFI_NOT_FOUND The proper value for a field was not found.
**/
EFI_STATUS
ValidatePrmContextBuffer (
IN CONST PRM_CONTEXT_BUFFER *PrmContextBuffer
)
{
if (PrmContextBuffer == NULL) {
return EFI_INVALID_PARAMETER;
}
if (PrmContextBuffer->Signature != PRM_CONTEXT_BUFFER_SIGNATURE) {
DEBUG ((DEBUG_ERROR, " %a %a: The PRM context buffer signature is invalid.\n", _DBGMSGID_, __func__));
return EFI_NOT_FOUND;
}
if (IsZeroGuid (&PrmContextBuffer->HandlerGuid)) {
DEBUG ((DEBUG_ERROR, " %a %a: The PRM context buffer GUID is zero.\n", _DBGMSGID_, __func__));
return EFI_NOT_FOUND;
}
if ((PrmContextBuffer->StaticDataBuffer != NULL) && EFI_ERROR (ValidatePrmDataBuffer (PrmContextBuffer->StaticDataBuffer))) {
DEBUG ((
DEBUG_ERROR,
" %a %a: Error in static buffer for PRM handler %g.\n",
_DBGMSGID_,
__func__,
&PrmContextBuffer->HandlerGuid
));
return EFI_NOT_FOUND;
}
return EFI_SUCCESS;
}
/**
Notification function of EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE.
This is notification function converts any registered PRM_RUNTIME_MMIO_RANGE
addresses to a virtual address.
@param[in] Event Event whose notification function is being invoked.
@param[in] Context Pointer to the notification function's context.
**/
VOID
EFIAPI
PrmConfigVirtualAddressChangeEvent (
IN EFI_EVENT Event,
IN VOID *Context
)
{
UINTN Index;
//
// Convert runtime MMIO ranges
//
for (Index = 0; Index < mMaxRuntimeMmioRangeCount; Index++) {
ConvertRuntimeMemoryRangeAddresses (mRuntimeMmioRanges[Index]);
}
}
/**
The PRM Config END_OF_DXE protocol notification event handler.
Finds all of the PRM_CONFIG_PROTOCOL instances installed at end of DXE and
marks all PRM_RUNTIME_MMIO_RANGE entries as EFI_MEMORY_RUNTIME.
@param[in] Event Event whose notification function is being invoked.
@param[in] Context The pointer to the notification function's context,
which is implementation-dependent.
**/
VOID
EFIAPI
PrmConfigEndOfDxeNotification (
IN EFI_EVENT Event,
IN VOID *Context
)
{
EFI_STATUS Status;
UINTN HandleCount;
UINTN BufferIndex;
UINTN HandleIndex;
EFI_HANDLE *HandleBuffer;
PRM_CONTEXT_BUFFER *CurrentContextBuffer;
PRM_CONFIG_PROTOCOL *PrmConfigProtocol;
DEBUG ((DEBUG_INFO, "%a %a - Entry.\n", _DBGMSGID_, __func__));
HandleBuffer = NULL;
Status = gBS->LocateHandleBuffer (
ByProtocol,
&gPrmConfigProtocolGuid,
NULL,
&HandleCount,
&HandleBuffer
);
if (!EFI_ERROR (Status)) {
for (HandleIndex = 0; HandleIndex < HandleCount; HandleIndex++) {
Status = gBS->HandleProtocol (
HandleBuffer[HandleIndex],
&gPrmConfigProtocolGuid,
(VOID **)&PrmConfigProtocol
);
ASSERT_EFI_ERROR (Status);
if (EFI_ERROR (Status) || (PrmConfigProtocol == NULL)) {
continue;
}
DEBUG ((
DEBUG_INFO,
" %a %a: Found PRM configuration protocol for PRM module %g.\n",
_DBGMSGID_,
__func__,
&PrmConfigProtocol->ModuleContextBuffers.ModuleGuid
));
DEBUG ((DEBUG_INFO, " %a %a: Validating module context buffers...\n", _DBGMSGID_, __func__));
for (BufferIndex = 0; BufferIndex < PrmConfigProtocol->ModuleContextBuffers.BufferCount; BufferIndex++) {
CurrentContextBuffer = &(PrmConfigProtocol->ModuleContextBuffers.Buffer[BufferIndex]);
Status = ValidatePrmContextBuffer (CurrentContextBuffer);
if (EFI_ERROR (Status)) {
DEBUG ((
DEBUG_ERROR,
" %a %a: Context buffer validation failed for PRM handler %g.\n",
_DBGMSGID_,
__func__,
CurrentContextBuffer->HandlerGuid
));
}
}
DEBUG ((DEBUG_INFO, " %a %a: Module context buffer validation complete.\n", _DBGMSGID_, __func__));
if (PrmConfigProtocol->ModuleContextBuffers.RuntimeMmioRanges != NULL) {
DEBUG ((
DEBUG_INFO,
" %a %a: Found %d PRM runtime MMIO ranges.\n",
_DBGMSGID_,
__func__,
PrmConfigProtocol->ModuleContextBuffers.RuntimeMmioRanges->Count
));
SetRuntimeMemoryRangeAttributes (PrmConfigProtocol->ModuleContextBuffers.RuntimeMmioRanges);
mMaxRuntimeMmioRangeCount++;
}
}
StoreVirtualMemoryAddressChangePointers ();
}
if (HandleBuffer != NULL) {
gBS->FreePool (HandleBuffer);
}
gBS->CloseEvent (Event);
}
/**
The entry point for this module.
@param[in] ImageHandle The firmware allocated handle for the EFI image.
@param[in] SystemTable A pointer to the EFI System Table.
@retval EFI_SUCCESS The entry point is executed successfully.
@retval Others An error occurred when executing this entry point.
**/
EFI_STATUS
EFIAPI
PrmConfigEntryPoint (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status;
EFI_EVENT Event;
DEBUG ((DEBUG_INFO, "%a %a - Entry.\n", _DBGMSGID_, __func__));
//
// Register a notification function to change memory attributes at end of DXE
//
Event = NULL;
Status = gBS->CreateEventEx (
EVT_NOTIFY_SIGNAL,
TPL_CALLBACK,
PrmConfigEndOfDxeNotification,
NULL,
&gEfiEndOfDxeEventGroupGuid,
&Event
);
ASSERT_EFI_ERROR (Status);
//
// Register a notification function for virtual address change
//
Event = NULL;
Status = gBS->CreateEventEx (
EVT_NOTIFY_SIGNAL,
TPL_NOTIFY,
PrmConfigVirtualAddressChangeEvent,
NULL,
&gEfiEventVirtualAddressChangeGuid,
&Event
);
ASSERT_EFI_ERROR (Status);
return EFI_SUCCESS;
}