root/sys/dev/vmware/vmci/vmci_event.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>
#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);
}