diff --git a/src/include/sof/batch.h b/src/include/sof/batch.h new file mode 100644 index 000000000000..e276b23f1e19 --- /dev/null +++ b/src/include/sof/batch.h @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright(c) 2025 Intel Corporation. + */ + +#ifndef __ZEPHYR_BATCH_H__ +#define __ZEPHYR_BATCH_H__ + +struct list_item; +/** + * Allocate memory tracked as part of a batch. + * + * @param head Pointer to the batch 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 *batch_alloc(struct list_item *head, size_t size); + +/** + * Return a block to the batch + * + * @param head Pointer to the batch 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 batch. Memory is never freed by design, unused blocks + * are kept in the batch for future re-use. + */ +int batch_free(struct list_item *head, void *data); + +#endif diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt index f7a37d50cd72..1ed6a6190fa0 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 batch.c) if(CONFIG_LIBRARY) add_local_sources(sof diff --git a/src/lib/batch.c b/src/lib/batch.c new file mode 100644 index 000000000000..dc378bb0dac0 --- /dev/null +++ b/src/lib/batch.c @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2025 Intel Corporation. + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +struct batch { + struct list_item list; + unsigned int n; + uint32_t mask; + size_t size; + uint8_t data[]; +}; + +static int batch_add(struct list_item *head, unsigned int n, size_t size) +{ + if (n > 32) + 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 batch *abatch = rzalloc(0, n * aligned_size + sizeof(*abatch)); + + if (!abatch) + return -ENOMEM; + + abatch->n = n; + /* clear bit means free */ + abatch->mask = 0; + abatch->size = size; + + list_item_append(&abatch->list, head); + + return 0; +} + +void *batch_alloc(struct list_item *head, size_t size) +{ + size_t aligned_size = ALIGN_UP(size, sizeof(int)); + struct list_item *list; + struct batch *abatch; + + /* Make sure size * 32 still fits in 32 bits */ + if (!size || aligned_size > (UINT_MAX >> 5) - sizeof(*abatch)) + return NULL; + + list_for_item(list, head) { + abatch = container_of(list, struct batch, list); + + uint32_t free_mask = MASK(abatch->n - 1, 0) & ~abatch->mask; + + /* sanity check */ + if (size != abatch->size) + return NULL; + + if (!free_mask) + continue; + + /* Find first free - guaranteed valid now */ + unsigned int bit = ffs(free_mask) - 1; + + abatch->mask |= BIT(bit); + + return abatch->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 */ + abatch = container_of(head->prev, struct batch, list); + + if (abatch->n == 32) + new_n = 32; + else + new_n = abatch->n << 1; + } + + if (batch_add(head, new_n, size) < 0) + return NULL; + + /* Return the first element of the new batch, which is now the last one in the list */ + abatch = container_of(head->prev, struct batch, list); + abatch->mask = 1; + + return abatch->data; +} + +int batch_free(struct list_item *head, void *data) +{ + struct list_item *list; + struct batch *abatch; + + list_for_item(list, head) { + abatch = container_of(list, struct batch, list); + + size_t aligned_size = ALIGN_UP(abatch->size, sizeof(int)); + + if ((uint8_t *)data >= abatch->data && + (uint8_t *)data < abatch->data + aligned_size * abatch->n) { + unsigned int n = ((uint8_t *)data - abatch->data) / aligned_size; + + if ((uint8_t *)data != abatch->data + n * aligned_size) + return -EINVAL; + + abatch->mask &= ~BIT(n); + + return 0; + } + } + + return -EINVAL; +} diff --git a/test/ztest/unit/batch/CMakeLists.txt b/test/ztest/unit/batch/CMakeLists.txt new file mode 100644 index 000000000000..f5000742e9bd --- /dev/null +++ b/test/ztest/unit/batch/CMakeLists.txt @@ -0,0 +1,30 @@ +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(test_batch) + +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_batch_ztest.c + ${SOF_ROOT}/src/lib/batch.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/batch/prj.conf b/test/ztest/unit/batch/prj.conf new file mode 100644 index 000000000000..d34c7781cd0a --- /dev/null +++ b/test/ztest/unit/batch/prj.conf @@ -0,0 +1,2 @@ +CONFIG_ZTEST=y +CONFIG_SOF_FULL_ZEPHYR_APPLICATION=n diff --git a/test/ztest/unit/batch/test_batch_ztest.c b/test/ztest/unit/batch/test_batch_ztest.c new file mode 100644 index 000000000000..e68bc4d681bb --- /dev/null +++ b/test/ztest/unit/batch/test_batch_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(batch_suite, test_batch_wrong_size) +{ + struct list_item head = LIST_INIT(head); + /* new batch of 2 blocks */ + uint8_t *block1 = batch_alloc(&head, DATA_SIZE); + /* should fail because of a different size */ + uint8_t *block2 = batch_alloc(&head, DATA_SIZE + 1); + /* second block in the first batch */ + uint8_t *block3 = batch_alloc(&head, DATA_SIZE); + /* new batch of 4 blocks */ + uint8_t *block4 = batch_alloc(&head, DATA_SIZE); + /* should fail because of a different size */ + uint8_t *block5 = batch_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(batch_free(&head, block1 + 1)); + zassert_ok(batch_free(&head, block1)); + zassert_not_ok(batch_free(&head, block3 + 1)); + zassert_ok(batch_free(&head, block3)); + zassert_not_ok(batch_free(&head, block4 + 1)); + zassert_ok(batch_free(&head, block4)); +} + +ZTEST(batch_suite, test_batch) +{ + 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 = batch_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(batch_free(&head, blocks[k]), "free failed"); +} + +ZTEST_SUITE(batch_suite, NULL, NULL, NULL, NULL, NULL); diff --git a/test/ztest/unit/batch/testcase.yaml b/test/ztest/unit/batch/testcase.yaml new file mode 100644 index 000000000000..bc03e815c8d1 --- /dev/null +++ b/test/ztest/unit/batch/testcase.yaml @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: BSD-3-Clause +# +# Copyright(c) 2025 Intel Corporation. +# +# Batch allocator unit tests for Ztest framework + +tests: + sof.batch: + tags: unit + platform_allow: native_sim + integration_platforms: + - native_sim + build_only: false 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