Changeset View
Changeset View
Standalone View
Standalone View
head/sys/dev/vmware/vmci/vmci_doorbell.c
Property | Old Value | New Value |
---|---|---|
svn:eol-style | null | native \ No newline at end of property |
svn:keywords | null | FreeBSD=%H \ No newline at end of property |
svn:mime-type | null | text/plain \ No newline at end of property |
/*- | |||||
* Copyright (c) 2018 VMware, Inc. All Rights Reserved. | |||||
* | |||||
* SPDX-License-Identifier: (BSD-2-Clause AND GPL-2.0) | |||||
*/ | |||||
/* This file implements the VMCI doorbell API. */ | |||||
#include <sys/cdefs.h> | |||||
__FBSDID("$FreeBSD$"); | |||||
#include <sys/types.h> | |||||
#include "vmci_doorbell.h" | |||||
#include "vmci_driver.h" | |||||
#include "vmci_kernel_api.h" | |||||
#include "vmci_kernel_defs.h" | |||||
#include "vmci_resource.h" | |||||
#include "vmci_utils.h" | |||||
#define LGPFX "vmci_doorbell: " | |||||
#define VMCI_DOORBELL_INDEX_TABLE_SIZE 64 | |||||
#define VMCI_DOORBELL_HASH(_idx) \ | |||||
vmci_hash_id((_idx), VMCI_DOORBELL_INDEX_TABLE_SIZE) | |||||
/* Describes a doorbell notification handle allocated by the host. */ | |||||
struct vmci_doorbell_entry { | |||||
struct vmci_resource resource; | |||||
uint32_t idx; | |||||
vmci_list_item(vmci_doorbell_entry) idx_list_item; | |||||
vmci_privilege_flags priv_flags; | |||||
bool is_doorbell; | |||||
bool run_delayed; | |||||
vmci_callback notify_cb; | |||||
void *client_data; | |||||
vmci_event destroy_event; | |||||
volatile int active; | |||||
}; | |||||
struct vmci_doorbell_index_table { | |||||
vmci_lock lock; | |||||
vmci_list(vmci_doorbell_entry) entries[VMCI_DOORBELL_INDEX_TABLE_SIZE]; | |||||
}; | |||||
/* The VMCI index table keeps track of currently registered doorbells. */ | |||||
static struct vmci_doorbell_index_table vmci_doorbell_it; | |||||
/* | |||||
* The max_notify_idx is one larger than the currently known bitmap index in | |||||
* use, and is used to determine how much of the bitmap needs to be scanned. | |||||
*/ | |||||
static uint32_t max_notify_idx; | |||||
/* | |||||
* The notify_idx_count is used for determining whether there are free entries | |||||
* within the bitmap (if notify_idx_count + 1 < max_notify_idx). | |||||
*/ | |||||
static uint32_t notify_idx_count; | |||||
/* | |||||
* The last_notify_idx_reserved is used to track the last index handed out - in | |||||
* the case where multiple handles share a notification index, we hand out | |||||
* indexes round robin based on last_notify_idx_reserved. | |||||
*/ | |||||
static uint32_t last_notify_idx_reserved; | |||||
/* This is a one entry cache used to by the index allocation. */ | |||||
static uint32_t last_notify_idx_released = PAGE_SIZE; | |||||
static void vmci_doorbell_free_cb(void *client_data); | |||||
static int vmci_doorbell_release_cb(void *client_data); | |||||
static void vmci_doorbell_delayed_dispatch_cb(void *data); | |||||
/* | |||||
*------------------------------------------------------------------------------ | |||||
* | |||||
* vmci_doorbell_init -- | |||||
* | |||||
* General init code. | |||||
* | |||||
* Result: | |||||
* VMCI_SUCCESS on success, lock allocation error otherwise. | |||||
* | |||||
* Side effects: | |||||
* None. | |||||
* | |||||
*------------------------------------------------------------------------------ | |||||
*/ | |||||
int | |||||
vmci_doorbell_init(void) | |||||
{ | |||||
uint32_t bucket; | |||||
for (bucket = 0; bucket < ARRAYSIZE(vmci_doorbell_it.entries); | |||||
++bucket) | |||||
vmci_list_init(&vmci_doorbell_it.entries[bucket]); | |||||
return (vmci_init_lock(&vmci_doorbell_it.lock, | |||||
"VMCI Doorbell index table lock")); | |||||
} | |||||
/* | |||||
*------------------------------------------------------------------------------ | |||||
* | |||||
* vmci_doorbell_exit -- | |||||
* | |||||
* General exit code. | |||||
* | |||||
* Result: | |||||
* None. | |||||
* | |||||
* Side effects: | |||||
* None. | |||||
* | |||||
*------------------------------------------------------------------------------ | |||||
*/ | |||||
void | |||||
vmci_doorbell_exit(void) | |||||
{ | |||||
vmci_cleanup_lock(&vmci_doorbell_it.lock); | |||||
} | |||||
/* | |||||
*------------------------------------------------------------------------------ | |||||
* | |||||
* vmci_doorbell_free_cb -- | |||||
* | |||||
* Callback to free doorbell entry structure when resource is no longer used, | |||||
* i.e. the reference count reached 0. The entry is freed in | |||||
* vmci_doorbell_destroy(), which is waiting on the signal that gets fired | |||||
* here. | |||||
* | |||||
* Result: | |||||
* None. | |||||
* | |||||
* Side effects: | |||||
* Signals VMCI event. | |||||
* | |||||
*------------------------------------------------------------------------------ | |||||
*/ | |||||
static void | |||||
vmci_doorbell_free_cb(void *client_data) | |||||
{ | |||||
struct vmci_doorbell_entry *entry; | |||||
entry = (struct vmci_doorbell_entry *)client_data; | |||||
ASSERT(entry); | |||||
vmci_signal_event(&entry->destroy_event); | |||||
} | |||||
/* | |||||
*------------------------------------------------------------------------------ | |||||
* | |||||
* vmci_doorbell_release_cb -- | |||||
* | |||||
* Callback to release the resource reference. It is called by the | |||||
* vmci_wait_on_event function before it blocks. | |||||
* | |||||
* Result: | |||||
* Always 0. | |||||
* | |||||
* Side effects: | |||||
* None. | |||||
* | |||||
*------------------------------------------------------------------------------ | |||||
*/ | |||||
static int | |||||
vmci_doorbell_release_cb(void *client_data) | |||||
{ | |||||
struct vmci_doorbell_entry *entry; | |||||
entry = (struct vmci_doorbell_entry *)client_data; | |||||
ASSERT(entry); | |||||
vmci_resource_release(&entry->resource); | |||||
return (0); | |||||
} | |||||
/* | |||||
*------------------------------------------------------------------------------ | |||||
* | |||||
* vmci_doorbell_get_priv_flags -- | |||||
* | |||||
* Utility function that retrieves the privilege flags associated with a | |||||
* given doorbell handle. For guest endpoints, the privileges are determined | |||||
* by the context ID, but for host endpoints privileges are associated with | |||||
* the complete handle. Hypervisor endpoints are not yet supported. | |||||
* | |||||
* Result: | |||||
* VMCI_SUCCESS on success, | |||||
* VMCI_ERROR_NOT_FOUND if handle isn't found, | |||||
* VMCI_ERROR_INVALID_ARGS if handle is invalid. | |||||
* | |||||
* Side effects: | |||||
* None. | |||||
* | |||||
*------------------------------------------------------------------------------ | |||||
*/ | |||||
int | |||||
vmci_doorbell_get_priv_flags(struct vmci_handle handle, | |||||
vmci_privilege_flags *priv_flags) | |||||
{ | |||||
if (priv_flags == NULL || handle.context == VMCI_INVALID_ID) | |||||
return (VMCI_ERROR_INVALID_ARGS); | |||||
if (handle.context == VMCI_HOST_CONTEXT_ID) { | |||||
struct vmci_doorbell_entry *entry; | |||||
struct vmci_resource *resource; | |||||
resource = vmci_resource_get(handle, | |||||
VMCI_RESOURCE_TYPE_DOORBELL); | |||||
if (resource == NULL) | |||||
return (VMCI_ERROR_NOT_FOUND); | |||||
entry = RESOURCE_CONTAINER( | |||||
resource, struct vmci_doorbell_entry, resource); | |||||
*priv_flags = entry->priv_flags; | |||||
vmci_resource_release(resource); | |||||
} else if (handle.context == VMCI_HYPERVISOR_CONTEXT_ID) { | |||||
/* Hypervisor endpoints for notifications are not supported. */ | |||||
return (VMCI_ERROR_INVALID_ARGS); | |||||
} else | |||||
*priv_flags = VMCI_NO_PRIVILEGE_FLAGS; | |||||
return (VMCI_SUCCESS); | |||||
} | |||||
/* | |||||
*------------------------------------------------------------------------------ | |||||
* | |||||
* vmci_doorbell_index_table_find -- | |||||
* | |||||
* Find doorbell entry by bitmap index. | |||||
* | |||||
* Results: | |||||
* Entry if found, NULL if not. | |||||
* | |||||
* Side effects: | |||||
* None. | |||||
* | |||||
*------------------------------------------------------------------------------ | |||||
*/ | |||||
static struct vmci_doorbell_entry * | |||||
vmci_doorbell_index_table_find(uint32_t idx) | |||||
{ | |||||
struct vmci_doorbell_entry *iter; | |||||
uint32_t bucket; | |||||
bucket = VMCI_DOORBELL_HASH(idx); | |||||
vmci_list_scan(iter, &vmci_doorbell_it.entries[bucket], idx_list_item) { | |||||
if (idx == iter->idx) | |||||
return (iter); | |||||
} | |||||
return (NULL); | |||||
} | |||||
/* | |||||
*------------------------------------------------------------------------------ | |||||
* | |||||
* vmci_doorbell_index_table_add -- | |||||
* | |||||
* Add the given entry to the index table. This will hold() the entry's | |||||
* resource so that the entry is not deleted before it is removed from the | |||||
* table. | |||||
* | |||||
* Results: | |||||
* None. | |||||
* | |||||
* Side effects: | |||||
* None. | |||||
* | |||||
*------------------------------------------------------------------------------ | |||||
*/ | |||||
static void | |||||
vmci_doorbell_index_table_add(struct vmci_doorbell_entry *entry) | |||||
{ | |||||
uint32_t bucket; | |||||
uint32_t new_notify_idx; | |||||
ASSERT(entry); | |||||
vmci_resource_hold(&entry->resource); | |||||
vmci_grab_lock_bh(&vmci_doorbell_it.lock); | |||||
/* | |||||
* Below we try to allocate an index in the notification bitmap with | |||||
* "not too much" sharing between resources. If we use less that the | |||||
* full bitmap, we either add to the end if there are no unused flags | |||||
* within the currently used area, or we search for unused ones. If we | |||||
* use the full bitmap, we allocate the index round robin. | |||||
*/ | |||||
if (max_notify_idx < PAGE_SIZE || notify_idx_count < PAGE_SIZE) { | |||||
if (last_notify_idx_released < max_notify_idx && | |||||
!vmci_doorbell_index_table_find(last_notify_idx_released)) { | |||||
new_notify_idx = last_notify_idx_released; | |||||
last_notify_idx_released = PAGE_SIZE; | |||||
} else { | |||||
bool reused = false; | |||||
new_notify_idx = last_notify_idx_reserved; | |||||
if (notify_idx_count + 1 < max_notify_idx) { | |||||
do { | |||||
if (!vmci_doorbell_index_table_find( | |||||
new_notify_idx)) { | |||||
reused = true; | |||||
break; | |||||
} | |||||
new_notify_idx = (new_notify_idx + 1) % | |||||
max_notify_idx; | |||||
} while (new_notify_idx != | |||||
last_notify_idx_released); | |||||
} | |||||
if (!reused) { | |||||
new_notify_idx = max_notify_idx; | |||||
max_notify_idx++; | |||||
} | |||||
} | |||||
} else { | |||||
new_notify_idx = (last_notify_idx_reserved + 1) % PAGE_SIZE; | |||||
} | |||||
last_notify_idx_reserved = new_notify_idx; | |||||
notify_idx_count++; | |||||
entry->idx = new_notify_idx; | |||||
bucket = VMCI_DOORBELL_HASH(entry->idx); | |||||
vmci_list_insert(&vmci_doorbell_it.entries[bucket], entry, | |||||
idx_list_item); | |||||
vmci_release_lock_bh(&vmci_doorbell_it.lock); | |||||
} | |||||
/* | |||||
*------------------------------------------------------------------------------ | |||||
* | |||||
* vmci_doorbell_index_table_remove -- | |||||
* | |||||
* Remove the given entry from the index table. This will release() the | |||||
* entry's resource. | |||||
* | |||||
* Results: | |||||
* None. | |||||
* | |||||
* Side effects: | |||||
* None. | |||||
* | |||||
*------------------------------------------------------------------------------ | |||||
*/ | |||||
static void | |||||
vmci_doorbell_index_table_remove(struct vmci_doorbell_entry *entry) | |||||
{ | |||||
ASSERT(entry); | |||||
vmci_grab_lock_bh(&vmci_doorbell_it.lock); | |||||
vmci_list_remove(entry, idx_list_item); | |||||
notify_idx_count--; | |||||
if (entry->idx == max_notify_idx - 1) { | |||||
/* | |||||
* If we delete an entry with the maximum known notification | |||||
* index, we take the opportunity to prune the current max. As | |||||
* there might be other unused indices immediately below, we | |||||
* lower the maximum until we hit an index in use | |||||
*/ | |||||
while (max_notify_idx > 0 && | |||||
!vmci_doorbell_index_table_find(max_notify_idx - 1)) | |||||
max_notify_idx--; | |||||
} | |||||
last_notify_idx_released = entry->idx; | |||||
vmci_release_lock_bh(&vmci_doorbell_it.lock); | |||||
vmci_resource_release(&entry->resource); | |||||
} | |||||
/* | |||||
*------------------------------------------------------------------------------ | |||||
* | |||||
* vmci_doorbell_link -- | |||||
* | |||||
* Creates a link between the given doorbell handle and the given index in | |||||
* the bitmap in the device backend. | |||||
* | |||||
* Results: | |||||
* VMCI_SUCCESS if success, appropriate error code otherwise. | |||||
* | |||||
* Side effects: | |||||
* Notification state is created in hypervisor. | |||||
* | |||||
*------------------------------------------------------------------------------ | |||||
*/ | |||||
static int | |||||
vmci_doorbell_link(struct vmci_handle handle, bool is_doorbell, | |||||
uint32_t notify_idx) | |||||
{ | |||||
struct vmci_doorbell_link_msg link_msg; | |||||
vmci_id resource_id; | |||||
ASSERT(!VMCI_HANDLE_INVALID(handle)); | |||||
if (is_doorbell) | |||||
resource_id = VMCI_DOORBELL_LINK; | |||||
else { | |||||
ASSERT(false); | |||||
return (VMCI_ERROR_UNAVAILABLE); | |||||
} | |||||
link_msg.hdr.dst = VMCI_MAKE_HANDLE(VMCI_HYPERVISOR_CONTEXT_ID, | |||||
resource_id); | |||||
link_msg.hdr.src = VMCI_ANON_SRC_HANDLE; | |||||
link_msg.hdr.payload_size = sizeof(link_msg) - VMCI_DG_HEADERSIZE; | |||||
link_msg.handle = handle; | |||||
link_msg.notify_idx = notify_idx; | |||||
return (vmci_send_datagram((struct vmci_datagram *)&link_msg)); | |||||
} | |||||
/* | |||||
*------------------------------------------------------------------------------ | |||||
* | |||||
* vmci_doorbell_unlink -- | |||||
* | |||||
* Unlinks the given doorbell handle from an index in the bitmap in the | |||||
* device backend. | |||||
* | |||||
* Results: | |||||
* VMCI_SUCCESS if success, appropriate error code otherwise. | |||||
* | |||||
* Side effects: | |||||
* Notification state is destroyed in hypervisor. | |||||
* | |||||
*------------------------------------------------------------------------------ | |||||
*/ | |||||
static int | |||||
vmci_doorbell_unlink(struct vmci_handle handle, bool is_doorbell) | |||||
{ | |||||
struct vmci_doorbell_unlink_msg unlink_msg; | |||||
vmci_id resource_id; | |||||
ASSERT(!VMCI_HANDLE_INVALID(handle)); | |||||
if (is_doorbell) | |||||
resource_id = VMCI_DOORBELL_UNLINK; | |||||
else { | |||||
ASSERT(false); | |||||
return (VMCI_ERROR_UNAVAILABLE); | |||||
} | |||||
unlink_msg.hdr.dst = VMCI_MAKE_HANDLE(VMCI_HYPERVISOR_CONTEXT_ID, | |||||
resource_id); | |||||
unlink_msg.hdr.src = VMCI_ANON_SRC_HANDLE; | |||||
unlink_msg.hdr.payload_size = sizeof(unlink_msg) - VMCI_DG_HEADERSIZE; | |||||
unlink_msg.handle = handle; | |||||
return (vmci_send_datagram((struct vmci_datagram *)&unlink_msg)); | |||||
} | |||||
/* | |||||
*------------------------------------------------------------------------------ | |||||
* | |||||
* vmci_doorbell_create -- | |||||
* | |||||
* Creates a doorbell with the given callback. If the handle is | |||||
* VMCI_INVALID_HANDLE, a free handle will be assigned, if possible. The | |||||
* callback can be run immediately (potentially with locks held - the | |||||
* default) or delayed (in a kernel thread) by specifying the flag | |||||
* VMCI_FLAG_DELAYED_CB. If delayed execution is selected, a given callback | |||||
* may not be run if the kernel is unable to allocate memory for the delayed | |||||
* execution (highly unlikely). | |||||
* | |||||
* Results: | |||||
* VMCI_SUCCESS on success, appropriate error code otherwise. | |||||
* | |||||
* Side effects: | |||||
* None. | |||||
* | |||||
*------------------------------------------------------------------------------ | |||||
*/ | |||||
int | |||||
vmci_doorbell_create(struct vmci_handle *handle, uint32_t flags, | |||||
vmci_privilege_flags priv_flags, vmci_callback notify_cb, void *client_data) | |||||
{ | |||||
struct vmci_doorbell_entry *entry; | |||||
struct vmci_handle new_handle; | |||||
int result; | |||||
if (!handle || !notify_cb || flags & ~VMCI_FLAG_DELAYED_CB || | |||||
priv_flags & ~VMCI_PRIVILEGE_ALL_FLAGS) | |||||
return (VMCI_ERROR_INVALID_ARGS); | |||||
entry = vmci_alloc_kernel_mem(sizeof(*entry), VMCI_MEMORY_NORMAL); | |||||
if (entry == NULL) { | |||||
VMCI_LOG_WARNING(LGPFX"Failed allocating memory for datagram " | |||||
"entry.\n"); | |||||
return (VMCI_ERROR_NO_MEM); | |||||
} | |||||
if (!vmci_can_schedule_delayed_work() && | |||||
(flags & VMCI_FLAG_DELAYED_CB)) { | |||||
result = VMCI_ERROR_INVALID_ARGS; | |||||
goto free_mem; | |||||
} | |||||
if (VMCI_HANDLE_INVALID(*handle)) { | |||||
vmci_id context_id; | |||||
context_id = vmci_get_context_id(); | |||||
vmci_id resource_id = vmci_resource_get_id(context_id); | |||||
if (resource_id == VMCI_INVALID_ID) { | |||||
result = VMCI_ERROR_NO_HANDLE; | |||||
goto free_mem; | |||||
} | |||||
new_handle = VMCI_MAKE_HANDLE(context_id, resource_id); | |||||
} else { | |||||
if (VMCI_INVALID_ID == handle->resource) { | |||||
VMCI_LOG_DEBUG(LGPFX"Invalid argument " | |||||
"(handle=0x%x:0x%x).\n", handle->context, | |||||
handle->resource); | |||||
result = VMCI_ERROR_INVALID_ARGS; | |||||
goto free_mem; | |||||
} | |||||
new_handle = *handle; | |||||
} | |||||
entry->idx = 0; | |||||
entry->priv_flags = priv_flags; | |||||
entry->is_doorbell = true; | |||||
entry->run_delayed = (flags & VMCI_FLAG_DELAYED_CB) ? true : false; | |||||
entry->notify_cb = notify_cb; | |||||
entry->client_data = client_data; | |||||
atomic_store_int(&entry->active, 0); | |||||
vmci_create_event(&entry->destroy_event); | |||||
result = vmci_resource_add(&entry->resource, | |||||
VMCI_RESOURCE_TYPE_DOORBELL, new_handle, vmci_doorbell_free_cb, | |||||
entry); | |||||
if (result != VMCI_SUCCESS) { | |||||
VMCI_LOG_WARNING(LGPFX"Failed to add new resource " | |||||
"(handle=0x%x:0x%x).\n", new_handle.context, | |||||
new_handle.resource); | |||||
if (result == VMCI_ERROR_DUPLICATE_ENTRY) | |||||
result = VMCI_ERROR_ALREADY_EXISTS; | |||||
goto destroy; | |||||
} | |||||
vmci_doorbell_index_table_add(entry); | |||||
result = vmci_doorbell_link(new_handle, entry->is_doorbell, entry->idx); | |||||
if (VMCI_SUCCESS != result) | |||||
goto destroy_resource; | |||||
atomic_store_int(&entry->active, 1); | |||||
if (VMCI_HANDLE_INVALID(*handle)) | |||||
*handle = new_handle; | |||||
return (result); | |||||
destroy_resource: | |||||
vmci_doorbell_index_table_remove(entry); | |||||
vmci_resource_remove(new_handle, VMCI_RESOURCE_TYPE_DOORBELL); | |||||
destroy: | |||||
vmci_destroy_event(&entry->destroy_event); | |||||
free_mem: | |||||
vmci_free_kernel_mem(entry, sizeof(*entry)); | |||||
return (result); | |||||
} | |||||
/* | |||||
*------------------------------------------------------------------------------ | |||||
* | |||||
* vmci_doorbell_destroy -- | |||||
* | |||||
* Destroys a doorbell previously created with vmci_doorbell_create. This | |||||
* operation may block waiting for a callback to finish. | |||||
* | |||||
* Results: | |||||
* VMCI_SUCCESS on success, appropriate error code otherwise. | |||||
* | |||||
* Side effects: | |||||
* May block. | |||||
* | |||||
*------------------------------------------------------------------------------ | |||||
*/ | |||||
int | |||||
vmci_doorbell_destroy(struct vmci_handle handle) | |||||
{ | |||||
struct vmci_doorbell_entry *entry; | |||||
struct vmci_resource *resource; | |||||
int result; | |||||
if (VMCI_HANDLE_INVALID(handle)) | |||||
return (VMCI_ERROR_INVALID_ARGS); | |||||
resource = vmci_resource_get(handle, VMCI_RESOURCE_TYPE_DOORBELL); | |||||
if (resource == NULL) { | |||||
VMCI_LOG_DEBUG(LGPFX"Failed to destroy doorbell " | |||||
"(handle=0x%x:0x%x).\n", handle.context, handle.resource); | |||||
return (VMCI_ERROR_NOT_FOUND); | |||||
} | |||||
entry = RESOURCE_CONTAINER(resource, struct vmci_doorbell_entry, | |||||
resource); | |||||
vmci_doorbell_index_table_remove(entry); | |||||
result = vmci_doorbell_unlink(handle, entry->is_doorbell); | |||||
if (VMCI_SUCCESS != result) { | |||||
/* | |||||
* The only reason this should fail would be an inconsistency | |||||
* between guest and hypervisor state, where the guest believes | |||||
* it has an active registration whereas the hypervisor doesn't. | |||||
* One case where this may happen is if a doorbell is | |||||
* unregistered following a hibernation at a time where the | |||||
* doorbell state hasn't been restored on the hypervisor side | |||||
* yet. Since the handle has now been removed in the guest, | |||||
* we just print a warning and return success. | |||||
*/ | |||||
VMCI_LOG_DEBUG(LGPFX"Unlink of %s (handle=0x%x:0x%x) unknown " | |||||
"by hypervisor (error=%d).\n", | |||||
entry->is_doorbell ? "doorbell" : "queuepair", | |||||
handle.context, handle.resource, result); | |||||
} | |||||
/* | |||||
* Now remove the resource from the table. It might still be in use | |||||
* after this, in a callback or still on the delayed work queue. | |||||
*/ | |||||
vmci_resource_remove(handle, VMCI_RESOURCE_TYPE_DOORBELL); | |||||
/* | |||||
* We now wait on the destroyEvent and release the reference we got | |||||
* above. | |||||
*/ | |||||
vmci_wait_on_event(&entry->destroy_event, vmci_doorbell_release_cb, | |||||
entry); | |||||
/* | |||||
* We know that we are now the only reference to the above entry so | |||||
* can safely free it. | |||||
*/ | |||||
vmci_destroy_event(&entry->destroy_event); | |||||
vmci_free_kernel_mem(entry, sizeof(*entry)); | |||||
return (VMCI_SUCCESS); | |||||
} | |||||
/* | |||||
*------------------------------------------------------------------------------ | |||||
* | |||||
* vmci_doorbell_notify_as_guest -- | |||||
* | |||||
* Notify another guest or the host. We send a datagram down to the host | |||||
* via the hypervisor with the notification info. | |||||
* | |||||
* Results: | |||||
* VMCI_SUCCESS on success, appropriate error code otherwise. | |||||
* | |||||
* Side effects: | |||||
* May do a hypercall. | |||||
* | |||||
*------------------------------------------------------------------------------ | |||||
*/ | |||||
static int | |||||
vmci_doorbell_notify_as_guest(struct vmci_handle handle, | |||||
vmci_privilege_flags priv_flags) | |||||
{ | |||||
struct vmci_doorbell_notify_msg notify_msg; | |||||
notify_msg.hdr.dst = VMCI_MAKE_HANDLE(VMCI_HYPERVISOR_CONTEXT_ID, | |||||
VMCI_DOORBELL_NOTIFY); | |||||
notify_msg.hdr.src = VMCI_ANON_SRC_HANDLE; | |||||
notify_msg.hdr.payload_size = sizeof(notify_msg) - VMCI_DG_HEADERSIZE; | |||||
notify_msg.handle = handle; | |||||
return (vmci_send_datagram((struct vmci_datagram *)¬ify_msg)); | |||||
} | |||||
/* | |||||
*------------------------------------------------------------------------------ | |||||
* | |||||
* vmci_doorbell_notify -- | |||||
* | |||||
* Generates a notification on the doorbell identified by the handle. For | |||||
* host side generation of notifications, the caller can specify what the | |||||
* privilege of the calling side is. | |||||
* | |||||
* Results: | |||||
* VMCI_SUCCESS on success, appropriate error code otherwise. | |||||
* | |||||
* Side effects: | |||||
* May do a hypercall. | |||||
* | |||||
*------------------------------------------------------------------------------ | |||||
*/ | |||||
int | |||||
vmci_doorbell_notify(struct vmci_handle dst, vmci_privilege_flags priv_flags) | |||||
{ | |||||
struct vmci_handle src; | |||||
if (VMCI_HANDLE_INVALID(dst) || | |||||
(priv_flags & ~VMCI_PRIVILEGE_ALL_FLAGS)) | |||||
return (VMCI_ERROR_INVALID_ARGS); | |||||
src = VMCI_INVALID_HANDLE; | |||||
return (vmci_doorbell_notify_as_guest(dst, priv_flags)); | |||||
} | |||||
/* | |||||
*------------------------------------------------------------------------------ | |||||
* | |||||
* vmci_doorbell_delayed_dispatch_cb -- | |||||
* | |||||
* Calls the specified callback in a delayed context. | |||||
* | |||||
* Results: | |||||
* None. | |||||
* | |||||
* Side effects: | |||||
* None. | |||||
* | |||||
*------------------------------------------------------------------------------ | |||||
*/ | |||||
static void | |||||
vmci_doorbell_delayed_dispatch_cb(void *data) | |||||
{ | |||||
struct vmci_doorbell_entry *entry = (struct vmci_doorbell_entry *)data; | |||||
ASSERT(data); | |||||
entry->notify_cb(entry->client_data); | |||||
vmci_resource_release(&entry->resource); | |||||
} | |||||
/* | |||||
*------------------------------------------------------------------------------ | |||||
* | |||||
* vmci_doorbell_sync -- | |||||
* | |||||
* Use this as a synchronization point when setting globals, for example, | |||||
* during device shutdown. | |||||
* | |||||
* Results: | |||||
* None. | |||||
* | |||||
* Side effects: | |||||
* None. | |||||
* | |||||
*------------------------------------------------------------------------------ | |||||
*/ | |||||
void | |||||
vmci_doorbell_sync(void) | |||||
{ | |||||
vmci_grab_lock_bh(&vmci_doorbell_it.lock); | |||||
vmci_release_lock_bh(&vmci_doorbell_it.lock); | |||||
vmci_resource_sync(); | |||||
} | |||||
/* | |||||
*------------------------------------------------------------------------------ | |||||
* | |||||
* vmci_register_notification_bitmap -- | |||||
* | |||||
* Register the notification bitmap with the host. | |||||
* | |||||
* Results: | |||||
* true if the bitmap is registered successfully with the device, false | |||||
* otherwise. | |||||
* | |||||
* Side effects: | |||||
* None. | |||||
* | |||||
*------------------------------------------------------------------------------ | |||||
*/ | |||||
bool | |||||
vmci_register_notification_bitmap(PPN bitmap_ppn) | |||||
{ | |||||
struct vmci_notify_bitmap_set_msg bitmap_set_msg; | |||||
int result; | |||||
/* | |||||
* Do not ASSERT() on the guest device here. This function can get | |||||
* called during device initialization, so the ASSERT() will fail even | |||||
* though the device is (almost) up. | |||||
*/ | |||||
bitmap_set_msg.hdr.dst = VMCI_MAKE_HANDLE(VMCI_HYPERVISOR_CONTEXT_ID, | |||||
VMCI_SET_NOTIFY_BITMAP); | |||||
bitmap_set_msg.hdr.src = VMCI_ANON_SRC_HANDLE; | |||||
bitmap_set_msg.hdr.payload_size = | |||||
sizeof(bitmap_set_msg) - VMCI_DG_HEADERSIZE; | |||||
bitmap_set_msg.bitmap_ppn = bitmap_ppn; | |||||
result = vmci_send_datagram((struct vmci_datagram *)&bitmap_set_msg); | |||||
if (result != VMCI_SUCCESS) { | |||||
VMCI_LOG_DEBUG(LGPFX"Failed to register (PPN=%u) as " | |||||
"notification bitmap (error=%d).\n", | |||||
bitmap_ppn, result); | |||||
return (false); | |||||
} | |||||
return (true); | |||||
} | |||||
/* | |||||
*------------------------------------------------------------------------------ | |||||
* | |||||
* vmci_doorbell_fire_entries -- | |||||
* | |||||
* Executes or schedules the handlers for a given notify index. | |||||
* | |||||
* Result: | |||||
* Notification hash entry if found. NULL otherwise. | |||||
* | |||||
* Side effects: | |||||
* Whatever the side effects of the handlers are. | |||||
* | |||||
*------------------------------------------------------------------------------ | |||||
*/ | |||||
static void | |||||
vmci_doorbell_fire_entries(uint32_t notify_idx) | |||||
{ | |||||
struct vmci_doorbell_entry *iter; | |||||
uint32_t bucket = VMCI_DOORBELL_HASH(notify_idx); | |||||
vmci_grab_lock_bh(&vmci_doorbell_it.lock); | |||||
vmci_list_scan(iter, &vmci_doorbell_it.entries[bucket], idx_list_item) { | |||||
if (iter->idx == notify_idx && | |||||
atomic_load_int(&iter->active) == 1) { | |||||
ASSERT(iter->notify_cb); | |||||
if (iter->run_delayed) { | |||||
int err; | |||||
vmci_resource_hold(&iter->resource); | |||||
err = vmci_schedule_delayed_work( | |||||
vmci_doorbell_delayed_dispatch_cb, iter); | |||||
if (err != VMCI_SUCCESS) { | |||||
vmci_resource_release(&iter->resource); | |||||
goto out; | |||||
} | |||||
} else | |||||
iter->notify_cb(iter->client_data); | |||||
} | |||||
} | |||||
out: | |||||
vmci_release_lock_bh(&vmci_doorbell_it.lock); | |||||
} | |||||
/* | |||||
*------------------------------------------------------------------------------ | |||||
* | |||||
* vmci_scan_notification_bitmap -- | |||||
* | |||||
* Scans the notification bitmap, collects pending notifications, resets | |||||
* the bitmap and invokes appropriate callbacks. | |||||
* | |||||
* Results: | |||||
* None. | |||||
* | |||||
* Side effects: | |||||
* May schedule tasks, allocate memory and run callbacks. | |||||
* | |||||
*------------------------------------------------------------------------------ | |||||
*/ | |||||
void | |||||
vmci_scan_notification_bitmap(uint8_t *bitmap) | |||||
{ | |||||
uint32_t idx; | |||||
ASSERT(bitmap); | |||||
for (idx = 0; idx < max_notify_idx; idx++) { | |||||
if (bitmap[idx] & 0x1) { | |||||
bitmap[idx] &= ~1; | |||||
vmci_doorbell_fire_entries(idx); | |||||
} | |||||
} | |||||
} |