qcacmn: Add qdf_slist

For memory savings it is sometimes desirable to use a single-linked list
instead of a double-linked list. However, the Linux kernel does not
provide a default implementation of a single-linked list.

Add a very simple single-linked list implementation to WLAN for general
use.

Change-Id: I2959f34649a0d8e0b312d9d67c81de17753b9b6c
CRs-Fixed: 2418493
This commit is contained in:
Dustin Brown
2019-02-15 12:58:25 -08:00
committed by nshrivas
parent f35d79a22b
commit b52a362e62
3 changed files with 362 additions and 0 deletions

199
qdf/inc/qdf_slist.h Normal file
View File

@@ -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 */

126
qdf/test/qdf_slist_test.c Normal file
View File

@@ -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;
}

37
qdf/test/qdf_slist_test.h Normal file
View File

@@ -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 */