Skip to content

Commit f7a0dfe

Browse files
committed
Add a object pool allocator
Add a simple "object pool allocator." It allocates memory for equally sized objects, that never has to be freed, but can be reused instead. The first call to this allocator allocates two instances of the object, the next one allocates 4, then 8, 16, and after that every new allocation request adds 32 instances. When those objects are freed, they are only marked as free and kept for reuse. Also add a unit test for it. Signed-off-by: Guennadi Liakhovetski <guennadi.liakhovetski@linux.intel.com>
1 parent 10e21d8 commit f7a0dfe

File tree

7 files changed

+306
-1
lines changed

7 files changed

+306
-1
lines changed

src/include/sof/objpool.h

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/* SPDX-License-Identifier: BSD-3-Clause
2+
*
3+
* Copyright(c) 2025 Intel Corporation.
4+
*/
5+
6+
#ifndef __ZEPHYR_OBJPOOL_H__
7+
#define __ZEPHYR_OBJPOOL_H__
8+
9+
struct list_item;
10+
/**
11+
* Allocate memory tracked as part of an object pool.
12+
*
13+
* @param head Pointer to the object pool list head.
14+
* @param size Size in bytes of memory blocks to allocate.
15+
*
16+
* @return a pointer to the allocated memory on success, NULL on failure.
17+
*
18+
* Allocate a memory block of @a size bytes. @a size is used upon the first
19+
* invocation to allocate memory on the heap, all consequent allocations with
20+
* the same @a head must use the same @a size value. First allocation with an
21+
* empty @a head allocates 2 blocks. After both blocks are taken and a third one
22+
* is requested, the next call allocates 4 blocks, then 8, 16 and 32. After that
23+
* 32 blocks are allocated every time. Note, that by design allocated blocks are
24+
* never freed. See more below.
25+
*/
26+
void *objpool_alloc(struct list_item *head, size_t size);
27+
28+
/**
29+
* Return a block to the object pool
30+
*
31+
* @param head Pointer to the object pool list head.
32+
* @param data Pointer to the object to return (can be NULL)
33+
*
34+
* @return 0 on success or a negative error code.
35+
*
36+
* Return a block to the object pool. Memory is never freed by design, unused
37+
* blocks are kept in the object pool for future re-use.
38+
*/
39+
int objpool_free(struct list_item *head, void *data);
40+
41+
#endif

src/lib/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# SPDX-License-Identifier: BSD-3-Clause
22

3-
set(common_files notifier.c dma.c dai.c)
3+
set(common_files notifier.c dma.c dai.c objpool.c)
44

