| /** @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; | |
| } |