/*- * Copyright (c) 2018 VMware, Inc. * * SPDX-License-Identifier: (BSD-2-Clause OR GPL-2.0) */ /* This file implements VMCI Event code. */ #include __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; if (!vmci_initialized_lock(&subscriber_lock)) return NULL; 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); }