opnsense-src/sys/dev/vmware/vmci/vmci_queue_pair.c
Mark Peek 63a938566d Add VMCI (Virtual Machine Communication Interface) driver
In a virtual machine, VMCI is exposed as a regular PCI device. The primary
communication mechanisms supported are a point-to-point bidirectional
transport based on a pair of memory-mapped queues, and asynchronous
notifications in the form of datagrams and doorbells. These features are
available to kernel level components such as vSockets through the VMCI
kernel API. In addition to this, the VMCI kernel API provides support for
receiving events related to the state of the VMCI communication channels,
and the virtual machine itself.

Submitted by: Vishnu Dasa <vdasa@vmware.com>
Reviewed by: bcr, imp
Obtained from: VMware
Differential Revision: https://reviews.freebsd.org/D14289
2018-03-25 00:57:00 +00:00

940 lines
25 KiB
C

/*-
* Copyright (c) 2018 VMware, Inc. All Rights Reserved.
*
* SPDX-License-Identifier: (BSD-2-Clause AND GPL-2.0)
*/
/* VMCI QueuePair API implementation. */
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include "vmci.h"
#include "vmci_driver.h"
#include "vmci_event.h"
#include "vmci_kernel_api.h"
#include "vmci_kernel_defs.h"
#include "vmci_queue_pair.h"
#define LGPFX "vmci_queue_pair: "
struct queue_pair_entry {
vmci_list_item(queue_pair_entry) list_item;
struct vmci_handle handle;
vmci_id peer;
uint32_t flags;
uint64_t produce_size;
uint64_t consume_size;
uint32_t ref_count;
};
struct qp_guest_endpoint {
struct queue_pair_entry qp;
uint64_t num_ppns;
void *produce_q;
void *consume_q;
bool hibernate_failure;
struct ppn_set ppn_set;
};
struct queue_pair_list {
vmci_list(queue_pair_entry) head;
volatile int hibernate;
vmci_mutex mutex;
};
#define QPE_NUM_PAGES(_QPE) \
((uint32_t)(CEILING(_QPE.produce_size, PAGE_SIZE) + \
CEILING(_QPE.consume_size, PAGE_SIZE) + 2))
static struct queue_pair_list qp_guest_endpoints;
static struct queue_pair_entry *queue_pair_list_find_entry(
struct queue_pair_list *qp_list, struct vmci_handle handle);
static void queue_pair_list_add_entry(struct queue_pair_list *qp_list,
struct queue_pair_entry *entry);
static void queue_pair_list_remove_entry(struct queue_pair_list *qp_list,
struct queue_pair_entry *entry);
static struct queue_pair_entry *queue_pair_list_get_head(
struct queue_pair_list *qp_list);
static int queue_pair_notify_peer_local(bool attach,
struct vmci_handle handle);
static struct qp_guest_endpoint *qp_guest_endpoint_create(
struct vmci_handle handle, vmci_id peer, uint32_t flags,
uint64_t produce_size, uint64_t consume_size,
void *produce_q, void *consume_q);
static void qp_guest_endpoint_destroy(struct qp_guest_endpoint *entry);
static int vmci_queue_pair_alloc_hypercall(
const struct qp_guest_endpoint *entry);
static int vmci_queue_pair_alloc_guest_work(struct vmci_handle *handle,
struct vmci_queue **produce_q, uint64_t produce_size,
struct vmci_queue **consume_q, uint64_t consume_size,
vmci_id peer, uint32_t flags,
vmci_privilege_flags priv_flags);
static int vmci_queue_pair_detach_guest_work(struct vmci_handle handle);
static int vmci_queue_pair_detach_hypercall(struct vmci_handle handle);
extern int vmci_send_datagram(struct vmci_datagram *);
/*
*------------------------------------------------------------------------------
*
* vmci_queue_pair_alloc --
*
* Allocates a VMCI QueuePair. Only checks validity of input arguments. The
* real work is done in the host or guest specific function.
*
* Results:
* VMCI_SUCCESS on success, appropriate error code otherwise.
*
* Side effects:
* None.
*
*------------------------------------------------------------------------------
*/
int
vmci_queue_pair_alloc(struct vmci_handle *handle, struct vmci_queue **produce_q,
uint64_t produce_size, struct vmci_queue **consume_q, uint64_t consume_size,
vmci_id peer, uint32_t flags, vmci_privilege_flags priv_flags)
{
if (!handle || !produce_q || !consume_q ||
(!produce_size && !consume_size) || (flags & ~VMCI_QP_ALL_FLAGS))
return (VMCI_ERROR_INVALID_ARGS);
return (vmci_queue_pair_alloc_guest_work(handle, produce_q,
produce_size, consume_q, consume_size, peer, flags, priv_flags));
}
/*
*------------------------------------------------------------------------------
*
* vmci_queue_pair_detach --
*
* Detaches from a VMCI QueuePair. Only checks validity of input argument.
* Real work is done in the host or guest specific function.
*
* Results:
* Success or failure.
*
* Side effects:
* Memory is freed.
*
*------------------------------------------------------------------------------
*/
int
vmci_queue_pair_detach(struct vmci_handle handle)
{
if (VMCI_HANDLE_INVALID(handle))
return (VMCI_ERROR_INVALID_ARGS);
return (vmci_queue_pair_detach_guest_work(handle));
}
/*
*------------------------------------------------------------------------------
*
* queue_pair_list_init --
*
* Initializes the list of QueuePairs.
*
* Results:
* Success or failure.
*
* Side effects:
* None.
*
*------------------------------------------------------------------------------
*/
static inline int
queue_pair_list_init(struct queue_pair_list *qp_list)
{
int ret;
vmci_list_init(&qp_list->head);
atomic_store_int(&qp_list->hibernate, 0);
ret = vmci_mutex_init(&qp_list->mutex, "VMCI QP List lock");
return (ret);
}
/*
*------------------------------------------------------------------------------
*
* queue_pair_list_destroy --
*
* Destroy the list's mutex.
*
* Results:
* None.
*
* Side effects:
* None.
*
*------------------------------------------------------------------------------
*/
static inline void
queue_pair_list_destroy(struct queue_pair_list *qp_list)
{
vmci_mutex_destroy(&qp_list->mutex);
vmci_list_init(&qp_list->head);
}
/*
*------------------------------------------------------------------------------
*
* queue_pair_list_find_entry --
*
* Finds the entry in the list corresponding to a given handle. Assumes that
* the list is locked.
*
* Results:
* Pointer to entry.
*
* Side effects:
* None.
*
*------------------------------------------------------------------------------
*/
static struct queue_pair_entry *
queue_pair_list_find_entry(struct queue_pair_list *qp_list,
struct vmci_handle handle)
{
struct queue_pair_entry *next;
if (VMCI_HANDLE_INVALID(handle))
return (NULL);
vmci_list_scan(next, &qp_list->head, list_item) {
if (VMCI_HANDLE_EQUAL(next->handle, handle))
return (next);
}
return (NULL);
}
/*
*------------------------------------------------------------------------------
*
* queue_pair_list_add_entry --
*
* Adds the given entry to the list. Assumes that the list is locked.
*
* Results:
* None.
*
* Side effects:
* None.
*
*------------------------------------------------------------------------------
*/
static void
queue_pair_list_add_entry(struct queue_pair_list *qp_list,
struct queue_pair_entry *entry)
{
if (entry)
vmci_list_insert(&qp_list->head, entry, list_item);
}
/*
*------------------------------------------------------------------------------
*
* queue_pair_list_remove_entry --
*
* Removes the given entry from the list. Assumes that the list is locked.
*
* Results:
* None.
*
* Side effects:
* None.
*
*------------------------------------------------------------------------------
*/
static void
queue_pair_list_remove_entry(struct queue_pair_list *qp_list,
struct queue_pair_entry *entry)
{
if (entry)
vmci_list_remove(entry, list_item);
}
/*
*------------------------------------------------------------------------------
*
* queue_pair_list_get_head --
*
* Returns the entry from the head of the list. Assumes that the list is
* locked.
*
* Results:
* Pointer to entry.
*
* Side effects:
* None.
*
*------------------------------------------------------------------------------
*/
static struct queue_pair_entry *
queue_pair_list_get_head(struct queue_pair_list *qp_list)
{
return (vmci_list_first(&qp_list->head));
}
/*
*------------------------------------------------------------------------------
*
* vmci_qp_guest_endpoints_init --
*
* Initalizes data structure state keeping track of queue pair guest
* endpoints.
*
* Results:
* VMCI_SUCCESS on success and appropriate failure code otherwise.
*
* Side effects:
* None.
*
*------------------------------------------------------------------------------
*/
int
vmci_qp_guest_endpoints_init(void)
{
return (queue_pair_list_init(&qp_guest_endpoints));
}
/*
*------------------------------------------------------------------------------
*
* vmci_qp_guest_endpoints_exit --
*
* Destroys all guest queue pair endpoints. If active guest queue pairs
* still exist, hypercalls to attempt detach from these queue pairs will be
* made. Any failure to detach is silently ignored.
*
* Results:
* None.
*
* Side effects:
* None.
*
*------------------------------------------------------------------------------
*/
void
vmci_qp_guest_endpoints_exit(void)
{
struct qp_guest_endpoint *entry;
vmci_mutex_acquire(&qp_guest_endpoints.mutex);
while ((entry =
(struct qp_guest_endpoint *)queue_pair_list_get_head(
&qp_guest_endpoints)) != NULL) {
/*
* Don't make a hypercall for local QueuePairs.
*/
if (!(entry->qp.flags & VMCI_QPFLAG_LOCAL))
vmci_queue_pair_detach_hypercall(entry->qp.handle);
/*
* We cannot fail the exit, so let's reset ref_count.
*/
entry->qp.ref_count = 0;
queue_pair_list_remove_entry(&qp_guest_endpoints, &entry->qp);
qp_guest_endpoint_destroy(entry);
}
atomic_store_int(&qp_guest_endpoints.hibernate, 0);
vmci_mutex_release(&qp_guest_endpoints.mutex);
queue_pair_list_destroy(&qp_guest_endpoints);
}
/*
*------------------------------------------------------------------------------
*
* vmci_qp_guest_endpoints_sync --
*
* Use this as a synchronization point when setting globals, for example,
* during device shutdown.
*
* Results:
* true.
*
* Side effects:
* None.
*
*------------------------------------------------------------------------------
*/
void
vmci_qp_guest_endpoints_sync(void)
{
vmci_mutex_acquire(&qp_guest_endpoints.mutex);
vmci_mutex_release(&qp_guest_endpoints.mutex);
}
/*
*------------------------------------------------------------------------------
*
* qp_guest_endpoint_create --
*
* Allocates and initializes a qp_guest_endpoint structure. Allocates a
* QueuePair rid (and handle) iff the given entry has an invalid handle.
* 0 through VMCI_RESERVED_RESOURCE_ID_MAX are reserved handles. Assumes
* that the QP list mutex is held by the caller.
*
* Results:
* Pointer to structure intialized.
*
* Side effects:
* None.
*
*------------------------------------------------------------------------------
*/
struct qp_guest_endpoint *
qp_guest_endpoint_create(struct vmci_handle handle, vmci_id peer,
uint32_t flags, uint64_t produce_size, uint64_t consume_size,
void *produce_q, void *consume_q)
{
struct qp_guest_endpoint *entry;
static vmci_id queue_pair_rid;
const uint64_t num_ppns = CEILING(produce_size, PAGE_SIZE) +
CEILING(consume_size, PAGE_SIZE) +
2; /* One page each for the queue headers. */
queue_pair_rid = VMCI_RESERVED_RESOURCE_ID_MAX + 1;
ASSERT((produce_size || consume_size) && produce_q && consume_q);
if (VMCI_HANDLE_INVALID(handle)) {
vmci_id context_id = vmci_get_context_id();
vmci_id old_rid = queue_pair_rid;
/*
* Generate a unique QueuePair rid. Keep on trying until we
* wrap around in the RID space.
*/
ASSERT(old_rid > VMCI_RESERVED_RESOURCE_ID_MAX);
do {
handle = VMCI_MAKE_HANDLE(context_id, queue_pair_rid);
entry =
(struct qp_guest_endpoint *)
queue_pair_list_find_entry(&qp_guest_endpoints,
handle);
queue_pair_rid++;
if (UNLIKELY(!queue_pair_rid)) {
/*
* Skip the reserved rids.
*/
queue_pair_rid =
VMCI_RESERVED_RESOURCE_ID_MAX + 1;
}
} while (entry && queue_pair_rid != old_rid);
if (UNLIKELY(entry != NULL)) {
ASSERT(queue_pair_rid == old_rid);
/*
* We wrapped around --- no rids were free.
*/
return (NULL);
}
}
ASSERT(!VMCI_HANDLE_INVALID(handle) &&
queue_pair_list_find_entry(&qp_guest_endpoints, handle) == NULL);
entry = vmci_alloc_kernel_mem(sizeof(*entry), VMCI_MEMORY_NORMAL);
if (entry) {
entry->qp.handle = handle;
entry->qp.peer = peer;
entry->qp.flags = flags;
entry->qp.produce_size = produce_size;
entry->qp.consume_size = consume_size;
entry->qp.ref_count = 0;
entry->num_ppns = num_ppns;
memset(&entry->ppn_set, 0, sizeof(entry->ppn_set));
entry->produce_q = produce_q;
entry->consume_q = consume_q;
}
return (entry);
}
/*
*------------------------------------------------------------------------------
*
* qp_guest_endpoint_destroy --
*
* Frees a qp_guest_endpoint structure.
*
* Results:
* None.
*
* Side effects:
* None.
*
*------------------------------------------------------------------------------
*/
void
qp_guest_endpoint_destroy(struct qp_guest_endpoint *entry)
{
ASSERT(entry);
ASSERT(entry->qp.ref_count == 0);
vmci_free_ppn_set(&entry->ppn_set);
vmci_free_queue(entry->produce_q, entry->qp.produce_size);
vmci_free_queue(entry->consume_q, entry->qp.consume_size);
vmci_free_kernel_mem(entry, sizeof(*entry));
}
/*
*------------------------------------------------------------------------------
*
* vmci_queue_pair_alloc_hypercall --
*
* Helper to make a QueuePairAlloc hypercall when the driver is
* supporting a guest device.
*
* Results:
* Result of the hypercall.
*
* Side effects:
* Memory is allocated & freed.
*
*------------------------------------------------------------------------------
*/
static int
vmci_queue_pair_alloc_hypercall(const struct qp_guest_endpoint *entry)
{
struct vmci_queue_pair_alloc_msg *alloc_msg;
size_t msg_size;
int result;
if (!entry || entry->num_ppns <= 2)
return (VMCI_ERROR_INVALID_ARGS);
ASSERT(!(entry->qp.flags & VMCI_QPFLAG_LOCAL));
msg_size = sizeof(*alloc_msg) + (size_t)entry->num_ppns * sizeof(PPN);
alloc_msg = vmci_alloc_kernel_mem(msg_size, VMCI_MEMORY_NORMAL);
if (!alloc_msg)
return (VMCI_ERROR_NO_MEM);
alloc_msg->hdr.dst = VMCI_MAKE_HANDLE(VMCI_HYPERVISOR_CONTEXT_ID,
VMCI_QUEUEPAIR_ALLOC);
alloc_msg->hdr.src = VMCI_ANON_SRC_HANDLE;
alloc_msg->hdr.payload_size = msg_size - VMCI_DG_HEADERSIZE;
alloc_msg->handle = entry->qp.handle;
alloc_msg->peer = entry->qp.peer;
alloc_msg->flags = entry->qp.flags;
alloc_msg->produce_size = entry->qp.produce_size;
alloc_msg->consume_size = entry->qp.consume_size;
alloc_msg->num_ppns = entry->num_ppns;
result = vmci_populate_ppn_list((uint8_t *)alloc_msg +
sizeof(*alloc_msg), &entry->ppn_set);
if (result == VMCI_SUCCESS)
result = vmci_send_datagram((struct vmci_datagram *)alloc_msg);
vmci_free_kernel_mem(alloc_msg, msg_size);
return (result);
}
/*
*------------------------------------------------------------------------------
*
* vmci_queue_pair_alloc_guest_work --
*
* This functions handles the actual allocation of a VMCI queue pair guest
* endpoint. Allocates physical pages for the queue pair. It makes OS
* dependent calls through generic wrappers.
*
* Results:
* Success or failure.
*
* Side effects:
* Memory is allocated.
*
*------------------------------------------------------------------------------
*/
static int
vmci_queue_pair_alloc_guest_work(struct vmci_handle *handle,
struct vmci_queue **produce_q, uint64_t produce_size,
struct vmci_queue **consume_q, uint64_t consume_size, vmci_id peer,
uint32_t flags, vmci_privilege_flags priv_flags)
{
struct qp_guest_endpoint *queue_pair_entry = NULL;
void *my_consume_q = NULL;
void *my_produce_q = NULL;
const uint64_t num_consume_pages = CEILING(consume_size, PAGE_SIZE) + 1;
const uint64_t num_produce_pages = CEILING(produce_size, PAGE_SIZE) + 1;
int result;
ASSERT(handle && produce_q && consume_q &&
(produce_size || consume_size));
if (priv_flags != VMCI_NO_PRIVILEGE_FLAGS)
return (VMCI_ERROR_NO_ACCESS);
vmci_mutex_acquire(&qp_guest_endpoints.mutex);
if ((atomic_load_int(&qp_guest_endpoints.hibernate) == 1) &&
!(flags & VMCI_QPFLAG_LOCAL)) {
/*
* While guest OS is in hibernate state, creating non-local
* queue pairs is not allowed after the point where the VMCI
* guest driver converted the existing queue pairs to local
* ones.
*/
result = VMCI_ERROR_UNAVAILABLE;
goto error;
}
if ((queue_pair_entry =
(struct qp_guest_endpoint *)queue_pair_list_find_entry(
&qp_guest_endpoints, *handle)) != NULL) {
if (queue_pair_entry->qp.flags & VMCI_QPFLAG_LOCAL) {
/* Local attach case. */
if (queue_pair_entry->qp.ref_count > 1) {
VMCI_LOG_DEBUG(LGPFX"Error attempting to "
"attach more than once.\n");
result = VMCI_ERROR_UNAVAILABLE;
goto error_keep_entry;
}
if (queue_pair_entry->qp.produce_size != consume_size ||
queue_pair_entry->qp.consume_size != produce_size ||
queue_pair_entry->qp.flags !=
(flags & ~VMCI_QPFLAG_ATTACH_ONLY)) {
VMCI_LOG_DEBUG(LGPFX"Error mismatched "
"queue pair in local attach.\n");
result = VMCI_ERROR_QUEUEPAIR_MISMATCH;
goto error_keep_entry;
}
/*
* Do a local attach. We swap the consume and produce
* queues for the attacher and deliver an attach event.
*/
result = queue_pair_notify_peer_local(true, *handle);
if (result < VMCI_SUCCESS)
goto error_keep_entry;
my_produce_q = queue_pair_entry->consume_q;
my_consume_q = queue_pair_entry->produce_q;
goto out;
}
result = VMCI_ERROR_ALREADY_EXISTS;
goto error_keep_entry;
}
my_produce_q = vmci_alloc_queue(produce_size, flags);
if (!my_produce_q) {
VMCI_LOG_WARNING(LGPFX"Error allocating pages for produce "
"queue.\n");
result = VMCI_ERROR_NO_MEM;
goto error;
}
my_consume_q = vmci_alloc_queue(consume_size, flags);
if (!my_consume_q) {
VMCI_LOG_WARNING(LGPFX"Error allocating pages for consume "
"queue.\n");
result = VMCI_ERROR_NO_MEM;
goto error;
}
queue_pair_entry = qp_guest_endpoint_create(*handle, peer, flags,
produce_size, consume_size, my_produce_q, my_consume_q);
if (!queue_pair_entry) {
VMCI_LOG_WARNING(LGPFX"Error allocating memory in %s.\n",
__FUNCTION__);
result = VMCI_ERROR_NO_MEM;
goto error;
}
result = vmci_alloc_ppn_set(my_produce_q, num_produce_pages,
my_consume_q, num_consume_pages, &queue_pair_entry->ppn_set);
if (result < VMCI_SUCCESS) {
VMCI_LOG_WARNING(LGPFX"vmci_alloc_ppn_set failed.\n");
goto error;
}
/*
* It's only necessary to notify the host if this queue pair will be
* attached to from another context.
*/
if (queue_pair_entry->qp.flags & VMCI_QPFLAG_LOCAL) {
/* Local create case. */
vmci_id context_id = vmci_get_context_id();
/*
* Enforce similar checks on local queue pairs as we do for
* regular ones. The handle's context must match the creator
* or attacher context id (here they are both the current
* context id) and the attach-only flag cannot exist during
* create. We also ensure specified peer is this context or
* an invalid one.
*/
if (queue_pair_entry->qp.handle.context != context_id ||
(queue_pair_entry->qp.peer != VMCI_INVALID_ID &&
queue_pair_entry->qp.peer != context_id)) {
result = VMCI_ERROR_NO_ACCESS;
goto error;
}
if (queue_pair_entry->qp.flags & VMCI_QPFLAG_ATTACH_ONLY) {
result = VMCI_ERROR_NOT_FOUND;
goto error;
}
} else {
result = vmci_queue_pair_alloc_hypercall(queue_pair_entry);
if (result < VMCI_SUCCESS) {
VMCI_LOG_WARNING(
LGPFX"vmci_queue_pair_alloc_hypercall result = "
"%d.\n", result);
goto error;
}
}
queue_pair_list_add_entry(&qp_guest_endpoints, &queue_pair_entry->qp);
out:
queue_pair_entry->qp.ref_count++;
*handle = queue_pair_entry->qp.handle;
*produce_q = (struct vmci_queue *)my_produce_q;
*consume_q = (struct vmci_queue *)my_consume_q;
/*
* We should initialize the queue pair header pages on a local queue
* pair create. For non-local queue pairs, the hypervisor initializes
* the header pages in the create step.
*/
if ((queue_pair_entry->qp.flags & VMCI_QPFLAG_LOCAL) &&
queue_pair_entry->qp.ref_count == 1) {
vmci_queue_header_init((*produce_q)->q_header, *handle);
vmci_queue_header_init((*consume_q)->q_header, *handle);
}
vmci_mutex_release(&qp_guest_endpoints.mutex);
return (VMCI_SUCCESS);
error:
vmci_mutex_release(&qp_guest_endpoints.mutex);
if (queue_pair_entry) {
/* The queues will be freed inside the destroy routine. */
qp_guest_endpoint_destroy(queue_pair_entry);
} else {
if (my_produce_q)
vmci_free_queue(my_produce_q, produce_size);
if (my_consume_q)
vmci_free_queue(my_consume_q, consume_size);
}
return (result);
error_keep_entry:
/* This path should only be used when an existing entry was found. */
ASSERT(queue_pair_entry->qp.ref_count > 0);
vmci_mutex_release(&qp_guest_endpoints.mutex);
return (result);
}
/*
*------------------------------------------------------------------------------
*
* vmci_queue_pair_detach_hypercall --
*
* Helper to make a QueuePairDetach hypercall when the driver is supporting
* a guest device.
*
* Results:
* Result of the hypercall.
*
* Side effects:
* None.
*
*------------------------------------------------------------------------------
*/
int
vmci_queue_pair_detach_hypercall(struct vmci_handle handle)
{
struct vmci_queue_pair_detach_msg detach_msg;
detach_msg.hdr.dst = VMCI_MAKE_HANDLE(VMCI_HYPERVISOR_CONTEXT_ID,
VMCI_QUEUEPAIR_DETACH);
detach_msg.hdr.src = VMCI_ANON_SRC_HANDLE;
detach_msg.hdr.payload_size = sizeof(handle);
detach_msg.handle = handle;
return (vmci_send_datagram((struct vmci_datagram *)&detach_msg));
}
/*
*------------------------------------------------------------------------------
*
* vmci_queue_pair_detach_guest_work --
*
* Helper for VMCI QueuePair detach interface. Frees the physical pages for
* the queue pair.
*
* Results:
* Success or failure.
*
* Side effects:
* Memory may be freed.
*
*------------------------------------------------------------------------------
*/
static int
vmci_queue_pair_detach_guest_work(struct vmci_handle handle)
{
struct qp_guest_endpoint *entry;
int result;
uint32_t ref_count;
ASSERT(!VMCI_HANDLE_INVALID(handle));
vmci_mutex_acquire(&qp_guest_endpoints.mutex);
entry = (struct qp_guest_endpoint *)queue_pair_list_find_entry(
&qp_guest_endpoints, handle);
if (!entry) {
vmci_mutex_release(&qp_guest_endpoints.mutex);
return (VMCI_ERROR_NOT_FOUND);
}
ASSERT(entry->qp.ref_count >= 1);
if (entry->qp.flags & VMCI_QPFLAG_LOCAL) {
result = VMCI_SUCCESS;
if (entry->qp.ref_count > 1) {
result = queue_pair_notify_peer_local(false, handle);
/*
* We can fail to notify a local queuepair because we
* can't allocate. We still want to release the entry
* if that happens, so don't bail out yet.
*/
}
} else {
result = vmci_queue_pair_detach_hypercall(handle);
if (entry->hibernate_failure) {
if (result == VMCI_ERROR_NOT_FOUND) {
/*
* If a queue pair detach failed when entering
* hibernation, the guest driver and the device
* may disagree on its existence when coming
* out of hibernation. The guest driver will
* regard it as a non-local queue pair, but
* the device state is gone, since the device
* has been powered off. In this case, we
* treat the queue pair as a local queue pair
* with no peer.
*/
ASSERT(entry->qp.ref_count == 1);
result = VMCI_SUCCESS;
}
}
if (result < VMCI_SUCCESS) {
/*
* We failed to notify a non-local queuepair. That other
* queuepair might still be accessing the shared
* memory, so don't release the entry yet. It will get
* cleaned up by vmci_queue_pair_Exit() if necessary
* (assuming we are going away, otherwise why did this
* fail?).
*/
vmci_mutex_release(&qp_guest_endpoints.mutex);
return (result);
}
}
/*
* If we get here then we either failed to notify a local queuepair, or
* we succeeded in all cases. Release the entry if required.
*/
entry->qp.ref_count--;
if (entry->qp.ref_count == 0)
queue_pair_list_remove_entry(&qp_guest_endpoints, &entry->qp);
/* If we didn't remove the entry, this could change once we unlock. */
ref_count = entry ? entry->qp.ref_count :
0xffffffff; /*
* Value does not matter, silence the
* compiler.
*/
vmci_mutex_release(&qp_guest_endpoints.mutex);
if (ref_count == 0)
qp_guest_endpoint_destroy(entry);
return (result);
}
/*
*------------------------------------------------------------------------------
*
* queue_pair_notify_peer_local --
*
* Dispatches a queue pair event message directly into the local event
* queue.
*
* Results:
* VMCI_SUCCESS on success, error code otherwise
*
* Side effects:
* None.
*
*------------------------------------------------------------------------------
*/
static int
queue_pair_notify_peer_local(bool attach, struct vmci_handle handle)
{
struct vmci_event_msg *e_msg;
struct vmci_event_payload_qp *e_payload;
/* buf is only 48 bytes. */
vmci_id context_id;
context_id = vmci_get_context_id();
char buf[sizeof(*e_msg) + sizeof(*e_payload)];
e_msg = (struct vmci_event_msg *)buf;
e_payload = vmci_event_msg_payload(e_msg);
e_msg->hdr.dst = VMCI_MAKE_HANDLE(context_id, VMCI_EVENT_HANDLER);
e_msg->hdr.src = VMCI_MAKE_HANDLE(VMCI_HYPERVISOR_CONTEXT_ID,
VMCI_CONTEXT_RESOURCE_ID);
e_msg->hdr.payload_size = sizeof(*e_msg) + sizeof(*e_payload) -
sizeof(e_msg->hdr);
e_msg->event_data.event = attach ? VMCI_EVENT_QP_PEER_ATTACH :
VMCI_EVENT_QP_PEER_DETACH;
e_payload->peer_id = context_id;
e_payload->handle = handle;
return (vmci_event_dispatch((struct vmci_datagram *)e_msg));
}