/*
* VMware VMCI Driver
*/
#include <linux/vmw_vmci_defs.h>
#include <linux/vmw_vmci_api.h>
#include <linux/highmem.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include "vmci_queue_pair.h"
#include "vmci_datagram.h"
#include "vmci_doorbell.h"
#include "vmci_context.h"
#include "vmci_driver.h"
#include "vmci_event.h"
/*
* List of current VMCI contexts. Contexts can be added by
* vmci_ctx_create() and removed via vmci_ctx_destroy().
* These, along with context lookup, are protected by the
* list structure's lock.
*/
static struct {
struct list_head head;
spinlock_t lock; /* Spinlock for context list operations */
} ctx_list = {
.head = LIST_HEAD_INIT(ctx_list.head),
.lock = __SPIN_LOCK_UNLOCKED(ctx_list.lock),
};
/* Used by contexts that did not set up notify flag pointers */
static bool ctx_dummy_notify;
static void ctx_signal_notify(struct vmci_ctx *context)
{
*context->notify = true;
}
static void ctx_clear_notify(struct vmci_ctx *context)
{
*context->notify = false;
}
/*
* If nothing requires the attention of the guest, clears both
* notify flag and call.
*/
static void ctx_clear_notify_call(struct vmci_ctx *context)
{
if (context->pending_datagrams == 0 &&
vmci_handle_arr_get_size(context->pending_doorbell_array) == 0)
ctx_clear_notify(context);
}
/*
* Sets the context's notify flag iff datagrams are pending for this
* context. Called from vmci_setup_notify().
*/
void vmci_ctx_check_signal_notify(struct vmci_ctx *context)
{
spin_lock(&context->lock);
if (context->pending_datagrams)
ctx_signal_notify(context);
spin_unlock(&context->lock);
}
/*
* Allocates and initializes a VMCI context.
*/
struct vmci_ctx *vmci_ctx_create(u32 cid, u32 priv_flags,
uintptr_t event_hnd,
int user_version,
const struct cred *cred)
{
struct vmci_ctx *context;
int error;
if (cid == VMCI_INVALID_ID) {
pr_devel("Invalid context ID for VMCI context\n");
error = -EINVAL;
goto err_out;
}
if (priv_flags & ~VMCI_PRIVILEGE_ALL_FLAGS) {
pr_devel("Invalid flag (flags=0x%x) for VMCI context\n",
priv_flags);
error = -EINVAL;
goto err_out;
}
if (user_version == 0) {
pr_devel("Invalid suer_version %d\n", user_version);
error = -EINVAL;
goto err_out;
}
context = kzalloc(sizeof(*context), GFP_KERNEL);
if (!context) {
pr_warn("Failed to allocate memory for VMCI context\n");
error = -EINVAL;
goto err_out;
}
kref_init(&context->kref);
spin_lock_init(&context->lock);
INIT_LIST_HEAD(&context->list_item);
INIT_LIST_HEAD(&context->datagram_queue);
INIT_LIST_HEAD(&context->notifier_list);
/* Initialize host-specific VMCI context. */
init_waitqueue_head(&context->host_context.wait_queue);
context->queue_pair_array = vmci_handle_arr_create(0);
if (!context->queue_pair_array) {
error = -ENOMEM;
goto err_free_ctx;
}
context->doorbell_array = vmci_handle_arr_create(0);
if (!context->doorbell_array) {
error = -ENOMEM;
goto err_free_qp_array;
}
context->pending_doorbell_array = vmci_handle_arr_create(0);
if (!context->pending_doorbell_array) {
error = -ENOMEM;
goto err_free_db_array;
}
context->user_version = user_version;
context->priv_flags = priv_flags;
if (cred)
context->cred = get_cred(cred);
context->notify = &ctx_dummy_notify;
context->notify_page = NULL;
/*
* If we collide with an existing context we generate a new
* and use it instead. The VMX will determine if regeneration
* is okay. Since there isn't 4B - 16 VMs running on a given
* host, the below loop will terminate.
*/
spin_lock(&ctx_list.lock);
while (vmci_ctx_exists(cid)) {
/* We reserve the lowest 16 ids for fixed contexts. */
cid = max(cid, VMCI_RESERVED_CID_LIMIT - 1) + 1;
if (cid == VMCI_INVALID_ID)
cid = VMCI_RESERVED_CID_LIMIT;
}
context->cid = cid;
list_add_tail_rcu(&context->list_item, &ctx_list.head);
spin_unlock(&ctx_list.lock);
return context;
err_free_db_array:
vmci_handle_arr_destroy(context->doorbell_array);
err_free_qp_array:
vmci_handle_arr_destroy(context->queue_pair_array);
err_free_ctx:
kfree(context);
err_out:
return ERR_PTR(error);
}
/*
* Destroy VMCI context.
*/
void vmci_ctx_destroy(struct vmci_ctx *context)
{
spin_lock(&ctx_list.lock);
list_del_rcu(&context->list_item);
spin_unlock(&ctx_list.lock);
synchronize_rcu();
vmci_ctx_put(context);
}
/*
* Fire notification for all contexts interested in given cid.
*/
static int ctx_fire_notification(u32 context_id, u32 priv_flags)
{
u32 i, array_size;
struct vmci_ctx *sub_ctx;
struct vmci_handle_arr *subscriber_array;
struct vmci_handle context_handle =
vmci_make_handle(context_id, VMCI_EVENT_HANDLER);
/*
* We create an array to hold the subscribers we find when
* scanning through all contexts.
*/
subscriber_array = vmci_handle_arr_create(0);
if (subscriber_array == NULL)
return VMCI_ERROR_NO_MEM;
/*
* Scan all contexts to find who is interested in being
* notified about given contextID.
*/
rcu_read_lock();
list_for_each_entry_rcu(sub_ctx, &ctx_list.head, list_item) {
struct vmci_handle_list *node;
/*
* We only deliver notifications of the removal of
* contexts, if the two contexts are allowed to
* interact.
*/
if (vmci_deny_interaction(priv_flags, sub_ctx->priv_flags))
continue;
list_for_each_entry_rcu(node, &sub_ctx->notifier_list, node) {
if (!vmci_handle_is_equal(node->handle, context_handle))
continue;
vmci_handle_arr_append_entry(&subscriber_array,
vmci_make_handle(sub_ctx->cid,
VMCI_EVENT_HANDLER));
}
}
rcu_read_unlock();
/* Fire event to all subscribers. */
array_size = vmci_handle_arr_get_size(subscriber_array);
for (i = 0; i < array_size; i++) {
int result;
struct vmci_event_ctx ev;
ev.msg.hdr.dst = vmci_handle_arr_get_entry(subscriber_array, i);
ev.msg.hdr.src = vmci_make_handle(VMCI_HYPERVISOR_CONTEXT_ID,
VMCI_CONTEXT_RESOURCE_ID);
ev.msg.hdr.payload_size = sizeof(ev) - sizeof(ev.msg.hdr);
ev.msg.event_data.event = VMCI_EVENT_CTX_REMOVED;
ev.payload.context_id = context_id;
result = vmci_datagram_dispatch(VMCI_HYPERVISOR_CONTEXT_ID,
&ev.msg.hdr, false);
if (result < VMCI_SUCCESS) {
pr_devel("Failed to enqueue event datagram (type=%d) for context (ID=0x%x)\n",
ev.msg.event_data.event,
ev.msg.hdr.dst.context);
/* We continue to enqueue on next subscriber. */
}
}
vmci_handle_arr_destroy(subscriber_array);
return VMCI_SUCCESS;
}
/*
* Returns the current number of pending datagrams. The call may
* also serve as a synchronization point for the datagram queue,
* as no enqueue operations can occur concurrently.
*/
int vmci_ctx_pending_datagrams(u32 cid, u32 *pending)
{
struct vmci_ctx *context;
context = vmci_ctx_get(cid);
if (context == NULL)
return VMCI_ERROR_INVALID_ARGS;
spin_lock(&context->lock);
if (pending)
*pending = context->pending_datagrams;
spin_unlock(&context->lock);
vmci_ctx_put(context);
return VMCI_SUCCESS;
}
/*
* Queues a VMCI datagram for the appropriate target VM context.
*/
int vmci_ctx_enqueue_datagram(u32 cid, struct vmci_datagram *dg)
{
struct vmci_datagram_queue_entry *dq_entry;
struct vmci_ctx *context;
struct vmci_handle dg_src;
size_t vmci_dg_size;
vmci_dg_size = VMCI_DG_SIZE(dg);
if (vmci_dg_size > VMCI_MAX_DG_SIZE) {
pr_devel("Datagram too large (bytes=%Zu)\n", vmci_dg_size);
return VMCI_ERROR_INVALID_ARGS;
}
/* Get the target VM's VMCI context. */
context = vmci_ctx_get(cid);
if (!context) {
pr_devel("Invalid context (ID=0x%x)\n", cid);
return VMCI_ERROR_INVALID_ARGS;
}
/* Allocate guest call entry and add it to the target VM's queue. */
dq_entry = kmalloc(sizeof(*dq_entry), GFP_KERNEL);
if (dq_entry == NULL) {
pr_warn("Failed to allocate memory for datagram\n");
vmci_ctx_put(context);
return VMCI_ERROR_NO_MEM;
}
dq_entry->dg = dg;
dq_entry->dg_size = vmci_dg_size;
dg_src = dg->src;
I