diff --git a/qdf/inc/qdf_ptr_hash.h b/qdf/inc/qdf_ptr_hash.h new file mode 100644 index 000000000000..c96eca1d7f2d --- /dev/null +++ b/qdf/inc/qdf_ptr_hash.h @@ -0,0 +1,311 @@ +/* + * 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_ptr_hash.h + * + * A minimal hashtable implementation for doing fast lookups via pointer. + * + * qdf_ptr_hash also has the benefit of knowing its own size, allowing a pointer + * to the hashtable to be passed around and embedded in other structs. Since + * every hashtable is not necessarily of the same size, this allows using hash + * tables in a lot of new places which would be impossible with the current + * kernel hashtable implementation. + * + * Because the size of the hashtable varies with the number of bits used in the + * hash, declaring a qdf_ptr_hash is a bit different. If you want to embed a + * qdf_ptr_hash in another type, use a combination of qdf_ptr_hash_declare() and + * qdf_ptr_hash_ptr(). If you just want to declare and use a qdf_ptr_hash, use + * qdf_ptr_hash_declare_ptr() instead. Either method will ensure the appropriate + * number of bytes is accounted for using an internal union, and provides the + * consumer with a pointer to a qdf_ptr_hash type which can be used with all of + * the other qdf_ptr_hash APIs. Alternatively, you can skip these complexities + * by simply dynamically allocating the qdf_ptr_hash via qdf_ptr_hash_create(). + */ + +#ifndef __QDF_PTR_HASH_H +#define __QDF_PTR_HASH_H + +#include "i_qdf_ptr_hash.h" +#include "qdf_mem.h" +#include "qdf_slist.h" +#include "qdf_types.h" +#include "qdf_util.h" + +/** + * struct qdf_ptr_hash_bucket - a type representing a hash bucket + * @list: the list used for hash chaining + */ +struct qdf_ptr_hash_bucket { + struct qdf_slist list; +}; + +/** + * struct qdf_ptr_hash - a hash table type for doing fast lookups via pointer + * @bits: the number of bits to use when hashing keys + * @count: the number of buckets, always equal to 2^@bits + * @buckets: empty bucket array for accessing a variable length array of buckets + */ +struct qdf_ptr_hash { + int8_t bits; + int16_t count; + struct qdf_ptr_hash_bucket buckets[0]; +}; + +/** + * struct qdf_ptr_hash_entry - entry type of membership in a qdf_ptr_hash + * @key: the value used as the key for insertion/lookup + * @node: the list node used for hash chaining + */ +struct qdf_ptr_hash_entry { + uintptr_t key; + struct qdf_slist_node node; +}; + +#define __qdf_ptr_hash_size(bits) (sizeof(struct qdf_ptr_hash) + \ + sizeof(((struct qdf_ptr_hash *)0)->buckets[0]) * (1 << bits)) + +/** + * qdf_ptr_hash_declare() - declare a new qdf_ptr_hash + * @name: the C identifier to use for the new hash table + * @bits: The number of bits to use for hashing + * + * Return: None + */ +#define qdf_ptr_hash_declare(name, _bits) \ +union { \ + struct qdf_ptr_hash ht; \ + uint8_t __raw[__qdf_ptr_hash_size(_bits)]; \ +} __##name = { .ht = { .bits = _bits, .count = (1 << _bits) } } + +/** + * qdf_ptr_hash_ptr() - get a pointer to a declared qdf_ptr_hash + * @name: the C identifier of the declared qdf_ptr_hash + * + * Return: pointer to a qdf_ptr_hash + */ +#define qdf_ptr_hash_ptr(name) &__##name.ht + +/** + * qdf_ptr_hash_declare_ptr() - declare a pointer to a new qdf_ptr_hash + * @name: the C identifier to use for the pointer to the new qdf_ptr_hash + * @bits: The number of bits to use for hashing + * + * Return: None + */ +#define qdf_ptr_hash_declare_ptr(name, bits) \ +qdf_ptr_hash_declare(name, bits); \ +struct qdf_ptr_hash *name = qdf_ptr_hash_ptr(name) + +#define __qdf_ptr_hash_for_each_bucket(ht, bkt) \ + for ((bkt) = (ht)->buckets; \ + (bkt) < (ht)->buckets + (ht)->count; \ + (bkt)++) + +/** + * qdf_ptr_hash_init() - initialize a qdf_ptr_hash + * @ht: the hash table to initialize + * + * Return: None + */ +static inline void qdf_ptr_hash_init(struct qdf_ptr_hash *ht) +{ + struct qdf_ptr_hash_bucket *bucket; + + __qdf_ptr_hash_for_each_bucket(ht, bucket) + qdf_slist_init(&bucket->list); +} + +/** + * qdf_ptr_hash_deinit() - de-initialize a qdf_ptr_hash + * @ht: the hash table to de-initialize + * + * Return: None + */ +static inline void qdf_ptr_hash_deinit(struct qdf_ptr_hash *ht) +{ + struct qdf_ptr_hash_bucket *bucket; + + __qdf_ptr_hash_for_each_bucket(ht, bucket) + qdf_slist_deinit(&bucket->list); +} + +/** + * qdf_ptr_hash_create() - allocate and initialize a qdf_ptr_hash + * @bits: the number of bits to use for hashing + * + * Return: qdf_ptr_hash pointer on succes, NULL on allocation failure + */ +static inline struct qdf_ptr_hash *qdf_ptr_hash_create(uint8_t bits) +{ + struct qdf_ptr_hash *ht = qdf_mem_malloc(__qdf_ptr_hash_size(bits)); + + if (!ht) + return NULL; + + ht->bits = bits; + ht->count = 1 << bits; + qdf_ptr_hash_init(ht); + + return ht; +} + +/** + * qdf_ptr_hash_destroy() - de-initialize and de-allocate a qdf_ptr_hash + * @ht: the qdf_ptr_hash to destroy + * + * Return: None + */ +static inline void qdf_ptr_hash_destroy(struct qdf_ptr_hash *ht) +{ + qdf_ptr_hash_deinit(ht); + qdf_mem_free(ht); +} + +/** + * qdf_ptr_hash_empty() - check if a qdf_ptr_hash has any entries + * @ht: the qdf_ptr_hash to check + * + * Return: true if @ht contains no entries + */ +static inline bool qdf_ptr_hash_empty(struct qdf_ptr_hash *ht) +{ + struct qdf_ptr_hash_bucket *bucket; + + __qdf_ptr_hash_for_each_bucket(ht, bucket) + if (!qdf_slist_empty(&bucket->list)) + return false; + + return true; +} + +static inline struct qdf_ptr_hash_bucket * +__qdf_ptr_hash_get_bucket(struct qdf_ptr_hash *ht, uintptr_t key) +{ + return ht->buckets + __qdf_ptr_hash_key(key, ht->bits); +} + +/** + * qdf_ptr_hash_add() - insert an entry into a qdf_ptr_hash + * @ht: the qdf_ptr_hash to insert into + * @key: the pointer to use as an insertion/lookup key + * @item: a pointer to a type that contains a qdf_ptr_hash_entry + * @entry_field: C identifier for the qdf_ptr_hash_entry field in @item + * + * Return: None + */ +#define qdf_ptr_hash_add(ht, key, item, entry_field) \ + __qdf_ptr_hash_add(ht, (uintptr_t)key, &(item)->entry_field) + +static inline void __qdf_ptr_hash_add(struct qdf_ptr_hash *ht, uintptr_t key, + struct qdf_ptr_hash_entry *entry) +{ + entry->key = key; + qdf_slist_push(&__qdf_ptr_hash_get_bucket(ht, key)->list, entry, node); +} + +/** + * qdf_ptr_hash_remove() - remove an entry from a qdf_ptr_hash + * @ht: the qdf_ptr_hash to remove from + * @key: the pointer to use as a lookup key + * @cursor: a pointer to a type that contains a qdf_ptr_hash_entry + * @entry_field: C identifier for the qdf_ptr_hash_entry field in @cursor + * + * Return: removed item of type @cursor on success, NULL otherwise + */ +#define qdf_ptr_hash_remove(ht, key, cursor, entry_field) ({ \ + struct qdf_ptr_hash_entry *_e = \ + __qdf_ptr_hash_remove(ht, (uintptr_t)key); \ + cursor = _e ? qdf_container_of(_e, typeof(*(cursor)), \ + entry_field) : NULL; \ + cursor; }) + +static inline struct qdf_ptr_hash_entry * +__qdf_ptr_hash_remove(struct qdf_ptr_hash *ht, uintptr_t key) +{ + struct qdf_ptr_hash_bucket *bucket = __qdf_ptr_hash_get_bucket(ht, key); + struct qdf_ptr_hash_entry *prev; + struct qdf_ptr_hash_entry *entry; + + qdf_slist_for_each_del(&bucket->list, prev, entry, node) { + if (entry->key == key) { + qdf_slist_remove(&bucket->list, prev, node); + entry->key = 0; + return entry; + } + } + + return NULL; +} + +#define __qdf_ptr_hash_for_each_in_bucket(bucket, cursor, entry_field) \ + qdf_slist_for_each(&(bucket)->list, cursor, entry_field.node) + +/** + * qdf_ptr_hash_for_each() - qdf_ptr_hash item iterator for all items + * @ht: the qdf_ptr_hash to iterate over + * @bucket: qdf_ptr_hash_bucket cursor pointer + * @cursor: a pointer to a type that contains a qdf_ptr_hash_entry + * @entry_field: C identifier for the qdf_ptr_hash_entry field in @cursor + */ +#define qdf_ptr_hash_for_each(ht, bucket, cursor, entry_field) \ + __qdf_ptr_hash_for_each_bucket(ht, bucket) \ + __qdf_ptr_hash_for_each_in_bucket(bucket, cursor, entry_field) + +/** + * qdf_ptr_hash_for_each_by_hash() - qdf_ptr_hash item iterator for items which + * hash to the same value as @key + * @ht: the qdf_ptr_hash to iterate over + * @key: the pointer to use as a lookup key + * @cursor: a pointer to a type that contains a qdf_ptr_hash_entry + * @entry_field: C identifier for the qdf_ptr_hash_entry field in @cursor + */ +#define qdf_ptr_hash_for_each_by_hash(ht, key, cursor, entry_field) \ + __qdf_ptr_hash_for_each_in_bucket( \ + __qdf_ptr_hash_get_bucket(ht, (uintptr_t)key), \ + cursor, entry_field) + +/** + * qdf_ptr_hash_for_each_by_key() - qdf_ptr_hash item iterator for items whose + * keys equal @key + * @ht: the qdf_ptr_hash to iterate over + * @key: the pointer to use as a lookup key + * @cursor: a pointer to a type that contains a qdf_ptr_hash_entry + * @entry_field: C identifier for the qdf_ptr_hash_entry field in @cursor + */ +#define qdf_ptr_hash_for_each_by_key(ht, _key, cursor, entry_field) \ + qdf_ptr_hash_for_each_by_hash(ht, _key, cursor, entry_field) \ + if ((cursor)->entry_field.key == (uintptr_t)_key) + +/** + * qdf_ptr_hash_get() - get the first item whose key matches @key + * @ht: the qdf_ptr_hash to look in + * @key: the pointer to use as a lookup key + * @cursor: a pointer to a type that contains a qdf_ptr_hash_entry + * @entry_field: C identifier for the qdf_ptr_hash_entry field in @cursor + * + * Return: first item matching @key of type @cursor on success, NULL otherwise + */ +#define qdf_ptr_hash_get(ht, key, cursor, entry_field) ({ \ + cursor = NULL; \ + qdf_ptr_hash_for_each_by_key(ht, key, cursor, entry_field) \ + break; \ + cursor; }) + +#endif /* __QDF_PTR_HASH_H */ + diff --git a/qdf/linux/src/i_qdf_ptr_hash.h b/qdf/linux/src/i_qdf_ptr_hash.h new file mode 100644 index 000000000000..86ae269e2373 --- /dev/null +++ b/qdf/linux/src/i_qdf_ptr_hash.h @@ -0,0 +1,26 @@ +/* + * 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 __I_QDF_PTR_HASH_H +#define __I_QDF_PTR_HASH_H + +#include "linux/hash.h" + +#define __qdf_ptr_hash_key(key, bits) hash_long(key, bits) + +#endif /* __I_QDF_PTR_HASH_H */ diff --git a/qdf/test/qdf_ptr_hash_test.c b/qdf/test/qdf_ptr_hash_test.c new file mode 100644 index 000000000000..26c973e0cb8e --- /dev/null +++ b/qdf/test/qdf_ptr_hash_test.c @@ -0,0 +1,196 @@ +/* + * 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_ptr_hash.h" +#include "qdf_ptr_hash_test.h" +#include "qdf_trace.h" + +#define qdf_ptr_hash_bits 4 /* 16 buckets */ +#define qdf_ptr_hash_entry_count 10 + +struct qdf_ptr_hash_test_item { + uint32_t id; + struct qdf_ptr_hash_entry entry; +}; + +static uint32_t __qdf_ptr_hash_test_empty(struct qdf_ptr_hash *ht) +{ + struct qdf_ptr_hash_test_item *item; + + /* a new ptr_hash should ... */ + + /* ... be empty */ + QDF_BUG(qdf_ptr_hash_empty(ht)); + + /* ... return NULL with get()'d */ + QDF_BUG(!qdf_ptr_hash_get(ht, NULL, item, entry)); + + return 0; +} + +static uint32_t qdf_ptr_hash_test_empty(void) +{ + qdf_ptr_hash_declare_ptr(ht, qdf_ptr_hash_bits); + int errors; + + qdf_ptr_hash_init(ht); + errors = __qdf_ptr_hash_test_empty(ht); + qdf_ptr_hash_deinit(ht); + + return errors; +} + +static uint32_t __qdf_ptr_hash_test_add_remove(struct qdf_ptr_hash *ht) +{ + struct qdf_ptr_hash_test_item items[qdf_ptr_hash_entry_count]; + struct qdf_ptr_hash_test_item *item; + int i; + + /* a ptr_hash with items should ... */ + for (i = 0; i < qdf_ptr_hash_entry_count; i++) { + items[i].id = i; + qdf_ptr_hash_add(ht, &items[i], &items[i], entry); + } + + /* ... not be empty */ + QDF_BUG(!qdf_ptr_hash_empty(ht)); + + /* ... be able to get() all items previously add()'d */ + for (i = 0; i < qdf_ptr_hash_entry_count; i++) { + QDF_BUG(qdf_ptr_hash_get(ht, &items[i], item, entry)); + QDF_BUG(item->id == items[i].id); + } + + /* ... be able to remove() all items previously add()'d */ + for (i = 0; i < qdf_ptr_hash_entry_count; i++) { + QDF_BUG(qdf_ptr_hash_remove(ht, &items[i], item, entry)); + QDF_BUG(item->id == items[i].id); + } + + /* ... be empty after remove()'ing all items */ + QDF_BUG(qdf_ptr_hash_empty(ht)); + + return 0; +} + +static uint32_t qdf_ptr_hash_test_add_remove(void) +{ + qdf_ptr_hash_declare_ptr(ht, qdf_ptr_hash_bits); + int errors; + + qdf_ptr_hash_init(ht); + errors = __qdf_ptr_hash_test_add_remove(ht); + qdf_ptr_hash_deinit(ht); + + return errors; +} + +static uint32_t __qdf_ptr_hash_test_for_each(struct qdf_ptr_hash *ht) +{ + struct qdf_ptr_hash_bucket *bucket; + struct qdf_ptr_hash_test_item items[qdf_ptr_hash_entry_count]; + struct qdf_ptr_hash_test_item *item; + int i; + int count; + + /* a ptr_hash with items should ... */ + for (i = 0; i < qdf_ptr_hash_entry_count; i++) { + items[i].id = i; + qdf_ptr_hash_add(ht, i, &items[i], entry); + } + + /* ... be able to iterate over each item */ + count = 0; + qdf_ptr_hash_for_each(ht, bucket, item, entry) { + QDF_BUG(item->id == items[item->id].id); + count++; + } + QDF_BUG(count == qdf_ptr_hash_entry_count); + + /* ... be able to interate by hash value */ + count = 0; + for (i = 0; i < qdf_ptr_hash_entry_count; i++) { + qdf_ptr_hash_for_each_by_hash(ht, i, item, entry) { + QDF_BUG(item->id == items[item->id].id); + count++; + } + } + QDF_BUG(count >= qdf_ptr_hash_entry_count); + + /* ... be able to interate by key value */ + for (i = 0; i < qdf_ptr_hash_entry_count; i++) { + count = 0; + qdf_ptr_hash_for_each_by_key(ht, i, item, entry) { + QDF_BUG(item->id == items[i].id); + count++; + } + QDF_BUG(count == 1); + } + + /* ... be able to remove each item */ + for (i = 0; i < qdf_ptr_hash_entry_count; i++) { + qdf_ptr_hash_remove(ht, i, item, entry); + QDF_BUG(item); + QDF_BUG(item->id == items[i].id); + } + + /* ... be empty after all items are removed */ + QDF_BUG(qdf_ptr_hash_empty(ht)); + + return 0; +} + +static uint32_t qdf_ptr_hash_test_for_each(void) +{ + qdf_ptr_hash_declare_ptr(ht, qdf_ptr_hash_bits); + int errors; + + qdf_ptr_hash_init(ht); + errors = __qdf_ptr_hash_test_for_each(ht); + qdf_ptr_hash_deinit(ht); + + return errors; +} + +static uint32_t qdf_ptr_hash_test_create_destroy(void) +{ + struct qdf_ptr_hash *ht = qdf_ptr_hash_create(qdf_ptr_hash_bits); + uint32_t errors = 0; + + QDF_BUG(ht); + errors += __qdf_ptr_hash_test_empty(ht); + errors += __qdf_ptr_hash_test_add_remove(ht); + errors += __qdf_ptr_hash_test_for_each(ht); + + qdf_ptr_hash_destroy(ht); + + return errors; +} + +uint32_t qdf_ptr_hash_unit_test(void) +{ + uint32_t errors = 0; + + errors += qdf_ptr_hash_test_empty(); + errors += qdf_ptr_hash_test_add_remove(); + errors += qdf_ptr_hash_test_for_each(); + errors += qdf_ptr_hash_test_create_destroy(); + + return errors; +} + diff --git a/qdf/test/qdf_ptr_hash_test.h b/qdf/test/qdf_ptr_hash_test.h new file mode 100644 index 000000000000..713e37d8d4e0 --- /dev/null +++ b/qdf/test/qdf_ptr_hash_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_PTR_HASH_TEST +#define __QDF_PTR_HASH_TEST + +#ifdef WLAN_PTR_HASH_TEST +/** + * qdf_ptr_hash_unit_test() - run the qdf hashtable unit test suite + * + * Return: number of failed test cases + */ +uint32_t qdf_ptr_hash_unit_test(void); +#else +static inline uint32_t qdf_ptr_hash_unit_test(void) +{ + return 0; +} +#endif /* WLAN_PTR_HASH_TEST */ + +#endif /* __QDF_PTR_HASH_TEST */ +