/*++ Copyright (c) 2009, Hewlett-Packard Company. All rights reserved.
Portions copyright (c) 2010, Apple Inc. All rights reserved.
Portions copyright (c) 2013-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" EFI_STATUS SectionToGcdAttributes ( IN UINT32 SectionAttributes, OUT UINT64 *GcdAttributes ) { *GcdAttributes = 0; // determine cacheability attributes switch (SectionAttributes & TT_DESCRIPTOR_SECTION_CACHE_POLICY_MASK) { case TT_DESCRIPTOR_SECTION_CACHE_POLICY_STRONGLY_ORDERED: *GcdAttributes |= EFI_MEMORY_UC; break; case TT_DESCRIPTOR_SECTION_CACHE_POLICY_SHAREABLE_DEVICE: *GcdAttributes |= EFI_MEMORY_UC; break; case TT_DESCRIPTOR_SECTION_CACHE_POLICY_WRITE_THROUGH_NO_ALLOC: *GcdAttributes |= EFI_MEMORY_WT; break; case TT_DESCRIPTOR_SECTION_CACHE_POLICY_WRITE_BACK_NO_ALLOC: *GcdAttributes |= EFI_MEMORY_WB; break; case TT_DESCRIPTOR_SECTION_CACHE_POLICY_NON_CACHEABLE: *GcdAttributes |= EFI_MEMORY_WC; break; case TT_DESCRIPTOR_SECTION_CACHE_POLICY_WRITE_BACK_ALLOC: *GcdAttributes |= EFI_MEMORY_WB; break; case TT_DESCRIPTOR_SECTION_CACHE_POLICY_NON_SHAREABLE_DEVICE: *GcdAttributes |= EFI_MEMORY_UC; break; default: return EFI_UNSUPPORTED; } // determine protection attributes switch (SectionAttributes & TT_DESCRIPTOR_SECTION_AP_MASK) { case TT_DESCRIPTOR_SECTION_AP_NO_NO: // no read, no write // *GcdAttributes |= EFI_MEMORY_RO | EFI_MEMORY_RP; break; case TT_DESCRIPTOR_SECTION_AP_RW_NO: case TT_DESCRIPTOR_SECTION_AP_RW_RW: // normal read/write access, do not add additional attributes break; // read only cases map to write-protect case TT_DESCRIPTOR_SECTION_AP_RO_NO: case TT_DESCRIPTOR_SECTION_AP_RO_RO: *GcdAttributes |= EFI_MEMORY_RO; break; default: return EFI_UNSUPPORTED; } // now process eXectue Never attribute if ((SectionAttributes & TT_DESCRIPTOR_SECTION_XN_MASK) != 0 ) { *GcdAttributes |= EFI_MEMORY_XP; } return EFI_SUCCESS; } EFI_STATUS PageToGcdAttributes ( IN UINT32 PageAttributes, OUT UINT64 *GcdAttributes ) { *GcdAttributes = 0; // determine cacheability attributes switch (PageAttributes & TT_DESCRIPTOR_PAGE_CACHE_POLICY_MASK) { case TT_DESCRIPTOR_PAGE_CACHE_POLICY_STRONGLY_ORDERED: *GcdAttributes |= EFI_MEMORY_UC; break; case TT_DESCRIPTOR_PAGE_CACHE_POLICY_SHAREABLE_DEVICE: *GcdAttributes |= EFI_MEMORY_UC; break; case TT_DESCRIPTOR_PAGE_CACHE_POLICY_WRITE_THROUGH_NO_ALLOC: *GcdAttributes |= EFI_MEMORY_WT; break; case TT_DESCRIPTOR_PAGE_CACHE_POLICY_WRITE_BACK_NO_ALLOC: *GcdAttributes |= EFI_MEMORY_WB; break; case TT_DESCRIPTOR_PAGE_CACHE_POLICY_NON_CACHEABLE: *GcdAttributes |= EFI_MEMORY_WC; break; case TT_DESCRIPTOR_PAGE_CACHE_POLICY_WRITE_BACK_ALLOC: *GcdAttributes |= EFI_MEMORY_WB; break; case TT_DESCRIPTOR_PAGE_CACHE_POLICY_NON_SHAREABLE_DEVICE: *GcdAttributes |= EFI_MEMORY_UC; break; default: return EFI_UNSUPPORTED; } // determine protection attributes switch (PageAttributes & TT_DESCRIPTOR_PAGE_AP_MASK) { case TT_DESCRIPTOR_PAGE_AP_NO_NO: // no read, no write // *GcdAttributes |= EFI_MEMORY_RO | EFI_MEMORY_RP; break; case TT_DESCRIPTOR_PAGE_AP_RW_NO: case TT_DESCRIPTOR_PAGE_AP_RW_RW: // normal read/write access, do not add additional attributes break; // read only cases map to write-protect case TT_DESCRIPTOR_PAGE_AP_RO_NO: case TT_DESCRIPTOR_PAGE_AP_RO_RO: *GcdAttributes |= EFI_MEMORY_RO; break; default: return EFI_UNSUPPORTED; } // now process eXectue Never attribute if ((PageAttributes & TT_DESCRIPTOR_PAGE_XN_MASK) != 0 ) { *GcdAttributes |= EFI_MEMORY_XP; } return EFI_SUCCESS; } EFI_STATUS SyncCacheConfigPage ( IN UINT32 SectionIndex, IN UINT32 FirstLevelDescriptor, IN UINTN NumberOfDescriptors, IN EFI_GCD_MEMORY_SPACE_DESCRIPTOR *MemorySpaceMap, IN OUT EFI_PHYSICAL_ADDRESS *NextRegionBase, IN OUT UINT64 *NextRegionLength, IN OUT UINT32 *NextSectionAttributes ) { EFI_STATUS Status; UINT32 i; volatile ARM_PAGE_TABLE_ENTRY *SecondLevelTable; UINT32 NextPageAttributes; UINT32 PageAttributes; UINT32 BaseAddress; UINT64 GcdAttributes; // Get the Base Address from FirstLevelDescriptor; BaseAddress = TT_DESCRIPTOR_PAGE_BASE_ADDRESS (SectionIndex << TT_DESCRIPTOR_SECTION_BASE_SHIFT); // Convert SectionAttributes into PageAttributes NextPageAttributes = TT_DESCRIPTOR_CONVERT_TO_PAGE_CACHE_POLICY (*NextSectionAttributes, 0) | TT_DESCRIPTOR_CONVERT_TO_PAGE_AP (*NextSectionAttributes); // obtain page table base SecondLevelTable = (ARM_PAGE_TABLE_ENTRY *)(FirstLevelDescriptor & TT_DESCRIPTOR_SECTION_PAGETABLE_ADDRESS_MASK); for (i = 0; i < TRANSLATION_TABLE_PAGE_COUNT; i++) { if ((SecondLevelTable[i] & TT_DESCRIPTOR_PAGE_TYPE_MASK) == TT_DESCRIPTOR_PAGE_TYPE_PAGE) { // extract attributes (cacheability and permissions) PageAttributes = SecondLevelTable[i] & (TT_DESCRIPTOR_PAGE_CACHE_POLICY_MASK | TT_DESCRIPTOR_PAGE_AP_MASK); if (NextPageAttributes == 0) { // start on a new region *NextRegionLength = 0; *NextRegionBase = BaseAddress | (i << TT_DESCRIPTOR_PAGE_BASE_SHIFT); NextPageAttributes = PageAttributes; } else if (PageAttributes != NextPageAttributes) { // Convert Section Attributes into GCD Attributes Status = PageToGcdAttributes (NextPageAttributes, &GcdAttributes); ASSERT_EFI_ERROR (Status); // update GCD with these changes (this will recurse into our own CpuSetMemoryAttributes below which is OK) SetGcdMemorySpaceAttributes (MemorySpaceMap, NumberOfDescriptors, *NextRegionBase, *NextRegionLength, GcdAttributes); // start on a new region *NextRegionLength = 0; *NextRegionBase = BaseAddress | (i << TT_DESCRIPTOR_PAGE_BASE_SHIFT); NextPageAttributes = PageAttributes; } } else if (NextPageAttributes != 0) { // Convert Page Attributes into GCD Attributes Status = PageToGcdAttributes (NextPageAttributes, &GcdAttributes); ASSERT_EFI_ERROR (Status); // update GCD with these changes (this will recurse into our own CpuSetMemoryAttributes below which is OK) SetGcdMemorySpaceAttributes (MemorySpaceMap, NumberOfDescriptors, *NextRegionBase, *NextRegionLength, GcdAttributes); *NextRegionLength = 0; *NextRegionBase = BaseAddress | (i << TT_DESCRIPTOR_PAGE_BASE_SHIFT); NextPageAttributes = 0; } *NextRegionLength += TT_DESCRIPTOR_PAGE_SIZE; } // Convert back PageAttributes into SectionAttributes *NextSectionAttributes = TT_DESCRIPTOR_CONVERT_TO_SECTION_CACHE_POLICY (NextPageAttributes, 0) | TT_DESCRIPTOR_CONVERT_TO_SECTION_AP (NextPageAttributes); return EFI_SUCCESS; } EFI_STATUS SyncCacheConfig ( IN EFI_CPU_ARCH_PROTOCOL *CpuProtocol ) { EFI_STATUS Status; UINT32 i; EFI_PHYSICAL_ADDRESS NextRegionBase; UINT64 NextRegionLength; UINT32 NextSectionAttributes; UINT32 SectionAttributes; UINT64 GcdAttributes; volatile ARM_FIRST_LEVEL_DESCRIPTOR *FirstLevelTable; UINTN NumberOfDescriptors; EFI_GCD_MEMORY_SPACE_DESCRIPTOR *MemorySpaceMap; DEBUG ((DEBUG_PAGE, "SyncCacheConfig()\n")); // 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 FirstLevelTable = (ARM_FIRST_LEVEL_DESCRIPTOR *)(ArmGetTTBR0BaseAddress ()); // Get the first region NextSectionAttributes = FirstLevelTable[0] & (TT_DESCRIPTOR_SECTION_CACHE_POLICY_MASK | TT_DESCRIPTOR_SECTION_AP_MASK); // iterate through each 1MB descriptor NextRegionBase = NextRegionLength = 0; for (i = 0; i < TRANSLATION_TABLE_SECTION_COUNT; i++) { if ((FirstLevelTable[i] & TT_DESCRIPTOR_SECTION_TYPE_MASK) == TT_DESCRIPTOR_SECTION_TYPE_SECTION) { // extract attributes (cacheability and permissions) SectionAttributes = FirstLevelTable[i] & (TT_DESCRIPTOR_SECTION_CACHE_POLICY_MASK | TT_DESCRIPTOR_SECTION_AP_MASK); if (NextSectionAttributes == 0) { // start on a new region NextRegionLength = 0; NextRegionBase = TT_DESCRIPTOR_SECTION_BASE_ADDRESS (i << TT_DESCRIPTOR_SECTION_BASE_SHIFT); NextSectionAttributes = SectionAttributes; } else if (SectionAttributes != NextSectionAttributes) { // Convert Section Attributes into GCD Attributes Status = SectionToGcdAttributes (NextSectionAttributes, &GcdAttributes); ASSERT_EFI_ERROR (Status); // update GCD with these changes (this will recurse into our own CpuSetMemoryAttributes below which is OK) SetGcdMemorySpaceAttributes (MemorySpaceMap, NumberOfDescriptors, NextRegionBase, NextRegionLength, GcdAttributes); // start on a new region NextRegionLength = 0; NextRegionBase = TT_DESCRIPTOR_SECTION_BASE_ADDRESS (i << TT_DESCRIPTOR_SECTION_BASE_SHIFT); NextSectionAttributes = SectionAttributes; } NextRegionLength += TT_DESCRIPTOR_SECTION_SIZE; } else if (TT_DESCRIPTOR_SECTION_TYPE_IS_PAGE_TABLE (FirstLevelTable[i])) { // In this case any bits set in the 'NextSectionAttributes' are garbage and were set from // bits that are actually part of the pagetable address. We clear it out to zero so that // the SyncCacheConfigPage will use the page attributes instead of trying to convert the // section attributes into page attributes NextSectionAttributes = 0; Status = SyncCacheConfigPage ( i, FirstLevelTable[i], NumberOfDescriptors, MemorySpaceMap, &NextRegionBase, &NextRegionLength, &NextSectionAttributes ); ASSERT_EFI_ERROR (Status); } else { // We do not support yet 16MB sections ASSERT ((FirstLevelTable[i] & TT_DESCRIPTOR_SECTION_TYPE_MASK) != TT_DESCRIPTOR_SECTION_TYPE_SUPERSECTION); // start on a new region if (NextSectionAttributes != 0) { // Convert Section Attributes into GCD Attributes Status = SectionToGcdAttributes (NextSectionAttributes, &GcdAttributes); ASSERT_EFI_ERROR (Status); // update GCD with these changes (this will recurse into our own CpuSetMemoryAttributes below which is OK) SetGcdMemorySpaceAttributes (MemorySpaceMap, NumberOfDescriptors, NextRegionBase, NextRegionLength, GcdAttributes); NextRegionLength = 0; NextRegionBase = TT_DESCRIPTOR_SECTION_BASE_ADDRESS (i << TT_DESCRIPTOR_SECTION_BASE_SHIFT); NextSectionAttributes = 0; } NextRegionLength += TT_DESCRIPTOR_SECTION_SIZE; } } // section entry loop if (NextSectionAttributes != 0) { // Convert Section Attributes into GCD Attributes Status = SectionToGcdAttributes (NextSectionAttributes, &GcdAttributes); ASSERT_EFI_ERROR (Status); // update GCD with these changes (this will recurse into our own CpuSetMemoryAttributes below which is OK) SetGcdMemorySpaceAttributes (MemorySpaceMap, NumberOfDescriptors, NextRegionBase, NextRegionLength, GcdAttributes); } FreePool (MemorySpaceMap); return EFI_SUCCESS; } UINT64 EfiAttributeToArmAttribute ( IN UINT64 EfiAttributes ) { UINT64 ArmAttributes; switch (EfiAttributes & EFI_MEMORY_CACHETYPE_MASK) { case EFI_MEMORY_UC: // Map to strongly ordered ArmAttributes = TT_DESCRIPTOR_SECTION_CACHE_POLICY_STRONGLY_ORDERED; // TEX[2:0] = 0, C=0, B=0 break; case EFI_MEMORY_WC: // Map to normal non-cacheable ArmAttributes = TT_DESCRIPTOR_SECTION_CACHE_POLICY_NON_CACHEABLE; // TEX [2:0]= 001 = 0x2, B=0, C=0 break; case EFI_MEMORY_WT: // Write through with no-allocate ArmAttributes = TT_DESCRIPTOR_SECTION_CACHE_POLICY_WRITE_THROUGH_NO_ALLOC; // TEX [2:0] = 0, C=1, B=0 break; case EFI_MEMORY_WB: // Write back (with allocate) ArmAttributes = TT_DESCRIPTOR_SECTION_CACHE_POLICY_WRITE_BACK_ALLOC; // TEX [2:0] = 001, C=1, B=1 break; case EFI_MEMORY_UCE: default: ArmAttributes = TT_DESCRIPTOR_SECTION_TYPE_FAULT; break; } // Determine protection attributes if ((EfiAttributes & EFI_MEMORY_RO) != 0) { ArmAttributes |= TT_DESCRIPTOR_SECTION_AP_RO_RO; } else { ArmAttributes |= TT_DESCRIPTOR_SECTION_AP_RW_RW; } // Determine eXecute Never attribute if ((EfiAttributes & EFI_MEMORY_XP) != 0) { ArmAttributes |= TT_DESCRIPTOR_SECTION_XN_MASK; } return ArmAttributes; } EFI_STATUS GetMemoryRegionPage ( IN UINT32 *PageTable, IN OUT UINTN *BaseAddress, OUT UINTN *RegionLength, OUT UINTN *RegionAttributes ) { UINT32 PageAttributes; UINT32 TableIndex; UINT32 PageDescriptor; // Convert the section attributes into page attributes PageAttributes = ConvertSectionAttributesToPageAttributes (*RegionAttributes, 0); // Calculate index into first level translation table for start of modification TableIndex = ((*BaseAddress) & TT_DESCRIPTOR_PAGE_INDEX_MASK) >> TT_DESCRIPTOR_PAGE_BASE_SHIFT; ASSERT (TableIndex < TRANSLATION_TABLE_PAGE_COUNT); // Go through the page table to find the end of the section for ( ; TableIndex < TRANSLATION_TABLE_PAGE_COUNT; TableIndex++) { // Get the section at the given index PageDescriptor = PageTable[TableIndex]; if ((PageDescriptor & TT_DESCRIPTOR_PAGE_TYPE_MASK) == TT_DESCRIPTOR_PAGE_TYPE_FAULT) { // Case: End of the boundary of the region return EFI_SUCCESS; } else if ((PageDescriptor & TT_DESCRIPTOR_PAGE_TYPE_PAGE) == TT_DESCRIPTOR_PAGE_TYPE_PAGE) { if ((PageDescriptor & TT_DESCRIPTOR_PAGE_ATTRIBUTE_MASK) == PageAttributes) { *RegionLength = *RegionLength + TT_DESCRIPTOR_PAGE_SIZE; } else { // Case: End of the boundary of the region return EFI_SUCCESS; } } else { // We do not support Large Page yet. We return EFI_SUCCESS that means end of the region. ASSERT (0); return EFI_SUCCESS; } } return EFI_NOT_FOUND; } EFI_STATUS GetMemoryRegion ( IN OUT UINTN *BaseAddress, OUT UINTN *RegionLength, OUT UINTN *RegionAttributes ) { EFI_STATUS Status; UINT32 TableIndex; UINT32 PageAttributes; UINT32 PageTableIndex; UINT32 SectionDescriptor; ARM_FIRST_LEVEL_DESCRIPTOR *FirstLevelTable; UINT32 *PageTable; // Initialize the arguments *RegionLength = 0; // Obtain page table base FirstLevelTable = (ARM_FIRST_LEVEL_DESCRIPTOR *)ArmGetTTBR0BaseAddress (); // Calculate index into first level translation table for start of modification TableIndex = TT_DESCRIPTOR_SECTION_BASE_ADDRESS (*BaseAddress) >> TT_DESCRIPTOR_SECTION_BASE_SHIFT; ASSERT (TableIndex < TRANSLATION_TABLE_SECTION_COUNT); // Get the section at the given index SectionDescriptor = FirstLevelTable[TableIndex]; if (!SectionDescriptor) { return EFI_NOT_FOUND; } // If 'BaseAddress' belongs to the section then round it to the section boundary if (((SectionDescriptor & TT_DESCRIPTOR_SECTION_TYPE_MASK) == TT_DESCRIPTOR_SECTION_TYPE_SECTION) || ((SectionDescriptor & TT_DESCRIPTOR_SECTION_TYPE_MASK) == TT_DESCRIPTOR_SECTION_TYPE_SUPERSECTION)) { *BaseAddress = (*BaseAddress) & TT_DESCRIPTOR_SECTION_BASE_ADDRESS_MASK; *RegionAttributes = SectionDescriptor & TT_DESCRIPTOR_SECTION_ATTRIBUTE_MASK; } else { // Otherwise, we round it to the page boundary *BaseAddress = (*BaseAddress) & TT_DESCRIPTOR_PAGE_BASE_ADDRESS_MASK; // Get the attribute at the page table level (Level 2) PageTable = (UINT32 *)(SectionDescriptor & TT_DESCRIPTOR_SECTION_PAGETABLE_ADDRESS_MASK); // Calculate index into first level translation table for start of modification PageTableIndex = ((*BaseAddress) & TT_DESCRIPTOR_PAGE_INDEX_MASK) >> TT_DESCRIPTOR_PAGE_BASE_SHIFT; ASSERT (PageTableIndex < TRANSLATION_TABLE_PAGE_COUNT); PageAttributes = PageTable[PageTableIndex] & TT_DESCRIPTOR_PAGE_ATTRIBUTE_MASK; *RegionAttributes = TT_DESCRIPTOR_CONVERT_TO_SECTION_CACHE_POLICY (PageAttributes, 0) | TT_DESCRIPTOR_CONVERT_TO_SECTION_AP (PageAttributes); } for ( ; TableIndex < TRANSLATION_TABLE_SECTION_COUNT; TableIndex++) { // Get the section at the given index SectionDescriptor = FirstLevelTable[TableIndex]; // If the entry is a level-2 page table then we scan it to find the end of the region if (TT_DESCRIPTOR_SECTION_TYPE_IS_PAGE_TABLE (SectionDescriptor)) { // Extract the page table location from the descriptor PageTable = (UINT32 *)(SectionDescriptor & TT_DESCRIPTOR_SECTION_PAGETABLE_ADDRESS_MASK); // Scan the page table to find the end of the region. Status = GetMemoryRegionPage (PageTable, BaseAddress, RegionLength, RegionAttributes); // If we have found the end of the region (Status == EFI_SUCCESS) then we exit the for-loop if (Status == EFI_SUCCESS) { break; } } else if (((SectionDescriptor & TT_DESCRIPTOR_SECTION_TYPE_MASK) == TT_DESCRIPTOR_SECTION_TYPE_SECTION) || ((SectionDescriptor & TT_DESCRIPTOR_SECTION_TYPE_MASK) == TT_DESCRIPTOR_SECTION_TYPE_SUPERSECTION)) { if ((SectionDescriptor & TT_DESCRIPTOR_SECTION_ATTRIBUTE_MASK) != *RegionAttributes) { // If the attributes of the section differ from the one targeted then we exit the loop break; } else { *RegionLength = *RegionLength + TT_DESCRIPTOR_SECTION_SIZE; } } else { // If we are on an invalid section then it means it is the end of our section. break; } } return EFI_SUCCESS; }