| /* SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB */ | |
| /* | |
| * Copyright (c) 2018-2025 Advanced Micro Devices, Inc. All rights reserved. | |
| */ | |
| #ifndef IONIC_TABLE_H | |
| #define IONIC_TABLE_H | |
| #include <stdbool.h> | |
| #include <stdint.h> | |
| #include <stdlib.h> | |
| #include <stdio.h> | |
| /* Number of valid bits in a key */ | |
| #define TBL_KEY_SHIFT 24 | |
| /* Number bits used for index in a node */ | |
| #define TBL_NODE_SHIFT 12 | |
| #define TBL_NODE_MASK (BIT(TBL_NODE_SHIFT) - 1u) | |
| #define TBL_NODE_CAPACITY BIT(TBL_NODE_SHIFT) | |
| #define TBL_ROOT_CAPACITY BIT(TBL_KEY_SHIFT - TBL_NODE_SHIFT) | |
| struct ionic_tbl_node { | |
| void *val[TBL_NODE_CAPACITY]; | |
| }; | |
| struct ionic_tbl_root { | |
| /* for lookup in table */ | |
| struct ionic_tbl_node *node[TBL_ROOT_CAPACITY]; | |
| /* for insertion and deletion in table */ | |
| int refcount[TBL_ROOT_CAPACITY]; | |
| struct ionic_tbl_node *free_node; | |
| }; | |
| /** | |
| * ionic_tbl_init() - Initialize a table | |
| * @tbl: Table root | |
| */ | |
| static inline void ionic_tbl_init(struct ionic_tbl_root *tbl) | |
| { | |
| uint32_t node_i; | |
| tbl->free_node = NULL; | |
| for (node_i = 0; node_i < TBL_ROOT_CAPACITY; ++node_i) { | |
| tbl->node[node_i] = NULL; | |
| tbl->refcount[node_i] = 0; | |
| } | |
| } | |
| /** | |
| * ionic_tbl_init() - Destroy the table, which should be empty | |
| * @tbl: Table root | |
| */ | |
| static inline void ionic_tbl_destroy(struct ionic_tbl_root *tbl) | |
| { | |
| uint32_t node_i; | |
| /* The table should be empty. If not empty, it means the context is | |
| * being destroyed, but there are qps still in the table that have not | |
| * been destroyed. | |
| * | |
| * The interface is such that freeing the context must succeed, so here | |
| * will make a best effort to free table resources. Any qps that were | |
| * not destroyed, however will still refer to the context after it is | |
| * freed. Those qps must not be used, not even for ibv_destroy_qp, or | |
| * the application will likely crash. | |
| * | |
| * This best-effort freeing of resources replaces an assert. The | |
| * assert was seen in perftest, which will destroy the context even if | |
| * there is an error destroying a qp or other resource. | |
| */ | |
| for (node_i = 0; node_i < TBL_ROOT_CAPACITY; ++node_i) { | |
| if (!tbl->node[node_i]) | |
| continue; | |
| free(tbl->node[node_i]); | |
| } | |
| free(tbl->free_node); | |
| } | |
| /** | |
| * ionic_tbl_lookup() - Lookup value for key in the table | |
| * @tbl: Table root | |
| * @key: Key for lookup | |
| * | |
| * Synopsis: | |
| * | |
| * pthread_spin_lock(&my_table_lock); | |
| * val = ionic_tbl_lookup(&my_table, key); | |
| * if (val) | |
| * my_val_routine(val); | |
| * pthread_spin_unlock(&my_table_lock); | |
| * | |
| * Return: Value for key | |
| */ | |
| static inline void *ionic_tbl_lookup(struct ionic_tbl_root *tbl, uint32_t key) | |
| { | |
| uint32_t node_i = key >> TBL_NODE_SHIFT; | |
| if (unlikely(key >> TBL_KEY_SHIFT)) | |
| return NULL; | |
| if (unlikely(!tbl->node[node_i])) | |
| return NULL; | |
| return tbl->node[node_i]->val[key & TBL_NODE_MASK]; | |
| } | |
| /** | |
| * ionic_tbl_alloc_node() - Allocate the free node prior to insertion | |
| * @tbl: Table root | |
| * | |
| * This should be called before inserting. | |
| * | |
| * Synopsis: see ionic_tbl_insert() | |
| */ | |
| static inline void ionic_tbl_alloc_node(struct ionic_tbl_root *tbl) | |
| { | |
| if (!tbl->free_node) | |
| tbl->free_node = calloc(1, sizeof(*tbl->free_node)); | |
| } | |
| /** | |
| * ionic_tbl_free_node() - Free the free node prior to deletion | |
| * @tbl: Table root | |
| * | |
| * This should be called before deleting. | |
| * | |
| * Synopsis: see ionic_tbl_delete() | |
| */ | |
| static inline void ionic_tbl_free_node(struct ionic_tbl_root *tbl) | |
| { | |
| free(tbl->free_node); | |
| tbl->free_node = NULL; | |
| } | |
| /** | |
| * ionic_tbl_insert() - Insert a value for key in the table | |
| * @tbl: Table root | |
| * @val: Value to insert | |
| * @key: Key to insert | |
| * | |
| * The tbl->free_node must not be null when inserting. | |
| * | |
| * Synopsis: | |
| * | |
| * pthread_mutex_lock(&my_table_mut); | |
| * ionic_tbl_alloc_node(&my_table); | |
| * ionic_tbl_insert(&my_table, val, key); | |
| * pthread_mutex_unlock(&my_table_mut); | |
| * | |
| * pthread_spin_lock(&my_table_lock); | |
| * pthread_spin_unlock(&my_table_lock); | |
| */ | |
| static inline void ionic_tbl_insert(struct ionic_tbl_root *tbl, | |
| void *val, uint32_t key) | |
| { | |
| struct ionic_tbl_node *node; | |
| uint32_t node_i = key >> TBL_NODE_SHIFT; | |
| if (unlikely(key >> TBL_KEY_SHIFT)) { | |
| assert(key >> TBL_KEY_SHIFT == 0); | |
| return; | |
| } | |
| node = tbl->node[node_i]; | |
| if (!node) | |
| node = tbl->free_node; | |
| if (unlikely(!node)) { | |
| assert(node != NULL); | |
| return; | |
| } | |
| /* warning: with NDEBUG the old value will leak */ | |
| assert(node->val[key & TBL_NODE_MASK] == NULL); | |
| node->val[key & TBL_NODE_MASK] = val; | |
| if (!tbl->refcount[node_i]) { | |
| tbl->node[node_i] = node; | |
| tbl->free_node = NULL; | |
| } | |
| ++tbl->refcount[node_i]; | |
| } | |
| /** | |
| * ionic_tbl_delete() - Delete the value for key in the table | |
| * @tbl: Table root | |
| * @val: Value to insert | |
| * @key: Key to insert | |
| * | |
| * The tbl->free_node must be null when deleting. | |
| * | |
| * Synopsis: | |
| * | |
| * pthread_mutex_lock(&my_table_mut); | |
| * ionic_tbl_free_node(&my_table); | |
| * ionic_tbl_delete(&my_table, key); | |
| * pthread_mutex_unlock(&my_table_mut); | |
| * | |
| * pthread_spin_lock(&my_table_lock); | |
| * pthread_spin_unlock(&my_table_lock); | |
| * free(old_val_at_key); | |
| */ | |
| static inline void ionic_tbl_delete(struct ionic_tbl_root *tbl, uint32_t key) | |
| { | |
| struct ionic_tbl_node *node; | |
| uint32_t node_i = key >> TBL_NODE_SHIFT; | |
| if (unlikely(key >> TBL_KEY_SHIFT)) { | |
| assert(key >> TBL_KEY_SHIFT == 0); | |
| return; | |
| } | |
| node = tbl->node[node_i]; | |
| if (unlikely(!node)) { | |
| assert(node != NULL); | |
| return; | |
| } | |
| if (unlikely(!node->val[key & TBL_NODE_MASK])) { | |
| assert(node->val[key & TBL_NODE_MASK] != NULL); | |
| return; | |
| } | |
| node->val[key & TBL_NODE_MASK] = NULL; | |
| --tbl->refcount[node_i]; | |
| if (!tbl->refcount[node_i]) { | |
| /* warning: with NDEBUG the old free node will leak */ | |
| assert(node != NULL); | |
| tbl->free_node = node; | |
| tbl->node[node_i] = NULL; | |
| } | |
| } | |
| #endif /* IONIC_TABLE_H */ |