/** @file * File managing the MMU for ARMv7 architecture * * Copyright (c) 2011-2016, ARM Limited. All rights reserved. * * SPDX-License-Identifier: BSD-2-Clause-Patent * **/ #include #include #include #include #include #include #include #include #include #define ID_MMFR0_SHARELVL_SHIFT 12 #define ID_MMFR0_SHARELVL_MASK 0xf #define ID_MMFR0_SHARELVL_ONE 0 #define ID_MMFR0_SHARELVL_TWO 1 #define ID_MMFR0_INNERSHR_SHIFT 28 #define ID_MMFR0_INNERSHR_MASK 0xf #define ID_MMFR0_OUTERSHR_SHIFT 8 #define ID_MMFR0_OUTERSHR_MASK 0xf #define ID_MMFR0_SHR_IMP_UNCACHED 0 #define ID_MMFR0_SHR_IMP_HW_COHERENT 1 #define ID_MMFR0_SHR_IGNORED 0xf UINTN EFIAPI ArmReadIdMmfr0 ( VOID ); BOOLEAN EFIAPI ArmHasMpExtensions ( VOID ); STATIC BOOLEAN PreferNonshareableMemory ( VOID ) { UINTN Mmfr; UINTN Val; if (FeaturePcdGet (PcdNormalMemoryNonshareableOverride)) { return TRUE; } // // Check whether the innermost level of shareability (the level we will use // by default to map normal memory) is implemented with hardware coherency // support. Otherwise, revert to mapping as non-shareable. // Mmfr = ArmReadIdMmfr0 (); switch ((Mmfr >> ID_MMFR0_SHARELVL_SHIFT) & ID_MMFR0_SHARELVL_MASK) { case ID_MMFR0_SHARELVL_ONE: // one level of shareability Val = (Mmfr >> ID_MMFR0_OUTERSHR_SHIFT) & ID_MMFR0_OUTERSHR_MASK; break; case ID_MMFR0_SHARELVL_TWO: // two levels of shareability Val = (Mmfr >> ID_MMFR0_INNERSHR_SHIFT) & ID_MMFR0_INNERSHR_MASK; break; default: // unexpected value -> shareable is the safe option ASSERT (FALSE); return FALSE; } return Val != ID_MMFR0_SHR_IMP_HW_COHERENT; } STATIC VOID PopulateLevel2PageTable ( IN UINT32 *SectionEntry, IN UINT32 PhysicalBase, IN UINT32 RemainLength, IN ARM_MEMORY_REGION_ATTRIBUTES Attributes ) { UINT32* PageEntry; UINT32 Pages; UINT32 Index; UINT32 PageAttributes; UINT32 SectionDescriptor; UINT32 TranslationTable; UINT32 BaseSectionAddress; UINT32 FirstPageOffset; switch (Attributes) { case ARM_MEMORY_REGION_ATTRIBUTE_WRITE_BACK: case ARM_MEMORY_REGION_ATTRIBUTE_NONSECURE_WRITE_BACK: PageAttributes = TT_DESCRIPTOR_PAGE_WRITE_BACK; break; case ARM_MEMORY_REGION_ATTRIBUTE_WRITE_BACK_NONSHAREABLE: case ARM_MEMORY_REGION_ATTRIBUTE_NONSECURE_WRITE_BACK_NONSHAREABLE: PageAttributes = TT_DESCRIPTOR_PAGE_WRITE_BACK; PageAttributes &= ~TT_DESCRIPTOR_PAGE_S_SHARED; break; case ARM_MEMORY_REGION_ATTRIBUTE_WRITE_THROUGH: case ARM_MEMORY_REGION_ATTRIBUTE_NONSECURE_WRITE_THROUGH: PageAttributes = TT_DESCRIPTOR_PAGE_WRITE_THROUGH; break; case ARM_MEMORY_REGION_ATTRIBUTE_DEVICE: case ARM_MEMORY_REGION_ATTRIBUTE_NONSECURE_DEVICE: PageAttributes = TT_DESCRIPTOR_PAGE_DEVICE; break; case ARM_MEMORY_REGION_ATTRIBUTE_UNCACHED_UNBUFFERED: case ARM_MEMORY_REGION_ATTRIBUTE_NONSECURE_UNCACHED_UNBUFFERED: PageAttributes = TT_DESCRIPTOR_PAGE_UNCACHED; break; default: PageAttributes = TT_DESCRIPTOR_PAGE_UNCACHED; break; } if (PreferNonshareableMemory ()) { PageAttributes &= ~TT_DESCRIPTOR_PAGE_S_SHARED; } // Check if the Section Entry has already been populated. Otherwise attach a // Level 2 Translation Table to it if (*SectionEntry != 0) { // The entry must be a page table. Otherwise it exists an overlapping in the memory map if (TT_DESCRIPTOR_SECTION_TYPE_IS_PAGE_TABLE(*SectionEntry)) { TranslationTable = *SectionEntry & TT_DESCRIPTOR_SECTION_PAGETABLE_ADDRESS_MASK; } else if ((*SectionEntry & TT_DESCRIPTOR_SECTION_TYPE_MASK) == TT_DESCRIPTOR_SECTION_TYPE_SECTION) { // Case where a virtual memory map descriptor overlapped a section entry // Allocate a Level2 Page Table for this Section TranslationTable = (UINTN)AllocateAlignedPages ( EFI_SIZE_TO_PAGES (TRANSLATION_TABLE_PAGE_SIZE), TRANSLATION_TABLE_PAGE_ALIGNMENT); // Translate the Section Descriptor into Page Descriptor SectionDescriptor = TT_DESCRIPTOR_PAGE_TYPE_PAGE | ConvertSectionAttributesToPageAttributes (*SectionEntry, FALSE); BaseSectionAddress = TT_DESCRIPTOR_SECTION_BASE_ADDRESS(*SectionEntry); // // Make sure we are not inadvertently hitting in the caches // when populating the page tables // InvalidateDataCacheRange ((VOID *)TranslationTable, TRANSLATION_TABLE_PAGE_SIZE); // Populate the new Level2 Page Table for the section PageEntry = (UINT32*)TranslationTable; for (Index = 0; Index < TRANSLATION_TABLE_PAGE_COUNT; Index++) { PageEntry[Index] = TT_DESCRIPTOR_PAGE_BASE_ADDRESS(BaseSectionAddress + (Index << 12)) | SectionDescriptor; } // Overwrite the section entry to point to the new Level2 Translation Table *SectionEntry = (TranslationTable & TT_DESCRIPTOR_SECTION_PAGETABLE_ADDRESS_MASK) | (IS_ARM_MEMORY_REGION_ATTRIBUTES_SECURE(Attributes) ? (1 << 3) : 0) | TT_DESCRIPTOR_SECTION_TYPE_PAGE_TABLE; } else { // We do not support the other section type (16MB Section) ASSERT(0); return; } } else { TranslationTable = (UINTN)AllocateAlignedPages ( EFI_SIZE_TO_PAGES (TRANSLATION_TABLE_PAGE_SIZE), TRANSLATION_TABLE_PAGE_ALIGNMENT); // // Make sure we are not inadvertently hitting in the caches // when populating the page tables // InvalidateDataCacheRange ((VOID *)TranslationTable, TRANSLATION_TABLE_PAGE_SIZE); ZeroMem ((VOID *)TranslationTable, TRANSLATION_TABLE_PAGE_SIZE); *SectionEntry = (TranslationTable & TT_DESCRIPTOR_SECTION_PAGETABLE_ADDRESS_MASK) | (IS_ARM_MEMORY_REGION_ATTRIBUTES_SECURE(Attributes) ? (1 << 3) : 0) | TT_DESCRIPTOR_SECTION_TYPE_PAGE_TABLE; } FirstPageOffset = (PhysicalBase & TT_DESCRIPTOR_PAGE_INDEX_MASK) >> TT_DESCRIPTOR_PAGE_BASE_SHIFT; PageEntry = (UINT32 *)TranslationTable + FirstPageOffset; Pages = RemainLength / TT_DESCRIPTOR_PAGE_SIZE; ASSERT (FirstPageOffset + Pages <= TRANSLATION_TABLE_PAGE_COUNT); for (Index = 0; Index < Pages; Index++) { *PageEntry++ = TT_DESCRIPTOR_PAGE_BASE_ADDRESS(PhysicalBase) | PageAttributes; PhysicalBase += TT_DESCRIPTOR_PAGE_SIZE; } // // Invalidate again to ensure that any line fetches that may have occurred // [speculatively] since the previous invalidate are evicted again. // ArmDataMemoryBarrier (); InvalidateDataCacheRange ((UINT32 *)TranslationTable + FirstPageOffset, RemainLength / TT_DESCRIPTOR_PAGE_SIZE * sizeof (*PageEntry)); } STATIC VOID FillTranslationTable ( IN UINT32 *TranslationTable, IN ARM_MEMORY_REGION_DESCRIPTOR *MemoryRegion ) { UINT32 *SectionEntry; UINT32 Attributes; UINT32 PhysicalBase; UINT64 RemainLength; UINT32 PageMapLength; ASSERT(MemoryRegion->Length > 0); if (MemoryRegion->PhysicalBase >= SIZE_4GB) { return; } PhysicalBase = MemoryRegion->PhysicalBase; RemainLength = MIN(MemoryRegion->Length, SIZE_4GB - PhysicalBase); switch (MemoryRegion->Attributes) { case ARM_MEMORY_REGION_ATTRIBUTE_WRITE_BACK: Attributes = TT_DESCRIPTOR_SECTION_WRITE_BACK(0); break; case ARM_MEMORY_REGION_ATTRIBUTE_WRITE_BACK_NONSHAREABLE: Attributes = TT_DESCRIPTOR_SECTION_WRITE_BACK(0); Attributes &= ~TT_DESCRIPTOR_SECTION_S_SHARED; break; case ARM_MEMORY_REGION_ATTRIBUTE_WRITE_THROUGH: Attributes = TT_DESCRIPTOR_SECTION_WRITE_THROUGH(0); break; case ARM_MEMORY_REGION_ATTRIBUTE_DEVICE: Attributes = TT_DESCRIPTOR_SECTION_DEVICE(0); break; case ARM_MEMORY_REGION_ATTRIBUTE_UNCACHED_UNBUFFERED: Attributes = TT_DESCRIPTOR_SECTION_UNCACHED(0); break; case ARM_MEMORY_REGION_ATTRIBUTE_NONSECURE_WRITE_BACK: Attributes = TT_DESCRIPTOR_SECTION_WRITE_BACK(1); break; case ARM_MEMORY_REGION_ATTRIBUTE_NONSECURE_WRITE_BACK_NONSHAREABLE: Attributes = TT_DESCRIPTOR_SECTION_WRITE_BACK(1); Attributes &= ~TT_DESCRIPTOR_SECTION_S_SHARED; break; case ARM_MEMORY_REGION_ATTRIBUTE_NONSECURE_WRITE_THROUGH: Attributes = TT_DESCRIPTOR_SECTION_WRITE_THROUGH(1); break; case ARM_MEMORY_REGION_ATTRIBUTE_NONSECURE_DEVICE: Attributes = TT_DESCRIPTOR_SECTION_DEVICE(1); break; case ARM_MEMORY_REGION_ATTRIBUTE_NONSECURE_UNCACHED_UNBUFFERED: Attributes = TT_DESCRIPTOR_SECTION_UNCACHED(1); break; default: Attributes = TT_DESCRIPTOR_SECTION_UNCACHED(0); break; } if (PreferNonshareableMemory ()) { Attributes &= ~TT_DESCRIPTOR_SECTION_S_SHARED; } // Get the first section entry for this mapping SectionEntry = TRANSLATION_TABLE_ENTRY_FOR_VIRTUAL_ADDRESS(TranslationTable, MemoryRegion->VirtualBase); while (RemainLength != 0) { if (PhysicalBase % TT_DESCRIPTOR_SECTION_SIZE == 0 && RemainLength >= TT_DESCRIPTOR_SECTION_SIZE) { // Case: Physical address aligned on the Section Size (1MB) && the length // is greater than the Section Size *SectionEntry = TT_DESCRIPTOR_SECTION_BASE_ADDRESS(PhysicalBase) | Attributes; // // Issue a DMB to ensure that the page table entry update made it to // memory before we issue the invalidate, otherwise, a subsequent // speculative fetch could observe the old value. // ArmDataMemoryBarrier (); ArmInvalidateDataCacheEntryByMVA ((UINTN)SectionEntry++); PhysicalBase += TT_DESCRIPTOR_SECTION_SIZE; RemainLength -= TT_DESCRIPTOR_SECTION_SIZE; } else { PageMapLength = MIN (RemainLength, TT_DESCRIPTOR_SECTION_SIZE - (PhysicalBase % TT_DESCRIPTOR_SECTION_SIZE)); // Case: Physical address aligned on the Section Size (1MB) && the length // does not fill a section // Case: Physical address NOT aligned on the Section Size (1MB) PopulateLevel2PageTable (SectionEntry, PhysicalBase, PageMapLength, MemoryRegion->Attributes); // // Issue a DMB to ensure that the page table entry update made it to // memory before we issue the invalidate, otherwise, a subsequent // speculative fetch could observe the old value. // ArmDataMemoryBarrier (); ArmInvalidateDataCacheEntryByMVA ((UINTN)SectionEntry++); // If it is the last entry if (RemainLength < TT_DESCRIPTOR_SECTION_SIZE) { break; } PhysicalBase += PageMapLength; RemainLength -= PageMapLength; } } } RETURN_STATUS EFIAPI ArmConfigureMmu ( IN ARM_MEMORY_REGION_DESCRIPTOR *MemoryTable, OUT VOID **TranslationTableBase OPTIONAL, OUT UINTN *TranslationTableSize OPTIONAL ) { VOID *TranslationTable; ARM_MEMORY_REGION_ATTRIBUTES TranslationTableAttribute; UINT32 TTBRAttributes; TranslationTable = AllocateAlignedPages ( EFI_SIZE_TO_PAGES (TRANSLATION_TABLE_SECTION_SIZE), TRANSLATION_TABLE_SECTION_ALIGNMENT); if (TranslationTable == NULL) { return RETURN_OUT_OF_RESOURCES; } if (TranslationTableBase != NULL) { *TranslationTableBase = TranslationTable; } if (TranslationTableSize != NULL) { *TranslationTableSize = TRANSLATION_TABLE_SECTION_SIZE; } // // Make sure we are not inadvertently hitting in the caches // when populating the page tables // InvalidateDataCacheRange (TranslationTable, TRANSLATION_TABLE_SECTION_SIZE); ZeroMem (TranslationTable, TRANSLATION_TABLE_SECTION_SIZE); // By default, mark the translation table as belonging to a uncached region TranslationTableAttribute = ARM_MEMORY_REGION_ATTRIBUTE_UNCACHED_UNBUFFERED; while (MemoryTable->Length != 0) { // Find the memory attribute for the Translation Table if (((UINTN)TranslationTable >= MemoryTable->PhysicalBase) && ((UINTN)TranslationTable <= MemoryTable->PhysicalBase - 1 + MemoryTable->Length)) { TranslationTableAttribute = MemoryTable->Attributes; } FillTranslationTable (TranslationTable, MemoryTable); MemoryTable++; } // Translate the Memory Attributes into Translation Table Register Attributes if ((TranslationTableAttribute == ARM_MEMORY_REGION_ATTRIBUTE_WRITE_BACK) || (TranslationTableAttribute == ARM_MEMORY_REGION_ATTRIBUTE_NONSECURE_WRITE_BACK)) { TTBRAttributes = ArmHasMpExtensions () ? TTBR_MP_WRITE_BACK_ALLOC : TTBR_WRITE_BACK_ALLOC; } else { // Page tables must reside in memory mapped as write-back cacheable ASSERT (0); return RETURN_UNSUPPORTED; } if (TTBRAttributes & TTBR_SHAREABLE) { if (PreferNonshareableMemory ()) { TTBRAttributes ^= TTBR_SHAREABLE; } else { // // Unlike the S bit in the short descriptors, which implies inner shareable // on an implementation that supports two levels, the meaning of the S bit // in the TTBR depends on the NOS bit, which defaults to Outer Shareable. // However, we should only set this bit after we have confirmed that the // implementation supports multiple levels, or else the NOS bit is UNK/SBZP // if (((ArmReadIdMmfr0 () >> 12) & 0xf) != 0) { TTBRAttributes |= TTBR_NOT_OUTER_SHAREABLE; } } } ArmSetTTBR0 ((VOID *)((UINTN)TranslationTable | TTBRAttributes)); // // The TTBCR register value is undefined at reset in the Non-Secure world. // Writing 0 has the effect of: // Clearing EAE: Use short descriptors, as mandated by specification. // Clearing PD0 and PD1: Translation Table Walk Disable is off. // Clearing N: Perform all translation table walks through TTBR0. // (0 is the default reset value in systems not implementing // the Security Extensions.) // ArmSetTTBCR (0); ArmSetDomainAccessControl (DOMAIN_ACCESS_CONTROL_NONE(15) | DOMAIN_ACCESS_CONTROL_NONE(14) | DOMAIN_ACCESS_CONTROL_NONE(13) | DOMAIN_ACCESS_CONTROL_NONE(12) | DOMAIN_ACCESS_CONTROL_NONE(11) | DOMAIN_ACCESS_CONTROL_NONE(10) | DOMAIN_ACCESS_CONTROL_NONE( 9) | DOMAIN_ACCESS_CONTROL_NONE( 8) | DOMAIN_ACCESS_CONTROL_NONE( 7) | DOMAIN_ACCESS_CONTROL_NONE( 6) | DOMAIN_ACCESS_CONTROL_NONE( 5) | DOMAIN_ACCESS_CONTROL_NONE( 4) | DOMAIN_ACCESS_CONTROL_NONE( 3) | DOMAIN_ACCESS_CONTROL_NONE( 2) | DOMAIN_ACCESS_CONTROL_NONE( 1) | DOMAIN_ACCESS_CONTROL_CLIENT(0)); ArmEnableInstructionCache(); ArmEnableDataCache(); ArmEnableMmu(); return RETURN_SUCCESS; }