Changeset View
Changeset View
Standalone View
Standalone View
sys/dev/vmware/vmci/vmci_event.c
- This file was added.
Property | Old Value | New Value |
---|---|---|
svn:eol-style | null | native \ No newline at end of property |
svn:executable | null | * \ 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 VMCI Event code. */ | |||||
#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); | |||||
} |