/* * Copyright 2015 Rockchip Electronics Co. LTD * * 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. */ #define MODULE_TAG "mpp_list" #include #include #include #include #include "mpp_log.h" #include "mpp_list.h" #include "mpp_common.h" #define LIST_DEBUG(fmt, ...) mpp_log(fmt, ## __VA_ARGS__) #define LIST_ERROR(fmt, ...) mpp_err(fmt, ## __VA_ARGS__) RK_U32 mpp_list::keys = 0; typedef struct mpp_list_node { mpp_list_node* prev; mpp_list_node* next; RK_U32 key; RK_S32 size; } mpp_list_node; static inline void list_node_init(mpp_list_node *node) { node->prev = node->next = node; } static inline void list_node_init_with_key_and_size(mpp_list_node *node, RK_U32 key, RK_S32 size) { list_node_init(node); node->key = key; node->size = size; } static mpp_list_node* create_list(void *data, RK_S32 size, RK_U32 key) { mpp_list_node *node = (mpp_list_node*)malloc(sizeof(mpp_list_node) + size); if (node) { void *dst = (void*)(node + 1); list_node_init_with_key_and_size(node, key, size); memcpy(dst, data, size); } else { LIST_ERROR("failed to allocate list node"); } return node; } static inline void _mpp_list_add(mpp_list_node * _new, mpp_list_node * prev, mpp_list_node * next) { next->prev = _new; _new->next = next; _new->prev = prev; prev->next = _new; } static inline void mpp_list_add(mpp_list_node *_new, mpp_list_node *head) { _mpp_list_add(_new, head, head->next); } static inline void mpp_list_add_tail(mpp_list_node *_new, mpp_list_node *head) { _mpp_list_add(_new, head->prev, head); } RK_S32 mpp_list::add_at_head(void *data, RK_S32 size) { RK_S32 ret = -EINVAL; if (head) { mpp_list_node *node = create_list(data, size, 0); if (node) { mpp_list_add(node, head); count++; ret = 0; } else { ret = -ENOMEM; } } return ret; } RK_S32 mpp_list::add_at_tail(void *data, RK_S32 size) { RK_S32 ret = -EINVAL; if (head) { mpp_list_node *node = create_list(data, size, 0); if (node) { mpp_list_add_tail(node, head); count++; ret = 0; } else { ret = -ENOMEM; } } return ret; } static void release_list(mpp_list_node*node, void *data, RK_S32 size) { void *src = (void*)(node + 1); if (node->size == size) { if (data) memcpy(data, src, size); } else { LIST_ERROR("node size check failed when release_list"); size = (size < node->size) ? (size) : (node->size); if (data) memcpy(data, src, size); } free(node); } static inline void _mpp_list_del(mpp_list_node *prev, mpp_list_node *next) { next->prev = prev; prev->next = next; } static inline void mpp_list_del_init(mpp_list_node *node) { _mpp_list_del(node->prev, node->next); list_node_init(node); } static inline void _list_del_node_no_lock(mpp_list_node *node, void *data, RK_S32 size) { mpp_list_del_init(node); release_list(node, data, size); } RK_S32 mpp_list::del_at_head(void *data, RK_S32 size) { RK_S32 ret = -EINVAL; if (head && count) { _list_del_node_no_lock(head->next, data, size); count--; ret = 0; } return ret; } RK_S32 mpp_list::del_at_tail(void *data, RK_S32 size) { RK_S32 ret = -EINVAL; if (head && count) { _list_del_node_no_lock(head->prev, data, size); count--; ret = 0; } return ret; } static mpp_list_node* create_list_with_size(void *data, RK_S32 size, RK_U32 key) { mpp_list_node *node = (mpp_list_node*)malloc(sizeof(mpp_list_node) + sizeof(size) + size); if (node) { RK_S32 *dst = (RK_S32 *)(node + 1); list_node_init_with_key_and_size(node, key, size); *dst++ = size; memcpy(dst, data, size); } else { LIST_ERROR("failed to allocate list node"); } return node; } RK_S32 mpp_list::fifo_wr(void *data, RK_S32 size) { RK_S32 ret = -EINVAL; if (head) { mpp_list_node *node = create_list_with_size(data, size, 0); if (node) { mpp_list_add_tail(node, head); count++; ret = 0; } else { ret = -ENOMEM; } } return ret; } static void release_list_with_size(mpp_list_node* node, void *data, RK_S32 *size) { RK_S32 *src = (RK_S32*)(node + 1); RK_S32 data_size = *src++; *size = data_size; if (data) memcpy(data, src, data_size); free(node); } RK_S32 mpp_list::fifo_rd(void *data, RK_S32 *size) { RK_S32 ret = -EINVAL; if (head && count) { mpp_list_node *node = head->next; mpp_list_del_init(node); release_list_with_size(node, data, size); count--; ret = 0; } return ret; } RK_S32 mpp_list::list_is_empty() { RK_S32 ret = (count == 0); return ret; } RK_S32 mpp_list::list_size() { RK_S32 ret = count; return ret; } RK_S32 mpp_list::add_by_key(void *data, RK_S32 size, RK_U32 *key) { RK_S32 ret = 0; if (head) { RK_U32 list_key = get_key(); *key = list_key; mpp_list_node *node = create_list(data, size, list_key); if (node) { mpp_list_add_tail(node, head); count++; ret = 0; } else { ret = -ENOMEM; } } return ret; } RK_S32 mpp_list::del_by_key(void *data, RK_S32 size, RK_U32 key) { RK_S32 ret = 0; if (head && count) { struct mpp_list_node *tmp = head->next; ret = -EINVAL; while (tmp->next != head) { if (tmp->key == key) { _list_del_node_no_lock(tmp, data, size); count--; break; } } } return ret; } RK_S32 mpp_list::show_by_key(void *data, RK_U32 key) { RK_S32 ret = -EINVAL; (void)data; (void)key; return ret; } RK_S32 mpp_list::flush() { if (head) { while (count) { mpp_list_node* node = head->next; mpp_list_del_init(node); if (destroy) { destroy((void*)(node + 1)); } free(node); count--; } } signal(); return 0; } MPP_RET mpp_list::wait_lt(RK_S64 timeout, RK_S32 val) { if (list_size() < val) return MPP_OK; if (!timeout) return MPP_NOK; if (timeout < 0) wait(); else wait(timeout); return list_size() < val ? MPP_OK : MPP_NOK; } MPP_RET mpp_list::wait_le(RK_S64 timeout, RK_S32 val) { if (list_size() <= val) return MPP_OK; if (!timeout) return MPP_NOK; if (timeout < 0) wait(); else wait(timeout); return list_size() <= val ? MPP_OK : MPP_NOK; } MPP_RET mpp_list::wait_gt(RK_S64 timeout, RK_S32 val) { if (list_size() > val) return MPP_OK; if (!timeout) return MPP_NOK; if (timeout < 0) wait(); else wait(timeout); return list_size() > val ? MPP_OK : MPP_NOK; } MPP_RET mpp_list::wait_ge(RK_S64 timeout, RK_S32 val) { if (list_size() >= val) return MPP_OK; if (!timeout) return MPP_NOK; if (timeout < 0) wait(); else wait(timeout); return list_size() >= val ? MPP_OK : MPP_NOK; } RK_U32 mpp_list::get_key() { return keys++; } mpp_list::mpp_list(node_destructor func) : destroy(NULL), head(NULL), count(0) { destroy = func; head = (mpp_list_node*)malloc(sizeof(mpp_list_node)); if (NULL == head) { LIST_ERROR("failed to allocate list header"); } else { list_node_init_with_key_and_size(head, 0, 0); } } mpp_list::~mpp_list() { flush(); if (head) free(head); head = NULL; destroy = NULL; } /* list sort porting from kernel list_sort.c */ /* * Returns a list organized in an intermediate format suited * to chaining of merge() calls: null-terminated, no reserved or * sentinel head node, "prev" links not maintained. */ static struct list_head *merge(void *priv, list_cmp_func_t cmp, struct list_head *a, struct list_head *b) { struct list_head *head, **tail = &head; for (;;) { /* if equal, take 'a' -- important for sort stability */ if (cmp(priv, a, b) <= 0) { *tail = a; tail = &a->next; a = a->next; if (!a) { *tail = b; break; } } else { *tail = b; tail = &b->next; b = b->next; if (!b) { *tail = a; break; } } } return head; } /* * Combine final list merge with restoration of standard doubly-linked * list structure. This approach duplicates code from merge(), but * runs faster than the tidier alternatives of either a separate final * prev-link restoration pass, or maintaining the prev links * throughout. */ static void merge_final(void *priv, list_cmp_func_t cmp, struct list_head *head, struct list_head *a, struct list_head *b) { struct list_head *tail = head; RK_U8 count = 0; for (;;) { /* if equal, take 'a' -- important for sort stability */ if (cmp(priv, a, b) <= 0) { tail->next = a; a->prev = tail; tail = a; a = a->next; if (!a) break; } else { tail->next = b; b->prev = tail; tail = b; b = b->next; if (!b) { b = a; break; } } } /* Finish linking remainder of list b on to tail */ tail->next = b; do { /* * If the merge is highly unbalanced (e.g. the input is * already sorted), this loop may run many iterations. * Continue callbacks to the client even though no * element comparison is needed, so the client's cmp() * routine can invoke cond_resched() periodically. */ if (!++count) cmp(priv, b, b); b->prev = tail; tail = b; b = b->next; } while (b); /* And the final links to make a circular doubly-linked list */ tail->next = head; head->prev = tail; } void list_sort(void *priv, struct list_head *head, list_cmp_func_t cmp) { struct list_head *list = head->next, *pending = NULL; size_t count = 0; /* Count of pending */ if (list == head->prev) /* Zero or one elements */ return; /* Convert to a null-terminated singly-linked list. */ head->prev->next = NULL; /* * Data structure invariants: * - All lists are singly linked and null-terminated; prev * pointers are not maintained. * - pending is a prev-linked "list of lists" of sorted * sublists awaiting further merging. * - Each of the sorted sublists is power-of-two in size. * - Sublists are sorted by size and age, smallest & newest at front. * - There are zero to two sublists of each size. * - A pair of pending sublists are merged as soon as the number * of following pending elements equals their size (i.e. * each time count reaches an odd multiple of that size). * That ensures each later final merge will be at worst 2:1. * - Each round consists of: * - Merging the two sublists selected by the highest bit * which flips when count is incremented, and * - Adding an element from the input as a size-1 sublist. */ do { size_t bits; struct list_head **tail = &pending; /* Find the least-significant clear bit in count */ for (bits = count; bits & 1; bits >>= 1) tail = &(*tail)->prev; /* Do the indicated merge */ if (bits) { struct list_head *a = *tail, *b = a->prev; a = merge(priv, cmp, b, a); /* Install the merged result in place of the inputs */ a->prev = b->prev; *tail = a; } /* Move one element from input list to pending */ list->prev = pending; pending = list; list = list->next; pending->next = NULL; count++; } while (list); /* End of input; merge together all the pending lists. */ list = pending; pending = pending->prev; for (;;) { struct list_head *next = pending->prev; if (!next) break; list = merge(priv, cmp, pending, list); pending = next; } /* The final merge, rebuilding prev links */ merge_final(priv, cmp, head, pending, list); } #if BUILD_RK_LIST_TEST #include "vpu_mem.h" #include #include #include #define LOOP_RK_LIST 600 #define COUNT_ADD 100 #define COUNT_DEL 99 volatile int err = 0; static int mpp_list_fifo_test(mpp_list *list_0) { int count; VPUMemLinear_t m; for (count = 0; count < COUNT_ADD; count++) { err |= VPUMallocLinear(&m, 100); if (err) { printf("VPUMallocLinear in mpp_list_fifo_test\n"); break; } err |= list_0->add_at_head(&m, sizeof(m)); if (err) { printf("add_at_head in mpp_list_fifo_test\n"); break; } } if (!err) { for (count = 0; count < COUNT_DEL; count++) { err |= list_0->del_at_tail(&m, sizeof(m)); if (err) { printf("del_at_tail in mpp_list_fifo_test\n"); break; } err |= VPUFreeLinear(&m); if (err) { printf("VPUFreeLinear in mpp_list_fifo_test\n"); break; } } } return err; } static int mpp_list_filo_test(mpp_list *list_0) { int count; VPUMemLinear_t m; for (count = 0; count < COUNT_ADD + COUNT_DEL; count++) { if (count & 1) { err |= list_0->del_at_head(&m, sizeof(m)); if (err) { printf("del_at_head in mpp_list_filo_test\n"); break; } err |= VPUFreeLinear(&m); if (err) { printf("VPUFreeLinear in mpp_list_fifo_test\n"); break; } } else { err |= VPUMallocLinear(&m, 100); if (err) { printf("VPUMallocLinear in mpp_list_filo_test\n"); break; } err |= list_0->add_at_head(&m, sizeof(m)); if (err) { printf("add_at_head in mpp_list_fifo_test\n"); break; } } } return err; } void *mpp_list_test_loop_0(void *pdata) { int i; mpp_list *list_0 = (mpp_list *)pdata; printf("mpp_list test 0 loop start\n"); for (i = 0; i < LOOP_RK_LIST; i++) { err |= mpp_list_filo_test(list_0); if (err) break; } if (err) { printf("thread: found vpu mem operation err %d\n", err); } else { printf("thread: test done and found no err\n"); } return NULL; } int mpp_list_test_0() { int i, err = 0; printf("mpp_list test 0 FIFO start\n"); mpp_list *list_0 = new mpp_list((node_destructor)VPUFreeLinear); pthread_t mThread; pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); pthread_create(&mThread, &attr, mpp_list_test_loop_0, (void*)list_0); pthread_attr_destroy(&attr); for (i = 0; i < LOOP_RK_LIST; i++) { err |= mpp_list_fifo_test(list_0); if (err) break; } if (err) { printf("main : found mpp_list operation err %d\n", err); } else { printf("main : test done and found no err\n"); } void *dummy; pthread_join(mThread, &dummy); printf("mpp_list test 0 end size %d\n", list_0->list_size()); delete list_0; return err; } #define TOTAL_RK_LIST_TEST_COUNT 1 typedef int (*RK_LIST_TEST_FUNC)(void); RK_LIST_TEST_FUNC test_func[TOTAL_RK_LIST_TEST_COUNT] = { mpp_list_test_0, }; int main(int argc, char *argv[]) { int i, start = 0, end = 0; if (argc < 2) { end = TOTAL_RK_LIST_TEST_COUNT; } else if (argc == 2) { start = atoi(argv[1]); end = start + 1; } else if (argc == 3) { start = atoi(argv[1]); end = atoi(argv[2]); } else { printf("too many argc %d\n", argc); return -1; } if (start < 0 || start > TOTAL_RK_LIST_TEST_COUNT || end < 0 || end > TOTAL_RK_LIST_TEST_COUNT) { printf("invalid input: start %d end %d\n", start, end); return -1; } for (i = start; i < end; i++) { int err = test_func[i](); if (err) { printf("test case %d return err %d\n", i, err); break; } } return 0; } #endif