mirror of https://github.com/acidanthera/audk.git
403 lines
14 KiB
C
403 lines
14 KiB
C
/*++
|
|
|
|
Copyright (c) 2009, Hewlett-Packard Company. All rights reserved.<BR>
|
|
Portions copyright (c) 2010, Apple Inc. All rights reserved.<BR>
|
|
Portions copyright (c) 2011-2021, Arm Limited. All rights reserved.<BR>
|
|
Copyright (c) 2017, Intel Corporation. All rights reserved.<BR>
|
|
|
|
SPDX-License-Identifier: BSD-2-Clause-Patent
|
|
|
|
|
|
--*/
|
|
|
|
#include <Library/MemoryAllocationLib.h>
|
|
#include "CpuDxe.h"
|
|
|
|
#define INVALID_ENTRY ((UINT32)~0)
|
|
|
|
#define MIN_T0SZ 16
|
|
#define BITS_PER_LEVEL 9
|
|
|
|
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;
|
|
}
|
|
|
|
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_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;
|
|
}
|
|
|
|
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_ATTR_INDX_MASK;
|
|
} else {
|
|
return INVALID_ENTRY;
|
|
}
|
|
}
|
|
|
|
STATIC
|
|
UINT64
|
|
GetNextEntryAttribute (
|
|
IN UINT64 *TableAddress,
|
|
IN UINTN EntryCount,
|
|
IN UINTN TableLevel,
|
|
IN UINT64 BaseAddress,
|
|
IN OUT UINT32 *PrevEntryAttribute,
|
|
IN OUT UINT64 *StartGcdRegion
|
|
)
|
|
{
|
|
UINTN Index;
|
|
UINT64 Entry;
|
|
UINT32 EntryAttribute;
|
|
UINT32 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);
|
|
ASSERT_EFI_ERROR (Status);
|
|
|
|
// We cannot get more than 3-level page table
|
|
ASSERT (TableLevel <= 3);
|
|
|
|
// 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_ATTR_INDX_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));
|
|
}
|
|
|
|
EFI_STATUS
|
|
SyncCacheConfig (
|
|
IN EFI_CPU_ARCH_PROTOCOL *CpuProtocol
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
UINT32 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);
|
|
ASSERT_EFI_ERROR (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) {
|
|
SetGcdMemorySpaceAttributes (MemorySpaceMap, NumberOfDescriptors,
|
|
BaseAddressGcdRegion,
|
|
EndAddressGcdRegion - BaseAddressGcdRegion,
|
|
PageAttributeToGcdAttribute (PageAttribute));
|
|
}
|
|
|
|
FreePool (MemorySpaceMap);
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
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
|
|
ArmAttributes |= TT_AF;
|
|
|
|
// Determine protection attributes
|
|
if ((EfiAttributes & EFI_MEMORY_RO) != 0) {
|
|
ArmAttributes |= TT_AP_RO_RO;
|
|
}
|
|
|
|
// Process eXecute Never attribute
|
|
if ((EfiAttributes & EFI_MEMORY_XP) != 0) {
|
|
ArmAttributes |= TT_PXN_MASK;
|
|
}
|
|
|
|
return ArmAttributes;
|
|
}
|
|
|
|
// This function will recursively go down the page table to find the first block address linked to 'BaseAddress'.
|
|
// And then the function will identify the size of the region that has the same page table attribute.
|
|
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);
|
|
|
|
// In case of 'Success', it means the end of the block region has been found into the upper
|
|
// level translation table
|
|
if (!EFI_ERROR(Status)) {
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
// 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 {
|
|
// We have an 'Invalid' entry
|
|
return EFI_UNSUPPORTED;
|
|
}
|
|
|
|
while (BlockEntry <= LastBlockEntry) {
|
|
if ((*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;
|
|
}
|
|
|
|
EFI_STATUS
|
|
GetMemoryRegion (
|
|
IN OUT UINTN *BaseAddress,
|
|
OUT UINTN *RegionLength,
|
|
OUT UINTN *RegionAttributes
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
UINT64 *TranslationTable;
|
|
UINTN TableLevel;
|
|
UINTN EntryCount;
|
|
UINTN T0SZ;
|
|
|
|
ASSERT ((BaseAddress != NULL) && (RegionLength != NULL) && (RegionAttributes != NULL));
|
|
|
|
TranslationTable = ArmGetTTBR0BaseAddress ();
|
|
|
|
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
|
|
if (Status == EFI_NOT_FOUND) {
|
|
return EFI_SUCCESS;
|
|
} else {
|
|
return Status;
|
|
}
|
|
}
|