/*
* vmciQueuePair.c --
*
- * Implements the VMCI QueuePair API.
+ * VMCI QueuePair API implementation in the host driver.
*/
-#ifdef __linux__
+#if defined(__linux__) && !defined(VMKERNEL)
# include "driver-config.h"
-# include <asm/page.h>
-# include <linux/module.h>
-#elif defined(_WIN32)
-# include <wdm.h>
-#elif defined(__APPLE__)
-# include <IOKit/IOLib.h>
-#endif /* __linux__ */
-
+# include "compat_kernel.h"
+# include "compat_module.h"
+#endif // __linux__
+#include "vmci_kernel_if.h"
#include "vm_assert.h"
-#include "vm_atomic.h"
+#include "vmci_defs.h"
#include "vmci_handle_array.h"
-#include "vmci_kernel_if.h"
+#include "vmci_infrastructure.h"
+#include "vmciCommonInt.h"
+#include "vmciContext.h"
+#include "vmciDatagram.h"
#include "vmciEvent.h"
-#include "vmciInt.h"
+#include "vmciHashtable.h"
#include "vmciKernelAPI.h"
-#include "vmciQueuePairInt.h"
-#include "vmciUtil.h"
+#include "vmciQueuePair.h"
+#include "vmciResource.h"
+#include "vmciRoute.h"
+#ifdef VMX86_TOOLS
+# include "vmciInt.h"
+# include "vmciUtil.h"
+#elif defined(VMKERNEL)
+# include "vmciVmkInt.h"
+# include "vm_libc.h"
+# include "helper_ext.h"
+# include "vmciDriver.h"
+#else
+# include "vmciDriver.h"
+#endif
+
+#define LGPFX "VMCIQueuePair: "
+
+/*
+ * The context that creates the QueuePair becomes producer of produce queue,
+ * and consumer of consume queue. The context on other end for the QueuePair
+ * has roles reversed for these two queues.
+ */
+
+typedef struct QueuePairEntry {
+ VMCIListItem listItem;
+ VMCIHandle handle;
+ VMCIId peer;
+ uint32 flags;
+ uint64 produceSize;
+ uint64 consumeSize;
+ uint32 refCount;
+} QueuePairEntry;
+
+typedef struct QPBrokerEntry {
+ QueuePairEntry qp;
+ VMCIId createId;
+ VMCIId attachId;
+ Bool pageStoreSet;
+ Bool allowAttach;
+ Bool requireTrustedAttach;
+ Bool createdByTrusted;
+#ifdef VMKERNEL
+ QueuePairPageStore store;
+#elif defined(__linux__) || defined(_WIN32) || defined(__APPLE__)
+ /*
+ * Always created but only used if a host endpoint attaches to this
+ * queue.
+ */
+
+ VMCIQueue *produceQ;
+ VMCIQueue *consumeQ;
+ char producePageFile[VMCI_PATH_MAX];
+ char consumePageFile[VMCI_PATH_MAX];
+ PageStoreAttachInfo *attachInfo;
+#endif
+} QPBrokerEntry;
+
+#if !defined(VMKERNEL)
+typedef struct QPGuestEndpoint {
+ QueuePairEntry qp;
+ uint64 numPPNs;
+ void *produceQ;
+ void *consumeQ;
+ Bool hibernateFailure;
+ PPNSet ppnSet;
+} QPGuestEndpoint;
+#endif
+
+#ifdef VMKERNEL
+typedef VMCILock VMCIQPLock;
+# define VMCIQPLock_Init(_l, _r) VMCI_InitLock(_l, "VMCIQPLock", \
+ VMCI_LOCK_RANK_HIGH); \
+ _r = VMCI_SUCCESS
+# define VMCIQPLock_Destroy(_l) VMCI_CleanupLock(_l)
+# define VMCIQPLock_Acquire(_l) VMCI_GrabLock(_l, NULL)
+# define VMCIQPLock_Release(_l) VMCI_ReleaseLock(_l, 0)
+#else
+typedef VMCIMutex VMCIQPLock;
+# define VMCIQPLock_Init(_l, _r) _r = VMCIMutex_Init(_l)
+# define VMCIQPLock_Destroy(_l) VMCIMutex_Destroy(_l)
+# define VMCIQPLock_Acquire(_l) VMCIMutex_Acquire(_l)
+# define VMCIQPLock_Release(_l) VMCIMutex_Release(_l)
+#endif
+
+typedef struct QueuePairList {
+ VMCIList head;
+ Atomic_uint32 hibernate;
+ VMCIQPLock lock;
+} QueuePairList;
+
+static QueuePairList qpBrokerList;
+
+#if !defined(VMKERNEL)
+ static QueuePairList qpGuestEndpoints;
+ static VMCIHandleArray *hibernateFailedList;
+ static VMCILock hibernateFailedListLock;
+#endif
+
+static QueuePairEntry *QueuePairList_FindEntry(QueuePairList *qpList,
+ VMCIHandle handle);
+static void QueuePairList_AddEntry(QueuePairList *qpList,
+ QueuePairEntry *entry);
+static void QueuePairList_RemoveEntry(QueuePairList *qpList,
+ QueuePairEntry *entry);
+static QueuePairEntry *QueuePairList_GetHead(QueuePairList *qpList);
+
+static int QueuePairNotifyPeer(Bool attach, VMCIHandle handle, VMCIId myId,
+ VMCIId peerId);
+static int VMCIQPBrokerAllocInt(VMCIHandle handle, VMCIId peer,
+ uint32 flags, VMCIPrivilegeFlags privFlags,
+ uint64 produceSize,
+ uint64 consumeSize,
+ QueuePairPageStore *pageStore,
+ VMCIContext *context,
+ QPBrokerEntry **ent);
+
+#if !defined(VMKERNEL)
+static int VMCIQueuePairAllocHostWork(VMCIHandle *handle, VMCIQueue **produceQ,
+ uint64 produceSize, VMCIQueue **consumeQ,
+ uint64 consumeSize,
+ VMCIId peer, uint32 flags,
+ VMCIPrivilegeFlags privFlags);
+static int VMCIQueuePairDetachHostWork(VMCIHandle handle);
+
+static int QueuePairNotifyPeerLocal(Bool attach, VMCIHandle handle);
+
+static QPGuestEndpoint *QPGuestEndpointCreate(VMCIHandle handle,
+ VMCIId peer, uint32 flags,
+ uint64 produceSize,
+ uint64 consumeSize,
+ void *produceQ, void *consumeQ);
+static void QPGuestEndpointDestroy(QPGuestEndpoint *entry);
+static int VMCIQueuePairAllocHypercall(const QPGuestEndpoint *entry);
+static int VMCIQueuePairAllocGuestWork(VMCIHandle *handle, VMCIQueue **produceQ,
+ uint64 produceSize, VMCIQueue **consumeQ,
+ uint64 consumeSize,
+ VMCIId peer, uint32 flags,
+ VMCIPrivilegeFlags privFlags);
+static int VMCIQueuePairDetachGuestWork(VMCIHandle handle);
+static int VMCIQueuePairDetachHypercall(VMCIHandle handle);
+static void VMCIQPMarkHibernateFailed(QPGuestEndpoint *entry);
+static void VMCIQPUnmarkHibernateFailed(QPGuestEndpoint *entry);
+
+extern int VMCI_SendDatagram(VMCIDatagram *);
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * VMCIQueuePair_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
+VMCIQueuePair_Alloc(VMCIHandle *handle, // IN/OUT
+ VMCIQueue **produceQ, // OUT
+ uint64 produceSize, // IN
+ VMCIQueue **consumeQ, // OUT
+ uint64 consumeSize, // IN
+ VMCIId peer, // IN
+ uint32 flags, // IN
+ VMCIPrivilegeFlags privFlags, // IN
+ Bool guestEndpoint) // IN
+{
+ if (!handle || !produceQ || !consumeQ || (!produceSize && !consumeSize) ||
+ (flags & ~VMCI_QP_ALL_FLAGS)) {
+ return VMCI_ERROR_INVALID_ARGS;
+ }
+
+ if (guestEndpoint) {
+ return VMCIQueuePairAllocGuestWork(handle, produceQ, produceSize, consumeQ,
+ consumeSize, peer, flags, privFlags);
+ } else {
+ return VMCIQueuePairAllocHostWork(handle, produceQ, produceSize, consumeQ,
+ consumeSize, peer, flags, privFlags);
+ }
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * VMCIQueuePair_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
+VMCIQueuePair_Detach(VMCIHandle handle, // IN
+ Bool guestEndpoint) // IN
+{
+ if (VMCI_HANDLE_INVALID(handle)) {
+ return VMCI_ERROR_INVALID_ARGS;
+ }
+
+ if (guestEndpoint) {
+ return VMCIQueuePairDetachGuestWork(handle);
+ } else {
+ return VMCIQueuePairDetachHostWork(handle);
+ }
+}
+#endif // !defined(VMKERNEL)
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * QueuePairList_Init --
+ *
+ * Initializes the list of QueuePairs.
+ *
+ * Results:
+ * Success or failure.
+ *
+ * Side effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+static INLINE int
+QueuePairList_Init(QueuePairList *qpList) // IN
+{
+ int ret;
+
+ VMCIList_Init(&qpList->head);
+ Atomic_Write(&qpList->hibernate, 0);
+ VMCIQPLock_Init(&qpList->lock, ret);
+
+ return ret;
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * QueuePairList_Destroy --
+ *
+ * Destroy the list's lock.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+static INLINE void
+QueuePairList_Destroy(QueuePairList *qpList)
+{
+ VMCIQPLock_Destroy(&qpList->lock);
+ VMCIList_Init(&qpList->head);
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * VMCIQPBroker_Lock --
+ *
+ * Acquires the lock protecting a VMCI queue pair broker transaction.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+void
+VMCIQPBroker_Lock(void)
+{
+ VMCIQPLock_Acquire(&qpBrokerList.lock);
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * VMCIQPBroker_Unlock --
+ *
+ * Releases the lock protecting a VMCI queue pair broker transaction.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+void
+VMCIQPBroker_Unlock(void)
+{
+ VMCIQPLock_Release(&qpBrokerList.lock);
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * QueuePairList_FindEntry --
+ *
+ * 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 QueuePairEntry *
+QueuePairList_FindEntry(QueuePairList *qpList, // IN
+ VMCIHandle handle) // IN
+{
+ VMCIListItem *next;
+
+ if (VMCI_HANDLE_INVALID(handle)) {
+ return NULL;
+ }
+
+ VMCIList_Scan(next, &qpList->head) {
+ QueuePairEntry *entry = VMCIList_Entry(next, QueuePairEntry, listItem);
+
+ if (VMCI_HANDLE_EQUAL(entry->handle, handle)) {
+ return entry;
+ }
+ }
+
+ return NULL;
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * QueuePairList_AddEntry --
+ *
+ * Adds the given entry to the list. Assumes that the list is locked.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+static void
+QueuePairList_AddEntry(QueuePairList *qpList, // IN
+ QueuePairEntry *entry) // IN
+{
+ if (entry) {
+ VMCIList_Insert(&entry->listItem, &qpList->head);
+ }
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * QueuePairList_RemoveEntry --
+ *
+ * Removes the given entry from the list. Assumes that the list is locked.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+static void
+QueuePairList_RemoveEntry(QueuePairList *qpList, // IN
+ QueuePairEntry *entry) // IN
+{
+ if (entry) {
+ VMCIList_Remove(&entry->listItem, &qpBrokerList.head);
+ }
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * QueuePairList_GetHead --
+ *
+ * Returns the entry from the head of the list. Assumes that the list is
+ * locked.
+ *
+ * Results:
+ * Pointer to entry.
+ *
+ * Side effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+static QueuePairEntry *
+QueuePairList_GetHead(QueuePairList *qpList)
+{
+ VMCIListItem *first = VMCIList_First(&qpList->head);
+
+ if (first) {
+ QueuePairEntry *entry = VMCIList_Entry(first, QueuePairEntry, listItem);
+ return entry;
+ }
+
+ return NULL;
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * QueuePairDenyConnection --
+ *
+ * On ESX we check if the domain names of the two contexts match.
+ * Otherwise we deny the connection. We always allow the connection on
+ * hosted.
+ *
+ * Results:
+ * Boolean result.
+ *
+ * Side effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+static INLINE Bool
+QueuePairDenyConnection(VMCIId contextId, // IN: Unused on hosted
+ VMCIId peerId) // IN: Unused on hosted
+{
+#ifndef VMKERNEL
+ return FALSE; /* Allow on hosted. */
+#else
+ char contextDomain[VMCI_DOMAIN_NAME_MAXLEN];
+ char peerDomain[VMCI_DOMAIN_NAME_MAXLEN];
+
+ ASSERT(contextId != VMCI_INVALID_ID);
+ if (peerId == VMCI_INVALID_ID) {
+ return FALSE; /* Allow. */
+ }
+ if (VMCIContext_GetDomainName(contextId, contextDomain,
+ sizeof contextDomain) != VMCI_SUCCESS) {
+ return TRUE; /* Deny. */
+ }
+ if (VMCIContext_GetDomainName(peerId, peerDomain, sizeof peerDomain) !=
+ VMCI_SUCCESS) {
+ return TRUE; /* Deny. */
+ }
+ return strcmp(contextDomain, peerDomain) ? TRUE : /* Deny. */
+ FALSE; /* Allow. */
+#endif
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * VMCIQPBroker_Init --
+ *
+ * Initalizes queue pair broker state.
+ *
+ * Results:
+ * Success or failure.
+ *
+ * Side effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+int
+VMCIQPBroker_Init(void)
+{
+ return QueuePairList_Init(&qpBrokerList);
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * VMCIQPBroker_Exit --
+ *
+ * Destroys the queue pair broker state.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+void
+VMCIQPBroker_Exit(void)
+{
+ QPBrokerEntry *entry;
+
+ VMCIQPBroker_Lock();
+
+ while ((entry = (QPBrokerEntry *)QueuePairList_GetHead(&qpBrokerList))) {
+ QueuePairList_RemoveEntry(&qpBrokerList, &entry->qp);
+ VMCI_FreeKernelMem(entry, sizeof *entry);
+ }
+
+ VMCIQPBroker_Unlock();
+ QueuePairList_Destroy(&qpBrokerList);
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * VMCIQPBroker_Alloc --
+ *
+ * Requests that a queue pair be allocated with the VMCI queue
+ * pair broker. Allocates a queue pair entry if one does not
+ * exist. Attaches to one if it exists, and retrieves the page
+ * files backing that QueuePair. Assumes that the queue pair
+ * broker lock is held.
+ *
+ * Results:
+ * Success or failure.
+ *
+ * Side effects:
+ * Memory may be allocated.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+int
+VMCIQPBroker_Alloc(VMCIHandle handle, // IN
+ VMCIId peer, // IN
+ uint32 flags, // IN
+ VMCIPrivilegeFlags privFlags, // IN
+ uint64 produceSize, // IN
+ uint64 consumeSize, // IN
+ QueuePairPageStore *pageStore, // IN/OUT
+ VMCIContext *context) // IN: Caller
+{
+ return VMCIQPBrokerAllocInt(handle, peer, flags, privFlags,
+ produceSize, consumeSize,
+ pageStore, context, NULL);
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * VMCIQPBrokerAllocInt --
+ *
+ * QueuePair_Alloc for use when setting up queue pair endpoints
+ * on the host. Like QueuePair_Alloc, but returns a pointer to
+ * the QPBrokerEntry on success.
+ *
+ * Results:
+ * Success or failure.
+ *
+ * Side effects:
+ * Memory may be allocated.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+static int
+VMCIQPBrokerAllocInt(VMCIHandle handle, // IN
+ VMCIId peer, // IN
+ uint32 flags, // IN
+ VMCIPrivilegeFlags privFlags, // IN
+ uint64 produceSize, // IN
+ uint64 consumeSize, // IN
+ QueuePairPageStore *pageStore, // IN/OUT
+ VMCIContext *context, // IN: Caller
+ QPBrokerEntry **ent) // OUT
+{
+ QPBrokerEntry *entry = NULL;
+ int result;
+ const VMCIId contextId = VMCIContext_GetId(context);
+ Bool isLocal = flags & VMCI_QPFLAG_LOCAL;
+
+ if (VMCI_HANDLE_INVALID(handle) ||
+ (flags & ~VMCI_QP_ALL_FLAGS) ||
+ (isLocal && (!vmkernel || contextId != VMCI_HOST_CONTEXT_ID ||
+ handle.context != contextId)) ||
+ !(produceSize || consumeSize) ||
+ !context || contextId == VMCI_INVALID_ID ||
+ handle.context == VMCI_INVALID_ID) {
+ return VMCI_ERROR_INVALID_ARGS;
+ }
+
+#ifdef VMKERNEL
+ if (!pageStore || (!pageStore->shared && !isLocal)) {
+ return VMCI_ERROR_INVALID_ARGS;
+ }
+#else
+ /*
+ * On hosted, pageStore can be NULL if the caller doesn't want the
+ * information
+ */
+ if (pageStore && !VMCI_QP_PAGESTORE_IS_WELLFORMED(pageStore)) {
+ return VMCI_ERROR_INVALID_ARGS;
+ }
+#endif // VMKERNEL
+
+
+ /*
+ * In the initial argument check, we ensure that non-vmkernel hosts
+ * are not allowed to create local queue pairs.
+ */
+
+ ASSERT(vmkernel || !isLocal);
+
+ if (!isLocal && VMCIHandleArray_HasEntry(context->queuePairArray, handle)) {
+ VMCI_DEBUG_LOG(4, (LGPFX"Context (ID=0x%x) already attached to queue pair "
+ "(handle=0x%x:0x%x).\n",
+ contextId, handle.context, handle.resource));
+ result = VMCI_ERROR_ALREADY_EXISTS;
+ goto out;
+ }
+
+ entry = (QPBrokerEntry *)QueuePairList_FindEntry(&qpBrokerList, handle);
+ if (!entry) { /* Create case. */
+ /*
+ * Do not create if the caller asked not to.
+ */
+
+ if (flags & VMCI_QPFLAG_ATTACH_ONLY) {
+ result = VMCI_ERROR_NOT_FOUND;
+ goto out;
+ }
+
+ /*
+ * Creator's context ID should match handle's context ID or the creator
+ * must allow the context in handle's context ID as the "peer".
+ */
+
+ if (handle.context != contextId && handle.context != peer) {
+ result = VMCI_ERROR_NO_ACCESS;
+ goto out;
+ }
+
+ /*
+ * Check if we should allow this QueuePair connection.
+ */
+
+ if (QueuePairDenyConnection(contextId, peer)) {
+ result = VMCI_ERROR_NO_ACCESS;
+ goto out;
+ }
+
+ entry = VMCI_AllocKernelMem(sizeof *entry, VMCI_MEMORY_ATOMIC);
+ if (!entry) {
+ result = VMCI_ERROR_NO_MEM;
+ goto out;
+ }
+
+ memset(entry, 0, sizeof *entry);
+ entry->qp.handle = handle;
+ entry->qp.peer = peer;
+ entry->qp.flags = flags;
+ entry->qp.produceSize = produceSize;
+ entry->qp.consumeSize = consumeSize;
+ entry->qp.refCount = 1;
+ entry->createId = contextId;
+ entry->attachId = VMCI_INVALID_ID;
+ entry->pageStoreSet = FALSE;
+ entry->allowAttach = TRUE;
+ entry->requireTrustedAttach =
+ (context->privFlags & VMCI_PRIVILEGE_FLAG_RESTRICTED) ? TRUE : FALSE;
+ entry->createdByTrusted =
+ (privFlags & VMCI_PRIVILEGE_FLAG_TRUSTED) ? TRUE : FALSE;
+
+#ifndef VMKERNEL
+ {
+ uint64 numProducePages;
+ uint64 numConsumePages;
+
+ entry->produceQ = VMCIHost_AllocQueue(produceSize);
+ if (entry->produceQ == NULL) {
+ result = VMCI_ERROR_NO_MEM;
+ goto errorDealloc;
+ }
+
+ entry->consumeQ = VMCIHost_AllocQueue(consumeSize);
+ if (entry->consumeQ == NULL) {
+ result = VMCI_ERROR_NO_MEM;
+ goto errorDealloc;
+ }
+
+ entry->attachInfo = VMCI_AllocKernelMem(sizeof *entry->attachInfo,
+ VMCI_MEMORY_NORMAL);
+ if (entry->attachInfo == NULL) {
+ result = VMCI_ERROR_NO_MEM;
+ goto errorDealloc;
+ }
+ memset(entry->attachInfo, 0, sizeof *entry->attachInfo);
+
+ VMCI_InitQueueMutex(entry->produceQ, entry->consumeQ);
+
+ numProducePages = CEILING(produceSize, PAGE_SIZE) + 1;
+ numConsumePages = CEILING(consumeSize, PAGE_SIZE) + 1;
+
+ entry->attachInfo->numProducePages = numProducePages;
+ entry->attachInfo->numConsumePages = numConsumePages;
+ }
+#endif /* !VMKERNEL */
+
+ VMCIList_InitEntry(&entry->qp.listItem);
+
+ QueuePairList_AddEntry(&qpBrokerList, &entry->qp);
+ result = VMCI_SUCCESS_QUEUEPAIR_CREATE;
+ } else { /* Attach case. */
+
+ /*
+ * Check for failure conditions.
+ */
+
+ if (isLocal) {
+ if (!(entry->qp.flags & VMCI_QPFLAG_LOCAL) ||
+ contextId != entry->createId) {
+ result = VMCI_ERROR_INVALID_ARGS;
+ goto out;
+ }
+ } else if (contextId == entry->createId || contextId == entry->attachId) {
+ result = VMCI_ERROR_ALREADY_EXISTS;
+ goto out;
+ }
+
+ /*
+ * QueuePairs are create/destroy entities. There's no notion of
+ * disconnecting/re-attaching, so once a queue pair entry has
+ * been attached to, no further attaches are allowed. This
+ * guards against both re-attaching and attaching to a queue
+ * pair that already has two peers.
+ */
+
+ if (!entry->allowAttach) {
+ result = VMCI_ERROR_UNAVAILABLE;
+ goto out;
+ }
+ ASSERT(entry->qp.refCount < 2);
+ ASSERT(entry->attachId == VMCI_INVALID_ID);
+
+ /*
+ * If we are attaching from a restricted context then the queuepair
+ * must have been created by a trusted endpoint.
+ */
+
+ if (context->privFlags & VMCI_PRIVILEGE_FLAG_RESTRICTED) {
+ if (!entry->createdByTrusted) {
+ result = VMCI_ERROR_NO_ACCESS;
+ goto out;
+ }
+ }
+
+ /*
+ * If we are attaching to a queuepair that was created by a restricted
+ * context then we must be trusted.
+ */
+
+ if (entry->requireTrustedAttach) {
+ if (!(privFlags & VMCI_PRIVILEGE_FLAG_TRUSTED)) {
+ result = VMCI_ERROR_NO_ACCESS;
+ goto out;
+ }
+ }
+
+ /*
+ * If the creator specifies VMCI_INVALID_ID in "peer" field, access
+ * control check is not performed.
+ */
+
+ if (entry->qp.peer != VMCI_INVALID_ID && entry->qp.peer != contextId) {
+ result = VMCI_ERROR_NO_ACCESS;
+ goto out;
+ }
+
+#ifndef VMKERNEL
+ /*
+ * VMKernel doesn't need to check the capabilities because the
+ * whole system is installed as the kernel and matching VMX.
+ */
+
+ if (entry->createId == VMCI_HOST_CONTEXT_ID) {
+ /*
+ * Do not attach if the caller doesn't support Host Queue Pairs
+ * and a host created this queue pair.
+ */
+
+ if (!VMCIContext_SupportsHostQP(context)) {
+ result = VMCI_ERROR_INVALID_RESOURCE;
+ goto out;
+ }
+ } else if (contextId == VMCI_HOST_CONTEXT_ID) {
+ VMCIContext *createContext;
+ Bool supportsHostQP;
+
+ /*
+ * Do not attach a host to a user created queue pair if that
+ * user doesn't support host queue pair end points.
+ */
+
+ createContext = VMCIContext_Get(entry->createId);
+ supportsHostQP = VMCIContext_SupportsHostQP(createContext);
+ VMCIContext_Release(createContext);
+
+ if (!supportsHostQP) {
+ result = VMCI_ERROR_INVALID_RESOURCE;
+ goto out;
+ }
+ }
+#endif // !VMKERNEL
+
+ if (entry->qp.produceSize != consumeSize ||
+ entry->qp.consumeSize != produceSize ||
+ entry->qp.flags != (flags & ~VMCI_QPFLAG_ATTACH_ONLY)) {
+ result = VMCI_ERROR_QUEUEPAIR_MISMATCH;
+ goto out;
+ }
+
+ /*
+ * On VMKERNEL (e.g., ESX) we don't allow an attach until
+ * the page store information has been set.
+ *
+ * However, on hosted products we support an attach to a
+ * QueuePair that hasn't had its page store established yet. In
+ * fact, that's how a VMX guest will approach a host-created
+ * QueuePair. After the VMX guest does the attach, VMX will
+ * receive the CREATE status code to indicate that it should
+ * create the page files for the QueuePair contents. It will
+ * then issue a separate call down to set the page store. That
+ * will complete the attach case.
+ */
+ if (vmkernel && !entry->pageStoreSet) {
+ result = VMCI_ERROR_QUEUEPAIR_NOTSET;
+ goto out;
+ }
+
+ /*
+ * Check if we should allow this QueuePair connection.
+ */
+
+ if (QueuePairDenyConnection(contextId, entry->createId)) {
+ result = VMCI_ERROR_NO_ACCESS;
+ goto out;
+ }
+
+#ifdef VMKERNEL
+ pageStore->store = entry->store.store;
+#else
+ if (pageStore && entry->pageStoreSet) {
+ ASSERT(entry->producePageFile[0] && entry->consumePageFile[0]);
+ if (pageStore->producePageFileSize < sizeof entry->consumePageFile) {
+ result = VMCI_ERROR_NO_MEM;
+ goto out;
+ }
+ if (pageStore->consumePageFileSize < sizeof entry->producePageFile) {
+ result = VMCI_ERROR_NO_MEM;
+ goto out;
+ }
+
+ if (pageStore->user) {
+ if (VMCI_CopyToUser(pageStore->producePageFile,
+ entry->consumePageFile,
+ sizeof entry->consumePageFile)) {
+ result = VMCI_ERROR_GENERIC;
+ goto out;
+ }
+
+ if (VMCI_CopyToUser(pageStore->consumePageFile,
+ entry->producePageFile,
+ sizeof entry->producePageFile)) {
+ result = VMCI_ERROR_GENERIC;
+ goto out;
+ }
+ } else {
+ memcpy(VMCIVA64ToPtr(pageStore->producePageFile),
+ entry->consumePageFile,
+ sizeof entry->consumePageFile);
+ memcpy(VMCIVA64ToPtr(pageStore->consumePageFile),
+ entry->producePageFile,
+ sizeof entry->producePageFile);
+ }
+ }
+#endif // VMKERNEL
+
+ /*
+ * We only send notification if the other end of the QueuePair
+ * is not the host (in hosted products). In the case that a
+ * host created the QueuePair, we'll send notification when the
+ * guest issues the SetPageStore() (see next function). The
+ * reason is that the host can't use the QueuePair until the
+ * SetPageStore() is complete.
+ *
+ * Note that in ESX we always send the notification now
+ * because the host can begin to enqueue immediately.
+ */
+
+ if (vmkernel || entry->createId != VMCI_HOST_CONTEXT_ID) {
+ result = QueuePairNotifyPeer(TRUE, handle, contextId, entry->createId);
+ if (result < VMCI_SUCCESS) {
+ goto out;
+ }
+ }
+
+ entry->attachId = contextId;
+ entry->qp.refCount++;
+ entry->allowAttach = FALSE;
+
+ /*
+ * Default response to an attach is _ATTACH. However, if a host
+ * created the QueuePair then we're a guest (because
+ * host-to-host isn't supported). And thus, the guest's VMX
+ * needs to create the backing for the port. So, we send up a
+ * _CREATE response.
+ */
+
+ if (!vmkernel && entry->createId == VMCI_HOST_CONTEXT_ID) {
+ result = VMCI_SUCCESS_QUEUEPAIR_CREATE;
+ } else {
+ result = VMCI_SUCCESS_QUEUEPAIR_ATTACH;
+ }
+ }
+
+#ifndef VMKERNEL
+ goto out;
+
+ /*
+ * Cleanup is only necessary on hosted
+ */
+
+errorDealloc:
+ if (entry->produceQ != NULL) {
+ VMCI_FreeKernelMem(entry->produceQ, sizeof *entry->produceQ);
+ }
+ if (entry->consumeQ != NULL) {
+ VMCI_FreeKernelMem(entry->consumeQ, sizeof *entry->consumeQ);
+ }
+ if (entry->attachInfo != NULL) {
+ VMCI_FreeKernelMem(entry->attachInfo, sizeof *entry->attachInfo);
+ }
+ VMCI_FreeKernelMem(entry, sizeof *entry);
+#endif // !VMKERNEL
+
+out:
+ if (result >= VMCI_SUCCESS) {
+ ASSERT(entry);
+ if (ent != NULL) {
+ *ent = entry;
+ }
+
+ /*
+ * When attaching to local queue pairs, the context already has
+ * an entry tracking the queue pair, so don't add another one.
+ */
+
+ if (!isLocal || result == VMCI_SUCCESS_QUEUEPAIR_CREATE) {
+ ASSERT(!VMCIHandleArray_HasEntry(context->queuePairArray, handle));
+ VMCIHandleArray_AppendEntry(&context->queuePairArray, handle);
+ } else {
+ ASSERT(VMCIHandleArray_HasEntry(context->queuePairArray, handle));
+ }
+ }
+ return result;
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * VMCIQPBroker_SetPageStore --
+ *
+ * The creator of a queue pair uses this to regsiter the page
+ * store for a given queue pair. Assumes that the queue pair
+ * broker lock is held.
+ *
+ * Note now that sometimes the client that attaches to a queue
+ * pair will set the page store. This happens on hosted products
+ * because the host doesn't have a mechanism for creating the
+ * backing memory for queue contents. ESX does and so this is a
+ * moot point there. For example, note that in
+ * VMCIQPBrokerAllocInt() an attaching guest receives the _CREATE
+ * result code (instead of _ATTACH) on hosted products only, not
+ * on VMKERNEL.
+ *
+ * As a result, this routine now always creates the host
+ * information even if the queue pair is only used by guests. At
+ * the time a guest creates a queue pair it doesn't know if a
+ * host or guest will attach. So, the host information always
+ * has to be created.
+ *
+ * Results:
+ * Success or failure.
+ *
+ * Side effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+int
+VMCIQPBroker_SetPageStore(VMCIHandle handle, // IN
+ QueuePairPageStore *pageStore, // IN
+ VMCIContext *context) // IN: Caller
+{
+ QPBrokerEntry *entry;
+ int result;
+ const VMCIId contextId = VMCIContext_GetId(context);
+#ifndef VMKERNEL
+ QueuePairPageStore normalizedPageStore;
+#endif
+
+ if (VMCI_HANDLE_INVALID(handle) || !pageStore ||
+ !VMCI_QP_PAGESTORE_IS_WELLFORMED(pageStore) ||
+ !context || contextId == VMCI_INVALID_ID) {
+ return VMCI_ERROR_INVALID_ARGS;
+ }
+
+ if (!VMCIHandleArray_HasEntry(context->queuePairArray, handle)) {
+ VMCI_WARNING((LGPFX"Context (ID=0x%x) not attached to queue pair "
+ "(handle=0x%x:0x%x).\n", contextId, handle.context,
+ handle.resource));
+ result = VMCI_ERROR_NOT_FOUND;
+ goto out;
+ }
+
+#ifndef VMKERNEL
+ /*
+ * If the client supports Host QueuePairs then it must provide the
+ * UVA's of the mmap()'d files backing the QueuePairs.
+ */
+
+ if (VMCIContext_SupportsHostQP(context) &&
+ (pageStore->producePageUVA == 0 ||
+ pageStore->consumePageUVA == 0)) {
+ return VMCI_ERROR_INVALID_ARGS;
+ }
+#endif // !VMKERNEL
+
+ entry = (QPBrokerEntry *)QueuePairList_FindEntry(&qpBrokerList, handle);
+ if (!entry) {
+ result = VMCI_ERROR_NOT_FOUND;
+ goto out;
+ }
+
+ /*
+ * If I'm the owner then I can set the page store.
+ *
+ * Or, if a host created the QueuePair and I'm the attached peer
+ * then I can set the page store.
+ */
+
+ if (entry->createId != contextId &&
+ (entry->createId != VMCI_HOST_CONTEXT_ID ||
+ entry->attachId != contextId)) {
+ result = VMCI_ERROR_QUEUEPAIR_NOTOWNER;
+ goto out;
+ }
+ if (entry->pageStoreSet) {
+ result = VMCI_ERROR_UNAVAILABLE;
+ goto out;
+ }
+#ifdef VMKERNEL
+ entry->store = *pageStore;
+#else
+ /*
+ * Normalize the page store information from the point of view of
+ * the VMX process with respect to the QueuePair. If VMX has
+ * attached to a host-created QueuePair and is passing down
+ * PageStore information then we must switch the produce/consume
+ * queue information before applying it to the QueuePair.
+ *
+ * In other words, the QueuePair structure (entry->state) is
+ * oriented with respect to the host that created it. However, VMX
+ * is sending down information relative to its view of the world
+ * which is opposite of the host's.
+ */
+
+ if (entry->createId == contextId) {
+ normalizedPageStore.producePageFile = pageStore->producePageFile;
+ normalizedPageStore.consumePageFile = pageStore->consumePageFile;
+ normalizedPageStore.producePageFileSize = pageStore->producePageFileSize;
+ normalizedPageStore.consumePageFileSize = pageStore->consumePageFileSize;
+ normalizedPageStore.producePageUVA = pageStore->producePageUVA;
+ normalizedPageStore.consumePageUVA = pageStore->consumePageUVA;
+ } else {
+ normalizedPageStore.producePageFile = pageStore->consumePageFile;
+ normalizedPageStore.consumePageFile = pageStore->producePageFile;
+ normalizedPageStore.producePageFileSize = pageStore->consumePageFileSize;
+ normalizedPageStore.consumePageFileSize = pageStore->producePageFileSize;
+ normalizedPageStore.producePageUVA = pageStore->consumePageUVA;
+ normalizedPageStore.consumePageUVA = pageStore->producePageUVA;
+ }
+
+ if (normalizedPageStore.producePageFileSize > sizeof entry->producePageFile) {
+ result = VMCI_ERROR_NO_MEM;
+ goto out;
+ }
+ if (normalizedPageStore.consumePageFileSize > sizeof entry->consumePageFile) {
+ result = VMCI_ERROR_NO_MEM;
+ goto out;
+ }
+ if (pageStore->user) {
+ if (VMCI_CopyFromUser(entry->producePageFile,
+ normalizedPageStore.producePageFile,
+ (size_t)normalizedPageStore.producePageFileSize)) {
+ result = VMCI_ERROR_GENERIC;
+ goto out;
+ }
+
+ if (VMCI_CopyFromUser(entry->consumePageFile,
+ normalizedPageStore.consumePageFile,
+ (size_t)normalizedPageStore.consumePageFileSize)) {
+ result = VMCI_ERROR_GENERIC;
+ goto out;
+ }
+ } else {
+ memcpy(entry->consumePageFile,
+ VMCIVA64ToPtr(normalizedPageStore.consumePageFile),
+ (size_t)normalizedPageStore.consumePageFileSize);
+ memcpy(entry->producePageFile,
+ VMCIVA64ToPtr(normalizedPageStore.producePageFile),
+ (size_t)normalizedPageStore.producePageFileSize);
+ }
+
+ /*
+ * Copy the data into the attachInfo structure
+ */
+
+ memcpy(&entry->attachInfo->producePageFile[0],
+ &entry->producePageFile[0],
+ (size_t)normalizedPageStore.producePageFileSize);
+ memcpy(&entry->attachInfo->consumePageFile[0],
+ &entry->consumePageFile[0],
+ (size_t)normalizedPageStore.consumePageFileSize);
+
+ /*
+ * NOTE: The UVAs that follow may be 0. In this case an older VMX has
+ * issued a SetPageFile call without mapping the backing files for the
+ * queue contents. The result of this is that the queue pair cannot
+ * be connected by host.
+ */
+
+ entry->attachInfo->produceBuffer = normalizedPageStore.producePageUVA;
+ entry->attachInfo->consumeBuffer = normalizedPageStore.consumePageUVA;
+
+ if (VMCIContext_SupportsHostQP(context)) {
+ result = VMCIHost_GetUserMemory(entry->attachInfo,
+ entry->produceQ,
+ entry->consumeQ);
+
+ if (result < VMCI_SUCCESS) {
+ goto out;
+ }
+ }
+#endif // VMKERNEL
+
+ /*
+ * In the event that the QueuePair was created by a host in a
+ * hosted kernel, then we send notification now that the QueuePair
+ * contents backing files are attached to the Queues. Note in
+ * VMCIQPBrokerAllocInt(), above, we skipped this step when the
+ * creator was a host (on hosted).
+ */
+
+ if (!vmkernel && entry->createId == VMCI_HOST_CONTEXT_ID) {
+ result = QueuePairNotifyPeer(TRUE, handle, contextId, entry->createId);
+ if (result < VMCI_SUCCESS) {
+ goto out;
+ }
+ }
+
+ entry->pageStoreSet = TRUE;
+ result = VMCI_SUCCESS;
+
+out:
+ return result;
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * VMCIQPBroker_Detach --
+ *
+ * Informs the VMCI queue pair broker that a context has detached
+ * from a given QueuePair handle. Assumes that the queue pair
+ * broker lock is held. If the "detach" input parameter is
+ * FALSE, the queue pair entry is not removed from the list of
+ * queue pairs registered with the queue pair broker, and the
+ * context is not detached from the given handle. If "detach" is
+ * TRUE, the detach operation really happens. With "detach" set
+ * to FALSE, the caller can query if the "actual" detach
+ * operation would succeed or not. The return value from this
+ * function remains the same irrespective of the value of the
+ * boolean "detach".
+ *
+ * Also note that the result code for a VM detaching from a
+ * VM-host queue pair is always VMCI_SUCCESS_LAST_DETACH. This
+ * is so that VMX can unlink the backing files. On the host side
+ * the files are either locked (Mac OS/Linux) or the contents are
+ * saved (Windows).
+ *
+ * Results:
+ * Success or failure.
+ *
+ * Side effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+int
+VMCIQPBroker_Detach(VMCIHandle handle, // IN
+ VMCIContext *context, // IN
+ Bool detach) // IN: Really detach?
+{
+ QPBrokerEntry *entry;
+ int result;
+ const VMCIId contextId = VMCIContext_GetId(context);
+ VMCIId peerId;
+ Bool isLocal = FALSE;
+
+ if (VMCI_HANDLE_INVALID(handle) ||
+ !context || contextId == VMCI_INVALID_ID) {
+ return VMCI_ERROR_INVALID_ARGS;
+ }
+
+ if (!VMCIHandleArray_HasEntry(context->queuePairArray, handle)) {
+ VMCI_DEBUG_LOG(4, (LGPFX"Context (ID=0x%x) not attached to queue pair "
+ "(handle=0x%x:0x%x).\n",
+ contextId, handle.context, handle.resource));
+ result = VMCI_ERROR_NOT_FOUND;
+ goto out;
+ }
+
+ entry = (QPBrokerEntry *)QueuePairList_FindEntry(&qpBrokerList, handle);
+ if (!entry) {
+ result = VMCI_ERROR_NOT_FOUND;
+ goto out;
+ }
+
+ isLocal = entry->qp.flags & VMCI_QPFLAG_LOCAL;
+
+ ASSERT(vmkernel || !isLocal);
+
+ if (contextId != entry->createId && contextId != entry->attachId) {
+ result = VMCI_ERROR_QUEUEPAIR_NOTATTACHED;
+ goto out;
+ }
+
+ if (contextId == entry->createId) {
+ peerId = entry->attachId;
+ } else {
+ peerId = entry->createId;
+ }
+
+ if (!detach) {
+ /* Do not update the queue pair entry. */
+
+ ASSERT(entry->qp.refCount == 1 || entry->qp.refCount == 2);
+
+ if (entry->qp.refCount == 1 ||
+ (!vmkernel && peerId == VMCI_HOST_CONTEXT_ID)) {
+ result = VMCI_SUCCESS_LAST_DETACH;
+ } else {
+ result = VMCI_SUCCESS;
+ }
+
+ goto out;
+ }
+
+ if (contextId == entry->createId) {
+ entry->createId = VMCI_INVALID_ID;
+ } else {
+ entry->attachId = VMCI_INVALID_ID;
+ }
+ entry->qp.refCount--;
+
+#ifdef _WIN32
+ /*
+ * If the caller detaching is a usermode process (e.g., VMX), then
+ * we must detach the mappings now. On Windows.
+ *
+ * VMCIHost_SaveProduceQ() will save the guest's produceQ so that
+ * the host can pick up the data after the guest is gone.
+ *
+ * We save the ProduceQ whenever the guest detaches (even if VMX
+ * continues to run). If we didn't do this, then we'd have the
+ * problem of finding and releasing the memory when the client goes
+ * away because we won't be able to find the client in the list of
+ * QueuePair entries. The detach code path (has already) set the
+ * contextId for detached end-point to VMCI_INVALID_ID. (See just
+ * a few lines above where that happens.) Sure, we could fix that,
+ * and then we could look at all entries finding ones where the
+ * contextId of either creator or attach matches the going away
+ * context's Id. But, if we just copy out the guest's produceQ
+ * -always- then we reduce the logic changes elsewhere.
+ */
+
+ /*
+ * Some example paths through this code:
+ *
+ * Guest-to-guest: the code will call ReleaseUserMemory() once when
+ * the first guest detaches. And then a second time when the
+ * second guest detaches. That's OK. Nobody is using the user
+ * memory (because there is no host attached) and
+ * ReleaseUserMemory() tracks its resources.
+ *
+ * Host detaches first: the code will not call anything because
+ * contextId == VMCI_HOST_CONTEXT_ID and because (in the second if
+ * () clause below) refCount > 0.
+ *
+ * Guest detaches second: the first if clause, below, will not be
+ * taken because refCount is already 0. The second if () clause
+ * (below) will be taken and it will simply call
+ * ReleaseUserMemory().
+ *
+ * Guest detaches first: the code will call SaveProduceQ().
+ *
+ * Host detaches second: the code will call ReleaseUserMemory()
+ * which will free the kernel allocated Q memory.
+ */
+
+ if (entry->pageStoreSet &&
+ contextId != VMCI_HOST_CONTEXT_ID &&
+ VMCIContext_SupportsHostQP(context) &&
+ entry->qp.refCount) {
+ /*
+ * It's important to pass down produceQ and consumeQ in the
+ * correct order because the produceQ that is to be saved is the
+ * guest's, so we have to be sure that the routine sees the
+ * guest's produceQ as (in this case) the first Q parameter.
+ */
+
+ if (entry->attachId == VMCI_HOST_CONTEXT_ID) {
+ VMCIHost_SaveProduceQ(entry->attachInfo,
+ entry->produceQ,
+ entry->consumeQ,
+ entry->qp.produceSize);
+ } else if (entry->createId == VMCI_HOST_CONTEXT_ID) {
+ VMCIHost_SaveProduceQ(entry->attachInfo,
+ entry->consumeQ,
+ entry->produceQ,
+ entry->qp.consumeSize);
+ } else {
+ VMCIHost_ReleaseUserMemory(entry->attachInfo,
+ entry->produceQ,
+ entry->consumeQ);
+ }
+ }
+#endif // _WIN32
+
+ if (!entry->qp.refCount) {
+ QueuePairList_RemoveEntry(&qpBrokerList, &entry->qp);
+
+#ifndef VMKERNEL
+ if (entry->pageStoreSet &&
+ VMCIContext_SupportsHostQP(context)) {
+ VMCIHost_ReleaseUserMemory(entry->attachInfo,
+ entry->produceQ,
+ entry->consumeQ);
+ }
+ if (entry->attachInfo) {
+ VMCI_FreeKernelMem(entry->attachInfo, sizeof *entry->attachInfo);
+ }
+ if (entry->produceQ) {
+ VMCI_FreeKernelMem(entry->produceQ, sizeof *entry->produceQ);
+ }
+ if (entry->consumeQ) {
+ VMCI_FreeKernelMem(entry->consumeQ, sizeof *entry->consumeQ);
+ }
+#endif // !VMKERNEL
+
+ VMCI_FreeKernelMem(entry, sizeof *entry);
+ result = VMCI_SUCCESS_LAST_DETACH;
+ } else {
+ /*
+ * XXX: If we ever allow the creator to detach and attach again
+ * to the same queue pair, we need to handle the mapping of the
+ * shared memory region in vmkernel differently. Currently, we
+ * assume that an attaching VM always needs to swap the two
+ * queues.
+ */
+
+ ASSERT(peerId != VMCI_INVALID_ID);
+ QueuePairNotifyPeer(FALSE, handle, contextId, peerId);
+ if (!vmkernel && peerId == VMCI_HOST_CONTEXT_ID) {
+ result = VMCI_SUCCESS_LAST_DETACH;
+ } else {
+ result = VMCI_SUCCESS;
+ }
+ }
+
+out:
+ if (result >= VMCI_SUCCESS && detach) {
+ if (!isLocal || result == VMCI_SUCCESS_LAST_DETACH) {
+ VMCIHandleArray_RemoveEntry(context->queuePairArray, handle);
+ }
+ }
+ return result;
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * QueuePairNotifyPeer --
+ *
+ * Enqueues an event datagram to notify the peer VM attached to
+ * the given queue pair handle about attach/detach event by the
+ * given VM.
+ *
+ * Results:
+ * Payload size of datagram enqueued on success, error code otherwise.
+ *
+ * Side effects:
+ * Memory is allocated.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+int
+QueuePairNotifyPeer(Bool attach, // IN: attach or detach?
+ VMCIHandle handle, // IN
+ VMCIId myId, // IN
+ VMCIId peerId) // IN: CID of VM to notify
+{
+ int rv;
+ VMCIEventMsg *eMsg;
+ VMCIEventPayload_QP *evPayload;
+ char buf[sizeof *eMsg + sizeof *evPayload];
+
+ if (VMCI_HANDLE_INVALID(handle) || myId == VMCI_INVALID_ID ||
+ peerId == VMCI_INVALID_ID) {
+ return VMCI_ERROR_INVALID_ARGS;
+ }
-#define LGPFX "VMCIQueuePair: "
+ /*
+ * Notification message contains: queue pair handle and
+ * attaching/detaching VM's context id.
+ */
-typedef struct QueuePairEntry {
- VMCIListItem listItem;
- VMCIHandle handle;
- VMCIId peer;
- uint32 flags;
- uint64 produceSize;
- uint64 consumeSize;
- uint32 refCount;
-} QueuePairEntry;
+ eMsg = (VMCIEventMsg *)buf;
-typedef struct QPGuestEndpoint {
- QueuePairEntry qp;
- uint64 numPPNs;
- void *produceQ;
- void *consumeQ;
- Bool hibernateFailure;
- PPNSet ppnSet;
-} QPGuestEndpoint;
+ /*
+ * In VMCIContext_EnqueueDatagram() we enforce the upper limit on number of
+ * pending events from the hypervisor to a given VM otherwise a rogue VM
+ * could do arbitrary number of attached and detaches causing memory
+ * pressure in the host kernel.
+ */
-typedef struct QueuePairList {
- VMCIList head;
- Atomic_uint32 hibernate;
- VMCIMutex lock;
-} QueuePairList;
+ /* Clear out any garbage. */
+ memset(eMsg, 0, sizeof *eMsg + sizeof *evPayload);
-static QueuePairList qpGuestEndpoints;
-static VMCIHandleArray *hibernateFailedList;
-static VMCILock hibernateFailedListLock;
+ eMsg->hdr.dst = VMCI_MAKE_HANDLE(peerId, VMCI_EVENT_HANDLER);
+ eMsg->hdr.src = VMCI_MAKE_HANDLE(VMCI_HYPERVISOR_CONTEXT_ID,
+ VMCI_CONTEXT_RESOURCE_ID);
+ eMsg->hdr.payloadSize = sizeof *eMsg + sizeof *evPayload - sizeof eMsg->hdr;
+ eMsg->eventData.event = attach ? VMCI_EVENT_QP_PEER_ATTACH :
+ VMCI_EVENT_QP_PEER_DETACH;
+ evPayload = VMCIEventMsgPayload(eMsg);
+ evPayload->handle = handle;
+ evPayload->peerId = myId;
+
+ rv = VMCIDatagram_Dispatch(VMCI_HYPERVISOR_CONTEXT_ID, (VMCIDatagram *)eMsg,
+ FALSE);
+ if (rv < VMCI_SUCCESS) {
+ VMCI_WARNING((LGPFX"Failed to enqueue QueuePair %s event datagram for "
+ "context (ID=0x%x).\n", attach ? "ATTACH" : "DETACH",
+ peerId));
+ }
-static QueuePairEntry *QueuePairList_FindEntry(QueuePairList *qpList,
- VMCIHandle handle);
-static void QueuePairList_AddEntry(QueuePairList *qpList,
- QueuePairEntry *entry);
-static void QueuePairList_RemoveEntry(QueuePairList *qpList,
- QueuePairEntry *entry);
-static QueuePairEntry *QueuePairList_GetHead(QueuePairList *qpList);
-static QPGuestEndpoint *QPGuestEndpointCreate(VMCIHandle handle,
- VMCIId peer, uint32 flags,
- uint64 produceSize,
- uint64 consumeSize,
- void *produceQ, void *consumeQ);
-static void QPGuestEndpointDestroy(QPGuestEndpoint *entry);
-static int VMCIQueuePairAlloc_HyperCall(const QPGuestEndpoint *entry);
-static int VMCIQueuePairAllocHelper(VMCIHandle *handle, VMCIQueue **produceQ,
- uint64 produceSize, VMCIQueue **consumeQ,
- uint64 consumeSize,
- VMCIId peer, uint32 flags);
-static int VMCIQueuePairDetachHelper(VMCIHandle handle);
-static int VMCIQueuePairDetachHyperCall(VMCIHandle handle);
-static int QueuePairNotifyPeerLocal(Bool attach, VMCIHandle handle);
-static void VMCIQPMarkHibernateFailed(QPGuestEndpoint *entry);
-static void VMCIQPUnmarkHibernateFailed(QPGuestEndpoint *entry);
+ return rv;
+}
+#if !defined(VMKERNEL)
/*
- *-----------------------------------------------------------------------------
+ *----------------------------------------------------------------------
*
- * QueuePairList_Init --
+ * VMCIQueuePairAllocHostWork --
*
- * Initializes a queue pair list.
+ * This function implements the kernel API for allocating a queue
+ * pair.
*
* Results:
- * Success or failure.
+ * VMCI_SUCCESS on succes and appropriate failure code otherwise.
*
* Side effects:
- * None.
+ * May allocate memory.
*
- *-----------------------------------------------------------------------------
+ *----------------------------------------------------------------------
*/
-static INLINE int
-QueuePairList_Init(QueuePairList *qpList)
+static int
+VMCIQueuePairAllocHostWork(VMCIHandle *handle, // IN/OUT
+ VMCIQueue **produceQ, // OUT
+ uint64 produceSize, // IN
+ VMCIQueue **consumeQ, // OUT
+ uint64 consumeSize, // IN
+ VMCIId peer, // IN
+ uint32 flags, // IN
+ VMCIPrivilegeFlags privFlags) // IN
{
- int ret;
+ VMCIContext *context;
+ int result;
+ QPBrokerEntry *entry;
- VMCIList_Init(&qpList->head);
- Atomic_Write(&qpList->hibernate, 0);
- ret = VMCIMutex_Init(&qpList->lock);
+ result = VMCI_SUCCESS;
- return ret;
+ if (VMCI_HANDLE_INVALID(*handle)) {
+ VMCIId resourceID = VMCIResource_GetID(VMCI_HOST_CONTEXT_ID);
+ if (resourceID == VMCI_INVALID_ID) {
+ return VMCI_ERROR_NO_HANDLE;
+ }
+ *handle = VMCI_MAKE_HANDLE(VMCI_HOST_CONTEXT_ID, resourceID);
+ }
+
+ context = VMCIContext_Get(VMCI_HOST_CONTEXT_ID);
+ ASSERT(context);
+
+ entry = NULL;
+ VMCIQPBroker_Lock();
+ result = VMCIQPBrokerAllocInt(*handle, peer, flags, privFlags, produceSize,
+ consumeSize, NULL, context, &entry);
+
+ if (result >= VMCI_SUCCESS) {
+ ASSERT(entry != NULL);
+
+ if (entry->createId == VMCI_HOST_CONTEXT_ID) {
+ *produceQ = entry->produceQ;
+ *consumeQ = entry->consumeQ;
+ } else {
+ *produceQ = entry->consumeQ;
+ *consumeQ = entry->produceQ;
+ }
+
+ result = VMCI_SUCCESS;
+ } else {
+ *handle = VMCI_INVALID_HANDLE;
+ VMCI_DEBUG_LOG(4, (LGPFX"queue pair broker failed to alloc (result=%d).\n",
+ result));
+ }
+
+ VMCIQPBroker_Unlock();
+ VMCIContext_Release(context);
+ return result;
}
/*
- *-----------------------------------------------------------------------------
+ *----------------------------------------------------------------------
*
- * QueuePairList_Destroy --
+ * VMCIQueuePairDetachHostWork --
*
- * Cleans up state queue pair list state created by
- * QueuePairList_Init. It destroys the lock protecting the
- * QueuePair list.
+ * This function implements the host kernel API for detaching from
+ * a queue pair.
*
* Results:
- * None.
+ * VMCI_SUCCESS on success and appropriate failure code otherwise.
*
* Side effects:
- * None.
+ * May deallocate memory.
*
- *-----------------------------------------------------------------------------
+ *----------------------------------------------------------------------
*/
-static INLINE void
-QueuePairList_Destroy(QueuePairList *qpList)
+static int
+VMCIQueuePairDetachHostWork(VMCIHandle handle) // IN
{
- VMCIList_Init(&qpList->head);
- VMCIMutex_Destroy(&qpList->lock); /* No-op on Linux and Windows. */
+ int result;
+ VMCIContext *context;
+
+ context = VMCIContext_Get(VMCI_HOST_CONTEXT_ID);
+
+ VMCIQPBroker_Lock();
+ result = VMCIQPBroker_Detach(handle, context, TRUE);
+ VMCIQPBroker_Unlock();
+
+ VMCIContext_Release(context);
+ return result;
}
{
QPGuestEndpoint *entry;
- VMCIMutex_Acquire(&qpGuestEndpoints.lock);
+ VMCIQPLock_Acquire(&qpGuestEndpoints.lock);
while ((entry = (QPGuestEndpoint *)QueuePairList_GetHead(&qpGuestEndpoints))) {
/*
* Don't make a hypercall for local QueuePairs.
*/
if (!(entry->qp.flags & VMCI_QPFLAG_LOCAL)) {
- VMCIQueuePairDetachMsg detachMsg;
-
- detachMsg.hdr.dst = VMCI_MAKE_HANDLE(VMCI_HYPERVISOR_CONTEXT_ID,
- VMCI_QUEUEPAIR_DETACH);
- detachMsg.hdr.src = VMCI_ANON_SRC_HANDLE;
- detachMsg.hdr.payloadSize = sizeof entry->qp.handle;
- detachMsg.handle = entry->qp.handle;
-
- (void)VMCI_SendDatagram((VMCIDatagram *)&detachMsg);
+ VMCIQueuePairDetachHypercall(entry->qp.handle);
}
/*
* We cannot fail the exit, so let's reset refCount.
}
Atomic_Write(&qpGuestEndpoints.hibernate, 0);
- VMCIMutex_Release(&qpGuestEndpoints.lock);
+ VMCIQPLock_Release(&qpGuestEndpoints.lock);
QueuePairList_Destroy(&qpGuestEndpoints);
VMCI_CleanupLock(&hibernateFailedListLock);
VMCIHandleArray_Destroy(hibernateFailedList);
void
VMCIQPGuestEndpoints_Sync(void)
{
- VMCIMutex_Acquire(&qpGuestEndpoints.lock);
- VMCIMutex_Release(&qpGuestEndpoints.lock);
-}
-
-
-/*
- *-----------------------------------------------------------------------------
- *
- * QueuePairList_FindEntry --
- *
- * Searches the list of QueuePairs to find if an entry already exists.
- * Assumes that the lock on the list is held.
- *
- * Results:
- * Pointer to the entry if it exists, NULL otherwise.
- *
- * Side effects:
- * None.
- *
- *-----------------------------------------------------------------------------
- */
-
-static QueuePairEntry *
-QueuePairList_FindEntry(QueuePairList *qpList, // IN
- VMCIHandle handle) // IN
-{
- VMCIListItem *next;
-
- if (VMCI_HANDLE_INVALID(handle)) {
- return NULL;
- }
-
- VMCIList_Scan(next, &qpList->head) {
- QueuePairEntry *entry = VMCIList_Entry(next, QueuePairEntry, listItem);
-
- if (VMCI_HANDLE_EQUAL(entry->handle, handle)) {
- return entry;
- }
- }
-
- return NULL;
-}
-
-
-/*
- *-----------------------------------------------------------------------------
- *
- * QueuePairList_AddEntry --
- *
- * Appends a QueuePair entry to the list. Assumes that the lock on the
- * list is held.
- *
- * Results:
- * None.
- *
- * Side effects:
- * None.
- *
- *-----------------------------------------------------------------------------
- */
-
-static void
-QueuePairList_AddEntry(QueuePairList *qpList, // IN
- QueuePairEntry *entry) // IN
-{
- if (entry) {
- VMCIList_Insert(&entry->listItem, &qpList->head);
- }
-}
-
-
-/*
- *-----------------------------------------------------------------------------
- *
- * QueuePairList_RemoveEntry --
- *
- * Removes a QueuePair entry from the list. Assumes that the lock on the
- * list is held.
- *
- * Results:
- * None.
- *
- * Side effects:
- * None.
- *
- *-----------------------------------------------------------------------------
- */
-
-static void
-QueuePairList_RemoveEntry(QueuePairList *qpList, // IN
- QueuePairEntry *entry) // IN
-{
- if (entry) {
- VMCIList_Remove(&entry->listItem, &qpList->head);
- }
-}
-
-
-/*
- *-----------------------------------------------------------------------------
- *
- * QueuePairList_GetHead --
- *
- * Returns the entry from the head of the list. Assumes that the list is
- * locked.
- *
- * Results:
- * Pointer to entry.
- *
- * Side effects:
- * None.
- *
- *-----------------------------------------------------------------------------
- */
-
-static QueuePairEntry *
-QueuePairList_GetHead(QueuePairList *qpList) // IN
-{
- VMCIListItem *first = VMCIList_First(&qpList->head);
-
- if (first) {
- QueuePairEntry *entry = VMCIList_Entry(first, QueuePairEntry, listItem);
- return entry;
- }
-
- return NULL;
-}
-
-
-/*
- *-----------------------------------------------------------------------------
- *
- * VMCIQueuePair_Alloc --
- *
- * Allocates a VMCI QueuePair. Only checks validity of input
- * arguments. Real work is done in the OS-specific helper
- * routine. The privilege flags argument is present to provide
- * compatibility with the host API; anything other than
- * VMCI_NO_PRIVILEGE_FLAGS will result in the error
- * VMCI_ERROR_NO_ACCESS, since requesting privileges from the
- * guest is not allowed.
- *
- * Results:
- * VMCI_SUCCESS on success, appropriate error code otherwise.
- *
- * Side effects:
- * None.
- *
- *-----------------------------------------------------------------------------
- */
-
-int
-VMCIQueuePair_Alloc(VMCIHandle *handle, // IN/OUT
- VMCIQueue **produceQ, // OUT
- uint64 produceSize, // IN
- VMCIQueue **consumeQ, // OUT
- uint64 consumeSize, // IN
- VMCIId peer, // IN
- uint32 flags, // IN
- VMCIPrivilegeFlags privFlags) // IN
-{
- if (privFlags != VMCI_NO_PRIVILEGE_FLAGS) {
- return VMCI_ERROR_NO_ACCESS;
- }
-
- if (!handle || !produceQ || !consumeQ || (!produceSize && !consumeSize) ||
- (flags & ~VMCI_QP_ALL_FLAGS)) {
- return VMCI_ERROR_INVALID_ARGS;
- }
-
- return VMCIQueuePairAllocHelper(handle, produceQ, produceSize, consumeQ,
- consumeSize, peer, flags);
-}
-
-
-/*
- *-----------------------------------------------------------------------------
- *
- * VMCIQueuePair_Detach --
- *
- * Detaches from a VMCI QueuePair. Only checks validity of input argument.
- * Real work is done in the OS-specific helper routine.
- *
- * Results:
- * Success or failure.
- *
- * Side effects:
- * Memory is freed.
- *
- *-----------------------------------------------------------------------------
- */
-
-int
-VMCIQueuePair_Detach(VMCIHandle handle) // IN
-{
- if (VMCI_HANDLE_INVALID(handle)) {
- return VMCI_ERROR_INVALID_ARGS;
- }
- return VMCIQueuePairDetachHelper(handle);
+ VMCIQPLock_Acquire(&qpGuestEndpoints.lock);
+ VMCIQPLock_Release(&qpGuestEndpoints.lock);
}
/*
*-----------------------------------------------------------------------------
*
- * VMCIQueuePairAlloc_HyperCall --
+ * VMCIQueuePairAllocHypercall --
*
- * Helper to make a QueuePairAlloc hypercall.
+ * Helper to make a QueuePairAlloc hypercall when the driver is
+ * supporting a guest device.
*
* Results:
* Result of the hypercall.
*-----------------------------------------------------------------------------
*/
-int
-VMCIQueuePairAlloc_HyperCall(const QPGuestEndpoint *entry) // IN
+static int
+VMCIQueuePairAllocHypercall(const QPGuestEndpoint *entry) // IN
{
VMCIQueuePairAllocMsg *allocMsg;
size_t msgSize;
/*
*-----------------------------------------------------------------------------
*
- * VMCIQueuePairAllocHelper --
+ * VMCIQueuePairAllocGuestWork --
*
- * Helper for VMCI QueuePairAlloc. Allocates physical pages for the
- * QueuePair. Makes OS dependent calls through generic wrappers.
+ * 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.
*/
static int
-VMCIQueuePairAllocHelper(VMCIHandle *handle, // IN/OUT
- VMCIQueue **produceQ, // OUT
- uint64 produceSize, // IN
- VMCIQueue **consumeQ, // OUT
- uint64 consumeSize, // IN
- VMCIId peer, // IN
- uint32 flags) // IN
+VMCIQueuePairAllocGuestWork(VMCIHandle *handle, // IN/OUT
+ VMCIQueue **produceQ, // OUT
+ uint64 produceSize, // IN
+ VMCIQueue **consumeQ, // OUT
+ uint64 consumeSize, // IN
+ VMCIId peer, // IN
+ uint32 flags, // IN
+ VMCIPrivilegeFlags privFlags) // IN
+
{
const uint64 numProducePages = CEILING(produceSize, PAGE_SIZE) + 1;
const uint64 numConsumePages = CEILING(consumeSize, PAGE_SIZE) + 1;
ASSERT(handle && produceQ && consumeQ && (produceSize || consumeSize));
- VMCIMutex_Acquire(&qpGuestEndpoints.lock);
+ if (privFlags != VMCI_NO_PRIVILEGE_FLAGS) {
+ return VMCI_ERROR_NO_ACCESS;
+ }
+
+ VMCIQPLock_Acquire(&qpGuestEndpoints.lock);
/* Do not allow alloc/attach if the device is being shutdown. */
if (VMCI_DeviceShutdown()) {
goto error;
}
} else {
- result = VMCIQueuePairAlloc_HyperCall(queuePairEntry);
+ result = VMCIQueuePairAllocHypercall(queuePairEntry);
if (result < VMCI_SUCCESS) {
- VMCI_WARNING((LGPFX"VMCIQueuePairAlloc_HyperCall result = %d.\n",
+ VMCI_WARNING((LGPFX"VMCIQueuePairAllocHypercall result = %d.\n",
result));
goto error;
}
VMCIQueueHeader_Init((*consumeQ)->qHeader, *handle);
}
- VMCIMutex_Release(&qpGuestEndpoints.lock);
+ VMCIQPLock_Release(&qpGuestEndpoints.lock);
return VMCI_SUCCESS;
error:
- VMCIMutex_Release(&qpGuestEndpoints.lock);
+ VMCIQPLock_Release(&qpGuestEndpoints.lock);
if (queuePairEntry) {
/* The queues will be freed inside the destroy routine. */
QPGuestEndpointDestroy(queuePairEntry);
errorKeepEntry:
/* This path should only be used when an existing entry was found. */
ASSERT(queuePairEntry->qp.refCount > 0);
- VMCIMutex_Release(&qpGuestEndpoints.lock);
+ VMCIQPLock_Release(&qpGuestEndpoints.lock);
return result;
}
/*
*-----------------------------------------------------------------------------
*
- * VMCIQueuePairDetachHyperCall --
+ * VMCIQueuePairDetachHypercall --
*
- * Helper to make a QueuePairDetach hypercall.
+ * Helper to make a QueuePairDetach hypercall when the driver is
+ * supporting a guest device.
*
* Results:
* Result of the hypercall.
*/
int
-VMCIQueuePairDetachHyperCall(VMCIHandle handle) // IN
+VMCIQueuePairDetachHypercall(VMCIHandle handle) // IN
{
VMCIQueuePairDetachMsg detachMsg;
/*
*-----------------------------------------------------------------------------
*
- * VMCIQueuePairDetachHelper --
+ * VMCIQueuePairDetachGuestWork --
*
- * Helper for VMCI QueuePair detach interface on Linux. Frees the physical
- * pages for the QueuePair.
+ * Helper for VMCI QueuePair detach interface. Frees the physical
+ * pages for the queue pair.
*
* Results:
* Success or failure.
*/
static int
-VMCIQueuePairDetachHelper(VMCIHandle handle) // IN
+VMCIQueuePairDetachGuestWork(VMCIHandle handle) // IN
{
int result;
QPGuestEndpoint *entry;
ASSERT(!VMCI_HANDLE_INVALID(handle));
- VMCIMutex_Acquire(&qpGuestEndpoints.lock);
+ VMCIQPLock_Acquire(&qpGuestEndpoints.lock);
entry = (QPGuestEndpoint *)QueuePairList_FindEntry(&qpGuestEndpoints, handle);
if (!entry) {
}
}
} else {
- result = VMCIQueuePairDetachHyperCall(handle);
+ result = VMCIQueuePairDetachHypercall(handle);
if (entry->hibernateFailure) {
if (result == VMCI_ERROR_NOT_FOUND) {
/*
* compiler.
*/
- VMCIMutex_Release(&qpGuestEndpoints.lock);
+ VMCIQPLock_Release(&qpGuestEndpoints.lock);
if (result >= VMCI_SUCCESS && refCount == 0) {
QPGuestEndpointDestroy(entry);
VMCI_ReleaseLock_BH(&hibernateFailedListLock, flags);
}
+
/*
*-----------------------------------------------------------------------------
*
if (toLocal) {
VMCIListItem *next;
- VMCIMutex_Acquire(&qpGuestEndpoints.lock);
+ VMCIQPLock_Acquire(&qpGuestEndpoints.lock);
VMCIList_Scan(next, &qpGuestEndpoints.head) {
QPGuestEndpoint *entry = (QPGuestEndpoint *)VMCIList_Entry(
* discard the content of the non-local queues.
*/
- result = VMCIQueuePairDetachHyperCall(entry->qp.handle);
+ result = VMCIQueuePairDetachHypercall(entry->qp.handle);
if (result < VMCI_SUCCESS) {
VMCI_WARNING((LGPFX"Hibernate failed to detach from handle "
"%x:%x\n",
}
Atomic_Write(&qpGuestEndpoints.hibernate, 1);
- VMCIMutex_Release(&qpGuestEndpoints.lock);
+ VMCIQPLock_Release(&qpGuestEndpoints.lock);
} else {
VMCILockFlags flags;
VMCIHandle handle;
Atomic_Write(&qpGuestEndpoints.hibernate, 0);
}
}
+
+#endif /* !VMKERNEL */