/*++
Copyright (c) 2009, Hewlett-Packard Company. All rights reserved.
Portions copyright (c) 2010, Apple Inc. All rights reserved.
Portions copyright (c) 2011-2021, Arm Limited. All rights reserved.
Copyright (c) 2017, Intel Corporation. All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent
--*/
#include
#include "CpuDxe.h"
#define INVALID_ENTRY ((UINT64)~0)
#define MIN_T0SZ 16
#define BITS_PER_LEVEL 9
/**
Parses T0SZ to determine the level and number of entries at the root
of the translation table.
@param T0SZ The T0SZ value to be parsed.
@param RootTableLevel The level of the root table.
@param RootTableEntryCount The number of entries in the root table.
**/
STATIC
VOID
GetRootTranslationTableInfo (
IN UINTN T0SZ,
OUT UINTN *RootTableLevel,
OUT UINTN *RootTableEntryCount
)
{
*RootTableLevel = (T0SZ - MIN_T0SZ) / BITS_PER_LEVEL;
*RootTableEntryCount = TT_ENTRY_COUNT >> (T0SZ - MIN_T0SZ) % BITS_PER_LEVEL;
}
/**
Converts ARM translation table attributes to GCD attributes.
@param PageAttributes The translation table attributes to be converted.
@retval The analogous GCD attributes.
**/
STATIC
UINT64
PageAttributeToGcdAttribute (
IN UINT64 PageAttributes
)
{
UINT64 GcdAttributes;
switch (PageAttributes & TT_ATTR_INDX_MASK) {
case TT_ATTR_INDX_DEVICE_MEMORY:
GcdAttributes = EFI_MEMORY_UC;
break;
case TT_ATTR_INDX_MEMORY_NON_CACHEABLE:
GcdAttributes = EFI_MEMORY_WC;
break;
case TT_ATTR_INDX_MEMORY_WRITE_THROUGH:
GcdAttributes = EFI_MEMORY_WT;
break;
case TT_ATTR_INDX_MEMORY_WRITE_BACK:
GcdAttributes = EFI_MEMORY_WB;
break;
default:
DEBUG ((
DEBUG_ERROR,
"PageAttributeToGcdAttribute: PageAttributes:0x%lX not supported.\n",
PageAttributes
));
ASSERT (0);
// The Global Coherency Domain (GCD) value is defined as a bit set.
// Returning 0 means no attribute has been set.
GcdAttributes = 0;
}
// Determine protection attributes
if ((PageAttributes & TT_AF) == 0) {
GcdAttributes |= EFI_MEMORY_RP;
}
if (((PageAttributes & TT_AP_MASK) == TT_AP_NO_RO) ||
((PageAttributes & TT_AP_MASK) == TT_AP_RO_RO))
{
// Read only cases map to write-protect
GcdAttributes |= EFI_MEMORY_RO;
}
// Process eXecute Never attribute
if ((PageAttributes & (TT_PXN_MASK | TT_UXN_MASK)) != 0) {
GcdAttributes |= EFI_MEMORY_XP;
}
return GcdAttributes;
}
/**
Convert an arch specific set of page attributes into a mask
of EFI_MEMORY_xx constants.
@param PageAttributes The set of page attributes.
@retval The mask of EFI_MEMORY_xx constants.
**/
UINT64
RegionAttributeToGcdAttribute (
IN UINTN PageAttributes
)
{
return PageAttributeToGcdAttribute (PageAttributes);
}
/**
Retrieves the attribute of the first page entry in the translation table.
@param[in] FirstLevelTableAddress The base address of the translation table.
@param[in] TableLevel The current level being traversed.
@retval The attributes of the first page entry found, or INVALID_ENTRY.
**/
STATIC
UINT64
GetFirstPageAttribute (
IN UINT64 *FirstLevelTableAddress,
IN UINTN TableLevel
)
{
UINT64 FirstEntry;
// Get the first entry of the table
FirstEntry = *FirstLevelTableAddress;
if ((TableLevel != 3) && ((FirstEntry & TT_TYPE_MASK) == TT_TYPE_TABLE_ENTRY)) {
// Only valid for Levels 0, 1 and 2
// Get the attribute of the subsequent table
return GetFirstPageAttribute ((UINT64 *)(FirstEntry & TT_ADDRESS_MASK_DESCRIPTION_TABLE), TableLevel + 1);
} else if (((FirstEntry & TT_TYPE_MASK) == TT_TYPE_BLOCK_ENTRY) ||
((TableLevel == 3) && ((FirstEntry & TT_TYPE_MASK) == TT_TYPE_BLOCK_ENTRY_LEVEL3)))
{
return FirstEntry & TT_ATTRIBUTES_MASK;
} else {
return INVALID_ENTRY;
}
}
/**
This function recursively traverses the translation table heirarchy to
synchronise the GCD with the translation table.
@param[in] TableAddress The address of the table being processed.
@param[in] EntryCount The number of entries in the current level of the table.
@param[in] TableLevel The current level of the memory table being processed.
@param[in] BaseAddress The starting address of the region.
@param[in, out] PrevEntryAttribute The attributes of the previous region.
@param[in, out] StartGcdRegion The start of the GCD region.
@retval The address at the end of the last region processed.
**/
STATIC
UINT64
GetNextEntryAttribute (
IN UINT64 *TableAddress,
IN UINTN EntryCount,
IN UINTN TableLevel,
IN UINT64 BaseAddress,
IN OUT UINT64 *PrevEntryAttribute,
IN OUT UINT64 *StartGcdRegion
)
{
UINTN Index;
UINT64 Entry;
UINT64 EntryAttribute;
UINT64 EntryType;
EFI_STATUS Status;
UINTN NumberOfDescriptors;
EFI_GCD_MEMORY_SPACE_DESCRIPTOR *MemorySpaceMap;
// Get the memory space map from GCD
MemorySpaceMap = NULL;
Status = gDS->GetMemorySpaceMap (&NumberOfDescriptors, &MemorySpaceMap);
if (EFI_ERROR (Status) || (TableLevel > 3)) {
ASSERT_EFI_ERROR (Status);
ASSERT (TableLevel <= 3);
return 0;
}
// While the top level table might not contain TT_ENTRY_COUNT entries;
// the subsequent ones should be filled up
for (Index = 0; Index < EntryCount; Index++) {
Entry = TableAddress[Index];
EntryType = Entry & TT_TYPE_MASK;
EntryAttribute = Entry & TT_ATTRIBUTES_MASK;
// If Entry is a Table Descriptor type entry then go through the sub-level table
if ((EntryType == TT_TYPE_BLOCK_ENTRY) ||
((TableLevel == 3) && (EntryType == TT_TYPE_BLOCK_ENTRY_LEVEL3)))
{
if ((*PrevEntryAttribute == INVALID_ENTRY) || (EntryAttribute != *PrevEntryAttribute)) {
if (*PrevEntryAttribute != INVALID_ENTRY) {
// Update GCD with the last region
SetGcdMemorySpaceAttributes (
MemorySpaceMap,
NumberOfDescriptors,
*StartGcdRegion,
(BaseAddress + (Index * TT_ADDRESS_AT_LEVEL (TableLevel))) - *StartGcdRegion,
PageAttributeToGcdAttribute (*PrevEntryAttribute)
);
}
// Start of the new region
*StartGcdRegion = BaseAddress + (Index * TT_ADDRESS_AT_LEVEL (TableLevel));
*PrevEntryAttribute = EntryAttribute;
} else {
continue;
}
} else if (EntryType == TT_TYPE_TABLE_ENTRY) {
// Table Entry type is only valid for Level 0, 1, 2
ASSERT (TableLevel < 3);
// Increase the level number and scan the sub-level table
GetNextEntryAttribute (
(UINT64 *)(Entry & TT_ADDRESS_MASK_DESCRIPTION_TABLE),
TT_ENTRY_COUNT,
TableLevel + 1,
(BaseAddress + (Index * TT_ADDRESS_AT_LEVEL (TableLevel))),
PrevEntryAttribute,
StartGcdRegion
);
} else {
if (*PrevEntryAttribute != INVALID_ENTRY) {
// Update GCD with the last region
SetGcdMemorySpaceAttributes (
MemorySpaceMap,
NumberOfDescriptors,
*StartGcdRegion,
(BaseAddress + (Index * TT_ADDRESS_AT_LEVEL (TableLevel))) - *StartGcdRegion,
PageAttributeToGcdAttribute (*PrevEntryAttribute)
);
// Start of the new region
*StartGcdRegion = BaseAddress + (Index * TT_ADDRESS_AT_LEVEL (TableLevel));
*PrevEntryAttribute = INVALID_ENTRY;
}
}
}
FreePool (MemorySpaceMap);
return BaseAddress + (EntryCount * TT_ADDRESS_AT_LEVEL (TableLevel));
}
/**
Sync the GCD memory space attributes with the translation table.
@param[in] CpuProtocol The CPU architectural protocol instance.
@retval EFI_SUCCESS The GCD memory space attributes are synced with
the MMU page table.
@retval Others The return value of GetMemorySpaceMap().
**/
EFI_STATUS
SyncCacheConfig (
IN EFI_CPU_ARCH_PROTOCOL *CpuProtocol
)
{
EFI_STATUS Status;
UINT64 PageAttribute;
UINT64 *FirstLevelTableAddress;
UINTN TableLevel;
UINTN TableCount;
UINTN NumberOfDescriptors;
EFI_GCD_MEMORY_SPACE_DESCRIPTOR *MemorySpaceMap;
UINTN Tcr;
UINTN T0SZ;
UINT64 BaseAddressGcdRegion;
UINT64 EndAddressGcdRegion;
// This code assumes MMU is enabled and filed with section translations
ASSERT (ArmMmuEnabled ());
//
// Get the memory space map from GCD
//
MemorySpaceMap = NULL;
Status = gDS->GetMemorySpaceMap (&NumberOfDescriptors, &MemorySpaceMap);
if (EFI_ERROR (Status)) {
ASSERT_EFI_ERROR (Status);
return Status;
}
// The GCD implementation maintains its own copy of the state of memory space attributes. GCD needs
// to know what the initial memory space attributes are. The CPU Arch. Protocol does not provide a
// GetMemoryAttributes function for GCD to get this so we must resort to calling GCD (as if we were
// a client) to update its copy of the attributes. This is bad architecture and should be replaced
// with a way for GCD to query the CPU Arch. driver of the existing memory space attributes instead.
// Obtain page table base
FirstLevelTableAddress = (UINT64 *)(ArmGetTTBR0BaseAddress ());
// Get Translation Control Register value
Tcr = ArmGetTCR ();
// Get Address Region Size
T0SZ = Tcr & TCR_T0SZ_MASK;
// Get the level of the first table for the indicated Address Region Size
GetRootTranslationTableInfo (T0SZ, &TableLevel, &TableCount);
// First Attribute of the Page Tables
PageAttribute = GetFirstPageAttribute (FirstLevelTableAddress, TableLevel);
// We scan from the start of the memory map (ie: at the address 0x0)
BaseAddressGcdRegion = 0x0;
EndAddressGcdRegion = GetNextEntryAttribute (
FirstLevelTableAddress,
TableCount,
TableLevel,
BaseAddressGcdRegion,
&PageAttribute,
&BaseAddressGcdRegion
);
// Update GCD with the last region if valid
if ((PageAttribute != INVALID_ENTRY) && (EndAddressGcdRegion > BaseAddressGcdRegion)) {
SetGcdMemorySpaceAttributes (
MemorySpaceMap,
NumberOfDescriptors,
BaseAddressGcdRegion,
EndAddressGcdRegion - BaseAddressGcdRegion,
PageAttributeToGcdAttribute (PageAttribute)
);
}
FreePool (MemorySpaceMap);
return EFI_SUCCESS;
}
/**
Convert EFI memory attributes to ARM translation table attributes.
@param[in] EfiAttributes EFI memory attributes.
@retval The analogous translation table attributes.
**/
UINT64
EfiAttributeToArmAttribute (
IN UINT64 EfiAttributes
)
{
UINT64 ArmAttributes;
switch (EfiAttributes & EFI_MEMORY_CACHETYPE_MASK) {
case EFI_MEMORY_UC:
if (ArmReadCurrentEL () == AARCH64_EL2) {
ArmAttributes = TT_ATTR_INDX_DEVICE_MEMORY | TT_XN_MASK;
} else {
ArmAttributes = TT_ATTR_INDX_DEVICE_MEMORY | TT_UXN_MASK | TT_PXN_MASK;
}
break;
case EFI_MEMORY_WC:
ArmAttributes = TT_ATTR_INDX_MEMORY_NON_CACHEABLE;
break;
case EFI_MEMORY_WT:
ArmAttributes = TT_ATTR_INDX_MEMORY_WRITE_THROUGH | TT_SH_INNER_SHAREABLE;
break;
case EFI_MEMORY_WB:
ArmAttributes = TT_ATTR_INDX_MEMORY_WRITE_BACK | TT_SH_INNER_SHAREABLE;
break;
default:
ArmAttributes = TT_ATTR_INDX_MASK;
}
// Set the access flag to match the block attributes
if ((EfiAttributes & EFI_MEMORY_RP) == 0) {
ArmAttributes |= TT_AF;
}
// Determine protection attributes
if ((EfiAttributes & EFI_MEMORY_RO) != 0) {
ArmAttributes |= TT_AP_NO_RO;
}
// Process eXecute Never attribute
if ((EfiAttributes & EFI_MEMORY_XP) != 0) {
ArmAttributes |= TT_PXN_MASK;
}
return ArmAttributes;
}
/**
This function returns the attributes of the memory region containing the
specified address.
RegionLength and RegionAttributes are only valid if the result is EFI_SUCCESS.
@param[in] TranslationTable The translation table base address.
@param[in] TableLevel The level of the translation table.
@param[in] LastBlockEntry The last block address of the table level.
@param[in, out] BaseAddress The base address of the memory region.
@param[out] RegionLength The length of the memory region.
@param[out] RegionAttributes The attributes of the memory region.
@retval EFI_SUCCESS The attributes of the memory region were
returned successfully.
@retval EFI_NOT_FOUND The memory region was not found.
@retval EFI_NO_MAPPING The translation table entry associated with
BaseAddress is invalid.
**/
EFI_STATUS
GetMemoryRegionRec (
IN UINT64 *TranslationTable,
IN UINTN TableLevel,
IN UINT64 *LastBlockEntry,
IN OUT UINTN *BaseAddress,
OUT UINTN *RegionLength,
OUT UINTN *RegionAttributes
)
{
EFI_STATUS Status;
UINT64 *NextTranslationTable;
UINT64 *BlockEntry;
UINT64 BlockEntryType;
UINT64 EntryType;
if (TableLevel != 3) {
BlockEntryType = TT_TYPE_BLOCK_ENTRY;
} else {
BlockEntryType = TT_TYPE_BLOCK_ENTRY_LEVEL3;
}
// Find the block entry linked to the Base Address
BlockEntry = (UINT64 *)TT_GET_ENTRY_FOR_ADDRESS (TranslationTable, TableLevel, *BaseAddress);
EntryType = *BlockEntry & TT_TYPE_MASK;
if ((TableLevel < 3) && (EntryType == TT_TYPE_TABLE_ENTRY)) {
NextTranslationTable = (UINT64 *)(*BlockEntry & TT_ADDRESS_MASK_DESCRIPTION_TABLE);
// The entry is a page table, so we go to the next level
Status = GetMemoryRegionRec (
NextTranslationTable, // Address of the next level page table
TableLevel + 1, // Next Page Table level
(UINTN *)TT_LAST_BLOCK_ADDRESS (NextTranslationTable, TT_ENTRY_COUNT),
BaseAddress,
RegionLength,
RegionAttributes
);
// EFI_SUCCESS: The end of the end of the region was found.
// EFI_NO_MAPPING: The translation entry associated with BaseAddress is invalid.
if (Status != EFI_NOT_FOUND) {
return Status;
}
// Now we processed the table move to the next entry
BlockEntry++;
} else if (EntryType == BlockEntryType) {
// We have found the BlockEntry attached to the address. We save its start address (the start
// address might be before the 'BaseAddress') and attributes
*BaseAddress = *BaseAddress & ~(TT_ADDRESS_AT_LEVEL (TableLevel) - 1);
*RegionLength = 0;
*RegionAttributes = *BlockEntry & TT_ATTRIBUTES_MASK;
} else {
return EFI_NO_MAPPING;
}
while (BlockEntry <= LastBlockEntry) {
if (((*BlockEntry & TT_TYPE_MASK) == BlockEntryType) &&
((*BlockEntry & TT_ATTRIBUTES_MASK) == *RegionAttributes))
{
*RegionLength = *RegionLength + TT_BLOCK_ENTRY_SIZE_AT_LEVEL (TableLevel);
} else {
// In case we have found the end of the region we return success
return EFI_SUCCESS;
}
BlockEntry++;
}
// If we have reached the end of the TranslationTable and we have not found the end of the region then
// we return EFI_NOT_FOUND.
// The caller will continue to look for the memory region at its level.
return EFI_NOT_FOUND;
}
/**
Retrieves a memory region from a given base address.
This function retrieves a memory region starting from a given base address.
@param[in, out] BaseAddress The base address from which to retrieve
the memory region. On successful return, this is
updated to the end address of the retrieved region.
@param[out] RegionLength The length of the retrieved memory region.
@param[out] RegionAttributes The attributes of the retrieved memory region.
@retval EFI_STATUS Returns EFI_SUCCESS if the memory region is
retrieved successfully, or the status of the
recursive call to GetMemoryRegionRec.
@retval EFI_NOT_FOUND The memory region was not found.
@retval EFI_NO_MAPPING The translation table entry associated with
BaseAddress is invalid.
@retval EFI_INVALID_PARAMETER One of the input parameters was NULL.
**/
EFI_STATUS
GetMemoryRegion (
IN OUT UINTN *BaseAddress,
OUT UINTN *RegionLength,
OUT UINTN *RegionAttributes
)
{
EFI_STATUS Status;
UINT64 *TranslationTable;
UINTN TableLevel;
UINTN EntryCount;
UINTN T0SZ;
if ((BaseAddress == NULL) || (RegionLength == NULL) || (RegionAttributes == NULL)) {
ASSERT ((BaseAddress != NULL) && (RegionLength != NULL) && (RegionAttributes != NULL));
return EFI_INVALID_PARAMETER;
}
TranslationTable = ArmGetTTBR0BaseAddress ();
// Initialize the output parameters. These paramaters are only valid if the
// result is EFI_SUCCESS.
*RegionLength = 0;
*RegionAttributes = 0;
T0SZ = ArmGetTCR () & TCR_T0SZ_MASK;
// Get the Table info from T0SZ
GetRootTranslationTableInfo (T0SZ, &TableLevel, &EntryCount);
Status = GetMemoryRegionRec (
TranslationTable,
TableLevel,
(UINTN *)TT_LAST_BLOCK_ADDRESS (TranslationTable, EntryCount),
BaseAddress,
RegionLength,
RegionAttributes
);
// If the region continues up to the end of the root table then GetMemoryRegionRec()
// will return EFI_NOT_FOUND. Check if the region length was updated.
if ((Status == EFI_NOT_FOUND) && (*RegionLength > 0)) {
return EFI_SUCCESS;
}
return Status;
}