]> git.ipfire.org Git - thirdparty/open-vm-tools.git/commitdiff
Implement Windows hibernation support
authorVMware, Inc <>
Mon, 20 Sep 2010 18:21:07 +0000 (11:21 -0700)
committerMarcelo Vanzin <mvanzin@vmware.com>
Mon, 20 Sep 2010 18:21:07 +0000 (11:21 -0700)
This change improves the VMCI Windows guest drivers handling
of hibernation. Before this change, any device state that is
partly or fully mirrored in the device driver as well, would
become out of sync when coming out of hibernation, e.g., queue
pairs would no longer be allocated on the hypervisor side but
the guest driver would still believe that they were active.

This change deals with such state in two ways: 1) For queue
pairs, all non-local queue pairs are converted to local ones
when going into hibernation. This part utilizes the exisiting
support on the Windows host side for converting queue pairs to
local memory (on the Windows host side it is used when the VMX
goes away). 2) For doorbells, we simply reregister any
doorbells when leaving hibernation.

Signed-off-by: Marcelo Vanzin <mvanzin@vmware.com>
open-vm-tools/modules/linux/shared/vmci_kernel_if.h
open-vm-tools/modules/linux/vmci/vmciNotifications.c
open-vm-tools/modules/linux/vmci/vmciNotifications.h
open-vm-tools/modules/linux/vmci/vmciQPair.c
open-vm-tools/modules/linux/vmci/vmciQueuePair.c
open-vm-tools/modules/linux/vmci/vmciQueuePairInt.h

index 81534fe907f460ab77f6bb437afbc892a8975645..a06e208b317b75a6e1ec276d72413b751f675cb7 100644 (file)
@@ -314,9 +314,9 @@ void VMCI_FreePPNSet(PPNSet *ppnSet);
 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);
