multi_heap: Introduce shared multi-heap memory pool manager
The shared multi-heap memory pool manager uses the multi-heap allocator
to manage a set of reserved memory regions with different capabilities /
attributes (cacheable, non-cacheable, etc...) defined in the DT.
The user can request allocation from the shared pool specifying the
capability / attribute of interest for the memory (cacheable /
non-cacheable memory, etc...)
Signed-off-by: Carlo Caione <ccaione@baylibre.com>
diff --git a/doc/reference/memory_management/index.rst b/doc/reference/memory_management/index.rst
index cab7930..45d9431 100644
--- a/doc/reference/memory_management/index.rst
+++ b/doc/reference/memory_management/index.rst
@@ -9,3 +9,4 @@
:maxdepth: 1
demand_paging.rst
+ shared_multi_heap.rst
diff --git a/doc/reference/memory_management/shared_multi_heap.rst b/doc/reference/memory_management/shared_multi_heap.rst
new file mode 100644
index 0000000..8adc2e7
--- /dev/null
+++ b/doc/reference/memory_management/shared_multi_heap.rst
@@ -0,0 +1,84 @@
+.. _memory_management_shared_multi_heap:
+
+Shared Multi Heap
+#################
+
+The shared multi-heap memory pool manager uses the multi-heap allocator to
+manage a set of reserved memory regions with different capabilities /
+attributes (cacheable, non-cacheable, etc...) defined in the DT.
+
+The user can request allocation from the shared pool specifying the capability
+/ attribute of interest for the memory (cacheable / non-cacheable memory,
+etc...).
+
+The different heaps with their attributes available in the shared pool are
+defined into the DT file leveraging the ``reserved-memory`` nodes.
+
+This is a DT example declaring three different memory regions with different
+cacheability attributes: ``cacheable`` and ``non-cacheable``
+
+.. code-block:: devicetree
+
+ / {
+ reserved-memory {
+ compatible = "reserved-memory";
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ res0: reserved@42000000 {
+ compatible = "shared-multi-heap";
+ reg = <0x42000000 0x1000>;
+ capability = "cacheable";
+ label = "res0";
+ };
+
+ res1: reserved@43000000 {
+ compatible = "shared-multi-heap";
+ reg = <0x43000000 0x2000>;
+ capability = "non-cacheable";
+ label = "res1";
+ };
+
+ res2: reserved2@44000000 {
+ compatible = "shared-multi-heap";
+ reg = <0x44000000 0x3000>;
+ capability = "cacheable";
+ label = "res2";
+ };
+ };
+
+The user can then request 4K from heap memory ``cacheable`` or
+``non-cacheable`` using the provided APIs:
+
+.. code-block:: c
+
+ // Allocate 4K from cacheable memory
+ shared_multi_heap_alloc(SMH_REG_ATTR_CACHEABLE, 0x1000);
+
+ // Allocate 4K from non-cacheable
+ shared_multi_heap_alloc(SMH_REG_ATTR_NON_CACHEABLE, 0x1000);
+
+The backend implementation will allocate the memory region from the heap with
+the correct attribute and using the region able to accommodate the required size.
+
+Special handling for MMU/MPU
+****************************
+
+For MMU/MPU enabled platform sometimes it is required to setup and configure
+the memory regions before these are added to the managed pool. This is done at
+init time using the :c:func:`shared_multi_heap_pool_init()` function that is
+accepting a :c:type:`smh_init_reg_fn_t` callback function. This callback will
+be called for each memory region at init time and it can be used to correctly
+map the region before this is considered valid and accessible.
+
+Adding new attributes
+*********************
+
+Currently only two memory attributes are supported: ``cacheable`` and
+``non-cacheable``. To add a new attribute:
+
+1. Add the new ``enum`` for the attribute in the :c:enum:`smh_reg_attr`
+2. Add the corresponding attribute name in :file:`shared-multi-heap.yaml`
+
+.. doxygengroup:: shared_multi_heap
+ :project: Zephyr
diff --git a/dts/bindings/multi_heap_manager/shared-multi-heap.yaml b/dts/bindings/multi_heap_manager/shared-multi-heap.yaml
new file mode 100644
index 0000000..fdf735c
--- /dev/null
+++ b/dts/bindings/multi_heap_manager/shared-multi-heap.yaml
@@ -0,0 +1,20 @@
+description: Shared multi-heap memory pool manager
+
+compatible: "shared-multi-heap"
+
+include:
+ - name: base.yaml
+ property-allowlist: ['reg', 'label']
+
+properties:
+ # Keep this is sync with shared_multi_heap.h
+ capability:
+ type: string
+ required: false
+ description: memory region capability
+ enum:
+ - "cacheable"
+ - "non-cacheable"
+
+ label:
+ required: true
diff --git a/include/multi_heap/shared_multi_heap.h b/include/multi_heap/shared_multi_heap.h
new file mode 100644
index 0000000..ea5367f
--- /dev/null
+++ b/include/multi_heap/shared_multi_heap.h
@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) 2021 Carlo Caione, <ccaione@baylibre.com>
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#ifndef ZEPHYR_INCLUDE_MULTI_HEAP_MANAGER_SMH_H_
+#define ZEPHYR_INCLUDE_MULTI_HEAP_MANAGER_SMH_H_
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief Shared multi-heap interface
+ * @defgroup shared_multi_heap Shared multi-heap interface
+ * @ingroup multi_heap
+ * @{
+ *
+ * The shared multi-heap manager uses the multi-heap allocator to manage a set
+ * of reserved memory regions with different capabilities / attributes
+ * (cacheable, non-cacheable, etc...) defined in the DT.
+ *
+ * The user can request allocation from the shared pool specifying the
+ * capability / attribute of interest for the memory (cacheable / non-cacheable
+ * memory, etc...)
+ *
+ */
+
+/**
+ * @brief Memory region attributes / capabilities
+ *
+ * ** This list needs to be kept in sync with shared-multi-heap.yaml **
+ */
+enum smh_reg_attr {
+ /** cacheable */
+ SMH_REG_ATTR_CACHEABLE,
+
+ /** non-cacheable */
+ SMH_REG_ATTR_NON_CACHEABLE,
+
+ /** must be the last item */
+ SMH_REG_ATTR_NUM,
+};
+
+/**
+ * @brief SMH region struct
+ *
+ * This struct is carrying information about the memory region to be added in
+ * the multi-heap pool. This is filled by the manager with the information
+ * coming from the reserved memory children nodes in the DT.
+ */
+struct shared_multi_heap_region {
+ enum smh_reg_attr attr;
+ uintptr_t addr;
+ size_t size;
+};
+
+/**
+ * @brief Region init function
+ *
+ * This is a user-provided function whose responsibility is to setup or
+ * initialize the memory region passed in input before this is added to the
+ * heap pool by the shared multi-heap manager. This function can be used by
+ * architectures using MMU / MPU that must correctly map the region before this
+ * is considered valid and accessible.
+ *
+ * @param reg Pointer to the SMH region structure.
+ * @param v_addr Virtual address obtained after mapping. For non-MMU
+ * architectures this value is the physical address of the
+ * region.
+ * @param size Size of the region after mapping.
+ *
+ * @return True if the region is ready to be added to the heap pool.
+ * False if the region must be skipped.
+ */
+typedef bool (*smh_init_reg_fn_t)(struct shared_multi_heap_region *reg,
+ uint8_t **v_addr, size_t *size);
+
+
+/**
+ * @brief Init the pool
+ *
+ * Initialize the shared multi-heap pool and hook-up the region init function.
+ *
+ * @param smh_init_reg_fn The function pointer to the region init function. Can
+ * be NULL for non-MPU / non-MMU architectures.
+ */
+int shared_multi_heap_pool_init(smh_init_reg_fn_t smh_init_reg_fn);
+
+/**
+ * @brief Allocate memory from the memory shared multi-heap pool
+ *
+ * Allocate a block of memory of the specified size in bytes and with a
+ * specified capability / attribute.
+ *
+ * @param attr Capability / attribute requested for the memory block.
+ * @param bytes Requested size of the allocation in bytes.
+ *
+ * @return A valid pointer to heap memory or NULL if no memory is available.
+ */
+void *shared_multi_heap_alloc(enum smh_reg_attr attr, size_t bytes);
+
+/**
+ * @brief Free memory from the shared multi-heap pool
+ *
+ * Free the passed block of memory.
+ *
+ * @param block Block to free.
+ */
+void shared_multi_heap_free(void *block);
+
+/**
+ * @}
+ */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* ZEPHYR_INCLUDE_MULTI_HEAP_MANAGER_SMH_H_ */
diff --git a/lib/os/CMakeLists.txt b/lib/os/CMakeLists.txt
index bcf238b..eebae83 100644
--- a/lib/os/CMakeLists.txt
+++ b/lib/os/CMakeLists.txt
@@ -43,6 +43,8 @@
zephyr_sources_ifdef(CONFIG_REBOOT reboot.c)
+zephyr_sources_ifdef(CONFIG_SHARED_MULTI_HEAP shared_multi_heap.c)
+
zephyr_library_include_directories(
${ZEPHYR_BASE}/kernel/include
${ZEPHYR_BASE}/arch/${ARCH}/include
diff --git a/lib/os/Kconfig b/lib/os/Kconfig
index 5a6db1d..9d02db8 100644
--- a/lib/os/Kconfig
+++ b/lib/os/Kconfig
@@ -63,6 +63,14 @@
storing variable length packets in a circular way and operate directly
on the buffer memory.
+config SHARED_MULTI_HEAP
+ bool "Shared multi-heap manager"
+ help
+ Enable support for a shared multi-heap manager that uses the
+ multi-heap allocator to manage a set of reserved memory regions with
+ different capabilities / attributes (cacheable, non-cacheable,
+ etc...) defined in the DT.
+
if MPSC_PBUF
config MPSC_CLEAR_ALLOCATED
bool "Clear allocated packet"
diff --git a/lib/os/shared_multi_heap.c b/lib/os/shared_multi_heap.c
new file mode 100644
index 0000000..bc45f80
--- /dev/null
+++ b/lib/os/shared_multi_heap.c
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2021 Carlo Caione, <ccaione@baylibre.com>
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include <zephyr.h>
+#include <device.h>
+#include <sys/sys_heap.h>
+#include <sys/multi_heap.h>
+#include <linker/linker-defs.h>
+
+#include <multi_heap/shared_multi_heap.h>
+
+#define DT_DRV_COMPAT shared_multi_heap
+
+#define NUM_REGIONS DT_NUM_INST_STATUS_OKAY(DT_DRV_COMPAT)
+
+static struct sys_multi_heap shared_multi_heap;
+static struct sys_heap heap_pool[SMH_REG_ATTR_NUM][NUM_REGIONS];
+
+static smh_init_reg_fn_t smh_init_reg;
+
+#define FOREACH_REG(n) \
+ { .addr = (uintptr_t) LINKER_DT_RESERVED_MEM_GET_PTR(DT_DRV_INST(n)), \
+ .size = LINKER_DT_RESERVED_MEM_GET_SIZE(DT_DRV_INST(n)), \
+ .attr = DT_ENUM_IDX(DT_DRV_INST(n), capability), \
+ },
+
+static struct shared_multi_heap_region dt_region[NUM_REGIONS] = {
+ DT_INST_FOREACH_STATUS_OKAY(FOREACH_REG)
+};
+
+static void *smh_choice(struct sys_multi_heap *mheap, void *cfg, size_t align, size_t size)
+{
+ enum smh_reg_attr attr;
+ struct sys_heap *h;
+ void *block;
+
+ attr = (enum smh_reg_attr) cfg;
+
+ if (attr >= SMH_REG_ATTR_NUM || size == 0) {
+ return NULL;
+ }
+
+ for (size_t reg = 0; reg < NUM_REGIONS; reg++) {
+ h = &heap_pool[attr][reg];
+
+ if (h->heap == NULL) {
+ return NULL;
+ }
+
+ block = sys_heap_aligned_alloc(h, align, size);
+ if (block != NULL) {
+ break;
+ }
+ }
+
+ return block;
+}
+
+static void smh_init_with_attr(enum smh_reg_attr attr)
+{
+ unsigned int slot = 0;
+ uint8_t *mapped;
+ size_t size;
+
+ for (size_t reg = 0; reg < NUM_REGIONS; reg++) {
+ if (dt_region[reg].attr == attr) {
+
+ if (smh_init_reg != NULL) {
+ smh_init_reg(&dt_region[reg], &mapped, &size);
+ } else {
+ mapped = (uint8_t *) dt_region[reg].addr;
+ size = dt_region[reg].size;
+ }
+
+ sys_heap_init(&heap_pool[attr][slot], mapped, size);
+ sys_multi_heap_add_heap(&shared_multi_heap, &heap_pool[attr][slot]);
+
+ slot++;
+ }
+ }
+}
+
+void shared_multi_heap_free(void *block)
+{
+ sys_multi_heap_free(&shared_multi_heap, block);
+}
+
+void *shared_multi_heap_alloc(enum smh_reg_attr attr, size_t bytes)
+{
+ return sys_multi_heap_alloc(&shared_multi_heap, (void *) attr, bytes);
+}
+
+int shared_multi_heap_pool_init(smh_init_reg_fn_t smh_init_reg_fn)
+{
+ smh_init_reg = smh_init_reg_fn;
+
+ sys_multi_heap_init(&shared_multi_heap, smh_choice);
+
+ for (size_t attr = 0; attr < SMH_REG_ATTR_NUM; attr++) {
+ smh_init_with_attr(attr);
+ }
+
+ return 0;
+}
+
+static int shared_multi_heap_init(const struct device *dev)
+{
+ __ASSERT_NO_MSG(NUM_REGIONS <= MAX_MULTI_HEAPS);
+
+ /* Nothing to do here. */
+
+ return 0;
+}
+SYS_INIT(shared_multi_heap_init, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);
diff --git a/tests/kernel/mem_heap/shared_multi_heap/CMakeLists.txt b/tests/kernel/mem_heap/shared_multi_heap/CMakeLists.txt
new file mode 100644
index 0000000..cc089db
--- /dev/null
+++ b/tests/kernel/mem_heap/shared_multi_heap/CMakeLists.txt
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: Apache-2.0
+
+cmake_minimum_required(VERSION 3.20.0)
+
+find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
+project(shared_multi_heap_pool)
+
+target_sources(app PRIVATE src/main.c)
diff --git a/tests/kernel/mem_heap/shared_multi_heap/boards/qemu_cortex_a53.overlay b/tests/kernel/mem_heap/shared_multi_heap/boards/qemu_cortex_a53.overlay
new file mode 100644
index 0000000..e516dc7
--- /dev/null
+++ b/tests/kernel/mem_heap/shared_multi_heap/boards/qemu_cortex_a53.overlay
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2021 Carlo Caione <ccaione@baylibre.com>
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+/ {
+ reserved-memory {
+ compatible = "reserved-memory";
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ res0: reserved@42000000 {
+ compatible = "shared-multi-heap";
+ reg = <0x42000000 0x1000>;
+ capability = "cacheable";
+ label = "res0";
+ };
+
+ res1: reserved@43000000 {
+ compatible = "shared-multi-heap";
+ reg = <0x43000000 0x2000>;
+ capability = "non-cacheable";
+ label = "res1";
+ };
+
+ res2: reserved2@44000000 {
+ compatible = "shared-multi-heap";
+ reg = <0x44000000 0x3000>;
+ capability = "cacheable";
+ label = "res2";
+ };
+ };
+};
diff --git a/tests/kernel/mem_heap/shared_multi_heap/linker_arm64_shared_pool.ld b/tests/kernel/mem_heap/shared_multi_heap/linker_arm64_shared_pool.ld
new file mode 100644
index 0000000..0906faa
--- /dev/null
+++ b/tests/kernel/mem_heap/shared_multi_heap/linker_arm64_shared_pool.ld
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) Carlo Caione <ccaione@baylibre.com>
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include <autoconf.h>
+#include <linker/sections.h>
+#include <devicetree.h>
+
+#include <linker/linker-defs.h>
+#include <linker/linker-tool.h>
+
+MEMORY
+{
+ LINKER_DT_RESERVED_MEM_REGIONS()
+}
+
+SECTIONS
+{
+ LINKER_DT_RESERVED_MEM_SECTIONS()
+}
+
+#include <arch/arm64/scripts/linker.ld>
diff --git a/tests/kernel/mem_heap/shared_multi_heap/prj.conf b/tests/kernel/mem_heap/shared_multi_heap/prj.conf
new file mode 100644
index 0000000..7efb4f4
--- /dev/null
+++ b/tests/kernel/mem_heap/shared_multi_heap/prj.conf
@@ -0,0 +1,7 @@
+# Copyright 2021 Carlo Caione <ccaione@baylibre.com>
+# SPDX-License-Identifier: Apache-2.0
+
+CONFIG_ZTEST=y
+CONFIG_HAVE_CUSTOM_LINKER_SCRIPT=y
+CONFIG_CUSTOM_LINKER_SCRIPT="linker_arm64_shared_pool.ld"
+CONFIG_SHARED_MULTI_HEAP=y
diff --git a/tests/kernel/mem_heap/shared_multi_heap/src/main.c b/tests/kernel/mem_heap/shared_multi_heap/src/main.c
new file mode 100644
index 0000000..2eaa6b9
--- /dev/null
+++ b/tests/kernel/mem_heap/shared_multi_heap/src/main.c
@@ -0,0 +1,129 @@
+/*
+ * Copyright (c) 2021 Carlo Caione <ccaione@baylibre.com>
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include <zephyr.h>
+#include <ztest.h>
+#include <linker/linker-defs.h>
+#include <sys/mem_manage.h>
+
+#include <multi_heap/shared_multi_heap.h>
+
+#define MAX_REGIONS (3)
+
+static struct {
+ struct shared_multi_heap_region *reg;
+ uint8_t *v_addr;
+} map[MAX_REGIONS];
+
+static bool smh_reg_init(struct shared_multi_heap_region *reg, uint8_t **v_addr, size_t *size)
+{
+ static int reg_idx;
+ uint32_t mem_attr;
+
+ mem_attr = (reg->attr == SMH_REG_ATTR_CACHEABLE) ? K_MEM_CACHE_WB : K_MEM_CACHE_NONE;
+ mem_attr |= K_MEM_PERM_RW;
+
+ z_phys_map(v_addr, reg->addr, reg->size, mem_attr);
+
+ *size = reg->size;
+
+ /* Save the mapping to retrieve the region from the vaddr */
+ map[reg_idx].reg = reg;
+ map[reg_idx].v_addr = *v_addr;
+
+ reg_idx++;
+
+ return true;
+}
+
+static struct shared_multi_heap_region *get_reg_addr(uint8_t *v_addr)
+{
+ for (size_t reg = 0; reg < MAX_REGIONS; reg++) {
+ if (v_addr >= map[reg].v_addr &&
+ v_addr < map[reg].v_addr + map[reg].reg->size) {
+ return map[reg].reg;
+ }
+ }
+ return NULL;
+}
+
+void test_shared_multi_heap(void)
+{
+ struct shared_multi_heap_region *reg;
+ uint8_t *block;
+
+ shared_multi_heap_pool_init(smh_reg_init);
+
+ /*
+ * Request a small cacheable chunk. It should be allocated in the
+ * smaller region (@ 0x42000000)
+ */
+ block = shared_multi_heap_alloc(SMH_REG_ATTR_CACHEABLE, 0x40);
+ reg = get_reg_addr(block);
+
+ zassert_equal(reg->addr, 0x42000000, "block in the wrong memory region");
+ zassert_equal(reg->attr, SMH_REG_ATTR_CACHEABLE, "wrong memery attribute");
+
+ /*
+ * Request another small cacheable chunk. It should be allocated in the
+ * smaller cacheable region (@ 0x42000000)
+ */
+ block = shared_multi_heap_alloc(SMH_REG_ATTR_CACHEABLE, 0x80);
+ reg = get_reg_addr(block);
+
+ zassert_equal(reg->addr, 0x42000000, "block in the wrong memory region");
+ zassert_equal(reg->attr, SMH_REG_ATTR_CACHEABLE, "wrong memory attribute");
+
+ /*
+ * Request a big cacheable chunk. It should be allocated in the
+ * bigger cacheable region (@ 0x44000000)
+ */
+ block = shared_multi_heap_alloc(SMH_REG_ATTR_CACHEABLE, 0x1200);
+ reg = get_reg_addr(block);
+
+ zassert_equal(reg->addr, 0x44000000, "block in the wrong memory region");
+ zassert_equal(reg->attr, SMH_REG_ATTR_CACHEABLE, "wrong memory attribute");
+
+ /*
+ * Request a non-cacheable chunk. It should be allocated in the
+ * non-cacheable region (@ 0x43000000)
+ */
+ block = shared_multi_heap_alloc(SMH_REG_ATTR_NON_CACHEABLE, 0x100);
+ reg = get_reg_addr(block);
+
+ zassert_equal(reg->addr, 0x43000000, "block in the wrong memory region");
+ zassert_equal(reg->attr, SMH_REG_ATTR_NON_CACHEABLE, "wrong memory attribute");
+
+ /*
+ * Request again a non-cacheable chunk. It should be allocated in the
+ * non-cacheable region (@ 0x43000000)
+ */
+ block = shared_multi_heap_alloc(SMH_REG_ATTR_NON_CACHEABLE, 0x100);
+ reg = get_reg_addr(block);
+
+ zassert_equal(reg->addr, 0x43000000, "block in the wrong memory region");
+ zassert_equal(reg->attr, SMH_REG_ATTR_NON_CACHEABLE, "wrong memory attribute");
+
+ /* Request a block too big */
+ block = shared_multi_heap_alloc(SMH_REG_ATTR_NON_CACHEABLE, 0x10000);
+ zassert_is_null(block, "allocated buffer too big for the region");
+
+ /* Request a 0-sized block */
+ block = shared_multi_heap_alloc(SMH_REG_ATTR_NON_CACHEABLE, 0);
+ zassert_is_null(block, "0 size accepted as valid");
+
+ /* Request a non-existent attribute */
+ block = shared_multi_heap_alloc(SMH_REG_ATTR_NUM + 1, 0x100);
+ zassert_is_null(block, "wrong attribute accepted as valid");
+
+}
+
+void test_main(void)
+{
+ ztest_test_suite(shared_multi_heap,
+ ztest_1cpu_unit_test(test_shared_multi_heap));
+ ztest_run_test_suite(shared_multi_heap);
+}
diff --git a/tests/kernel/mem_heap/shared_multi_heap/testcase.yaml b/tests/kernel/mem_heap/shared_multi_heap/testcase.yaml
new file mode 100644
index 0000000..4bf3559
--- /dev/null
+++ b/tests/kernel/mem_heap/shared_multi_heap/testcase.yaml
@@ -0,0 +1,8 @@
+# Copyright 2021 Carlo Caione <ccaione@baylibre.com>
+# SPDX-License-Identifier: Apache-2.0
+
+tests:
+ kernel.shared_multi_heap:
+ platform_allow: qemu_cortex_a53
+ tags: board multi_heap
+ harness: ztest