#include <sys/types.h>
#include <sys/systm.h>
#include "vmci_datagram.h"
#include "vmci_driver.h"
#include "vmci_kernel_api.h"
#include "vmci_kernel_defs.h"
#include "vmci_resource.h"
#define LGPFX "vmci_datagram: "
struct datagram_entry {
struct vmci_resource resource;
uint32_t flags;
bool run_delayed;
vmci_datagram_recv_cb recv_cb;
void *client_data;
vmci_event destroy_event;
vmci_privilege_flags priv_flags;
};
struct vmci_delayed_datagram_info {
struct datagram_entry *entry;
struct vmci_datagram msg;
};
static int vmci_datagram_get_priv_flags_int(vmci_id contextID,
struct vmci_handle handle,
vmci_privilege_flags *priv_flags);
static void datagram_free_cb(void *resource);
static int datagram_release_cb(void *client_data);
static void
datagram_free_cb(void *client_data)
{
struct datagram_entry *entry = (struct datagram_entry *)client_data;
ASSERT(entry);
vmci_signal_event(&entry->destroy_event);
}
static int
datagram_release_cb(void *client_data)
{
struct datagram_entry *entry;
entry = (struct datagram_entry *)client_data;
ASSERT(entry);
vmci_resource_release(&entry->resource);
return (0);
}
static int
datagram_create_hnd(vmci_id resource_id, uint32_t flags,
vmci_privilege_flags priv_flags, vmci_datagram_recv_cb recv_cb,
void *client_data, struct vmci_handle *out_handle)
{
struct datagram_entry *entry;
struct vmci_handle handle;
vmci_id context_id;
int result;
ASSERT(recv_cb != NULL);
ASSERT(out_handle != NULL);
ASSERT(!(priv_flags & ~VMCI_PRIVILEGE_ALL_FLAGS));
if ((flags & VMCI_FLAG_WELLKNOWN_DG_HND) != 0)
return (VMCI_ERROR_INVALID_ARGS);
else {
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);
}
if (resource_id == VMCI_INVALID_ID) {
resource_id = vmci_resource_get_id(context_id);
if (resource_id == VMCI_INVALID_ID)
return (VMCI_ERROR_NO_HANDLE);
}
handle = VMCI_MAKE_HANDLE(context_id, resource_id);
}
entry = vmci_alloc_kernel_mem(sizeof(*entry), VMCI_MEMORY_NORMAL);
if (entry == NULL) {
VMCI_LOG_WARNING(LGPFX"Failed allocating memory for datagram "
"entry.\n");
return (VMCI_ERROR_NO_MEM);
}
if (!vmci_can_schedule_delayed_work()) {
if (flags & VMCI_FLAG_DG_DELAYED_CB) {
vmci_free_kernel_mem(entry, sizeof(*entry));
return (VMCI_ERROR_INVALID_ARGS);
}
entry->run_delayed = false;
} else
entry->run_delayed = (flags & VMCI_FLAG_DG_DELAYED_CB) ?
true : false;
entry->flags = flags;
entry->recv_cb = recv_cb;
entry->client_data = client_data;
vmci_create_event(&entry->destroy_event);
entry->priv_flags = priv_flags;
result = vmci_resource_add(&entry->resource,
VMCI_RESOURCE_TYPE_DATAGRAM, handle, datagram_free_cb, entry);
if (result != VMCI_SUCCESS) {
VMCI_LOG_WARNING(LGPFX"Failed to add new resource "
"(handle=0x%x:0x%x).\n", handle.context, handle.resource);
vmci_destroy_event(&entry->destroy_event);
vmci_free_kernel_mem(entry, sizeof(*entry));
return (result);
}
*out_handle = handle;
return (VMCI_SUCCESS);
}
int
vmci_datagram_create_handle(vmci_id resource_id, uint32_t 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) {
VMCI_LOG_DEBUG(LGPFX"Client callback needed when creating "
"datagram.\n");
return (VMCI_ERROR_INVALID_ARGS);
}
return (datagram_create_hnd(resource_id, flags,
VMCI_DEFAULT_PROC_PRIVILEGE_FLAGS,
recv_cb, client_data, out_handle));
}
int
vmci_datagram_create_handle_priv(vmci_id resource_id, uint32_t flags,
vmci_privilege_flags 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) {
VMCI_LOG_DEBUG(LGPFX"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 (datagram_create_hnd(resource_id, flags, priv_flags, recv_cb,
client_data, out_handle));
}
int
vmci_datagram_destroy_handle(struct vmci_handle handle)
{
struct datagram_entry *entry;
struct vmci_resource *resource;
resource = vmci_resource_get(handle,
VMCI_RESOURCE_TYPE_DATAGRAM);
if (resource == NULL) {
VMCI_LOG_DEBUG(LGPFX"Failed to destroy datagram "
"(handle=0x%x:0x%x).\n", handle.context, handle.resource);
return (VMCI_ERROR_NOT_FOUND);
}
entry = RESOURCE_CONTAINER(resource, struct datagram_entry, resource);
vmci_resource_remove(handle, VMCI_RESOURCE_TYPE_DATAGRAM);
vmci_wait_on_event(&entry->destroy_event, datagram_release_cb, entry);
vmci_destroy_event(&entry->destroy_event);
vmci_free_kernel_mem(entry, sizeof(*entry));
return (VMCI_SUCCESS);
}
static int
vmci_datagram_get_priv_flags_int(vmci_id context_id, struct vmci_handle handle,
vmci_privilege_flags *priv_flags)
{
ASSERT(priv_flags);
ASSERT(context_id != VMCI_INVALID_ID);
if (context_id == VMCI_HOST_CONTEXT_ID) {
struct datagram_entry *src_entry;
struct vmci_resource *resource;
resource = vmci_resource_get(handle,
VMCI_RESOURCE_TYPE_DATAGRAM);
if (resource == NULL)
return (VMCI_ERROR_INVALID_ARGS);
src_entry = RESOURCE_CONTAINER(resource, struct datagram_entry,
resource);
*priv_flags = src_entry->priv_flags;
vmci_resource_release(resource);
} else if (context_id == VMCI_HYPERVISOR_CONTEXT_ID)
*priv_flags = VMCI_MAX_PRIVILEGE_FLAGS;
else
*priv_flags = VMCI_NO_PRIVILEGE_FLAGS;
return (VMCI_SUCCESS);
}
int
vmci_datagram_get_priv_flags(struct vmci_handle handle,
vmci_privilege_flags *priv_flags)
{
if (priv_flags == NULL || handle.context == VMCI_INVALID_ID)
return (VMCI_ERROR_INVALID_ARGS);
return (vmci_datagram_get_priv_flags_int(handle.context, handle,
priv_flags));
}
static void
vmci_datagram_delayed_dispatch_cb(void *data)
{
struct vmci_delayed_datagram_info *dg_info;
dg_info = (struct vmci_delayed_datagram_info *)data;
ASSERT(data);
dg_info->entry->recv_cb(dg_info->entry->client_data, &dg_info->msg);
vmci_resource_release(&dg_info->entry->resource);
vmci_free_kernel_mem(dg_info, sizeof(*dg_info) +
(size_t)dg_info->msg.payload_size);
}
static int
vmci_datagram_dispatch_as_guest(struct vmci_datagram *dg)
{
struct vmci_resource *resource;
int retval;
resource = vmci_resource_get(dg->src, VMCI_RESOURCE_TYPE_DATAGRAM);
if (NULL == resource)
return VMCI_ERROR_NO_HANDLE;
retval = vmci_send_datagram(dg);
vmci_resource_release(resource);
return (retval);
}
int
vmci_datagram_dispatch(vmci_id context_id, struct vmci_datagram *dg)
{
ASSERT(dg);
ASSERT_ON_COMPILE(sizeof(struct vmci_datagram) == 24);
if (VMCI_DG_SIZE(dg) > VMCI_MAX_DG_SIZE) {
VMCI_LOG_DEBUG(LGPFX"Payload (size=%lu bytes) too big to send."
"\n", dg->payload_size);
return (VMCI_ERROR_INVALID_ARGS);
}
return (vmci_datagram_dispatch_as_guest(dg));
}
int
vmci_datagram_invoke_guest_handler(struct vmci_datagram *dg)
{
struct datagram_entry *dst_entry;
struct vmci_resource *resource;
int retval;
ASSERT(dg);
if (dg->payload_size > VMCI_MAX_DG_PAYLOAD_SIZE) {
VMCI_LOG_DEBUG(LGPFX"Payload (size=%lu bytes) too large to "
"deliver.\n", dg->payload_size);
return (VMCI_ERROR_PAYLOAD_TOO_LARGE);
}
resource = vmci_resource_get(dg->dst, VMCI_RESOURCE_TYPE_DATAGRAM);
if (NULL == resource) {
VMCI_LOG_DEBUG(LGPFX"destination (handle=0x%x:0x%x) doesn't "
"exist.\n", dg->dst.context, dg->dst.resource);
return (VMCI_ERROR_NO_HANDLE);
}
dst_entry = RESOURCE_CONTAINER(resource, struct datagram_entry,
resource);
if (dst_entry->run_delayed) {
struct vmci_delayed_datagram_info *dg_info;
dg_info = vmci_alloc_kernel_mem(sizeof(*dg_info) +
(size_t)dg->payload_size, VMCI_MEMORY_ATOMIC);
if (NULL == dg_info) {
vmci_resource_release(resource);
retval = VMCI_ERROR_NO_MEM;
goto exit;
}
dg_info->entry = dst_entry;
memcpy(&dg_info->msg, dg, VMCI_DG_SIZE(dg));
retval = vmci_schedule_delayed_work(
vmci_datagram_delayed_dispatch_cb, dg_info);
if (retval < VMCI_SUCCESS) {
VMCI_LOG_WARNING(LGPFX"Failed to schedule delayed "
"work for datagram (result=%d).\n", retval);
vmci_free_kernel_mem(dg_info, sizeof(*dg_info) +
(size_t)dg->payload_size);
vmci_resource_release(resource);
dg_info = NULL;
goto exit;
}
} else {
dst_entry->recv_cb(dst_entry->client_data, dg);
vmci_resource_release(resource);
retval = VMCI_SUCCESS;
}
exit:
return (retval);
}
int
vmci_datagram_send(struct vmci_datagram *msg)
{
if (msg == NULL)
return (VMCI_ERROR_INVALID_ARGS);
return (vmci_datagram_dispatch(VMCI_INVALID_ID, msg));
}
void
vmci_datagram_sync(void)
{
vmci_resource_sync();
}
bool
vmci_datagram_check_host_capabilities(void)
{
return (true);
}