55
if(CONFIG_LIBRARY)
66
add_local_sources(sof

src/lib/objpool.c

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
// SPDX-License-Identifier: BSD-3-Clause
2+
//
3+
// Copyright(c) 2025 Intel Corporation.
4+
5+
#include <rtos/alloc.h>
6+
#include <rtos/bit.h>
7+
#include <sof/objpool.h>
8+
#include <sof/common.h>
9+
#include <sof/list.h>
10+
11+
#include <errno.h>
12+
#include <limits.h>
13+
#include <stdbool.h>
14+
#include <stddef.h>
15+
#include <stdint.h>
16+
17+
struct objpool {
18+
struct list_item list;
19+
unsigned int n;
20+
uint32_t mask;
21+
size_t size;
22+
uint8_t data[];
23+
};
24+
25+
#define OBJPOOL_BITS (sizeof(((struct objpool *)0)->mask) * 8)
26+
27+
static int objpool_add(struct list_item *head, unsigned int n, size_t size)
28+
{
29+
if (n > OBJPOOL_BITS)
30+
return -ENOMEM;
31+
32+
if (!is_power_of_2(n))
33+
return -EINVAL;
34+
35+
size_t aligned_size = ALIGN_UP(size, sizeof(int));
36+
37+
/* Initialize with 0 to give caller a chance to identify new allocations */
38+
struct objpool *pobjpool = rzalloc(0, n * aligned_size + sizeof(*pobjpool));
39+
40+
if (!pobjpool)
41+
return -ENOMEM;
42+
43+
pobjpool->n = n;
44+
/* clear bit means free */
45+
pobjpool->mask = 0;
46+
pobjpool->size = size;
47+
48+
list_item_append(&pobjpool->list, head);
49+
50+
return 0;
51+
}
52+
53+
void *objpool_alloc(struct list_item *head, size_t size)
54+
{
55+
size_t aligned_size = ALIGN_UP(size, sizeof(int));
56+
struct list_item *list;
57+
struct objpool *pobjpool;
58+
59+
/* Make sure size * 32 still fits in OBJPOOL_BITS bits */
60+
if (!size || aligned_size > (UINT_MAX >> 5) - sizeof(*pobjpool))
61+
return NULL;
62+
63+
list_for_item(list, head) {
64+
pobjpool = container_of(list, struct objpool, list);
65+
66+
uint32_t free_mask = MASK(pobjpool->n - 1, 0) & ~pobjpool->mask;
67+
68+
/* sanity check */
69+
if (size != pobjpool->size)
70+
return NULL;
71+
72+
if (!free_mask)
73+
continue;
74+
75+
/* Find first free - guaranteed valid now */
76+
unsigned int bit = ffs(free_mask) - 1;
77+
78+
pobjpool->mask |= BIT(bit);
79+
80+
return pobjpool->data + aligned_size * bit;
81+
}
82+
83+
/* no free elements found */
84+
unsigned int new_n;
85+
86+
if (list_is_empty(head)) {
87+
new_n = 2;
88+
} else {
89+
/* Check the last one */
90+
pobjpool = container_of(head->prev, struct objpool, list);
91+
92+
if (pobjpool->n == OBJPOOL_BITS)
93+
new_n = OBJPOOL_BITS;
94+
else
95+
new_n = pobjpool->n << 1;
96+
}
97+
98+
if (objpool_add(head, new_n, size) < 0)
99+
return NULL;
100+
101+
/* Return the first element of the new objpool, which is now the last one in the list */
102+
pobjpool = container_of(head->prev, struct objpool, list);
103+
pobjpool->mask = 1;
104+
105+
return pobjpool->data;
106+
}
107+
108+
int objpool_free(struct list_item *head, void *data)
109+
{
110+
struct list_item *list;
111+
struct objpool *pobjpool;
112+
113+
if (!data)
114+
return 0;
115+
116+
list_for_item(list, head) {
117+
pobjpool = container_of(list, struct objpool, list);
118+
119+
size_t aligned_size = ALIGN_UP(pobjpool->size, sizeof(int));
120+
121+
if ((uint8_t *)data >= pobjpool->data &&
122+
(uint8_t *)data < pobjpool->data + aligned_size * pobjpool->n) {
123+
unsigned int n = ((uint8_t *)data - pobjpool->data) / aligned_size;
124+
125+
if ((uint8_t *)data != pobjpool->data + n * aligned_size)
126+
return -EINVAL;
127+
128+
pobjpool->mask &= ~BIT(n);
129+
130+
return 0;
131+
}
132+
}
133+
134+
return -EINVAL;
135+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
cmake_minimum_required(VERSION 3.20.0)
2+
3+
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
4+
project(test_objpool)
5+
6+
set(SOF_ROOT "${PROJECT_SOURCE_DIR}/../../../..")
7+
8+
# Include SOF CMake functions
9+
include(${SOF_ROOT}/scripts/cmake/misc.cmake)
10+
11+
target_include_directories(app PRIVATE
12+
${SOF_ROOT}/zephyr/include
13+
${SOF_ROOT}/src/include
14+
${SOF_ROOT}/src/platform/posix/include
15+
)
16+
17+
# Define SOF-specific configurations for unit testing
18+
target_compile_definitions(app PRIVATE
19+
-DCONFIG_ZEPHYR_POSIX=1
20+
)
21+
22+
target_sources(app PRIVATE
23+
test_objpool_ztest.c
24+
${SOF_ROOT}/src/lib/objpool.c
25+
)
26+
27+
target_link_libraries(app PRIVATE "-Wl,--wrap=rzalloc")
28+
29+
# Add RELATIVE_FILE definitions for SOF trace functionality
30+
sof_append_relative_path_definitions(app)

test/ztest/unit/objpool/prj.conf

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
CONFIG_ZTEST=y
2+
CONFIG_SOF_FULL_ZEPHYR_APPLICATION=n
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
// SPDX-License-Identifier: BSD-3-Clause
2+
//
3+
// Copyright(c) 2025 Intel Corporation.
4+
5+
#define DATA_SIZE 5
6+
#define ALIGNED_SIZE ALIGN_UP(DATA_SIZE, sizeof(int))
7+
8+
#include <zephyr/ztest.h>
9+
#include <sof/objpool.h>
10+
#include <sof/common.h>
11+
#include <sof/list.h>
12+
#include <stdlib.h>
13+
#include <string.h>
14+
15+
void *__wrap_rzalloc(uint32_t flags, size_t bytes)
16+
{
17+
(void)flags;
18+
19+
void *ret = malloc(bytes);
20+
21+
if (ret)
22+
memset(ret, 0, bytes);
23+
24+
return ret;
25+
}
26+
27+
ZTEST(objpool_suite, test_objpool_wrong_size)
28+
{
29+
struct list_item head = LIST_INIT(head);
30+
/* new object pool of 2 blocks */
31+
uint8_t *block1 = objpool_alloc(&head, DATA_SIZE);
32+
/* should fail because of a different size */
33+
uint8_t *block2 = objpool_alloc(&head, DATA_SIZE + 1);
34+
/* second block in the first object pool */
35+
uint8_t *block3 = objpool_alloc(&head, DATA_SIZE);
36+
/* new object pool of 4 blocks */
37+
uint8_t *block4 = objpool_alloc(&head, DATA_SIZE);
38+
/* should fail because of a different size */
39+
uint8_t *block5 = objpool_alloc(&head, DATA_SIZE * 2);
40+
41+
zassert_not_null(block1);
42+
zassert_is_null(block2);
43+
zassert_not_null(block3);
44+
zassert_not_null(block4);
45+
zassert_is_null(block5);
46+
47+
zassert_not_ok(objpool_free(&head, block1 + 1));
48+
zassert_ok(objpool_free(&head, block1));
49+
zassert_not_ok(objpool_free(&head, block3 + 1));
50+
zassert_ok(objpool_free(&head, block3));
51+
zassert_not_ok(objpool_free(&head, block4 + 1));
52+
zassert_ok(objpool_free(&head, block4));
53+
}
54+
55+
ZTEST(objpool_suite, test_objpool)
56+
{
57+
struct list_item head = LIST_INIT(head);
58+
void *blocks[62]; /* 2 + 4 + 8 + 16 + 32 */
59+
unsigned int k = 0;
60+
61+
/* Loop over all powers: 2^1..2^5 */
62+
for (unsigned int i = 1; i <= 5; i++) {
63+
unsigned int n = 1 << i;
64+
uint8_t *start;
65+
66+
for (unsigned int j = 0; j < n; j++) {
67+
uint8_t *block = objpool_alloc(&head, DATA_SIZE);
68+
69+
zassert_not_null(block, "allocation failed loop %u iter %u", i, j);
70+
71+
if (!j)
72+
start = block;
73+
else
74+
zassert_equal(block, start + ALIGNED_SIZE * j, "wrong pointer");
75+
76+
blocks[k++] = block;
77+
}
78+
}
79+
80+
while (k--)
81+
zassert_ok(objpool_free(&head, blocks[k]), "free failed");
82+
}
83+
84+
ZTEST_SUITE(objpool_suite, NULL, NULL, NULL, NULL, NULL);
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# SPDX-License-Identifier: BSD-3-Clause
2+
#
3+
# Copyright(c) 2025 Intel Corporation.
4+
#
5+
# Object pool allocator unit tests for Ztest framework
6+
7+
tests:
8+
sof.objpool:
9+
tags: unit
10+
platform_allow: native_sim
11+
integration_platforms:
12+
- native_sim
13+
build_only: false

0 commit comments

Comments
 (0)