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 <olivier.martin@arm.com>



git-svn-id: https://svn.code.sf.net/p/edk2/code/trunk/edk2@14568 6f19259b-4bc3-4df7-8a09-765794883524
diff --git a/ArmPkg/Drivers/CpuDxe/AArch64/Mmu.c b/ArmPkg/Drivers/CpuDxe/AArch64/Mmu.c
index 40a4c1b..72f9b3c 100644
--- a/ArmPkg/Drivers/CpuDxe/AArch64/Mmu.c
+++ b/ArmPkg/Drivers/CpuDxe/AArch64/Mmu.c
@@ -194,3 +194,149 @@
 

   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 38b709d..474f105 100644
--- a/ArmPkg/Drivers/CpuDxe/ArmV6/Mmu.c
+++ b/ArmPkg/Drivers/CpuDxe/ArmV6/Mmu.c
@@ -696,3 +696,185 @@
 

   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 075f2a1..d5b5064 100644
--- a/ArmPkg/Drivers/CpuDxe/CpuDxe.h
+++ b/ArmPkg/Drivers/CpuDxe/CpuDxe.h
@@ -148,6 +148,19 @@
   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 e0ee9fc..723604d 100644
--- a/ArmPkg/Drivers/CpuDxe/CpuMmuCommon.c
+++ b/ArmPkg/Drivers/CpuDxe/CpuMmuCommon.c
@@ -178,18 +178,37 @@
   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