/* SPDX-License-Identifier: Apache-2.0 OR MIT */ /* * Copyright (c) 2015 Rockchip Electronics Co., Ltd. */ #define MODULE_TAG "mpp_buffer" #include #include "mpp_env.h" #include "mpp_hash.h" #include "mpp_lock.h" #include "mpp_time.h" #include "mpp_debug.h" #include "mpp_thread.h" #include "mpp_mem_pool.h" #include "mpp_singleton.h" #include "mpp_buffer_impl.h" #define MAX_GROUP_BIT 8 #define MAX_MISC_GROUP_BIT 3 #define BUFFER_OPS_MAX_COUNT 1024 #define MPP_ALLOCATOR_WITH_FLAG_NUM 8 /* NOTE: user may call buffer / buf_grp deinit after buffer service deinited */ typedef enum MppBufSrvStatus_e { MPP_BUF_SRV_UNINITED = -1, MPP_BUF_SRV_NORMAL = 0, MPP_BUF_SRV_FINALIZED = 1, MPP_BUF_SRV_BUTT, } MppBufSrvStatus; #define SEARCH_GROUP_BY_ID(srv, id) (get_group_by_id(srv, id)) #define get_srv_buffer() \ ({ \ MppBufferService *__tmp; \ if (srv_buffer) \ __tmp = srv_buffer; \ else { \ switch (srv_status) { \ case MPP_BUF_SRV_UNINITED : { \ mpp_buffer_service_init(); \ __tmp = srv_buffer; \ } break; \ case MPP_BUF_SRV_FINALIZED : { \ /* if called after buf srv deinited return NULL without error log */ \ __tmp = NULL; \ } break; \ default : { \ mpp_err("mpp buffer srv not init status %d at %s\n", __FUNCTION__); \ __tmp = NULL; \ } break; \ } \ } \ __tmp; \ }) static void mpp_buffer_service_init(); typedef MPP_RET (*BufferOp)(MppAllocator allocator, MppBufferInfo *data); typedef struct MppBufferService_t { rk_u32 group_id; rk_u32 group_count; rk_u32 finalizing; rk_u32 total_size; rk_u32 total_max; MppMutex lock; // misc group for internal / externl buffer with different type rk_u32 misc[MPP_BUFFER_MODE_BUTT][MPP_BUFFER_TYPE_BUTT][MPP_ALLOCATOR_WITH_FLAG_NUM]; rk_u32 misc_count; /* preset allocator apis */ MppAllocator allocator[MPP_BUFFER_TYPE_BUTT][MPP_ALLOCATOR_WITH_FLAG_NUM]; MppAllocatorApi *allocator_api[MPP_BUFFER_TYPE_BUTT]; struct list_head list_group; DECLARE_HASHTABLE(hash_group, MAX_GROUP_BIT); // list for used buffer which do not have group struct list_head list_orphan; } MppBufferService; static const char *mode2str[MPP_BUFFER_MODE_BUTT] = { "internal", "external", }; static const char *type2str[MPP_BUFFER_TYPE_BUTT] = { "normal", "ion", "dma-buf", "drm", }; static const char *ops2str[BUF_OPS_BUTT] = { "grp create ", "grp release", "grp reset", "grp orphan", "grp destroy", "buf commit ", "buf create ", "buf mmap ", "buf ref inc", "buf ref dec", "buf discard", "buf destroy", }; static MppMemPool pool_buf = NULL; static MppMemPool pool_buf_grp = NULL; static MppMemPool pool_buf_map_node = NULL; static MppBufferService *srv_buffer = NULL; static MppBufSrvStatus srv_status = MPP_BUF_SRV_UNINITED; rk_u32 mpp_buffer_debug = 0; static MppBufferGroupImpl *service_get_group(const char *tag, const char *caller, MppBufferMode mode, MppBufferType type, rk_u32 is_misc); static void service_put_group(MppBufferService *srv, MppBufferGroupImpl *p, const char *caller); static void service_dump(MppBufferService *srv, const char *info); static MppBufferGroupImpl *get_group_by_id(MppBufferService *srv, rk_u32 id) { MppBufferGroupImpl *impl = NULL; hash_for_each_possible(srv->hash_group, impl, hlist, id) { if (impl->group_id == id) break; } return impl; } static MppBufLogs *buf_logs_init(rk_u32 max_count) { MppBufLogs *logs = NULL; if (!max_count) return NULL; logs = mpp_malloc_size(MppBufLogs, sizeof(MppBufLogs) + max_count * sizeof(MppBufLog)); if (!logs) { mpp_err_f("failed to create %d buf logs\n", max_count); return NULL; } { pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); pthread_mutex_init(&logs->lock, &attr); pthread_mutexattr_destroy(&attr); } logs->max_count = max_count; logs->log_count = 0; logs->log_write = 0; logs->log_read = 0; logs->logs = (MppBufLog *)(logs + 1); return logs; } static void buf_logs_deinit(MppBufLogs *logs) { pthread_mutex_destroy(&logs->lock); MPP_FREE(logs); } static void buf_logs_write(MppBufLogs *logs, rk_u32 group_id, rk_s32 buffer_id, MppBufOps ops, rk_s32 ref_count, const char *caller) { MppBufLog *log = NULL; pthread_mutex_lock(&logs->lock); log = &logs->logs[logs->log_write]; log->group_id = group_id; log->buffer_id = buffer_id; log->ops = ops; log->ref_count = ref_count; log->caller = caller; logs->log_write++; if (logs->log_write >= logs->max_count) logs->log_write = 0; if (logs->log_count < logs->max_count) logs->log_count++; else { logs->log_read++; if (logs->log_read >= logs->max_count) logs->log_read = 0; } pthread_mutex_unlock(&logs->lock); } static void buf_logs_dump(MppBufLogs *logs) { while (logs->log_count) { MppBufLog *log = &logs->logs[logs->log_read]; if (log->buffer_id >= 0) mpp_log("group %3d buffer %4d ops %s ref_count %d caller %s\n", log->group_id, log->buffer_id, ops2str[log->ops], log->ref_count, log->caller); else mpp_log("group %3d ops %s\n", log->group_id, ops2str[log->ops]); logs->log_read++; if (logs->log_read >= logs->max_count) logs->log_read = 0; logs->log_count--; } mpp_assert(logs->log_read == logs->log_write); } static void buf_add_log(MppBufferImpl *buffer, MppBufOps ops, const char* caller) { if (buffer->log_runtime_en) { mpp_log("group %3d buffer %4d fd %3d ops %s ref_count %d caller %s\n", buffer->group_id, buffer->buffer_id, buffer->info.fd, ops2str[ops], buffer->ref_count, caller); } if (buffer->logs) buf_logs_write(buffer->logs, buffer->group_id, buffer->buffer_id, ops, buffer->ref_count, caller); } static void buf_grp_add_log(MppBufferGroupImpl *group, MppBufOps ops, const char* caller) { if (group->log_runtime_en) { mpp_log("group %3d mode %d type %d ops %s\n", group->group_id, group->mode, group->type, ops2str[ops]); } if (group->logs) buf_logs_write(group->logs, group->group_id, -1, ops, 0, caller); } static void dump_buffer_info(MppBufferImpl *buffer) { mpp_log("buffer %p fd %4d size %10d ref_count %3d discard %d caller %s\n", buffer, buffer->info.fd, buffer->info.size, buffer->ref_count, buffer->discard, buffer->caller); } void mpp_buffer_group_dump(MppBufferGroupImpl *group, const char *caller) { MppBufferImpl *pos, *n; mpp_log("\ndumping buffer group %p id %d from %s\n", group, group->group_id, caller); mpp_log("mode %s\n", mode2str[group->mode]); mpp_log("type %s\n", type2str[group->type]); mpp_log("limit size %d count %d\n", group->limit_size, group->limit_count); mpp_log("used buffer count %d\n", group->count_used); list_for_each_entry_safe(pos, n, &group->list_used, MppBufferImpl, list_status) { dump_buffer_info(pos); } mpp_log("unused buffer count %d\n", group->count_unused); list_for_each_entry_safe(pos, n, &group->list_unused, MppBufferImpl, list_status) { dump_buffer_info(pos); } if (group->logs) buf_logs_dump(group->logs); } static void clear_buffer_info(MppBufferInfo *info) { info->fd = -1; info->ptr = NULL; info->hnd = NULL; info->size = 0; info->index = -1; info->type = MPP_BUFFER_TYPE_BUTT; } static void service_put_buffer(MppBufferService *srv, MppBufferGroupImpl *group, MppBufferImpl *buffer, rk_u32 reuse, const char *caller) { struct list_head list_maps; MppDevBufMapNode *pos, *n; MppBufferInfo info; mpp_assert(group); pthread_mutex_lock(&buffer->lock); if (!srv && !srv->finalizing) { mpp_assert(buffer->ref_count == 0); if (buffer->ref_count > 0) { pthread_mutex_unlock(&buffer->lock); return; } } list_del_init(&buffer->list_status); if (reuse) { if (buffer->used && group) { group->count_used--; list_add_tail(&buffer->list_status, &group->list_unused); group->count_unused++; } else { mpp_err_f("can not reuse unused buffer %d at group %p:%d\n", buffer->buffer_id, group, buffer->group_id); } buffer->used = 0; pthread_mutex_unlock(&buffer->lock); return; } /* remove all map from buffer */ INIT_LIST_HEAD(&list_maps); list_for_each_entry_safe(pos, n, &buffer->list_maps, MppDevBufMapNode, list_buf) { list_move_tail(&pos->list_buf, &list_maps); pos->iova = (rk_u32)(-1); } mpp_assert(list_empty(&buffer->list_maps)); info = buffer->info; if (group) { rk_u32 destroy = 0; rk_u32 size = buffer->info.size; if (buffer->used) group->count_used--; else group->count_unused--; group->usage -= size; group->buffer_count--; /* reduce total buffer size record */ if (group->mode == MPP_BUFFER_INTERNAL && srv) MPP_FETCH_SUB(&srv->total_size, size); buf_add_log(buffer, BUF_DESTROY, caller); if (group->is_orphan && !group->usage && !group->is_finalizing) destroy = 1; if (destroy) service_put_group(srv, group, caller); } else { mpp_assert(srv_status); } clear_buffer_info(&buffer->info); pthread_mutex_unlock(&buffer->lock); list_for_each_entry_safe(pos, n, &list_maps, MppDevBufMapNode, list_buf) { MppDev dev = pos->dev; mpp_assert(dev); mpp_dev_ioctl(dev, MPP_DEV_LOCK_MAP, NULL); /* remove buffer from group */ mpp_dev_ioctl(dev, MPP_DEV_DETACH_FD, pos); mpp_dev_ioctl(dev, MPP_DEV_UNLOCK_MAP, NULL); mpp_mem_pool_put(pool_buf_map_node, pos, caller); } /* release buffer here */ { BufferOp func = (buffer->mode == MPP_BUFFER_INTERNAL) ? (buffer->alloc_api->free) : (buffer->alloc_api->release); func(buffer->allocator, &info); } mpp_mem_pool_put(pool_buf, buffer, caller); } static MPP_RET inc_buffer_ref(MppBufferImpl *buffer, const char *caller) { MPP_RET ret = MPP_OK; pthread_mutex_lock(&buffer->lock); buffer->ref_count++; buf_add_log(buffer, BUF_REF_INC, caller); if (!buffer->used) { MppBufferGroupImpl *group = NULL; MppBufferService *srv = get_srv_buffer(); if (srv) { mpp_mutex_lock(&srv->lock); group = SEARCH_GROUP_BY_ID(srv, buffer->group_id); mpp_mutex_unlock(&srv->lock); } // NOTE: when increasing ref_count the unused buffer must be under certain group mpp_assert(group); buffer->used = 1; if (group) { pthread_mutex_lock(&group->buf_lock); list_del_init(&buffer->list_status); list_add_tail(&buffer->list_status, &group->list_used); group->count_used++; group->count_unused--; pthread_mutex_unlock(&group->buf_lock); } else { mpp_err_f("unused buffer without group\n"); ret = MPP_NOK; } } pthread_mutex_unlock(&buffer->lock); return ret; } MPP_RET mpp_buffer_create(const char *tag, const char *caller, MppBufferGroupImpl *group, MppBufferInfo *info, MppBufferImpl **buffer) { MPP_BUF_FUNCTION_ENTER(); MPP_RET ret = MPP_OK; BufferOp func = NULL; MppBufferImpl *p = NULL; if (!group) { mpp_err_f("can not create buffer without group\n"); ret = MPP_NOK; goto RET; } if (group->limit_count && group->buffer_count >= group->limit_count) { if (group->log_runtime_en) mpp_log_f("group %d reach count limit %d\n", group->group_id, group->limit_count); ret = MPP_NOK; goto RET; } if (group->limit_size && info->size > group->limit_size) { mpp_err_f("required size %d reach group size limit %d\n", info->size, group->limit_size); ret = MPP_NOK; goto RET; } p = (MppBufferImpl *)mpp_mem_pool_get(pool_buf, caller); if (!p) { mpp_err_f("failed to allocate context\n"); ret = MPP_ERR_MALLOC; goto RET; } func = (group->mode == MPP_BUFFER_INTERNAL) ? (group->alloc_api->alloc) : (group->alloc_api->import); ret = func(group->allocator, info); if (ret) { mpp_err_f("failed to create buffer with size %d\n", info->size); mpp_mem_pool_put(pool_buf, p, caller); ret = MPP_ERR_MALLOC; goto RET; } if (!tag) tag = group->tag; snprintf(p->tag, sizeof(p->tag), "%s", tag); p->caller = caller; pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); pthread_mutex_init(&p->lock, &attr); pthread_mutexattr_destroy(&attr); p->allocator = group->allocator; p->alloc_api = group->alloc_api; p->log_runtime_en = group->log_runtime_en; p->log_history_en = group->log_history_en; p->group_id = group->group_id; p->mode = group->mode; p->type = group->type; p->uncached = (group->flags & MPP_ALLOC_FLAG_CACHABLE) ? 0 : 1; p->logs = group->logs; p->info = *info; pthread_mutex_lock(&group->buf_lock); p->buffer_id = group->buffer_id++; INIT_LIST_HEAD(&p->list_status); INIT_LIST_HEAD(&p->list_maps); if (buffer) { p->ref_count++; p->used = 1; list_add_tail(&p->list_status, &group->list_used); group->count_used++; *buffer = p; } else { list_add_tail(&p->list_status, &group->list_unused); group->count_unused++; } group->usage += info->size; group->buffer_count++; pthread_mutex_unlock(&group->buf_lock); buf_add_log(p, (group->mode == MPP_BUFFER_INTERNAL) ? (BUF_CREATE) : (BUF_COMMIT), caller); if (group->mode == MPP_BUFFER_INTERNAL) { MppBufferService *srv = get_srv_buffer(); if (srv) { rk_u32 total = MPP_ADD_FETCH(&srv->total_size, info->size); bool cas_ret; do { rk_u32 old_max = srv->total_max; rk_u32 new_max = MPP_MAX(total, old_max); cas_ret = MPP_BOOL_CAS(&srv->total_max, old_max, new_max); } while (!cas_ret); } } if (group->callback) group->callback(group->arg, group); RET: MPP_BUF_FUNCTION_LEAVE(); return ret; } MPP_RET mpp_buffer_mmap(MppBufferImpl *buffer, const char* caller) { MPP_BUF_FUNCTION_ENTER(); MPP_RET ret = buffer->alloc_api->mmap(buffer->allocator, &buffer->info); if (ret) mpp_err_f("buffer %d group %d fd %d map failed caller %s\n", buffer->buffer_id, buffer->group_id, buffer->info.fd, caller); buf_add_log(buffer, BUF_MMAP, caller); MPP_BUF_FUNCTION_LEAVE(); return ret; } MPP_RET mpp_buffer_ref_inc(MppBufferImpl *buffer, const char* caller) { MPP_BUF_FUNCTION_ENTER(); MPP_RET ret = inc_buffer_ref(buffer, caller); MPP_BUF_FUNCTION_LEAVE(); return ret; } MPP_RET mpp_buffer_ref_dec(MppBufferImpl *buffer, const char* caller) { MPP_RET ret = MPP_OK; rk_u32 release = 0; MPP_BUF_FUNCTION_ENTER(); pthread_mutex_lock(&buffer->lock); if (buffer->ref_count <= 0) { buf_add_log(buffer, BUF_REF_DEC, caller); mpp_err_f("buffer from %s found non-positive ref_count %d caller %s\n", buffer->caller, buffer->ref_count, caller); mpp_abort(); ret = MPP_NOK; pthread_mutex_unlock(&buffer->lock); goto done; } buffer->ref_count--; if (buffer->ref_count == 0) release = 1; buf_add_log(buffer, BUF_REF_DEC, caller); pthread_mutex_unlock(&buffer->lock); if (release) { MppBufferGroupImpl *group = NULL; MppBufferService *srv = get_srv_buffer(); if (srv) { mpp_mutex_lock(&srv->lock); group = SEARCH_GROUP_BY_ID(srv, buffer->group_id); mpp_mutex_unlock(&srv->lock); } mpp_assert(group); if (group) { rk_u32 reuse = 0; pthread_mutex_lock(&group->buf_lock); reuse = (!group->is_misc && !buffer->discard); service_put_buffer(srv, group, buffer, reuse, caller); if (group->callback) group->callback(group->arg, group); pthread_mutex_unlock(&group->buf_lock); } } done: MPP_BUF_FUNCTION_LEAVE(); return ret; } MPP_RET mpp_buffer_discard(MppBufferImpl *buffer, const char* caller) { MppBufferService *srv = get_srv_buffer(); MppBufferGroupImpl *group = NULL; MPP_BUF_FUNCTION_ENTER(); if (srv) { mpp_mutex_lock(&srv->lock); group = SEARCH_GROUP_BY_ID(srv, buffer->group_id); mpp_mutex_unlock(&srv->lock); } mpp_assert(group); if (group) { pthread_mutex_lock(&group->buf_lock); buffer->discard = 1; buf_add_log(buffer, BUF_DISCARD, caller); pthread_mutex_unlock(&group->buf_lock); } MPP_BUF_FUNCTION_LEAVE(); return MPP_OK; } MppBufferImpl *mpp_buffer_get_unused(MppBufferGroupImpl *p, size_t size, const char* caller) { MppBufferImpl *buffer = NULL; MPP_BUF_FUNCTION_ENTER(); pthread_mutex_lock(&p->buf_lock); if (!list_empty(&p->list_unused)) { MppBufferImpl *pos, *n; rk_s32 found = 0; rk_s32 search_count = 0; list_for_each_entry_safe(pos, n, &p->list_unused, MppBufferImpl, list_status) { mpp_buf_dbg(MPP_BUF_DBG_CHECK_SIZE, "request size %d on buf idx %d size %d\n", size, pos->buffer_id, pos->info.size); if (pos->info.size >= size) { buffer = pos; pthread_mutex_lock(&buffer->lock); buffer->ref_count++; buffer->used = 1; buf_add_log(buffer, BUF_REF_INC, caller); list_del_init(&buffer->list_status); list_add_tail(&buffer->list_status, &p->list_used); p->count_used++; p->count_unused--; pthread_mutex_unlock(&buffer->lock); found = 1; break; } else { if (MPP_BUFFER_INTERNAL == p->mode) { service_put_buffer(get_srv_buffer(), p, pos, 0, caller); } else search_count++; } } if (!found && search_count) { mpp_err_f("can not found match buffer with size larger than %d\n", size); mpp_buffer_group_dump(p, caller); } } pthread_mutex_unlock(&p->buf_lock); MPP_BUF_FUNCTION_LEAVE(); return buffer; } rk_u32 mpp_buffer_to_addr(MppBuffer buffer, size_t offset) { MppBufferImpl *impl = (MppBufferImpl *)buffer; rk_u32 addr = 0; if (!impl) { mpp_err_f("NULL buffer convert to zero address\n"); return 0; } if (impl->info.fd >= (1 << 10)) { mpp_err_f("buffer fd %d is too large\n"); return 0; } if (impl->offset + offset >= SZ_4M) { mpp_err_f("offset %d + %d is larger than 4M use extra info to send offset\n"); return 0; } addr = impl->info.fd + ((impl->offset + offset) << 10); return addr; } static MppDevBufMapNode *mpp_buffer_attach_dev_lock(const char *caller, MppBuffer buffer, MppDev dev) { MppBufferImpl *impl = (MppBufferImpl *)buffer; MppDevBufMapNode *pos, *n; MppDevBufMapNode *node = NULL; MPP_RET ret = MPP_OK; mpp_dev_ioctl(dev, MPP_DEV_LOCK_MAP, NULL); pthread_mutex_lock(&impl->lock); list_for_each_entry_safe(pos, n, &impl->list_maps, MppDevBufMapNode, list_buf) { if (pos->dev == dev) { node = pos; goto DONE; } } node = (MppDevBufMapNode *)mpp_mem_pool_get(pool_buf_map_node, caller); if (!node) { mpp_err("mpp_buffer_attach_dev failed to allocate map node\n"); ret = MPP_NOK; goto DONE; } INIT_LIST_HEAD(&node->list_buf); INIT_LIST_HEAD(&node->list_dev); node->lock_buf = &impl->lock; node->buffer = impl; node->dev = dev; node->pool = pool_buf_map_node; node->buf_fd = impl->info.fd; ret = mpp_dev_ioctl(dev, MPP_DEV_ATTACH_FD, node); if (ret) { mpp_mem_pool_put(pool_buf_map_node, node, caller); node = NULL; goto DONE; } list_add_tail(&node->list_buf, &impl->list_maps); DONE: pthread_mutex_unlock(&impl->lock); mpp_dev_ioctl(dev, MPP_DEV_UNLOCK_MAP, NULL); return node; } MPP_RET mpp_buffer_attach_dev_f(const char *caller, MppBuffer buffer, MppDev dev) { MppDevBufMapNode *node; node = mpp_buffer_attach_dev_lock(caller, buffer, dev); return node ? MPP_OK : MPP_NOK; } MPP_RET mpp_buffer_detach_dev_f(const char *caller, MppBuffer buffer, MppDev dev) { MppBufferImpl *impl = (MppBufferImpl *)buffer; MppDevBufMapNode *pos, *n; MPP_RET ret = MPP_OK; mpp_dev_ioctl(dev, MPP_DEV_LOCK_MAP, NULL); pthread_mutex_lock(&impl->lock); list_for_each_entry_safe(pos, n, &impl->list_maps, MppDevBufMapNode, list_buf) { if (pos->dev == dev) { list_del_init(&pos->list_buf); ret = mpp_dev_ioctl(dev, MPP_DEV_DETACH_FD, pos); mpp_mem_pool_put(pool_buf_map_node, pos, caller); break; } } pthread_mutex_unlock(&impl->lock); mpp_dev_ioctl(dev, MPP_DEV_UNLOCK_MAP, NULL); return ret; } rk_u32 mpp_buffer_get_iova_f(const char *caller, MppBuffer buffer, MppDev dev) { MppDevBufMapNode *node; node = mpp_buffer_attach_dev_lock(caller, buffer, dev); return node ? node->iova : (rk_u32)(-1); } MPP_RET mpp_buffer_group_init(MppBufferGroupImpl **group, const char *tag, const char *caller, MppBufferMode mode, MppBufferType type) { MPP_BUF_FUNCTION_ENTER(); mpp_assert(caller); *group = service_get_group(tag, caller, mode, type, 0); MPP_BUF_FUNCTION_LEAVE(); return ((*group) ? (MPP_OK) : (MPP_NOK)); } MPP_RET mpp_buffer_group_deinit(MppBufferGroupImpl *p) { if (!p) { mpp_err_f("found NULL pointer\n"); return MPP_ERR_NULL_PTR; } MPP_BUF_FUNCTION_ENTER(); service_put_group(get_srv_buffer(), p, __FUNCTION__); MPP_BUF_FUNCTION_LEAVE(); return MPP_OK; } MPP_RET mpp_buffer_group_reset(MppBufferGroupImpl *p) { if (!p) { mpp_err_f("found NULL pointer\n"); return MPP_ERR_NULL_PTR; } MPP_BUF_FUNCTION_ENTER(); pthread_mutex_lock(&p->buf_lock); buf_grp_add_log(p, GRP_RESET, NULL); if (!list_empty(&p->list_used)) { MppBufferImpl *pos, *n; list_for_each_entry_safe(pos, n, &p->list_used, MppBufferImpl, list_status) { buf_add_log(pos, BUF_DISCARD, NULL); pos->discard = 1; } } // remove unused list if (!list_empty(&p->list_unused)) { MppBufferService *srv = get_srv_buffer(); MppBufferImpl *pos, *n; list_for_each_entry_safe(pos, n, &p->list_unused, MppBufferImpl, list_status) { service_put_buffer(srv, p, pos, 0, __FUNCTION__); } } pthread_mutex_unlock(&p->buf_lock); MPP_BUF_FUNCTION_LEAVE(); return MPP_OK; } MPP_RET mpp_buffer_group_set_callback(MppBufferGroupImpl *p, MppBufCallback callback, void *arg) { if (!p) return MPP_OK; MPP_BUF_FUNCTION_ENTER(); p->arg = arg; p->callback = callback; MPP_BUF_FUNCTION_LEAVE(); return MPP_OK; } rk_u32 mpp_buffer_total_now() { MppBufferService *srv = get_srv_buffer(); rk_u32 size = 0; if (srv) size = srv->total_size; return size; } rk_u32 mpp_buffer_total_max() { MppBufferService *srv = get_srv_buffer(); rk_u32 size = 0; if (srv) size = srv->total_max; return size; } static rk_u32 type_to_flag(MppBufferType type) { rk_u32 flag = MPP_ALLOC_FLAG_NONE; if (type & MPP_BUFFER_FLAGS_DMA32) flag += MPP_ALLOC_FLAG_DMA32; if (type & MPP_BUFFER_FLAGS_CACHABLE) flag += MPP_ALLOC_FLAG_CACHABLE; if (type & MPP_BUFFER_FLAGS_CONTIG) flag += MPP_ALLOC_FLAG_CMA; return flag; } static rk_u32 service_get_misc(MppBufferService *srv, MppBufferMode mode, MppBufferType type) { rk_u32 flag = type_to_flag(type); type = (MppBufferType)(type & MPP_BUFFER_TYPE_MASK); if (type == MPP_BUFFER_TYPE_NORMAL) return 0; mpp_assert(mode < MPP_BUFFER_MODE_BUTT); mpp_assert(type < MPP_BUFFER_TYPE_BUTT); mpp_assert(flag < MPP_ALLOC_FLAG_TYPE_NB); return srv->misc[mode][type][flag]; } MppBufferGroupImpl *mpp_buffer_get_misc_group(MppBufferMode mode, MppBufferType type) { MppBufferService *srv = get_srv_buffer(); MppBufferGroupImpl *misc; MppBufferType buf_type; MppMutex *lock = &srv->lock; rk_u32 id; buf_type = (MppBufferType)(type & MPP_BUFFER_TYPE_MASK); if (buf_type == MPP_BUFFER_TYPE_NORMAL) return NULL; mpp_assert(mode < MPP_BUFFER_MODE_BUTT); mpp_assert(buf_type < MPP_BUFFER_TYPE_BUTT); mpp_mutex_lock(lock); id = service_get_misc(srv, mode, type); if (!id) { char tag[32]; rk_s32 offset = 0; offset += snprintf(tag + offset, sizeof(tag) - offset, "misc"); offset += snprintf(tag + offset, sizeof(tag) - offset, "_%s", buf_type == MPP_BUFFER_TYPE_ION ? "ion" : buf_type == MPP_BUFFER_TYPE_DRM ? "drm" : "na"); offset += snprintf(tag + offset, sizeof(tag) - offset, "_%s", mode == MPP_BUFFER_INTERNAL ? "int" : "ext"); misc = service_get_group(tag, __FUNCTION__, mode, type, 1); } else misc = get_group_by_id(srv, id); mpp_mutex_unlock(lock); return misc; } static void mpp_buffer_service_init() { MppBufferService *srv = srv_buffer; rk_s32 i, j, k; if (srv) return; mpp_env_get_u32("mpp_buffer_debug", &mpp_buffer_debug, 0); srv = mpp_calloc(MppBufferService, 1); if (!srv) { mpp_err_f("alloc buffer service failed\n"); return; } srv_buffer = srv; srv_status = MPP_BUF_SRV_NORMAL; pool_buf = mpp_mem_pool_init_f(MODULE_TAG, sizeof(MppBufferImpl)); pool_buf_grp = mpp_mem_pool_init_f("mpp_buf_grp", sizeof(MppBufferGroupImpl)); pool_buf_map_node = mpp_mem_pool_init_f("mpp_buf_map_node", sizeof(MppDevBufMapNode)); srv->group_id = 1; INIT_LIST_HEAD(&srv->list_group); INIT_LIST_HEAD(&srv->list_orphan); // NOTE: Do not create misc group at beginning. Only create on when needed. for (i = 0; i < MPP_BUFFER_MODE_BUTT; i++) for (j = 0; j < MPP_BUFFER_TYPE_BUTT; j++) for (k = 0; k < MPP_ALLOCATOR_WITH_FLAG_NUM; k++) srv->misc[i][j][k] = 0; for (i = 0; i < (rk_s32)HASH_SIZE(srv->hash_group); i++) INIT_HLIST_HEAD(&srv->hash_group[i]); mpp_mutex_init(&srv->lock); } static void mpp_buffer_service_deinit() { MppBufferService *srv = srv_buffer; rk_s32 i, j, k; if (!srv) return; srv->finalizing = 1; // first remove legacy group which is the normal case if (srv->misc_count) { mpp_log_f("cleaning misc group\n"); for (i = 0; i < MPP_BUFFER_MODE_BUTT; i++) for (j = 0; j < MPP_BUFFER_TYPE_BUTT; j++) for (k = 0; k < MPP_ALLOCATOR_WITH_FLAG_NUM; k++) { rk_u32 id = srv->misc[i][j][k]; if (id) { service_put_group(srv, get_group_by_id(srv, id), __FUNCTION__); srv->misc[i][j][k] = 0; } } } // then remove the remaining group which is the leak one if (!list_empty(&srv->list_group)) { MppBufferGroupImpl *pos, *n; if (mpp_buffer_debug & MPP_BUF_DBG_DUMP_ON_EXIT) service_dump(srv, "leaked group found"); mpp_log_f("cleaning leaked group\n"); list_for_each_entry_safe(pos, n, &srv->list_group, MppBufferGroupImpl, list_group) { service_put_group(srv, pos, __FUNCTION__); } } // remove all orphan buffer group if (!list_empty(&srv->list_orphan)) { MppBufferGroupImpl *pos, *n; mpp_log_f("cleaning leaked buffer\n"); list_for_each_entry_safe(pos, n, &srv->list_orphan, MppBufferGroupImpl, list_group) { pos->clear_on_exit = 1; pos->is_finalizing = 1; service_put_group(srv, pos, __FUNCTION__); } } for (i = 0; i < MPP_BUFFER_TYPE_BUTT; i++) { for (j = 0; j < MPP_ALLOCATOR_WITH_FLAG_NUM; j++) { if (srv->allocator[i][j]) mpp_allocator_put(&(srv->allocator[i][j])); } } mpp_mutex_destroy(&srv->lock); MPP_FREE(srv_buffer); srv_status = MPP_BUF_SRV_FINALIZED; } rk_u32 service_get_group_id(MppBufferService *srv) { static rk_u32 overflowed = 0; rk_u32 id = 0; if (!overflowed) { /* avoid 0 group id */ if (srv->group_id) id = srv->group_id++; else { overflowed = 1; srv->group_id = 1; } } if (overflowed) { id = srv->group_id++; /* when it is overflow avoid the used id */ while (get_group_by_id(srv, id)) id = srv->group_id++; } srv->group_count++; return id; } static MppBufferGroupImpl *service_get_group(const char *tag, const char *caller, MppBufferMode mode, MppBufferType type, rk_u32 is_misc) { MppBufferType buffer_type = (MppBufferType)(type & MPP_BUFFER_TYPE_MASK); MppBufferGroupImpl *p = NULL; MppBufferService *srv = get_srv_buffer(); MppMutex *lock; rk_u32 flag; rk_u32 id; /* env update */ mpp_env_get_u32("mpp_buffer_debug", &mpp_buffer_debug, mpp_buffer_debug); if (mode >= MPP_BUFFER_MODE_BUTT || buffer_type >= MPP_BUFFER_TYPE_BUTT) { mpp_err("MppBufferService get_group found invalid mode %d type %x\n", mode, type); return NULL; } if (!srv) { mpp_err("MppBufferService get_group failed to get service\n"); return NULL; } lock = &srv->lock; p = (MppBufferGroupImpl *)mpp_mem_pool_get(pool_buf_grp, caller); if (!p) { mpp_err("MppBufferService failed to allocate group context\n"); return NULL; } flag = type_to_flag(type); p->flags = (MppAllocFlagType)flag; { MppAllocator allocator = NULL; MppAllocatorApi *alloc_api = NULL; mpp_mutex_lock(lock); allocator = srv->allocator[buffer_type][flag]; alloc_api = srv->allocator_api[buffer_type]; // allocate general buffer first if (!allocator) { mpp_allocator_get(&allocator, &alloc_api, type, p->flags); srv->allocator[buffer_type][flag] = allocator; srv->allocator_api[buffer_type] = alloc_api; } p->allocator = allocator; p->alloc_api = alloc_api; p->flags = mpp_allocator_get_flags(allocator); mpp_mutex_unlock(lock); } if (!p->allocator || !p->alloc_api) { mpp_mem_pool_put(pool_buf_grp, p, caller); mpp_err("MppBufferService get_group failed to get allocater with mode %d type %x\n", mode, type); return NULL; } INIT_LIST_HEAD(&p->list_group); INIT_LIST_HEAD(&p->list_used); INIT_LIST_HEAD(&p->list_unused); INIT_HLIST_NODE(&p->hlist); p->log_runtime_en = (mpp_buffer_debug & MPP_BUF_DBG_OPS_RUNTIME) ? (1) : (0); p->log_history_en = (mpp_buffer_debug & MPP_BUF_DBG_OPS_HISTORY) ? (1) : (0); p->caller = caller; p->mode = mode; p->type = buffer_type; p->limit = BUFFER_GROUP_SIZE_DEFAULT; p->clear_on_exit = (mpp_buffer_debug & MPP_BUF_DBG_CLR_ON_EXIT) ? (1) : (0); p->dump_on_exit = (mpp_buffer_debug & MPP_BUF_DBG_DUMP_ON_EXIT) ? (1) : (0); { pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); pthread_mutex_init(&p->buf_lock, &attr); pthread_mutexattr_destroy(&attr); } if (p->log_history_en) p->logs = buf_logs_init(BUFFER_OPS_MAX_COUNT); mpp_mutex_lock(lock); id = service_get_group_id(srv); if (tag) { snprintf(p->tag, sizeof(p->tag) - 1, "%s_%d", tag, id); } else { snprintf(p->tag, sizeof(p->tag) - 1, "unknown"); } p->group_id = id; list_add_tail(&p->list_group, &srv->list_group); hash_add(srv->hash_group, &p->hlist, id); buf_grp_add_log(p, GRP_CREATE, caller); if (is_misc) { srv->misc[mode][buffer_type][flag] = id; p->is_misc = 1; srv->misc_count++; } mpp_mutex_unlock(lock); return p; } static void destroy_group(MppBufferService *srv, MppBufferGroupImpl *group) { mpp_assert(group->count_used == 0); mpp_assert(group->count_unused == 0); if (group->count_unused || group->count_used) { mpp_err("mpp_buffer_group %s deinit mismatch counter used %4d unused %4d found\n", group->caller, group->count_used, group->count_unused); group->count_unused = 0; group->count_used = 0; } buf_grp_add_log(group, GRP_DESTROY, __FUNCTION__); list_del_init(&group->list_group); hash_del(&group->hlist); pthread_mutex_destroy(&group->buf_lock); if (group->logs) { buf_logs_deinit(group->logs); group->logs = NULL; } if (srv) { MppBufferMode mode = group->mode; MppBufferType type = group->type; rk_u32 flag = type_to_flag(type); rk_u32 id = group->group_id; srv->group_count--; if (id == srv->misc[mode][type][flag]) { srv->misc[mode][type][flag] = 0; srv->misc_count--; } } if (pool_buf_grp) mpp_mem_pool_put_f(pool_buf_grp, group); } static void service_put_group(MppBufferService *srv, MppBufferGroupImpl *p, const char *caller) { MppMutex *lock; if (!srv) return; lock = &srv->lock; if (!srv->finalizing) mpp_mutex_lock(lock); buf_grp_add_log(p, GRP_RELEASE, caller); // remove unused list if (!list_empty(&p->list_unused)) { MppBufferImpl *pos, *n; list_for_each_entry_safe(pos, n, &p->list_unused, MppBufferImpl, list_status) { service_put_buffer(srv, p, pos, 0, caller); } } if (list_empty(&p->list_used)) { destroy_group(srv, p); } else { if (!srv->finalizing || (srv->finalizing && p->dump_on_exit)) { mpp_err("mpp_group %p tag %s caller %s mode %s type %s deinit with %d bytes not released\n", p, p->tag, p->caller, mode2str[p->mode], type2str[p->type], p->usage); mpp_buffer_group_dump(p, caller); } /* if clear on exit we need to release remaining buffer */ if (p->clear_on_exit) { MppBufferImpl *pos, *n; if (p->dump_on_exit) mpp_err("force release all remaining buffer\n"); list_for_each_entry_safe(pos, n, &p->list_used, MppBufferImpl, list_status) { if (p->dump_on_exit) mpp_err("clearing buffer %p\n", pos); pos->ref_count = 0; pos->discard = 1; service_put_buffer(srv, p, pos, 0, caller); } destroy_group(srv, p); } else { // otherwise move the group to list_orphan and wait for buffer release buf_grp_add_log(p, GRP_ORPHAN, caller); list_del_init(&p->list_group); list_add_tail(&p->list_group, &srv->list_orphan); p->is_orphan = 1; } } if (!srv->finalizing) mpp_mutex_unlock(lock); } static void service_dump(MppBufferService *srv, const char *info) { MppBufferGroupImpl *group; struct hlist_node *n; rk_u32 key; mpp_mutex_lock(&srv->lock); mpp_log("dumping all buffer groups for %s\n", info); if (hash_empty(srv->hash_group)) { mpp_log("no buffer group can be dumped\n"); } else { hash_for_each_safe(srv->hash_group, key, n, group, hlist) { mpp_buffer_group_dump(group, __FUNCTION__); } } mpp_mutex_unlock(&srv->lock); } MPP_SINGLETON(MPP_SGLN_BUFFER, mpp_buffer, mpp_buffer_service_init, mpp_buffer_service_deinit)