@@ -329,13 +329,8 @@ void VMCIHost_ReleaseUserMemory(struct PageStoreAttachInfo *attach,
                                 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.
@@ -345,13 +340,31 @@ void VMCIHost_SaveProduceQ(struct PageStoreAttachInfo *attach,
                            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_
index fdbbd56ccacc00bd378cb799acbf503160986b0f..12df706e48c3d6099ac44cb269bd5918550dbb2a 100644 (file)
@@ -208,6 +208,67 @@ VMCINotifications_Exit(void)
 }
 
 
+/*
+ *----------------------------------------------------------------------
+ *
+ * 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);
+}
+
+
 /*
  *-------------------------------------------------------------------------
  *
@@ -783,8 +844,8 @@ VMCINotificationRegister(VMCIHandle *handle,     // IN
 
    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);
@@ -844,15 +905,16 @@ VMCINotificationUnregister(VMCIHandle handle, // IN
        * 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;
 }
@@ -891,7 +953,7 @@ VMCI_RegisterNotificationBitmap(PPN bitmapPPN) // IN
    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;
index 940ec5564c9dd2ad408ba47ebf4e914566ec4966..09c3da57d83333393b4efb7dbc7bc5da744c3c57 100644 (file)
@@ -35,5 +35,7 @@ int VMCINotificationRegister(VMCIHandle *handle, Bool doorbell, uint32 flags,
                              VMCICallback notifyCB, void *callbackData);
 int VMCINotificationUnregister(VMCIHandle handle, Bool doorbell);
 
+void VMCINotifications_Hibernate(Bool enterHibernation);
+
 #endif /* !_VMCI_NOTIFICATIONS_H_ */
 
index e4ba9e4deee89429db6863d40b08ca76225658a8..6da36c92bb0c24f60b6e797045b775b5aa1641a0 100644 (file)
@@ -253,8 +253,8 @@ VMCIQPair_Detach(VMCIQPair **qpair) // IN/OUT
 static INLINE void
 VMCIQPairLock(const VMCIQPair *qpair) // IN
 {
-#if !defined VMX86_TOOLS && !defined VMX86_VMX
-   VMCIHost_AcquireQueueMutex(qpair->produceQ);
+#if !defined VMX86_VMX
+   VMCI_AcquireQueueMutex(qpair->produceQ);
 #endif
 }
 
@@ -278,8 +278,8 @@ VMCIQPairLock(const VMCIQPair *qpair) // IN
 static INLINE void
 VMCIQPairUnlock(const VMCIQPair *qpair) // IN
 {
-#if !defined VMX86_TOOLS && !defined VMX86_VMX
-   VMCIHost_ReleaseQueueMutex(qpair->produceQ);
+#if !defined VMX86_VMX
+   VMCI_ReleaseQueueMutex(qpair->produceQ);
 #endif
 }
 
@@ -617,8 +617,8 @@ EnqueueLocked(VMCIQueue *produceQ,                   // IN
    size_t written;
    ssize_t result;
 
-#if !defined VMX86_TOOLS && !defined VMX86_VMX
-   if (UNLIKELY(VMCIHost_EnqueueToDevNull(produceQ))) {
+#if !defined VMX86_VMX
+   if (UNLIKELY(VMCI_EnqueueToDevNull(produceQ))) {
       return (ssize_t) bufSize;
    }
 
index 927255ff06105cf87fc3aa1801e7034464905186..c97e286b74d9574ceba28bef4d62739e25960b91 100644 (file)
@@ -58,6 +58,7 @@ typedef struct QueuePairEntry {
 
 typedef struct QueuePairList {
    ListItem  *head;
+   Bool      hibernate;
    VMCIMutex mutex;
 } QueuePairList;
 
@@ -79,6 +80,7 @@ static int VMCIQueuePairAllocHelper(VMCIHandle *handle, VMCIQueue **produceQ,
                                     uint64 consumeSize,
                                     VMCIId peer, uint32 flags);
 static int VMCIQueuePairDetachHelper(VMCIHandle handle);
+static int VMCIQueuePairDetachHyperCall(VMCIHandle handle);
 static int QueuePairNotifyPeerLocal(Bool attach, VMCIHandle handle);
 
 
@@ -194,6 +196,7 @@ void
 VMCIQueuePair_Init(void)
 {
    queuePairList.head = NULL;
+   queuePairList.hibernate = FALSE;
    QueuePairLock_Init();
 }
 
@@ -244,6 +247,7 @@ VMCIQueuePair_Exit(void)
       QueuePairEntryDestroy(entry);
    }
 
+   queuePairList.hibernate = FALSE;
    QueuePairList_Unlock();
    QueuePairLock_Destroy();
 }
@@ -683,6 +687,17 @@ VMCIQueuePairAllocHelper(VMCIHandle *handle,   // IN/OUT:
 
    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. */
@@ -781,6 +796,8 @@ VMCIQueuePairAllocHelper(VMCIHandle *handle,   // IN/OUT:
       }
    }
 
+   VMCI_InitQueueMutex((VMCIQueue *)myProduceQ, (VMCIQueue *)myConsumeQ);
+
    QueuePairList_AddEntry(queuePairEntry);
 
 out:
@@ -827,6 +844,37 @@ errorKeepEntry:
 }
 
 
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * 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);
+}
+
+
 /*
  *-----------------------------------------------------------------------------
  *
@@ -873,15 +921,7 @@ VMCIQueuePairDetachHelper(VMCIHandle handle)   // IN:
          }
       }
    } 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:
@@ -952,3 +992,140 @@ QueuePairNotifyPeerLocal(Bool attach,           // IN: attach or detach?
 
    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();
+}
index 93b0b95e74ed7824840a23c0172f9c7a9cf351cd..29849007a6bceade6bedf5f30ba2fd1e7bf6b66d 100644 (file)
@@ -39,6 +39,8 @@ int VMCIQueuePair_AllocPriv(VMCIHandle *handle, VMCIQueue **produceQ,
                             VMCIPrivilegeFlags privFlags);
 int VMCIQueuePair_Detach(VMCIHandle handle);
 
+void VMCIQueuePair_Hibernate(Bool enterHibernation);
+
 
 #endif /* !_VMCI_QUEUE_PAIR_INT_H_ */