blob: 354935b6526c759fecbec7d56c0da37fa2d765ae [file] [log] [blame] [edit]
/* 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 */