* Copyright (c) 2025 Huawei Technologies Co., Ltd.
* This program is free software, you can redistribute it and/or modify it under the terms and conditions of
* CANN Open Software License Agreement Version 2.0 (the "License").
* Please refer to the License for details. You may not use this file except in compliance with the License.
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
* See LICENSE in the root of the software repository for the full text of the License.
*/
#include <unistd.h>
#include "drv_buff_unibuff.h"
#include "drv_buff_common.h"
#include "atomic_ref.h"
#include "buff_mng.h"
#include "buff_ioctl.h"
#include "drv_buff_mbuf.h"
#include "drv_buff_memzone.h"
#include "buff_user_interface.h"
#include "buff_range.h"
#define RANGE_IDLE_CNT_MAX 5
#define BUFF_RANGE_MAX_BLOCKS_NUM XSMEM_BLOCK_MAX_NUM
#define BUFF_RANGE_MAX_ROWS_NUM 512
#define BUFF_RANGE_MAX_COLUMNS_NUM 512
#define BUFF_RANGE_COLUMN_MASK (0x1FF)
#define BUFF_RANGE_ROW_BIT_SHIFT (9)
#define ATOMIC_SET(x, y) __sync_lock_test_and_set((x), (y))
#define CAS(ptr, oldval, newval) __sync_bool_compare_and_swap(ptr, oldval, newval)
struct section_base {
unsigned int owner : 1;
unsigned int idlecnt : 7;
unsigned int pool_id : 24;
};
typedef union range_section_base {
struct section_base info;
unsigned int value;
} range_section_base;
struct range_section {
unsigned int devid;
union atomic_status status;
range_section_base base;
void *start;
unsigned long size;
unsigned long long mem_mng;
};
struct range_section_array {
struct range_section *cur_addr;
};
static THREAD struct range_section_array g_range_section[BUFF_RANGE_MAX_ROWS_NUM] = {};
static drvError_t add_others_buff_to_range(unsigned int block_id, int pool_id, void *start, unsigned long size,
unsigned long long mem_mng);
static inline drvError_t range_get_index(unsigned int block_id, unsigned int *row, unsigned int *column)
{
if (block_id >= BUFF_RANGE_MAX_BLOCKS_NUM) {
buff_err("Block id is invalid. (block_id=%u)\n", block_id);
return DRV_ERROR_PARA_ERROR;
}
*row = block_id >> BUFF_RANGE_ROW_BIT_SHIFT;
*column = block_id & BUFF_RANGE_COLUMN_MASK;
return DRV_ERROR_NONE;
}
static inline struct range_section_array *range_get_array(unsigned int row)
{
return &g_range_section[row];
}
static inline bool range_section_array_is_valid(struct range_section_array *array)
{
return (array->cur_addr != NULL);
}
static bool range_section_array_get(struct range_section_array *array)
{
struct range_section *addr = NULL;
if (!range_section_array_is_valid(array)) {
addr = (struct range_section *)calloc(BUFF_RANGE_MAX_COLUMNS_NUM, sizeof(struct range_section));
if (addr == NULL) {
buff_err("Malloc section array failed.\n");
return false;
}
if (!CAS(&array->cur_addr, NULL, addr)) {
free(addr);
}
}
return true;
}
static inline bool range_section_is_valid(struct range_section *section)
{
return atomic_ref_is_valid(§ion->status);
}
static inline bool is_others_section(const struct range_section *section)
{
return (section->base.info.owner == RANGE_OWNER_OTHERS);
}
static inline bool is_self_section(const struct range_section *section)
{
return (section->base.info.owner == RANGE_OWNER_SELF);
}
static inline bool buff_range_check(const void *blk_start, unsigned long blk_size, const void *start, unsigned long size)
{
if (size > (ULONG_MAX - (unsigned long)(uintptr_t)start)) {
return false;
}
if ((start < blk_start) || (((uintptr_t)start + size) > ((uintptr_t)blk_start + blk_size))) {
return false;
}
return true;
}
static inline bool range_section_check(const struct range_section *section, const void *start, unsigned long size)
{
return buff_range_check(section->start, section->size, start, size);
}
static inline void range_section_info_fill(struct range_section *section, range_section_base *base, void *start,
unsigned long size, unsigned long long mem_mng)
{
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-value"
(void)ATOMIC_SET(&(section->base.value), base->value);
(void)ATOMIC_SET(&(section->start), start);
(void)ATOMIC_SET(&(section->size), size);
(void)ATOMIC_SET(&(section->mem_mng), mem_mng);
(void)ATOMIC_SET(&(section->devid), (mem_mng != 0) ?
((struct common_handle_t *)(uintptr_t)mem_mng)->devid : BUFF_INVALID_DEV);
#pragma GCC diagnostic pop
}
static bool range_section_add(struct range_section *section)
{
do {
if (atomic_ref_try_init(§ion->status)) {
return true;
}
if (atomic_ref_inc(§ion->status)) {
break;
}
atomic_ref_self_healing(§ion->status);
} while (true);
return false;
}
static drvError_t add_buff_to_range(uint32_t block_id, range_section_base *base, void *start, unsigned long size,
unsigned long long mem_mng)
{
struct range_section_array *array = NULL;
struct range_section *section = NULL;
unsigned int row, column;
if (range_get_index(block_id, &row, &column) != DRV_ERROR_NONE) {
return DRV_ERROR_PARA_ERROR;
}
array = range_get_array(row);
if (!range_section_array_get(array)) {
return DRV_ERROR_OUT_OF_MEMORY;
}
section = &array->cur_addr[column];
range_section_info_fill(section, base, start, size, mem_mng);
if (range_section_add(section)) {
return DRV_ERROR_NONE;
} else {
return DRV_ERROR_REPEATED_INIT;
}
}
drvError_t add_self_buff_to_range(uint32_t blk_id, int pool_id, void *start, unsigned long size,
unsigned long long mem_mng)
{
range_section_base base;
base.info.owner = RANGE_OWNER_SELF;
base.info.idlecnt = 0;
base.info.pool_id = (uint32_t)pool_id & 0xFFFFFF;
return add_buff_to_range(blk_id, &base, start, size, mem_mng);
}
static drvError_t add_others_buff_to_range(unsigned int block_id, int pool_id, void *start, unsigned long size,
unsigned long long mem_mng)
{
range_section_base base;
base.info.owner = RANGE_OWNER_OTHERS;
base.info.idlecnt = 0;
base.info.pool_id = (unsigned int)pool_id & 0xFFFFFF;
return add_buff_to_range(block_id, &base, start, size, mem_mng);
}
static inline bool range_section_refer(struct range_section *section)
{
return atomic_ref_inc(§ion->status);
}
static inline void range_section_unref(struct range_section *section)
{
(void)atomic_ref_dec(§ion->status);
}
STATIC struct range_section *range_section_get(unsigned int block_id, const void *start, unsigned long size,
struct range_section_array **out_array)
{
struct range_section_array *array = NULL;
struct range_section *section = NULL;
unsigned int row, column;
if (range_get_index(block_id, &row, &column) != DRV_ERROR_NONE) {
return NULL;
}
array = range_get_array(row);
if (!range_section_array_is_valid(array)) {
return NULL;
}
section = &array->cur_addr[column];
if (!range_section_refer(section)) {
return NULL;
}
if (!range_section_check(section, start, size)) {
range_section_unref(section);
buff_warn("Section range check failed. (block_id=%u, start=%p; size=%lu; section->start=%p, "
"section->size=%lu)\n", block_id, start, size, section->start, section->size);
return NULL;
}
*out_array = array;
return section;
}
static void range_section_put(struct range_section *section)
{
range_section_unref(section);
}
drvError_t buff_range_owner_get(uint32_t blk_id, void *start, unsigned long size, int *owner)
{
struct range_section_array *array = NULL;
struct range_section *section = NULL;
section = range_section_get(blk_id, start, size, &array);
if (section == NULL) {
buff_err("Range Section Get fail.\n");
return DRV_ERROR_NO_RESOURCES;
}
*owner = section->base.info.owner;
range_section_put(section);
return DRV_ERROR_NONE;
}
drvError_t buff_range_get_ref(void *start, unsigned long size, uint32_t *refcnt, uint32_t blk_id)
{
struct range_section_array *array = NULL;
struct range_section *section = NULL;
section = range_section_get(blk_id, start, size, &array);
if (section == NULL) {
buff_err("Range Section Get fail.\n");
return DRV_ERROR_NO_RESOURCES;
}
*refcnt = atomic_value_get(§ion->status);
range_section_put(section);
return DRV_ERROR_NONE;
}
drvError_t buff_range_mem_mng_get(uint32_t blk_id, void *start, unsigned long size, unsigned long long *mem_mng)
{
struct range_section_array *array = NULL;
struct range_section *section = NULL;
section = range_section_get(blk_id, start, size, &array);
if (section == NULL) {
buff_err("Range Section Get fail.\n");
return DRV_ERROR_NO_RESOURCES;
}
*mem_mng = section->mem_mng;
range_section_put(section);
return DRV_ERROR_NONE;
}
static drvError_t buff_range_section_get(unsigned int block_id, void *start, unsigned long size)
{
struct range_section_array *array = NULL;
struct range_section *section = NULL;
section = range_section_get(block_id, start, size, &array);
if (section == NULL) {
return DRV_ERROR_NO_RESOURCES;
}
if (is_others_section(section)) {
(void)range_section_refer(section);
section->base.info.idlecnt = 0;
}
range_section_put(section);
return DRV_ERROR_NONE;
}
drvError_t buff_range_get(uint32_t blk_id, void *start, unsigned long size)
{
void *alloc_addr = NULL;
unsigned long alloc_size;
int pool_id;
unsigned int new_id;
drvError_t ret;
if (buff_range_section_get(blk_id, start, size) == DRV_ERROR_NONE) {
return DRV_ERROR_NONE;
}
ret = buff_blk_get(start, &pool_id, &alloc_addr, &alloc_size, &new_id);
if (ret != DRV_ERROR_NONE) {
buff_warn("Not alloc. (block_id=%u, size=%lu)\n", blk_id, size);
return DRV_ERROR_NO_RESOURCES;
}
if (new_id != blk_id) {
(void)buff_blk_put(pool_id, alloc_addr);
buff_warn("Buff has been realloced. (block_id=%u, size=%lu)\n", blk_id, size);
return DRV_ERROR_NO_RESOURCES;
}
if (!buff_range_check(alloc_addr, alloc_size, start, size)) {
buff_warn("Invalid buff. (block_id=%u, alloc_size=%lu, size=%lu)\n",
blk_id, alloc_size, size);
buff_blk_put(pool_id, alloc_addr);
return DRV_ERROR_NO_RESOURCES;
}
ret = add_others_buff_to_range(new_id, pool_id, alloc_addr, alloc_size, 0);
if (ret == DRV_ERROR_NONE) {
return DRV_ERROR_NONE;
} else if (ret == DRV_ERROR_REPEATED_INIT) {
buff_blk_put(pool_id, alloc_addr);
return DRV_ERROR_NONE;
} else {
buff_blk_put(pool_id, alloc_addr);
buff_err("Add buff to range failed. (block_id=%u, size=%lu)\n", new_id, size);
return ret;
}
}
void buff_range_put(uint32_t blk_id, void *start)
{
struct range_section_array *array = NULL;
struct range_section *section = NULL;
section = range_section_get(blk_id, start, 1UL, &array);
if (section == NULL) {
buff_warn("Put invalid blk_id, buff may have been released. (blk_id=%u; start=%p)\n",
blk_id, start);
return;
}
if (is_others_section(section)) {
range_section_unref(section);
}
range_section_put(section);
}
void del_self_buff_from_range(uint32_t blk_id, void *start)
{
struct range_section_array *array = NULL;
struct range_section *section = NULL;
section = range_section_get(blk_id, start, 1UL, &array);
if (section == NULL) {
buff_err("Put invalid buff. (blk_id=%u)\n", blk_id);
return;
}
range_section_unref(section);
range_section_put(section);
}
static bool is_idle_section(struct range_section *section)
{
return (section->status.info.value == 0);
}
static bool idle_section_can_free(struct range_section *section, bool is_free_immediately)
{
if (is_free_immediately) {
return true;
}
if (!is_others_section(section)) {
return true;
}
if (section->base.info.idlecnt > RANGE_IDLE_CNT_MAX) {
return true;
}
section->base.info.idlecnt++;
return false;
}
static inline bool range_section_try_del(struct range_section *section)
{
return atomic_ref_try_uninit(§ion->status);
}
static drvError_t _idle_buff_range_free(struct range_section *section, bool is_free_immediately)
{
void *mem_mng = NULL;
void *start = NULL;
int pool_id;
if (range_section_is_valid(section) == false) {
return DRV_ERROR_NONE;
}
if (is_idle_section(section) == false) {
return DRV_ERROR_BUSY;
}
if (idle_section_can_free(section, is_free_immediately) == false) {
return DRV_ERROR_TRY_AGAIN;
}
pool_id = (int)section->base.info.pool_id;
start = section->start;
mem_mng = (void *)(uintptr_t)section->mem_mng;
if (!range_section_try_del(section)) {
return DRV_ERROR_STATUS_FAIL;
}
section->mem_mng = 0;
buff_blk_put(pool_id, start);
if (mem_mng != NULL) {
free(mem_mng);
}
return DRV_ERROR_NONE;
}
drvError_t idle_buff_range_free(uint32_t devid, uint32_t using_buff_max_show_cnt)
{
unsigned int using_buff_show_cnt = 0;
struct range_section *section = NULL;
unsigned int row;
drvError_t ret = DRV_ERROR_NONE;
drvError_t tmp_ret;
for (row = 0; row < BUFF_RANGE_MAX_ROWS_NUM; row++) {
struct range_section_array *array = range_get_array(row);
unsigned int column;
if (array->cur_addr == NULL) {
continue;
}
for (column = 0; column < BUFF_RANGE_MAX_COLUMNS_NUM; column++) {
section = &array->cur_addr[column];
if ((devid != BUFF_INVALID_DEV) && (devid != section->devid)) {
continue;
}
tmp_ret = _idle_buff_range_free(section, true);
if ((using_buff_show_cnt < using_buff_max_show_cnt) && (tmp_ret != DRV_ERROR_NONE)) {
buff_debug("Buff is in use. (start=0x%llx; size=%lu; ret=%u)\n",
(uint64_t)(uintptr_t)section->start, section->size, tmp_ret);
using_buff_show_cnt++;
ret = DRV_ERROR_BUSY;
}
}
}
return ret;
}
* thread releases the memory slowly. If the memory is not released in time,
* The OOM occurs.
*/
void idle_buff_range_free_ahead(uint32_t blk_id)
{
struct range_section_array *array = NULL;
struct range_section *section = NULL;
unsigned int row, column;
if (range_get_index(blk_id, &row, &column) != DRV_ERROR_NONE) {
return;
}
array = range_get_array(row);
if (!range_section_array_is_valid(array)) {
return;
}
section = &array->cur_addr[column];
if (is_self_section(section)) {
(void)_idle_buff_range_free(section, false);
} else if (section->size > MZ_DEFAULT_CFG_MAX_SIZE) {
#ifndef EMU_ST
(void)_idle_buff_range_free(section, true);
#endif
}
}
#ifdef EMU_ST
void del_others_range(void)
{
unsigned int row;
for (row = 0; row < BUFF_RANGE_MAX_ROWS_NUM; row++) {
struct range_section_array *array = range_get_array(row);
struct range_section *addr = NULL;
unsigned int column;
if (array->cur_addr == NULL) {
continue;
}
addr = array->cur_addr;
for (column = 0; column < BUFF_RANGE_MAX_COLUMNS_NUM; column++) {
struct range_section *section = &array->cur_addr[column];
if (!range_section_is_valid(section)) {
continue;
}
if (section->mem_mng != 0) {
free((void *)section->mem_mng);
section->mem_mng = 0;
}
}
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-value"
ATOMIC_SET(&array->cur_addr, NULL);
#pragma GCC diagnostic pop
free(addr);
}
}
#endif
void buff_range_show(void)
{
unsigned int row;
int num = 0;
for (row = 0; row < BUFF_RANGE_MAX_ROWS_NUM; row++) {
struct range_section_array *array = range_get_array(row);
unsigned int column;
if (!range_section_array_is_valid(array)) {
continue;
}
for (column = 0; column < BUFF_RANGE_MAX_COLUMNS_NUM; column++) {
struct range_section *section = &array->cur_addr[column];
if (!range_section_is_valid(section)) {
continue;
}
buff_show("Buff range show. (id=%d, owner=%u, refcnt=%u, size=%lx)\n",
num, section->base.info.owner, section->status.info.value, section->size);
num++;
}
}
buff_show("Buff range show. (total_num=%d, owner_self=%d, others=%d)\n", num, RANGE_OWNER_SELF, RANGE_OWNER_OTHERS);
}
#ifdef EMU_ST
uint32_t buff_range_get_valid_section_num(void)
{
uint32_t num = 0;
uint32_t row;
for (row = 0; row < BUFF_RANGE_MAX_ROWS_NUM; row++) {
struct range_section_array *array = range_get_array(row);
uint32_t column;
if (!range_section_array_is_valid(array)) {
continue;
}
for (column = 0; column < BUFF_RANGE_MAX_COLUMNS_NUM; column++) {
struct range_section *section = &array->cur_addr[column];
if (range_section_is_valid(section)) {
num++;
}
}
}
return num;
}
#endif
static drvError_t buff_get_blk_id_from_buf(void *buf, uint32_t *blk_id)
{
unsigned long size;
drvError_t ret;
int pool_id;
void *addr;
ret = buff_blk_get(buf, &pool_id, &addr, &size, blk_id);
if (ret != DRV_ERROR_NONE) {
buff_err("buff_blk_get fail. (ret=%d; buf=%p)\n", ret, buf);
return ret;
}
buff_blk_put(pool_id, addr);
return DRV_ERROR_NONE;
}
drvError_t halBuffGet(Mbuf *mbuf, void *buf, unsigned long size)
{
uint32_t blk_id;
if (is_buff_addr((uintptr_t)buf) == false) {
return DRV_ERROR_INVALID_VALUE;
}
if (mbuf == NULL) {
drvError_t ret = buff_get_blk_id_from_buf(buf, &blk_id);
if (ret != DRV_ERROR_NONE) {
buff_err("Get blk from buf fail. (ret=%d; buf=%p)\n", ret, buf);
return ret;
}
} else {
blk_id = mbuf->data_blk_id;
}
return buff_range_get(blk_id, buf, size);
}
void halBuffPut(Mbuf *mbuf, void *buf)
{
uint32_t blk_id;
if (mbuf == NULL) {
drvError_t ret = buff_get_blk_id_from_buf(buf, &blk_id);
if (ret != DRV_ERROR_NONE) {
buff_err("Get blk from buf fail. (ret=%d)\n", ret);
return;
}
} else {
blk_id = mbuf->data_blk_id;
}
buff_range_put(blk_id, buf);
}
#ifndef EMU_ST
int halBuffPoolGet(void* poolStart)
{
(void)poolStart;
return (int)DRV_ERROR_NOT_SUPPORT;
}
int halBuffPoolPut(void* poolStart)
{
(void)poolStart;
return (int)DRV_ERROR_NOT_SUPPORT;
}
#endif
drvError_t buff_device_open(uint32_t devid, halDevOpenIn *in, halDevOpenOut *out)
{
(void)in;
(void)out;
buff_info("buff open finish. (devid = %u)\n", devid);
return DRV_ERROR_NONE;
}
drvError_t buff_device_close(uint32_t devid, halDevCloseIn *in)
{
(void)in;
buff_info("buff close finish. (devid = %u)\n", devid);
return DRV_ERROR_NONE;
}