568 lines
12 KiB
C
568 lines
12 KiB
C
/*-
|
|
* Copyright (c) 2018 VMware, Inc.
|
|
*
|
|
* SPDX-License-Identifier: (BSD-2-Clause OR GPL-2.0)
|
|
*/
|
|
|
|
/* Implementation of the VMCI Hashtable. */
|
|
|
|
#include <sys/cdefs.h>
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
#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);
|
|
|
|
/*
|
|
*------------------------------------------------------------------------------
|
|
*
|
|
* vmci_hashtable_create --
|
|
*
|
|
* Creates a hashtable.
|
|
*
|
|
* Result:
|
|
* None.
|
|
*
|
|
* Side effects:
|
|
* None.
|
|
*
|
|
*------------------------------------------------------------------------------
|
|
*/
|
|
|
|
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);
|
|
}
|
|
|
|
/*
|
|
*------------------------------------------------------------------------------
|
|
*
|
|
* vmci_hashtable_destroy --
|
|
*
|
|
* This function should be called at module exit time. We rely on the
|
|
* module ref count to insure that no one is accessing any hash table
|
|
* entries at this point in time. Hence we should be able to just remove
|
|
* all entries from the hash table.
|
|
*
|
|
* Result:
|
|
* None.
|
|
*
|
|
* Side effects:
|
|
* None.
|
|
*
|
|
*------------------------------------------------------------------------------
|
|
*/
|
|
|
|
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));
|
|
}
|
|
|
|
/*
|
|
*------------------------------------------------------------------------------
|
|
*
|
|
* vmci_hashtable_init_entry --
|
|
*
|
|
* Initializes a hash entry.
|
|
*
|
|
* Result:
|
|
* None.
|
|
*
|
|
* Side effects:
|
|
* None.
|
|
*
|
|
*------------------------------------------------------------------------------
|
|
*/
|
|
void
|
|
vmci_hashtable_init_entry(struct vmci_hash_entry *entry,
|
|
struct vmci_handle handle)
|
|
{
|
|
|
|
ASSERT(entry);
|
|
entry->handle = handle;
|
|
entry->ref_count = 0;
|
|
}
|
|
|
|
/*
|
|
*------------------------------------------------------------------------------
|
|
*
|
|
* vmci_hashtable_add_entry --
|
|
*
|
|
* Adds an entry to the hashtable.
|
|
*
|
|
* Result:
|
|
* None.
|
|
*
|
|
* Side effects:
|
|
* None.
|
|
*
|
|
*------------------------------------------------------------------------------
|
|
*/
|
|
|
|
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);
|
|
|
|
/* New entry is added to top/front of hash bucket. */
|
|
entry->ref_count++;
|
|
entry->next = table->entries[idx];
|
|
table->entries[idx] = entry;
|
|
vmci_release_lock_bh(&table->lock);
|
|
|
|
return (VMCI_SUCCESS);
|
|
}
|
|
|
|
/*
|
|
*------------------------------------------------------------------------------
|
|
*
|
|
* vmci_hashtable_remove_entry --
|
|
*
|
|
* Removes an entry from the hashtable.
|
|
*
|
|
* Result:
|
|
* None.
|
|
*
|
|
* Side effects:
|
|
* None.
|
|
*
|
|
*------------------------------------------------------------------------------
|
|
*/
|
|
|
|
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);
|
|
|
|
/* First unlink the entry. */
|
|
result = hashtable_unlink_entry(table, entry);
|
|
if (result != VMCI_SUCCESS) {
|
|
/* We failed to find the entry. */
|
|
goto done;
|
|
}
|
|
|
|
/* Decrement refcount and check if this is last reference. */
|
|
entry->ref_count--;
|
|
if (entry->ref_count == 0) {
|
|
result = VMCI_SUCCESS_ENTRY_DEAD;
|
|
goto done;
|
|
}
|
|
|
|
done:
|
|
vmci_release_lock_bh(&table->lock);
|
|
|
|
return (result);
|
|
}
|
|
|
|
/*
|
|
*------------------------------------------------------------------------------
|
|
*
|
|
* vmci_hashtable_get_entry_locked --
|
|
*
|
|
* Looks up an entry in the hash table, that is already locked.
|
|
*
|
|
* Result:
|
|
* If the element is found, a pointer to the element is returned.
|
|
* Otherwise NULL is returned.
|
|
*
|
|
* Side effects:
|
|
* The reference count of the returned element is increased.
|
|
*
|
|
*------------------------------------------------------------------------------
|
|
*/
|
|
|
|
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);
|
|
}
|
|
|
|
/*
|
|
*------------------------------------------------------------------------------
|
|
*
|
|
* vmci_hashtable_get_entry --
|
|
*
|
|
* Gets an entry from the hashtable.
|
|
*
|
|
* Result:
|
|
* None.
|
|
*
|
|
* Side effects:
|
|
* None.
|
|
*
|
|
*------------------------------------------------------------------------------
|
|
*/
|
|
|
|
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);
|
|
}
|
|
|
|
/*
|
|
*------------------------------------------------------------------------------
|
|
*
|
|
* vmci_hashtable_hold_entry --
|
|
*
|
|
* Hold the given entry. This will increment the entry's reference count.
|
|
* This is like a GetEntry() but without having to lookup the entry by
|
|
* handle.
|
|
*
|
|
* Result:
|
|
* None.
|
|
*
|
|
* Side effects:
|
|
* None.
|
|
*
|
|
*------------------------------------------------------------------------------
|
|
*/
|
|
|
|
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);
|
|
}
|
|
|
|
/*
|
|
*------------------------------------------------------------------------------
|
|
*
|
|
* vmci_hashtable_release_entry_locked --
|
|
*
|
|
* Releases an element previously obtained with
|
|
* vmci_hashtable_get_entry_locked.
|
|
*
|
|
* Result:
|
|
* If the entry is removed from the hash table, VMCI_SUCCESS_ENTRY_DEAD
|
|
* is returned. Otherwise, VMCI_SUCCESS is returned.
|
|
*
|
|
* Side effects:
|
|
* The reference count of the entry is decreased and the entry is removed
|
|
* from the hash table on 0.
|
|
*
|
|
*------------------------------------------------------------------------------
|
|
*/
|
|
|
|
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--;
|
|
/* Check if this is last reference and report if so. */
|
|
if (entry->ref_count == 0) {
|
|
/*
|
|
* Remove entry from hash table if not already removed. This
|
|
* could have happened already because VMCIHashTable_RemoveEntry
|
|
* was called to unlink it. We ignore if it is not found.
|
|
* Datagram handles will often have RemoveEntry called, whereas
|
|
* SharedMemory regions rely on ReleaseEntry to unlink the entry
|
|
* , since the creator does not call RemoveEntry when it
|
|
* detaches.
|
|
*/
|
|
|
|
hashtable_unlink_entry(table, entry);
|
|
result = VMCI_SUCCESS_ENTRY_DEAD;
|
|
}
|
|
|
|
return (result);
|
|
}
|
|
|
|
/*
|
|
*------------------------------------------------------------------------------
|
|
*
|
|
* vmci_hashtable_release_entry --
|
|
*
|
|
* Releases an entry from the hashtable.
|
|
*
|
|
* Result:
|
|
* None.
|
|
*
|
|
* Side effects:
|
|
* None.
|
|
*
|
|
*------------------------------------------------------------------------------
|
|
*/
|
|
|
|
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);
|
|
}
|
|
|
|
/*
|
|
*------------------------------------------------------------------------------
|
|
*
|
|
* vmci_hashtable_entry_exists --
|
|
*
|
|
* Returns whether an entry exists in the hashtable
|
|
*
|
|
* Result:
|
|
* true if handle already in hashtable. false otherwise.
|
|
*
|
|
* Side effects:
|
|
* None.
|
|
*
|
|
*------------------------------------------------------------------------------
|
|
*/
|
|
|
|
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);
|
|
}
|
|
|
|
/*
|
|
*------------------------------------------------------------------------------
|
|
*
|
|
* vmci_hashtable_entry_exists_locked --
|
|
*
|
|
* Unlocked version of vmci_hashtable_entry_exists.
|
|
*
|
|
* Result:
|
|
* true if handle already in hashtable. false otherwise.
|
|
*
|
|
* Side effects:
|
|
* None.
|
|
*
|
|
*------------------------------------------------------------------------------
|
|
*/
|
|
|
|
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);
|
|
}
|
|
|
|
/*
|
|
*------------------------------------------------------------------------------
|
|
*
|
|
* hashtable_unlink_entry --
|
|
*
|
|
* Assumes caller holds table lock.
|
|
*
|
|
* Result:
|
|
* None.
|
|
*
|
|
* Side effects:
|
|
* None.
|
|
*
|
|
*------------------------------------------------------------------------------
|
|
*/
|
|
|
|
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);
|
|
|
|
/* Remove entry and break. */
|
|
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);
|
|
}
|
|
|
|
/*
|
|
*------------------------------------------------------------------------------
|
|
*
|
|
* vmci_hashtable_sync --
|
|
*
|
|
* Use this as a synchronization point when setting globals, for example,
|
|
* during device shutdown.
|
|
*
|
|
* Results:
|
|
* None.
|
|
*
|
|
* Side effects:
|
|
* None.
|
|
*
|
|
*------------------------------------------------------------------------------
|
|
*/
|
|
|
|
void
|
|
vmci_hashtable_sync(struct vmci_hashtable *table)
|
|
{
|
|
|
|
ASSERT(table);
|
|
vmci_grab_lock_bh(&table->lock);
|
|
vmci_release_lock_bh(&table->lock);
|
|
}
|