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>
This commit is contained in:
Carlo Caione 2021-09-23 17:51:25 +02:00 committed by Anas Nashif
parent 863600cd0e
commit 43cb00df08
13 changed files with 564 additions and 0 deletions

View file

@ -9,3 +9,4 @@ The following contains various topics regarding memory management.
:maxdepth: 1
demand_paging.rst
shared_multi_heap.rst

View file

@ -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

View file

@ -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

View file

@ -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_ */

View file

@ -43,6 +43,8 @@ zephyr_sources_ifdef(CONFIG_SCHED_DEADLINE p4wq.c)
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

View file

@ -63,6 +63,14 @@ config MPSC_PBUF
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"

117
lib/os/shared_multi_heap.c Normal file
View file

@ -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);

View file

@ -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)

View file

@ -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";
};
};
};

View file

@ -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>

View file

@ -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

View file

@ -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);
}

View file

@ -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