|  | /* | 
|  | * Copyright (c) 2021 Intel Corporation | 
|  | * | 
|  | * SPDX-License-Identifier: Apache-2.0 | 
|  | */ | 
|  |  | 
|  | #include <zephyr/zephyr.h> | 
|  | #include <zephyr/sys/__assert.h> | 
|  | #include <zephyr/sys/check.h> | 
|  | #include <zephyr/sys/heap_listener.h> | 
|  | #include <zephyr/sys/mem_blocks.h> | 
|  | #include <zephyr/sys/util.h> | 
|  |  | 
|  | static void *alloc_blocks(sys_mem_blocks_t *mem_block, size_t num_blocks) | 
|  | { | 
|  | size_t offset; | 
|  | int r; | 
|  | uint8_t *blk; | 
|  | void *ret = NULL; | 
|  |  | 
|  | #ifdef CONFIG_SYS_MEM_BLOCKS_RUNTIME_STATS | 
|  | k_spinlock_key_t  key = k_spin_lock(&mem_block->lock); | 
|  | #endif | 
|  |  | 
|  | /* Find an unallocated block */ | 
|  | r = sys_bitarray_alloc(mem_block->bitmap, num_blocks, &offset); | 
|  | if (r == 0) { | 
|  |  | 
|  | #ifdef CONFIG_SYS_MEM_BLOCKS_RUNTIME_STATS | 
|  | mem_block->used_blocks += (uint32_t)num_blocks; | 
|  |  | 
|  | if (mem_block->max_used_blocks < mem_block->used_blocks) { | 
|  | mem_block->max_used_blocks = mem_block->used_blocks; | 
|  | } | 
|  |  | 
|  | k_spin_unlock(&mem_block->lock, key); | 
|  | #endif | 
|  |  | 
|  | /* Calculate the start address of the newly allocated block */ | 
|  |  | 
|  | blk = mem_block->buffer + (offset << mem_block->blk_sz_shift); | 
|  |  | 
|  | ret = blk; | 
|  | } | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int free_blocks(sys_mem_blocks_t *mem_block, void *ptr, size_t num_blocks) | 
|  | { | 
|  | size_t offset; | 
|  | uint8_t *blk = ptr; | 
|  | int ret = 0; | 
|  |  | 
|  | /* Make sure incoming block is within the mem_block buffer */ | 
|  | if (blk < mem_block->buffer) { | 
|  | ret = -EFAULT; | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | offset = (blk - mem_block->buffer) >> mem_block->blk_sz_shift; | 
|  | if (offset >= mem_block->num_blocks) { | 
|  | ret = -EFAULT; | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | #ifdef CONFIG_SYS_MEM_BLOCKS_RUNTIME_STATS | 
|  | k_spinlock_key_t  key = k_spin_lock(&mem_block->lock); | 
|  | #endif | 
|  | ret = sys_bitarray_free(mem_block->bitmap, num_blocks, offset); | 
|  |  | 
|  | #ifdef CONFIG_SYS_MEM_BLOCKS_RUNTIME_STATS | 
|  | if (ret == 0) { | 
|  | mem_block->used_blocks -= (uint32_t) num_blocks; | 
|  | } | 
|  |  | 
|  | k_spin_unlock(&mem_block->lock, key); | 
|  | #endif | 
|  |  | 
|  | out: | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | int sys_mem_blocks_alloc_contiguous(sys_mem_blocks_t *mem_block, size_t count, | 
|  | void **out_block) | 
|  | { | 
|  | int ret = 0; | 
|  |  | 
|  | __ASSERT_NO_MSG(mem_block != NULL); | 
|  | __ASSERT_NO_MSG(out_block != NULL); | 
|  |  | 
|  | if (count == 0) { | 
|  | /* Nothing to allocate */ | 
|  | *out_block = NULL; | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | if (count > mem_block->num_blocks) { | 
|  | /* Definitely not enough blocks to be allocated */ | 
|  | ret = -ENOMEM; | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | void *ptr = alloc_blocks(mem_block, count); | 
|  |  | 
|  | if (ptr == NULL) { | 
|  | ret = -ENOMEM; | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | *out_block = ptr; | 
|  | #ifdef CONFIG_SYS_MEM_BLOCKS_LISTENER | 
|  | heap_listener_notify_alloc(HEAP_ID_FROM_POINTER(mem_block), | 
|  | ptr, count << mem_block->blk_sz_shift); | 
|  | #endif | 
|  |  | 
|  | out: | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | int sys_mem_blocks_alloc(sys_mem_blocks_t *mem_block, size_t count, | 
|  | void **out_blocks) | 
|  | { | 
|  | int ret = 0; | 
|  | int i; | 
|  |  | 
|  | __ASSERT_NO_MSG(mem_block != NULL); | 
|  | __ASSERT_NO_MSG(out_blocks != NULL); | 
|  | __ASSERT_NO_MSG(mem_block->bitmap != NULL); | 
|  | __ASSERT_NO_MSG(mem_block->buffer != NULL); | 
|  |  | 
|  | if (count == 0) { | 
|  | /* Nothing to allocate */ | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | if (count > mem_block->num_blocks) { | 
|  | /* Definitely not enough blocks to be allocated */ | 
|  | ret = -ENOMEM; | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | for (i = 0; i < count; i++) { | 
|  | void *ptr = alloc_blocks(mem_block, 1); | 
|  |  | 
|  | if (ptr == NULL) { | 
|  | break; | 
|  | } | 
|  |  | 
|  | out_blocks[i] = ptr; | 
|  |  | 
|  | #ifdef CONFIG_SYS_MEM_BLOCKS_LISTENER | 
|  | heap_listener_notify_alloc(HEAP_ID_FROM_POINTER(mem_block), | 
|  | ptr, BIT(mem_block->blk_sz_shift)); | 
|  | #endif | 
|  | } | 
|  |  | 
|  | /* If error, free already allocated blocks. */ | 
|  | if (i < count) { | 
|  | (void)sys_mem_blocks_free(mem_block, i, out_blocks); | 
|  | ret = -ENOMEM; | 
|  | } | 
|  |  | 
|  | out: | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | int sys_mem_blocks_is_region_free(sys_mem_blocks_t *mem_block, void *in_block, size_t count) | 
|  | { | 
|  | bool result; | 
|  | size_t offset; | 
|  |  | 
|  | __ASSERT_NO_MSG(mem_block != NULL); | 
|  | __ASSERT_NO_MSG(mem_block->bitmap != NULL); | 
|  | __ASSERT_NO_MSG(mem_block->buffer != NULL); | 
|  |  | 
|  | offset = ((uint8_t *)in_block - mem_block->buffer) >> mem_block->blk_sz_shift; | 
|  |  | 
|  | __ASSERT_NO_MSG(offset + count <= mem_block->num_blocks); | 
|  |  | 
|  | result = sys_bitarray_is_region_cleared(mem_block->bitmap, count, offset); | 
|  | return result; | 
|  | } | 
|  |  | 
|  | int sys_mem_blocks_get(sys_mem_blocks_t *mem_block, void *in_block, size_t count) | 
|  | { | 
|  | int ret = 0; | 
|  | int offset; | 
|  |  | 
|  | __ASSERT_NO_MSG(mem_block != NULL); | 
|  | __ASSERT_NO_MSG(mem_block->bitmap != NULL); | 
|  | __ASSERT_NO_MSG(mem_block->buffer != NULL); | 
|  |  | 
|  | if (count == 0) { | 
|  | /* Nothing to allocate */ | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | offset = ((uint8_t *)in_block - mem_block->buffer) >> mem_block->blk_sz_shift; | 
|  |  | 
|  | if (offset + count > mem_block->num_blocks) { | 
|  | /* Definitely not enough blocks to be allocated */ | 
|  | ret = -ENOMEM; | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | #ifdef CONFIG_SYS_MEM_BLOCKS_RUNTIME_STATS | 
|  | k_spinlock_key_t  key = k_spin_lock(&mem_block->lock); | 
|  | #endif | 
|  |  | 
|  | ret = sys_bitarray_test_and_set_region(mem_block->bitmap, count, offset, true); | 
|  |  | 
|  | if (ret != 0) { | 
|  | #ifdef CONFIG_SYS_MEM_BLOCKS_RUNTIME_STATS | 
|  | k_spin_unlock(&mem_block->lock, key); | 
|  | #endif | 
|  | ret = -ENOMEM; | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | #ifdef CONFIG_SYS_MEM_BLOCKS_RUNTIME_STATS | 
|  | mem_block->used_blocks += (uint32_t)count; | 
|  |  | 
|  | if (mem_block->max_used_blocks < mem_block->used_blocks) { | 
|  | mem_block->max_used_blocks = mem_block->used_blocks; | 
|  | } | 
|  |  | 
|  | k_spin_unlock(&mem_block->lock, key); | 
|  | #endif | 
|  |  | 
|  | #ifdef CONFIG_SYS_MEM_BLOCKS_LISTENER | 
|  | heap_listener_notify_alloc(HEAP_ID_FROM_POINTER(mem_block), | 
|  | in_block, count << mem_block->blk_sz_shift); | 
|  | #endif | 
|  |  | 
|  | out: | 
|  | return ret; | 
|  | } | 
|  |  | 
|  |  | 
|  | int sys_mem_blocks_free(sys_mem_blocks_t *mem_block, size_t count, | 
|  | void **in_blocks) | 
|  | { | 
|  | int ret = 0; | 
|  | int i; | 
|  |  | 
|  | __ASSERT_NO_MSG(mem_block != NULL); | 
|  | __ASSERT_NO_MSG(in_blocks != NULL); | 
|  | __ASSERT_NO_MSG(mem_block->bitmap != NULL); | 
|  | __ASSERT_NO_MSG(mem_block->buffer != NULL); | 
|  |  | 
|  | if (count == 0) { | 
|  | /* Nothing to be freed. */ | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | if (count > mem_block->num_blocks) { | 
|  | ret = -EINVAL; | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | for (i = 0; i < count; i++) { | 
|  | void *ptr = in_blocks[i]; | 
|  |  | 
|  | int r = free_blocks(mem_block, ptr, 1); | 
|  |  | 
|  | if (r != 0) { | 
|  | ret = r; | 
|  | } | 
|  | #ifdef CONFIG_SYS_MEM_BLOCKS_LISTENER | 
|  | else { | 
|  | /* | 
|  | * Since we do not keep track of failed free ops, | 
|  | * we need to notify free one-by-one, instead of | 
|  | * notifying at the end of function. | 
|  | */ | 
|  | heap_listener_notify_free(HEAP_ID_FROM_POINTER(mem_block), | 
|  | ptr, BIT(mem_block->blk_sz_shift)); | 
|  | } | 
|  | #endif | 
|  | } | 
|  |  | 
|  | out: | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | int sys_mem_blocks_free_contiguous(sys_mem_blocks_t *mem_block, void *block, size_t count) | 
|  | { | 
|  | int ret = 0; | 
|  |  | 
|  | __ASSERT_NO_MSG(mem_block != NULL); | 
|  | __ASSERT_NO_MSG(mem_block->bitmap != NULL); | 
|  | __ASSERT_NO_MSG(mem_block->buffer != NULL); | 
|  |  | 
|  | if (count == 0) { | 
|  | /* Nothing to be freed. */ | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | if (count > mem_block->num_blocks) { | 
|  | ret = -EINVAL; | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | ret = free_blocks(mem_block, block, count); | 
|  |  | 
|  | if (ret != 0) { | 
|  | goto out; | 
|  | } | 
|  | #ifdef CONFIG_SYS_MEM_BLOCKS_LISTENER | 
|  | heap_listener_notify_free(HEAP_ID_FROM_POINTER(mem_block), | 
|  | block, count << mem_block->blk_sz_shift); | 
|  | #endif | 
|  |  | 
|  | out: | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | void sys_multi_mem_blocks_init(sys_multi_mem_blocks_t *group, | 
|  | sys_multi_mem_blocks_choice_fn_t choice_fn) | 
|  | { | 
|  | group->num_allocators = 0; | 
|  | group->choice_fn = choice_fn; | 
|  | } | 
|  |  | 
|  | void sys_multi_mem_blocks_add_allocator(sys_multi_mem_blocks_t *group, | 
|  | sys_mem_blocks_t *alloc) | 
|  | { | 
|  | __ASSERT_NO_MSG(group->num_allocators < ARRAY_SIZE(group->allocators)); | 
|  |  | 
|  | group->allocators[group->num_allocators++] = alloc; | 
|  | } | 
|  |  | 
|  | int sys_multi_mem_blocks_alloc(sys_multi_mem_blocks_t *group, | 
|  | void *cfg, size_t count, | 
|  | void **out_blocks, | 
|  | size_t *blk_size) | 
|  | { | 
|  | sys_mem_blocks_t *allocator; | 
|  | int ret = 0; | 
|  |  | 
|  | __ASSERT_NO_MSG(group != NULL); | 
|  | __ASSERT_NO_MSG(out_blocks != NULL); | 
|  |  | 
|  | if (count == 0) { | 
|  | if (blk_size != NULL) { | 
|  | *blk_size = 0; | 
|  | } | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | allocator = group->choice_fn(group, cfg); | 
|  | if (allocator == NULL) { | 
|  | ret = -EINVAL; | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | if (count > allocator->num_blocks) { | 
|  | ret = -ENOMEM; | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | ret = sys_mem_blocks_alloc(allocator, count, out_blocks); | 
|  |  | 
|  | if ((ret == 0) && (blk_size != NULL)) { | 
|  | *blk_size = BIT(allocator->blk_sz_shift); | 
|  | } | 
|  |  | 
|  | out: | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | int sys_multi_mem_blocks_free(sys_multi_mem_blocks_t *group, | 
|  | size_t count, void **in_blocks) | 
|  | { | 
|  | int i; | 
|  | int ret = 0; | 
|  | sys_mem_blocks_t *allocator = NULL; | 
|  |  | 
|  | __ASSERT_NO_MSG(group != NULL); | 
|  | __ASSERT_NO_MSG(in_blocks != NULL); | 
|  |  | 
|  | if (count == 0) { | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | for (i = 0; i < group->num_allocators; i++) { | 
|  | /* | 
|  | * Find out which allocator the allocated blocks | 
|  | * belong to. | 
|  | */ | 
|  |  | 
|  | uint8_t *start, *end; | 
|  | sys_mem_blocks_t *one_alloc; | 
|  |  | 
|  | one_alloc = group->allocators[i]; | 
|  | start = one_alloc->buffer; | 
|  | end = start + (BIT(one_alloc->blk_sz_shift) * one_alloc->num_blocks); | 
|  |  | 
|  | if (((uint8_t *)in_blocks[0] >= start) && | 
|  | ((uint8_t *)in_blocks[0] < end)) { | 
|  | allocator = one_alloc; | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (allocator != NULL) { | 
|  | ret = sys_mem_blocks_free(allocator, count, in_blocks); | 
|  | } else { | 
|  | ret = -EINVAL; | 
|  | } | 
|  |  | 
|  | out: | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | #ifdef CONFIG_SYS_MEM_BLOCKS_RUNTIME_STATS | 
|  | int sys_mem_blocks_runtime_stats_get(sys_mem_blocks_t *mem_block, | 
|  | struct sys_memory_stats *stats) | 
|  | { | 
|  | if ((mem_block == NULL) || (stats == NULL)) { | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | stats->allocated_bytes = mem_block->used_blocks << | 
|  | mem_block->blk_sz_shift; | 
|  | stats->free_bytes = (mem_block->num_blocks << mem_block->blk_sz_shift) - | 
|  | stats->allocated_bytes; | 
|  | stats->max_allocated_bytes = mem_block->max_used_blocks << | 
|  | mem_block->blk_sz_shift; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | int sys_mem_blocks_runtime_stats_reset_max(sys_mem_blocks_t *mem_block) | 
|  | { | 
|  | if (mem_block == NULL) { | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | mem_block->max_used_blocks = mem_block->used_blocks; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  | #endif |