diff --git a/qdf/inc/qdf_slist.h b/qdf/inc/qdf_slist.h new file mode 100644 index 000000000000..b56f829f9b5c --- /dev/null +++ b/qdf/inc/qdf_slist.h @@ -0,0 +1,199 @@ +/* + * Copyright (c) 2019 The Linux Foundation. All rights reserved. + * + * Permission to use, copy, modify, and/or distribute this software for + * any purpose with or without fee is hereby granted, provided that the + * above copyright notice and this permission notice appear in all + * copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL + * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE + * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR + * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +/** + * DOC: qdf_slist.h + * + * A minimal, singly linked list implementation, with push front, pop front, and + * remove capabilities. These are all O(1) operations. + * + * In order to remove an item, a pointer to the previous item must be known. + * Thus, removing an item is most efficient when combined with + * qdf_slist_for_each_del(). For cases where you need efficient removal of an + * arbitrary list node without iteration, consider using the doubly linked list + * qdf_list instead. + */ + +#ifndef __QDF_SLIST_H +#define __QDF_SLIST_H + +#include "qdf_trace.h" +#include "qdf_util.h" + +#define __qdf_slist_poison ((void *)0xdeaddeaddeaddeadull) + +/** + * struct qdf_slist - a singly linked list + * @head: pointer to the head of the list + */ +struct qdf_slist { + struct qdf_slist_node *head; +}; + +/** + * struct qdf_slist_node - a singly linked list node + * @next: pointer to the next node in the list, NULL if there is none + */ +struct qdf_slist_node { + struct qdf_slist_node *next; +}; + +#define __qdf_slist_item(node, cursor, node_field) ({ \ + struct qdf_slist_node *__n = (node); \ + (__n ? qdf_container_of(__n, typeof(*(cursor)), node_field) : NULL); }) + +#define __qdf_slist_next_item(slist, cursor, node_field) \ + __qdf_slist_item(cursor ? (cursor)->node_field.next : \ + (slist)->head, cursor, node_field) + +/** + * qdf_slist_for_each - iterate over all of the items in @slist + * @slist: pointer to the qdf_slist to iterate over + * @cursor: cursor pointer of the list's item type, populated for each item + * @node_field: name of the qdf_slist_node field in the item's type + */ +#define qdf_slist_for_each(slist, cursor, node_field) \ + for (cursor = __qdf_slist_item((slist)->head, cursor, node_field); \ + cursor; \ + cursor = __qdf_slist_item((cursor)->node_field.next, \ + cursor, node_field)) + +/** + * qdf_slist_for_each_del - iterate over all of the items in @slist, + * allowing for the safe deletion of nodes during iteration + * @slist: pointer to the qdf_slist to iterate over + * @prev: cursor pointer, populated with the previous item + * @cursor: cursor pointer of the list's item type, populated for each item + * @node_field: name of the qdf_slist_node field in the item's type + */ +#define qdf_slist_for_each_del(slist, prev, cursor, node_field) \ + for (prev = NULL, \ + cursor = __qdf_slist_item((slist)->head, cursor, node_field); \ + cursor; \ + prev = __qdf_slist_next_item(slist, prev, node_field) == \ + cursor ? cursor : prev, \ + cursor = __qdf_slist_next_item(slist, prev, node_field)) + +/** + * qdf_slist_init() - initialize a qdf_slist + * @slist: the list to initialize + * + * Return: None + */ +static inline void qdf_slist_init(struct qdf_slist *slist) +{ + slist->head = NULL; +} + +/** + * qdf_slist_deinit() - deinitialize a qdf_slist + * @slist: the list to deinitialize + * + * Return: None + */ +static inline void qdf_slist_deinit(struct qdf_slist *slist) +{ + QDF_BUG(!slist->head); + slist->head = __qdf_slist_poison; +} + +/** + * qdf_slist_empty() - check if a qdf_slist is empty + * @slist: the list to check + * + * Return: true if @slist contains zero items + */ +static inline bool qdf_slist_empty(struct qdf_slist *slist) +{ + return !slist->head; +} + +/** + * qdf_slist_push() - push an item into the front of a qdf_slist + * @slist: the list to push into + * @cursor: the item to push + * @node_field: name of the qdf_slist_node field in the item's type + * + * Return: None + */ +#define qdf_slist_push(slist, cursor, node_field) \ + __qdf_slist_push(slist, &(cursor)->node_field) + +static inline void +__qdf_slist_push(struct qdf_slist *slist, struct qdf_slist_node *node) +{ + node->next = slist->head; + slist->head = node; +} + +/** + * qdf_slist_pop() - pop an item from the front of a qdf_slist + * @slist: the list to pop from + * @cursor: cursor pointer of the list's item type, not populated + * @node_field: name of the qdf_slist_node field in the item's type + * + * Return: pointer to the popped item, NULL if @slist was empty + */ +#define qdf_slist_pop(slist, cursor, node_field) \ + __qdf_slist_item(__qdf_slist_pop(slist), cursor, node_field) + +static inline struct qdf_slist_node *__qdf_slist_pop(struct qdf_slist *slist) +{ + struct qdf_slist_node *node = slist->head; + + if (!node) + return NULL; + + slist->head = node->next; + node->next = __qdf_slist_poison; + + return node; +} + +/** + * qdf_slist_remove() - remove an item from a qdf_slist + * @slist: the list to remove from + * @prev: pointer to the item previous to the item to remove, NULL removes head + * @node_field: name of the qdf_slist_node field in the item's type + * + * Return: pointer to the removed item, NULL if none was removed + */ +#define qdf_slist_remove(slist, prev, node_field) \ + __qdf_slist_item(__qdf_slist_remove(slist, \ + prev ? &(prev)->node_field : NULL), prev, node_field) + +static inline struct qdf_slist_node * +__qdf_slist_remove(struct qdf_slist *slist, struct qdf_slist_node *prev) +{ + struct qdf_slist_node *node; + + if (!prev) + return __qdf_slist_pop(slist); + + if (!prev->next) + return NULL; + + node = prev->next; + prev->next = node->next; + node->next = __qdf_slist_poison; + + return node; +} + +#endif /* __QDF_SLIST_H */ + diff --git a/qdf/test/qdf_slist_test.c b/qdf/test/qdf_slist_test.c new file mode 100644 index 000000000000..ca3e987446b4 --- /dev/null +++ b/qdf/test/qdf_slist_test.c @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2019 The Linux Foundation. All rights reserved. + * + * Permission to use, copy, modify, and/or distribute this software for + * any purpose with or without fee is hereby granted, provided that the + * above copyright notice and this permission notice appear in all + * copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL + * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE + * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR + * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +#include "qdf_slist.h" +#include "qdf_slist_test.h" +#include "qdf_trace.h" + +struct qdf_slist_test_item { + uint32_t id; + struct qdf_slist_node node; +}; + +#define qdf_slist_node_count 10 + +static uint32_t qdf_slist_test_empty(void) +{ + struct qdf_slist list; + struct qdf_slist_test_item *item; + + /* a new list should ... */ + qdf_slist_init(&list); + + /* ... be empty */ + QDF_BUG(qdf_slist_empty(&list)); + + /* ... return NULL when pop()'d */ + QDF_BUG(!qdf_slist_pop(&list, item, node)); + + qdf_slist_deinit(&list); + + return 0; +} + +static uint32_t qdf_slist_test_push_pop(void) +{ + struct qdf_slist list; + struct qdf_slist_test_item items[qdf_slist_node_count]; + struct qdf_slist_test_item *item; + int i; + + qdf_slist_init(&list); + + /* a list with items should ... */ + for (i = 0; i < qdf_slist_node_count; i++) + qdf_slist_push(&list, &items[i], node); + + /* ... not be empty */ + QDF_BUG(!qdf_slist_empty(&list)); + + /* ... be able to pop() all items previously push()'d */ + for (i = 0; i < qdf_slist_node_count; i++) + QDF_BUG(qdf_slist_pop(&list, item, node)); + + /* ... be empty after pop()'ing all items */ + QDF_BUG(qdf_slist_empty(&list)); + + qdf_slist_deinit(&list); + + return 0; +} + +static uint32_t qdf_slist_test_for_each(void) +{ + struct qdf_slist list; + struct qdf_slist_test_item items[qdf_slist_node_count]; + struct qdf_slist_test_item *prev; + struct qdf_slist_test_item *item; + int i; + + qdf_slist_init(&list); + + /* a list with items should ... */ + for (i = 0; i < qdf_slist_node_count; i++) + qdf_slist_push(&list, &items[i], node); + + /* ... be able to iterate over each item */ + i = 0; + qdf_slist_for_each(&list, item, node) { + item->id = i++; + } + QDF_BUG(i == qdf_slist_node_count); + + /* ... be able to remove each item in the same order */ + i = 0; + qdf_slist_for_each_del(&list, prev, item, node) { + QDF_BUG(item); + QDF_BUG(item->id == i++); + QDF_BUG(qdf_slist_remove(&list, prev, node)->id == item->id); + } + QDF_BUG(i == qdf_slist_node_count); + + /* ... be empty after all items are removed */ + QDF_BUG(!qdf_slist_pop(&list, item, node)); + QDF_BUG(qdf_slist_empty(&list)); + + qdf_slist_deinit(&list); + + return 0; +} + +uint32_t qdf_slist_unit_test(void) +{ + uint32_t errors = 0; + + errors += qdf_slist_test_empty(); + errors += qdf_slist_test_push_pop(); + errors += qdf_slist_test_for_each(); + + return errors; +} + diff --git a/qdf/test/qdf_slist_test.h b/qdf/test/qdf_slist_test.h new file mode 100644 index 000000000000..7e85a15c61f9 --- /dev/null +++ b/qdf/test/qdf_slist_test.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2019 The Linux Foundation. All rights reserved. + * + * Permission to use, copy, modify, and/or distribute this software for + * any purpose with or without fee is hereby granted, provided that the + * above copyright notice and this permission notice appear in all + * copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL + * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE + * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR + * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef __QDF_SLIST_TEST_H +#define __QDF_SLIST_TEST_H + +#ifdef WLAN_SLIST_TEST +/** + * qdf_slist_unit_test() - run the qdf slist unit test suite + * + * Return: number of failed test cases + */ +uint32_t qdf_slist_unit_test(void); +#else +static inline uint32_t qdf_slist_unit_test(void) +{ + return 0; +} +#endif /* WLAN_SLIST_TEST */ + +#endif /* __QDF_SLIST_TEST_H */ +