xlat: Introduce API to change memory attributes of a region
authorSandrine Bailleux <sandrine.bailleux@arm.com>
Tue, 17 Oct 2017 11:02:03 +0000 (12:02 +0100)
committerAntonio Nino Diaz <antonio.ninodiaz@arm.com>
Tue, 17 Oct 2017 11:02:36 +0000 (12:02 +0100)
This patch introduces a new API in the translation tables library
(v2), that allows to change the memory attributes of a memory
region. It may be used to change its execution permissions and
data access permissions.

As a prerequisite, the memory must be already mapped. Moreover, it
must be mapped at the finest granularity (currently 4 KB).

Change-Id: I242a8c6f0f3ef2b0a81a61e28706540462faca3c
Co-authored-by: Sandrine Bailleux <sandrine.bailleux@arm.com>
Co-authored-by: Antonio Nino Diaz <antonio.ninodiaz@arm.com>
Signed-off-by: Antonio Nino Diaz <antonio.ninodiaz@arm.com>
include/lib/xlat_tables/xlat_tables_v2.h
lib/xlat_tables_v2/aarch32/xlat_tables_arch.c
lib/xlat_tables_v2/xlat_tables_internal.c

index bdad13327ebc1d6a8a28804b303f2b1605881b16..73a9c5334404a58d74c171d8172d33239c35276d 100644 (file)
@@ -251,6 +251,49 @@ int mmap_remove_dynamic_region_ctx(xlat_ctx_t *ctx,
 
 #endif /* PLAT_XLAT_TABLES_DYNAMIC */
 
+/*
+ * Change the memory attributes of the memory region starting from a given
+ * virtual address in a set of translation tables.
+ *
+ * This function can only be used after the translation tables have been
+ * initialized.
+ *
+ * The base address of the memory region must be aligned on a page boundary.
+ * The size of this memory region must be a multiple of a page size.
+ * The memory region must be already mapped by the given translation tables
+ * and it must be mapped at the granularity of a page.
+ *
+ * Return 0 on success, a negative value on error.
+ *
+ * In case of error, the memory attributes remain unchanged and this function
+ * has no effect.
+ *
+ * ctx
+ *   Translation context to work on.
+ * base_va:
+ *   Virtual address of the 1st page to change the attributes of.
+ * size:
+ *   Size in bytes of the memory region.
+ * attr:
+ *   New attributes of the page tables. The attributes that can be changed are
+ *   data access (MT_RO/MT_RW), instruction access (MT_EXECUTE_NEVER/MT_EXECUTE)
+ *   and user/privileged access (MT_USER/MT_PRIVILEGED) in the case of contexts
+ *   that are used in the EL1&0 translation regime. Also, note that this
+ *   function doesn't allow to remap a region as RW and executable, or to remap
+ *   device memory as executable.
+ *
+ * NOTE: The caller of this function must be able to write to the translation
+ * tables, i.e. the memory where they are stored must be mapped with read-write
+ * access permissions. This function assumes it is the case. If this is not
+ * the case then this function might trigger a data abort exception.
+ *
+ * NOTE2: The caller is responsible for making sure that the targeted
+ * translation tables are not modified by any other code while this function is
+ * executing.
+ */
+int change_mem_attributes(xlat_ctx_t *ctx, uintptr_t base_va, size_t size,
+                       mmap_attr_t attr);
+
 /*
  * Query the memory attributes of a memory page in a set of translation tables.
  *
index cbc86850498dd91ec93c00edf9111b477408820e..642f799a4d19d6961bbb66db48e051ffb248969a 100644 (file)
@@ -27,8 +27,6 @@ int is_mmu_enabled_ctx(const xlat_ctx_t *ctx __unused)
        return (read_sctlr() & SCTLR_M_BIT) != 0;
 }
 
-#if PLAT_XLAT_TABLES_DYNAMIC
-
 void xlat_arch_tlbi_va(uintptr_t va)
 {
        /*
@@ -77,8 +75,6 @@ void xlat_arch_tlbi_va_sync(void)
        isb();
 }
 
-#endif /* PLAT_XLAT_TABLES_DYNAMIC */
-
 int xlat_arch_current_el(void)
 {
        /*
index 651354ee550a304722feabb5f15a1a5e7e4ec247..0acfacbf14c2e06bd7f4767743a9aafc748bd47a 100644 (file)
@@ -1507,3 +1507,156 @@ int get_mem_attributes(const xlat_ctx_t *ctx, uintptr_t base_va,
        return get_mem_attributes_internal(ctx, base_va, attributes,
                                           NULL, NULL, NULL);
 }
+
+
+int change_mem_attributes(xlat_ctx_t *ctx,
+                       uintptr_t base_va,
+                       size_t size,
+                       mmap_attr_t attr)
+{
+       /* Note: This implementation isn't optimized. */
+
+       assert(ctx != NULL);
+       assert(ctx->initialized);
+
+       unsigned long long virt_addr_space_size =
+               (unsigned long long)ctx->va_max_address + 1;
+       assert(virt_addr_space_size > 0);
+
+       if (!IS_PAGE_ALIGNED(base_va)) {
+               WARN("%s: Address %p is not aligned on a page boundary.\n",
+                    __func__, (void *)base_va);
+               return -EINVAL;
+       }
+
+       if (size == 0) {
+               WARN("%s: Size is 0.\n", __func__);
+               return -EINVAL;
+       }
+
+       if ((size % PAGE_SIZE) != 0) {
+               WARN("%s: Size 0x%zx is not a multiple of a page size.\n",
+                    __func__, size);
+               return -EINVAL;
+       }
+
+       if (((attr & MT_EXECUTE_NEVER) == 0) && ((attr & MT_RW) != 0)) {
+               WARN("%s() doesn't allow to remap memory as read-write and executable.\n",
+                    __func__);
+               return -EINVAL;
+       }
+
+       int pages_count = size / PAGE_SIZE;
+
+       VERBOSE("Changing memory attributes of %i pages starting from address %p...\n",
+               pages_count, (void *)base_va);
+
+       uintptr_t base_va_original = base_va;
+
+       /*
+        * Sanity checks.
+        */
+       for (int i = 0; i < pages_count; ++i) {
+               uint64_t *entry;
+               uint64_t desc;
+               int level;
+
+               entry = find_xlat_table_entry(base_va,
+                                             ctx->base_table,
+                                             ctx->base_table_entries,
+                                             virt_addr_space_size,
+                                             &level);
+               if (entry == NULL) {
+                       WARN("Address %p is not mapped.\n", (void *)base_va);
+                       return -EINVAL;
+               }
+
+               desc = *entry;
+
+               /*
+                * Check that all the required pages are mapped at page
+                * granularity.
+                */
+               if (((desc & DESC_MASK) != PAGE_DESC) ||
+                       (level != XLAT_TABLE_LEVEL_MAX)) {
+                       WARN("Address %p is not mapped at the right granularity.\n",
+                            (void *)base_va);
+                       WARN("Granularity is 0x%llx, should be 0x%x.\n",
+                            (unsigned long long)XLAT_BLOCK_SIZE(level), PAGE_SIZE);
+                       return -EINVAL;
+               }
+
+               /*
+                * If the region type is device, it shouldn't be executable.
+                */
+               int attr_index = (desc >> ATTR_INDEX_SHIFT) & ATTR_INDEX_MASK;
+               if (attr_index == ATTR_DEVICE_INDEX) {
+                       if ((attr & MT_EXECUTE_NEVER) == 0) {
+                               WARN("Setting device memory as executable at address %p.",
+                                    (void *)base_va);
+                               return -EINVAL;
+                       }
+               }
+
+               base_va += PAGE_SIZE;
+       }
+
+       /* Restore original value. */
+       base_va = base_va_original;
+
+       VERBOSE("%s: All pages are mapped, now changing their attributes...\n",
+               __func__);
+
+       for (int i = 0; i < pages_count; ++i) {
+
+               mmap_attr_t old_attr, new_attr;
+               uint64_t *entry;
+               int level;
+               unsigned long long addr_pa;
+
+               get_mem_attributes_internal(ctx, base_va, &old_attr,
+                                           &entry, &addr_pa, &level);
+
+               VERBOSE("Old attributes: 0x%x\n", old_attr);
+
+               /*
+                * From attr, only MT_RO/MT_RW, MT_EXECUTE/MT_EXECUTE_NEVER and
+                * MT_USER/MT_PRIVILEGED are taken into account. Any other
+                * information is ignored.
+                */
+
+               /* Clean the old attributes so that they can be rebuilt. */
+               new_attr = old_attr & ~(MT_RW|MT_EXECUTE_NEVER|MT_USER);
+
+               /*
+                * Update attributes, but filter out the ones this function
+                * isn't allowed to change.
+                */
+               new_attr |= attr & (MT_RW|MT_EXECUTE_NEVER|MT_USER);
+
+               VERBOSE("New attributes: 0x%x\n", new_attr);
+
+               /*
+                * The break-before-make sequence requires writing an invalid
+                * descriptor and making sure that the system sees the change
+                * before writing the new descriptor.
+                */
+               *entry = INVALID_DESC;
+
+               /* Invalidate any cached copy of this mapping in the TLBs. */
+               xlat_arch_tlbi_va_regime(base_va, ctx->xlat_regime);
+
+               /* Ensure completion of the invalidation. */
+               xlat_arch_tlbi_va_sync();
+
+               /* Write new descriptor */
+               *entry = xlat_desc(ctx, new_attr, addr_pa, level);
+
+               base_va += PAGE_SIZE;
+       }
+
+       /* Ensure that the last descriptor writen is seen by the system. */
+       dsbish();
+
+       return 0;
+}