| /** @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 <Uefi.h> | |
| #include <Chipset/ArmV7.h> | |
| #include <Library/BaseMemoryLib.h> | |
| #include <Library/CacheMaintenanceLib.h> | |
| #include <Library/MemoryAllocationLib.h> | |
| #include <Library/ArmLib.h> | |
| #include <Library/BaseLib.h> | |
| #include <Library/DebugLib.h> | |
| #include <Library/PcdLib.h> | |
| #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 = (UINT32)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 ( | |
| (UINT32)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; | |
| 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); | |
| while (MemoryTable->Length != 0) { | |
| FillTranslationTable (TranslationTable, MemoryTable); | |
| MemoryTable++; | |
| } | |
| TTBRAttributes = ArmHasMpExtensions () ? TTBR_MP_WRITE_BACK_ALLOC | |
| : TTBR_WRITE_BACK_ALLOC; | |
| 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; | |
| } |