mirror of
https://github.com/AetherDroid/android_kernel_samsung_on5xelte.git
synced 2025-09-09 01:28:05 -04:00
Fixed MTP to work with TWRP
This commit is contained in:
commit
f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions
16
drivers/misc/vmw_vmci/Kconfig
Normal file
16
drivers/misc/vmw_vmci/Kconfig
Normal file
|
@ -0,0 +1,16 @@
|
|||
#
|
||||
# VMware VMCI device
|
||||
#
|
||||
|
||||
config VMWARE_VMCI
|
||||
tristate "VMware VMCI Driver"
|
||||
depends on X86 && PCI
|
||||
help
|
||||
This is VMware's Virtual Machine Communication Interface. It enables
|
||||
high-speed communication between host and guest in a virtual
|
||||
environment via the VMCI virtual device.
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called vmw_vmci.
|
4
drivers/misc/vmw_vmci/Makefile
Normal file
4
drivers/misc/vmw_vmci/Makefile
Normal file
|
@ -0,0 +1,4 @@
|
|||
obj-$(CONFIG_VMWARE_VMCI) += vmw_vmci.o
|
||||
vmw_vmci-y += vmci_context.o vmci_datagram.o vmci_doorbell.o \
|
||||
vmci_driver.o vmci_event.o vmci_guest.o vmci_handle_array.o \
|
||||
vmci_host.o vmci_queue_pair.o vmci_resource.o vmci_route.o
|
1214
drivers/misc/vmw_vmci/vmci_context.c
Normal file
1214
drivers/misc/vmw_vmci/vmci_context.c
Normal file
File diff suppressed because it is too large
Load diff
182
drivers/misc/vmw_vmci/vmci_context.h
Normal file
182
drivers/misc/vmw_vmci/vmci_context.h
Normal file
|
@ -0,0 +1,182 @@
|
|||
/*
|
||||
* VMware VMCI driver (vmciContext.h)
|
||||
*
|
||||
* Copyright (C) 2012 VMware, Inc. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation version 2 and no later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* for more details.
|
||||
*/
|
||||
|
||||
#ifndef _VMCI_CONTEXT_H_
|
||||
#define _VMCI_CONTEXT_H_
|
||||
|
||||
#include <linux/vmw_vmci_defs.h>
|
||||
#include <linux/atomic.h>
|
||||
#include <linux/kref.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/wait.h>
|
||||
|
||||
#include "vmci_handle_array.h"
|
||||
#include "vmci_datagram.h"
|
||||
|
||||
/* Used to determine what checkpoint state to get and set. */
|
||||
enum {
|
||||
VMCI_NOTIFICATION_CPT_STATE = 1,
|
||||
VMCI_WELLKNOWN_CPT_STATE = 2,
|
||||
VMCI_DG_OUT_STATE = 3,
|
||||
VMCI_DG_IN_STATE = 4,
|
||||
VMCI_DG_IN_SIZE_STATE = 5,
|
||||
VMCI_DOORBELL_CPT_STATE = 6,
|
||||
};
|
||||
|
||||
/* Host specific struct used for signalling */
|
||||
struct vmci_host {
|
||||
wait_queue_head_t wait_queue;
|
||||
};
|
||||
|
||||
struct vmci_handle_list {
|
||||
struct list_head node;
|
||||
struct vmci_handle handle;
|
||||
};
|
||||
|
||||
struct vmci_ctx {
|
||||
struct list_head list_item; /* For global VMCI list. */
|
||||
u32 cid;
|
||||
struct kref kref;
|
||||
struct list_head datagram_queue; /* Head of per VM queue. */
|
||||
u32 pending_datagrams;
|
||||
size_t datagram_queue_size; /* Size of datagram queue in bytes. */
|
||||
|
||||
/*
|
||||
* Version of the code that created
|
||||
* this context; e.g., VMX.
|
||||
*/
|
||||
int user_version;
|
||||
spinlock_t lock; /* Locks callQueue and handle_arrays. */
|
||||
|
||||
/*
|
||||
* queue_pairs attached to. The array of
|
||||
* handles for queue pairs is accessed
|
||||
* from the code for QP API, and there
|
||||
* it is protected by the QP lock. It
|
||||
* is also accessed from the context
|
||||
* clean up path, which does not
|
||||
* require a lock. VMCILock is not
|
||||
* used to protect the QP array field.
|
||||
*/
|
||||
struct vmci_handle_arr *queue_pair_array;
|
||||
|
||||
/* Doorbells created by context. */
|
||||
struct vmci_handle_arr *doorbell_array;
|
||||
|
||||
/* Doorbells pending for context. */
|
||||
struct vmci_handle_arr *pending_doorbell_array;
|
||||
|
||||
/* Contexts current context is subscribing to. */
|
||||
struct list_head notifier_list;
|
||||
unsigned int n_notifiers;
|
||||
|
||||
struct vmci_host host_context;
|
||||
u32 priv_flags;
|
||||
|
||||
const struct cred *cred;
|
||||
bool *notify; /* Notify flag pointer - hosted only. */
|
||||
struct page *notify_page; /* Page backing the notify UVA. */
|
||||
};
|
||||
|
||||
/* VMCINotifyAddRemoveInfo: Used to add/remove remote context notifications. */
|
||||
struct vmci_ctx_info {
|
||||
u32 remote_cid;
|
||||
int result;
|
||||
};
|
||||
|
||||
/* VMCICptBufInfo: Used to set/get current context's checkpoint state. */
|
||||
struct vmci_ctx_chkpt_buf_info {
|
||||
u64 cpt_buf;
|
||||
u32 cpt_type;
|
||||
u32 buf_size;
|
||||
s32 result;
|
||||
u32 _pad;
|
||||
};
|
||||
|
||||
/*
|
||||
* VMCINotificationReceiveInfo: Used to recieve pending notifications
|
||||
* for doorbells and queue pairs.
|
||||
*/
|
||||
struct vmci_ctx_notify_recv_info {
|
||||
u64 db_handle_buf_uva;
|
||||
u64 db_handle_buf_size;
|
||||
u64 qp_handle_buf_uva;
|
||||
u64 qp_handle_buf_size;
|
||||
s32 result;
|
||||
u32 _pad;
|
||||
};
|
||||
|
||||
/*
|
||||
* Utilility function that checks whether two entities are allowed
|
||||
* to interact. If one of them is restricted, the other one must
|
||||
* be trusted.
|
||||
*/
|
||||
static inline bool vmci_deny_interaction(u32 part_one, u32 part_two)
|
||||
{
|
||||
return ((part_one & VMCI_PRIVILEGE_FLAG_RESTRICTED) &&
|
||||
!(part_two & VMCI_PRIVILEGE_FLAG_TRUSTED)) ||
|
||||
((part_two & VMCI_PRIVILEGE_FLAG_RESTRICTED) &&
|
||||
!(part_one & VMCI_PRIVILEGE_FLAG_TRUSTED));
|
||||
}
|
||||
|
||||
struct vmci_ctx *vmci_ctx_create(u32 cid, u32 flags,
|
||||
uintptr_t event_hnd, int version,
|
||||
const struct cred *cred);
|
||||
void vmci_ctx_destroy(struct vmci_ctx *context);
|
||||
|
||||
bool vmci_ctx_supports_host_qp(struct vmci_ctx *context);
|
||||
int vmci_ctx_enqueue_datagram(u32 cid, struct vmci_datagram *dg);
|
||||
int vmci_ctx_dequeue_datagram(struct vmci_ctx *context,
|
||||
size_t *max_size, struct vmci_datagram **dg);
|
||||
int vmci_ctx_pending_datagrams(u32 cid, u32 *pending);
|
||||
struct vmci_ctx *vmci_ctx_get(u32 cid);
|
||||
void vmci_ctx_put(struct vmci_ctx *context);
|
||||
bool vmci_ctx_exists(u32 cid);
|
||||
|
||||
int vmci_ctx_add_notification(u32 context_id, u32 remote_cid);
|
||||
int vmci_ctx_remove_notification(u32 context_id, u32 remote_cid);
|
||||
int vmci_ctx_get_chkpt_state(u32 context_id, u32 cpt_type,
|
||||
u32 *num_cids, void **cpt_buf_ptr);
|
||||
int vmci_ctx_set_chkpt_state(u32 context_id, u32 cpt_type,
|
||||
u32 num_cids, void *cpt_buf);
|
||||
|
||||
int vmci_ctx_qp_create(struct vmci_ctx *context, struct vmci_handle handle);
|
||||
int vmci_ctx_qp_destroy(struct vmci_ctx *context, struct vmci_handle handle);
|
||||
bool vmci_ctx_qp_exists(struct vmci_ctx *context, struct vmci_handle handle);
|
||||
|
||||
void vmci_ctx_check_signal_notify(struct vmci_ctx *context);
|
||||
void vmci_ctx_unset_notify(struct vmci_ctx *context);
|
||||
|
||||
int vmci_ctx_dbell_create(u32 context_id, struct vmci_handle handle);
|
||||
int vmci_ctx_dbell_destroy(u32 context_id, struct vmci_handle handle);
|
||||
int vmci_ctx_dbell_destroy_all(u32 context_id);
|
||||
int vmci_ctx_notify_dbell(u32 cid, struct vmci_handle handle,
|
||||
u32 src_priv_flags);
|
||||
|
||||
int vmci_ctx_rcv_notifications_get(u32 context_id, struct vmci_handle_arr
|
||||
**db_handle_array, struct vmci_handle_arr
|
||||
**qp_handle_array);
|
||||
void vmci_ctx_rcv_notifications_release(u32 context_id, struct vmci_handle_arr
|
||||
*db_handle_array, struct vmci_handle_arr
|
||||
*qp_handle_array, bool success);
|
||||
|
||||
static inline u32 vmci_ctx_get_id(struct vmci_ctx *context)
|
||||
{
|
||||
if (!context)
|
||||
return VMCI_INVALID_ID;
|
||||
return context->cid;
|
||||
}
|
||||
|
||||
#endif /* _VMCI_CONTEXT_H_ */
|
503
drivers/misc/vmw_vmci/vmci_datagram.c
Normal file
503
drivers/misc/vmw_vmci/vmci_datagram.c
Normal file
|
@ -0,0 +1,503 @@
|
|||
/*
|
||||
* VMware VMCI Driver
|
||||
*
|
||||
* Copyright (C) 2012 VMware, Inc. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation version 2 and no later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* for more details.
|
||||
*/
|
||||
|
||||
#include <linux/vmw_vmci_defs.h>
|
||||
#include <linux/vmw_vmci_api.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/bug.h>
|
||||
|
||||
#include "vmci_datagram.h"
|
||||
#include "vmci_resource.h"
|
||||
#include "vmci_context.h"
|
||||
#include "vmci_driver.h"
|
||||
#include "vmci_event.h"
|
||||
#include "vmci_route.h"
|
||||
|
||||
/*
|
||||
* struct datagram_entry describes the datagram entity. It is used for datagram
|
||||
* entities created only on the host.
|
||||
*/
|
||||
struct datagram_entry {
|
||||
struct vmci_resource resource;
|
||||
u32 flags;
|
||||
bool run_delayed;
|
||||
vmci_datagram_recv_cb recv_cb;
|
||||
void *client_data;
|
||||
u32 priv_flags;
|
||||
};
|
||||
|
||||
struct delayed_datagram_info {
|
||||
struct datagram_entry *entry;
|
||||
struct work_struct work;
|
||||
bool in_dg_host_queue;
|
||||
/* msg and msg_payload must be together. */
|
||||
struct vmci_datagram msg;
|
||||
u8 msg_payload[];
|
||||
};
|
||||
|
||||
/* Number of in-flight host->host datagrams */
|
||||
static atomic_t delayed_dg_host_queue_size = ATOMIC_INIT(0);
|
||||
|
||||
/*
|
||||
* Create a datagram entry given a handle pointer.
|
||||
*/
|
||||
static int dg_create_handle(u32 resource_id,
|
||||
u32 flags,
|
||||
u32 priv_flags,
|
||||
vmci_datagram_recv_cb recv_cb,
|
||||
void *client_data, struct vmci_handle *out_handle)
|
||||
{
|
||||
int result;
|
||||
u32 context_id;
|
||||
struct vmci_handle handle;
|
||||
struct datagram_entry *entry;
|
||||
|
||||
if ((flags & VMCI_FLAG_WELLKNOWN_DG_HND) != 0)
|
||||
return VMCI_ERROR_INVALID_ARGS;
|
||||
|
||||
if ((flags & VMCI_FLAG_ANYCID_DG_HND) != 0) {
|
||||
context_id = VMCI_INVALID_ID;
|
||||
} else {
|
||||
context_id = vmci_get_context_id();
|
||||
if (context_id == VMCI_INVALID_ID)
|
||||
return VMCI_ERROR_NO_RESOURCES;
|
||||
}
|
||||
|
||||
handle = vmci_make_handle(context_id, resource_id);
|
||||
|
||||
entry = kmalloc(sizeof(*entry), GFP_KERNEL);
|
||||
if (!entry) {
|
||||
pr_warn("Failed allocating memory for datagram entry\n");
|
||||
return VMCI_ERROR_NO_MEM;
|
||||
}
|
||||
|
||||
entry->run_delayed = (flags & VMCI_FLAG_DG_DELAYED_CB) ? true : false;
|
||||
entry->flags = flags;
|
||||
entry->recv_cb = recv_cb;
|
||||
entry->client_data = client_data;
|
||||
entry->priv_flags = priv_flags;
|
||||
|
||||
/* Make datagram resource live. */
|
||||
result = vmci_resource_add(&entry->resource,
|
||||
VMCI_RESOURCE_TYPE_DATAGRAM,
|
||||
handle);
|
||||
if (result != VMCI_SUCCESS) {
|
||||
pr_warn("Failed to add new resource (handle=0x%x:0x%x), error: %d\n",
|
||||
handle.context, handle.resource, result);
|
||||
kfree(entry);
|
||||
return result;
|
||||
}
|
||||
|
||||
*out_handle = vmci_resource_handle(&entry->resource);
|
||||
return VMCI_SUCCESS;
|
||||
}
|
||||
|
||||
/*
|
||||
* Internal utility function with the same purpose as
|
||||
* vmci_datagram_get_priv_flags that also takes a context_id.
|
||||
*/
|
||||
static int vmci_datagram_get_priv_flags(u32 context_id,
|
||||
struct vmci_handle handle,
|
||||
u32 *priv_flags)
|
||||
{
|
||||
if (context_id == VMCI_INVALID_ID)
|
||||
return VMCI_ERROR_INVALID_ARGS;
|
||||
|
||||
if (context_id == VMCI_HOST_CONTEXT_ID) {
|
||||
struct datagram_entry *src_entry;
|
||||
struct vmci_resource *resource;
|
||||
|
||||
resource = vmci_resource_by_handle(handle,
|
||||
VMCI_RESOURCE_TYPE_DATAGRAM);
|
||||
if (!resource)
|
||||
return VMCI_ERROR_INVALID_ARGS;
|
||||
|
||||
src_entry = container_of(resource, struct datagram_entry,
|
||||
resource);
|
||||
*priv_flags = src_entry->priv_flags;
|
||||
vmci_resource_put(resource);
|
||||
} else if (context_id == VMCI_HYPERVISOR_CONTEXT_ID)
|
||||
*priv_flags = VMCI_MAX_PRIVILEGE_FLAGS;
|
||||
else
|
||||
*priv_flags = vmci_context_get_priv_flags(context_id);
|
||||
|
||||
return VMCI_SUCCESS;
|
||||
}
|
||||
|
||||
/*
|
||||
* Calls the specified callback in a delayed context.
|
||||
*/
|
||||
static void dg_delayed_dispatch(struct work_struct *work)
|
||||
{
|
||||
struct delayed_datagram_info *dg_info =
|
||||
container_of(work, struct delayed_datagram_info, work);
|
||||
|
||||
dg_info->entry->recv_cb(dg_info->entry->client_data, &dg_info->msg);
|
||||
|
||||
vmci_resource_put(&dg_info->entry->resource);
|
||||
|
||||
if (dg_info->in_dg_host_queue)
|
||||
atomic_dec(&delayed_dg_host_queue_size);
|
||||
|
||||
kfree(dg_info);
|
||||
}
|
||||
|
||||
/*
|
||||
* Dispatch datagram as a host, to the host, or other vm context. This
|
||||
* function cannot dispatch to hypervisor context handlers. This should
|
||||
* have been handled before we get here by vmci_datagram_dispatch.
|
||||
* Returns number of bytes sent on success, error code otherwise.
|
||||
*/
|
||||
static int dg_dispatch_as_host(u32 context_id, struct vmci_datagram *dg)
|
||||
{
|
||||
int retval;
|
||||
size_t dg_size;
|
||||
u32 src_priv_flags;
|
||||
|
||||
dg_size = VMCI_DG_SIZE(dg);
|
||||
|
||||
/* Host cannot send to the hypervisor. */
|
||||
if (dg->dst.context == VMCI_HYPERVISOR_CONTEXT_ID)
|
||||
return VMCI_ERROR_DST_UNREACHABLE;
|
||||
|
||||
/* Check that source handle matches sending context. */
|
||||
if (dg->src.context != context_id) {
|
||||
pr_devel("Sender context (ID=0x%x) is not owner of src datagram entry (handle=0x%x:0x%x)\n",
|
||||
context_id, dg->src.context, dg->src.resource);
|
||||
return VMCI_ERROR_NO_ACCESS;
|
||||
}
|
||||
|
||||
/* Get hold of privileges of sending endpoint. */
|
||||
retval = vmci_datagram_get_priv_flags(context_id, dg->src,
|
||||
&src_priv_flags);
|
||||
if (retval != VMCI_SUCCESS) {
|
||||
pr_warn("Couldn't get privileges (handle=0x%x:0x%x)\n",
|
||||
dg->src.context, dg->src.resource);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/* Determine if we should route to host or guest destination. */
|
||||
if (dg->dst.context == VMCI_HOST_CONTEXT_ID) {
|
||||
/* Route to host datagram entry. */
|
||||
struct datagram_entry *dst_entry;
|
||||
struct vmci_resource *resource;
|
||||
|
||||
if (dg->src.context == VMCI_HYPERVISOR_CONTEXT_ID &&
|
||||
dg->dst.resource == VMCI_EVENT_HANDLER) {
|
||||
return vmci_event_dispatch(dg);
|
||||
}
|
||||
|
||||
resource = vmci_resource_by_handle(dg->dst,
|
||||
VMCI_RESOURCE_TYPE_DATAGRAM);
|
||||
if (!resource) {
|
||||
pr_devel("Sending to invalid destination (handle=0x%x:0x%x)\n",
|
||||
dg->dst.context, dg->dst.resource);
|
||||
return VMCI_ERROR_INVALID_RESOURCE;
|
||||
}
|
||||
dst_entry = container_of(resource, struct datagram_entry,
|
||||
resource);
|
||||
if (vmci_deny_interaction(src_priv_flags,
|
||||
dst_entry->priv_flags)) {
|
||||
vmci_resource_put(resource);
|
||||
return VMCI_ERROR_NO_ACCESS;
|
||||
}
|
||||
|
||||
/*
|
||||
* If a VMCI datagram destined for the host is also sent by the
|
||||
* host, we always run it delayed. This ensures that no locks
|
||||
* are held when the datagram callback runs.
|
||||
*/
|
||||
if (dst_entry->run_delayed ||
|
||||
dg->src.context == VMCI_HOST_CONTEXT_ID) {
|
||||
struct delayed_datagram_info *dg_info;
|
||||
|
||||
if (atomic_add_return(1, &delayed_dg_host_queue_size)
|
||||
== VMCI_MAX_DELAYED_DG_HOST_QUEUE_SIZE) {
|
||||
atomic_dec(&delayed_dg_host_queue_size);
|
||||
vmci_resource_put(resource);
|
||||
return VMCI_ERROR_NO_MEM;
|
||||
}
|
||||
|
||||
dg_info = kmalloc(sizeof(*dg_info) +
|
||||
(size_t) dg->payload_size, GFP_ATOMIC);
|
||||
if (!dg_info) {
|
||||
atomic_dec(&delayed_dg_host_queue_size);
|
||||
vmci_resource_put(resource);
|
||||
return VMCI_ERROR_NO_MEM;
|
||||
}
|
||||
|
||||
dg_info->in_dg_host_queue = true;
|
||||
dg_info->entry = dst_entry;
|
||||
memcpy(&dg_info->msg, dg, dg_size);
|
||||
|
||||
INIT_WORK(&dg_info->work, dg_delayed_dispatch);
|
||||
schedule_work(&dg_info->work);
|
||||
retval = VMCI_SUCCESS;
|
||||
|
||||
} else {
|
||||
retval = dst_entry->recv_cb(dst_entry->client_data, dg);
|
||||
vmci_resource_put(resource);
|
||||
if (retval < VMCI_SUCCESS)
|
||||
return retval;
|
||||
}
|
||||
} else {
|
||||
/* Route to destination VM context. */
|
||||
struct vmci_datagram *new_dg;
|
||||
|
||||
if (context_id != dg->dst.context) {
|
||||
if (vmci_deny_interaction(src_priv_flags,
|
||||
vmci_context_get_priv_flags
|
||||
(dg->dst.context))) {
|
||||
return VMCI_ERROR_NO_ACCESS;
|
||||
} else if (VMCI_CONTEXT_IS_VM(context_id)) {
|
||||
/*
|
||||
* If the sending context is a VM, it
|
||||
* cannot reach another VM.
|
||||
*/
|
||||
|
||||
pr_devel("Datagram communication between VMs not supported (src=0x%x, dst=0x%x)\n",
|
||||
context_id, dg->dst.context);
|
||||
return VMCI_ERROR_DST_UNREACHABLE;
|
||||
}
|
||||
}
|
||||
|
||||
/* We make a copy to enqueue. */
|
||||
new_dg = kmalloc(dg_size, GFP_KERNEL);
|
||||
if (new_dg == NULL)
|
||||
return VMCI_ERROR_NO_MEM;
|
||||
|
||||
memcpy(new_dg, dg, dg_size);
|
||||
retval = vmci_ctx_enqueue_datagram(dg->dst.context, new_dg);
|
||||
if (retval < VMCI_SUCCESS) {
|
||||
kfree(new_dg);
|
||||
return retval;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* We currently truncate the size to signed 32 bits. This doesn't
|
||||
* matter for this handler as it only support 4Kb messages.
|
||||
*/
|
||||
return (int)dg_size;
|
||||
}
|
||||
|
||||
/*
|
||||
* Dispatch datagram as a guest, down through the VMX and potentially to
|
||||
* the host.
|
||||
* Returns number of bytes sent on success, error code otherwise.
|
||||
*/
|
||||
static int dg_dispatch_as_guest(struct vmci_datagram *dg)
|
||||
{
|
||||
int retval;
|
||||
struct vmci_resource *resource;
|
||||
|
||||
resource = vmci_resource_by_handle(dg->src,
|
||||
VMCI_RESOURCE_TYPE_DATAGRAM);
|
||||
if (!resource)
|
||||
return VMCI_ERROR_NO_HANDLE;
|
||||
|
||||
retval = vmci_send_datagram(dg);
|
||||
vmci_resource_put(resource);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*
|
||||
* Dispatch datagram. This will determine the routing for the datagram
|
||||
* and dispatch it accordingly.
|
||||
* Returns number of bytes sent on success, error code otherwise.
|
||||
*/
|
||||
int vmci_datagram_dispatch(u32 context_id,
|
||||
struct vmci_datagram *dg, bool from_guest)
|
||||
{
|
||||
int retval;
|
||||
enum vmci_route route;
|
||||
|
||||
BUILD_BUG_ON(sizeof(struct vmci_datagram) != 24);
|
||||
|
||||
if (dg->payload_size > VMCI_MAX_DG_SIZE ||
|
||||
VMCI_DG_SIZE(dg) > VMCI_MAX_DG_SIZE) {
|
||||
pr_devel("Payload (size=%llu bytes) too big to send\n",
|
||||
(unsigned long long)dg->payload_size);
|
||||
return VMCI_ERROR_INVALID_ARGS;
|
||||
}
|
||||
|
||||
retval = vmci_route(&dg->src, &dg->dst, from_guest, &route);
|
||||
if (retval < VMCI_SUCCESS) {
|
||||
pr_devel("Failed to route datagram (src=0x%x, dst=0x%x, err=%d)\n",
|
||||
dg->src.context, dg->dst.context, retval);
|
||||
return retval;
|
||||
}
|
||||
|
||||
if (VMCI_ROUTE_AS_HOST == route) {
|
||||
if (VMCI_INVALID_ID == context_id)
|
||||
context_id = VMCI_HOST_CONTEXT_ID;
|
||||
return dg_dispatch_as_host(context_id, dg);
|
||||
}
|
||||
|
||||
if (VMCI_ROUTE_AS_GUEST == route)
|
||||
return dg_dispatch_as_guest(dg);
|
||||
|
||||
pr_warn("Unknown route (%d) for datagram\n", route);
|
||||
return VMCI_ERROR_DST_UNREACHABLE;
|
||||
}
|
||||
|
||||
/*
|
||||
* Invoke the handler for the given datagram. This is intended to be
|
||||
* called only when acting as a guest and receiving a datagram from the
|
||||
* virtual device.
|
||||
*/
|
||||
int vmci_datagram_invoke_guest_handler(struct vmci_datagram *dg)
|
||||
{
|
||||
struct vmci_resource *resource;
|
||||
struct datagram_entry *dst_entry;
|
||||
|
||||
resource = vmci_resource_by_handle(dg->dst,
|
||||
VMCI_RESOURCE_TYPE_DATAGRAM);
|
||||
if (!resource) {
|
||||
pr_devel("destination (handle=0x%x:0x%x) doesn't exist\n",
|
||||
dg->dst.context, dg->dst.resource);
|
||||
return VMCI_ERROR_NO_HANDLE;
|
||||
}
|
||||
|
||||
dst_entry = container_of(resource, struct datagram_entry, resource);
|
||||
if (dst_entry->run_delayed) {
|
||||
struct delayed_datagram_info *dg_info;
|
||||
|
||||
dg_info = kmalloc(sizeof(*dg_info) + (size_t)dg->payload_size,
|
||||
GFP_ATOMIC);
|
||||
if (!dg_info) {
|
||||
vmci_resource_put(resource);
|
||||
return VMCI_ERROR_NO_MEM;
|
||||
}
|
||||
|
||||
dg_info->in_dg_host_queue = false;
|
||||
dg_info->entry = dst_entry;
|
||||
memcpy(&dg_info->msg, dg, VMCI_DG_SIZE(dg));
|
||||
|
||||
INIT_WORK(&dg_info->work, dg_delayed_dispatch);
|
||||
schedule_work(&dg_info->work);
|
||||
} else {
|
||||
dst_entry->recv_cb(dst_entry->client_data, dg);
|
||||
vmci_resource_put(resource);
|
||||
}
|
||||
|
||||
return VMCI_SUCCESS;
|
||||
}
|
||||
|
||||
/*
|
||||
* vmci_datagram_create_handle_priv() - Create host context datagram endpoint
|
||||
* @resource_id: The resource ID.
|
||||
* @flags: Datagram Flags.
|
||||
* @priv_flags: Privilege Flags.
|
||||
* @recv_cb: Callback when receiving datagrams.
|
||||
* @client_data: Pointer for a datagram_entry struct
|
||||
* @out_handle: vmci_handle that is populated as a result of this function.
|
||||
*
|
||||
* Creates a host context datagram endpoint and returns a handle to it.
|
||||
*/
|
||||
int vmci_datagram_create_handle_priv(u32 resource_id,
|
||||
u32 flags,
|
||||
u32 priv_flags,
|
||||
vmci_datagram_recv_cb recv_cb,
|
||||
void *client_data,
|
||||
struct vmci_handle *out_handle)
|
||||
{
|
||||
if (out_handle == NULL)
|
||||
return VMCI_ERROR_INVALID_ARGS;
|
||||
|
||||
if (recv_cb == NULL) {
|
||||
pr_devel("Client callback needed when creating datagram\n");
|
||||
return VMCI_ERROR_INVALID_ARGS;
|
||||
}
|
||||
|
||||
if (priv_flags & ~VMCI_PRIVILEGE_ALL_FLAGS)
|
||||
return VMCI_ERROR_INVALID_ARGS;
|
||||
|
||||
return dg_create_handle(resource_id, flags, priv_flags, recv_cb,
|
||||
client_data, out_handle);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(vmci_datagram_create_handle_priv);
|
||||
|
||||
/*
|
||||
* vmci_datagram_create_handle() - Create host context datagram endpoint
|
||||
* @resource_id: Resource ID.
|
||||
* @flags: Datagram Flags.
|
||||
* @recv_cb: Callback when receiving datagrams.
|
||||
* @client_ata: Pointer for a datagram_entry struct
|
||||
* @out_handle: vmci_handle that is populated as a result of this function.
|
||||
*
|
||||
* Creates a host context datagram endpoint and returns a handle to
|
||||
* it. Same as vmci_datagram_create_handle_priv without the priviledge
|
||||
* flags argument.
|
||||
*/
|
||||
int vmci_datagram_create_handle(u32 resource_id,
|
||||
u32 flags,
|
||||
vmci_datagram_recv_cb recv_cb,
|
||||
void *client_data,
|
||||
struct vmci_handle *out_handle)
|
||||
{
|
||||
return vmci_datagram_create_handle_priv(
|
||||
resource_id, flags,
|
||||
VMCI_DEFAULT_PROC_PRIVILEGE_FLAGS,
|
||||
recv_cb, client_data,
|
||||
out_handle);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(vmci_datagram_create_handle);
|
||||
|
||||
/*
|
||||
* vmci_datagram_destroy_handle() - Destroys datagram handle
|
||||
* @handle: vmci_handle to be destroyed and reaped.
|
||||
*
|
||||
* Use this function to destroy any datagram handles created by
|
||||
* vmci_datagram_create_handle{,Priv} functions.
|
||||
*/
|
||||
int vmci_datagram_destroy_handle(struct vmci_handle handle)
|
||||
{
|
||||
struct datagram_entry *entry;
|
||||
struct vmci_resource *resource;
|
||||
|
||||
resource = vmci_resource_by_handle(handle, VMCI_RESOURCE_TYPE_DATAGRAM);
|
||||
if (!resource) {
|
||||
pr_devel("Failed to destroy datagram (handle=0x%x:0x%x)\n",
|
||||
handle.context, handle.resource);
|
||||
return VMCI_ERROR_NOT_FOUND;
|
||||
}
|
||||
|
||||
entry = container_of(resource, struct datagram_entry, resource);
|
||||
|
||||
vmci_resource_put(&entry->resource);
|
||||
vmci_resource_remove(&entry->resource);
|
||||
kfree(entry);
|
||||
|
||||
return VMCI_SUCCESS;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(vmci_datagram_destroy_handle);
|
||||
|
||||
/*
|
||||
* vmci_datagram_send() - Send a datagram
|
||||
* @msg: The datagram to send.
|
||||
*
|
||||
* Sends the provided datagram on its merry way.
|
||||
*/
|
||||
int vmci_datagram_send(struct vmci_datagram *msg)
|
||||
{
|
||||
if (msg == NULL)
|
||||
return VMCI_ERROR_INVALID_ARGS;
|
||||
|
||||
return vmci_datagram_dispatch(VMCI_INVALID_ID, msg, false);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(vmci_datagram_send);
|
52
drivers/misc/vmw_vmci/vmci_datagram.h
Normal file
52
drivers/misc/vmw_vmci/vmci_datagram.h
Normal file
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* VMware VMCI Driver
|
||||
*
|
||||
* Copyright (C) 2012 VMware, Inc. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation version 2 and no later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* for more details.
|
||||
*/
|
||||
|
||||
#ifndef _VMCI_DATAGRAM_H_
|
||||
#define _VMCI_DATAGRAM_H_
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/list.h>
|
||||
|
||||
#include "vmci_context.h"
|
||||
|
||||
#define VMCI_MAX_DELAYED_DG_HOST_QUEUE_SIZE 256
|
||||
|
||||
/*
|
||||
* The struct vmci_datagram_queue_entry is a queue header for the in-kernel VMCI
|
||||
* datagram queues. It is allocated in non-paged memory, as the
|
||||
* content is accessed while holding a spinlock. The pending datagram
|
||||
* itself may be allocated from paged memory. We shadow the size of
|
||||
* the datagram in the non-paged queue entry as this size is used
|
||||
* while holding the same spinlock as above.
|
||||
*/
|
||||
struct vmci_datagram_queue_entry {
|
||||
struct list_head list_item; /* For queuing. */
|
||||
size_t dg_size; /* Size of datagram. */
|
||||
struct vmci_datagram *dg; /* Pending datagram. */
|
||||
};
|
||||
|
||||
/* VMCIDatagramSendRecvInfo */
|
||||
struct vmci_datagram_snd_rcv_info {
|
||||
u64 addr;
|
||||
u32 len;
|
||||
s32 result;
|
||||
};
|
||||
|
||||
/* Datagram API for non-public use. */
|
||||
int vmci_datagram_dispatch(u32 context_id, struct vmci_datagram *dg,
|
||||
bool from_guest);
|
||||
int vmci_datagram_invoke_guest_handler(struct vmci_datagram *dg);
|
||||
|
||||
#endif /* _VMCI_DATAGRAM_H_ */
|
601
drivers/misc/vmw_vmci/vmci_doorbell.c
Normal file
601
drivers/misc/vmw_vmci/vmci_doorbell.c
Normal file
|
@ -0,0 +1,601 @@
|
|||
/*
|
||||
* VMware VMCI Driver
|
||||
*
|
||||
* Copyright (C) 2012 VMware, Inc. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation version 2 and no later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* for more details.
|
||||
*/
|
||||
|
||||
#include <linux/vmw_vmci_defs.h>
|
||||
#include <linux/vmw_vmci_api.h>
|
||||
#include <linux/completion.h>
|
||||
#include <linux/hash.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include "vmci_datagram.h"
|
||||
#include "vmci_doorbell.h"
|
||||
#include "vmci_resource.h"
|
||||
#include "vmci_driver.h"
|
||||
#include "vmci_route.h"
|
||||
|
||||
|
||||
#define VMCI_DOORBELL_INDEX_BITS 6
|
||||
#define VMCI_DOORBELL_INDEX_TABLE_SIZE (1 << VMCI_DOORBELL_INDEX_BITS)
|
||||
#define VMCI_DOORBELL_HASH(_idx) hash_32(_idx, VMCI_DOORBELL_INDEX_BITS)
|
||||
|
||||
/*
|
||||
* DoorbellEntry describes the a doorbell notification handle allocated by the
|
||||
* host.
|
||||
*/
|
||||
struct dbell_entry {
|
||||
struct vmci_resource resource;
|
||||
struct hlist_node node;
|
||||
struct work_struct work;
|
||||
vmci_callback notify_cb;
|
||||
void *client_data;
|
||||
u32 idx;
|
||||
u32 priv_flags;
|
||||
bool run_delayed;
|
||||
atomic_t active; /* Only used by guest personality */
|
||||
};
|
||||
|
||||
/* The VMCI index table keeps track of currently registered doorbells. */
|
||||
struct dbell_index_table {
|
||||
spinlock_t lock; /* Index table lock */
|
||||
struct hlist_head entries[VMCI_DOORBELL_INDEX_TABLE_SIZE];
|
||||
};
|
||||
|
||||
static struct dbell_index_table vmci_doorbell_it = {
|
||||
.lock = __SPIN_LOCK_UNLOCKED(vmci_doorbell_it.lock),
|
||||
};
|
||||
|
||||
/*
|
||||
* The max_notify_idx is one larger than the currently known bitmap index in
|
||||
* use, and is used to determine how much of the bitmap needs to be scanned.
|
||||
*/
|
||||
static u32 max_notify_idx;
|
||||
|
||||
/*
|
||||
* The notify_idx_count is used for determining whether there are free entries
|
||||
* within the bitmap (if notify_idx_count + 1 < max_notify_idx).
|
||||
*/
|
||||
static u32 notify_idx_count;
|
||||
|
||||
/*
|
||||
* The last_notify_idx_reserved is used to track the last index handed out - in
|
||||
* the case where multiple handles share a notification index, we hand out
|
||||
* indexes round robin based on last_notify_idx_reserved.
|
||||
*/
|
||||
static u32 last_notify_idx_reserved;
|
||||
|
||||
/* This is a one entry cache used to by the index allocation. */
|
||||
static u32 last_notify_idx_released = PAGE_SIZE;
|
||||
|
||||
|
||||
/*
|
||||
* Utility function that retrieves the privilege flags associated
|
||||
* with a given doorbell handle. For guest endpoints, the
|
||||
* privileges are determined by the context ID, but for host
|
||||
* endpoints privileges are associated with the complete
|
||||
* handle. Hypervisor endpoints are not yet supported.
|
||||
*/
|
||||
int vmci_dbell_get_priv_flags(struct vmci_handle handle, u32 *priv_flags)
|
||||
{
|
||||
if (priv_flags == NULL || handle.context == VMCI_INVALID_ID)
|
||||
return VMCI_ERROR_INVALID_ARGS;
|
||||
|
||||
if (handle.context == VMCI_HOST_CONTEXT_ID) {
|
||||
struct dbell_entry *entry;
|
||||
struct vmci_resource *resource;
|
||||
|
||||
resource = vmci_resource_by_handle(handle,
|
||||
VMCI_RESOURCE_TYPE_DOORBELL);
|
||||
if (!resource)
|
||||
return VMCI_ERROR_NOT_FOUND;
|
||||
|
||||
entry = container_of(resource, struct dbell_entry, resource);
|
||||
*priv_flags = entry->priv_flags;
|
||||
vmci_resource_put(resource);
|
||||
} else if (handle.context == VMCI_HYPERVISOR_CONTEXT_ID) {
|
||||
/*
|
||||
* Hypervisor endpoints for notifications are not
|
||||
* supported (yet).
|
||||
*/
|
||||
return VMCI_ERROR_INVALID_ARGS;
|
||||
} else {
|
||||
*priv_flags = vmci_context_get_priv_flags(handle.context);
|
||||
}
|
||||
|
||||
return VMCI_SUCCESS;
|
||||
}
|
||||
|
||||
/*
|
||||
* Find doorbell entry by bitmap index.
|
||||
*/
|
||||
static struct dbell_entry *dbell_index_table_find(u32 idx)
|
||||
{
|
||||
u32 bucket = VMCI_DOORBELL_HASH(idx);
|
||||
struct dbell_entry *dbell;
|
||||
|
||||
hlist_for_each_entry(dbell, &vmci_doorbell_it.entries[bucket],
|
||||
node) {
|
||||
if (idx == dbell->idx)
|
||||
return dbell;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Add the given entry to the index table. This willi take a reference to the
|
||||
* entry's resource so that the entry is not deleted before it is removed from
|
||||
* the * table.
|
||||
*/
|
||||
static void dbell_index_table_add(struct dbell_entry *entry)
|
||||
{
|
||||
u32 bucket;
|
||||
u32 new_notify_idx;
|
||||
|
||||
vmci_resource_get(&entry->resource);
|
||||
|
||||
spin_lock_bh(&vmci_doorbell_it.lock);
|
||||
|
||||
/*
|
||||
* Below we try to allocate an index in the notification
|
||||
* bitmap with "not too much" sharing between resources. If we
|
||||
* use less that the full bitmap, we either add to the end if
|
||||
* there are no unused flags within the currently used area,
|
||||
* or we search for unused ones. If we use the full bitmap, we
|
||||
* allocate the index round robin.
|
||||
*/
|
||||
if (max_notify_idx < PAGE_SIZE || notify_idx_count < PAGE_SIZE) {
|
||||
if (last_notify_idx_released < max_notify_idx &&
|
||||
!dbell_index_table_find(last_notify_idx_released)) {
|
||||
new_notify_idx = last_notify_idx_released;
|
||||
last_notify_idx_released = PAGE_SIZE;
|
||||
} else {
|
||||
bool reused = false;
|
||||
new_notify_idx = last_notify_idx_reserved;
|
||||
if (notify_idx_count + 1 < max_notify_idx) {
|
||||
do {
|
||||
if (!dbell_index_table_find
|
||||
(new_notify_idx)) {
|
||||
reused = true;
|
||||
break;
|
||||
}
|
||||
new_notify_idx = (new_notify_idx + 1) %
|
||||
max_notify_idx;
|
||||
} while (new_notify_idx !=
|
||||
last_notify_idx_released);
|
||||
}
|
||||
if (!reused) {
|
||||
new_notify_idx = max_notify_idx;
|
||||
max_notify_idx++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
new_notify_idx = (last_notify_idx_reserved + 1) % PAGE_SIZE;
|
||||
}
|
||||
|
||||
last_notify_idx_reserved = new_notify_idx;
|
||||
notify_idx_count++;
|
||||
|
||||
entry->idx = new_notify_idx;
|
||||
bucket = VMCI_DOORBELL_HASH(entry->idx);
|
||||
hlist_add_head(&entry->node, &vmci_doorbell_it.entries[bucket]);
|
||||
|
||||
spin_unlock_bh(&vmci_doorbell_it.lock);
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove the given entry from the index table. This will release() the
|
||||
* entry's resource.
|
||||
*/
|
||||
static void dbell_index_table_remove(struct dbell_entry *entry)
|
||||
{
|
||||
spin_lock_bh(&vmci_doorbell_it.lock);
|
||||
|
||||
hlist_del_init(&entry->node);
|
||||
|
||||
notify_idx_count--;
|
||||
if (entry->idx == max_notify_idx - 1) {
|
||||
/*
|
||||
* If we delete an entry with the maximum known
|
||||
* notification index, we take the opportunity to
|
||||
* prune the current max. As there might be other
|
||||
* unused indices immediately below, we lower the
|
||||
* maximum until we hit an index in use.
|
||||
*/
|
||||
while (max_notify_idx > 0 &&
|
||||
!dbell_index_table_find(max_notify_idx - 1))
|
||||
max_notify_idx--;
|
||||
}
|
||||
|
||||
last_notify_idx_released = entry->idx;
|
||||
|
||||
spin_unlock_bh(&vmci_doorbell_it.lock);
|
||||
|
||||
vmci_resource_put(&entry->resource);
|
||||
}
|
||||
|
||||
/*
|
||||
* Creates a link between the given doorbell handle and the given
|
||||
* index in the bitmap in the device backend. A notification state
|
||||
* is created in hypervisor.
|
||||
*/
|
||||
static int dbell_link(struct vmci_handle handle, u32 notify_idx)
|
||||
{
|
||||
struct vmci_doorbell_link_msg link_msg;
|
||||
|
||||
link_msg.hdr.dst = vmci_make_handle(VMCI_HYPERVISOR_CONTEXT_ID,
|
||||
VMCI_DOORBELL_LINK);
|
||||
link_msg.hdr.src = VMCI_ANON_SRC_HANDLE;
|
||||
link_msg.hdr.payload_size = sizeof(link_msg) - VMCI_DG_HEADERSIZE;
|
||||
link_msg.handle = handle;
|
||||
link_msg.notify_idx = notify_idx;
|
||||
|
||||
return vmci_send_datagram(&link_msg.hdr);
|
||||
}
|
||||
|
||||
/*
|
||||
* Unlinks the given doorbell handle from an index in the bitmap in
|
||||
* the device backend. The notification state is destroyed in hypervisor.
|
||||
*/
|
||||
static int dbell_unlink(struct vmci_handle handle)
|
||||
{
|
||||
struct vmci_doorbell_unlink_msg unlink_msg;
|
||||
|
||||
unlink_msg.hdr.dst = vmci_make_handle(VMCI_HYPERVISOR_CONTEXT_ID,
|
||||
VMCI_DOORBELL_UNLINK);
|
||||
unlink_msg.hdr.src = VMCI_ANON_SRC_HANDLE;
|
||||
unlink_msg.hdr.payload_size = sizeof(unlink_msg) - VMCI_DG_HEADERSIZE;
|
||||
unlink_msg.handle = handle;
|
||||
|
||||
return vmci_send_datagram(&unlink_msg.hdr);
|
||||
}
|
||||
|
||||
/*
|
||||
* Notify another guest or the host. We send a datagram down to the
|
||||
* host via the hypervisor with the notification info.
|
||||
*/
|
||||
static int dbell_notify_as_guest(struct vmci_handle handle, u32 priv_flags)
|
||||
{
|
||||
struct vmci_doorbell_notify_msg notify_msg;
|
||||
|
||||
notify_msg.hdr.dst = vmci_make_handle(VMCI_HYPERVISOR_CONTEXT_ID,
|
||||
VMCI_DOORBELL_NOTIFY);
|
||||
notify_msg.hdr.src = VMCI_ANON_SRC_HANDLE;
|
||||
notify_msg.hdr.payload_size = sizeof(notify_msg) - VMCI_DG_HEADERSIZE;
|
||||
notify_msg.handle = handle;
|
||||
|
||||
return vmci_send_datagram(¬ify_msg.hdr);
|
||||
}
|
||||
|
||||
/*
|
||||
* Calls the specified callback in a delayed context.
|
||||
*/
|
||||
static void dbell_delayed_dispatch(struct work_struct *work)
|
||||
{
|
||||
struct dbell_entry *entry = container_of(work,
|
||||
struct dbell_entry, work);
|
||||
|
||||
entry->notify_cb(entry->client_data);
|
||||
vmci_resource_put(&entry->resource);
|
||||
}
|
||||
|
||||
/*
|
||||
* Dispatches a doorbell notification to the host context.
|
||||
*/
|
||||
int vmci_dbell_host_context_notify(u32 src_cid, struct vmci_handle handle)
|
||||
{
|
||||
struct dbell_entry *entry;
|
||||
struct vmci_resource *resource;
|
||||
|
||||
if (vmci_handle_is_invalid(handle)) {
|
||||
pr_devel("Notifying an invalid doorbell (handle=0x%x:0x%x)\n",
|
||||
handle.context, handle.resource);
|
||||
return VMCI_ERROR_INVALID_ARGS;
|
||||
}
|
||||
|
||||
resource = vmci_resource_by_handle(handle,
|
||||
VMCI_RESOURCE_TYPE_DOORBELL);
|
||||
if (!resource) {
|
||||
pr_devel("Notifying an unknown doorbell (handle=0x%x:0x%x)\n",
|
||||
handle.context, handle.resource);
|
||||
return VMCI_ERROR_NOT_FOUND;
|
||||
}
|
||||
|
||||
entry = container_of(resource, struct dbell_entry, resource);
|
||||
if (entry->run_delayed) {
|
||||
schedule_work(&entry->work);
|
||||
} else {
|
||||
entry->notify_cb(entry->client_data);
|
||||
vmci_resource_put(resource);
|
||||
}
|
||||
|
||||
return VMCI_SUCCESS;
|
||||
}
|
||||
|
||||
/*
|
||||
* Register the notification bitmap with the host.
|
||||
*/
|
||||
bool vmci_dbell_register_notification_bitmap(u32 bitmap_ppn)
|
||||
{
|
||||
int result;
|
||||
struct vmci_notify_bm_set_msg bitmap_set_msg;
|
||||
|
||||
bitmap_set_msg.hdr.dst = vmci_make_handle(VMCI_HYPERVISOR_CONTEXT_ID,
|
||||
VMCI_SET_NOTIFY_BITMAP);
|
||||
bitmap_set_msg.hdr.src = VMCI_ANON_SRC_HANDLE;
|
||||
bitmap_set_msg.hdr.payload_size = sizeof(bitmap_set_msg) -
|
||||
VMCI_DG_HEADERSIZE;
|
||||
bitmap_set_msg.bitmap_ppn = bitmap_ppn;
|
||||
|
||||
result = vmci_send_datagram(&bitmap_set_msg.hdr);
|
||||
if (result != VMCI_SUCCESS) {
|
||||
pr_devel("Failed to register (PPN=%u) as notification bitmap (error=%d)\n",
|
||||
bitmap_ppn, result);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Executes or schedules the handlers for a given notify index.
|
||||
*/
|
||||
static void dbell_fire_entries(u32 notify_idx)
|
||||
{
|
||||
u32 bucket = VMCI_DOORBELL_HASH(notify_idx);
|
||||
struct dbell_entry *dbell;
|
||||
|
||||
spin_lock_bh(&vmci_doorbell_it.lock);
|
||||
|
||||
hlist_for_each_entry(dbell, &vmci_doorbell_it.entries[bucket], node) {
|
||||
if (dbell->idx == notify_idx &&
|
||||
atomic_read(&dbell->active) == 1) {
|
||||
if (dbell->run_delayed) {
|
||||
vmci_resource_get(&dbell->resource);
|
||||
schedule_work(&dbell->work);
|
||||
} else {
|
||||
dbell->notify_cb(dbell->client_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
spin_unlock_bh(&vmci_doorbell_it.lock);
|
||||
}
|
||||
|
||||
/*
|
||||
* Scans the notification bitmap, collects pending notifications,
|
||||
* resets the bitmap and invokes appropriate callbacks.
|
||||
*/
|
||||
void vmci_dbell_scan_notification_entries(u8 *bitmap)
|
||||
{
|
||||
u32 idx;
|
||||
|
||||
for (idx = 0; idx < max_notify_idx; idx++) {
|
||||
if (bitmap[idx] & 0x1) {
|
||||
bitmap[idx] &= ~1;
|
||||
dbell_fire_entries(idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* vmci_doorbell_create() - Creates a doorbell
|
||||
* @handle: A handle used to track the resource. Can be invalid.
|
||||
* @flags: Flag that determines context of callback.
|
||||
* @priv_flags: Privileges flags.
|
||||
* @notify_cb: The callback to be ivoked when the doorbell fires.
|
||||
* @client_data: A parameter to be passed to the callback.
|
||||
*
|
||||
* Creates a doorbell with the given callback. If the handle is
|
||||
* VMCI_INVALID_HANDLE, a free handle will be assigned, if
|
||||
* possible. The callback can be run immediately (potentially with
|
||||
* locks held - the default) or delayed (in a kernel thread) by
|
||||
* specifying the flag VMCI_FLAG_DELAYED_CB. If delayed execution
|
||||
* is selected, a given callback may not be run if the kernel is
|
||||
* unable to allocate memory for the delayed execution (highly
|
||||
* unlikely).
|
||||
*/
|
||||
int vmci_doorbell_create(struct vmci_handle *handle,
|
||||
u32 flags,
|
||||
u32 priv_flags,
|
||||
vmci_callback notify_cb, void *client_data)
|
||||
{
|
||||
struct dbell_entry *entry;
|
||||
struct vmci_handle new_handle;
|
||||
int result;
|
||||
|
||||
if (!handle || !notify_cb || flags & ~VMCI_FLAG_DELAYED_CB ||
|
||||
priv_flags & ~VMCI_PRIVILEGE_ALL_FLAGS)
|
||||
return VMCI_ERROR_INVALID_ARGS;
|
||||
|
||||
entry = kmalloc(sizeof(*entry), GFP_KERNEL);
|
||||
if (entry == NULL) {
|
||||
pr_warn("Failed allocating memory for datagram entry\n");
|
||||
return VMCI_ERROR_NO_MEM;
|
||||
}
|
||||
|
||||
if (vmci_handle_is_invalid(*handle)) {
|
||||
u32 context_id = vmci_get_context_id();
|
||||
|
||||
/* Let resource code allocate a free ID for us */
|
||||
new_handle = vmci_make_handle(context_id, VMCI_INVALID_ID);
|
||||
} else {
|
||||
bool valid_context = false;
|
||||
|
||||
/*
|
||||
* Validate the handle. We must do both of the checks below
|
||||
* because we can be acting as both a host and a guest at the
|
||||
* same time. We always allow the host context ID, since the
|
||||
* host functionality is in practice always there with the
|
||||
* unified driver.
|
||||
*/
|
||||
if (handle->context == VMCI_HOST_CONTEXT_ID ||
|
||||
(vmci_guest_code_active() &&
|
||||
vmci_get_context_id() == handle->context)) {
|
||||
valid_context = true;
|
||||
}
|
||||
|
||||
if (!valid_context || handle->resource == VMCI_INVALID_ID) {
|
||||
pr_devel("Invalid argument (handle=0x%x:0x%x)\n",
|
||||
handle->context, handle->resource);
|
||||
result = VMCI_ERROR_INVALID_ARGS;
|
||||
goto free_mem;
|
||||
}
|
||||
|
||||
new_handle = *handle;
|
||||
}
|
||||
|
||||
entry->idx = 0;
|
||||
INIT_HLIST_NODE(&entry->node);
|
||||
entry->priv_flags = priv_flags;
|
||||
INIT_WORK(&entry->work, dbell_delayed_dispatch);
|
||||
entry->run_delayed = flags & VMCI_FLAG_DELAYED_CB;
|
||||
entry->notify_cb = notify_cb;
|
||||
entry->client_data = client_data;
|
||||
atomic_set(&entry->active, 0);
|
||||
|
||||
result = vmci_resource_add(&entry->resource,
|
||||
VMCI_RESOURCE_TYPE_DOORBELL,
|
||||
new_handle);
|
||||
if (result != VMCI_SUCCESS) {
|
||||
pr_warn("Failed to add new resource (handle=0x%x:0x%x), error: %d\n",
|
||||
new_handle.context, new_handle.resource, result);
|
||||
goto free_mem;
|
||||
}
|
||||
|
||||
new_handle = vmci_resource_handle(&entry->resource);
|
||||
if (vmci_guest_code_active()) {
|
||||
dbell_index_table_add(entry);
|
||||
result = dbell_link(new_handle, entry->idx);
|
||||
if (VMCI_SUCCESS != result)
|
||||
goto destroy_resource;
|
||||
|
||||
atomic_set(&entry->active, 1);
|
||||
}
|
||||
|
||||
*handle = new_handle;
|
||||
|
||||
return result;
|
||||
|
||||
destroy_resource:
|
||||
dbell_index_table_remove(entry);
|
||||
vmci_resource_remove(&entry->resource);
|
||||
free_mem:
|
||||
kfree(entry);
|
||||
return result;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(vmci_doorbell_create);
|
||||
|
||||
/*
|
||||
* vmci_doorbell_destroy() - Destroy a doorbell.
|
||||
* @handle: The handle tracking the resource.
|
||||
*
|
||||
* Destroys a doorbell previously created with vmcii_doorbell_create. This
|
||||
* operation may block waiting for a callback to finish.
|
||||
*/
|
||||
int vmci_doorbell_destroy(struct vmci_handle handle)
|
||||
{
|
||||
struct dbell_entry *entry;
|
||||
struct vmci_resource *resource;
|
||||
|
||||
if (vmci_handle_is_invalid(handle))
|
||||
return VMCI_ERROR_INVALID_ARGS;
|
||||
|
||||
resource = vmci_resource_by_handle(handle,
|
||||
VMCI_RESOURCE_TYPE_DOORBELL);
|
||||
if (!resource) {
|
||||
pr_devel("Failed to destroy doorbell (handle=0x%x:0x%x)\n",
|
||||
handle.context, handle.resource);
|
||||
return VMCI_ERROR_NOT_FOUND;
|
||||
}
|
||||
|
||||
entry = container_of(resource, struct dbell_entry, resource);
|
||||
|
||||
if (vmci_guest_code_active()) {
|
||||
int result;
|
||||
|
||||
dbell_index_table_remove(entry);
|
||||
|
||||
result = dbell_unlink(handle);
|
||||
if (VMCI_SUCCESS != result) {
|
||||
|
||||
/*
|
||||
* The only reason this should fail would be
|
||||
* an inconsistency between guest and
|
||||
* hypervisor state, where the guest believes
|
||||
* it has an active registration whereas the
|
||||
* hypervisor doesn't. One case where this may
|
||||
* happen is if a doorbell is unregistered
|
||||
* following a hibernation at a time where the
|
||||
* doorbell state hasn't been restored on the
|
||||
* hypervisor side yet. Since the handle has
|
||||
* now been removed in the guest, we just
|
||||
* print a warning and return success.
|
||||
*/
|
||||
pr_devel("Unlink of doorbell (handle=0x%x:0x%x) unknown by hypervisor (error=%d)\n",
|
||||
handle.context, handle.resource, result);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Now remove the resource from the table. It might still be in use
|
||||
* after this, in a callback or still on the delayed work queue.
|
||||
*/
|
||||
vmci_resource_put(&entry->resource);
|
||||
vmci_resource_remove(&entry->resource);
|
||||
|
||||
kfree(entry);
|
||||
|
||||
return VMCI_SUCCESS;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(vmci_doorbell_destroy);
|
||||
|
||||
/*
|
||||
* vmci_doorbell_notify() - Ring the doorbell (and hide in the bushes).
|
||||
* @dst: The handlle identifying the doorbell resource
|
||||
* @priv_flags: Priviledge flags.
|
||||
*
|
||||
* Generates a notification on the doorbell identified by the
|
||||
* handle. For host side generation of notifications, the caller
|
||||
* can specify what the privilege of the calling side is.
|
||||
*/
|
||||
int vmci_doorbell_notify(struct vmci_handle dst, u32 priv_flags)
|
||||
{
|
||||
int retval;
|
||||
enum vmci_route route;
|
||||
struct vmci_handle src;
|
||||
|
||||
if (vmci_handle_is_invalid(dst) ||
|
||||
(priv_flags & ~VMCI_PRIVILEGE_ALL_FLAGS))
|
||||
return VMCI_ERROR_INVALID_ARGS;
|
||||
|
||||
src = VMCI_INVALID_HANDLE;
|
||||
retval = vmci_route(&src, &dst, false, &route);
|
||||
if (retval < VMCI_SUCCESS)
|
||||
return retval;
|
||||
|
||||
if (VMCI_ROUTE_AS_HOST == route)
|
||||
return vmci_ctx_notify_dbell(VMCI_HOST_CONTEXT_ID,
|
||||
dst, priv_flags);
|
||||
|
||||
if (VMCI_ROUTE_AS_GUEST == route)
|
||||
return dbell_notify_as_guest(dst, priv_flags);
|
||||
|
||||
pr_warn("Unknown route (%d) for doorbell\n", route);
|
||||
return VMCI_ERROR_DST_UNREACHABLE;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(vmci_doorbell_notify);
|
51
drivers/misc/vmw_vmci/vmci_doorbell.h
Normal file
51
drivers/misc/vmw_vmci/vmci_doorbell.h
Normal file
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* VMware VMCI Driver
|
||||
*
|
||||
* Copyright (C) 2012 VMware, Inc. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation version 2 and no later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* for more details.
|
||||
*/
|
||||
|
||||
#ifndef VMCI_DOORBELL_H
|
||||
#define VMCI_DOORBELL_H
|
||||
|
||||
#include <linux/vmw_vmci_defs.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#include "vmci_driver.h"
|
||||
|
||||
/*
|
||||
* VMCINotifyResourceInfo: Used to create and destroy doorbells, and
|
||||
* generate a notification for a doorbell or queue pair.
|
||||
*/
|
||||
struct vmci_dbell_notify_resource_info {
|
||||
struct vmci_handle handle;
|
||||
u16 resource;
|
||||
u16 action;
|
||||
s32 result;
|
||||
};
|
||||
|
||||
/*
|
||||
* Structure used for checkpointing the doorbell mappings. It is
|
||||
* written to the checkpoint as is, so changing this structure will
|
||||
* break checkpoint compatibility.
|
||||
*/
|
||||
struct dbell_cpt_state {
|
||||
struct vmci_handle handle;
|
||||
u64 bitmap_idx;
|
||||
};
|
||||
|
||||
int vmci_dbell_host_context_notify(u32 src_cid, struct vmci_handle handle);
|
||||
int vmci_dbell_get_priv_flags(struct vmci_handle handle, u32 *priv_flags);
|
||||
|
||||
bool vmci_dbell_register_notification_bitmap(u32 bitmap_ppn);
|
||||
void vmci_dbell_scan_notification_entries(u8 *bitmap);
|
||||
|
||||
#endif /* VMCI_DOORBELL_H */
|
117
drivers/misc/vmw_vmci/vmci_driver.c
Normal file
117
drivers/misc/vmw_vmci/vmci_driver.c
Normal file
|
@ -0,0 +1,117 @@
|
|||
/*
|
||||
* VMware VMCI Driver
|
||||
*
|
||||
* Copyright (C) 2012 VMware, Inc. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation version 2 and no later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* for more details.
|
||||
*/
|
||||
|
||||
#include <linux/vmw_vmci_defs.h>
|
||||
#include <linux/vmw_vmci_api.h>
|
||||
#include <linux/atomic.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
|
||||
#include "vmci_driver.h"
|
||||
#include "vmci_event.h"
|
||||
|
||||
static bool vmci_disable_host;
|
||||
module_param_named(disable_host, vmci_disable_host, bool, 0);
|
||||
MODULE_PARM_DESC(disable_host,
|
||||
"Disable driver host personality (default=enabled)");
|
||||
|
||||
static bool vmci_disable_guest;
|
||||
module_param_named(disable_guest, vmci_disable_guest, bool, 0);
|
||||
MODULE_PARM_DESC(disable_guest,
|
||||
"Disable driver guest personality (default=enabled)");
|
||||
|
||||
static bool vmci_guest_personality_initialized;
|
||||
static bool vmci_host_personality_initialized;
|
||||
|
||||
/*
|
||||
* vmci_get_context_id() - Gets the current context ID.
|
||||
*
|
||||
* Returns the current context ID. Note that since this is accessed only
|
||||
* from code running in the host, this always returns the host context ID.
|
||||
*/
|
||||
u32 vmci_get_context_id(void)
|
||||
{
|
||||
if (vmci_guest_code_active())
|
||||
return vmci_get_vm_context_id();
|
||||
else if (vmci_host_code_active())
|
||||
return VMCI_HOST_CONTEXT_ID;
|
||||
|
||||
return VMCI_INVALID_ID;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(vmci_get_context_id);
|
||||
|
||||
static int __init vmci_drv_init(void)
|
||||
{
|
||||
int vmci_err;
|
||||
int error;
|
||||
|
||||
vmci_err = vmci_event_init();
|
||||
if (vmci_err < VMCI_SUCCESS) {
|
||||
pr_err("Failed to initialize VMCIEvent (result=%d)\n",
|
||||
vmci_err);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!vmci_disable_guest) {
|
||||
error = vmci_guest_init();
|
||||
if (error) {
|
||||
pr_warn("Failed to initialize guest personality (err=%d)\n",
|
||||
error);
|
||||
} else {
|
||||
vmci_guest_personality_initialized = true;
|
||||
pr_info("Guest personality initialized and is %s\n",
|
||||
vmci_guest_code_active() ?
|
||||
"active" : "inactive");
|
||||
}
|
||||
}
|
||||
|
||||
if (!vmci_disable_host) {
|
||||
error = vmci_host_init();
|
||||
if (error) {
|
||||
pr_warn("Unable to initialize host personality (err=%d)\n",
|
||||
error);
|
||||
} else {
|
||||
vmci_host_personality_initialized = true;
|
||||
pr_info("Initialized host personality\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (!vmci_guest_personality_initialized &&
|
||||
!vmci_host_personality_initialized) {
|
||||
vmci_event_exit();
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
module_init(vmci_drv_init);
|
||||
|
||||
static void __exit vmci_drv_exit(void)
|
||||
{
|
||||
if (vmci_guest_personality_initialized)
|
||||
vmci_guest_exit();
|
||||
|
||||
if (vmci_host_personality_initialized)
|
||||
vmci_host_exit();
|
||||
|
||||
vmci_event_exit();
|
||||
}
|
||||
module_exit(vmci_drv_exit);
|
||||
|
||||
MODULE_AUTHOR("VMware, Inc.");
|
||||
MODULE_DESCRIPTION("VMware Virtual Machine Communication Interface.");
|
||||
MODULE_VERSION("1.1.0.0-k");
|
||||
MODULE_LICENSE("GPL v2");
|
57
drivers/misc/vmw_vmci/vmci_driver.h
Normal file
57
drivers/misc/vmw_vmci/vmci_driver.h
Normal file
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* VMware VMCI Driver
|
||||
*
|
||||
* Copyright (C) 2012 VMware, Inc. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation version 2 and no later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* for more details.
|
||||
*/
|
||||
|
||||
#ifndef _VMCI_DRIVER_H_
|
||||
#define _VMCI_DRIVER_H_
|
||||
|
||||
#include <linux/vmw_vmci_defs.h>
|
||||
#include <linux/wait.h>
|
||||
|
||||
#include "vmci_queue_pair.h"
|
||||
#include "vmci_context.h"
|
||||
|
||||
enum vmci_obj_type {
|
||||
VMCIOBJ_VMX_VM = 10,
|
||||
VMCIOBJ_CONTEXT,
|
||||
VMCIOBJ_SOCKET,
|
||||
VMCIOBJ_NOT_SET,
|
||||
};
|
||||
|
||||
/* For storing VMCI structures in file handles. */
|
||||
struct vmci_obj {
|
||||
void *ptr;
|
||||
enum vmci_obj_type type;
|
||||
};
|
||||
|
||||
/*
|
||||
* Needed by other components of this module. It's okay to have one global
|
||||
* instance of this because there can only ever be one VMCI device. Our
|
||||
* virtual hardware enforces this.
|
||||
*/
|
||||
extern struct pci_dev *vmci_pdev;
|
||||
|
||||
u32 vmci_get_context_id(void);
|
||||
int vmci_send_datagram(struct vmci_datagram *dg);
|
||||
|
||||
int vmci_host_init(void);
|
||||
void vmci_host_exit(void);
|
||||
bool vmci_host_code_active(void);
|
||||
|
||||
int vmci_guest_init(void);
|
||||
void vmci_guest_exit(void);
|
||||
bool vmci_guest_code_active(void);
|
||||
u32 vmci_get_vm_context_id(void);
|
||||
|
||||
#endif /* _VMCI_DRIVER_H_ */
|
224
drivers/misc/vmw_vmci/vmci_event.c
Normal file
224
drivers/misc/vmw_vmci/vmci_event.c
Normal file
|
@ -0,0 +1,224 @@
|
|||
/*
|
||||
* VMware VMCI Driver
|
||||
*
|
||||
* Copyright (C) 2012 VMware, Inc. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation version 2 and no later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* for more details.
|
||||
*/
|
||||
|
||||
#include <linux/vmw_vmci_defs.h>
|
||||
#include <linux/vmw_vmci_api.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include "vmci_driver.h"
|
||||
#include "vmci_event.h"
|
||||
|
||||
#define EVENT_MAGIC 0xEABE0000
|
||||
#define VMCI_EVENT_MAX_ATTEMPTS 10
|
||||
|
||||
struct vmci_subscription {
|
||||
u32 id;
|
||||
u32 event;
|
||||
vmci_event_cb callback;
|
||||
void *callback_data;
|
||||
struct list_head node; /* on one of subscriber lists */
|
||||
};
|
||||
|
||||
static struct list_head subscriber_array[VMCI_EVENT_MAX];
|
||||
static DEFINE_MUTEX(subscriber_mutex);
|
||||
|
||||
int __init vmci_event_init(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < VMCI_EVENT_MAX; i++)
|
||||
INIT_LIST_HEAD(&subscriber_array[i]);
|
||||
|
||||
return VMCI_SUCCESS;
|
||||
}
|
||||
|
||||
void vmci_event_exit(void)
|
||||
{
|
||||
int e;
|
||||
|
||||
/* We free all memory at exit. */
|
||||
for (e = 0; e < VMCI_EVENT_MAX; e++) {
|
||||
struct vmci_subscription *cur, *p2;
|
||||
list_for_each_entry_safe(cur, p2, &subscriber_array[e], node) {
|
||||
|
||||
/*
|
||||
* We should never get here because all events
|
||||
* should have been unregistered before we try
|
||||
* to unload the driver module.
|
||||
*/
|
||||
pr_warn("Unexpected free events occurring\n");
|
||||
list_del(&cur->node);
|
||||
kfree(cur);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Find entry. Assumes subscriber_mutex is held.
|
||||
*/
|
||||
static struct vmci_subscription *event_find(u32 sub_id)
|
||||
{
|
||||
int e;
|
||||
|
||||
for (e = 0; e < VMCI_EVENT_MAX; e++) {
|
||||
struct vmci_subscription *cur;
|
||||
list_for_each_entry(cur, &subscriber_array[e], node) {
|
||||
if (cur->id == sub_id)
|
||||
return cur;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Actually delivers the events to the subscribers.
|
||||
* The callback function for each subscriber is invoked.
|
||||
*/
|
||||
static void event_deliver(struct vmci_event_msg *event_msg)
|
||||
{
|
||||
struct vmci_subscription *cur;
|
||||
struct list_head *subscriber_list;
|
||||
|
||||
rcu_read_lock();
|
||||
subscriber_list = &subscriber_array[event_msg->event_data.event];
|
||||
list_for_each_entry_rcu(cur, subscriber_list, node) {
|
||||
cur->callback(cur->id, &event_msg->event_data,
|
||||
cur->callback_data);
|
||||
}
|
||||
rcu_read_unlock();
|
||||
}
|
||||
|
||||
/*
|
||||
* Dispatcher for the VMCI_EVENT_RECEIVE datagrams. Calls all
|
||||
* subscribers for given event.
|
||||
*/
|
||||
int vmci_event_dispatch(struct vmci_datagram *msg)
|
||||
{
|
||||
struct vmci_event_msg *event_msg = (struct vmci_event_msg *)msg;
|
||||
|
||||
if (msg->payload_size < sizeof(u32) ||
|
||||
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;
|
||||
|
||||
event_deliver(event_msg);
|
||||
return VMCI_SUCCESS;
|
||||
}
|
||||
|
||||
/*
|
||||
* vmci_event_subscribe() - Subscribe to a given event.
|
||||
* @event: The event to subscribe to.
|
||||
* @callback: The callback to invoke upon the event.
|
||||
* @callback_data: Data to pass to the callback.
|
||||
* @subscription_id: ID used to track subscription. Used with
|
||||
* vmci_event_unsubscribe()
|
||||
*
|
||||
* Subscribes to the provided event. The callback specified will be
|
||||
* fired from RCU critical section and therefore must not sleep.
|
||||
*/
|
||||
int vmci_event_subscribe(u32 event,
|
||||
vmci_event_cb callback,
|
||||
void *callback_data,
|
||||
u32 *new_subscription_id)
|
||||
{
|
||||
struct vmci_subscription *sub;
|
||||
int attempts;
|
||||
int retval;
|
||||
bool have_new_id = false;
|
||||
|
||||
if (!new_subscription_id) {
|
||||
pr_devel("%s: Invalid subscription (NULL)\n", __func__);
|
||||
return VMCI_ERROR_INVALID_ARGS;
|
||||
}
|
||||
|
||||
if (!VMCI_EVENT_VALID(event) || !callback) {
|
||||
pr_devel("%s: Failed to subscribe to event (type=%d) (callback=%p) (data=%p)\n",
|
||||
__func__, event, callback, callback_data);
|
||||
return VMCI_ERROR_INVALID_ARGS;
|
||||
}
|
||||
|
||||
sub = kzalloc(sizeof(*sub), GFP_KERNEL);
|
||||
if (!sub)
|
||||
return VMCI_ERROR_NO_MEM;
|
||||
|
||||
sub->id = VMCI_EVENT_MAX;
|
||||
sub->event = event;
|
||||
sub->callback = callback;
|
||||
sub->callback_data = callback_data;
|
||||
INIT_LIST_HEAD(&sub->node);
|
||||
|
||||
mutex_lock(&subscriber_mutex);
|
||||
|
||||
/* Creation of a new event is always allowed. */
|
||||
for (attempts = 0; attempts < VMCI_EVENT_MAX_ATTEMPTS; attempts++) {
|
||||
static u32 subscription_id;
|
||||
/*
|
||||
* We try to get an id a couple of time before
|
||||
* claiming we are out of resources.
|
||||
*/
|
||||
|
||||
/* Test for duplicate id. */
|
||||
if (!event_find(++subscription_id)) {
|
||||
sub->id = subscription_id;
|
||||
have_new_id = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (have_new_id) {
|
||||
list_add_rcu(&sub->node, &subscriber_array[event]);
|
||||
retval = VMCI_SUCCESS;
|
||||
} else {
|
||||
retval = VMCI_ERROR_NO_RESOURCES;
|
||||
}
|
||||
|
||||
mutex_unlock(&subscriber_mutex);
|
||||
|
||||
*new_subscription_id = sub->id;
|
||||
return retval;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(vmci_event_subscribe);
|
||||
|
||||
/*
|
||||
* vmci_event_unsubscribe() - unsubscribe from an event.
|
||||
* @sub_id: A subscription ID as provided by vmci_event_subscribe()
|
||||
*
|
||||
* Unsubscribe from given event. Removes it from list and frees it.
|
||||
* Will return callback_data if requested by caller.
|
||||
*/
|
||||
int vmci_event_unsubscribe(u32 sub_id)
|
||||
{
|
||||
struct vmci_subscription *s;
|
||||
|
||||
mutex_lock(&subscriber_mutex);
|
||||
s = event_find(sub_id);
|
||||
if (s)
|
||||
list_del_rcu(&s->node);
|
||||
mutex_unlock(&subscriber_mutex);
|
||||
|
||||
if (!s)
|
||||
return VMCI_ERROR_NOT_FOUND;
|
||||
|
||||
synchronize_rcu();
|
||||
kfree(s);
|
||||
|
||||
return VMCI_SUCCESS;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(vmci_event_unsubscribe);
|
25
drivers/misc/vmw_vmci/vmci_event.h
Normal file
25
drivers/misc/vmw_vmci/vmci_event.h
Normal file
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* VMware VMCI Driver
|
||||
*
|
||||
* Copyright (C) 2012 VMware, Inc. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation version 2 and no later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* for more details.
|
||||
*/
|
||||
|
||||
#ifndef __VMCI_EVENT_H__
|
||||
#define __VMCI_EVENT_H__
|
||||
|
||||
#include <linux/vmw_vmci_api.h>
|
||||
|
||||
int vmci_event_init(void);
|
||||
void vmci_event_exit(void);
|
||||
int vmci_event_dispatch(struct vmci_datagram *msg);
|
||||
|
||||
#endif /*__VMCI_EVENT_H__ */
|
771
drivers/misc/vmw_vmci/vmci_guest.c
Normal file
771
drivers/misc/vmw_vmci/vmci_guest.c
Normal file
|
@ -0,0 +1,771 @@
|
|||
/*
|
||||
* VMware VMCI Driver
|
||||
*
|
||||
* Copyright (C) 2012 VMware, Inc. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation version 2 and no later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* for more details.
|
||||
*/
|
||||
|
||||
#include <linux/vmw_vmci_defs.h>
|
||||
#include <linux/vmw_vmci_api.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/highmem.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/smp.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/vmalloc.h>
|
||||
|
||||
#include "vmci_datagram.h"
|
||||
#include "vmci_doorbell.h"
|
||||
#include "vmci_context.h"
|
||||
#include "vmci_driver.h"
|
||||
#include "vmci_event.h"
|
||||
|
||||
#define PCI_DEVICE_ID_VMWARE_VMCI 0x0740
|
||||
|
||||
#define VMCI_UTIL_NUM_RESOURCES 1
|
||||
|
||||
static bool vmci_disable_msi;
|
||||
module_param_named(disable_msi, vmci_disable_msi, bool, 0);
|
||||
MODULE_PARM_DESC(disable_msi, "Disable MSI use in driver - (default=0)");
|
||||
|
||||
static bool vmci_disable_msix;
|
||||
module_param_named(disable_msix, vmci_disable_msix, bool, 0);
|
||||
MODULE_PARM_DESC(disable_msix, "Disable MSI-X use in driver - (default=0)");
|
||||
|
||||
static u32 ctx_update_sub_id = VMCI_INVALID_ID;
|
||||
static u32 vm_context_id = VMCI_INVALID_ID;
|
||||
|
||||
struct vmci_guest_device {
|
||||
struct device *dev; /* PCI device we are attached to */
|
||||
void __iomem *iobase;
|
||||
|
||||
unsigned int irq;
|
||||
unsigned int intr_type;
|
||||
bool exclusive_vectors;
|
||||
struct msix_entry msix_entries[VMCI_MAX_INTRS];
|
||||
|
||||
struct tasklet_struct datagram_tasklet;
|
||||
struct tasklet_struct bm_tasklet;
|
||||
|
||||
void *data_buffer;
|
||||
void *notification_bitmap;
|
||||
dma_addr_t notification_base;
|
||||
};
|
||||
|
||||
/* vmci_dev singleton device and supporting data*/
|
||||
struct pci_dev *vmci_pdev;
|
||||
static struct vmci_guest_device *vmci_dev_g;
|
||||
static DEFINE_SPINLOCK(vmci_dev_spinlock);
|
||||
|
||||
static atomic_t vmci_num_guest_devices = ATOMIC_INIT(0);
|
||||
|
||||
bool vmci_guest_code_active(void)
|
||||
{
|
||||
return atomic_read(&vmci_num_guest_devices) != 0;
|
||||
}
|
||||
|
||||
u32 vmci_get_vm_context_id(void)
|
||||
{
|
||||
if (vm_context_id == VMCI_INVALID_ID) {
|
||||
struct vmci_datagram get_cid_msg;
|
||||
get_cid_msg.dst =
|
||||
vmci_make_handle(VMCI_HYPERVISOR_CONTEXT_ID,
|
||||
VMCI_GET_CONTEXT_ID);
|
||||
get_cid_msg.src = VMCI_ANON_SRC_HANDLE;
|
||||
get_cid_msg.payload_size = 0;
|
||||
vm_context_id = vmci_send_datagram(&get_cid_msg);
|
||||
}
|
||||
return vm_context_id;
|
||||
}
|
||||
|
||||
/*
|
||||
* VM to hypervisor call mechanism. We use the standard VMware naming
|
||||
* convention since shared code is calling this function as well.
|
||||
*/
|
||||
int vmci_send_datagram(struct vmci_datagram *dg)
|
||||
{
|
||||
unsigned long flags;
|
||||
int result;
|
||||
|
||||
/* Check args. */
|
||||
if (dg == NULL)
|
||||
return VMCI_ERROR_INVALID_ARGS;
|
||||
|
||||
/*
|
||||
* Need to acquire spinlock on the device because the datagram
|
||||
* data may be spread over multiple pages and the monitor may
|
||||
* interleave device user rpc calls from multiple
|
||||
* VCPUs. Acquiring the spinlock precludes that
|
||||
* possibility. Disabling interrupts to avoid incoming
|
||||
* datagrams during a "rep out" and possibly landing up in
|
||||
* this function.
|
||||
*/
|
||||
spin_lock_irqsave(&vmci_dev_spinlock, flags);
|
||||
|
||||
if (vmci_dev_g) {
|
||||
iowrite8_rep(vmci_dev_g->iobase + VMCI_DATA_OUT_ADDR,
|
||||
dg, VMCI_DG_SIZE(dg));
|
||||
result = ioread32(vmci_dev_g->iobase + VMCI_RESULT_LOW_ADDR);
|
||||
} else {
|
||||
result = VMCI_ERROR_UNAVAILABLE;
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&vmci_dev_spinlock, flags);
|
||||
|
||||
return result;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(vmci_send_datagram);
|
||||
|
||||
/*
|
||||
* Gets called with the new context id if updated or resumed.
|
||||
* Context id.
|
||||
*/
|
||||
static void vmci_guest_cid_update(u32 sub_id,
|
||||
const struct vmci_event_data *event_data,
|
||||
void *client_data)
|
||||
{
|
||||
const struct vmci_event_payld_ctx *ev_payload =
|
||||
vmci_event_data_const_payload(event_data);
|
||||
|
||||
if (sub_id != ctx_update_sub_id) {
|
||||
pr_devel("Invalid subscriber (ID=0x%x)\n", sub_id);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!event_data || ev_payload->context_id == VMCI_INVALID_ID) {
|
||||
pr_devel("Invalid event data\n");
|
||||
return;
|
||||
}
|
||||
|
||||
pr_devel("Updating context from (ID=0x%x) to (ID=0x%x) on event (type=%d)\n",
|
||||
vm_context_id, ev_payload->context_id, event_data->event);
|
||||
|
||||
vm_context_id = ev_payload->context_id;
|
||||
}
|
||||
|
||||
/*
|
||||
* Verify that the host supports the hypercalls we need. If it does not,
|
||||
* try to find fallback hypercalls and use those instead. Returns
|
||||
* true if required hypercalls (or fallback hypercalls) are
|
||||
* supported by the host, false otherwise.
|
||||
*/
|
||||
static int vmci_check_host_caps(struct pci_dev *pdev)
|
||||
{
|
||||
bool result;
|
||||
struct vmci_resource_query_msg *msg;
|
||||
u32 msg_size = sizeof(struct vmci_resource_query_hdr) +
|
||||
VMCI_UTIL_NUM_RESOURCES * sizeof(u32);
|
||||
struct vmci_datagram *check_msg;
|
||||
|
||||
check_msg = kmalloc(msg_size, GFP_KERNEL);
|
||||
if (!check_msg) {
|
||||
dev_err(&pdev->dev, "%s: Insufficient memory\n", __func__);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
check_msg->dst = vmci_make_handle(VMCI_HYPERVISOR_CONTEXT_ID,
|
||||
VMCI_RESOURCES_QUERY);
|
||||
check_msg->src = VMCI_ANON_SRC_HANDLE;
|
||||
check_msg->payload_size = msg_size - VMCI_DG_HEADERSIZE;
|
||||
msg = (struct vmci_resource_query_msg *)VMCI_DG_PAYLOAD(check_msg);
|
||||
|
||||
msg->num_resources = VMCI_UTIL_NUM_RESOURCES;
|
||||
msg->resources[0] = VMCI_GET_CONTEXT_ID;
|
||||
|
||||
/* Checks that hyper calls are supported */
|
||||
result = vmci_send_datagram(check_msg) == 0x01;
|
||||
kfree(check_msg);
|
||||
|
||||
dev_dbg(&pdev->dev, "%s: Host capability check: %s\n",
|
||||
__func__, result ? "PASSED" : "FAILED");
|
||||
|
||||
/* We need the vector. There are no fallbacks. */
|
||||
return result ? 0 : -ENXIO;
|
||||
}
|
||||
|
||||
/*
|
||||
* Reads datagrams from the data in port and dispatches them. We
|
||||
* always start reading datagrams into only the first page of the
|
||||
* datagram buffer. If the datagrams don't fit into one page, we
|
||||
* use the maximum datagram buffer size for the remainder of the
|
||||
* invocation. This is a simple heuristic for not penalizing
|
||||
* small datagrams.
|
||||
*
|
||||
* This function assumes that it has exclusive access to the data
|
||||
* in port for the duration of the call.
|
||||
*/
|
||||
static void vmci_dispatch_dgs(unsigned long data)
|
||||
{
|
||||
struct vmci_guest_device *vmci_dev = (struct vmci_guest_device *)data;
|
||||
u8 *dg_in_buffer = vmci_dev->data_buffer;
|
||||
struct vmci_datagram *dg;
|
||||
size_t dg_in_buffer_size = VMCI_MAX_DG_SIZE;
|
||||
size_t current_dg_in_buffer_size = PAGE_SIZE;
|
||||
size_t remaining_bytes;
|
||||
|
||||
BUILD_BUG_ON(VMCI_MAX_DG_SIZE < PAGE_SIZE);
|
||||
|
||||
ioread8_rep(vmci_dev->iobase + VMCI_DATA_IN_ADDR,
|
||||
vmci_dev->data_buffer, current_dg_in_buffer_size);
|
||||
dg = (struct vmci_datagram *)dg_in_buffer;
|
||||
remaining_bytes = current_dg_in_buffer_size;
|
||||
|
||||
while (dg->dst.resource != VMCI_INVALID_ID ||
|
||||
remaining_bytes > PAGE_SIZE) {
|
||||
unsigned dg_in_size;
|
||||
|
||||
/*
|
||||
* When the input buffer spans multiple pages, a datagram can
|
||||
* start on any page boundary in the buffer.
|
||||
*/
|
||||
if (dg->dst.resource == VMCI_INVALID_ID) {
|
||||
dg = (struct vmci_datagram *)roundup(
|
||||
(uintptr_t)dg + 1, PAGE_SIZE);
|
||||
remaining_bytes =
|
||||
(size_t)(dg_in_buffer +
|
||||
current_dg_in_buffer_size -
|
||||
(u8 *)dg);
|
||||
continue;
|
||||
}
|
||||
|
||||
dg_in_size = VMCI_DG_SIZE_ALIGNED(dg);
|
||||
|
||||
if (dg_in_size <= dg_in_buffer_size) {
|
||||
int result;
|
||||
|
||||
/*
|
||||
* If the remaining bytes in the datagram
|
||||
* buffer doesn't contain the complete
|
||||
* datagram, we first make sure we have enough
|
||||
* room for it and then we read the reminder
|
||||
* of the datagram and possibly any following
|
||||
* datagrams.
|
||||
*/
|
||||
if (dg_in_size > remaining_bytes) {
|
||||
if (remaining_bytes !=
|
||||
current_dg_in_buffer_size) {
|
||||
|
||||
/*
|
||||
* We move the partial
|
||||
* datagram to the front and
|
||||
* read the reminder of the
|
||||
* datagram and possibly
|
||||
* following calls into the
|
||||
* following bytes.
|
||||
*/
|
||||
memmove(dg_in_buffer, dg_in_buffer +
|
||||
current_dg_in_buffer_size -
|
||||
remaining_bytes,
|
||||
remaining_bytes);
|
||||
dg = (struct vmci_datagram *)
|
||||
dg_in_buffer;
|
||||
}
|
||||
|
||||
if (current_dg_in_buffer_size !=
|
||||
dg_in_buffer_size)
|
||||
current_dg_in_buffer_size =
|
||||
dg_in_buffer_size;
|
||||
|
||||
ioread8_rep(vmci_dev->iobase +
|
||||
VMCI_DATA_IN_ADDR,
|
||||
vmci_dev->data_buffer +
|
||||
remaining_bytes,
|
||||
current_dg_in_buffer_size -
|
||||
remaining_bytes);
|
||||
}
|
||||
|
||||
/*
|
||||
* We special case event datagrams from the
|
||||
* hypervisor.
|
||||
*/
|
||||
if (dg->src.context == VMCI_HYPERVISOR_CONTEXT_ID &&
|
||||
dg->dst.resource == VMCI_EVENT_HANDLER) {
|
||||
result = vmci_event_dispatch(dg);
|
||||
} else {
|
||||
result = vmci_datagram_invoke_guest_handler(dg);
|
||||
}
|
||||
if (result < VMCI_SUCCESS)
|
||||
dev_dbg(vmci_dev->dev,
|
||||
"Datagram with resource (ID=0x%x) failed (err=%d)\n",
|
||||
dg->dst.resource, result);
|
||||
|
||||
/* On to the next datagram. */
|
||||
dg = (struct vmci_datagram *)((u8 *)dg +
|
||||
dg_in_size);
|
||||
} else {
|
||||
size_t bytes_to_skip;
|
||||
|
||||
/*
|
||||
* Datagram doesn't fit in datagram buffer of maximal
|
||||
* size. We drop it.
|
||||
*/
|
||||
dev_dbg(vmci_dev->dev,
|
||||
"Failed to receive datagram (size=%u bytes)\n",
|
||||
dg_in_size);
|
||||
|
||||
bytes_to_skip = dg_in_size - remaining_bytes;
|
||||
if (current_dg_in_buffer_size != dg_in_buffer_size)
|
||||
current_dg_in_buffer_size = dg_in_buffer_size;
|
||||
|
||||
for (;;) {
|
||||
ioread8_rep(vmci_dev->iobase +
|
||||
VMCI_DATA_IN_ADDR,
|
||||
vmci_dev->data_buffer,
|
||||
current_dg_in_buffer_size);
|
||||
if (bytes_to_skip <= current_dg_in_buffer_size)
|
||||
break;
|
||||
|
||||
bytes_to_skip -= current_dg_in_buffer_size;
|
||||
}
|
||||
dg = (struct vmci_datagram *)(dg_in_buffer +
|
||||
bytes_to_skip);
|
||||
}
|
||||
|
||||
remaining_bytes =
|
||||
(size_t) (dg_in_buffer + current_dg_in_buffer_size -
|
||||
(u8 *)dg);
|
||||
|
||||
if (remaining_bytes < VMCI_DG_HEADERSIZE) {
|
||||
/* Get the next batch of datagrams. */
|
||||
|
||||
ioread8_rep(vmci_dev->iobase + VMCI_DATA_IN_ADDR,
|
||||
vmci_dev->data_buffer,
|
||||
current_dg_in_buffer_size);
|
||||
dg = (struct vmci_datagram *)dg_in_buffer;
|
||||
remaining_bytes = current_dg_in_buffer_size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Scans the notification bitmap for raised flags, clears them
|
||||
* and handles the notifications.
|
||||
*/
|
||||
static void vmci_process_bitmap(unsigned long data)
|
||||
{
|
||||
struct vmci_guest_device *dev = (struct vmci_guest_device *)data;
|
||||
|
||||
if (!dev->notification_bitmap) {
|
||||
dev_dbg(dev->dev, "No bitmap present in %s\n", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
vmci_dbell_scan_notification_entries(dev->notification_bitmap);
|
||||
}
|
||||
|
||||
/*
|
||||
* Enable MSI-X. Try exclusive vectors first, then shared vectors.
|
||||
*/
|
||||
static int vmci_enable_msix(struct pci_dev *pdev,
|
||||
struct vmci_guest_device *vmci_dev)
|
||||
{
|
||||
int i;
|
||||
int result;
|
||||
|
||||
for (i = 0; i < VMCI_MAX_INTRS; ++i) {
|
||||
vmci_dev->msix_entries[i].entry = i;
|
||||
vmci_dev->msix_entries[i].vector = i;
|
||||
}
|
||||
|
||||
result = pci_enable_msix_exact(pdev,
|
||||
vmci_dev->msix_entries, VMCI_MAX_INTRS);
|
||||
if (result == 0)
|
||||
vmci_dev->exclusive_vectors = true;
|
||||
else if (result == -ENOSPC)
|
||||
result = pci_enable_msix_exact(pdev, vmci_dev->msix_entries, 1);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Interrupt handler for legacy or MSI interrupt, or for first MSI-X
|
||||
* interrupt (vector VMCI_INTR_DATAGRAM).
|
||||
*/
|
||||
static irqreturn_t vmci_interrupt(int irq, void *_dev)
|
||||
{
|
||||
struct vmci_guest_device *dev = _dev;
|
||||
|
||||
/*
|
||||
* If we are using MSI-X with exclusive vectors then we simply schedule
|
||||
* the datagram tasklet, since we know the interrupt was meant for us.
|
||||
* Otherwise we must read the ICR to determine what to do.
|
||||
*/
|
||||
|
||||
if (dev->intr_type == VMCI_INTR_TYPE_MSIX && dev->exclusive_vectors) {
|
||||
tasklet_schedule(&dev->datagram_tasklet);
|
||||
} else {
|
||||
unsigned int icr;
|
||||
|
||||
/* Acknowledge interrupt and determine what needs doing. */
|
||||
icr = ioread32(dev->iobase + VMCI_ICR_ADDR);
|
||||
if (icr == 0 || icr == ~0)
|
||||
return IRQ_NONE;
|
||||
|
||||
if (icr & VMCI_ICR_DATAGRAM) {
|
||||
tasklet_schedule(&dev->datagram_tasklet);
|
||||
icr &= ~VMCI_ICR_DATAGRAM;
|
||||
}
|
||||
|
||||
if (icr & VMCI_ICR_NOTIFICATION) {
|
||||
tasklet_schedule(&dev->bm_tasklet);
|
||||
icr &= ~VMCI_ICR_NOTIFICATION;
|
||||
}
|
||||
|
||||
if (icr != 0)
|
||||
dev_warn(dev->dev,
|
||||
"Ignoring unknown interrupt cause (%d)\n",
|
||||
icr);
|
||||
}
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
/*
|
||||
* Interrupt handler for MSI-X interrupt vector VMCI_INTR_NOTIFICATION,
|
||||
* which is for the notification bitmap. Will only get called if we are
|
||||
* using MSI-X with exclusive vectors.
|
||||
*/
|
||||
static irqreturn_t vmci_interrupt_bm(int irq, void *_dev)
|
||||
{
|
||||
struct vmci_guest_device *dev = _dev;
|
||||
|
||||
/* For MSI-X we can just assume it was meant for us. */
|
||||
tasklet_schedule(&dev->bm_tasklet);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
/*
|
||||
* Most of the initialization at module load time is done here.
|
||||
*/
|
||||
static int vmci_guest_probe_device(struct pci_dev *pdev,
|
||||
const struct pci_device_id *id)
|
||||
{
|
||||
struct vmci_guest_device *vmci_dev;
|
||||
void __iomem *iobase;
|
||||
unsigned int capabilities;
|
||||
unsigned long cmd;
|
||||
int vmci_err;
|
||||
int error;
|
||||
|
||||
dev_dbg(&pdev->dev, "Probing for vmci/PCI guest device\n");
|
||||
|
||||
error = pcim_enable_device(pdev);
|
||||
if (error) {
|
||||
dev_err(&pdev->dev,
|
||||
"Failed to enable VMCI device: %d\n", error);
|
||||
return error;
|
||||
}
|
||||
|
||||
error = pcim_iomap_regions(pdev, 1 << 0, KBUILD_MODNAME);
|
||||
if (error) {
|
||||
dev_err(&pdev->dev, "Failed to reserve/map IO regions\n");
|
||||
return error;
|
||||
}
|
||||
|
||||
iobase = pcim_iomap_table(pdev)[0];
|
||||
|
||||
dev_info(&pdev->dev, "Found VMCI PCI device at %#lx, irq %u\n",
|
||||
(unsigned long)iobase, pdev->irq);
|
||||
|
||||
vmci_dev = devm_kzalloc(&pdev->dev, sizeof(*vmci_dev), GFP_KERNEL);
|
||||
if (!vmci_dev) {
|
||||
dev_err(&pdev->dev,
|
||||
"Can't allocate memory for VMCI device\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
vmci_dev->dev = &pdev->dev;
|
||||
vmci_dev->intr_type = VMCI_INTR_TYPE_INTX;
|
||||
vmci_dev->exclusive_vectors = false;
|
||||
vmci_dev->iobase = iobase;
|
||||
|
||||
tasklet_init(&vmci_dev->datagram_tasklet,
|
||||
vmci_dispatch_dgs, (unsigned long)vmci_dev);
|
||||
tasklet_init(&vmci_dev->bm_tasklet,
|
||||
vmci_process_bitmap, (unsigned long)vmci_dev);
|
||||
|
||||
vmci_dev->data_buffer = vmalloc(VMCI_MAX_DG_SIZE);
|
||||
if (!vmci_dev->data_buffer) {
|
||||
dev_err(&pdev->dev,
|
||||
"Can't allocate memory for datagram buffer\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
pci_set_master(pdev); /* To enable queue_pair functionality. */
|
||||
|
||||
/*
|
||||
* Verify that the VMCI Device supports the capabilities that
|
||||
* we need. If the device is missing capabilities that we would
|
||||
* like to use, check for fallback capabilities and use those
|
||||
* instead (so we can run a new VM on old hosts). Fail the load if
|
||||
* a required capability is missing and there is no fallback.
|
||||
*
|
||||
* Right now, we need datagrams. There are no fallbacks.
|
||||
*/
|
||||
capabilities = ioread32(vmci_dev->iobase + VMCI_CAPS_ADDR);
|
||||
if (!(capabilities & VMCI_CAPS_DATAGRAM)) {
|
||||
dev_err(&pdev->dev, "Device does not support datagrams\n");
|
||||
error = -ENXIO;
|
||||
goto err_free_data_buffer;
|
||||
}
|
||||
|
||||
/*
|
||||
* If the hardware supports notifications, we will use that as
|
||||
* well.
|
||||
*/
|
||||
if (capabilities & VMCI_CAPS_NOTIFICATIONS) {
|
||||
vmci_dev->notification_bitmap = dma_alloc_coherent(
|
||||
&pdev->dev, PAGE_SIZE, &vmci_dev->notification_base,
|
||||
GFP_KERNEL);
|
||||
if (!vmci_dev->notification_bitmap) {
|
||||
dev_warn(&pdev->dev,
|
||||
"Unable to allocate notification bitmap\n");
|
||||
} else {
|
||||
memset(vmci_dev->notification_bitmap, 0, PAGE_SIZE);
|
||||
capabilities |= VMCI_CAPS_NOTIFICATIONS;
|
||||
}
|
||||
}
|
||||
|
||||
dev_info(&pdev->dev, "Using capabilities 0x%x\n", capabilities);
|
||||
|
||||
/* Let the host know which capabilities we intend to use. */
|
||||
iowrite32(capabilities, vmci_dev->iobase + VMCI_CAPS_ADDR);
|
||||
|
||||
/* Set up global device so that we can start sending datagrams */
|
||||
spin_lock_irq(&vmci_dev_spinlock);
|
||||
vmci_dev_g = vmci_dev;
|
||||
vmci_pdev = pdev;
|
||||
spin_unlock_irq(&vmci_dev_spinlock);
|
||||
|
||||
/*
|
||||
* Register notification bitmap with device if that capability is
|
||||
* used.
|
||||
*/
|
||||
if (capabilities & VMCI_CAPS_NOTIFICATIONS) {
|
||||
unsigned long bitmap_ppn =
|
||||
vmci_dev->notification_base >> PAGE_SHIFT;
|
||||
if (!vmci_dbell_register_notification_bitmap(bitmap_ppn)) {
|
||||
dev_warn(&pdev->dev,
|
||||
"VMCI device unable to register notification bitmap with PPN 0x%x\n",
|
||||
(u32) bitmap_ppn);
|
||||
error = -ENXIO;
|
||||
goto err_remove_vmci_dev_g;
|
||||
}
|
||||
}
|
||||
|
||||
/* Check host capabilities. */
|
||||
error = vmci_check_host_caps(pdev);
|
||||
if (error)
|
||||
goto err_remove_bitmap;
|
||||
|
||||
/* Enable device. */
|
||||
|
||||
/*
|
||||
* We subscribe to the VMCI_EVENT_CTX_ID_UPDATE here so we can
|
||||
* update the internal context id when needed.
|
||||
*/
|
||||
vmci_err = vmci_event_subscribe(VMCI_EVENT_CTX_ID_UPDATE,
|
||||
vmci_guest_cid_update, NULL,
|
||||
&ctx_update_sub_id);
|
||||
if (vmci_err < VMCI_SUCCESS)
|
||||
dev_warn(&pdev->dev,
|
||||
"Failed to subscribe to event (type=%d): %d\n",
|
||||
VMCI_EVENT_CTX_ID_UPDATE, vmci_err);
|
||||
|
||||
/*
|
||||
* Enable interrupts. Try MSI-X first, then MSI, and then fallback on
|
||||
* legacy interrupts.
|
||||
*/
|
||||
if (!vmci_disable_msix && !vmci_enable_msix(pdev, vmci_dev)) {
|
||||
vmci_dev->intr_type = VMCI_INTR_TYPE_MSIX;
|
||||
vmci_dev->irq = vmci_dev->msix_entries[0].vector;
|
||||
} else if (!vmci_disable_msi && !pci_enable_msi(pdev)) {
|
||||
vmci_dev->intr_type = VMCI_INTR_TYPE_MSI;
|
||||
vmci_dev->irq = pdev->irq;
|
||||
} else {
|
||||
vmci_dev->intr_type = VMCI_INTR_TYPE_INTX;
|
||||
vmci_dev->irq = pdev->irq;
|
||||
}
|
||||
|
||||
/*
|
||||
* Request IRQ for legacy or MSI interrupts, or for first
|
||||
* MSI-X vector.
|
||||
*/
|
||||
error = request_irq(vmci_dev->irq, vmci_interrupt, IRQF_SHARED,
|
||||
KBUILD_MODNAME, vmci_dev);
|
||||
if (error) {
|
||||
dev_err(&pdev->dev, "Irq %u in use: %d\n",
|
||||
vmci_dev->irq, error);
|
||||
goto err_disable_msi;
|
||||
}
|
||||
|
||||
/*
|
||||
* For MSI-X with exclusive vectors we need to request an
|
||||
* interrupt for each vector so that we get a separate
|
||||
* interrupt handler routine. This allows us to distinguish
|
||||
* between the vectors.
|
||||
*/
|
||||
if (vmci_dev->exclusive_vectors) {
|
||||
error = request_irq(vmci_dev->msix_entries[1].vector,
|
||||
vmci_interrupt_bm, 0, KBUILD_MODNAME,
|
||||
vmci_dev);
|
||||
if (error) {
|
||||
dev_err(&pdev->dev,
|
||||
"Failed to allocate irq %u: %d\n",
|
||||
vmci_dev->msix_entries[1].vector, error);
|
||||
goto err_free_irq;
|
||||
}
|
||||
}
|
||||
|
||||
dev_dbg(&pdev->dev, "Registered device\n");
|
||||
|
||||
atomic_inc(&vmci_num_guest_devices);
|
||||
|
||||
/* Enable specific interrupt bits. */
|
||||
cmd = VMCI_IMR_DATAGRAM;
|
||||
if (capabilities & VMCI_CAPS_NOTIFICATIONS)
|
||||
cmd |= VMCI_IMR_NOTIFICATION;
|
||||
iowrite32(cmd, vmci_dev->iobase + VMCI_IMR_ADDR);
|
||||
|
||||
/* Enable interrupts. */
|
||||
iowrite32(VMCI_CONTROL_INT_ENABLE,
|
||||
vmci_dev->iobase + VMCI_CONTROL_ADDR);
|
||||
|
||||
pci_set_drvdata(pdev, vmci_dev);
|
||||
return 0;
|
||||
|
||||
err_free_irq:
|
||||
free_irq(vmci_dev->irq, vmci_dev);
|
||||
tasklet_kill(&vmci_dev->datagram_tasklet);
|
||||
tasklet_kill(&vmci_dev->bm_tasklet);
|
||||
|
||||
err_disable_msi:
|
||||
if (vmci_dev->intr_type == VMCI_INTR_TYPE_MSIX)
|
||||
pci_disable_msix(pdev);
|
||||
else if (vmci_dev->intr_type == VMCI_INTR_TYPE_MSI)
|
||||
pci_disable_msi(pdev);
|
||||
|
||||
vmci_err = vmci_event_unsubscribe(ctx_update_sub_id);
|
||||
if (vmci_err < VMCI_SUCCESS)
|
||||
dev_warn(&pdev->dev,
|
||||
"Failed to unsubscribe from event (type=%d) with subscriber (ID=0x%x): %d\n",
|
||||
VMCI_EVENT_CTX_ID_UPDATE, ctx_update_sub_id, vmci_err);
|
||||
|
||||
err_remove_bitmap:
|
||||
if (vmci_dev->notification_bitmap) {
|
||||
iowrite32(VMCI_CONTROL_RESET,
|
||||
vmci_dev->iobase + VMCI_CONTROL_ADDR);
|
||||
dma_free_coherent(&pdev->dev, PAGE_SIZE,
|
||||
vmci_dev->notification_bitmap,
|
||||
vmci_dev->notification_base);
|
||||
}
|
||||
|
||||
err_remove_vmci_dev_g:
|
||||
spin_lock_irq(&vmci_dev_spinlock);
|
||||
vmci_pdev = NULL;
|
||||
vmci_dev_g = NULL;
|
||||
spin_unlock_irq(&vmci_dev_spinlock);
|
||||
|
||||
err_free_data_buffer:
|
||||
vfree(vmci_dev->data_buffer);
|
||||
|
||||
/* The rest are managed resources and will be freed by PCI core */
|
||||
return error;
|
||||
}
|
||||
|
||||
static void vmci_guest_remove_device(struct pci_dev *pdev)
|
||||
{
|
||||
struct vmci_guest_device *vmci_dev = pci_get_drvdata(pdev);
|
||||
int vmci_err;
|
||||
|
||||
dev_dbg(&pdev->dev, "Removing device\n");
|
||||
|
||||
atomic_dec(&vmci_num_guest_devices);
|
||||
|
||||
vmci_qp_guest_endpoints_exit();
|
||||
|
||||
vmci_err = vmci_event_unsubscribe(ctx_update_sub_id);
|
||||
if (vmci_err < VMCI_SUCCESS)
|
||||
dev_warn(&pdev->dev,
|
||||
"Failed to unsubscribe from event (type=%d) with subscriber (ID=0x%x): %d\n",
|
||||
VMCI_EVENT_CTX_ID_UPDATE, ctx_update_sub_id, vmci_err);
|
||||
|
||||
spin_lock_irq(&vmci_dev_spinlock);
|
||||
vmci_dev_g = NULL;
|
||||
vmci_pdev = NULL;
|
||||
spin_unlock_irq(&vmci_dev_spinlock);
|
||||
|
||||
dev_dbg(&pdev->dev, "Resetting vmci device\n");
|
||||
iowrite32(VMCI_CONTROL_RESET, vmci_dev->iobase + VMCI_CONTROL_ADDR);
|
||||
|
||||
/*
|
||||
* Free IRQ and then disable MSI/MSI-X as appropriate. For
|
||||
* MSI-X, we might have multiple vectors, each with their own
|
||||
* IRQ, which we must free too.
|
||||
*/
|
||||
free_irq(vmci_dev->irq, vmci_dev);
|
||||
if (vmci_dev->intr_type == VMCI_INTR_TYPE_MSIX) {
|
||||
if (vmci_dev->exclusive_vectors)
|
||||
free_irq(vmci_dev->msix_entries[1].vector, vmci_dev);
|
||||
pci_disable_msix(pdev);
|
||||
} else if (vmci_dev->intr_type == VMCI_INTR_TYPE_MSI) {
|
||||
pci_disable_msi(pdev);
|
||||
}
|
||||
|
||||
tasklet_kill(&vmci_dev->datagram_tasklet);
|
||||
tasklet_kill(&vmci_dev->bm_tasklet);
|
||||
|
||||
if (vmci_dev->notification_bitmap) {
|
||||
/*
|
||||
* The device reset above cleared the bitmap state of the
|
||||
* device, so we can safely free it here.
|
||||
*/
|
||||
|
||||
dma_free_coherent(&pdev->dev, PAGE_SIZE,
|
||||
vmci_dev->notification_bitmap,
|
||||
vmci_dev->notification_base);
|
||||
}
|
||||
|
||||
vfree(vmci_dev->data_buffer);
|
||||
|
||||
/* The rest are managed resources and will be freed by PCI core */
|
||||
}
|
||||
|
||||
static const struct pci_device_id vmci_ids[] = {
|
||||
{ PCI_DEVICE(PCI_VENDOR_ID_VMWARE, PCI_DEVICE_ID_VMWARE_VMCI), },
|
||||
{ 0 },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(pci, vmci_ids);
|
||||
|
||||
static struct pci_driver vmci_guest_driver = {
|
||||
.name = KBUILD_MODNAME,
|
||||
.id_table = vmci_ids,
|
||||
.probe = vmci_guest_probe_device,
|
||||
.remove = vmci_guest_remove_device,
|
||||
};
|
||||
|
||||
int __init vmci_guest_init(void)
|
||||
{
|
||||
return pci_register_driver(&vmci_guest_driver);
|
||||
}
|
||||
|
||||
void __exit vmci_guest_exit(void)
|
||||
{
|
||||
pci_unregister_driver(&vmci_guest_driver);
|
||||
}
|
142
drivers/misc/vmw_vmci/vmci_handle_array.c
Normal file
142
drivers/misc/vmw_vmci/vmci_handle_array.c
Normal file
|
@ -0,0 +1,142 @@
|
|||
/*
|
||||
* VMware VMCI Driver
|
||||
*
|
||||
* Copyright (C) 2012 VMware, Inc. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation version 2 and no later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* for more details.
|
||||
*/
|
||||
|
||||
#include <linux/slab.h>
|
||||
#include "vmci_handle_array.h"
|
||||
|
||||
static size_t handle_arr_calc_size(size_t capacity)
|
||||
{
|
||||
return sizeof(struct vmci_handle_arr) +
|
||||
capacity * sizeof(struct vmci_handle);
|
||||
}
|
||||
|
||||
struct vmci_handle_arr *vmci_handle_arr_create(size_t capacity)
|
||||
{
|
||||
struct vmci_handle_arr *array;
|
||||
|
||||
if (capacity == 0)
|
||||
capacity = VMCI_HANDLE_ARRAY_DEFAULT_SIZE;
|
||||
|
||||
array = kmalloc(handle_arr_calc_size(capacity), GFP_ATOMIC);
|
||||
if (!array)
|
||||
return NULL;
|
||||
|
||||
array->capacity = capacity;
|
||||
array->size = 0;
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
void vmci_handle_arr_destroy(struct vmci_handle_arr *array)
|
||||
{
|
||||
kfree(array);
|
||||
}
|
||||
|
||||
void vmci_handle_arr_append_entry(struct vmci_handle_arr **array_ptr,
|
||||
struct vmci_handle handle)
|
||||
{
|
||||
struct vmci_handle_arr *array = *array_ptr;
|
||||
|
||||
if (unlikely(array->size >= array->capacity)) {
|
||||
/* reallocate. */
|
||||
struct vmci_handle_arr *new_array;
|
||||
size_t new_capacity = array->capacity * VMCI_ARR_CAP_MULT;
|
||||
size_t new_size = handle_arr_calc_size(new_capacity);
|
||||
|
||||
new_array = krealloc(array, new_size, GFP_ATOMIC);
|
||||
if (!new_array)
|
||||
return;
|
||||
|
||||
new_array->capacity = new_capacity;
|
||||
*array_ptr = array = new_array;
|
||||
}
|
||||
|
||||
array->entries[array->size] = handle;
|
||||
array->size++;
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle that was removed, VMCI_INVALID_HANDLE if entry not found.
|
||||
*/
|
||||
struct vmci_handle vmci_handle_arr_remove_entry(struct vmci_handle_arr *array,
|
||||
struct vmci_handle entry_handle)
|
||||
{
|
||||
struct vmci_handle handle = VMCI_INVALID_HANDLE;
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < array->size; i++) {
|
||||
if (vmci_handle_is_equal(array->entries[i], entry_handle)) {
|
||||
handle = array->entries[i];
|
||||
array->size--;
|
||||
array->entries[i] = array->entries[array->size];
|
||||
array->entries[array->size] = VMCI_INVALID_HANDLE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle that was removed, VMCI_INVALID_HANDLE if array was empty.
|
||||
*/
|
||||
struct vmci_handle vmci_handle_arr_remove_tail(struct vmci_handle_arr *array)
|
||||
{
|
||||
struct vmci_handle handle = VMCI_INVALID_HANDLE;
|
||||
|
||||
if (array->size) {
|
||||
array->size--;
|
||||
handle = array->entries[array->size];
|
||||
array->entries[array->size] = VMCI_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle at given index, VMCI_INVALID_HANDLE if invalid index.
|
||||
*/
|
||||
struct vmci_handle
|
||||
vmci_handle_arr_get_entry(const struct vmci_handle_arr *array, size_t index)
|
||||
{
|
||||
if (unlikely(index >= array->size))
|
||||
return VMCI_INVALID_HANDLE;
|
||||
|
||||
return array->entries[index];
|
||||
}
|
||||
|
||||
bool vmci_handle_arr_has_entry(const struct vmci_handle_arr *array,
|
||||
struct vmci_handle entry_handle)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < array->size; i++)
|
||||
if (vmci_handle_is_equal(array->entries[i], entry_handle))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* NULL if the array is empty. Otherwise, a pointer to the array
|
||||
* of VMCI handles in the handle array.
|
||||
*/
|
||||
struct vmci_handle *vmci_handle_arr_get_handles(struct vmci_handle_arr *array)
|
||||
{
|
||||
if (array->size)
|
||||
return array->entries;
|
||||
|
||||
return NULL;
|
||||
}
|
52
drivers/misc/vmw_vmci/vmci_handle_array.h
Normal file
52
drivers/misc/vmw_vmci/vmci_handle_array.h
Normal file
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* VMware VMCI Driver
|
||||
*
|
||||
* Copyright (C) 2012 VMware, Inc. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation version 2 and no later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* for more details.
|
||||
*/
|
||||
|
||||
#ifndef _VMCI_HANDLE_ARRAY_H_
|
||||
#define _VMCI_HANDLE_ARRAY_H_
|
||||
|
||||
#include <linux/vmw_vmci_defs.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#define VMCI_HANDLE_ARRAY_DEFAULT_SIZE 4
|
||||
#define VMCI_ARR_CAP_MULT 2 /* Array capacity multiplier */
|
||||
|
||||
struct vmci_handle_arr {
|
||||
size_t capacity;
|
||||
size_t size;
|
||||
struct vmci_handle entries[];
|
||||
};
|
||||
|
||||
struct vmci_handle_arr *vmci_handle_arr_create(size_t capacity);
|
||||
void vmci_handle_arr_destroy(struct vmci_handle_arr *array);
|
||||
void vmci_handle_arr_append_entry(struct vmci_handle_arr **array_ptr,
|
||||
struct vmci_handle handle);
|
||||
struct vmci_handle vmci_handle_arr_remove_entry(struct vmci_handle_arr *array,
|
||||
struct vmci_handle
|
||||
entry_handle);
|
||||
struct vmci_handle vmci_handle_arr_remove_tail(struct vmci_handle_arr *array);
|
||||
struct vmci_handle
|
||||
vmci_handle_arr_get_entry(const struct vmci_handle_arr *array, size_t index);
|
||||
bool vmci_handle_arr_has_entry(const struct vmci_handle_arr *array,
|
||||
struct vmci_handle entry_handle);
|
||||
struct vmci_handle *vmci_handle_arr_get_handles(struct vmci_handle_arr *array);
|
||||
|
||||
static inline size_t vmci_handle_arr_get_size(
|
||||
const struct vmci_handle_arr *array)
|
||||
{
|
||||
return array->size;
|
||||
}
|
||||
|
||||
|
||||
#endif /* _VMCI_HANDLE_ARRAY_H_ */
|
1039
drivers/misc/vmw_vmci/vmci_host.c
Normal file
1039
drivers/misc/vmw_vmci/vmci_host.c
Normal file
File diff suppressed because it is too large
Load diff
3339
drivers/misc/vmw_vmci/vmci_queue_pair.c
Normal file
3339
drivers/misc/vmw_vmci/vmci_queue_pair.c
Normal file
File diff suppressed because it is too large
Load diff
173
drivers/misc/vmw_vmci/vmci_queue_pair.h
Normal file
173
drivers/misc/vmw_vmci/vmci_queue_pair.h
Normal file
|
@ -0,0 +1,173 @@
|
|||
/*
|
||||
* VMware VMCI Driver
|
||||
*
|
||||
* Copyright (C) 2012 VMware, Inc. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation version 2 and no later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* for more details.
|
||||
*/
|
||||
|
||||
#ifndef _VMCI_QUEUE_PAIR_H_
|
||||
#define _VMCI_QUEUE_PAIR_H_
|
||||
|
||||
#include <linux/vmw_vmci_defs.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#include "vmci_context.h"
|
||||
|
||||
/* Callback needed for correctly waiting on events. */
|
||||
typedef int (*vmci_event_release_cb) (void *client_data);
|
||||
|
||||
/* Guest device port I/O. */
|
||||
struct ppn_set {
|
||||
u64 num_produce_pages;
|
||||
u64 num_consume_pages;
|
||||
u32 *produce_ppns;
|
||||
u32 *consume_ppns;
|
||||
bool initialized;
|
||||
};
|
||||
|
||||
/* VMCIqueue_pairAllocInfo */
|
||||
struct vmci_qp_alloc_info {
|
||||
struct vmci_handle handle;
|
||||
u32 peer;
|
||||
u32 flags;
|
||||
u64 produce_size;
|
||||
u64 consume_size;
|
||||
u64 ppn_va; /* Start VA of queue pair PPNs. */
|
||||
u64 num_ppns;
|
||||
s32 result;
|
||||
u32 version;
|
||||
};
|
||||
|
||||
/* VMCIqueue_pairSetVAInfo */
|
||||
struct vmci_qp_set_va_info {
|
||||
struct vmci_handle handle;
|
||||
u64 va; /* Start VA of queue pair PPNs. */
|
||||
u64 num_ppns;
|
||||
u32 version;
|
||||
s32 result;
|
||||
};
|
||||
|
||||
/*
|
||||
* For backwards compatibility, here is a version of the
|
||||
* VMCIqueue_pairPageFileInfo before host support end-points was added.
|
||||
* Note that the current version of that structure requires VMX to
|
||||
* pass down the VA of the mapped file. Before host support was added
|
||||
* there was nothing of the sort. So, when the driver sees the ioctl
|
||||
* with a parameter that is the sizeof
|
||||
* VMCIqueue_pairPageFileInfo_NoHostQP then it can infer that the version
|
||||
* of VMX running can't attach to host end points because it doesn't
|
||||
* provide the VA of the mapped files.
|
||||
*
|
||||
* The Linux driver doesn't get an indication of the size of the
|
||||
* structure passed down from user space. So, to fix a long standing
|
||||
* but unfiled bug, the _pad field has been renamed to version.
|
||||
* Existing versions of VMX always initialize the PageFileInfo
|
||||
* structure so that _pad, er, version is set to 0.
|
||||
*
|
||||
* A version value of 1 indicates that the size of the structure has
|
||||
* been increased to include two UVA's: produce_uva and consume_uva.
|
||||
* These UVA's are of the mmap()'d queue contents backing files.
|
||||
*
|
||||
* In addition, if when VMX is sending down the
|
||||
* VMCIqueue_pairPageFileInfo structure it gets an error then it will
|
||||
* try again with the _NoHostQP version of the file to see if an older
|
||||
* VMCI kernel module is running.
|
||||
*/
|
||||
|
||||
/* VMCIqueue_pairPageFileInfo */
|
||||
struct vmci_qp_page_file_info {
|
||||
struct vmci_handle handle;
|
||||
u64 produce_page_file; /* User VA. */
|
||||
u64 consume_page_file; /* User VA. */
|
||||
u64 produce_page_file_size; /* Size of the file name array. */
|
||||
u64 consume_page_file_size; /* Size of the file name array. */
|
||||
s32 result;
|
||||
u32 version; /* Was _pad. */
|
||||
u64 produce_va; /* User VA of the mapped file. */
|
||||
u64 consume_va; /* User VA of the mapped file. */
|
||||
};
|
||||
|
||||
/* vmci queuepair detach info */
|
||||
struct vmci_qp_dtch_info {
|
||||
struct vmci_handle handle;
|
||||
s32 result;
|
||||
u32 _pad;
|
||||
};
|
||||
|
||||
/*
|
||||
* struct vmci_qp_page_store describes how the memory of a given queue pair
|
||||
* is backed. When the queue pair is between the host and a guest, the
|
||||
* page store consists of references to the guest pages. On vmkernel,
|
||||
* this is a list of PPNs, and on hosted, it is a user VA where the
|
||||
* queue pair is mapped into the VMX address space.
|
||||
*/
|
||||
struct vmci_qp_page_store {
|
||||
/* Reference to pages backing the queue pair. */
|
||||
u64 pages;
|
||||
/* Length of pageList/virtual addres range (in pages). */
|
||||
u32 len;
|
||||
};
|
||||
|
||||
/*
|
||||
* This data type contains the information about a queue.
|
||||
* There are two queues (hence, queue pairs) per transaction model between a
|
||||
* pair of end points, A & B. One queue is used by end point A to transmit
|
||||
* commands and responses to B. The other queue is used by B to transmit
|
||||
* commands and responses.
|
||||
*
|
||||
* struct vmci_queue_kern_if is a per-OS defined Queue structure. It contains
|
||||
* either a direct pointer to the linear address of the buffer contents or a
|
||||
* pointer to structures which help the OS locate those data pages. See
|
||||
* vmciKernelIf.c for each platform for its definition.
|
||||
*/
|
||||
struct vmci_queue {
|
||||
struct vmci_queue_header *q_header;
|
||||
struct vmci_queue_header *saved_header;
|
||||
struct vmci_queue_kern_if *kernel_if;
|
||||
};
|
||||
|
||||
/*
|
||||
* Utility function that checks whether the fields of the page
|
||||
* store contain valid values.
|
||||
* Result:
|
||||
* true if the page store is wellformed. false otherwise.
|
||||
*/
|
||||
static inline bool
|
||||
VMCI_QP_PAGESTORE_IS_WELLFORMED(struct vmci_qp_page_store *page_store)
|
||||
{
|
||||
return page_store->len >= 2;
|
||||
}
|
||||
|
||||
void vmci_qp_broker_exit(void);
|
||||
int vmci_qp_broker_alloc(struct vmci_handle handle, u32 peer,
|
||||
u32 flags, u32 priv_flags,
|
||||
u64 produce_size, u64 consume_size,
|
||||
struct vmci_qp_page_store *page_store,
|
||||
struct vmci_ctx *context);
|
||||
int vmci_qp_broker_set_page_store(struct vmci_handle handle,
|
||||
u64 produce_uva, u64 consume_uva,
|
||||
struct vmci_ctx *context);
|
||||
int vmci_qp_broker_detach(struct vmci_handle handle, struct vmci_ctx *context);
|
||||
|
||||
void vmci_qp_guest_endpoints_exit(void);
|
||||
|
||||
int vmci_qp_alloc(struct vmci_handle *handle,
|
||||
struct vmci_queue **produce_q, u64 produce_size,
|
||||
struct vmci_queue **consume_q, u64 consume_size,
|
||||
u32 peer, u32 flags, u32 priv_flags,
|
||||
bool guest_endpoint, vmci_event_release_cb wakeup_cb,
|
||||
void *client_data);
|
||||
int vmci_qp_broker_map(struct vmci_handle handle,
|
||||
struct vmci_ctx *context, u64 guest_mem);
|
||||
int vmci_qp_broker_unmap(struct vmci_handle handle,
|
||||
struct vmci_ctx *context, u32 gid);
|
||||
|
||||
#endif /* _VMCI_QUEUE_PAIR_H_ */
|
227
drivers/misc/vmw_vmci/vmci_resource.c
Normal file
227
drivers/misc/vmw_vmci/vmci_resource.c
Normal file
|
@ -0,0 +1,227 @@
|
|||
/*
|
||||
* VMware VMCI Driver
|
||||
*
|
||||
* Copyright (C) 2012 VMware, Inc. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation version 2 and no later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* for more details.
|
||||
*/
|
||||
|
||||
#include <linux/vmw_vmci_defs.h>
|
||||
#include <linux/hash.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/rculist.h>
|
||||
|
||||
#include "vmci_resource.h"
|
||||
#include "vmci_driver.h"
|
||||
|
||||
|
||||
#define VMCI_RESOURCE_HASH_BITS 7
|
||||
#define VMCI_RESOURCE_HASH_BUCKETS (1 << VMCI_RESOURCE_HASH_BITS)
|
||||
|
||||
struct vmci_hash_table {
|
||||
spinlock_t lock;
|
||||
struct hlist_head entries[VMCI_RESOURCE_HASH_BUCKETS];
|
||||
};
|
||||
|
||||
static struct vmci_hash_table vmci_resource_table = {
|
||||
.lock = __SPIN_LOCK_UNLOCKED(vmci_resource_table.lock),
|
||||
};
|
||||
|
||||
static unsigned int vmci_resource_hash(struct vmci_handle handle)
|
||||
{
|
||||
return hash_32(handle.resource, VMCI_RESOURCE_HASH_BITS);
|
||||
}
|
||||
|
||||
/*
|
||||
* Gets a resource (if one exists) matching given handle from the hash table.
|
||||
*/
|
||||
static struct vmci_resource *vmci_resource_lookup(struct vmci_handle handle,
|
||||
enum vmci_resource_type type)
|
||||
{
|
||||
struct vmci_resource *r, *resource = NULL;
|
||||
unsigned int idx = vmci_resource_hash(handle);
|
||||
|
||||
rcu_read_lock();
|
||||
hlist_for_each_entry_rcu(r,
|
||||
&vmci_resource_table.entries[idx], node) {
|
||||
u32 cid = r->handle.context;
|
||||
u32 rid = r->handle.resource;
|
||||
|
||||
if (r->type == type &&
|
||||
rid == handle.resource &&
|
||||
(cid == handle.context || cid == VMCI_INVALID_ID)) {
|
||||
resource = r;
|
||||
break;
|
||||
}
|
||||
}
|
||||
rcu_read_unlock();
|
||||
|
||||
return resource;
|
||||
}
|
||||
|
||||
/*
|
||||
* Find an unused resource ID and return it. The first
|
||||
* VMCI_RESERVED_RESOURCE_ID_MAX are reserved so we start from
|
||||
* its value + 1.
|
||||
* Returns VMCI resource id on success, VMCI_INVALID_ID on failure.
|
||||
*/
|
||||
static u32 vmci_resource_find_id(u32 context_id,
|
||||
enum vmci_resource_type resource_type)
|
||||
{
|
||||
static u32 resource_id = VMCI_RESERVED_RESOURCE_ID_MAX + 1;
|
||||
u32 old_rid = resource_id;
|
||||
u32 current_rid;
|
||||
|
||||
/*
|
||||
* Generate a unique resource ID. Keep on trying until we wrap around
|
||||
* in the RID space.
|
||||
*/
|
||||
do {
|
||||
struct vmci_handle handle;
|
||||
|
||||
current_rid = resource_id;
|
||||
resource_id++;
|
||||
if (unlikely(resource_id == VMCI_INVALID_ID)) {
|
||||
/* Skip the reserved rids. */
|
||||
resource_id = VMCI_RESERVED_RESOURCE_ID_MAX + 1;
|
||||
}
|
||||
|
||||
handle = vmci_make_handle(context_id, current_rid);
|
||||
if (!vmci_resource_lookup(handle, resource_type))
|
||||
return current_rid;
|
||||
} while (resource_id != old_rid);
|
||||
|
||||
return VMCI_INVALID_ID;
|
||||
}
|
||||
|
||||
|
||||
int vmci_resource_add(struct vmci_resource *resource,
|
||||
enum vmci_resource_type resource_type,
|
||||
struct vmci_handle handle)
|
||||
|
||||
{
|
||||
unsigned int idx;
|
||||
int result;
|
||||
|
||||
spin_lock(&vmci_resource_table.lock);
|
||||
|
||||
if (handle.resource == VMCI_INVALID_ID) {
|
||||
handle.resource = vmci_resource_find_id(handle.context,
|
||||
resource_type);
|
||||
if (handle.resource == VMCI_INVALID_ID) {
|
||||
result = VMCI_ERROR_NO_HANDLE;
|
||||
goto out;
|
||||
}
|
||||
} else if (vmci_resource_lookup(handle, resource_type)) {
|
||||
result = VMCI_ERROR_ALREADY_EXISTS;
|
||||
goto out;
|
||||
}
|
||||
|
||||
resource->handle = handle;
|
||||
resource->type = resource_type;
|
||||
INIT_HLIST_NODE(&resource->node);
|
||||
kref_init(&resource->kref);
|
||||
init_completion(&resource->done);
|
||||
|
||||
idx = vmci_resource_hash(resource->handle);
|
||||
hlist_add_head_rcu(&resource->node, &vmci_resource_table.entries[idx]);
|
||||
|
||||
result = VMCI_SUCCESS;
|
||||
|
||||
out:
|
||||
spin_unlock(&vmci_resource_table.lock);
|
||||
return result;
|
||||
}
|
||||
|
||||
void vmci_resource_remove(struct vmci_resource *resource)
|
||||
{
|
||||
struct vmci_handle handle = resource->handle;
|
||||
unsigned int idx = vmci_resource_hash(handle);
|
||||
struct vmci_resource *r;
|
||||
|
||||
/* Remove resource from hash table. */
|
||||
spin_lock(&vmci_resource_table.lock);
|
||||
|
||||
hlist_for_each_entry(r, &vmci_resource_table.entries[idx], node) {
|
||||
if (vmci_handle_is_equal(r->handle, resource->handle)) {
|
||||
hlist_del_init_rcu(&r->node);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
spin_unlock(&vmci_resource_table.lock);
|
||||
synchronize_rcu();
|
||||
|
||||
vmci_resource_put(resource);
|
||||
wait_for_completion(&resource->done);
|
||||
}
|
||||
|
||||
struct vmci_resource *
|
||||
vmci_resource_by_handle(struct vmci_handle resource_handle,
|
||||
enum vmci_resource_type resource_type)
|
||||
{
|
||||
struct vmci_resource *r, *resource = NULL;
|
||||
|
||||
rcu_read_lock();
|
||||
|
||||
r = vmci_resource_lookup(resource_handle, resource_type);
|
||||
if (r &&
|
||||
(resource_type == r->type ||
|
||||
resource_type == VMCI_RESOURCE_TYPE_ANY)) {
|
||||
resource = vmci_resource_get(r);
|
||||
}
|
||||
|
||||
rcu_read_unlock();
|
||||
|
||||
return resource;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get a reference to given resource.
|
||||
*/
|
||||
struct vmci_resource *vmci_resource_get(struct vmci_resource *resource)
|
||||
{
|
||||
kref_get(&resource->kref);
|
||||
|
||||
return resource;
|
||||
}
|
||||
|
||||
static void vmci_release_resource(struct kref *kref)
|
||||
{
|
||||
struct vmci_resource *resource =
|
||||
container_of(kref, struct vmci_resource, kref);
|
||||
|
||||
/* Verify the resource has been unlinked from hash table */
|
||||
WARN_ON(!hlist_unhashed(&resource->node));
|
||||
|
||||
/* Signal that container of this resource can now be destroyed */
|
||||
complete(&resource->done);
|
||||
}
|
||||
|
||||
/*
|
||||
* Resource's release function will get called if last reference.
|
||||
* If it is the last reference, then we are sure that nobody else
|
||||
* can increment the count again (it's gone from the resource hash
|
||||
* table), so there's no need for locking here.
|
||||
*/
|
||||
int vmci_resource_put(struct vmci_resource *resource)
|
||||
{
|
||||
/*
|
||||
* We propagate the information back to caller in case it wants to know
|
||||
* whether entry was freed.
|
||||
*/
|
||||
return kref_put(&resource->kref, vmci_release_resource) ?
|
||||
VMCI_SUCCESS_ENTRY_DEAD : VMCI_SUCCESS;
|
||||
}
|
||||
|
||||
struct vmci_handle vmci_resource_handle(struct vmci_resource *resource)
|
||||
{
|
||||
return resource->handle;
|
||||
}
|
59
drivers/misc/vmw_vmci/vmci_resource.h
Normal file
59
drivers/misc/vmw_vmci/vmci_resource.h
Normal file
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* VMware VMCI Driver
|
||||
*
|
||||
* Copyright (C) 2012 VMware, Inc. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation version 2 and no later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* for more details.
|
||||
*/
|
||||
|
||||
#ifndef _VMCI_RESOURCE_H_
|
||||
#define _VMCI_RESOURCE_H_
|
||||
|
||||
#include <linux/vmw_vmci_defs.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#include "vmci_context.h"
|
||||
|
||||
|
||||
enum vmci_resource_type {
|
||||
VMCI_RESOURCE_TYPE_ANY,
|
||||
VMCI_RESOURCE_TYPE_API,
|
||||
VMCI_RESOURCE_TYPE_GROUP,
|
||||
VMCI_RESOURCE_TYPE_DATAGRAM,
|
||||
VMCI_RESOURCE_TYPE_DOORBELL,
|
||||
VMCI_RESOURCE_TYPE_QPAIR_GUEST,
|
||||
VMCI_RESOURCE_TYPE_QPAIR_HOST
|
||||
};
|
||||
|
||||
struct vmci_resource {
|
||||
struct vmci_handle handle;
|
||||
enum vmci_resource_type type;
|
||||
struct hlist_node node;
|
||||
struct kref kref;
|
||||
struct completion done;
|
||||
};
|
||||
|
||||
|
||||
int vmci_resource_add(struct vmci_resource *resource,
|
||||
enum vmci_resource_type resource_type,
|
||||
struct vmci_handle handle);
|
||||
|
||||
void vmci_resource_remove(struct vmci_resource *resource);
|
||||
|
||||
struct vmci_resource *
|
||||
vmci_resource_by_handle(struct vmci_handle resource_handle,
|
||||
enum vmci_resource_type resource_type);
|
||||
|
||||
struct vmci_resource *vmci_resource_get(struct vmci_resource *resource);
|
||||
int vmci_resource_put(struct vmci_resource *resource);
|
||||
|
||||
struct vmci_handle vmci_resource_handle(struct vmci_resource *resource);
|
||||
|
||||
#endif /* _VMCI_RESOURCE_H_ */
|
226
drivers/misc/vmw_vmci/vmci_route.c
Normal file
226
drivers/misc/vmw_vmci/vmci_route.c
Normal file
|
@ -0,0 +1,226 @@
|
|||
/*
|
||||
* VMware VMCI Driver
|
||||
*
|
||||
* Copyright (C) 2012 VMware, Inc. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation version 2 and no later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* for more details.
|
||||
*/
|
||||
|
||||
#include <linux/vmw_vmci_defs.h>
|
||||
#include <linux/vmw_vmci_api.h>
|
||||
|
||||
#include "vmci_context.h"
|
||||
#include "vmci_driver.h"
|
||||
#include "vmci_route.h"
|
||||
|
||||
/*
|
||||
* Make a routing decision for the given source and destination handles.
|
||||
* This will try to determine the route using the handles and the available
|
||||
* devices. Will set the source context if it is invalid.
|
||||
*/
|
||||
int vmci_route(struct vmci_handle *src,
|
||||
const struct vmci_handle *dst,
|
||||
bool from_guest,
|
||||
enum vmci_route *route)
|
||||
{
|
||||
bool has_host_device = vmci_host_code_active();
|
||||
bool has_guest_device = vmci_guest_code_active();
|
||||
|
||||
*route = VMCI_ROUTE_NONE;
|
||||
|
||||
/*
|
||||
* "from_guest" is only ever set to true by
|
||||
* IOCTL_VMCI_DATAGRAM_SEND (or by the vmkernel equivalent),
|
||||
* which comes from the VMX, so we know it is coming from a
|
||||
* guest.
|
||||
*
|
||||
* To avoid inconsistencies, test these once. We will test
|
||||
* them again when we do the actual send to ensure that we do
|
||||
* not touch a non-existent device.
|
||||
*/
|
||||
|
||||
/* Must have a valid destination context. */
|
||||
if (VMCI_INVALID_ID == dst->context)
|
||||
return VMCI_ERROR_INVALID_ARGS;
|
||||
|
||||
/* Anywhere to hypervisor. */
|
||||
if (VMCI_HYPERVISOR_CONTEXT_ID == dst->context) {
|
||||
|
||||
/*
|
||||
* If this message already came from a guest then we
|
||||
* cannot send it to the hypervisor. It must come
|
||||
* from a local client.
|
||||
*/
|
||||
if (from_guest)
|
||||
return VMCI_ERROR_DST_UNREACHABLE;
|
||||
|
||||
/*
|
||||
* We must be acting as a guest in order to send to
|
||||
* the hypervisor.
|
||||
*/
|
||||
if (!has_guest_device)
|
||||
return VMCI_ERROR_DEVICE_NOT_FOUND;
|
||||
|
||||
/* And we cannot send if the source is the host context. */
|
||||
if (VMCI_HOST_CONTEXT_ID == src->context)
|
||||
return VMCI_ERROR_INVALID_ARGS;
|
||||
|
||||
/*
|
||||
* If the client passed the ANON source handle then
|
||||
* respect it (both context and resource are invalid).
|
||||
* However, if they passed only an invalid context,
|
||||
* then they probably mean ANY, in which case we
|
||||
* should set the real context here before passing it
|
||||
* down.
|
||||
*/
|
||||
if (VMCI_INVALID_ID == src->context &&
|
||||
VMCI_INVALID_ID != src->resource)
|
||||
src->context = vmci_get_context_id();
|
||||
|
||||
/* Send from local client down to the hypervisor. */
|
||||
*route = VMCI_ROUTE_AS_GUEST;
|
||||
return VMCI_SUCCESS;
|
||||
}
|
||||
|
||||
/* Anywhere to local client on host. */
|
||||
if (VMCI_HOST_CONTEXT_ID == dst->context) {
|
||||
/*
|
||||
* If it is not from a guest but we are acting as a
|
||||
* guest, then we need to send it down to the host.
|
||||
* Note that if we are also acting as a host then this
|
||||
* will prevent us from sending from local client to
|
||||
* local client, but we accept that restriction as a
|
||||
* way to remove any ambiguity from the host context.
|
||||
*/
|
||||
if (src->context == VMCI_HYPERVISOR_CONTEXT_ID) {
|
||||
/*
|
||||
* If the hypervisor is the source, this is
|
||||
* host local communication. The hypervisor
|
||||
* may send vmci event datagrams to the host
|
||||
* itself, but it will never send datagrams to
|
||||
* an "outer host" through the guest device.
|
||||
*/
|
||||
|
||||
if (has_host_device) {
|
||||
*route = VMCI_ROUTE_AS_HOST;
|
||||
return VMCI_SUCCESS;
|
||||
} else {
|
||||
return VMCI_ERROR_DEVICE_NOT_FOUND;
|
||||
}
|
||||
}
|
||||
|
||||
if (!from_guest && has_guest_device) {
|
||||
/* If no source context then use the current. */
|
||||
if (VMCI_INVALID_ID == src->context)
|
||||
src->context = vmci_get_context_id();
|
||||
|
||||
/* Send it from local client down to the host. */
|
||||
*route = VMCI_ROUTE_AS_GUEST;
|
||||
return VMCI_SUCCESS;
|
||||
}
|
||||
|
||||
/*
|
||||
* Otherwise we already received it from a guest and
|
||||
* it is destined for a local client on this host, or
|
||||
* it is from another local client on this host. We
|
||||
* must be acting as a host to service it.
|
||||
*/
|
||||
if (!has_host_device)
|
||||
return VMCI_ERROR_DEVICE_NOT_FOUND;
|
||||
|
||||
if (VMCI_INVALID_ID == src->context) {
|
||||
/*
|
||||
* If it came from a guest then it must have a
|
||||
* valid context. Otherwise we can use the
|
||||
* host context.
|
||||
*/
|
||||
if (from_guest)
|
||||
return VMCI_ERROR_INVALID_ARGS;
|
||||
|
||||
src->context = VMCI_HOST_CONTEXT_ID;
|
||||
}
|
||||
|
||||
/* Route to local client. */
|
||||
*route = VMCI_ROUTE_AS_HOST;
|
||||
return VMCI_SUCCESS;
|
||||
}
|
||||
|
||||
/*
|
||||
* If we are acting as a host then this might be destined for
|
||||
* a guest.
|
||||
*/
|
||||
if (has_host_device) {
|
||||
/* It will have a context if it is meant for a guest. */
|
||||
if (vmci_ctx_exists(dst->context)) {
|
||||
if (VMCI_INVALID_ID == src->context) {
|
||||
/*
|
||||
* If it came from a guest then it
|
||||
* must have a valid context.
|
||||
* Otherwise we can use the host
|
||||
* context.
|
||||
*/
|
||||
|
||||
if (from_guest)
|
||||
return VMCI_ERROR_INVALID_ARGS;
|
||||
|
||||
src->context = VMCI_HOST_CONTEXT_ID;
|
||||
} else if (VMCI_CONTEXT_IS_VM(src->context) &&
|
||||
src->context != dst->context) {
|
||||
/*
|
||||
* VM to VM communication is not
|
||||
* allowed. Since we catch all
|
||||
* communication destined for the host
|
||||
* above, this must be destined for a
|
||||
* VM since there is a valid context.
|
||||
*/
|
||||
|
||||
return VMCI_ERROR_DST_UNREACHABLE;
|
||||
}
|
||||
|
||||
/* Pass it up to the guest. */
|
||||
*route = VMCI_ROUTE_AS_HOST;
|
||||
return VMCI_SUCCESS;
|
||||
} else if (!has_guest_device) {
|
||||
/*
|
||||
* The host is attempting to reach a CID
|
||||
* without an active context, and we can't
|
||||
* send it down, since we have no guest
|
||||
* device.
|
||||
*/
|
||||
|
||||
return VMCI_ERROR_DST_UNREACHABLE;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* We must be a guest trying to send to another guest, which means
|
||||
* we need to send it down to the host. We do not filter out VM to
|
||||
* VM communication here, since we want to be able to use the guest
|
||||
* driver on older versions that do support VM to VM communication.
|
||||
*/
|
||||
if (!has_guest_device) {
|
||||
/*
|
||||
* Ending up here means we have neither guest nor host
|
||||
* device.
|
||||
*/
|
||||
return VMCI_ERROR_DEVICE_NOT_FOUND;
|
||||
}
|
||||
|
||||
/* If no source context then use the current context. */
|
||||
if (VMCI_INVALID_ID == src->context)
|
||||
src->context = vmci_get_context_id();
|
||||
|
||||
/*
|
||||
* Send it from local client down to the host, which will
|
||||
* route it to the other guest for us.
|
||||
*/
|
||||
*route = VMCI_ROUTE_AS_GUEST;
|
||||
return VMCI_SUCCESS;
|
||||
}
|
30
drivers/misc/vmw_vmci/vmci_route.h
Normal file
30
drivers/misc/vmw_vmci/vmci_route.h
Normal file
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* VMware VMCI Driver
|
||||
*
|
||||
* Copyright (C) 2012 VMware, Inc. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation version 2 and no later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* for more details.
|
||||
*/
|
||||
|
||||
#ifndef _VMCI_ROUTE_H_
|
||||
#define _VMCI_ROUTE_H_
|
||||
|
||||
#include <linux/vmw_vmci_defs.h>
|
||||
|
||||
enum vmci_route {
|
||||
VMCI_ROUTE_NONE,
|
||||
VMCI_ROUTE_AS_HOST,
|
||||
VMCI_ROUTE_AS_GUEST,
|
||||
};
|
||||
|
||||
int vmci_route(struct vmci_handle *src, const struct vmci_handle *dst,
|
||||
bool from_guest, enum vmci_route *route);
|
||||
|
||||
#endif /* _VMCI_ROUTE_H_ */
|
Loading…
Add table
Add a link
Reference in a new issue