#include <sys/cdefs.h>
#include "vmci.h"
#include "vmci_driver.h"
#include "vmci_hashtable.h"
#include "vmci_kernel_defs.h"
#include "vmci_utils.h"
#define LGPFX "vmci_hashtable: "
#define VMCI_HASHTABLE_HASH(_h, _sz) \
vmci_hash_id(VMCI_HANDLE_TO_RESOURCE_ID(_h), (_sz))
static int hashtable_unlink_entry(struct vmci_hashtable *table,
struct vmci_hash_entry *entry);
static bool vmci_hashtable_entry_exists_locked(struct vmci_hashtable *table,
struct vmci_handle handle);
struct vmci_hashtable *
vmci_hashtable_create(int size)
{
struct vmci_hashtable *table;
table = vmci_alloc_kernel_mem(sizeof(*table),
VMCI_MEMORY_NORMAL);
if (table == NULL)
return (NULL);
memset(table, 0, sizeof(*table));
table->entries = vmci_alloc_kernel_mem(sizeof(*table->entries) * size,
VMCI_MEMORY_NORMAL);
if (table->entries == NULL) {
vmci_free_kernel_mem(table, sizeof(*table));
return (NULL);
}
memset(table->entries, 0, sizeof(*table->entries) * size);
table->size = size;
if (vmci_init_lock(&table->lock, "VMCI Hashtable lock") <
VMCI_SUCCESS) {
vmci_free_kernel_mem(table->entries, sizeof(*table->entries) * size);
vmci_free_kernel_mem(table, sizeof(*table));
return (NULL);
}
return (table);
}
void
vmci_hashtable_destroy(struct vmci_hashtable *table)
{
ASSERT(table);
vmci_grab_lock_bh(&table->lock);
vmci_free_kernel_mem(table->entries, sizeof(*table->entries) *
table->size);
table->entries = NULL;
vmci_release_lock_bh(&table->lock);
vmci_cleanup_lock(&table->lock);
vmci_free_kernel_mem(table, sizeof(*table));
}
void
vmci_hashtable_init_entry(struct vmci_hash_entry *entry,
struct vmci_handle handle)
{
ASSERT(entry);
entry->handle = handle;
entry->ref_count = 0;
}
int
vmci_hashtable_add_entry(struct vmci_hashtable *table,
struct vmci_hash_entry *entry)
{
int idx;
ASSERT(entry);
ASSERT(table);
vmci_grab_lock_bh(&table->lock);
if (vmci_hashtable_entry_exists_locked(table, entry->handle)) {
VMCI_LOG_DEBUG(LGPFX"Entry (handle=0x%x:0x%x) already "
"exists.\n", entry->handle.context,
entry->handle.resource);
vmci_release_lock_bh(&table->lock);
return (VMCI_ERROR_DUPLICATE_ENTRY);
}
idx = VMCI_HASHTABLE_HASH(entry->handle, table->size);
ASSERT(idx < table->size);
entry->ref_count++;
entry->next = table->entries[idx];
table->entries[idx] = entry;
vmci_release_lock_bh(&table->lock);
return (VMCI_SUCCESS);
}
int
vmci_hashtable_remove_entry(struct vmci_hashtable *table,
struct vmci_hash_entry *entry)
{
int result;
ASSERT(table);
ASSERT(entry);
vmci_grab_lock_bh(&table->lock);
result = hashtable_unlink_entry(table, entry);
if (result != VMCI_SUCCESS) {
goto done;
}
entry->ref_count--;
if (entry->ref_count == 0) {
result = VMCI_SUCCESS_ENTRY_DEAD;
goto done;
}
done:
vmci_release_lock_bh(&table->lock);
return (result);
}
static struct vmci_hash_entry *
vmci_hashtable_get_entry_locked(struct vmci_hashtable *table,
struct vmci_handle handle)
{
struct vmci_hash_entry *cur = NULL;
int idx;
ASSERT(!VMCI_HANDLE_EQUAL(handle, VMCI_INVALID_HANDLE));
ASSERT(table);
idx = VMCI_HASHTABLE_HASH(handle, table->size);
cur = table->entries[idx];
while (true) {
if (cur == NULL)
break;
if (VMCI_HANDLE_TO_RESOURCE_ID(cur->handle) ==
VMCI_HANDLE_TO_RESOURCE_ID(handle)) {
if ((VMCI_HANDLE_TO_CONTEXT_ID(cur->handle) ==
VMCI_HANDLE_TO_CONTEXT_ID(handle)) ||
(VMCI_INVALID_ID == VMCI_HANDLE_TO_CONTEXT_ID(cur->handle))) {
cur->ref_count++;
break;
}
}
cur = cur->next;
}
return (cur);
}
struct vmci_hash_entry *
vmci_hashtable_get_entry(struct vmci_hashtable *table,
struct vmci_handle handle)
{
struct vmci_hash_entry *entry;
if (VMCI_HANDLE_EQUAL(handle, VMCI_INVALID_HANDLE))
return (NULL);
ASSERT(table);
vmci_grab_lock_bh(&table->lock);
entry = vmci_hashtable_get_entry_locked(table, handle);
vmci_release_lock_bh(&table->lock);
return (entry);
}
void
vmci_hashtable_hold_entry(struct vmci_hashtable *table,
struct vmci_hash_entry *entry)
{
ASSERT(table);
ASSERT(entry);
vmci_grab_lock_bh(&table->lock);
entry->ref_count++;
vmci_release_lock_bh(&table->lock);
}
static int
vmci_hashtable_release_entry_locked(struct vmci_hashtable *table,
struct vmci_hash_entry *entry)
{
int result = VMCI_SUCCESS;
ASSERT(table);
ASSERT(entry);
entry->ref_count--;
if (entry->ref_count == 0) {
hashtable_unlink_entry(table, entry);
result = VMCI_SUCCESS_ENTRY_DEAD;
}
return (result);
}
int
vmci_hashtable_release_entry(struct vmci_hashtable *table,
struct vmci_hash_entry *entry)
{
int result;
ASSERT(table);
vmci_grab_lock_bh(&table->lock);
result = vmci_hashtable_release_entry_locked(table, entry);
vmci_release_lock_bh(&table->lock);
return (result);
}
bool
vmci_hashtable_entry_exists(struct vmci_hashtable *table,
struct vmci_handle handle)
{
bool exists;
ASSERT(table);
vmci_grab_lock_bh(&table->lock);
exists = vmci_hashtable_entry_exists_locked(table, handle);
vmci_release_lock_bh(&table->lock);
return (exists);
}
static bool
vmci_hashtable_entry_exists_locked(struct vmci_hashtable *table,
struct vmci_handle handle)
{
struct vmci_hash_entry *entry;
int idx;
ASSERT(table);
idx = VMCI_HASHTABLE_HASH(handle, table->size);
entry = table->entries[idx];
while (entry) {
if (VMCI_HANDLE_TO_RESOURCE_ID(entry->handle) ==
VMCI_HANDLE_TO_RESOURCE_ID(handle))
if ((VMCI_HANDLE_TO_CONTEXT_ID(entry->handle) ==
VMCI_HANDLE_TO_CONTEXT_ID(handle)) ||
(VMCI_INVALID_ID == VMCI_HANDLE_TO_CONTEXT_ID(handle)) ||
(VMCI_INVALID_ID == VMCI_HANDLE_TO_CONTEXT_ID(entry->handle)))
return (true);
entry = entry->next;
}
return (false);
}
static int
hashtable_unlink_entry(struct vmci_hashtable *table,
struct vmci_hash_entry *entry)
{
int result;
struct vmci_hash_entry *prev, *cur;
int idx;
idx = VMCI_HASHTABLE_HASH(entry->handle, table->size);
prev = NULL;
cur = table->entries[idx];
while (true) {
if (cur == NULL) {
result = VMCI_ERROR_NOT_FOUND;
break;
}
if (VMCI_HANDLE_EQUAL(cur->handle, entry->handle)) {
ASSERT(cur == entry);
if (prev)
prev->next = cur->next;
else
table->entries[idx] = cur->next;
cur->next = NULL;
result = VMCI_SUCCESS;
break;
}
prev = cur;
cur = cur->next;
}
return (result);
}
void
vmci_hashtable_sync(struct vmci_hashtable *table)
{
ASSERT(table);
vmci_grab_lock_bh(&table->lock);
vmci_release_lock_bh(&table->lock);
}