From: Ard Biesheuvel <[email protected]>

In order to permit the module loader to allocate MOD_TEXT and
MOD_INIT_TEXT from the same chunk of memory, add an API function to
execmem that splits an existing execmem cache allocation in two.

This will be used on arm64 to avoid .text and .init.text being placed
far away from each other.

Signed-off-by: Ard Biesheuvel <[email protected]>
---
 include/linux/execmem.h |  9 ++++
 mm/execmem.c            | 44 ++++++++++++++++++++
 2 files changed, 53 insertions(+)

diff --git a/include/linux/execmem.h b/include/linux/execmem.h
index 7de229134e30..59bd862cb061 100644
--- a/include/linux/execmem.h
+++ b/include/linux/execmem.h
@@ -178,6 +178,15 @@ void execmem_free(void *ptr);
 
 DEFINE_FREE(execmem, void *, if (_T) execmem_free(_T));
 
+/**
+ * execmem_split - allocate space from an existing execmem cache allocation
+ * @ptr  - the existing execmem cache allocation
+ * @size - the size to carve out from the existing allocation
+ *
+ * Return: the address of the carved out allocation, or %NULL on failure.
+ */
+void *execmem_split(void *ptr, size_t size);
+
 #ifdef CONFIG_MMU
 /**
  * execmem_vmap - create virtual mapping for EXECMEM_MODULE_DATA memory
diff --git a/mm/execmem.c b/mm/execmem.c
index 084a207e4278..6db0c1d82a9d 100644
--- a/mm/execmem.c
+++ b/mm/execmem.c
@@ -439,6 +439,33 @@ static bool execmem_cache_free(void *ptr)
        return true;
 }
 
+static void *execmem_cache_split(void *ptr, size_t size)
+{
+       struct maple_tree *busy_areas = &execmem_cache.busy_areas;
+       unsigned long addr = (unsigned long)ptr;
+       MA_STATE(mas, busy_areas, addr, addr);
+       void *area;
+       int err;
+
+       guard(mutex)(&execmem_cache.mutex);
+
+       area = mas_walk(&mas);
+       if (!area)
+               return ERR_PTR(-ENOENT);
+
+       if (size >= mas_range_len(&mas))
+               return ERR_PTR(-EINVAL);
+
+       addr += mas_range_len(&mas) - size;
+       mas_set_range(&mas, addr, mas.last);
+
+       err = mas_store_gfp(&mas, (void *)addr, GFP_KERNEL);
+       if (err)
+               return ERR_PTR(err);
+
+       return (void *)addr;
+}
+
 #else /* CONFIG_ARCH_HAS_EXECMEM_ROX */
 /*
  * when ROX cache is not used the permissions defined by architectures for
@@ -459,6 +486,11 @@ static bool execmem_cache_free(void *ptr)
 {
        return false;
 }
+
+static void *execmem_cache_split(void *ptr, size_t size)
+{
+       return ERR_PTR(-ENOENT);
+}
 #endif /* CONFIG_ARCH_HAS_EXECMEM_ROX */
 
 void *execmem_alloc(enum execmem_type type, size_t size)
@@ -511,6 +543,18 @@ bool execmem_is_rox(enum execmem_type type)
        return !!(execmem_info->ranges[type].flags & EXECMEM_ROX_CACHE);
 }
 
+void *execmem_split(void *ptr, size_t size)
+{
+       if (!ptr || !size)
+               return NULL;
+
+       ptr = execmem_cache_split(ptr, size);
+       if (!IS_ERR(ptr))
+               return ptr;
+       VM_WARN_ON(ptr != ERR_PTR(-ENOMEM));
+       return NULL;
+}
+
 static bool execmem_validate(struct execmem_info *info)
 {
        struct execmem_range *r = &info->ranges[EXECMEM_DEFAULT];
-- 
2.55.0.860.g4b6b3295ed-goog


Reply via email to