int VMCI_PopulatePPNList(uint8 *callBuf, const PPNSet *ppnSet);
#endif
-#if !defined(VMX86_TOOLS)
struct VMCIQueue;
+#if !defined(VMX86_TOOLS)
#if !defined(VMKERNEL)
struct PageStoreAttachInfo;
struct VMCIQueue *VMCIHost_AllocQueue(uint64 queueSize);
struct VMCIQueue *produceQ,
struct VMCIQueue *detachQ);
#endif // VMKERNEL
-#ifdef _WIN32
-void VMCIHost_InitQueueMutex(struct VMCIQueue *produceQ,
- struct VMCIQueue *consumeQ);
-void VMCIHost_AcquireQueueMutex(struct VMCIQueue *queue);
-void VMCIHost_ReleaseQueueMutex(struct VMCIQueue *queue);
-Bool VMCIHost_EnqueueToDevNull(struct VMCIQueue *queue);
+#ifdef _WIN32
/*
* Special routine used on the Windows platform to save a queue when
* its backing memory goes away.
struct VMCIQueue *produceQ,
struct VMCIQueue *detachQ,
const uint64 produceQSize);
-#else // _WIN32
-# define VMCIHost_InitQueueMutex(_pq, _cq)
-# define VMCIHost_AcquireQueueMutex(_q)
-# define VMCIHost_ReleaseQueueMutex(_q)
-# define VMCIHost_EnqueueToDevNull(_q) FALSE
#endif // _WIN32
+
#endif // !VMX86_TOOLS
+#ifdef _WIN32
+void VMCI_InitQueueMutex(struct VMCIQueue *produceQ,
+ struct VMCIQueue *consumeQ);
+void VMCI_AcquireQueueMutex(struct VMCIQueue *queue);
+void VMCI_ReleaseQueueMutex(struct VMCIQueue *queue);
+Bool VMCI_EnqueueToDevNull(struct VMCIQueue *queue);
+int VMCI_ConvertToLocalQueue(struct VMCIQueue *queueInfo,
+ struct VMCIQueue *otherQueueInfo,
+ uint64 size, Bool keepContent,
+ void **oldQueue);
+void VMCI_RevertToNonLocalQueue(struct VMCIQueue *queueInfo,
+ void *nonLocalQueue, uint64 size);
+void VMCI_FreeQueueBuffer(void *queue, uint64 size);
+#else // _WIN32
+# define VMCI_InitQueueMutex(_pq, _cq)
+# define VMCI_AcquireQueueMutex(_q)
+# define VMCI_ReleaseQueueMutex(_q)
+# define VMCI_EnqueueToDevNull(_q) FALSE
+# define VMCI_ConvertToLocalQueue(_pq, _cq, _s, _oq, _kc) VMCI_ERROR_UNAVAILABLE
+# define VMCI_RevertToNonLocalQueue(_q, _nlq, _s)
+# define VMCI_FreeQueueBuffer(_q, _s)
+#endif // !_WIN32
#endif // _VMCI_KERNEL_IF_H_
}
+/*
+ *----------------------------------------------------------------------
+ *
+ * VMCINotifications_Hibernate --
+ *
+ * When a guest leaves hibernation, the device driver state is out
+ * of sync with the device state, since the driver state has
+ * doorbells registered that aren't known to the device. This
+ * function takes care of reregistering any doorbells. In case an
+ * error occurs during reregistration (this is highly unlikely
+ * since 1) it succeeded the first time 2) the device driver is the
+ * only source of doorbell registrations), we simply log the
+ * error. The doorbell can still be destroyed using
+ * VMCIDoorbell_Destroy.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+void
+VMCINotifications_Hibernate(Bool enterHibernate) // IN
+{
+ VMCILockFlags flags;
+ uint32 bucket;
+ ListItem *iter;
+
+ if (enterHibernate) {
+ /*
+ * Nothing to do when entering hibernation.
+ */
+
+ return;
+ }
+
+ VMCI_GrabLock_BH(&vmciNotifyHT.lock, &flags);
+
+ for (bucket = 0; bucket < HASH_TABLE_SIZE; bucket++) {
+ LIST_SCAN(iter, vmciNotifyHT.entriesByIdx[bucket]) {
+ VMCINotifyHashEntry *cur;
+ int result;
+
+ cur = LIST_CONTAINER(iter, VMCINotifyHashEntry, idxListItem);
+ result = LinkNotificationHypercall(cur->handle, cur->doorbell, cur->idx);
+ if (result != VMCI_SUCCESS && result != VMCI_ERROR_DUPLICATE_ENTRY) {
+ VMCI_LOG(("Failed to reregister doorbell handle 0x%x:0x%x of "
+ "resource %s to index (error: %d).\n",
+ cur->handle.context, cur->handle.resource,
+ cur->doorbell ? "doorbell" : "queue pair", result));
+ }
+ }
+ }
+
+ VMCI_ReleaseLock_BH(&vmciNotifyHT.lock, flags);
+}
+
+
/*
*-------------------------------------------------------------------------
*
result = LinkNotificationHypercall(entry->handle, doorbell, entry->idx);
if (result != VMCI_SUCCESS) {
- VMCI_LOG(("Failed to link handle 0x%x:0x%x of resource %s to index, "
- "err 0x%x.\n", entry->handle.context, entry->handle.resource,
+ VMCI_LOG(("Failed to link handle 0x%x:0x%x of resource %s to index "
+ "(error: %d).\n", entry->handle.context, entry->handle.resource,
entry->doorbell ? "doorbell" : "queue pair", result));
VMCINotifyHashRemoveEntry(entry->handle, entry->doorbell);
VMCI_DestroyEvent(&entry->destroyEvent);
* The only reason this should fail would be an inconsistency
* between guest and hypervisor state, where the guest believes
* it has an active registration whereas the hypervisor
- * doesn't. Since the handle has now been removed in the guest,
- * we just print a warning and return success.
+ * doesn't. One case where this may happen is if a doorbell is
+ * unregistered following a hibernation at a time where the
+ * doorbell state hasn't been restored on the hypervisor side
+ * yet. Since the handle has now been removed in the guest, we
+ * just print a warning and return success.
*/
- ASSERT(FALSE);
-
- VMCI_LOG(("Unlink of %s handle 0x%x:0x%x unknown by hypervisor.\n",
- doorbell ? "doorbell" : "queuepair",
- handle.context, handle.resource));
+ VMCI_LOG(("Unlink of %s handle 0x%x:0x%x unknown by hypervisor "
+ "(error: %d).\n", doorbell ? "doorbell" : "queuepair",
+ handle.context, handle.resource, result));
}
return VMCI_SUCCESS;
}
result = VMCI_SendDatagram((VMCIDatagram *)&bitmapSetMsg);
if (result != VMCI_SUCCESS) {
VMCI_LOG(("VMCINotifications: Failed to register PPN %u as notification "
- "bitmap (error : %d).\n", bitmapPPN, result));
+ "bitmap (error: %d).\n", bitmapPPN, result));
return FALSE;
}
return TRUE;
typedef struct QueuePairList {
ListItem *head;
+ Bool hibernate;
VMCIMutex mutex;
} QueuePairList;
uint64 consumeSize,
VMCIId peer, uint32 flags);
static int VMCIQueuePairDetachHelper(VMCIHandle handle);
+static int VMCIQueuePairDetachHyperCall(VMCIHandle handle);
static int QueuePairNotifyPeerLocal(Bool attach, VMCIHandle handle);
VMCIQueuePair_Init(void)
{
queuePairList.head = NULL;
+ queuePairList.hibernate = FALSE;
QueuePairLock_Init();
}
QueuePairEntryDestroy(entry);
}
+ queuePairList.hibernate = FALSE;
QueuePairList_Unlock();
QueuePairLock_Destroy();
}
QueuePairList_Lock();
+ if (queuePairList.hibernate && !(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.
+ */
+
+ return VMCI_ERROR_UNAVAILABLE;
+ }
+
if ((queuePairEntry = QueuePairList_FindEntry(*handle))) {
if (queuePairEntry->flags & VMCI_QPFLAG_LOCAL) {
/* Local attach case. */
}
}
+ VMCI_InitQueueMutex((VMCIQueue *)myProduceQ, (VMCIQueue *)myConsumeQ);
+
QueuePairList_AddEntry(queuePairEntry);
out:
}
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * VMCIQueuePairDetachHyperCall --
+ *
+ * Helper to make a QueuePairDetach hypercall.
+ *
+ * Results:
+ * Result of the hypercall.
+ *
+ * Side effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+int
+VMCIQueuePairDetachHyperCall(VMCIHandle handle) // IN:
+{
+ 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 handle;
+ detachMsg.handle = handle;
+
+ return VMCI_SendDatagram((VMCIDatagram *)&detachMsg);
+}
+
+
/*
*-----------------------------------------------------------------------------
*
}
}
} else {
- 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 handle;
- detachMsg.handle = handle;
-
- result = VMCI_SendDatagram((VMCIDatagram *)&detachMsg);
+ result = VMCIQueuePairDetachHyperCall(handle);
}
out:
return VMCIEvent_Dispatch((VMCIDatagram *)eMsg);
}
+
+
+/*
+ *----------------------------------------------------------------------------
+ *
+ * VMCIQueuePair_Hibernate --
+ *
+ * When the guest enters hibernation, any non-local queue pairs
+ * will disconnect no later than at the time the VMCI device
+ * powers off. To preserve the content of the non-local queue
+ * pairs for this guest, we make a local copy of the content and
+ * disconnect from the queue pairs. This will ensure that the
+ * peer doesn't continue to update the queue pair state while the
+ * guest OS is checkpointing the memory (otherwise we might end
+ * up with a inconsistent snapshot where the pointers of the
+ * consume queue are checkpointed later than the data pages they
+ * point to, possibly indicating that non-valid data is
+ * valid). While we are in hibernation mode, we block the
+ * allocation of new non-local queue pairs. Note that while we
+ * are doing the conversion to local queue pairs, we are holding
+ * the queue pair list lock, which will prevent concurrent
+ * creation of additional non-local queue pairs.
+ *
+ * The hibernation cannot fail, so if we are unable to either
+ * save the queue pair state or detach from a queue pair, we deal
+ * with it by keeping the queue pair around, and converting it to
+ * a local queue pair when going out of hibernation. Since
+ * failing a detach is highly unlikely (it would require a queue
+ * pair being actively used as part of a DMA operation), this is
+ * an acceptable fall back. Once we come back from hibernation,
+ * these queue pairs will no longer be external, so we simply
+ * mark them as local at that point.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Queue pairs are detached.
+ *
+ *----------------------------------------------------------------------------
+ */
+
+void
+VMCIQueuePair_Hibernate(Bool enterHibernation)
+{
+ ListItem *next;
+
+ QueuePairList_Lock();
+
+ if (enterHibernation) {
+
+ LIST_SCAN(next, queuePairList.head) {
+ QueuePairEntry *entry = LIST_CONTAINER(next, QueuePairEntry, listItem);
+
+ if (!(entry->flags & VMCI_QPFLAG_LOCAL)) {
+ VMCIQueue *prodQ;
+ VMCIQueue *consQ;
+ void *oldProdQ;
+ void *oldConsQ;
+ int result;
+
+ prodQ = (VMCIQueue *)entry->produceQ;
+ consQ = (VMCIQueue *)entry->consumeQ;
+ oldConsQ = oldProdQ = NULL;
+
+ VMCI_AcquireQueueMutex(prodQ);
+
+ result = VMCI_ConvertToLocalQueue(consQ, prodQ, entry->consumeSize,
+ TRUE, &oldConsQ);
+ if (result != VMCI_SUCCESS) {
+ VMCI_LOG((LGPFX "Hibernate failed to create local consume queue "
+ "from handle %x:%x (error: %d)\n",
+ entry->handle.context, entry->handle.resource, result));
+ VMCI_ReleaseQueueMutex(prodQ);
+ continue;
+ }
+ result = VMCI_ConvertToLocalQueue(prodQ, consQ, entry->produceSize,
+ FALSE, &oldProdQ);
+ if (result != VMCI_SUCCESS) {
+ VMCI_LOG((LGPFX "Hibernate failed to create local produce queue "
+ "from handle %x:%x (error: %d)\n",
+ entry->handle.context, entry->handle.resource, result));
+ VMCI_RevertToNonLocalQueue(consQ, oldConsQ, entry->consumeSize);
+ VMCI_ReleaseQueueMutex(prodQ);
+ continue;
+ }
+
+ /*
+ * Now that the contents of the queue pair has been saved,
+ * we can detach from the non-local queue pair. This will
+ * revert the content of the non-local queues to that
+ * before the queue pair allocation and any buffered data
+ * will be lost.
+ */
+
+ result = VMCIQueuePairDetachHyperCall(entry->handle);
+ if (result < VMCI_SUCCESS) {
+ VMCI_LOG((LGPFX "Hibernate failed to detach from handle %x:%x\n",
+ entry->handle.context, entry->handle.resource));
+ VMCI_RevertToNonLocalQueue(consQ, oldConsQ, entry->consumeSize);
+ VMCI_RevertToNonLocalQueue(prodQ, oldProdQ, entry->produceSize);
+ VMCI_ReleaseQueueMutex(prodQ);
+ continue;
+ }
+
+ entry->flags |= VMCI_QPFLAG_LOCAL;
+
+ VMCI_ReleaseQueueMutex(prodQ);
+
+ VMCI_FreeQueueBuffer(oldProdQ, entry->produceSize);
+ VMCI_FreeQueueBuffer(oldConsQ, entry->consumeSize);
+
+ QueuePairNotifyPeerLocal(FALSE, entry->handle);
+ }
+ }
+ queuePairList.hibernate = TRUE;
+ } else {
+ queuePairList.hibernate = FALSE;
+
+ LIST_SCAN(next, queuePairList.head) {
+ QueuePairEntry *entry = LIST_CONTAINER(next, QueuePairEntry, listItem);
+
+ if (!(entry->flags & VMCI_QPFLAG_LOCAL)) {
+ /*
+ * Any queue pairs, that couldn't be deallocated when
+ * entering hibernation are now assumed to be local, since
+ * the VMCI device has been reset.
+ */
+
+ entry->flags |= VMCI_QPFLAG_LOCAL;
+ QueuePairNotifyPeerLocal(FALSE, entry->handle);
+ }
+ }
+ }
+
+ QueuePairList_Unlock();
+}