diff --git a/src/include/sof/objpool.h b/src/include/sof/objpool.h new file mode 100644 index 000000000000..64d677a7b52e --- /dev/null +++ b/src/include/sof/objpool.h @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright(c) 2025 Intel Corporation. + */ + +#ifndef __ZEPHYR_OBJPOOL_H__ +#define __ZEPHYR_OBJPOOL_H__ + +struct list_item; +/** + * Allocate memory tracked as part of an object pool. + * + * @param head Pointer to the object pool list head. + * @param size Size in bytes of memory blocks to allocate. + * + * @return a pointer to the allocated memory on success, NULL on failure. + * + * Allocate a memory block of @a size bytes. @a size is used upon the first + * invocation to allocate memory on the heap, all consequent allocations with + * the same @a head must use the same @a size value. First allocation with an + * empty @a head allocates 2 blocks. After both blocks are taken and a third one + * is requested, the next call allocates 4 blocks, then 8, 16 and 32. After that + * 32 blocks are allocated every time. Note, that by design allocated blocks are + * never freed. See more below. + */ +void *objpool_alloc(struct list_item *head, size_t size); + +/** + * Return a block to the object pool + * + * @param head Pointer to the object pool list head. + * @param size Size in bytes of memory blocks to allocate. + * + * @return 0 on success or a negative error code. + * + * Return a block to the object pool. Memory is never freed by design, unused + * blocks are kept in the object pool for future re-use. + */ +int objpool_free(struct list_item *head, void *data); + +#endif diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt index f7a37d50cd72..707753635f69 100644 --- a/src/lib/CMakeLists.txt +++ b/src/lib/CMakeLists.txt @@ -1,6 +1,6 @@ # SPDX-License-Identifier: BSD-3-Clause -set(common_files notifier.c dma.c dai.c) +set(common_files notifier.c dma.c dai.c objpool.c) if(CONFIG_LIBRARY) add_local_sources(sof diff --git a/src/lib/objpool.c b/src/lib/objpool.c new file mode 100644 index 000000000000..aa28ae491794 --- /dev/null +++ b/src/lib/objpool.c @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2025 Intel Corporation. + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +struct objpool { + struct list_item list; + unsigned int n; + uint32_t mask; + size_t size; + uint8_t data[]; +}; + +#define OBJPOOL_BITS (sizeof(((struct objpool *)0)->mask) * 8) + +static int objpool_add(struct list_item *head, unsigned int n, size_t size) +{ + if (n > OBJPOOL_BITS) + return -ENOMEM; + + if (!is_power_of_2(n)) + return -EINVAL; + + size_t aligned_size = ALIGN_UP(size, sizeof(int)); + + /* Initialize with 0 to give caller a chance to identify new allocations */ + struct objpool *pobjpool = rzalloc(0, n * aligned_size + sizeof(*pobjpool)); + + if (!pobjpool) + return -ENOMEM; + + pobjpool->n = n; + /* clear bit means free */ + pobjpool->mask = 0; + pobjpool->size = size; + + list_item_append(&pobjpool->list, head); + + return 0; +} + +void *objpool_alloc(struct list_item *head, size_t size) +{ + size_t aligned_size = ALIGN_UP(size, sizeof(int)); + struct list_item *list; + struct objpool *pobjpool; + + /* Make sure size * 32 still fits in OBJPOOL_BITS bits */ + if (!size || aligned_size > (UINT_MAX >> 5) - sizeof(*pobjpool)) + return NULL; + + list_for_item(list, head) { + pobjpool = container_of(list, struct objpool, list); + + uint32_t free_mask = MASK(pobjpool->n - 1, 0) & ~pobjpool->mask; + + /* sanity check */ + if (size != pobjpool->size) + return NULL; + + if (!free_mask) + continue; + + /* Find first free - guaranteed valid now */ + unsigned int bit = ffs(free_mask) - 1; + + pobjpool->mask |= BIT(bit); + + return pobjpool->data + aligned_size * bit; + } + + /* no free elements found */ + unsigned int new_n; + + if (list_is_empty(head)) { + new_n = 2; + } else { + /* Check the last one */ + pobjpool = container_of(head->prev, struct objpool, list); + + if (pobjpool->n == OBJPOOL_BITS) + new_n = OBJPOOL_BITS; + else + new_n = pobjpool->n << 1; + } + + if (objpool_add(head, new_n, size) < 0) + return NULL; + + /* Return the first element of the new objpool, which is now the last one in the list */ + pobjpool = container_of(head->prev, struct objpool, list); + pobjpool->mask = 1; + + return pobjpool->data; +} + +int objpool_free(struct list_item *head, void *data) +{ + struct list_item *list; + struct objpool *pobjpool; + + list_for_item(list, head) { + pobjpool = container_of(list, struct objpool, list); + + size_t aligned_size = ALIGN_UP(pobjpool->size, sizeof(int)); + + if ((uint8_t *)data >= pobjpool->data && + (uint8_t *)data < pobjpool->data + aligned_size * pobjpool->n) { + unsigned int n = ((uint8_t *)data - pobjpool->data) / aligned_size; + + if ((uint8_t *)data != pobjpool->data + n * aligned_size) + return -EINVAL; + + pobjpool->mask &= ~BIT(n); + + return 0; + } + } + + return -EINVAL; +} diff --git a/test/ztest/unit/fast-get/prj.conf b/test/ztest/unit/fast-get/prj.conf index f530dfdd94f9..d34c7781cd0a 100644 --- a/test/ztest/unit/fast-get/prj.conf +++ b/test/ztest/unit/fast-get/prj.conf @@ -1,2 +1,2 @@ CONFIG_ZTEST=y -CONFIG_SOF_FULL_ZEPHYR_APPLICATION=n \ No newline at end of file +CONFIG_SOF_FULL_ZEPHYR_APPLICATION=n diff --git a/test/ztest/unit/list/prj.conf b/test/ztest/unit/list/prj.conf index f530dfdd94f9..d34c7781cd0a 100644 --- a/test/ztest/unit/list/prj.conf +++ b/test/ztest/unit/list/prj.conf @@ -1,2 +1,2 @@ CONFIG_ZTEST=y -CONFIG_SOF_FULL_ZEPHYR_APPLICATION=n \ No newline at end of file +CONFIG_SOF_FULL_ZEPHYR_APPLICATION=n diff --git a/test/ztest/unit/math/basic/arithmetic/prj.conf b/test/ztest/unit/math/basic/arithmetic/prj.conf index f530dfdd94f9..d34c7781cd0a 100644 --- a/test/ztest/unit/math/basic/arithmetic/prj.conf +++ b/test/ztest/unit/math/basic/arithmetic/prj.conf @@ -1,2 +1,2 @@ CONFIG_ZTEST=y -CONFIG_SOF_FULL_ZEPHYR_APPLICATION=n \ No newline at end of file +CONFIG_SOF_FULL_ZEPHYR_APPLICATION=n diff --git a/test/ztest/unit/math/basic/trigonometry/prj.conf b/test/ztest/unit/math/basic/trigonometry/prj.conf index f530dfdd94f9..d34c7781cd0a 100644 --- a/test/ztest/unit/math/basic/trigonometry/prj.conf +++ b/test/ztest/unit/math/basic/trigonometry/prj.conf @@ -1,2 +1,2 @@ CONFIG_ZTEST=y -CONFIG_SOF_FULL_ZEPHYR_APPLICATION=n \ No newline at end of file +CONFIG_SOF_FULL_ZEPHYR_APPLICATION=n diff --git a/test/ztest/unit/objpool/CMakeLists.txt b/test/ztest/unit/objpool/CMakeLists.txt new file mode 100644 index 000000000000..e1c1abefa0d7 --- /dev/null +++ b/test/ztest/unit/objpool/CMakeLists.txt @@ -0,0 +1,30 @@ +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(test_objpool) + +set(SOF_ROOT "${PROJECT_SOURCE_DIR}/../../../..") + +# Include SOF CMake functions +include(${SOF_ROOT}/scripts/cmake/misc.cmake) + +target_include_directories(app PRIVATE + ${SOF_ROOT}/zephyr/include + ${SOF_ROOT}/src/include + ${SOF_ROOT}/src/platform/posix/include +) + +# Define SOF-specific configurations for unit testing +target_compile_definitions(app PRIVATE + -DCONFIG_ZEPHYR_POSIX=1 +) + +target_sources(app PRIVATE + test_objpool_ztest.c + ${SOF_ROOT}/src/lib/objpool.c +) + +target_link_libraries(app PRIVATE "-Wl,--wrap=rzalloc") + +# Add RELATIVE_FILE definitions for SOF trace functionality +sof_append_relative_path_definitions(app) diff --git a/test/ztest/unit/objpool/prj.conf b/test/ztest/unit/objpool/prj.conf new file mode 100644 index 000000000000..d34c7781cd0a --- /dev/null +++ b/test/ztest/unit/objpool/prj.conf @@ -0,0 +1,2 @@ +CONFIG_ZTEST=y +CONFIG_SOF_FULL_ZEPHYR_APPLICATION=n diff --git a/test/ztest/unit/objpool/test_objpool_ztest.c b/test/ztest/unit/objpool/test_objpool_ztest.c new file mode 100644 index 000000000000..1fcc65f4ec51 --- /dev/null +++ b/test/ztest/unit/objpool/test_objpool_ztest.c @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2025 Intel Corporation. + +#define DATA_SIZE 5 +#define ALIGNED_SIZE ALIGN_UP(DATA_SIZE, sizeof(int)) + +#include +#include +#include +#include +#include +#include + +void *__wrap_rzalloc(uint32_t flags, size_t bytes) +{ + (void)flags; + + void *ret = malloc(bytes); + + if (ret) + memset(ret, 0, bytes); + + return ret; +} + +ZTEST(objpool_suite, test_objpool_wrong_size) +{ + struct list_item head = LIST_INIT(head); + /* new object pool of 2 blocks */ + uint8_t *block1 = objpool_alloc(&head, DATA_SIZE); + /* should fail because of a different size */ + uint8_t *block2 = objpool_alloc(&head, DATA_SIZE + 1); + /* second block in the first object pool */ + uint8_t *block3 = objpool_alloc(&head, DATA_SIZE); + /* new object pool of 4 blocks */ + uint8_t *block4 = objpool_alloc(&head, DATA_SIZE); + /* should fail because of a different size */ + uint8_t *block5 = objpool_alloc(&head, DATA_SIZE * 2); + + zassert_not_null(block1); + zassert_is_null(block2); + zassert_not_null(block3); + zassert_not_null(block4); + zassert_is_null(block5); + + zassert_not_ok(objpool_free(&head, block1 + 1)); + zassert_ok(objpool_free(&head, block1)); + zassert_not_ok(objpool_free(&head, block3 + 1)); + zassert_ok(objpool_free(&head, block3)); + zassert_not_ok(objpool_free(&head, block4 + 1)); + zassert_ok(objpool_free(&head, block4)); +} + +ZTEST(objpool_suite, test_objpool) +{ + struct list_item head = LIST_INIT(head); + void *blocks[62]; /* 2 + 4 + 8 + 16 + 32 */ + unsigned int k = 0; + + /* Loop over all powers: 2^1..2^5 */ + for (unsigned int i = 1; i <= 5; i++) { + unsigned int n = 1 << i; + uint8_t *start; + + for (unsigned int j = 0; j < n; j++) { + uint8_t *block = objpool_alloc(&head, DATA_SIZE); + + zassert_not_null(block, "allocation failed loop %u iter %u", i, j); + + if (!j) + start = block; + else + zassert_equal(block, start + ALIGNED_SIZE * j, "wrong pointer"); + + blocks[k++] = block; + } + } + + while (k--) + zassert_ok(objpool_free(&head, blocks[k]), "free failed"); +} + +ZTEST_SUITE(objpool_suite, NULL, NULL, NULL, NULL, NULL); diff --git a/test/ztest/unit/objpool/testcase.yaml b/test/ztest/unit/objpool/testcase.yaml new file mode 100644 index 000000000000..dc137a22adc4 --- /dev/null +++ b/test/ztest/unit/objpool/testcase.yaml @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: BSD-3-Clause +# +# Copyright(c) 2025 Intel Corporation. +# +# Object pool allocator unit tests for Ztest framework + +tests: + sof.objpool: + tags: unit + platform_allow: native_sim + integration_platforms: + - native_sim + build_only: false