575 lines
13 KiB
C
Executable file
575 lines
13 KiB
C
Executable file
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Samsung Exynos SoC series dsp driver
|
|
*
|
|
* Copyright (c) 2019 Samsung Electronics Co., Ltd.
|
|
* http://www.samsung.com/
|
|
*/
|
|
|
|
#include "dl/dsp-tlsf-allocator.h"
|
|
#include "dl/dsp-lib-manager.h"
|
|
|
|
static void __set_fl(struct dsp_tlsf *tlsf, unsigned char fl)
|
|
{
|
|
tlsf->fl |= 1 << fl;
|
|
}
|
|
|
|
static void __unset_fl(struct dsp_tlsf *tlsf, unsigned char fl)
|
|
{
|
|
tlsf->fl &= ~(1 << fl);
|
|
}
|
|
|
|
static void __set_sl(struct dsp_tlsf *tlsf, unsigned char fl, unsigned char sl)
|
|
{
|
|
tlsf->sl[fl] |= 1 << sl;
|
|
}
|
|
|
|
static void __unset_sl(struct dsp_tlsf *tlsf, unsigned char fl,
|
|
unsigned char sl)
|
|
{
|
|
tlsf->sl[fl] &= ~(1 << sl);
|
|
}
|
|
|
|
static unsigned char __get_fl_size(struct dsp_tlsf *tlsf)
|
|
{
|
|
return tlsf->max_sh - TLSF_MIN_BLOCK_SHIFT + 1;
|
|
}
|
|
|
|
static struct dsp_list_head *__get_head_from_idx(struct dsp_tlsf *tlsf,
|
|
struct dsp_tlsf_idx *idx)
|
|
{
|
|
DL_DEBUG("Idx (%d, %d)\n", idx->fl, idx->sl);
|
|
return &tlsf->fb[idx->fl][idx->sl];
|
|
}
|
|
|
|
static unsigned int __floor_log2(size_t size)
|
|
{
|
|
size_t ret = 0, pow2;
|
|
|
|
if (size < 1)
|
|
return 0;
|
|
|
|
for (pow2 = 1; pow2 <= size; ret++)
|
|
pow2 <<= 1;
|
|
|
|
return (unsigned int)(ret - 1);
|
|
}
|
|
|
|
static unsigned int __find_last_set(size_t size)
|
|
{
|
|
return __floor_log2(size);
|
|
}
|
|
|
|
static unsigned char __get_max_sh(size_t size)
|
|
{
|
|
unsigned char sh;
|
|
|
|
for (sh = 1; sh < (1 << 5) - 1; sh++) {
|
|
unsigned int tmp_size = (1 << (sh + 1)) - 4;
|
|
|
|
if (tmp_size >= size)
|
|
return sh;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int __align_mem_size(struct dsp_tlsf *tlsf, size_t size)
|
|
{
|
|
if (size < TLSF_MIN_BLOCK_SIZE)
|
|
size = TLSF_MIN_BLOCK_SIZE;
|
|
|
|
size += tlsf->align - 1;
|
|
return size & ~(tlsf->align - 1);
|
|
}
|
|
|
|
static int __get_tlsf_insert_index(size_t size, struct dsp_tlsf_idx *idx)
|
|
{
|
|
idx->fl = __find_last_set(size);
|
|
if (idx->fl < TLSF_MIN_BLOCK_SHIFT) {
|
|
DL_ERROR("fl(%u)/size(%zu) is invalid\n", idx->fl, size);
|
|
return -1;
|
|
}
|
|
idx->sl = (size >> (idx->fl - TLSF_SL_SHIFT)) - TLSF_SL_SIZE;
|
|
idx->fl -= TLSF_MIN_BLOCK_SHIFT;
|
|
DL_DEBUG("TLSF insert idx : (%u, %u)\n",
|
|
(unsigned int)idx->fl, (unsigned int)idx->sl);
|
|
return 0;
|
|
}
|
|
|
|
static int __get_tlsf_search_index(size_t size, struct dsp_tlsf_idx *idx)
|
|
{
|
|
unsigned int fl;
|
|
|
|
DL_DEBUG("TLSF search\n");
|
|
fl = __find_last_set(size);
|
|
if (fl < TLSF_MIN_BLOCK_SHIFT) {
|
|
DL_ERROR("fl(%u)/size(%zu) is invalid\n", fl, size);
|
|
return -1;
|
|
}
|
|
size = size + (1 << (fl - TLSF_SL_SHIFT)) - 1;
|
|
return __get_tlsf_insert_index(size, idx);
|
|
}
|
|
|
|
static int __find_bit_upper(unsigned int bitmap, size_t bitmap_size,
|
|
unsigned int index)
|
|
{
|
|
for (; index < bitmap_size; index++) {
|
|
unsigned int mask = 1 << index;
|
|
|
|
if (bitmap & mask)
|
|
return index;
|
|
}
|
|
|
|
DL_DEBUG("No bit in bitmap\n");
|
|
return -1;
|
|
}
|
|
|
|
const char *__dsp_tlsf_mem_type_to_str(enum dsp_tlsf_mem_type type)
|
|
{
|
|
switch (type) {
|
|
case MEM_EMPTY:
|
|
return "MEM_EMPTY";
|
|
case MEM_USE:
|
|
return "MEM_USE";
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void dsp_tlsf_mem_init(struct dsp_tlsf_mem *mem)
|
|
{
|
|
mem->lib = NULL;
|
|
dsp_list_node_init(&mem->mem_list_node);
|
|
dsp_list_node_init(&mem->tlsf_node);
|
|
}
|
|
|
|
void dsp_tlsf_mem_print(struct dsp_tlsf_mem *mem)
|
|
{
|
|
DL_BUF_STR("[0x%lx] %s size(%zu) idx(fl:%u, sl:%u)", mem->start_addr,
|
|
__dsp_tlsf_mem_type_to_str(mem->type), mem->size,
|
|
mem->tlsf_idx.fl, mem->tlsf_idx.sl);
|
|
|
|
if (mem->lib)
|
|
DL_BUF_STR(" lib(%s) ref(%u) %s", mem->lib->name,
|
|
mem->lib->ref_cnt,
|
|
(mem->lib->loaded) ? "loaded" : "unloaded");
|
|
|
|
DL_BUF_STR("\n");
|
|
DL_PRINT_BUF(INFO);
|
|
}
|
|
|
|
struct dsp_tlsf_mem *dsp_tlsf_mem_empty_merge(struct dsp_tlsf_mem *mem1,
|
|
struct dsp_tlsf_mem *mem2, struct dsp_list_head *mem_list)
|
|
{
|
|
if (mem1->type != MEM_EMPTY || mem2->type != MEM_EMPTY) {
|
|
DL_DEBUG("TLSF mem is not empty\n");
|
|
return NULL;
|
|
}
|
|
|
|
dsp_list_node_remove(mem_list, &mem2->mem_list_node);
|
|
mem1->size = mem1->size + mem2->size;
|
|
dsp_dl_free(mem2);
|
|
return mem1;
|
|
}
|
|
|
|
int dsp_tlsf_insert_block(struct dsp_tlsf_mem *mem, struct dsp_tlsf *tlsf)
|
|
{
|
|
int ret;
|
|
struct dsp_list_head *head;
|
|
|
|
DL_DEBUG("TLSF insert block\n");
|
|
|
|
if (mem->size & (tlsf->align - 1)) {
|
|
DL_ERROR("size(0x%zx) align error\n", mem->size);
|
|
return -1;
|
|
}
|
|
|
|
ret = __get_tlsf_insert_index(mem->size, &mem->tlsf_idx);
|
|
if (ret == -1) {
|
|
DL_ERROR("[%s] CHK_ERR\n", __func__);
|
|
return -1;
|
|
}
|
|
|
|
head = __get_head_from_idx(tlsf, &mem->tlsf_idx);
|
|
|
|
dsp_list_node_init(&mem->tlsf_node);
|
|
dsp_list_node_push_back(head, &mem->tlsf_node);
|
|
|
|
__set_fl(tlsf, mem->tlsf_idx.fl);
|
|
__set_sl(tlsf, mem->tlsf_idx.fl, mem->tlsf_idx.sl);
|
|
return 0;
|
|
}
|
|
|
|
int dsp_tlsf_find_block(size_t size, struct dsp_tlsf_mem **mem,
|
|
struct dsp_tlsf *tlsf)
|
|
{
|
|
int ret;
|
|
struct dsp_tlsf_idx tlsf_idx;
|
|
struct dsp_list_head *head;
|
|
|
|
if (size < TLSF_MIN_BLOCK_SIZE || size > tlsf->max_size) {
|
|
DL_ERROR("size(%zu) is invalid\n", size);
|
|
return -1;
|
|
}
|
|
|
|
if (size & (tlsf->align - 1)) {
|
|
DL_ERROR("size(0x%zx) align error\n", size);
|
|
return -1;
|
|
}
|
|
|
|
ret = __get_tlsf_search_index(size, &tlsf_idx);
|
|
if (ret == -1) {
|
|
DL_ERROR("[%s] CHK_ERR\n", __func__);
|
|
return -1;
|
|
}
|
|
|
|
tlsf_idx.sl = __find_bit_upper(tlsf->sl[tlsf_idx.fl],
|
|
TLSF_SL_SIZE, tlsf_idx.sl);
|
|
if (tlsf_idx.sl == -1) {
|
|
tlsf_idx.fl = __find_bit_upper(tlsf->fl, __get_fl_size(tlsf),
|
|
tlsf_idx.fl + 1);
|
|
if (tlsf_idx.fl == -1) {
|
|
DL_DEBUG("Cannot find block due to fl\n");
|
|
return -1;
|
|
}
|
|
|
|
tlsf_idx.sl = __find_bit_upper(tlsf->sl[tlsf_idx.fl],
|
|
TLSF_SL_SIZE, 0);
|
|
if (tlsf_idx.sl == -1) {
|
|
DL_DEBUG("Cannot find block due to sl\n");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
DL_DEBUG("Find block fl(%d), sl(%d)\n", tlsf_idx.fl, tlsf_idx.sl);
|
|
|
|
head = __get_head_from_idx(tlsf, &tlsf_idx);
|
|
if (head->num <= 0) {
|
|
DL_ERROR("Block head is empty\n");
|
|
return -1;
|
|
}
|
|
|
|
*mem = container_of(head->next, struct dsp_tlsf_mem, tlsf_node);
|
|
return 0;
|
|
}
|
|
|
|
void dsp_tlsf_remove_block(struct dsp_tlsf_mem *mem, struct dsp_tlsf *tlsf)
|
|
{
|
|
struct dsp_list_head *tlsf_head;
|
|
|
|
DL_DEBUG("TLSF remove idx : (%u, %u)\n",
|
|
mem->tlsf_idx.fl, mem->tlsf_idx.sl);
|
|
tlsf_head = __get_head_from_idx(tlsf, &mem->tlsf_idx);
|
|
dsp_list_node_remove(tlsf_head, &mem->tlsf_node);
|
|
|
|
if (dsp_list_is_empty(tlsf_head)) {
|
|
__unset_sl(tlsf, mem->tlsf_idx.fl, mem->tlsf_idx.sl);
|
|
|
|
if (tlsf->sl[mem->tlsf_idx.fl] == 0)
|
|
__unset_fl(tlsf, mem->tlsf_idx.fl);
|
|
}
|
|
}
|
|
|
|
int dsp_tlsf_init(struct dsp_tlsf *tlsf, unsigned long start_addr,
|
|
size_t size, unsigned int align)
|
|
{
|
|
int idx, jdx;
|
|
struct dsp_tlsf_mem *init_mem;
|
|
|
|
size = size & ~(align - 1);
|
|
DL_DEBUG("Tlsf max size aligned : %zu\n", size);
|
|
|
|
tlsf->align = align;
|
|
tlsf->max_size = size;
|
|
tlsf->max_sh = __get_max_sh(size);
|
|
|
|
DL_DEBUG("max_sh : %d\n", tlsf->max_sh);
|
|
|
|
tlsf->fl = 0;
|
|
DL_DEBUG("fl size : %u\n", (unsigned int)__get_fl_size(tlsf));
|
|
|
|
tlsf->sl = (unsigned char *)dsp_dl_malloc(
|
|
sizeof(*tlsf->sl) * __get_fl_size(tlsf),
|
|
"TLSF sl");
|
|
|
|
for (idx = 0; idx < __get_fl_size(tlsf); idx++)
|
|
tlsf->sl[idx] = 0;
|
|
|
|
DL_DEBUG("sl alloced\n");
|
|
|
|
tlsf->fb = (struct dsp_list_head (*)[8])dsp_dl_malloc(
|
|
sizeof(*tlsf->fb) * __get_fl_size(tlsf),
|
|
"TLSF fb");
|
|
|
|
for (idx = 0; idx < __get_fl_size(tlsf); idx++)
|
|
for (jdx = 0; jdx < TLSF_SL_SIZE; jdx++)
|
|
dsp_list_head_init(&tlsf->fb[idx][jdx]);
|
|
|
|
DL_DEBUG("fb (%d, %d) alloced\n", __get_fl_size(tlsf), TLSF_SL_SIZE);
|
|
|
|
dsp_list_head_init(&tlsf->mem_list);
|
|
init_mem = (struct dsp_tlsf_mem *)dsp_dl_malloc(
|
|
sizeof(*init_mem), "Init mem");
|
|
dsp_tlsf_mem_init(init_mem);
|
|
init_mem->type = MEM_EMPTY;
|
|
init_mem->start_addr = start_addr;
|
|
init_mem->size = size;
|
|
|
|
DL_DEBUG("init mem size : %zu\n", init_mem->size);
|
|
|
|
dsp_list_node_push_back(&tlsf->mem_list, &init_mem->mem_list_node);
|
|
dsp_tlsf_insert_block(init_mem, tlsf);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void dsp_tlsf_delete(struct dsp_tlsf *tlsf)
|
|
{
|
|
dsp_list_free(&tlsf->mem_list, struct dsp_tlsf_mem, mem_list_node);
|
|
dsp_dl_free(tlsf->sl);
|
|
dsp_dl_free(tlsf->fb);
|
|
}
|
|
|
|
int dsp_tlsf_is_prev_empty(struct dsp_tlsf_mem *mem)
|
|
{
|
|
struct dsp_list_node *mem_node = &mem->mem_list_node;
|
|
struct dsp_tlsf_mem *prev_mem;
|
|
|
|
if (mem_node->prev == NULL)
|
|
return 0;
|
|
|
|
prev_mem = container_of(mem_node->prev, struct dsp_tlsf_mem,
|
|
mem_list_node);
|
|
if (prev_mem->type != MEM_EMPTY)
|
|
return 0;
|
|
else
|
|
return 1;
|
|
}
|
|
|
|
int dsp_tlsf_is_next_empty(struct dsp_tlsf_mem *mem)
|
|
{
|
|
struct dsp_list_node *mem_node = &mem->mem_list_node;
|
|
struct dsp_tlsf_mem *next_mem;
|
|
|
|
if (mem_node->next == NULL)
|
|
return 0;
|
|
|
|
next_mem = container_of(mem_node->next, struct dsp_tlsf_mem,
|
|
mem_list_node);
|
|
if (next_mem->type != MEM_EMPTY)
|
|
return 0;
|
|
else
|
|
return 1;
|
|
}
|
|
|
|
int dsp_tlsf_malloc(size_t size, struct dsp_tlsf_mem **mem,
|
|
struct dsp_tlsf *tlsf)
|
|
{
|
|
int ret;
|
|
struct dsp_tlsf_mem *new_mem;
|
|
|
|
size = __align_mem_size(tlsf, size);
|
|
ret = dsp_tlsf_find_block(size, mem, tlsf);
|
|
if (ret == -1) {
|
|
DL_DEBUG("[%s] CHK_ERR\n", __func__);
|
|
return -1;
|
|
}
|
|
|
|
(*mem)->type = MEM_USE;
|
|
dsp_tlsf_remove_block(*mem, tlsf);
|
|
|
|
if ((*mem)->size - size >= TLSF_MIN_BLOCK_SIZE) {
|
|
new_mem = (struct dsp_tlsf_mem *)dsp_dl_malloc(
|
|
sizeof(*new_mem), "TLSF New mem");
|
|
dsp_tlsf_mem_init(new_mem);
|
|
new_mem->type = MEM_EMPTY;
|
|
new_mem->start_addr = (*mem)->start_addr + size;
|
|
new_mem->size = (*mem)->size - size;
|
|
|
|
dsp_list_node_insert_back(&tlsf->mem_list,
|
|
&(*mem)->mem_list_node,
|
|
&new_mem->mem_list_node);
|
|
dsp_tlsf_insert_block(new_mem, tlsf);
|
|
|
|
(*mem)->size = size;
|
|
}
|
|
|
|
DL_DEBUG("TLSF malloc end\n");
|
|
return 0;
|
|
}
|
|
|
|
int dsp_tlsf_free(struct dsp_tlsf_mem *mem, struct dsp_tlsf *tlsf)
|
|
{
|
|
int ret;
|
|
struct dsp_tlsf_mem *merge;
|
|
|
|
if (mem->type == MEM_EMPTY) {
|
|
DL_ERROR("struct dsp_tlsf_mem is already empty\n");
|
|
return -1;
|
|
}
|
|
|
|
mem->type = MEM_EMPTY;
|
|
mem->lib = NULL;
|
|
DL_DEBUG("0x%lx free\n", mem->start_addr);
|
|
|
|
merge = mem;
|
|
|
|
if (dsp_tlsf_is_prev_empty(merge)) {
|
|
struct dsp_list_node *node = &merge->mem_list_node;
|
|
struct dsp_tlsf_mem *prev_mem = container_of(node->prev,
|
|
struct dsp_tlsf_mem, mem_list_node);
|
|
|
|
DL_DEBUG("Merge prev\n");
|
|
DL_DEBUG("prev_mem : 0x%lx\n", prev_mem->start_addr);
|
|
dsp_tlsf_remove_block(prev_mem, tlsf);
|
|
|
|
merge = dsp_tlsf_mem_empty_merge(prev_mem, merge,
|
|
&tlsf->mem_list);
|
|
|
|
if (!merge) {
|
|
DL_ERROR("struct dsp_tlsf_mem Merge failed\n");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (dsp_tlsf_is_next_empty(merge)) {
|
|
struct dsp_list_node *node = &merge->mem_list_node;
|
|
struct dsp_tlsf_mem *next_mem = container_of(node->next,
|
|
struct dsp_tlsf_mem, mem_list_node);
|
|
|
|
DL_DEBUG("Merge next\n");
|
|
DL_DEBUG("next_mem : 0x%lx\n", next_mem->start_addr);
|
|
dsp_tlsf_remove_block(next_mem, tlsf);
|
|
|
|
merge = dsp_tlsf_mem_empty_merge(merge, next_mem,
|
|
&tlsf->mem_list);
|
|
|
|
if (!merge) {
|
|
DL_ERROR("struct dsp_tlsf_mem Merge failed\n");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
ret = dsp_tlsf_insert_block(merge, tlsf);
|
|
if (ret == -1) {
|
|
DL_ERROR("[%s] CHK_ERR\n", __func__);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void dsp_tlsf_print(struct dsp_tlsf *tlsf)
|
|
{
|
|
int idx, jdx;
|
|
struct dsp_list_node *node;
|
|
|
|
DL_INFO("TLSF table\n");
|
|
DL_BUF_STR("First level: ");
|
|
|
|
for (idx = __get_fl_size(tlsf) - 1; idx >= 0; idx--) {
|
|
DL_BUF_STR("%d ", (tlsf->fl & (1 << idx)) >> idx);
|
|
if (idx % 4 == 0 && idx != 0)
|
|
DL_BUF_STR(" ");
|
|
}
|
|
|
|
DL_BUF_STR("\n");
|
|
DL_PRINT_BUF(INFO);
|
|
|
|
DL_INFO("Second level\n");
|
|
|
|
for (idx = __get_fl_size(tlsf) - 1; idx >= 0; idx--) {
|
|
DL_BUF_STR("[%d] ", idx);
|
|
|
|
for (jdx = TLSF_SL_SIZE - 1; jdx >= 0; jdx--) {
|
|
DL_BUF_STR("%d ", (tlsf->sl[idx] & (1 << jdx)) >> jdx);
|
|
if (jdx % 4 == 0 && jdx != 0)
|
|
DL_BUF_STR(" ");
|
|
}
|
|
|
|
DL_BUF_STR("\n");
|
|
DL_PRINT_BUF(INFO);
|
|
}
|
|
|
|
DL_INFO("\n");
|
|
|
|
DL_INFO("Memory remained\n");
|
|
for (idx = 0; idx < __get_fl_size(tlsf); idx++) {
|
|
for (jdx = 0; jdx < TLSF_SL_SIZE; jdx++) {
|
|
if (tlsf->sl[idx] & (1 << jdx)) {
|
|
struct dsp_tlsf_idx tlsf_idx;
|
|
struct dsp_list_head *head;
|
|
|
|
tlsf_idx.fl = idx;
|
|
tlsf_idx.sl = jdx;
|
|
head = __get_head_from_idx(tlsf, &tlsf_idx);
|
|
dsp_list_for_each(node, head) {
|
|
struct dsp_tlsf_mem *mem;
|
|
|
|
mem = container_of(node,
|
|
struct dsp_tlsf_mem,
|
|
tlsf_node);
|
|
dsp_tlsf_mem_print(mem);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
DL_INFO("\n");
|
|
|
|
DL_INFO("Memory list\n");
|
|
dsp_list_for_each(node, &tlsf->mem_list) {
|
|
struct dsp_tlsf_mem *mem =
|
|
container_of(node, struct dsp_tlsf_mem, mem_list_node);
|
|
|
|
dsp_tlsf_mem_print(mem);
|
|
}
|
|
}
|
|
|
|
int dsp_tlsf_can_be_loaded(struct dsp_tlsf *tlsf, size_t size)
|
|
{
|
|
struct dsp_list_node *node;
|
|
unsigned int max_alloc = 0;
|
|
unsigned int local_alloc = 0;
|
|
int accum_flag = 0;
|
|
|
|
dsp_list_for_each(node, &tlsf->mem_list) {
|
|
struct dsp_tlsf_mem *cur_mem =
|
|
container_of(node, struct dsp_tlsf_mem, mem_list_node);
|
|
|
|
if (accum_flag) {
|
|
if (cur_mem->type == MEM_USE &&
|
|
cur_mem->lib->ref_cnt > 0) {
|
|
accum_flag = 0;
|
|
DL_DEBUG("Local alloc : %u\n", local_alloc);
|
|
|
|
if (local_alloc > max_alloc)
|
|
max_alloc = local_alloc;
|
|
|
|
local_alloc = 0;
|
|
} else
|
|
local_alloc += cur_mem->size;
|
|
} else {
|
|
if (cur_mem->type != MEM_USE ||
|
|
cur_mem->lib->ref_cnt == 0) {
|
|
accum_flag = 1;
|
|
local_alloc = cur_mem->size;
|
|
}
|
|
}
|
|
}
|
|
|
|
DL_DEBUG("Local alloc : %u\n", local_alloc);
|
|
|
|
if (accum_flag) {
|
|
if (local_alloc > max_alloc)
|
|
max_alloc = local_alloc;
|
|
}
|
|
|
|
DL_DEBUG("Max alloc : %u, size : %zu\n", max_alloc, size);
|
|
|
|
if (max_alloc >= size)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|