blob: addcf76a83e7a97bc88331dba39ee7ec5d78fecf [file]
/** @file
*
* Copyright (c) 2013, ARM Limited. All rights reserved.
* Copyright (c) 2017, Intel Corporation. All rights reserved.<BR>
*
* SPDX-License-Identifier: BSD-2-Clause-Patent
*
**/
#include "CpuDxe.h"
/**
Searches memory descriptors covered by given memory range.
This function searches into the Gcd Memory Space for descriptors
(from StartIndex to EndIndex) that contains the memory range
specified by BaseAddress and Length.
@param MemorySpaceMap Gcd Memory Space Map as array.
@param NumberOfDescriptors Number of descriptors in map.
@param BaseAddress BaseAddress for the requested range.
@param Length Length for the requested range.
@param StartIndex Start index into the Gcd Memory Space Map.
@param EndIndex End index into the Gcd Memory Space Map.
@retval EFI_SUCCESS Search successfully.
@retval EFI_NOT_FOUND The requested descriptors does not exist.
**/
STATIC
EFI_STATUS
SearchGcdMemorySpaces (
IN EFI_GCD_MEMORY_SPACE_DESCRIPTOR *MemorySpaceMap,
IN UINTN NumberOfDescriptors,
IN EFI_PHYSICAL_ADDRESS BaseAddress,
IN UINT64 Length,
OUT UINTN *StartIndex,
OUT UINTN *EndIndex
)
{
UINTN Index;
*StartIndex = 0;
*EndIndex = 0;
for (Index = 0; Index < NumberOfDescriptors; Index++) {
if ((BaseAddress >= MemorySpaceMap[Index].BaseAddress) &&
(BaseAddress < (MemorySpaceMap[Index].BaseAddress + MemorySpaceMap[Index].Length)))
{
*StartIndex = Index;
}
if (((BaseAddress + Length - 1) >= MemorySpaceMap[Index].BaseAddress) &&
((BaseAddress + Length - 1) < (MemorySpaceMap[Index].BaseAddress + MemorySpaceMap[Index].Length)))
{
*EndIndex = Index;
return EFI_SUCCESS;
}
}
return EFI_NOT_FOUND;
}
/**
Sets the attributes for a specified range in Gcd Memory Space Map.
This function sets the attributes for a specified range in
Gcd Memory Space Map.
@param MemorySpaceMap Gcd Memory Space Map as array
@param NumberOfDescriptors Number of descriptors in map
@param BaseAddress BaseAddress for the range
@param Length Length for the range
@param Attributes Attributes to set
@retval EFI_SUCCESS Memory attributes set successfully
@retval EFI_NOT_FOUND The specified range does not exist in Gcd Memory Space
**/
EFI_STATUS
SetGcdMemorySpaceAttributes (
IN EFI_GCD_MEMORY_SPACE_DESCRIPTOR *MemorySpaceMap,
IN UINTN NumberOfDescriptors,
IN EFI_PHYSICAL_ADDRESS BaseAddress,
IN UINT64 Length,
IN UINT64 Attributes
)
{
EFI_STATUS Status;
UINTN Index;
UINTN StartIndex;
UINTN EndIndex;
EFI_PHYSICAL_ADDRESS RegionStart;
UINT64 RegionLength;
UINT64 Capabilities;
DEBUG ((
DEBUG_GCD,
"SetGcdMemorySpaceAttributes[0x%lX; 0x%lX] = 0x%lX\n",
BaseAddress,
BaseAddress + Length,
Attributes
));
// We do not support a smaller granularity than 4KB on ARM Architecture
if ((Length & EFI_PAGE_MASK) != 0) {
DEBUG ((
DEBUG_WARN,
"Warning: We do not support smaller granularity than 4KB on ARM Architecture (passed length: 0x%lX).\n",
Length
));
}
//
// Get all memory descriptors covered by the memory range
//
Status = SearchGcdMemorySpaces (
MemorySpaceMap,
NumberOfDescriptors,
BaseAddress,
Length,
&StartIndex,
&EndIndex
);
if (EFI_ERROR (Status)) {
return Status;
}
//
// Go through all related descriptors and set attributes accordingly
//
for (Index = StartIndex; Index <= EndIndex; Index++) {
if (MemorySpaceMap[Index].GcdMemoryType == EfiGcdMemoryTypeNonExistent) {
continue;
}
//
// Calculate the start and end address of the overlapping range
//
if (BaseAddress >= MemorySpaceMap[Index].BaseAddress) {
RegionStart = BaseAddress;
} else {
RegionStart = MemorySpaceMap[Index].BaseAddress;
}
if ((BaseAddress + Length - 1) < (MemorySpaceMap[Index].BaseAddress + MemorySpaceMap[Index].Length)) {
RegionLength = BaseAddress + Length - RegionStart;
} else {
RegionLength = MemorySpaceMap[Index].BaseAddress + MemorySpaceMap[Index].Length - RegionStart;
}
// Always add RO, RP, and XP, as all memory is capable of supporting these types (they are software constructs,
// not hardware features) and they are critical to maintaining a security boundary.
Capabilities = MemorySpaceMap[Index].Capabilities | EFI_MEMORY_RO | EFI_MEMORY_RP | EFI_MEMORY_XP;
// Update GCD capabilities as these may have changed in the page table from the original GCD setting
// this follows the same pattern as x86 GCD and Page Table syncing
Status = gDS->SetMemorySpaceCapabilities (
RegionStart,
RegionLength,
Capabilities
);
if (EFI_ERROR (Status)) {
DEBUG ((
DEBUG_ERROR,
"%a - failed to update GCD capabilities: 0x%llx on memory region: 0x%llx length: 0x%llx Status: %r\n",
__func__,
Capabilities,
RegionStart,
RegionLength,
Status
));
// If we fail to set capabilities, we should assert as this is a GCD internal error, but follow the previous
// behavior and try to set the attributes (which may or may not fail)
ASSERT_EFI_ERROR (Status);
}
//
// Set memory attributes according to page table attributes and the original attributes of descriptor
//
Status = gDS->SetMemorySpaceAttributes (
RegionStart,
RegionLength,
(MemorySpaceMap[Index].Attributes & ~EFI_MEMORY_CACHETYPE_MASK) | (Attributes & Capabilities)
);
if (EFI_ERROR (Status)) {
DEBUG ((
DEBUG_ERROR,
"%a - failed to update GCD attributes: 0x%llx on memory region: 0x%llx length: 0x%llx Status: %r\n",
__func__,
Attributes,
RegionStart,
RegionLength,
Status
));
ASSERT_EFI_ERROR (Status);
}
}
return EFI_SUCCESS;
}
/**
Checks if the specified ARM translation table attributes are cacheable memory.
@param[in] Attributes ARM memory attributes.
@retval TRUE if the attributes are cacheable, FALSE otherwise.
**/
STATIC
BOOLEAN
IsArmAttributeCacheable (
IN UINT64 Attributes
)
{
return ((Attributes & TT_ATTR_INDX_MASK) == TT_ATTR_INDX_MEMORY_WRITE_THROUGH) ||
((Attributes & TT_ATTR_INDX_MASK) == TT_ATTR_INDX_MEMORY_WRITE_BACK);
}
/**
This function modifies the attributes for the memory region specified by BaseAddress and
Length from their current attributes to the attributes specified by Attributes.
@param This The EFI_CPU_ARCH_PROTOCOL instance.
@param BaseAddress The physical address that is the start address of a memory region.
@param Length The size in bytes of the memory region.
@param Attributes The bit mask of attributes to set for the memory region.
@retval EFI_SUCCESS The attributes were set for the memory region.
@retval EFI_ACCESS_DENIED The attributes for the memory resource range specified by
BaseAddress and Length cannot be modified.
@retval EFI_INVALID_PARAMETER Length is zero.
@retval EFI_OUT_OF_RESOURCES There are not enough system resources to modify the attributes of
the memory resource range.
@retval EFI_UNSUPPORTED The processor does not support one or more bytes of the memory
resource range specified by BaseAddress and Length.
The bit mask of attributes is not support for the memory resource
range specified by BaseAddress and Length.
**/
EFI_STATUS
EFIAPI
CpuSetMemoryAttributes (
IN EFI_CPU_ARCH_PROTOCOL *This,
IN EFI_PHYSICAL_ADDRESS BaseAddress,
IN UINT64 Length,
IN UINT64 EfiAttributes
)
{
EFI_STATUS Status;
UINTN ArmAttributes;
UINTN RegionBaseAddress;
UINTN RegionLength;
UINTN RegionArmAttributes;
BOOLEAN FlushCache;
if (mIsFlushingGCD) {
return EFI_SUCCESS;
}
if (!IS_ALIGNED (BaseAddress, SIZE_4KB)) {
// Minimum granularity is SIZE_4KB (4KB on ARM)
DEBUG ((DEBUG_PAGE, "CpuSetMemoryAttributes(%lx, %lx, %lx): Minimum granularity is SIZE_4KB\n", BaseAddress, Length, EfiAttributes));
return EFI_UNSUPPORTED;
}
// Convert the 'Attribute' into ARM Attribute
ArmAttributes = EfiAttributeToArmAttribute (EfiAttributes);
// Get the region starting from 'BaseAddress' and its 'Attribute'
RegionBaseAddress = BaseAddress;
Status = GetMemoryRegion (&RegionBaseAddress, &RegionLength, &RegionArmAttributes);
// Data & Instruction Caches are flushed when we set new memory attributes.
// So, we only set the attributes if the new region is different.
if (EFI_ERROR (Status) || (RegionArmAttributes != ArmAttributes) ||
((BaseAddress + Length) > (RegionBaseAddress + RegionLength)))
{
//
// If the region was previously mapped as a cacheable normal memory,
// and is changed to device or non-cacheable memory, ensure any
// stale cache lines are written back and invalidated. If this is not done,
// depending on the caching behavior of the platform, dirty cache lines may
// be written back corrupting data in the future, or stale cache lines may
// persist if caching is enabled later. In scenarios where the caller didn't
// explicitly change the cacheability, ignore this. This could still lead to
// unexpected caches, but this is a necessary concession to incorrect behavior
// in higher-level components.
//
FlushCache = FALSE;
if (!EFI_ERROR (Status) &&
((EfiAttributes & EFI_MEMORY_RP) == 0) &&
((EfiAttributes & EFI_MEMORY_CACHETYPE_MASK) != 0) &&
IsArmAttributeCacheable (RegionArmAttributes) &&
!IsArmAttributeCacheable (ArmAttributes))
{
FlushCache = TRUE;
}
Status = ArmSetMemoryAttributes (BaseAddress, Length, EfiAttributes, 0);
if (EFI_ERROR (Status)) {
return Status;
}
if (FlushCache) {
//
// A data barrier is required to ensure that all page updates are complete and the TLB is flushed prior to the
// cache invalidation to avoid any unexpected cache lines being created.
//
ArmDataSynchronizationBarrier ();
WriteBackInvalidateDataCacheRange ((VOID *)(UINTN)BaseAddress, (UINTN)Length);
}
}
return EFI_SUCCESS;
}