blob: 12e19627e8be0e52d02ebd28cc7a119be90f0ae4 [file] [log] [blame]
/*
<:copyright-BRCM:2016:DUAL/GPL:standard
Broadcom Proprietary and Confidential.(c) 2016 Broadcom
All Rights Reserved
Unless you and Broadcom execute a separate written software license
agreement governing use of this software, this software is licensed
to you under the terms of the GNU General Public License version 2
(the "GPL"), available at http://www.broadcom.com/licenses/GPLv2.php,
with the following added to such license:
As a special exception, the copyright holders of this software give
you permission to link this software with independent modules, and
to copy and distribute the resulting executable under terms of your
choice, provided that you also meet, for each linked independent
module, the terms and conditions of the license of that module.
An independent module is a module which is not derived from this
software. The special exception does not apply to any modifications
of the software.
Not withstanding the above, under no circumstances may you combine
this software in any way with any other Broadcom software provided
under a license other than the GPL, without Broadcom's express prior
written consent.
:>
*/
#include "bcmos_hash_table.h"
#define ht_overhead sizeof(void *)
/** Create a hash table using a block pool
* \param[in] pool_parm pointer to block pool parameters
* \param[in] max_data_entries Maximum entries the hash table needs to hold
* \param[in] entry_size Size of each entry in bytes
* \param[in] key_size Size of each key in bytes
* \return pointer to newly created hash table
*/
static hash_table *hash_table_create_in_pool(bcmos_blk_pool_parm *pool_parm,
uint32_t max_data_entries,
uint16_t entry_size,
uint8_t key_size)
{
uint32_t lookup_table_entries = max_data_entries + (max_data_entries / 4);
hash_table *ht;
bcmos_errno err;
if (pool_parm == NULL)
{
BUG();
return NULL;
}
entry_size += key_size;
ht = bcmos_alloc(sizeof(hash_table));
if (ht == NULL)
{
BUG();
return NULL;
}
ht->obj_len = entry_size;
ht->key_len = key_size;
ht->data_offset = ht_overhead;
ht->ht_lookup_tbl_entries = lookup_table_entries;
ht->ht_max_data_entries = max_data_entries;
ht->look_up_entries_tbl = bcmos_alloc(lookup_table_entries * sizeof(ht_block));
if (ht->look_up_entries_tbl == NULL)
{
bcmos_free(ht);
BUG();
return NULL;
}
ht->ht_cur_entries = 0;
err = bcmos_blk_pool_create(&ht->key_data_pool, pool_parm);
if (err != BCM_ERR_OK)
{
bcmos_free(ht->look_up_entries_tbl);
bcmos_free(ht);
BUG();
return NULL;
}
if (ht->ht_max_data_entries > ht->ht_lookup_tbl_entries)
{
BUG();
}
ht->obj_len -= ht->key_len;
memset(ht->look_up_entries_tbl, 0, lookup_table_entries * sizeof(ht_block));
return ht;
}
hash_table *hash_table_create(uint32_t max_data_entries,
uint16_t entry_size,
uint8_t key_size,
char *pool_name)
{
bcmos_blk_pool_parm parm = {0};
parm.name = pool_name;
parm.blk_size = entry_size + key_size + ht_overhead;
parm.num_blks = max_data_entries;
return hash_table_create_in_pool(&parm, max_data_entries, entry_size, key_size);
}
/** Hash a length of bytes into a uint32_t
* \param[in] key Bytes to hash
* \param[in] len Number of bytes in key
* \return The hash as a uint32_t
*/
static uint32_t get_hash_for_key(const uint8_t *key, uint8_t len)
{
uint32_t hash = 5381;
uint8_t i;
const uint8_t *tmp = key;
for (i = 0; i < len; tmp++, i++)
{
hash = ((hash << 5) + hash) + (*tmp);
}
return hash;
}
/** Gets a hash key used for the keyDataPool in the HashTable
* \param[in] ht Associated hashtable we are getting hash for key for
* \param[in] key Key we are getting hash for
* \return An index into the keyDataPool
*/
static uint32_t get_hash_val_for_key(const hash_table *ht, const uint8_t *key)
{
return get_hash_for_key(key, ht->key_len) % ht->ht_lookup_tbl_entries;
}
/** Returns the location of the key within an HtBlock
* \param[in] ht Hash table in question
* \param[in] block HtBlock in question
* \return pointer to the data within the block
*/
static uint8_t *get_key_from_block_in_table(const hash_table *ht, ht_block *block)
{
return(uint8_t *) block + ht->data_offset + ht->obj_len;
}
/** Gets and populates a HtBlock with all of its data
* \param[in] next Next block in this buckets chain
* \param[in] ht Hash table in question
* \param[in] key This blocks key
* \param[in] val This blocks data
* \return The block that we allocated and returned
*/
static ht_block *fill_ht_block(ht_block *next,
hash_table *ht,
const uint8_t *key,
const void *val)
{
ht_block *dest_block = bcmos_blk_pool_alloc(&ht->key_data_pool);
if (dest_block != NULL)
{
/* storage is nextobj ptr, hash obj,
key which keeps all uint32_t aligned. */
dest_block->next_chain = next;
if (val != NULL)
{
memcpy((uint8_t *) dest_block + ht->data_offset, val, ht->obj_len);
}
else
{
memset((uint8_t *) dest_block + ht->data_offset,0,ht->obj_len);
}
/* Need to put key in after obj */
memcpy(
(uint8_t *) dest_block + ht->data_offset + ht->obj_len,
key,
ht->key_len);
}
return dest_block;
}
/** Determine whether two keys in a particular hash table match
* \param[in] ht Hashtable
* \param[in] key_a first key to compare
* \param[in] key_b second key to compare
* \return whether they are the same
*/
static bcmos_bool is_key_match(const hash_table *ht, const uint8_t *key_a, const uint8_t *key_b)
{
return memcmp(key_a, key_b, ht->key_len) == 0;
}
/** Searches a chained bucket looking for an instance of the key. If found returns the block if found the key in.
* Prev guy is set to the block in the chain before the block we returned (except in the case * where there is no
* block before the one we returned.
* \param[in] ht HashTable in question
* \param[in] key_to_find Key we wonder if exists
* \param[in] chain_start Where to start looking in the chain
* \param[in] prev_block The previous guy before the block we returned (if exists)
* \return The block that matches doesExists (if exists)
*/
static ht_block *get_key_loc_in_chain(
const hash_table *ht,
const uint8_t *key_to_find,
ht_block *chain_start,
ht_block **prev_block)
{
*prev_block = NULL;
while (chain_start != NULL)
{
if (is_key_match(ht, key_to_find, get_key_from_block_in_table(ht, chain_start)))
{
return chain_start;
}
*prev_block = chain_start;
chain_start = chain_start->next_chain;
}
return NULL;
}
bcmos_bool hash_table_remove(hash_table *ht, const uint8_t *key)
{
uint32_t hash_val = get_hash_val_for_key(ht, key);
ht_block *prev_entry;
ht_block *entry = get_key_loc_in_chain(
ht,
key,
ht->look_up_entries_tbl[hash_val].next_chain,
&prev_entry);
if (entry == NULL)
{
/* No one to delete */
return BCMOS_FALSE;
}
else
{
ht->ht_cur_entries--;
if (prev_entry == NULL)
{
/* last entry */
ht->look_up_entries_tbl[hash_val].next_chain = entry->next_chain;
}
else
{
prev_entry->next_chain = entry->next_chain;
}
bcmos_blk_pool_free(entry);
return BCMOS_TRUE;
}
}
/** Returns a pointer to the data within the HT
* \param[in] ht Hashtable in question
* \param[in] block_ptr HtBlock that we wonder where its data is
*/
static void *get_ht_data_ptr(const hash_table *ht, ht_block *block_ptr)
{
return(uint8_t*)block_ptr + ht->data_offset;
}
/** Get an entry in the hash table
* \param[in] ht pointer to hash table
* \param[in] key pointer to key data
* \param[in] hash_val hash value of key
* \return pointer to hash table entry
*/
static inline void *ht_get_internal(const hash_table *ht,
const uint8_t *key,
uint32_t hash_val)
{
ht_block *tmp;
ht_block *ret;
ret = get_key_loc_in_chain(
ht,
key,
ht->look_up_entries_tbl[hash_val].next_chain,
&tmp);
if (ret != NULL)
{
return get_ht_data_ptr(ht,ret);
}
else
{
return ret;
}
}
void *hash_table_get(const hash_table *ht, const uint8_t *key)
{
uint32_t hashVal = get_hash_val_for_key(ht, key);
return ht_get_internal(ht,key,hashVal);
}
void *hash_table_put(hash_table *ht, const uint8_t *key, const void *val)
{
void *ret_block;
uint32_t hash_val;
if (ht->ht_cur_entries >= ht->ht_max_data_entries)
{
return NULL;
}
hash_val = get_hash_val_for_key(ht, key);
ret_block = ht_get_internal((const hash_table *) ht, key, hash_val);
if (ret_block != NULL)
{
/* replace existing value with new value */
if (val != NULL)
{
memcpy(ret_block, val, ht->obj_len);
}
else
{
memset(ret_block, 0, ht->obj_len);
}
return ret_block;
}
else
{
ht_block *new_block=fill_ht_block(
ht->look_up_entries_tbl[hash_val].next_chain, ht, key, val);
if (new_block != NULL)
{
ht->look_up_entries_tbl[hash_val].next_chain = new_block;
ht->ht_cur_entries++;
return get_ht_data_ptr(ht,new_block);
}
else
{
return NULL;
}
}
}
ht_iterator ht_iterator_get(const hash_table *ht)
{
ht_iterator to_ret;
to_ret.ht = ht;
to_ret.cur_idx = 0;
to_ret.removed_at = BCMOS_FALSE;
to_ret.still_valid = BCMOS_TRUE;
to_ret.cur_block = NULL;
return to_ret;
}
ht_iterator ht_iterator_get_by_key(const hash_table *ht, const uint8_t *key)
{
ht_block *tmp;
uint32_t hash_val = get_hash_val_for_key(ht, key);
ht_iterator to_ret = {};
to_ret.ht = ht;
to_ret.removed_at = BCMOS_FALSE;
to_ret.cur_block = get_key_loc_in_chain(ht, key, ht->look_up_entries_tbl[hash_val].next_chain, &tmp);
to_ret.still_valid = (to_ret.cur_block != NULL);
to_ret.cur_idx = hash_val;
return to_ret;
}
/** Advance linear scan iterator
* \param[in] it Iterator to advance
*/
static void ht_iterator_scan_adv(ht_iterator *it)
{
if (it->cur_block != NULL)
{
it->cur_block = it->cur_block->next_chain;
if (it->cur_block != NULL)
{
it->still_valid = BCMOS_TRUE;
return;
}
else
{
it->cur_idx++;
}
}
/* find non null entry */
while (it->cur_idx < it->ht->ht_lookup_tbl_entries)
{
if (it->ht->look_up_entries_tbl[it->cur_idx].next_chain != NULL)
{
it->cur_block = it->ht->look_up_entries_tbl[it->cur_idx].next_chain;
it->still_valid = BCMOS_TRUE;
return;
}
else
{
it->cur_idx++;
}
}
it->still_valid = BCMOS_FALSE;
}
void ht_iterator_deref(const ht_iterator *hti, uint8_t **key, void **obj)
{
if (!hti->still_valid)
{
BCMOS_TRACE_ERR("%s: Invalid deref\n", __FUNCTION__);
}
else
{
*key = get_key_from_block_in_table(hti->ht, hti->cur_block);
*obj = get_ht_data_ptr(hti->ht, hti->cur_block); /* to data */
}
}
void ht_iterator_remove_at(hash_table *ht, ht_iterator *it)
{
if (ht != it->ht)
{
BCMOS_TRACE_ERR("%s: Incorrect writeable hash table pointer\n", __FUNCTION__);
}
else if (it->removed_at)
{
BCMOS_TRACE_ERR("%s: No delete twice\n", __FUNCTION__);
}
else
{
uint8_t *key;
uint8_t *obj;
ht_iterator_deref(it, &key, (void **) &obj);
it->removed_at = BCMOS_TRUE;
it->still_valid = ht_iterator_next(it);
if (!hash_table_remove(ht, key))
{
BCMOS_TRACE_ERR("%s: Remove error\n", __FUNCTION__);
}
}
}
bcmos_bool ht_iterator_next(ht_iterator *it)
{
if (it->still_valid)
{
if (it->removed_at)
{
it->removed_at = BCMOS_FALSE;
}
else
{
ht_iterator_scan_adv(it);
}
}
return it->still_valid; /* No entry found */
}
void hash_table_clear(hash_table *ht)
{
ht_iterator it = ht_iterator_get(ht);
while (ht_iterator_next(&it))
{
ht_iterator_remove_at(ht, &it);
}
}