From: VMware, Inc <> Date: Mon, 20 Sep 2010 18:21:07 +0000 (-0700) Subject: Implement Windows hibernation support X-Git-Tag: 2010.09.19-301124~3 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=30dbb17af40a3db47c32265d406cd5e96e65614b;p=thirdparty%2Fopen-vm-tools.git Implement Windows hibernation support 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 --- diff --git a/open-vm-tools/modules/linux/shared/vmci_kernel_if.h b/open-vm-tools/modules/linux/shared/vmci_kernel_if.h index 81534fe90..a06e208b3 100644 --- a/open-vm-tools/modules/linux/shared/vmci_kernel_if.h +++ b/open-vm-tools/modules/linux/shared/vmci_kernel_if.h @@ -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_ diff --git a/open-vm-tools/modules/linux/vmci/vmciNotifications.c b/open-vm-tools/modules/linux/vmci/vmciNotifications.c index fdbbd56cc..12df706e4 100644 --- a/open-vm-tools/modules/linux/vmci/vmciNotifications.c +++ b/open-vm-tools/modules/linux/vmci/vmciNotifications.c @@ -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; diff --git a/open-vm-tools/modules/linux/vmci/vmciNotifications.h b/open-vm-tools/modules/linux/vmci/vmciNotifications.h index 940ec5564..09c3da57d 100644 --- a/open-vm-tools/modules/linux/vmci/vmciNotifications.h +++ b/open-vm-tools/modules/linux/vmci/vmciNotifications.h @@ -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_ */ diff --git a/open-vm-tools/modules/linux/vmci/vmciQPair.c b/open-vm-tools/modules/linux/vmci/vmciQPair.c index e4ba9e4de..6da36c92b 100644 --- a/open-vm-tools/modules/linux/vmci/vmciQPair.c +++ b/open-vm-tools/modules/linux/vmci/vmciQPair.c @@ -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; } diff --git a/open-vm-tools/modules/linux/vmci/vmciQueuePair.c b/open-vm-tools/modules/linux/vmci/vmciQueuePair.c index 927255ff0..c97e286b7 100644 --- a/open-vm-tools/modules/linux/vmci/vmciQueuePair.c +++ b/open-vm-tools/modules/linux/vmci/vmciQueuePair.c @@ -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(); +} diff --git a/open-vm-tools/modules/linux/vmci/vmciQueuePairInt.h b/open-vm-tools/modules/linux/vmci/vmciQueuePairInt.h index 93b0b95e7..29849007a 100644 --- a/open-vm-tools/modules/linux/vmci/vmciQueuePairInt.h +++ b/open-vm-tools/modules/linux/vmci/vmciQueuePairInt.h @@ -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_ */