summaryrefslogtreecommitdiff
path: root/kernel/mem_pool.c
diff options
context:
space:
mode:
Diffstat (limited to 'kernel/mem_pool.c')
-rw-r--r--kernel/mem_pool.c612
1 files changed, 612 insertions, 0 deletions
diff --git a/kernel/mem_pool.c b/kernel/mem_pool.c
new file mode 100644
index 000000000..f3a34847a
--- /dev/null
+++ b/kernel/mem_pool.c
@@ -0,0 +1,612 @@
+/*
+ * Copyright (c) 2016 Wind River Systems, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @brief Memory pools.
+ */
+
+#include <kernel.h>
+#include <kernel_structs.h>
+#include <debug/object_tracing_common.h>
+#include <ksched.h>
+#include <wait_q.h>
+#include <init.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define _QUAD_BLOCK_AVAILABLE 0x0F
+#define _QUAD_BLOCK_ALLOCATED 0x0
+
+extern struct k_mem_pool _k_mem_pool_list_start[];
+extern struct k_mem_pool _k_mem_pool_list_end[];
+
+struct k_mem_pool *_trace_list_k_mem_pool;
+
+static void init_one_memory_pool(struct k_mem_pool *pool);
+
+/**
+ *
+ * @brief Initialize kernel memory pool subsystem
+ *
+ * Perform any initialization of memory pool that wasn't done at build time.
+ *
+ * @return N/A
+ */
+static int init_static_pools(struct device *unused)
+{
+ ARG_UNUSED(unused);
+ struct k_mem_pool *pool;
+
+ /* perform initialization for each memory pool */
+
+ for (pool = _k_mem_pool_list_start;
+ pool < _k_mem_pool_list_end;
+ pool++) {
+ init_one_memory_pool(pool);
+ }
+ return 0;
+}
+
+SYS_INIT(init_static_pools, PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_OBJECTS);
+
+/**
+ *
+ * @brief Initialize the memory pool
+ *
+ * Initialize the internal memory accounting structures of the memory pool
+ *
+ * @param pool memory pool descriptor
+ *
+ * @return N/A
+ */
+static void init_one_memory_pool(struct k_mem_pool *pool)
+{
+ /*
+ * mark block set for largest block size
+ * as owning all of the memory pool buffer space
+ */
+
+ int remaining_blocks = pool->nr_of_maxblocks;
+ int j = 0;
+ char *memptr = pool->bufblock;
+
+ while (remaining_blocks >= 4) {
+ pool->block_set[0].quad_block[j].mem_blocks = memptr;
+ pool->block_set[0].quad_block[j].mem_status =
+ _QUAD_BLOCK_AVAILABLE;
+ j++;
+ remaining_blocks -= 4;
+ memptr +=
+ OCTET_TO_SIZEOFUNIT(pool->block_set[0].block_size)
+ * 4;
+ }
+
+ if (remaining_blocks != 0) {
+ pool->block_set[0].quad_block[j].mem_blocks = memptr;
+ pool->block_set[0].quad_block[j].mem_status =
+ _QUAD_BLOCK_AVAILABLE >> (4 - remaining_blocks);
+ /* non-existent blocks are marked as unavailable */
+ }
+
+ /*
+ * note: all other block sets own no blocks, since their
+ * first quad-block has a NULL memory pointer
+ */
+ sys_dlist_init(&pool->wait_q);
+ SYS_TRACING_OBJ_INIT(k_mem_pool, pool);
+}
+
+/**
+ *
+ * @brief Determines which block set corresponds to the specified data size
+ *
+ * Finds the block set with the smallest blocks that can hold the specified
+ * amount of data.
+ *
+ * @return block set index
+ */
+static int compute_block_set_index(struct k_mem_pool *pool, size_t data_size)
+{
+ size_t block_size = pool->min_block_size;
+ int offset = pool->nr_of_block_sets - 1;
+
+ while (data_size > block_size) {
+ block_size *= 4;
+ offset--;
+ }
+
+ return offset;
+}
+
+
+/**
+ *
+ * @brief Return an allocated block to its block set
+ *
+ * @param ptr pointer to start of block
+ * @param pool memory pool descriptor
+ * @param index block set identifier
+ *
+ * @return N/A
+ */
+static void free_existing_block(char *ptr, struct k_mem_pool *pool, int index)
+{
+ struct k_mem_pool_quad_block *quad_block =
+ pool->block_set[index].quad_block;
+ char *block_ptr;
+ uint32_t i, j;
+
+ /*
+ * search block set's quad-blocks until the block is located,
+ * then mark it as unused
+ *
+ * note: block *must* exist, so no need to do array bounds checking
+ */
+
+ for (i = 0; ; i++) {
+ __ASSERT((i < pool->block_set[index].nr_of_entries) &&
+ (quad_block[i].mem_blocks != NULL),
+ "Attempt to free unallocated memory pool block\n");
+
+ block_ptr = quad_block[i].mem_blocks;
+ for (j = 0; j < 4; j++) {
+ if (ptr == block_ptr) {
+ quad_block[i].mem_status |= (1 << j);
+ return;
+ }
+ block_ptr += OCTET_TO_SIZEOFUNIT(
+ pool->block_set[index].block_size);
+ }
+ }
+}
+
+
+/**
+ *
+ * @brief Defragment the specified memory pool block sets
+ *
+ * Reassembles any quad-blocks that are entirely unused into larger blocks
+ * (to the extent permitted).
+ *
+ * @param pool memory pool descriptor
+ * @param start_block_set_index index of smallest block set to defragment
+ * @param last_block_set_index index of largest block set to defragment
+ *
+ * @return N/A
+ */
+static void defrag(struct k_mem_pool *pool,
+ int start_block_set_index, int last_block_set_index)
+{
+ uint32_t i;
+ uint32_t k;
+ int j;
+ struct k_mem_pool_quad_block *quad_block;
+
+ /* process block sets from smallest to largest permitted sizes */
+
+ for (j = start_block_set_index; j > last_block_set_index; j--) {
+
+ quad_block = pool->block_set[j].quad_block;
+ i = 0;
+
+ do {
+ /* block set is done if no more quad-blocks exist */
+
+ if (quad_block[i].mem_blocks == NULL) {
+ break;
+ }
+
+ /* reassemble current quad-block, if possible */
+
+ if (quad_block[i].mem_status == _QUAD_BLOCK_AVAILABLE) {
+
+ /*
+ * mark the corresponding block in next larger
+ * block set as free
+ */
+
+ free_existing_block(
+ quad_block[i].mem_blocks, pool, j - 1);
+
+ /*
+ * delete the quad-block from this block set
+ * by replacing it with the last quad-block
+ *
+ * (algorithm works even when the deleted
+ * quad-block is the last quad_block)
+ */
+
+ k = i;
+ while (((k+1) !=
+ pool->block_set[j].nr_of_entries) &&
+ (quad_block[k + 1].mem_blocks != NULL)) {
+ k++;
+ }
+
+ quad_block[i].mem_blocks =
+ quad_block[k].mem_blocks;
+ quad_block[i].mem_status =
+ quad_block[k].mem_status;
+
+ quad_block[k].mem_blocks = NULL;
+
+ /* loop & process replacement quad_block[i] */
+ } else {
+ i++;
+ }
+
+ /* block set is done if at end of quad-block array */
+
+ } while (i < pool->block_set[j].nr_of_entries);
+ }
+}
+
+
+/**
+ *
+ * @brief Allocate block from an existing block set
+ *
+ * @param block_set pointer to block set
+ * @param unused_block_index the index of first unused quad-block
+ * when allocation fails, it is the number of quad
+ * blocks in the block set
+ *
+ * @return pointer to allocated block, or NULL if none available
+ */
+static char *get_existing_block(struct k_mem_pool_block_set *block_set,
+ int *unused_block_index)
+{
+ char *found = NULL;
+ uint32_t i = 0;
+ int status;
+ int free_bit;
+
+ do {
+ /* give up if no more quad-blocks exist */
+
+ if (block_set->quad_block[i].mem_blocks == NULL) {
+ break;
+ }
+
+ /* allocate a block from current quad-block, if possible */
+
+ status = block_set->quad_block[i].mem_status;
+ if (status != _QUAD_BLOCK_ALLOCATED) {
+ /* identify first free block */
+ free_bit = find_lsb_set(status) - 1;
+
+ /* compute address of free block */
+ found = block_set->quad_block[i].mem_blocks +
+ (OCTET_TO_SIZEOFUNIT(free_bit *
+ block_set->block_size));
+
+ /* mark block as unavailable (using XOR to invert) */
+ block_set->quad_block[i].mem_status ^=
+ 1 << free_bit;
+#ifdef CONFIG_OBJECT_MONITOR
+ block_set->count++;
+#endif
+ break;
+ }
+
+ /* move on to next quad-block; give up if at end of array */
+
+ } while (++i < block_set->nr_of_entries);
+
+ *unused_block_index = i;
+ return found;
+}
+
+
+/**
+ *
+ * @brief Allocate a block, recursively fragmenting larger blocks if necessary
+ *
+ * @param pool memory pool descriptor
+ * @param index index of block set currently being examined
+ * @param start_index index of block set for which allocation is being done
+ *
+ * @return pointer to allocated block, or NULL if none available
+ */
+static char *get_block_recursive(struct k_mem_pool *pool,
+ int index, int start_index)
+{
+ int i;
+ char *found, *larger_block;
+ struct k_mem_pool_block_set *fr_table;
+
+ /* give up if we've exhausted the set of maximum size blocks */
+
+ if (index < 0) {
+ return NULL;
+ }
+
+ /* try allocating a block from the current block set */
+
+ fr_table = pool->block_set;
+ i = 0;
+
+ found = get_existing_block(&(fr_table[index]), &i);
+ if (found != NULL) {
+ return found;
+ }
+
+#ifdef CONFIG_MEM_POOL_DEFRAG_BEFORE_SPLIT
+ /*
+ * do a partial defragmentation of memory pool & try allocating again
+ * - do this on initial invocation only, not recursive ones
+ * (since there is no benefit in repeating the defrag)
+ * - defrag only the blocks smaller than the desired size,
+ * and only until the size needed is reached
+ *
+ * note: defragging at this time tries to preserve the memory pool's
+ * larger blocks by fragmenting them only when necessary
+ * (i.e. at the cost of doing more frequent auto-defragmentations)
+ */
+
+ if (index == start_index) {
+ defrag(pool, pool->nr_of_block_sets - 1, start_index);
+ found = get_existing_block(&(fr_table[index]), &i);
+ if (found != NULL) {
+ return found;
+ }
+ }
+#endif
+
+ /* try allocating a block from the next largest block set */
+
+ larger_block = get_block_recursive(pool, index - 1, start_index);
+ if (larger_block != NULL) {
+ /*
+ * add a new quad-block to the current block set,
+ * then mark one of its 4 blocks as used and return it
+ *
+ * note: "i" was earlier set to indicate the first unused
+ * quad-block entry in the current block set
+ */
+
+ fr_table[index].quad_block[i].mem_blocks = larger_block;
+ fr_table[index].quad_block[i].mem_status =
+ _QUAD_BLOCK_AVAILABLE & (~0x1);
+#ifdef CONFIG_OBJECT_MONITOR
+ fr_table[index].count++;
+#endif
+ return larger_block;
+ }
+
+#ifdef CONFIG_MEM_POOL_SPLIT_BEFORE_DEFRAG
+ /*
+ * do a partial defragmentation of memory pool & try allocating again
+ * - do this on initial invocation only, not recursive ones
+ * (since there is no benefit in repeating the defrag)
+ * - defrag only the blocks smaller than the desired size,
+ * and only until the size needed is reached
+ *
+ * note: defragging at this time tries to limit the cost of doing
+ * auto-defragmentations by doing them only when necessary
+ * (i.e. at the cost of fragmenting the memory pool's larger blocks)
+ */
+
+ if (index == start_index) {
+ defrag(pool, pool->nr_of_block_sets - 1, start_index);
+ found = get_existing_block(&(fr_table[index]), &i);
+ if (found != NULL) {
+ return found;
+ }
+ }
+#endif
+
+ return NULL; /* can't find (or create) desired block */
+}
+
+
+/**
+ *
+ * @brief Examine threads that are waiting for memory pool blocks.
+ *
+ * This routine attempts to satisfy any incomplete block allocation requests for
+ * the specified memory pool. It can be invoked either by the explicit freeing
+ * of a used block or as a result of defragmenting the pool (which may create
+ * one or more new, larger blocks).
+ *
+ * @return N/A
+ */
+static void block_waiters_check(struct k_mem_pool *pool)
+{
+ char *found_block;
+ struct k_thread *waiter;
+ struct k_thread *next_waiter;
+ int offset;
+
+ unsigned int key = irq_lock();
+ waiter = (struct k_thread *)sys_dlist_peek_head(&pool->wait_q);
+
+ /* loop all waiters */
+ while (waiter != NULL) {
+ uint32_t req_size = (uint32_t)(waiter->base.swap_data);
+
+ /* locate block set to try allocating from */
+ offset = compute_block_set_index(pool, req_size);
+
+ /* allocate block (fragmenting a larger block, if needed) */
+ found_block = get_block_recursive(pool, offset, offset);
+
+ next_waiter = (struct k_thread *)sys_dlist_peek_next(
+ &pool->wait_q, &waiter->base.k_q_node);
+
+ /* if success : remove task from list and reschedule */
+ if (found_block != NULL) {
+ /* return found block */
+ _set_thread_return_value_with_data(waiter, 0,
+ found_block);
+
+
+ /*
+ * Schedule the thread. Threads will be rescheduled
+ * outside the function by k_sched_unlock()
+ */
+ _unpend_thread(waiter);
+ _abort_thread_timeout(waiter);
+ _ready_thread(waiter);
+ }
+ waiter = next_waiter;
+ }
+ irq_unlock(key);
+}
+
+void k_mem_pool_defrag(struct k_mem_pool *pool)
+{
+ _sched_lock();
+
+ /* do complete defragmentation of memory pool (i.e. all block sets) */
+ defrag(pool, pool->nr_of_block_sets - 1, 0);
+
+ /* reschedule anybody waiting for a block */
+ block_waiters_check(pool);
+ k_sched_unlock();
+}
+
+int k_mem_pool_alloc(struct k_mem_pool *pool, struct k_mem_block *block,
+ size_t size, int32_t timeout)
+{
+ char *found_block;
+ int offset;
+
+ _sched_lock();
+ /* locate block set to try allocating from */
+ offset = compute_block_set_index(pool, size);
+
+ /* allocate block (fragmenting a larger block, if needed) */
+ found_block = get_block_recursive(pool, offset, offset);
+
+
+ if (found_block != NULL) {
+ k_sched_unlock();
+ block->pool_id = pool;
+ block->addr_in_pool = found_block;
+ block->data = found_block;
+ block->req_size = size;
+ return 0;
+ }
+
+ /*
+ * no suitable block is currently available,
+ * so either wait for one to appear or indicate failure
+ */
+ if (likely(timeout != K_NO_WAIT)) {
+ int result;
+ unsigned int key = irq_lock();
+ _sched_unlock_no_reschedule();
+
+ _current->base.swap_data = (void *)size;
+ _pend_current_thread(&pool->wait_q, timeout);
+ result = _Swap(key);
+ if (result == 0) {
+ block->pool_id = pool;
+ block->addr_in_pool = _current->base.swap_data;
+ block->data = _current->base.swap_data;
+ block->req_size = size;
+ }
+ return result;
+ }
+ k_sched_unlock();
+ return -ENOMEM;
+}
+
+void k_mem_pool_free(struct k_mem_block *block)
+{
+ int offset;
+ struct k_mem_pool *pool = block->pool_id;
+
+ _sched_lock();
+ /* determine block set that block belongs to */
+ offset = compute_block_set_index(pool, block->req_size);
+
+ /* mark the block as unused */
+ free_existing_block(block->addr_in_pool, pool, offset);
+
+ /* reschedule anybody waiting for a block */
+ block_waiters_check(pool);
+ k_sched_unlock();
+}
+
+
+/*
+ * Heap memory pool support
+ */
+
+#if (CONFIG_HEAP_MEM_POOL_SIZE > 0)
+
+/*
+ * Case 1: Heap is defined using HEAP_MEM_POOL_SIZE configuration option.
+ *
+ * This module defines the heap memory pool and the _HEAP_MEM_POOL symbol
+ * that has the address of the associated memory pool struct.
+ */
+
+K_MEM_POOL_DEFINE(_heap_mem_pool, 64, CONFIG_HEAP_MEM_POOL_SIZE, 1, 4);
+#define _HEAP_MEM_POOL (&_heap_mem_pool)
+
+#else
+
+/*
+ * Case 2: Heap is defined using HEAP_SIZE item type in MDEF.
+ *
+ * Sysgen defines the heap memory pool and the _heap_mem_pool_ptr variable
+ * that has the address of the associated memory pool struct. This module
+ * defines the _HEAP_MEM_POOL symbol as an alias for _heap_mem_pool_ptr.
+ *
+ * Note: If the MDEF does not define the heap memory pool k_malloc() will
+ * compile successfully, but will trigger a link error if it is used.
+ */
+
+extern struct k_mem_pool * const _heap_mem_pool_ptr;
+#define _HEAP_MEM_POOL _heap_mem_pool_ptr
+
+#endif /* CONFIG_HEAP_MEM_POOL_SIZE */
+
+
+void *k_malloc(size_t size)
+{
+ struct k_mem_block block;
+
+ /*
+ * get a block large enough to hold an initial (hidden) block
+ * descriptor, as well as the space the caller requested
+ */
+ size += sizeof(struct k_mem_block);
+ if (k_mem_pool_alloc(_HEAP_MEM_POOL, &block, size, K_NO_WAIT) != 0) {
+ return NULL;
+ }
+
+ /* save the block descriptor info at the start of the actual block */
+ memcpy(block.data, &block, sizeof(struct k_mem_block));
+
+ /* return address of the user area part of the block to the caller */
+ return (char *)block.data + sizeof(struct k_mem_block);
+}
+
+
+void k_free(void *ptr)
+{
+ if (ptr != NULL) {
+ /* point to hidden block descriptor at start of block */
+ ptr = (char *)ptr - sizeof(struct k_mem_block);
+
+ /* return block to the heap memory pool */
+ k_mem_pool_free(ptr);
+ }
+}