/** @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.<BR> | |
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/MemoryAllocationLib.h> | |
#include <Library/UefiBootServicesTableLib.h> | |
#include <Library/UefiRuntimeServicesTableLib.h> | |
#include <Library/UefiLib.h> | |
#include <PiDxe.h> | |
#include <PrmContextBuffer.h> | |
#include <PrmDataBuffer.h> | |
#include <PrmMmio.h> | |
#include <Protocol/PrmConfig.h> | |
#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; | |
} |