-
Notifications
You must be signed in to change notification settings - Fork 8.2k
Introducing a memory attributes based heap allocator #63014
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -84,7 +84,112 @@ one by renaming the property and changing its value according to the following l | |
| "IO" -> <( DT_ARM_MPU(ATTR_MPU_IO) )> | ||
| "EXTMEM" -> <( DT_ARM_MPU(ATTR_MPU_EXTMEM) )> | ||
|
|
||
| Memory Attributes Heap Allocator | ||
| ******************************** | ||
|
|
||
| It is possible to leverage the memory attribute property ``zephyr,memory-attr`` | ||
| to define and create a set of memory heaps from which the user can allocate | ||
| memory from with certain attributes / capabilities. | ||
|
|
||
| When the :kconfig:option:`CONFIG_MEM_ATTR_HEAP` is set, every region marked | ||
| with one of the memory attributes listed in in | ||
| :zephyr_file:`include/zephyr/dt-bindings/memory-attr/memory-attr-sw.h` is added | ||
| to a pool of memory heaps used for dynamic allocation of memory buffers with | ||
| certain attributes. | ||
|
|
||
| Here a non exhaustive list of possible attributes: | ||
|
|
||
| .. code-block:: none | ||
|
|
||
| DT_MEM_SW_ALLOC_CACHE | ||
| DT_MEM_SW_ALLOC_NON_CACHE | ||
| DT_MEM_SW_ALLOC_DMA | ||
|
|
||
| For example we can define several memory regions with different attributes and | ||
| use the appropriate attribute to indicate that it is possible to dynamically | ||
| allocate memory from those regions: | ||
|
|
||
| .. code-block:: devicetree | ||
|
|
||
| mem_cacheable: memory@10000000 { | ||
| compatible = "mmio-sram"; | ||
| reg = <0x10000000 0x1000>; | ||
| zephyr,memory-attr = <( DT_MEM_CACHEABLE | DT_MEM_SW_ALLOC_CACHE )>; | ||
| }; | ||
|
|
||
| mem_non_cacheable: memory@20000000 { | ||
| compatible = "mmio-sram"; | ||
| reg = <0x20000000 0x1000>; | ||
| zephyr,memory-attr = <( DT_MEM_NON_CACHEABLE | ATTR_SW_ALLOC_NON_CACHE )>; | ||
| }; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ATTR_SW_ALLOC_NON_CACHE should be DT_MEM_SW_ALLOC_NON_CACHE? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. huuu, yeah, I'll fix that in a later commit. It would suck to lose all the ACKs to fix spelling errors. |
||
|
|
||
| mem_cacheable_big: memory@30000000 { | ||
| compatible = "mmio-sram"; | ||
| reg = <0x30000000 0x10000>; | ||
| zephyr,memory-attr = <( DT_MEM_CACHEABLE | DT_MEM_OOO | DT_MEM_SW_ALLOC_CACHE )>; | ||
| }; | ||
|
|
||
| mem_cacheable_dma: memory@40000000 { | ||
| compatible = "mmio-sram"; | ||
| reg = <0x40000000 0x10000>; | ||
| zephyr,memory-attr = <( DT_MEM_CACHEABLE | DT_MEM_DMA | | ||
| DT_MEM_SW_ALLOC_CACHE | DT_MEM_SW_ALLOC_DMA )>; | ||
| }; | ||
|
|
||
| The user can then dynamically carve memory out of those regions using the | ||
| provided functions, the library will take care of allocating memory from the | ||
| correct heap depending on the provided attribute and size: | ||
|
|
||
| .. code-block:: c | ||
|
|
||
| // Init the pool | ||
| mem_attr_heap_pool_init(); | ||
|
|
||
| // Allocate 0x100 bytes of cacheable memory from `mem_cacheable` | ||
| block = mem_attr_heap_alloc(DT_MEM_SW_ALLOC_CACHE, 0x100); | ||
|
|
||
| // Allocate 0x200 bytes of non-cacheable memory aligned to 32 bytes | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Allocate 0x100? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ditto |
||
| // from `mem_non_cacheable` | ||
| block = mem_attr_heap_aligned_alloc(ATTR_SW_ALLOC_NON_CACHE, 0x100, 32); | ||
|
|
||
| // Allocate 0x100 bytes of cacheable and dma-able memory from `mem_cacheable_dma` | ||
| block = mem_attr_heap_alloc(DT_MEM_SW_ALLOC_CACHE | DT_MEM_SW_ALLOC_DMA, 0x100); | ||
|
|
||
| When several regions are marked with the same attributes, the memory is allocated: | ||
|
|
||
| 1. From the regions where the ``zephyr,memory-attr`` property has the requested | ||
| property (or properties). | ||
|
|
||
| 2. Among the regions as at point 1, from the smallest region if there is any | ||
| unallocated space left for the requested size | ||
|
|
||
| 3. If there is not enough space, from the next bigger region able to | ||
| accommodate the requested size | ||
|
|
||
| The following example shows the point 3: | ||
|
|
||
| .. code-block:: c | ||
|
|
||
| // This memory is allocated from `mem_non_cacheable` | ||
| block = mem_attr_heap_alloc(DT_MEM_SW_ALLOC_CACHE, 0x100); | ||
|
|
||
| // This memory is allocated from `mem_cacheable_big` | ||
| block = mem_attr_heap_alloc(DT_MEM_SW_ALLOC_CACHE, 0x5000); | ||
|
|
||
| .. note:: | ||
|
|
||
| The framework is assuming that the memory regions used to create the heaps | ||
| are usable by the code and available at init time. The user must take of | ||
| initializing and setting the memory area before calling | ||
| :c:func:`mem_attr_heap_pool_init`. | ||
|
|
||
| That means that the region must be correctly configured in terms of MPU / | ||
| MMU (if needed) and that an actual heap can be created out of it, for | ||
| example by leveraging the ``zephyr,memory-region`` property to create a | ||
| proper linker section to accommodate the heap. | ||
|
|
||
| API Reference | ||
| ************* | ||
|
|
||
| .. doxygengroup:: memory_attr_interface | ||
| .. doxygengroup:: memory_attr_heap | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| /* | ||
| * Copyright (c) 2023 Carlo Caione <[email protected]> | ||
| * | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
| #ifndef ZEPHYR_INCLUDE_DT_BINDINGS_MEM_ATTR_SW_H_ | ||
| #define ZEPHYR_INCLUDE_DT_BINDINGS_MEM_ATTR_SW_H_ | ||
|
|
||
| #include <zephyr/sys/util_macro.h> | ||
| #include <zephyr/dt-bindings/memory-attr/memory-attr.h> | ||
|
|
||
| /* | ||
| * Software specific memory attributes. | ||
| */ | ||
| #define DT_MEM_SW_MASK DT_MEM_SW_ATTR_MASK | ||
| #define DT_MEM_SW_GET(x) ((x) & DT_MEM_SW_ATTR_MASK) | ||
| #define DT_MEM_SW(x) ((x) << DT_MEM_SW_ATTR_SHIFT) | ||
|
|
||
| #define ATTR_SW_ALLOC_CACHE BIT(0) | ||
| #define ATTR_SW_ALLOC_NON_CACHE BIT(1) | ||
| #define ATTR_SW_ALLOC_DMA BIT(2) | ||
|
|
||
| #define DT_MEM_SW_ALLOC_CACHE DT_MEM_SW(ATTR_SW_ALLOC_CACHE) | ||
| #define DT_MEM_SW_ALLOC_NON_CACHE DT_MEM_SW(ATTR_SW_ALLOC_NON_CACHE) | ||
| #define DT_MEM_SW_ALLOC_DMA DT_MEM_SW(ATTR_SW_ALLOC_DMA) | ||
|
|
||
| #endif /* ZEPHYR_INCLUDE_DT_BINDINGS_MEM_ATTR_SW_H_ */ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,98 @@ | ||
| /* | ||
| * Copyright (c) 2023 Carlo Caione, <[email protected]> | ||
| * | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
|
|
||
| #ifndef ZEPHYR_INCLUDE_MEM_ATTR_HEAP_H_ | ||
| #define ZEPHYR_INCLUDE_MEM_ATTR_HEAP_H_ | ||
|
|
||
| /** | ||
| * @brief Memory heaps based on memory attributes | ||
| * @defgroup memory_attr_heap Memory heaps based on memory attributes | ||
| * @ingroup mem_mgmt | ||
| * @{ | ||
| */ | ||
|
|
||
| #include <zephyr/mem_mgmt/mem_attr.h> | ||
|
|
||
| #ifdef __cplusplus | ||
| extern "C" { | ||
| #endif | ||
|
|
||
| /** | ||
| * @brief Init the memory pool | ||
| * | ||
| * This must be the first function to be called to initialize the memory pools | ||
| * from all the memory regions with the a software attribute. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: "the a software attribute" There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ditto |
||
| * | ||
| * @retval 0 on success. | ||
| * @retval -EALREADY if the pool was already initialized. | ||
| * @retval -ENOMEM too many regions already allocated. | ||
| */ | ||
| int mem_attr_heap_pool_init(void); | ||
carlocaione marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| /** | ||
| * @brief Allocate memory with a specified attribute and size. | ||
| * | ||
| * Allocates a block of memory of the specified size in bytes and with a | ||
| * specified capability / attribute. The attribute is used to select the | ||
| * correct memory heap to allocate memory from. | ||
| * | ||
| * @param attr capability / attribute requested for the memory block. | ||
| * @param bytes requested size of the allocation in bytes. | ||
| * | ||
| * @retval ptr a valid pointer to the allocated memory. | ||
| * @retval NULL if no memory is available with that attribute and size. | ||
| */ | ||
| void *mem_attr_heap_alloc(uint32_t attr, size_t bytes); | ||
|
|
||
| /** | ||
| * @brief Allocate aligned memory with a specified attribute, size and alignment. | ||
| * | ||
| * Allocates a block of memory of the specified size in bytes and with a | ||
| * specified capability / attribute. Takes an additional parameter specifying a | ||
| * power of two alignment in bytes. | ||
| * | ||
| * @param attr capability / attribute requested for the memory block. | ||
| * @param align power of two alignment for the returned pointer in bytes. | ||
| * @param bytes requested size of the allocation in bytes. | ||
| * | ||
| * @retval ptr a valid pointer to the allocated memory. | ||
| * @retval NULL if no memory is available with that attribute and size. | ||
| */ | ||
| void *mem_attr_heap_aligned_alloc(uint32_t attr, size_t align, size_t bytes); | ||
|
|
||
| /** | ||
| * @brief Free the allocated memory | ||
| * | ||
| * Used to free the passed block of memory that must be the return value of a | ||
| * previously call to @ref mem_attr_heap_alloc or @ref | ||
| * mem_attr_heap_aligned_alloc. | ||
| * | ||
| * @param block block to free, must be a pointer to a block allocated by | ||
| * @ref mem_attr_heap_alloc or @ref mem_attr_heap_aligned_alloc. | ||
| */ | ||
| void mem_attr_heap_free(void *block); | ||
|
|
||
| /** | ||
| * @brief Get a specific memory region descriptor for a provided address | ||
| * | ||
| * Finds the memory region descriptor struct controlling the provided pointer. | ||
| * | ||
| * @param addr address to be found, must be a pointer to a block allocated by | ||
| * @ref mem_attr_heap_alloc or @ref mem_attr_heap_aligned_alloc. | ||
| * | ||
| * @retval str pointer to a memory region structure the address belongs to. | ||
| */ | ||
| const struct mem_attr_region_t *mem_attr_heap_get_region(void *addr); | ||
|
|
||
| #ifdef __cplusplus | ||
| } | ||
| #endif | ||
|
|
||
| /** | ||
| * @} | ||
| */ | ||
|
|
||
| #endif /* ZEPHYR_INCLUDE_MEM_ATTR_HEAP_H_ */ | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,4 @@ | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| zephyr_sources_ifdef(CONFIG_MEM_ATTR mem_attr.c) | ||
| zephyr_sources_ifdef(CONFIG_MEM_ATTR_HEAP mem_attr_heap.c) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,136 @@ | ||
| /* | ||
| * Copyright (c) 2021 Carlo Caione, <[email protected]> | ||
| * | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
|
|
||
| #include <zephyr/kernel.h> | ||
| #include <zephyr/device.h> | ||
| #include <zephyr/sys/sys_heap.h> | ||
| #include <zephyr/mem_mgmt/mem_attr.h> | ||
| #include <zephyr/sys/multi_heap.h> | ||
| #include <zephyr/dt-bindings/memory-attr/memory-attr.h> | ||
| #include <zephyr/dt-bindings/memory-attr/memory-attr-sw.h> | ||
|
|
||
| struct ma_heap { | ||
| struct sys_heap heap; | ||
| uint32_t attr; | ||
| }; | ||
|
|
||
| struct { | ||
| struct ma_heap ma_heaps[MAX_MULTI_HEAPS]; | ||
carlocaione marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| struct sys_multi_heap multi_heap; | ||
| int nheaps; | ||
| } mah_data; | ||
|
|
||
| static void *mah_choice(struct sys_multi_heap *m_heap, void *cfg, size_t align, size_t size) | ||
| { | ||
| uint32_t attr; | ||
| void *block; | ||
|
|
||
| if (size == 0) { | ||
| return NULL; | ||
| } | ||
|
|
||
| attr = (uint32_t)(long) cfg; | ||
|
|
||
| /* Set in case the user requested a non-existing attr */ | ||
| block = NULL; | ||
|
|
||
| for (size_t hdx = 0; hdx < mah_data.nheaps; hdx++) { | ||
| struct ma_heap *h; | ||
|
|
||
| h = &mah_data.ma_heaps[hdx]; | ||
|
|
||
| if (h->attr != attr) { | ||
| continue; | ||
| } | ||
|
|
||
| block = sys_heap_aligned_alloc(&h->heap, align, size); | ||
| if (block != NULL) { | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| return block; | ||
| } | ||
|
|
||
| void mem_attr_heap_free(void *block) | ||
| { | ||
| sys_multi_heap_free(&mah_data.multi_heap, block); | ||
| } | ||
|
|
||
| void *mem_attr_heap_alloc(uint32_t attr, size_t bytes) | ||
| { | ||
| return sys_multi_heap_alloc(&mah_data.multi_heap, | ||
| (void *)(long) attr, bytes); | ||
| } | ||
|
|
||
| void *mem_attr_heap_aligned_alloc(uint32_t attr, size_t align, size_t bytes) | ||
| { | ||
| return sys_multi_heap_aligned_alloc(&mah_data.multi_heap, | ||
| (void *)(long) attr, align, bytes); | ||
| } | ||
|
|
||
| const struct mem_attr_region_t *mem_attr_heap_get_region(void *addr) | ||
| { | ||
| const struct sys_multi_heap_rec *heap_rec; | ||
|
|
||
| heap_rec = sys_multi_heap_get_heap(&mah_data.multi_heap, addr); | ||
|
|
||
| return (const struct mem_attr_region_t *) heap_rec->user_data; | ||
| } | ||
|
|
||
| static int ma_heap_add(const struct mem_attr_region_t *region, uint32_t attr) | ||
carlocaione marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| { | ||
| struct ma_heap *mh; | ||
| struct sys_heap *h; | ||
|
|
||
| /* No more heaps available */ | ||
| if (mah_data.nheaps >= MAX_MULTI_HEAPS) { | ||
| return -ENOMEM; | ||
| } | ||
|
|
||
| mh = &mah_data.ma_heaps[mah_data.nheaps++]; | ||
| h = &mh->heap; | ||
|
|
||
| mh->attr = attr; | ||
|
|
||
| sys_heap_init(h, (void *) region->dt_addr, region->dt_size); | ||
| sys_multi_heap_add_heap(&mah_data.multi_heap, h, (void *) region); | ||
|
|
||
| return 0; | ||
| } | ||
|
|
||
|
|
||
| int mem_attr_heap_pool_init(void) | ||
| { | ||
| const struct mem_attr_region_t *regions; | ||
| static atomic_t state; | ||
| size_t num_regions; | ||
|
|
||
| if (!atomic_cas(&state, 0, 1)) { | ||
| return -EALREADY; | ||
| } | ||
|
|
||
| sys_multi_heap_init(&mah_data.multi_heap, mah_choice); | ||
|
|
||
| num_regions = mem_attr_get_regions(®ions); | ||
|
|
||
| for (size_t idx = 0; idx < num_regions; idx++) { | ||
| uint32_t sw_attr; | ||
|
|
||
| sw_attr = DT_MEM_SW_ATTR_GET(regions[idx].dt_attr); | ||
|
|
||
| /* No SW attribute is present */ | ||
| if (!sw_attr) { | ||
| continue; | ||
| } | ||
|
|
||
| if (ma_heap_add(®ions[idx], sw_attr)) { | ||
| return -ENOMEM; | ||
| } | ||
| } | ||
|
|
||
| return 0; | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It worth to mention here if it is possible to specify multiple
DT_MEM_SW_ALLOC*attributes. LikeDT_MEM_SW_ALLOC_DMA | DT_MEM_SW_ALLOC_NON_CACHE.As I understand that wouldn't work (please correct me if I'm wrong) - so we should mention that in the docs and it would be useful to add compile-time assertion against such combination.
Alternatively we can just define
DT_MEM_SW_ALLOC*attributes not as a individual bits but as an enum:There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would work. From the allocator prospective
DT_MEM_SW_ALLOC_CACHEis different thanDT_MEM_SW_ALLOC_CACHE | DT_MEM_SW_ALLOC_DMA, so you can allocate from one region or from the other region depending on the parameter passed tomem_attr_heap_alloc().I'll add a new test for this case and I'll fix the documentation that indeed is a bit unclear on this point.