freebsd-dev/sys/dev/vmware/vmci/vmci_event.c
2020-09-01 21:30:01 +00:00

696 lines
16 KiB
C

/*-
* Copyright (c) 2018 VMware, Inc.
*
* SPDX-License-Identifier: (BSD-2-Clause OR GPL-2.0)
*/
/* This file implements VMCI Event code. */
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include "vmci.h"
#include "vmci_driver.h"
#include "vmci_event.h"
#include "vmci_kernel_api.h"
#include "vmci_kernel_defs.h"
#include "vmci_kernel_if.h"
#define LGPFX "vmci_event: "
#define EVENT_MAGIC 0xEABE0000
struct vmci_subscription {
vmci_id id;
int ref_count;
bool run_delayed;
vmci_event destroy_event;
vmci_event_type event;
vmci_event_cb callback;
void *callback_data;
vmci_list_item(vmci_subscription) subscriber_list_item;
};
static struct vmci_subscription *vmci_event_find(vmci_id sub_id);
static int vmci_event_deliver(struct vmci_event_msg *event_msg);
static int vmci_event_register_subscription(struct vmci_subscription *sub,
vmci_event_type event, uint32_t flags,
vmci_event_cb callback, void *callback_data);
static struct vmci_subscription *vmci_event_unregister_subscription(
vmci_id sub_id);
static vmci_list(vmci_subscription) subscriber_array[VMCI_EVENT_MAX];
static vmci_lock subscriber_lock;
struct vmci_delayed_event_info {
struct vmci_subscription *sub;
uint8_t event_payload[sizeof(struct vmci_event_data_max)];
};
struct vmci_event_ref {
struct vmci_subscription *sub;
vmci_list_item(vmci_event_ref) list_item;
};
/*
*------------------------------------------------------------------------------
*
* vmci_event_init --
*
* General init code.
*
* Results:
* VMCI_SUCCESS on success, appropriate error code otherwise.
*
* Side effects:
* None.
*
*------------------------------------------------------------------------------
*/
int
vmci_event_init(void)
{
int i;
for (i = 0; i < VMCI_EVENT_MAX; i++)
vmci_list_init(&subscriber_array[i]);
return (vmci_init_lock(&subscriber_lock, "VMCI Event subscriber lock"));
}
/*
*------------------------------------------------------------------------------
*
* vmci_event_exit --
*
* General exit code.
*
* Results:
* None.
*
* Side effects:
* None.
*
*------------------------------------------------------------------------------
*/
void
vmci_event_exit(void)
{
struct vmci_subscription *iter, *iter_2;
vmci_event_type e;
/* We free all memory at exit. */
for (e = 0; e < VMCI_EVENT_MAX; e++) {
vmci_list_scan_safe(iter, &subscriber_array[e],
subscriber_list_item, iter_2) {
/*
* We should never get here because all events should
* have been unregistered before we try to unload the
* driver module. Also, delayed callbacks could still
* be firing so this cleanup would not be safe. Still
* it is better to free the memory than not ... so we
* leave this code in just in case....
*/
ASSERT(false);
vmci_free_kernel_mem(iter, sizeof(*iter));
}
}
vmci_cleanup_lock(&subscriber_lock);
}
/*
*------------------------------------------------------------------------------
*
* vmci_event_sync --
*
* Use this as a synchronization point when setting globals, for example,
* during device shutdown.
*
* Results:
* true.
*
* Side effects:
* None.
*
*------------------------------------------------------------------------------
*/
void
vmci_event_sync(void)
{
vmci_grab_lock_bh(&subscriber_lock);
vmci_release_lock_bh(&subscriber_lock);
}
/*
*------------------------------------------------------------------------------
*
* vmci_event_check_host_capabilities --
*
* Verify that the host supports the hypercalls we need. If it does not,
* try to find fallback hypercalls and use those instead.
*
* Results:
* true if required hypercalls (or fallback hypercalls) are
* supported by the host, false otherwise.
*
* Side effects:
* None.
*
*------------------------------------------------------------------------------
*/
bool
vmci_event_check_host_capabilities(void)
{
/* vmci_event does not require any hypercalls. */
return (true);
}
/*
*------------------------------------------------------------------------------
*
* vmci_event_get --
*
* Gets a reference to the given struct vmci_subscription.
*
* Results:
* None.
*
* Side effects:
* None.
*
*------------------------------------------------------------------------------
*/
static void
vmci_event_get(struct vmci_subscription *entry)
{
ASSERT(entry);
entry->ref_count++;
}
/*
*------------------------------------------------------------------------------
*
* vmci_event_release --
*
* Releases the given struct vmci_subscription.
*
* Results:
* None.
*
* Side effects:
* Fires the destroy event if the reference count has gone to zero.
*
*------------------------------------------------------------------------------
*/
static void
vmci_event_release(struct vmci_subscription *entry)
{
ASSERT(entry);
ASSERT(entry->ref_count > 0);
entry->ref_count--;
if (entry->ref_count == 0)
vmci_signal_event(&entry->destroy_event);
}
/*
*------------------------------------------------------------------------------
*
* event_release_cb --
*
* Callback to release the event entry reference. It is called by the
* vmci_wait_on_event function before it blocks.
*
* Result:
* None.
*
* Side effects:
* None.
*
*------------------------------------------------------------------------------
*/
static int
event_release_cb(void *client_data)
{
struct vmci_subscription *sub = (struct vmci_subscription *)client_data;
ASSERT(sub);
vmci_grab_lock_bh(&subscriber_lock);
vmci_event_release(sub);
vmci_release_lock_bh(&subscriber_lock);
return (0);
}
/*
*------------------------------------------------------------------------------
*
* vmci_event_find --
*
* Find entry. Assumes lock is held.
*
* Results:
* Entry if found, NULL if not.
*
* Side effects:
* Increments the struct vmci_subscription refcount if an entry is found.
*
*------------------------------------------------------------------------------
*/
static struct vmci_subscription *
vmci_event_find(vmci_id sub_id)
{
struct vmci_subscription *iter;
vmci_event_type e;
for (e = 0; e < VMCI_EVENT_MAX; e++) {
vmci_list_scan(iter, &subscriber_array[e],
subscriber_list_item) {
if (iter->id == sub_id) {
vmci_event_get(iter);
return (iter);
}
}
}
return (NULL);
}
/*
*------------------------------------------------------------------------------
*
* vmci_event_delayed_dispatch_cb --
*
* Calls the specified callback in a delayed context.
*
* Results:
* None.
*
* Side effects:
* None.
*
*------------------------------------------------------------------------------
*/
static void
vmci_event_delayed_dispatch_cb(void *data)
{
struct vmci_delayed_event_info *event_info;
struct vmci_subscription *sub;
struct vmci_event_data *ed;
event_info = (struct vmci_delayed_event_info *)data;
ASSERT(event_info);
ASSERT(event_info->sub);
sub = event_info->sub;
ed = (struct vmci_event_data *)event_info->event_payload;
sub->callback(sub->id, ed, sub->callback_data);
vmci_grab_lock_bh(&subscriber_lock);
vmci_event_release(sub);
vmci_release_lock_bh(&subscriber_lock);
vmci_free_kernel_mem(event_info, sizeof(*event_info));
}
/*
*------------------------------------------------------------------------------
*
* vmci_event_deliver --
*
* Actually delivers the events to the subscribers.
*
* Results:
* None.
*
* Side effects:
* The callback function for each subscriber is invoked.
*
*------------------------------------------------------------------------------
*/
static int
vmci_event_deliver(struct vmci_event_msg *event_msg)
{
struct vmci_subscription *iter;
int err = VMCI_SUCCESS;
vmci_list(vmci_event_ref) no_delay_list;
vmci_list_init(&no_delay_list);
ASSERT(event_msg);
vmci_grab_lock_bh(&subscriber_lock);
vmci_list_scan(iter, &subscriber_array[event_msg->event_data.event],
subscriber_list_item) {
if (iter->run_delayed) {
struct vmci_delayed_event_info *event_info;
if ((event_info =
vmci_alloc_kernel_mem(sizeof(*event_info),
VMCI_MEMORY_ATOMIC)) == NULL) {
err = VMCI_ERROR_NO_MEM;
goto out;
}
vmci_event_get(iter);
memset(event_info, 0, sizeof(*event_info));
memcpy(event_info->event_payload,
VMCI_DG_PAYLOAD(event_msg),
(size_t)event_msg->hdr.payload_size);
event_info->sub = iter;
err =
vmci_schedule_delayed_work(
vmci_event_delayed_dispatch_cb, event_info);
if (err != VMCI_SUCCESS) {
vmci_event_release(iter);
vmci_free_kernel_mem(
event_info, sizeof(*event_info));
goto out;
}
} else {
struct vmci_event_ref *event_ref;
/*
* We construct a local list of subscribers and release
* subscriber_lock before invoking the callbacks. This
* is similar to delayed callbacks, but callbacks are
* invoked right away here.
*/
if ((event_ref = vmci_alloc_kernel_mem(
sizeof(*event_ref), VMCI_MEMORY_ATOMIC)) == NULL) {
err = VMCI_ERROR_NO_MEM;
goto out;
}
vmci_event_get(iter);
event_ref->sub = iter;
vmci_list_insert(&no_delay_list, event_ref, list_item);
}
}
out:
vmci_release_lock_bh(&subscriber_lock);
if (!vmci_list_empty(&no_delay_list)) {
struct vmci_event_data *ed;
struct vmci_event_ref *iter;
struct vmci_event_ref *iter_2;
vmci_list_scan_safe(iter, &no_delay_list, list_item, iter_2) {
struct vmci_subscription *cur;
uint8_t event_payload[sizeof(
struct vmci_event_data_max)];
cur = iter->sub;
/*
* We set event data before each callback to ensure
* isolation.
*/
memset(event_payload, 0, sizeof(event_payload));
memcpy(event_payload, VMCI_DG_PAYLOAD(event_msg),
(size_t)event_msg->hdr.payload_size);
ed = (struct vmci_event_data *)event_payload;
cur->callback(cur->id, ed, cur->callback_data);
vmci_grab_lock_bh(&subscriber_lock);
vmci_event_release(cur);
vmci_release_lock_bh(&subscriber_lock);
vmci_free_kernel_mem(iter, sizeof(*iter));
}
}
return (err);
}
/*
*------------------------------------------------------------------------------
*
* vmci_event_dispatch --
*
* Dispatcher for the VMCI_EVENT_RECEIVE datagrams. Calls all
* subscribers for given event.
*
* Results:
* VMCI_SUCCESS on success, error code otherwise.
*
* Side effects:
* None.
*
*------------------------------------------------------------------------------
*/
int
vmci_event_dispatch(struct vmci_datagram *msg)
{
struct vmci_event_msg *event_msg = (struct vmci_event_msg *)msg;
ASSERT(msg &&
msg->src.context == VMCI_HYPERVISOR_CONTEXT_ID &&
msg->dst.resource == VMCI_EVENT_HANDLER);
if (msg->payload_size < sizeof(vmci_event_type) ||
msg->payload_size > sizeof(struct vmci_event_data_max))
return (VMCI_ERROR_INVALID_ARGS);
if (!VMCI_EVENT_VALID(event_msg->event_data.event))
return (VMCI_ERROR_EVENT_UNKNOWN);
vmci_event_deliver(event_msg);
return (VMCI_SUCCESS);
}
/*
*------------------------------------------------------------------------------
*
* vmci_event_register_subscription --
*
* Initialize and add subscription to subscriber list.
*
* Results:
* VMCI_SUCCESS on success, error code otherwise.
*
* Side effects:
* None.
*
*------------------------------------------------------------------------------
*/
static int
vmci_event_register_subscription(struct vmci_subscription *sub,
vmci_event_type event, uint32_t flags, vmci_event_cb callback,
void *callback_data)
{
#define VMCI_EVENT_MAX_ATTEMPTS 10
static vmci_id subscription_id = 0;
int result;
uint32_t attempts = 0;
bool success;
ASSERT(sub);
if (!VMCI_EVENT_VALID(event) || callback == NULL) {
VMCI_LOG_DEBUG(LGPFX"Failed to subscribe to event"
" (type=%d) (callback=%p) (data=%p).\n",
event, callback, callback_data);
return (VMCI_ERROR_INVALID_ARGS);
}
if (!vmci_can_schedule_delayed_work()) {
/*
* If the platform doesn't support delayed work callbacks then
* don't allow registration for them.
*/
if (flags & VMCI_FLAG_EVENT_DELAYED_CB)
return (VMCI_ERROR_INVALID_ARGS);
sub->run_delayed = false;
} else {
/*
* The platform supports delayed work callbacks. Honor the
* requested flags
*/
sub->run_delayed = (flags & VMCI_FLAG_EVENT_DELAYED_CB) ?
true : false;
}
sub->ref_count = 1;
sub->event = event;
sub->callback = callback;
sub->callback_data = callback_data;
vmci_grab_lock_bh(&subscriber_lock);
for (success = false, attempts = 0;
success == false && attempts < VMCI_EVENT_MAX_ATTEMPTS;
attempts++) {
struct vmci_subscription *existing_sub = NULL;
/*
* We try to get an id a couple of time before claiming we are
* out of resources.
*/
sub->id = ++subscription_id;
/* Test for duplicate id. */
existing_sub = vmci_event_find(sub->id);
if (existing_sub == NULL) {
/* We succeeded if we didn't find a duplicate. */
success = true;
} else
vmci_event_release(existing_sub);
}
if (success) {
vmci_create_event(&sub->destroy_event);
vmci_list_insert(&subscriber_array[event], sub,
subscriber_list_item);
result = VMCI_SUCCESS;
} else
result = VMCI_ERROR_NO_RESOURCES;
vmci_release_lock_bh(&subscriber_lock);
return (result);
#undef VMCI_EVENT_MAX_ATTEMPTS
}
/*
*------------------------------------------------------------------------------
*
* vmci_event_unregister_subscription --
*
* Remove subscription from subscriber list.
*
* Results:
* struct vmci_subscription when found, NULL otherwise.
*
* Side effects:
* None.
*
*------------------------------------------------------------------------------
*/
static struct vmci_subscription *
vmci_event_unregister_subscription(vmci_id sub_id)
{
struct vmci_subscription *s;
vmci_grab_lock_bh(&subscriber_lock);
s = vmci_event_find(sub_id);
if (s != NULL) {
vmci_event_release(s);
vmci_list_remove(s, subscriber_list_item);
}
vmci_release_lock_bh(&subscriber_lock);
if (s != NULL) {
vmci_wait_on_event(&s->destroy_event, event_release_cb, s);
vmci_destroy_event(&s->destroy_event);
}
return (s);
}
/*
*------------------------------------------------------------------------------
*
* vmci_event_subscribe --
*
* Subscribe to given event. The callback specified can be fired in
* different contexts depending on what flag is specified while registering.
* If flags contains VMCI_FLAG_EVENT_NONE then the callback is fired with
* the subscriber lock held (and BH context on the guest). If flags contain
* VMCI_FLAG_EVENT_DELAYED_CB then the callback is fired with no locks held
* in thread context. This is useful because other vmci_event functions can
* be called, but it also increases the chances that an event will be
* dropped.
*
* Results:
* VMCI_SUCCESS on success, error code otherwise.
*
* Side effects:
* None.
*
*------------------------------------------------------------------------------
*/
int
vmci_event_subscribe(vmci_event_type event, vmci_event_cb callback,
void *callback_data, vmci_id *subscription_id)
{
int retval;
uint32_t flags = VMCI_FLAG_EVENT_NONE;
struct vmci_subscription *s = NULL;
if (subscription_id == NULL) {
VMCI_LOG_DEBUG(LGPFX"Invalid subscription (NULL).\n");
return (VMCI_ERROR_INVALID_ARGS);
}
s = vmci_alloc_kernel_mem(sizeof(*s), VMCI_MEMORY_NORMAL);
if (s == NULL)
return (VMCI_ERROR_NO_MEM);
retval = vmci_event_register_subscription(s, event, flags,
callback, callback_data);
if (retval < VMCI_SUCCESS) {
vmci_free_kernel_mem(s, sizeof(*s));
return (retval);
}
*subscription_id = s->id;
return (retval);
}
/*
*------------------------------------------------------------------------------
*
* vmci_event_unsubscribe --
*
* Unsubscribe to given event. Removes it from list and frees it.
* Will return callback_data if requested by caller.
*
* Results:
* VMCI_SUCCESS on success, error code otherwise.
*
* Side effects:
* None.
*
*------------------------------------------------------------------------------
*/
int
vmci_event_unsubscribe(vmci_id sub_id)
{
struct vmci_subscription *s;
/*
* Return subscription. At this point we know noone else is accessing
* the subscription so we can free it.
*/
s = vmci_event_unregister_subscription(sub_id);
if (s == NULL)
return (VMCI_ERROR_NOT_FOUND);
vmci_free_kernel_mem(s, sizeof(*s));
return (VMCI_SUCCESS);
}