blob: 31b5d226cc94be40edaf6722b7d1586e0ec94ae8 [file] [log] [blame] [edit]
// SPDX-License-Identifier: GPL-2.0+
#include <common.h>
#include <malloc.h>
#include "memory_pool.h"
/**
* An implementation of the simple and efficient memory pool allocator
* described in the following article.
*
* http://www.thinkmind.org/download.php?articleid=computation_tools_2012_1_10_80006
*/
struct memory_pool {
uintptr_t base; // base address of memory pool.
size_t block_size; // fixed size in bytes of each block.
size_t num_total; // total number of blocks in pool.
size_t num_free; // number of free blocks in pool.
size_t num_initialized; // number of initialized blocks in pool.
size_t head; // index of the next allocatable block. i.e. HEAD node of the
// implicit free-blocks list.
};
static bool is_power_of_2(size_t n) { return (n & (n - 1)) == 0; }
/* We could store a size_t index to point to the next free block at the
beginning of each free block. */
#define MIN_BLOCK_SIZE (sizeof(size_t))
#define MIN_ALIGNMENT (sizeof(void *))
static int init_pool(struct memory_pool *pool, uintptr_t pool_start,
uintptr_t pool_end, size_t block_size)
{
if (!pool || !pool_start)
return -EINVAL;
if (block_size < MIN_BLOCK_SIZE)
return -EINVAL;
pool_start = round_up(pool_start, MIN_ALIGNMENT);
// We need space for at least 1 block.
if (pool_start + block_size > pool_end)
return -ENOSPC;
pool->base = pool_start;
pool->block_size = block_size;
pool->num_total = (pool_end - pool_start) / block_size;
pool->num_free = pool->num_total;
pool->num_initialized = 0;
pool->head = 0;
return 0;
}
struct memory_pool *memory_pool_create(uintptr_t pool_start,
size_t pool_size, size_t block_size)
{
struct memory_pool *new_pool = malloc(sizeof(struct memory_pool));
if (!new_pool)
return NULL;
if (init_pool(new_pool, pool_start, pool_start + pool_size, block_size)) {
free(new_pool);
return NULL;
}
return new_pool;
}
void memory_pool_destroy(struct memory_pool *pool)
{
free(pool);
}
static uintptr_t get_block_address(struct memory_pool *pool, size_t index)
{
if (index < pool->num_total)
return pool->base + index * pool->block_size;
return 0;
}
static size_t get_block_index(struct memory_pool *pool, void *addr)
{
uintptr_t ptr = (uintptr_t) addr;
if (ptr < pool->base ||
ptr > (pool->base + pool->block_size * pool->num_total)) {
return pool->num_total; // tail.
}
return ((ptr - pool->base) / pool->block_size);
}
/**
* Allocates a buffer from the memory pool.
*
* @pool - pointer to an initialized pool.
* @align - desired buffer address alignment, zero or a power of 2.
* @bytes - desired buffer size in bytes.
*
* Returns NULL on error else buffer address.
*/
void *memory_pool_allocate(struct memory_pool *pool, size_t align, size_t bytes)
{
if (!pool || !bytes)
return NULL;
if (align < MIN_ALIGNMENT)
align = MIN_ALIGNMENT;
if (!is_power_of_2(align)) {
printf("%s: error: bad alignment: %zu\n", __func__, align);
return NULL;
}
if (pool->num_free == 0) {
printf("%s: error: out of free blocks.\n", __func__);
return NULL;
}
/* Initialize one new block (lazy expansion of the free-blocks list) on each
* allocation. */
if (pool->num_initialized < pool->num_total) {
size_t *next =
(size_t *)get_block_address(pool, pool->num_initialized);
*next = pool->num_initialized + 1;
pool->num_initialized++;
}
uintptr_t p_block = get_block_address(pool, pool->head);
uintptr_t ptr = round_up((uintptr_t) p_block, align);
if (ptr + bytes > p_block + pool->block_size) {
printf("%s: error: block_size (%zu) too small.\n", __func__,
pool->block_size);
return NULL;
}
pool->head = *(size_t *)p_block;
pool->num_free--;
return (void *)ptr;
}
/**
* Returns an allocated buffer to the memory pool.
*
* @pool - pointer to an initialized memory pool.
* @ptr - pointer to an previously allocated buffer.
*/
void memory_pool_deallocate(struct memory_pool *pool, void *ptr)
{
if (!ptr)
return;
size_t index = get_block_index(pool, ptr);
if (index == pool->num_total) {
panic("index out of range.");
}
/* insert new block at head. */
uintptr_t p_block = get_block_address(pool, index);
*(size_t *)p_block = (pool->num_free) ? pool->head : pool->num_total;
pool->head = index;
pool->num_free++;
return;
}
#ifdef MEMORY_POOL_TEST
static int unit_test(cmd_tbl_t * cmdtp, int flag, int argc, char *const argv[])
{
size_t num_blocks = 4;
size_t block_size = 70; // block size does not have to be power of 2.
size_t pool_size = num_blocks * block_size;
// static such that if our test fails across multiple invocations we don't
// waste unbounded amount memory.
static uintptr_t pool_start = 0;
if (!pool_start) {
pool_start = (uintptr_t)malloc(pool_size);
assert(pool_start);
}
struct memory_pool *pool = memory_pool_create (pool_start, pool_size,
block_size);
assert(pool);
// typical allocation/deallocation path.
void *p1 = memory_pool_allocate(pool, /* align = */ 0, /* bytes = */ 1);
void *p2 = memory_pool_allocate(pool, /* align = */ 0, /* bytes = */ 1);
void *p3 = memory_pool_allocate(pool, /* align = */ 0, /* bytes = */ 1);
assert (p1 && p2 && p3);
memory_pool_deallocate(pool, p1);
memory_pool_deallocate(pool, p2);
void *p = memory_pool_allocate(pool, /* align = */ 0, /* bytes = */ 1);
assert(p && pool->num_free == 2 && pool->num_initialized == 4);
p = memory_pool_allocate(pool, /* align = */ 0, /* bytes = */ 1);
assert(p);
p = memory_pool_allocate(pool, /* align = */ 0, /* bytes = */ 1);
assert(p);
p = memory_pool_allocate(pool, /* align = */ 0, /* bytes = */ 1);
assert(!p); // out of blocks
// RESET.
memory_pool_destroy(pool);
pool = memory_pool_create (pool_start, pool_size, block_size);
assert(pool);
// bad alignment (not power of 2).
p = memory_pool_allocate(pool, /* align = */ 10, /* bytes = */ 1);
assert(!p);
// valid alignment (best effort check).
p = memory_pool_allocate(pool, /* align = */ 64, /* bytes = */ 1);
assert(p && !((uintptr_t)p % 64));
// bytes > block_size.
p = memory_pool_allocate(pool, /* align = */ 0, /* bytes = */ block_size + 1);
assert(!p);
// RESET.
memory_pool_destroy(pool);
pool = memory_pool_create (pool_start, pool_size, block_size);
assert(pool);
// deallocating a ptr that has a larger alignment than block_start
// (simulated).
p = memory_pool_allocate(pool, /* align = */ 0, /* bytes = */ 64);
assert(p && pool->num_free == (num_blocks - 1));
memory_pool_deallocate(pool, (void *)((uintptr_t)p + 8));
assert(pool->num_free = num_blocks);
memory_pool_destroy(pool);
// If we do end up passing the test, clean up.
free((void *)pool_start);
pool_start = 0;
printf("PASS (ignore any error logs)!\n");
return 0;
}
U_BOOT_CMD(test_memory_pool, /* maxargs = */ 1, /* repeatable = */ 1,
unit_test, "Run unit tests", NULL);
#endif /* MEMORY_POOL_TEST */