/*- * Copyright (c) 2018 VMware, Inc. * * SPDX-License-Identifier: (BSD-2-Clause OR GPL-2.0) */ /* Implementation of the VMCI Hashtable. */ #include #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); }