From 2e969d2e9e98820a136002699ff1d02ba2a2f551 Mon Sep 17 00:00:00 2001 From: Olivier Martin Date: Mon, 19 Aug 2013 17:38:39 +0000 Subject: [PATCH] ArmPkg/CpuDxe: Added support to not set a memory region with the same attribute Changing the attribute implies some cache management (clean & invalidate). Preventing the cache management should improve the performance. Contributed-under: TianoCore Contribution Agreement 1.0 Signed-off-by: Olivier Martin git-svn-id: https://svn.code.sf.net/p/edk2/code/trunk/edk2@14568 6f19259b-4bc3-4df7-8a09-765794883524 --- ArmPkg/Drivers/CpuDxe/AArch64/Mmu.c | 146 +++++++++++++++++++++ ArmPkg/Drivers/CpuDxe/ArmV6/Mmu.c | 182 +++++++++++++++++++++++++++ ArmPkg/Drivers/CpuDxe/CpuDxe.h | 13 ++ ArmPkg/Drivers/CpuDxe/CpuMmuCommon.c | 27 +++- 4 files changed, 364 insertions(+), 4 deletions(-) diff --git a/ArmPkg/Drivers/CpuDxe/AArch64/Mmu.c b/ArmPkg/Drivers/CpuDxe/AArch64/Mmu.c index 40a4c1b0aa..72f9b3cc63 100644 --- a/ArmPkg/Drivers/CpuDxe/AArch64/Mmu.c +++ b/ArmPkg/Drivers/CpuDxe/AArch64/Mmu.c @@ -194,3 +194,149 @@ SyncCacheConfig ( return EFI_SUCCESS; } + +UINT64 +EfiAttributeToArmAttribute ( + IN UINT64 EfiAttributes + ) +{ + UINT64 ArmAttributes; + + switch (EfiAttributes & EFI_MEMORY_CACHETYPE_MASK) { + case EFI_MEMORY_UC: + ArmAttributes = TT_ATTR_INDX_DEVICE_MEMORY; + break; + case EFI_MEMORY_WC: + ArmAttributes = TT_ATTR_INDX_MEMORY_NON_CACHEABLE; + break; + case EFI_MEMORY_WT: + ArmAttributes = TT_ATTR_INDX_MEMORY_WRITE_THROUGH; + break; + case EFI_MEMORY_WB: + ArmAttributes = TT_ATTR_INDX_MEMORY_WRITE_BACK; + break; + default: + DEBUG ((EFI_D_ERROR, "EfiAttributeToArmAttribute: 0x%lX attributes is not supported.\n", EfiAttributes)); + ASSERT (0); + ArmAttributes = TT_ATTR_INDX_DEVICE_MEMORY; + } + + // Set the access flag to match the block attributes + ArmAttributes |= TT_AF; + + // Determine protection attributes + if (EfiAttributes & EFI_MEMORY_WP) { + ArmAttributes |= TT_AP_RO_RO; + } + + // Process eXecute Never attribute + if (EfiAttributes & EFI_MEMORY_XP) { + 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 (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; + } + } 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 'BaseAdress') 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; + } +} diff --git a/ArmPkg/Drivers/CpuDxe/ArmV6/Mmu.c b/ArmPkg/Drivers/CpuDxe/ArmV6/Mmu.c index 38b709de15..474f105d22 100644 --- a/ArmPkg/Drivers/CpuDxe/ArmV6/Mmu.c +++ b/ArmPkg/Drivers/CpuDxe/ArmV6/Mmu.c @@ -696,3 +696,185 @@ SetMemoryAttributes ( return Status; } + +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-cachable + 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_WP: + case EFI_MEMORY_XP: + case EFI_MEMORY_RP: + case EFI_MEMORY_UCE: + default: + // Cannot be implemented UEFI definition unclear for ARM + // Cause a page fault if these ranges are accessed. + ArmAttributes = TT_DESCRIPTOR_SECTION_TYPE_FAULT; + DEBUG ((EFI_D_PAGE, "SetMemoryAttributes(): Unsupported attribute %x will page fault on access\n", EfiAttributes)); + break; + } + + // Determine protection attributes + if (EfiAttributes & EFI_MEMORY_WP) { + ArmAttributes |= TT_DESCRIPTOR_SECTION_AP_RO_RO; + } else { + ArmAttributes |= TT_DESCRIPTOR_SECTION_AP_RW_RW; + } + + // Determine eXecute Never attribute + if (EfiAttributes & EFI_MEMORY_XP) { + 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 = TT_DESCRIPTOR_PAGE_BASE_ADDRESS(*BaseAddress) >> 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 '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 = TT_DESCRIPTOR_PAGE_BASE_ADDRESS (*BaseAddress) >> 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 ((SectionDescriptor & TT_DESCRIPTOR_SECTION_TYPE_MASK) == TT_DESCRIPTOR_SECTION_TYPE_PAGE_TABLE) { + // 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; +} diff --git a/ArmPkg/Drivers/CpuDxe/CpuDxe.h b/ArmPkg/Drivers/CpuDxe/CpuDxe.h index 075f2a18bf..d5b50641e3 100644 --- a/ArmPkg/Drivers/CpuDxe/CpuDxe.h +++ b/ArmPkg/Drivers/CpuDxe/CpuDxe.h @@ -148,6 +148,19 @@ SetMemoryAttributes ( IN EFI_PHYSICAL_ADDRESS VirtualMask ); +// The ARM Attributes might be defined on 64-bit (case of the long format description table) +UINT64 +EfiAttributeToArmAttribute ( + IN UINT64 EfiAttributes + ); + +EFI_STATUS +GetMemoryRegion ( + IN OUT UINTN *BaseAddress, + OUT UINTN *RegionLength, + OUT UINTN *RegionAttributes + ); + VOID GetRootTranslationTableInfo ( IN UINTN T0SZ, diff --git a/ArmPkg/Drivers/CpuDxe/CpuMmuCommon.c b/ArmPkg/Drivers/CpuDxe/CpuMmuCommon.c index e0ee9fc287..723604d1df 100644 --- a/ArmPkg/Drivers/CpuDxe/CpuMmuCommon.c +++ b/ArmPkg/Drivers/CpuDxe/CpuMmuCommon.c @@ -178,18 +178,37 @@ CpuSetMemoryAttributes ( IN EFI_CPU_ARCH_PROTOCOL *This, IN EFI_PHYSICAL_ADDRESS BaseAddress, IN UINT64 Length, - IN UINT64 Attributes + IN UINT64 EfiAttributes ) { - DEBUG ((EFI_D_PAGE, "CpuSetMemoryAttributes(%lx, %lx, %lx)\n", BaseAddress, Length, Attributes)); + EFI_STATUS Status; + UINTN ArmAttributes; + UINTN RegionBaseAddress; + UINTN RegionLength; + UINTN RegionArmAttributes; if ((BaseAddress & (SIZE_4KB - 1)) != 0) { // Minimum granularity is SIZE_4KB (4KB on ARM) - DEBUG ((EFI_D_PAGE, "CpuSetMemoryAttributes(%lx, %lx, %lx): Minimum ganularity is SIZE_4KB\n", BaseAddress, Length, Attributes)); + DEBUG ((EFI_D_PAGE, "CpuSetMemoryAttributes(%lx, %lx, %lx): Minimum ganularity is SIZE_4KB\n", BaseAddress, Length, EfiAttributes)); return EFI_UNSUPPORTED; } - return SetMemoryAttributes (BaseAddress, Length, Attributes, 0); + // Convert the 'Attribute' into ARM Attribute + ArmAttributes = EfiAttributeToArmAttribute (EfiAttributes); + + // Get the region starting from 'BaseAddress' and its 'Attribute' + RegionBaseAddress = BaseAddress; + Status = GetMemoryRegion (&RegionBaseAddress, &RegionLength, &RegionArmAttributes); + + // Data & Instruction Caches are flushed when we set new memory attributes. + // So, we only set the attributes if the new region is different. + if (EFI_ERROR (Status) || (RegionArmAttributes != ArmAttributes) || + ((BaseAddress + Length) > (RegionBaseAddress + RegionLength))) + { + return SetMemoryAttributes (BaseAddress, Length, EfiAttributes, 0); + } else { + return EFI_SUCCESS; + } } EFI_STATUS -- 2.39.2