#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)
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];
};
static struct vmci_doorbell_index_table vmci_doorbell_it;
static uint32_t max_notify_idx;
static uint32_t notify_idx_count;
static uint32_t last_notify_idx_reserved;
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);
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"));
}
void
vmci_doorbell_exit(void)
{
vmci_cleanup_lock(&vmci_doorbell_it.lock);
}
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);
}
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);
}
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) {
return (VMCI_ERROR_INVALID_ARGS);
} else
*priv_flags = VMCI_NO_PRIVILEGE_FLAGS;
return (VMCI_SUCCESS);
}
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);
}
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);
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);
}
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) {
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);
}
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));
}
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));
}
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);
}
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) {
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);
}
vmci_resource_remove(handle, VMCI_RESOURCE_TYPE_DOORBELL);
vmci_wait_on_event(&entry->destroy_event, vmci_doorbell_release_cb,
entry);
vmci_destroy_event(&entry->destroy_event);
vmci_free_kernel_mem(entry, sizeof(*entry));
return (VMCI_SUCCESS);
}
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));
}
int
vmci_doorbell_notify(struct vmci_handle dst, vmci_privilege_flags priv_flags)
{
if (VMCI_HANDLE_INVALID(dst) ||
(priv_flags & ~VMCI_PRIVILEGE_ALL_FLAGS))
return (VMCI_ERROR_INVALID_ARGS);
return (vmci_doorbell_notify_as_guest(dst, priv_flags));
}
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);
}
void
vmci_doorbell_sync(void)
{
vmci_grab_lock_bh(&vmci_doorbell_it.lock);
vmci_release_lock_bh(&vmci_doorbell_it.lock);
vmci_resource_sync();
}
bool
vmci_register_notification_bitmap(PPN bitmap_ppn)
{
struct vmci_notify_bitmap_set_msg bitmap_set_msg;
int result;
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);
}
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);
}
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);
}
}
}