#if defined(__linux__) && !defined(VMKERNEL)
# include "driver-config.h"
-
-# define EXPORT_SYMTAB
-
-# include <linux/module.h>
# include "compat_kernel.h"
+# include "compat_module.h"
#endif // __linux__
#include "vmci_defs.h"
#include "vmci_kernel_if.h"
#include "vmci_infrastructure.h"
#include "vmciEvent.h"
-#ifdef VMX86_TOOLS
+#ifdef VMX86_TOOLS
# include "vmciInt.h"
# include "vmciGuestKernelAPI.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"
+# include "vmciHostKernelAPI.h"
#endif
-#include "circList.h"
-#ifdef VMKERNEL
-# include "vm_libc.h"
-#endif
+#include "circList.h" /* Must come after vmciVmkInt.h. */
#define EVENT_MAGIC 0xEABE0000
typedef struct VMCISubscription {
VMCIId id;
+ int refCount;
+ Bool runDelayed;
+ VMCIEvent destroyEvent;
VMCI_Event event;
VMCI_EventCB callback;
void *callbackData;
static VMCISubscription *VMCIEventFind(VMCIId subID);
-static int VMCIEventRegisterSubscription(VMCISubscription *sub, VMCI_Event event,
- VMCI_EventCB callback,
+static int VMCIEventDeliver(VMCIEventMsg *eventMsg);
+static int VMCIEventRegisterSubscription(VMCISubscription *sub,
+ VMCI_Event event,
+ uint32 flags,
+ VMCI_EventCB callback,
void *callbackData);
static VMCISubscription *VMCIEventUnregisterSubscription(VMCIId subID);
* isn't so, and regular locks are used instead.
*/
-#ifdef VMX86_TOOLS
-#define VMCIEventInitLock(_lock, _name) VMCI_InitLock(_lock, _name, VMCI_LOCK_RANK_MIDDLE_BH)
+#ifdef VMX86_TOOLS
+#define VMCIEventInitLock(_lock, _name) VMCI_InitLock(_lock, _name, VMCI_LOCK_RANK_HIGHER_BH)
#define VMCIEventGrabLock(_lock, _flags) VMCI_GrabLock_BH(_lock, _flags)
#define VMCIEventReleaseLock(_lock, _flags) VMCI_ReleaseLock_BH(_lock, _flags)
#else
-#define VMCIEventInitLock(_lock, _name) VMCI_InitLock(_lock, _name, VMCI_LOCK_RANK_HIGH)
+#define VMCIEventInitLock(_lock, _name) VMCI_InitLock(_lock, _name, VMCI_LOCK_RANK_HIGHER)
#define VMCIEventGrabLock(_lock, _flags) VMCI_GrabLock(_lock, _flags)
#define VMCIEventReleaseLock(_lock, _flags) VMCI_ReleaseLock(_lock, _flags)
#endif
static ListItem *subscriberArray[VMCI_EVENT_MAX] = {NULL};
static VMCILock subscriberLock;
+typedef struct VMCIDelayedEventInfo {
+ VMCISubscription *sub;
+ uint8 eventPayload[sizeof(VMCIEventData_Max)];
+} VMCIDelayedEventInfo;
+
/*
*----------------------------------------------------------------------
void
VMCIEvent_Exit(void)
{
- VMCILockFlags flags;
ListItem *iter, *iter2;
VMCI_Event e;
/* We free all memory at exit. */
- VMCIEventGrabLock(&subscriberLock, &flags);
for (e = 0; e < VMCI_EVENT_MAX; e++) {
LIST_SCAN_SAFE(iter, iter2, subscriberArray[e]) {
- VMCISubscription *cur =
- LIST_CONTAINER(iter, VMCISubscription, subscriberListItem);
+ VMCISubscription *cur;
+
+ /*
+ * We should never get here because all events should have been
+ * unregistered before we try to unload the driver module.
+ * Also, delayed callbacks could still be firing so this cleanup
+ * would not be safe.
+ * Still it is better to free the memory than not ... so we
+ * leave this code in just in case....
+ *
+ */
+ ASSERT(FALSE);
+
+ cur = LIST_CONTAINER(iter, VMCISubscription, subscriberListItem);
VMCI_FreeKernelMem(cur, sizeof *cur);
}
- subscriberArray[e] = NULL;
}
- VMCIEventReleaseLock(&subscriberLock, flags);
VMCI_CleanupLock(&subscriberLock);
}
}
#endif
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * VMCIEventGet --
+ *
+ * Gets a reference to the given VMCISubscription.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+static void
+VMCIEventGet(VMCISubscription *entry) // IN
+{
+ ASSERT(entry);
+
+ entry->refCount++;
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * VMCIEventRelease --
+ *
+ * Releases the given VMCISubscription.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Fires the destroy event if the reference count has gone to zero.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+static void
+VMCIEventRelease(VMCISubscription *entry) // IN
+{
+ ASSERT(entry);
+ ASSERT(entry->refCount > 0);
+
+ entry->refCount--;
+ if (entry->refCount == 0) {
+ VMCI_SignalEvent(&entry->destroyEvent);
+ }
+}
+
+
+ /*
+ *------------------------------------------------------------------------------
+ *
+ * EventReleaseCB --
+ *
+ * Callback to release the event entry reference. It is called by the
+ * VMCI_WaitOnEvent function before it blocks.
+ *
+ * Result:
+ * None.
+ *
+ * Side effects:
+ * None.
+ *
+ *------------------------------------------------------------------------------
+ */
+
+static int
+EventReleaseCB(void *clientData) // IN
+{
+ VMCILockFlags flags;
+ VMCISubscription *sub = (VMCISubscription *)clientData;
+
+ ASSERT(sub);
+
+ VMCIEventGrabLock(&subscriberLock, &flags);
+ VMCIEventRelease(sub);
+ VMCIEventReleaseLock(&subscriberLock, flags);
+
+ return 0;
+}
+
+
/*
*-----------------------------------------------------------------------------
*
* Entry if found, NULL if not.
*
* Side effects:
- * None.
+ * Increments the VMCISubscription refcount if an entry is found.
*
*-----------------------------------------------------------------------------
*/
for (e = 0; e < VMCI_EVENT_MAX; e++) {
LIST_SCAN(iter, subscriberArray[e]) {
- VMCISubscription *cur =
+ VMCISubscription *cur =
LIST_CONTAINER(iter, VMCISubscription, subscriberListItem);
- if (cur->id == subID) {
- return cur;
- }
+ if (cur->id == subID) {
+ VMCIEventGet(cur);
+ return cur;
+ }
}
}
return NULL;
/*
*----------------------------------------------------------------------
*
- * VMCIEvent_Dispatch --
+ * VMCIEventDelayedDispatchCB --
+ *
+ * Calls the specified callback in a delayed context.
*
- * Dispatcher for the VMCI_EVENT_RECEIVE datagrams. Calls all
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+VMCIEventDelayedDispatchCB(void *data) // IN
+{
+ VMCIDelayedEventInfo *eventInfo;
+ VMCISubscription *sub;
+ VMCI_EventData *ed;
+ VMCILockFlags flags;
+
+ eventInfo = (VMCIDelayedEventInfo *)data;
+
+ ASSERT(eventInfo);
+ ASSERT(eventInfo->sub);
+
+ sub = eventInfo->sub;
+ ed = (VMCI_EventData *)eventInfo->eventPayload;
+
+ sub->callback(sub->id, ed, sub->callbackData);
+
+ VMCIEventGrabLock(&subscriberLock, &flags);
+ VMCIEventRelease(sub);
+ VMCIEventReleaseLock(&subscriberLock, flags);
+
+ VMCI_FreeKernelMem(eventInfo, sizeof *eventInfo);
+}
+
+
+/*
+ *----------------------------------------------------------------------------
+ *
+ * VMCIEventDeliver --
+ *
+ * Actually delivers the events to the subscribers.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * The callback function for each subscriber is invoked.
+ *
+ *----------------------------------------------------------------------------
+ */
+
+static int
+VMCIEventDeliver(VMCIEventMsg *eventMsg) // IN
+{
+ int err = VMCI_SUCCESS;
+ ListItem *iter;
+ VMCILockFlags flags;
+
+ ASSERT(eventMsg);
+
+ VMCIEventGrabLock(&subscriberLock, &flags);
+ LIST_SCAN(iter, subscriberArray[eventMsg->eventData.event]) {
+ VMCI_EventData *ed;
+ VMCISubscription *cur = LIST_CONTAINER(iter, VMCISubscription,
+ subscriberListItem);
+ ASSERT(cur && cur->event == eventMsg->eventData.event);
+
+ if (cur->runDelayed) {
+ VMCIDelayedEventInfo *eventInfo;
+ if ((eventInfo = VMCI_AllocKernelMem(sizeof *eventInfo,
+ VMCI_MEMORY_ATOMIC)) == NULL) {
+ err = VMCI_ERROR_NO_MEM;
+ goto out;
+ }
+
+ VMCIEventGet(cur);
+
+ memset(eventInfo, 0, sizeof *eventInfo);
+ memcpy(eventInfo->eventPayload, VMCI_DG_PAYLOAD(eventMsg),
+ (size_t)eventMsg->hdr.payloadSize);
+ eventInfo->sub = cur;
+ err = VMCI_ScheduleDelayedWork(VMCIEventDelayedDispatchCB,
+ eventInfo);
+ if (err != VMCI_SUCCESS) {
+ VMCIEventRelease(cur);
+ VMCI_FreeKernelMem(eventInfo, sizeof *eventInfo);
+ goto out;
+ }
+
+ } else {
+ uint8 eventPayload[sizeof(VMCIEventData_Max)];
+
+ /* We set event data before each callback to ensure isolation. */
+ memset(eventPayload, 0, sizeof eventPayload);
+ memcpy(eventPayload, VMCI_DG_PAYLOAD(eventMsg),
+ (size_t)eventMsg->hdr.payloadSize);
+ ed = (VMCI_EventData *)eventPayload;
+ cur->callback(cur->id, ed, cur->callbackData);
+ }
+ }
+
+out:
+ VMCIEventReleaseLock(&subscriberLock, flags);
+
+ return err;
+}
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * VMCIEvent_Dispatch --
+ *
+ * Dispatcher for the VMCI_EVENT_RECEIVE datagrams. Calls all
* subscribers for given event.
*
* Results:
int
VMCIEvent_Dispatch(VMCIDatagram *msg) // IN
{
- ListItem *iter;
- VMCILockFlags flags;
VMCIEventMsg *eventMsg = (VMCIEventMsg *)msg;
- ASSERT(msg &&
+ ASSERT(msg &&
msg->src.context == VMCI_HYPERVISOR_CONTEXT_ID &&
msg->dst.resource == VMCI_EVENT_HANDLER);
return VMCI_ERROR_EVENT_UNKNOWN;
}
- VMCIEventGrabLock(&subscriberLock, &flags);
- LIST_SCAN(iter, subscriberArray[eventMsg->eventData.event]) {
- uint8 eventPayload[sizeof(VMCIEventData_Max)];
- VMCI_EventData *ed;
- VMCISubscription *cur = LIST_CONTAINER(iter, VMCISubscription,
- subscriberListItem);
- ASSERT(cur && cur->event == eventMsg->eventData.event);
-
- /* We set event data before each callback to ensure isolation. */
- memset(eventPayload, 0, sizeof eventPayload);
- memcpy(eventPayload, VMCI_DG_PAYLOAD(eventMsg),
- (size_t)eventMsg->hdr.payloadSize);
- ed = (VMCI_EventData *)eventPayload;
- cur->callback(cur->id, ed, cur->callbackData);
- }
- VMCIEventReleaseLock(&subscriberLock, flags);
+ VMCIEventDeliver(eventMsg);
return VMCI_SUCCESS;
}
static int
VMCIEventRegisterSubscription(VMCISubscription *sub, // IN
VMCI_Event event, // IN
+ uint32 flags, // IN
VMCI_EventCB callback, // IN
void *callbackData) // IN
{
# define VMCI_EVENT_MAX_ATTEMPTS 10
static VMCIId subscriptionID = 0;
- VMCILockFlags flags;
+ VMCILockFlags lockFlags;
uint32 attempts = 0;
int result;
Bool success;
ASSERT(sub);
-
+
if (event >= VMCI_EVENT_MAX || callback == NULL) {
- VMCI_LOG(("VMCIEvent: Failed to subscribe to event %d cb %p data %p.\n",
- event, callback, callbackData));
+ VMCILOG(("VMCIEvent: Failed to subscribe to event %d cb %p data %p.\n",
+ event, callback, callbackData));
return VMCI_ERROR_INVALID_ARGS;
}
-
+
+ if (vmkernel) {
+ /*
+ * In the vmkernel we defer delivery of events to a helper world. This
+ * makes the event delivery more consistent across hosts and guests with
+ * regard to which locks are held.
+ */
+ sub->runDelayed = TRUE;
+ } else if (!VMCI_CanScheduleDelayedWork()) {
+ /*
+ * If the platform doesn't support delayed work callbacks then don't
+ * allow registration for them.
+ */
+ if (flags & VMCI_FLAG_EVENT_DELAYED_CB) {
+ return VMCI_ERROR_INVALID_ARGS;
+ }
+ sub->runDelayed = FALSE;
+ } else {
+ /*
+ * The platform supports delayed work callbacks. Honor the requested
+ * flags
+ */
+ sub->runDelayed = (flags & VMCI_FLAG_EVENT_DELAYED_CB) ? TRUE : FALSE;
+ }
+
+ sub->refCount = 1;
sub->event = event;
sub->callback = callback;
sub->callbackData = callbackData;
-
- VMCIEventGrabLock(&subscriberLock, &flags);
+
+ VMCIEventGrabLock(&subscriberLock, &lockFlags);
for (success = FALSE, attempts = 0;
- success == FALSE && attempts < VMCI_EVENT_MAX_ATTEMPTS;
- attempts++) {
+ success == FALSE && attempts < VMCI_EVENT_MAX_ATTEMPTS;
+ attempts++) {
+ VMCISubscription *existingSub = NULL;
- /*
+ /*
* We try to get an id a couple of time before claiming we are out of
* resources.
*/
sub->id = ++subscriptionID;
/* Test for duplicate id. */
- if (VMCIEventFind(sub->id) == NULL) {
- /* We succeeded if we didn't find a duplicate. */
- success = TRUE;
+ existingSub = VMCIEventFind(sub->id);
+ if (existingSub == NULL) {
+ /* We succeeded if we didn't find a duplicate. */
+ success = TRUE;
+ } else {
+ VMCIEventRelease(existingSub);
}
}
if (success) {
+ VMCI_CreateEvent(&sub->destroyEvent);
LIST_QUEUE(&sub->subscriberListItem, &subscriberArray[event]);
result = VMCI_SUCCESS;
} else {
result = VMCI_ERROR_NO_RESOURCES;
}
- VMCIEventReleaseLock(&subscriberLock, flags);
+ VMCIEventReleaseLock(&subscriberLock, lockFlags);
return result;
# undef VMCI_EVENT_MAX_ATTEMPTS
{
VMCILockFlags flags;
VMCISubscription *s;
-
+
VMCIEventGrabLock(&subscriberLock, &flags);
s = VMCIEventFind(subID);
if (s != NULL) {
+ VMCIEventRelease(s);
LIST_DEL(&s->subscriberListItem, &subscriberArray[s->event]);
}
VMCIEventReleaseLock(&subscriberLock, flags);
-
+
+ if (s != NULL) {
+ VMCI_WaitOnEvent(&s->destroyEvent, EventReleaseCB, s);
+ VMCI_DestroyEvent(&s->destroyEvent);
+ }
+
return s;
}
*
* VMCIEventSubscribe --
*
- * Subscribe to given event.
+ * Subscribe to given event. The callback specified can be fired
+ * in different contexts depending on what flag is specified while
+ * registering. If flags contains VMCI_FLAG_EVENT_NONE then the
+ * callback is fired with the subscriber lock held (and BH context
+ * on the guest). If flags contain VMCI_FLAG_EVENT_DELAYED_CB then
+ * the callback is fired with no locks held in thread context.
+ * This is useful because other VMCIEvent functions can be called,
+ * but it also increases the chances that an event will be dropped.
*
* Results:
* VMCI_SUCCESS on success, error code otherwise.
int
VMCIEventSubscribe(VMCI_Event event, // IN
+ uint32 flags, // IN
VMCI_EventCB callback, // IN
void *callbackData, // IN
VMCIId *subscriptionID) // OUT
VMCISubscription *s = NULL;
if (subscriptionID == NULL) {
- VMCI_LOG(("VMCIEvent: Invalid arguments.\n"));
+ VMCILOG(("VMCIEvent: Invalid arguments.\n"));
return VMCI_ERROR_INVALID_ARGS;
}
return VMCI_ERROR_NO_MEM;
}
- retval = VMCIEventRegisterSubscription(s, event, callback, callbackData);
+ retval = VMCIEventRegisterSubscription(s, event, flags,
+ callback, callbackData);
if (retval < VMCI_SUCCESS) {
VMCI_FreeKernelMem(s, sizeof *s);
return retval;
}
+#ifndef VMKERNEL
/*
*----------------------------------------------------------------------
*
*----------------------------------------------------------------------
*/
-#if defined(__linux__) && !defined(VMKERNEL)
+#if defined(__linux__)
EXPORT_SYMBOL(VMCIEvent_Subscribe);
#endif
int
VMCIEvent_Subscribe(VMCI_Event event, // IN
+ uint32 flags, // IN
VMCI_EventCB callback, // IN
void *callbackData, // IN
VMCIId *subscriptionID) // OUT
{
- return VMCIEventSubscribe(event, callback, callbackData, subscriptionID);
+ return VMCIEventSubscribe(event, flags, callback, callbackData,
+ subscriptionID);
}
+#endif /* !VMKERNEL */
/*
*
* VMCIEventUnsubscribe --
*
- * Unsubscribe to given event. Removes it from list and frees it.
+ * Unsubscribe to given event. Removes it from list and frees it.
* Will return callbackData if requested by caller.
*
* Results:
}
+#ifndef VMKERNEL
/*
*----------------------------------------------------------------------
*
*----------------------------------------------------------------------
*/
-#if defined(__linux__) && !defined(VMKERNEL)
+#if defined(__linux__)
EXPORT_SYMBOL(VMCIEvent_Unsubscribe);
#endif
{
return VMCIEventUnsubscribe(subID);
}
+
+#endif /* !VMKERNEL */
#endif
static VMCIHandle vmciStreamHandle = { VMCI_INVALID_ID, VMCI_INVALID_ID };
static VMCIId qpResumedSubId = VMCI_INVALID_ID;
+static VMCIId ctxChangedSubId = VMCI_INVALID_ID;
static int PROTOCOL_OVERRIDE = -1;
}
+/*
+ *----------------------------------------------------------------------------
+ *
+ * VSockVmciCidChangedCB --
+ *
+ * Invoked when the context id of the VM may have changed. In this case
+ * we need to reregister the stream control channel handler.
+ *
+ * XXX: Open stream sockets will be closed by the detached callback for the
+ * QP. However, this doesn't fix up bound stream sockets. We should figure
+ * out what the right thing to do is in that case.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------------
+ */
+
+static void
+VSockVmciCidChangedCB(VMCIId subId, // IN
+ VMCI_EventData *eData, // IN
+ void *clientData) // IN
+{
+ int err;
+ down(®istrationMutex);
+
+ if (!VMCI_HANDLE_INVALID(vmciStreamHandle)) {
+ VMCIDatagram_DestroyHnd(vmciStreamHandle);
+ vmciStreamHandle = VMCI_INVALID_HANDLE;
+ }
+
+ err = VSockVmciDatagramCreateHnd(VSOCK_PACKET_RID, 0,
+ VSockVmciRecvStreamCB, NULL,
+ &vmciStreamHandle,
+ TRUE);
+ if (err < 0) {
+ Warning("Unable to create datagram handle. (%d)\n", err);
+ }
+
+ up(®istrationMutex);
+}
+
+
/*
*----------------------------------------------------------------------------
*
* specifying the ATTACH_ONLY flag below.
*/
err = VMCIEvent_Subscribe(VMCI_EVENT_QP_PEER_DETACH,
+ VMCI_FLAG_EVENT_NONE,
VSockVmciPeerDetachCB,
pending,
&detachSubId);
* once and add a way to lookup sockets by queue pair handle.
*/
err = VMCIEvent_Subscribe(VMCI_EVENT_QP_PEER_ATTACH,
+ VMCI_FLAG_EVENT_NONE,
VSockVmciPeerAttachCB,
sk,
&attachSubId);
}
err = VMCIEvent_Subscribe(VMCI_EVENT_QP_PEER_DETACH,
+ VMCI_FLAG_EVENT_NONE,
VSockVmciPeerDetachCB,
sk,
&detachSubId);
}
#endif
+ /*
+ * Register the context id changed callback before creating our datagram
+ * handler to make sure we don't miss a context id change.
+ */
+ err = VMCIEvent_Subscribe(VMCI_EVENT_CTX_ID_UPDATE,
+ VMCI_FLAG_EVENT_DELAYED_CB,
+ VSockVmciCidChangedCB,
+ NULL,
+ &ctxChangedSubId);
+ if (err < VMCI_SUCCESS) {
+ Warning("Unable to subscribe to Ctx Id update event. (%d)\n", err);
+ err = VSockVmci_ErrorToVSockError(err);
+ ctxChangedSubId = VMCI_INVALID_ID;
+ return VSockVmci_ErrorToVSockError(err);
+ }
+
/*
* Create the datagram handle that we will use to send and receive all
* VSocket control messages for this context.
vmciStreamHandle.context == VMCI_INVALID_ID ||
vmciStreamHandle.resource == VMCI_INVALID_ID) {
Warning("Unable to create datagram handle. (%d)\n", err);
- return VSockVmci_ErrorToVSockError(err);
+ goto error;
}
err = VMCIEvent_Subscribe(VMCI_EVENT_QP_RESUMED,
+ VMCI_FLAG_EVENT_NONE,
VSockVmciQPResumedCB,
NULL,
&qpResumedSubId);
return vsockVmciFamilyOps.family;
error:
+ if (ctxChangedSubId != VMCI_INVALID_ID) {
+ VMCIEvent_Unsubscribe(ctxChangedSubId);
+ ctxChangedSubId = VMCI_INVALID_ID;
+ }
if (qpResumedSubId != VMCI_INVALID_ID) {
VMCIEvent_Unsubscribe(qpResumedSubId);
qpResumedSubId = VMCI_INVALID_ID;
}
- VMCIDatagram_DestroyHnd(vmciStreamHandle);
+
+ if (!VMCI_HANDLE_INVALID(vmciStreamHandle)) {
+ VMCIDatagram_DestroyHnd(vmciStreamHandle);
+ }
return err;
}
}
#endif
+ if (ctxChangedSubId != VMCI_INVALID_ID) {
+ VMCIEvent_Unsubscribe(ctxChangedSubId);
+ ctxChangedSubId = VMCI_INVALID_ID;
+ }
+
if (!VMCI_HANDLE_INVALID(vmciStreamHandle)) {
if (VMCIDatagram_DestroyHnd(vmciStreamHandle) != VMCI_SUCCESS) {
Warning("Could not destroy VMCI datagram handle.\n");