summaryrefslogtreecommitdiffstats
path: root/src/VBox/VMM/VMMR3/PDMThread.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/VMM/VMMR3/PDMThread.cpp')
-rw-r--r--src/VBox/VMM/VMMR3/PDMThread.cpp1103
1 files changed, 1103 insertions, 0 deletions
diff --git a/src/VBox/VMM/VMMR3/PDMThread.cpp b/src/VBox/VMM/VMMR3/PDMThread.cpp
new file mode 100644
index 00000000..f0ce41e7
--- /dev/null
+++ b/src/VBox/VMM/VMMR3/PDMThread.cpp
@@ -0,0 +1,1103 @@
+/* $Id: PDMThread.cpp $ */
+/** @file
+ * PDM Thread - VM Thread Management.
+ */
+
+/*
+ * Copyright (C) 2007-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+/// @todo \#define LOG_GROUP LOG_GROUP_PDM_THREAD
+#include "PDMInternal.h"
+#include <VBox/vmm/pdm.h>
+#include <VBox/vmm/mm.h>
+#include <VBox/vmm/vm.h>
+#include <VBox/vmm/uvm.h>
+#include <VBox/err.h>
+
+#include <VBox/log.h>
+#include <iprt/asm.h>
+#include <iprt/semaphore.h>
+#include <iprt/assert.h>
+#include <iprt/thread.h>
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static DECLCALLBACK(int) pdmR3ThreadMain(RTTHREAD Thread, void *pvUser);
+
+
+/**
+ * Wrapper around ASMAtomicCmpXchgSize.
+ */
+DECLINLINE(bool) pdmR3AtomicCmpXchgState(PPDMTHREAD pThread, PDMTHREADSTATE enmNewState, PDMTHREADSTATE enmOldState)
+{
+ bool fRc;
+ ASMAtomicCmpXchgSize(&pThread->enmState, enmNewState, enmOldState, fRc);
+ return fRc;
+}
+
+
+/**
+ * Does the wakeup call.
+ *
+ * @returns VBox status code. Already asserted on failure.
+ * @param pThread The PDM thread.
+ */
+static DECLCALLBACK(int) pdmR3ThreadWakeUp(PPDMTHREAD pThread)
+{
+ RTSemEventMultiSignal(pThread->Internal.s.SleepEvent);
+
+ int rc;
+ switch (pThread->Internal.s.enmType)
+ {
+ case PDMTHREADTYPE_DEVICE:
+ rc = pThread->u.Dev.pfnWakeUp(pThread->u.Dev.pDevIns, pThread);
+ break;
+
+ case PDMTHREADTYPE_USB:
+ rc = pThread->u.Usb.pfnWakeUp(pThread->u.Usb.pUsbIns, pThread);
+ break;
+
+ case PDMTHREADTYPE_DRIVER:
+ rc = pThread->u.Drv.pfnWakeUp(pThread->u.Drv.pDrvIns, pThread);
+ break;
+
+ case PDMTHREADTYPE_INTERNAL:
+ rc = pThread->u.Int.pfnWakeUp(pThread->Internal.s.pVM, pThread);
+ break;
+
+ case PDMTHREADTYPE_EXTERNAL:
+ rc = pThread->u.Ext.pfnWakeUp(pThread);
+ break;
+
+ default:
+ AssertMsgFailed(("%d\n", pThread->Internal.s.enmType));
+ rc = VERR_PDM_THREAD_IPE_1;
+ break;
+ }
+ AssertRC(rc);
+ return rc;
+}
+
+
+/**
+ * Allocates new thread instance.
+ *
+ * @returns VBox status code.
+ * @param pVM The cross context VM structure.
+ * @param ppThread Where to store the pointer to the instance.
+ */
+static int pdmR3ThreadNew(PVM pVM, PPPDMTHREAD ppThread)
+{
+ PPDMTHREAD pThread;
+ int rc = MMR3HeapAllocZEx(pVM, MM_TAG_PDM_THREAD, sizeof(*pThread), (void **)&pThread);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ pThread->u32Version = PDMTHREAD_VERSION;
+ pThread->enmState = PDMTHREADSTATE_INITIALIZING;
+ pThread->Thread = NIL_RTTHREAD;
+ pThread->Internal.s.pVM = pVM;
+
+ *ppThread = pThread;
+ return VINF_SUCCESS;
+}
+
+
+
+/**
+ * Initialize a new thread, this actually creates the thread.
+ *
+ * @returns VBox status code.
+ * @param pVM The cross context VM structure.
+ * @param ppThread Where the thread instance data handle is.
+ * @param cbStack The stack size, see RTThreadCreate().
+ * @param enmType The thread type, see RTThreadCreate().
+ * @param pszName The thread name, see RTThreadCreate().
+ */
+static int pdmR3ThreadInit(PVM pVM, PPPDMTHREAD ppThread, size_t cbStack, RTTHREADTYPE enmType, const char *pszName)
+{
+ PPDMTHREAD pThread = *ppThread;
+ PUVM pUVM = pVM->pUVM;
+
+ /*
+ * Initialize the remainder of the structure.
+ */
+ pThread->Internal.s.pVM = pVM;
+
+ int rc = RTSemEventMultiCreate(&pThread->Internal.s.BlockEvent);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTSemEventMultiCreate(&pThread->Internal.s.SleepEvent);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Create the thread and wait for it to initialize.
+ * The newly created thread will set the PDMTHREAD::Thread member.
+ */
+ RTTHREAD Thread;
+ rc = RTThreadCreate(&Thread, pdmR3ThreadMain, pThread, cbStack, enmType, RTTHREADFLAGS_WAITABLE, pszName);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTThreadUserWait(Thread, 60*1000);
+ if ( RT_SUCCESS(rc)
+ && pThread->enmState != PDMTHREADSTATE_SUSPENDED)
+ rc = VERR_PDM_THREAD_IPE_2;
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Insert it into the thread list.
+ */
+ RTCritSectEnter(&pUVM->pdm.s.ListCritSect);
+ pThread->Internal.s.pNext = NULL;
+ if (pUVM->pdm.s.pThreadsTail)
+ pUVM->pdm.s.pThreadsTail->Internal.s.pNext = pThread;
+ else
+ pUVM->pdm.s.pThreads = pThread;
+ pUVM->pdm.s.pThreadsTail = pThread;
+ RTCritSectLeave(&pUVM->pdm.s.ListCritSect);
+
+ rc = RTThreadUserReset(Thread);
+ AssertRC(rc);
+ return rc;
+ }
+
+ /* bailout */
+ RTThreadWait(Thread, 60*1000, NULL);
+ }
+ RTSemEventMultiDestroy(pThread->Internal.s.SleepEvent);
+ pThread->Internal.s.SleepEvent = NIL_RTSEMEVENTMULTI;
+ }
+ RTSemEventMultiDestroy(pThread->Internal.s.BlockEvent);
+ pThread->Internal.s.BlockEvent = NIL_RTSEMEVENTMULTI;
+ }
+ MMR3HeapFree(pThread);
+ *ppThread = NULL;
+
+ return rc;
+}
+
+
+/**
+ * Device Helper for creating a thread associated with a device.
+ *
+ * @returns VBox status code.
+ * @param pVM The cross context VM structure.
+ * @param pDevIns The device instance.
+ * @param ppThread Where to store the thread 'handle'.
+ * @param pvUser The user argument to the thread function.
+ * @param pfnThread The thread function.
+ * @param pfnWakeUp The wakup callback. This is called on the EMT thread when
+ * a state change is pending.
+ * @param cbStack See RTThreadCreate.
+ * @param enmType See RTThreadCreate.
+ * @param pszName See RTThreadCreate.
+ */
+int pdmR3ThreadCreateDevice(PVM pVM, PPDMDEVINS pDevIns, PPPDMTHREAD ppThread, void *pvUser, PFNPDMTHREADDEV pfnThread,
+ PFNPDMTHREADWAKEUPDEV pfnWakeUp, size_t cbStack, RTTHREADTYPE enmType, const char *pszName)
+{
+ int rc = pdmR3ThreadNew(pVM, ppThread);
+ if (RT_SUCCESS(rc))
+ {
+ PPDMTHREAD pThread = *ppThread;
+ pThread->pvUser = pvUser;
+ pThread->Internal.s.enmType = PDMTHREADTYPE_DEVICE;
+ pThread->u.Dev.pDevIns = pDevIns;
+ pThread->u.Dev.pfnThread = pfnThread;
+ pThread->u.Dev.pfnWakeUp = pfnWakeUp;
+ rc = pdmR3ThreadInit(pVM, ppThread, cbStack, enmType, pszName);
+ }
+ return rc;
+}
+
+
+/**
+ * USB Device Helper for creating a thread associated with an USB device.
+ *
+ * @returns VBox status code.
+ * @param pVM The cross context VM structure.
+ * @param pUsbIns The USB device instance.
+ * @param ppThread Where to store the thread 'handle'.
+ * @param pvUser The user argument to the thread function.
+ * @param pfnThread The thread function.
+ * @param pfnWakeUp The wakup callback. This is called on the EMT thread when
+ * a state change is pending.
+ * @param cbStack See RTThreadCreate.
+ * @param enmType See RTThreadCreate.
+ * @param pszName See RTThreadCreate.
+ */
+int pdmR3ThreadCreateUsb(PVM pVM, PPDMUSBINS pUsbIns, PPPDMTHREAD ppThread, void *pvUser, PFNPDMTHREADUSB pfnThread,
+ PFNPDMTHREADWAKEUPUSB pfnWakeUp, size_t cbStack, RTTHREADTYPE enmType, const char *pszName)
+{
+ int rc = pdmR3ThreadNew(pVM, ppThread);
+ if (RT_SUCCESS(rc))
+ {
+ PPDMTHREAD pThread = *ppThread;
+ pThread->pvUser = pvUser;
+ pThread->Internal.s.enmType = PDMTHREADTYPE_USB;
+ pThread->u.Usb.pUsbIns = pUsbIns;
+ pThread->u.Usb.pfnThread = pfnThread;
+ pThread->u.Usb.pfnWakeUp = pfnWakeUp;
+ rc = pdmR3ThreadInit(pVM, ppThread, cbStack, enmType, pszName);
+ }
+ return rc;
+}
+
+
+/**
+ * Driver Helper for creating a thread associated with a driver.
+ *
+ * @returns VBox status code.
+ * @param pVM The cross context VM structure.
+ * @param pDrvIns The driver instance.
+ * @param ppThread Where to store the thread 'handle'.
+ * @param pvUser The user argument to the thread function.
+ * @param pfnThread The thread function.
+ * @param pfnWakeUp The wakup callback. This is called on the EMT thread when
+ * a state change is pending.
+ * @param cbStack See RTThreadCreate.
+ * @param enmType See RTThreadCreate.
+ * @param pszName See RTThreadCreate.
+ */
+int pdmR3ThreadCreateDriver(PVM pVM, PPDMDRVINS pDrvIns, PPPDMTHREAD ppThread, void *pvUser, PFNPDMTHREADDRV pfnThread,
+ PFNPDMTHREADWAKEUPDRV pfnWakeUp, size_t cbStack, RTTHREADTYPE enmType, const char *pszName)
+{
+ int rc = pdmR3ThreadNew(pVM, ppThread);
+ if (RT_SUCCESS(rc))
+ {
+ PPDMTHREAD pThread = *ppThread;
+ pThread->pvUser = pvUser;
+ pThread->Internal.s.enmType = PDMTHREADTYPE_DRIVER;
+ pThread->u.Drv.pDrvIns = pDrvIns;
+ pThread->u.Drv.pfnThread = pfnThread;
+ pThread->u.Drv.pfnWakeUp = pfnWakeUp;
+ rc = pdmR3ThreadInit(pVM, ppThread, cbStack, enmType, pszName);
+ }
+ return rc;
+}
+
+
+/**
+ * Creates a PDM thread for internal use in the VM.
+ *
+ * @returns VBox status code.
+ * @param pVM The cross context VM structure.
+ * @param ppThread Where to store the thread 'handle'.
+ * @param pvUser The user argument to the thread function.
+ * @param pfnThread The thread function.
+ * @param pfnWakeUp The wakup callback. This is called on the EMT thread when
+ * a state change is pending.
+ * @param cbStack See RTThreadCreate.
+ * @param enmType See RTThreadCreate.
+ * @param pszName See RTThreadCreate.
+ */
+VMMR3DECL(int) PDMR3ThreadCreate(PVM pVM, PPPDMTHREAD ppThread, void *pvUser, PFNPDMTHREADINT pfnThread,
+ PFNPDMTHREADWAKEUPINT pfnWakeUp, size_t cbStack, RTTHREADTYPE enmType, const char *pszName)
+{
+ int rc = pdmR3ThreadNew(pVM, ppThread);
+ if (RT_SUCCESS(rc))
+ {
+ PPDMTHREAD pThread = *ppThread;
+ pThread->pvUser = pvUser;
+ pThread->Internal.s.enmType = PDMTHREADTYPE_INTERNAL;
+ pThread->u.Int.pfnThread = pfnThread;
+ pThread->u.Int.pfnWakeUp = pfnWakeUp;
+ rc = pdmR3ThreadInit(pVM, ppThread, cbStack, enmType, pszName);
+ }
+ return rc;
+}
+
+
+/**
+ * Creates a PDM thread for VM use by some external party.
+ *
+ * @returns VBox status code.
+ * @param pVM The cross context VM structure.
+ * @param ppThread Where to store the thread 'handle'.
+ * @param pvUser The user argument to the thread function.
+ * @param pfnThread The thread function.
+ * @param pfnWakeUp The wakup callback. This is called on the EMT thread when
+ * a state change is pending.
+ * @param cbStack See RTThreadCreate.
+ * @param enmType See RTThreadCreate.
+ * @param pszName See RTThreadCreate.
+ */
+VMMR3DECL(int) PDMR3ThreadCreateExternal(PVM pVM, PPPDMTHREAD ppThread, void *pvUser, PFNPDMTHREADEXT pfnThread,
+ PFNPDMTHREADWAKEUPEXT pfnWakeUp, size_t cbStack, RTTHREADTYPE enmType, const char *pszName)
+{
+ int rc = pdmR3ThreadNew(pVM, ppThread);
+ if (RT_SUCCESS(rc))
+ {
+ PPDMTHREAD pThread = *ppThread;
+ pThread->pvUser = pvUser;
+ pThread->Internal.s.enmType = PDMTHREADTYPE_EXTERNAL;
+ pThread->u.Ext.pfnThread = pfnThread;
+ pThread->u.Ext.pfnWakeUp = pfnWakeUp;
+ rc = pdmR3ThreadInit(pVM, ppThread, cbStack, enmType, pszName);
+ }
+ return rc;
+}
+
+
+/**
+ * Destroys a PDM thread.
+ *
+ * This will wakeup the thread, tell it to terminate, and wait for it terminate.
+ *
+ * @returns VBox status code.
+ * This reflects the success off destroying the thread and not the exit code
+ * of the thread as this is stored in *pRcThread.
+ * @param pThread The thread to destroy.
+ * @param pRcThread Where to store the thread exit code. Optional.
+ * @thread The emulation thread (EMT).
+ */
+VMMR3DECL(int) PDMR3ThreadDestroy(PPDMTHREAD pThread, int *pRcThread)
+{
+ /*
+ * Assert sanity.
+ */
+ AssertPtrReturn(pThread, VERR_INVALID_POINTER);
+ AssertReturn(pThread->u32Version == PDMTHREAD_VERSION, VERR_INVALID_MAGIC);
+ Assert(pThread->Thread != RTThreadSelf());
+ AssertPtrNullReturn(pRcThread, VERR_INVALID_POINTER);
+ PVM pVM = pThread->Internal.s.pVM;
+ VM_ASSERT_EMT(pVM);
+ PUVM pUVM = pVM->pUVM;
+
+ /*
+ * Advance the thread to the terminating state.
+ */
+ int rc = VINF_SUCCESS;
+ if (pThread->enmState <= PDMTHREADSTATE_TERMINATING)
+ {
+ for (;;)
+ {
+ PDMTHREADSTATE enmState = pThread->enmState;
+ switch (enmState)
+ {
+ case PDMTHREADSTATE_RUNNING:
+ if (!pdmR3AtomicCmpXchgState(pThread, PDMTHREADSTATE_TERMINATING, enmState))
+ continue;
+ rc = pdmR3ThreadWakeUp(pThread);
+ break;
+
+ case PDMTHREADSTATE_SUSPENDED:
+ case PDMTHREADSTATE_SUSPENDING:
+ case PDMTHREADSTATE_RESUMING:
+ case PDMTHREADSTATE_INITIALIZING:
+ if (!pdmR3AtomicCmpXchgState(pThread, PDMTHREADSTATE_TERMINATING, enmState))
+ continue;
+ break;
+
+ case PDMTHREADSTATE_TERMINATING:
+ case PDMTHREADSTATE_TERMINATED:
+ break;
+
+ default:
+ AssertMsgFailed(("enmState=%d\n", enmState));
+ rc = VERR_PDM_THREAD_IPE_2;
+ break;
+ }
+ break;
+ }
+ }
+ int rc2 = RTSemEventMultiSignal(pThread->Internal.s.BlockEvent);
+ AssertRC(rc2);
+
+ /*
+ * Wait for it to terminate and the do cleanups.
+ */
+ rc2 = RTThreadWait(pThread->Thread, RT_SUCCESS(rc) ? 60*1000 : 150, pRcThread);
+ if (RT_SUCCESS(rc2))
+ {
+ /* make it invalid. */
+ pThread->u32Version = 0xffffffff;
+ pThread->enmState = PDMTHREADSTATE_INVALID;
+ pThread->Thread = NIL_RTTHREAD;
+
+ /* unlink */
+ RTCritSectEnter(&pUVM->pdm.s.ListCritSect);
+ if (pUVM->pdm.s.pThreads == pThread)
+ {
+ pUVM->pdm.s.pThreads = pThread->Internal.s.pNext;
+ if (!pThread->Internal.s.pNext)
+ pUVM->pdm.s.pThreadsTail = NULL;
+ }
+ else
+ {
+ PPDMTHREAD pPrev = pUVM->pdm.s.pThreads;
+ while (pPrev && pPrev->Internal.s.pNext != pThread)
+ pPrev = pPrev->Internal.s.pNext;
+ Assert(pPrev);
+ if (pPrev)
+ pPrev->Internal.s.pNext = pThread->Internal.s.pNext;
+ if (!pThread->Internal.s.pNext)
+ pUVM->pdm.s.pThreadsTail = pPrev;
+ }
+ pThread->Internal.s.pNext = NULL;
+ RTCritSectLeave(&pUVM->pdm.s.ListCritSect);
+
+ /* free the resources */
+ RTSemEventMultiDestroy(pThread->Internal.s.BlockEvent);
+ pThread->Internal.s.BlockEvent = NIL_RTSEMEVENTMULTI;
+
+ RTSemEventMultiDestroy(pThread->Internal.s.SleepEvent);
+ pThread->Internal.s.SleepEvent = NIL_RTSEMEVENTMULTI;
+
+ MMR3HeapFree(pThread);
+ }
+ else if (RT_SUCCESS(rc))
+ rc = rc2;
+
+ return rc;
+}
+
+
+/**
+ * Destroys all threads associated with a device.
+ *
+ * This function is called by PDMDevice when a device is
+ * destroyed (not currently implemented).
+ *
+ * @returns VBox status code of the first failure.
+ * @param pVM The cross context VM structure.
+ * @param pDevIns the device instance.
+ */
+int pdmR3ThreadDestroyDevice(PVM pVM, PPDMDEVINS pDevIns)
+{
+ int rc = VINF_SUCCESS;
+ PUVM pUVM = pVM->pUVM;
+
+ AssertPtr(pDevIns);
+
+ RTCritSectEnter(&pUVM->pdm.s.ListCritSect);
+ PPDMTHREAD pThread = pUVM->pdm.s.pThreads;
+ while (pThread)
+ {
+ PPDMTHREAD pNext = pThread->Internal.s.pNext;
+ if ( pThread->Internal.s.enmType == PDMTHREADTYPE_DEVICE
+ && pThread->u.Dev.pDevIns == pDevIns)
+ {
+ int rc2 = PDMR3ThreadDestroy(pThread, NULL);
+ if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
+ rc = rc2;
+ }
+ pThread = pNext;
+ }
+ RTCritSectLeave(&pUVM->pdm.s.ListCritSect);
+ return rc;
+}
+
+
+/**
+ * Destroys all threads associated with an USB device.
+ *
+ * This function is called by PDMUsb when a device is destroyed.
+ *
+ * @returns VBox status code of the first failure.
+ * @param pVM The cross context VM structure.
+ * @param pUsbIns The USB device instance.
+ */
+int pdmR3ThreadDestroyUsb(PVM pVM, PPDMUSBINS pUsbIns)
+{
+ int rc = VINF_SUCCESS;
+ PUVM pUVM = pVM->pUVM;
+
+ AssertPtr(pUsbIns);
+
+ RTCritSectEnter(&pUVM->pdm.s.ListCritSect);
+ PPDMTHREAD pThread = pUVM->pdm.s.pThreads;
+ while (pThread)
+ {
+ PPDMTHREAD pNext = pThread->Internal.s.pNext;
+ if ( pThread->Internal.s.enmType == PDMTHREADTYPE_DEVICE
+ && pThread->u.Usb.pUsbIns == pUsbIns)
+ {
+ int rc2 = PDMR3ThreadDestroy(pThread, NULL);
+ if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
+ rc = rc2;
+ }
+ pThread = pNext;
+ }
+ RTCritSectLeave(&pUVM->pdm.s.ListCritSect);
+ return rc;
+}
+
+
+/**
+ * Destroys all threads associated with a driver.
+ *
+ * This function is called by PDMDriver when a driver is destroyed.
+ *
+ * @returns VBox status code of the first failure.
+ * @param pVM The cross context VM structure.
+ * @param pDrvIns The driver instance.
+ */
+int pdmR3ThreadDestroyDriver(PVM pVM, PPDMDRVINS pDrvIns)
+{
+ int rc = VINF_SUCCESS;
+ PUVM pUVM = pVM->pUVM;
+
+ AssertPtr(pDrvIns);
+
+ RTCritSectEnter(&pUVM->pdm.s.ListCritSect);
+ PPDMTHREAD pThread = pUVM->pdm.s.pThreads;
+ while (pThread)
+ {
+ PPDMTHREAD pNext = pThread->Internal.s.pNext;
+ if ( pThread->Internal.s.enmType == PDMTHREADTYPE_DRIVER
+ && pThread->u.Drv.pDrvIns == pDrvIns)
+ {
+ int rc2 = PDMR3ThreadDestroy(pThread, NULL);
+ if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
+ rc = rc2;
+ }
+ pThread = pNext;
+ }
+ RTCritSectLeave(&pUVM->pdm.s.ListCritSect);
+ return rc;
+}
+
+
+/**
+ * Called For VM power off.
+ *
+ * @param pVM The cross context VM structure.
+ */
+void pdmR3ThreadDestroyAll(PVM pVM)
+{
+ PUVM pUVM = pVM->pUVM;
+ RTCritSectEnter(&pUVM->pdm.s.ListCritSect);
+ PPDMTHREAD pThread = pUVM->pdm.s.pThreads;
+ while (pThread)
+ {
+ PPDMTHREAD pNext = pThread->Internal.s.pNext;
+ int rc2 = PDMR3ThreadDestroy(pThread, NULL);
+ AssertRC(rc2);
+ pThread = pNext;
+ }
+ Assert(!pUVM->pdm.s.pThreads && !pUVM->pdm.s.pThreadsTail);
+ RTCritSectLeave(&pUVM->pdm.s.ListCritSect);
+}
+
+
+/**
+ * Initiate termination of the thread (self) because something failed in a bad way.
+ *
+ * @param pThread The PDM thread.
+ */
+static void pdmR3ThreadBailMeOut(PPDMTHREAD pThread)
+{
+ for (;;)
+ {
+ PDMTHREADSTATE enmState = pThread->enmState;
+ switch (enmState)
+ {
+ case PDMTHREADSTATE_SUSPENDING:
+ case PDMTHREADSTATE_SUSPENDED:
+ case PDMTHREADSTATE_RESUMING:
+ case PDMTHREADSTATE_RUNNING:
+ if (!pdmR3AtomicCmpXchgState(pThread, PDMTHREADSTATE_TERMINATING, enmState))
+ continue;
+ break;
+
+ case PDMTHREADSTATE_TERMINATING:
+ case PDMTHREADSTATE_TERMINATED:
+ break;
+
+ case PDMTHREADSTATE_INITIALIZING:
+ default:
+ AssertMsgFailed(("enmState=%d\n", enmState));
+ break;
+ }
+ break;
+ }
+}
+
+
+/**
+ * Called by the PDM thread in response to a wakeup call with
+ * suspending as the new state.
+ *
+ * The thread will block in side this call until the state is changed in
+ * response to a VM state change or to the device/driver/whatever calling the
+ * PDMR3ThreadResume API.
+ *
+ * @returns VBox status code.
+ * On failure, terminate the thread.
+ * @param pThread The PDM thread.
+ */
+VMMR3DECL(int) PDMR3ThreadIAmSuspending(PPDMTHREAD pThread)
+{
+ /*
+ * Assert sanity.
+ */
+ AssertPtr(pThread);
+ AssertReturn(pThread->u32Version == PDMTHREAD_VERSION, VERR_INVALID_MAGIC);
+ Assert(pThread->Thread == RTThreadSelf() || pThread->enmState == PDMTHREADSTATE_INITIALIZING);
+ PDMTHREADSTATE enmState = pThread->enmState;
+ Assert( enmState == PDMTHREADSTATE_SUSPENDING
+ || enmState == PDMTHREADSTATE_INITIALIZING);
+
+ /*
+ * Update the state, notify the control thread (the API caller) and go to sleep.
+ */
+ int rc = VERR_WRONG_ORDER;
+ if (pdmR3AtomicCmpXchgState(pThread, PDMTHREADSTATE_SUSPENDED, enmState))
+ {
+ rc = RTThreadUserSignal(pThread->Thread);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTSemEventMultiWait(pThread->Internal.s.BlockEvent, RT_INDEFINITE_WAIT);
+ if ( RT_SUCCESS(rc)
+ && pThread->enmState != PDMTHREADSTATE_SUSPENDED)
+ return rc;
+
+ if (RT_SUCCESS(rc))
+ rc = VERR_PDM_THREAD_IPE_2;
+ }
+ }
+
+ AssertMsgFailed(("rc=%d enmState=%d\n", rc, pThread->enmState));
+ pdmR3ThreadBailMeOut(pThread);
+ return rc;
+}
+
+
+/**
+ * Called by the PDM thread in response to a resuming state.
+ *
+ * The purpose of this API is to tell the PDMR3ThreadResume caller that
+ * the PDM thread has successfully resumed. It will also do the
+ * state transition from the resuming to the running state.
+ *
+ * @returns VBox status code.
+ * On failure, terminate the thread.
+ * @param pThread The PDM thread.
+ */
+VMMR3DECL(int) PDMR3ThreadIAmRunning(PPDMTHREAD pThread)
+{
+ /*
+ * Assert sanity.
+ */
+ Assert(pThread->enmState == PDMTHREADSTATE_RESUMING);
+ Assert(pThread->Thread == RTThreadSelf());
+
+ /*
+ * Update the state and tell the control thread (the guy calling the resume API).
+ */
+ int rc = VERR_WRONG_ORDER;
+ if (pdmR3AtomicCmpXchgState(pThread, PDMTHREADSTATE_RUNNING, PDMTHREADSTATE_RESUMING))
+ {
+ rc = RTThreadUserSignal(pThread->Thread);
+ if (RT_SUCCESS(rc))
+ return rc;
+ }
+
+ AssertMsgFailed(("rc=%d enmState=%d\n", rc, pThread->enmState));
+ pdmR3ThreadBailMeOut(pThread);
+ return rc;
+}
+
+
+/**
+ * Called by the PDM thread instead of RTThreadSleep.
+ *
+ * The difference is that the sleep will be interrupted on state change. The
+ * thread must be in the running state, otherwise it will return immediately.
+ *
+ * @returns VBox status code.
+ * @retval VINF_SUCCESS on success or state change.
+ * @retval VERR_INTERRUPTED on signal or APC.
+ *
+ * @param pThread The PDM thread.
+ * @param cMillies The number of milliseconds to sleep.
+ */
+VMMR3DECL(int) PDMR3ThreadSleep(PPDMTHREAD pThread, RTMSINTERVAL cMillies)
+{
+ /*
+ * Assert sanity.
+ */
+ AssertReturn(pThread->enmState > PDMTHREADSTATE_INVALID && pThread->enmState < PDMTHREADSTATE_TERMINATED, VERR_PDM_THREAD_IPE_2);
+ AssertReturn(pThread->Thread == RTThreadSelf(), VERR_PDM_THREAD_INVALID_CALLER);
+
+ /*
+ * Reset the event semaphore, check the state and sleep.
+ */
+ RTSemEventMultiReset(pThread->Internal.s.SleepEvent);
+ if (pThread->enmState != PDMTHREADSTATE_RUNNING)
+ return VINF_SUCCESS;
+ return RTSemEventMultiWaitNoResume(pThread->Internal.s.SleepEvent, cMillies);
+}
+
+
+/**
+ * The PDM thread function.
+ *
+ * @returns return from pfnThread.
+ *
+ * @param Thread The thread handle.
+ * @param pvUser Pointer to the PDMTHREAD structure.
+ */
+static DECLCALLBACK(int) pdmR3ThreadMain(RTTHREAD Thread, void *pvUser)
+{
+ PPDMTHREAD pThread = (PPDMTHREAD)pvUser;
+ Log(("PDMThread: Initializing thread %RTthrd / %p / '%s'...\n", Thread, pThread, RTThreadGetName(Thread)));
+ pThread->Thread = Thread;
+
+ PUVM pUVM = pThread->Internal.s.pVM->pUVM;
+ if ( pUVM->pVmm2UserMethods
+ && pUVM->pVmm2UserMethods->pfnNotifyPdmtInit)
+ pUVM->pVmm2UserMethods->pfnNotifyPdmtInit(pUVM->pVmm2UserMethods, pUVM);
+
+ /*
+ * The run loop.
+ *
+ * It handles simple thread functions which returns when they see a suspending
+ * request and leaves the PDMR3ThreadIAmSuspending and PDMR3ThreadIAmRunning
+ * parts to us.
+ */
+ int rc;
+ for (;;)
+ {
+ switch (pThread->Internal.s.enmType)
+ {
+ case PDMTHREADTYPE_DEVICE:
+ rc = pThread->u.Dev.pfnThread(pThread->u.Dev.pDevIns, pThread);
+ break;
+
+ case PDMTHREADTYPE_USB:
+ rc = pThread->u.Usb.pfnThread(pThread->u.Usb.pUsbIns, pThread);
+ break;
+
+ case PDMTHREADTYPE_DRIVER:
+ rc = pThread->u.Drv.pfnThread(pThread->u.Drv.pDrvIns, pThread);
+ break;
+
+ case PDMTHREADTYPE_INTERNAL:
+ rc = pThread->u.Int.pfnThread(pThread->Internal.s.pVM, pThread);
+ break;
+
+ case PDMTHREADTYPE_EXTERNAL:
+ rc = pThread->u.Ext.pfnThread(pThread);
+ break;
+
+ default:
+ AssertMsgFailed(("%d\n", pThread->Internal.s.enmType));
+ rc = VERR_PDM_THREAD_IPE_1;
+ break;
+ }
+ if (RT_FAILURE(rc))
+ break;
+
+ /*
+ * If this is a simple thread function, the state will be suspending
+ * or initializing now. If it isn't we're supposed to terminate.
+ */
+ if ( pThread->enmState != PDMTHREADSTATE_SUSPENDING
+ && pThread->enmState != PDMTHREADSTATE_INITIALIZING)
+ {
+ Assert(pThread->enmState == PDMTHREADSTATE_TERMINATING);
+ break;
+ }
+ rc = PDMR3ThreadIAmSuspending(pThread);
+ if (RT_FAILURE(rc))
+ break;
+ if (pThread->enmState != PDMTHREADSTATE_RESUMING)
+ {
+ Assert(pThread->enmState == PDMTHREADSTATE_TERMINATING);
+ break;
+ }
+
+ rc = PDMR3ThreadIAmRunning(pThread);
+ if (RT_FAILURE(rc))
+ break;
+ }
+
+ if (RT_FAILURE(rc))
+ LogRel(("PDMThread: Thread '%s' (%RTthrd) quit unexpectedly with rc=%Rrc.\n", RTThreadGetName(Thread), Thread, rc));
+
+ /*
+ * Advance the state to terminating and then on to terminated.
+ */
+ for (;;)
+ {
+ PDMTHREADSTATE enmState = pThread->enmState;
+ if ( enmState == PDMTHREADSTATE_TERMINATING
+ || pdmR3AtomicCmpXchgState(pThread, PDMTHREADSTATE_TERMINATING, enmState))
+ break;
+ }
+
+ ASMAtomicXchgSize(&pThread->enmState, PDMTHREADSTATE_TERMINATED);
+ int rc2 = RTThreadUserSignal(Thread); AssertRC(rc2);
+
+ if ( pUVM->pVmm2UserMethods
+ && pUVM->pVmm2UserMethods->pfnNotifyPdmtTerm)
+ pUVM->pVmm2UserMethods->pfnNotifyPdmtTerm(pUVM->pVmm2UserMethods, pUVM);
+ Log(("PDMThread: Terminating thread %RTthrd / %p / '%s': %Rrc\n", Thread, pThread, RTThreadGetName(Thread), rc));
+ return rc;
+}
+
+
+/**
+ * Initiate termination of the thread because something failed in a bad way.
+ *
+ * @param pThread The PDM thread.
+ */
+static void pdmR3ThreadBailOut(PPDMTHREAD pThread)
+{
+ for (;;)
+ {
+ PDMTHREADSTATE enmState = pThread->enmState;
+ switch (enmState)
+ {
+ case PDMTHREADSTATE_SUSPENDING:
+ case PDMTHREADSTATE_SUSPENDED:
+ if (!pdmR3AtomicCmpXchgState(pThread, PDMTHREADSTATE_TERMINATING, enmState))
+ continue;
+ RTSemEventMultiSignal(pThread->Internal.s.BlockEvent);
+ break;
+
+ case PDMTHREADSTATE_RESUMING:
+ if (!pdmR3AtomicCmpXchgState(pThread, PDMTHREADSTATE_TERMINATING, enmState))
+ continue;
+ break;
+
+ case PDMTHREADSTATE_RUNNING:
+ if (!pdmR3AtomicCmpXchgState(pThread, PDMTHREADSTATE_TERMINATING, enmState))
+ continue;
+ pdmR3ThreadWakeUp(pThread);
+ break;
+
+ case PDMTHREADSTATE_TERMINATING:
+ case PDMTHREADSTATE_TERMINATED:
+ break;
+
+ case PDMTHREADSTATE_INITIALIZING:
+ default:
+ AssertMsgFailed(("enmState=%d\n", enmState));
+ break;
+ }
+ break;
+ }
+}
+
+
+/**
+ * Suspends the thread.
+ *
+ * This can be called at the power off / suspend notifications to suspend the
+ * PDM thread a bit early. The thread will be automatically suspend upon
+ * completion of the device/driver notification cycle.
+ *
+ * The caller is responsible for serializing the control operations on the
+ * thread. That basically means, always do these calls from the EMT.
+ *
+ * @returns VBox status code.
+ * @param pThread The PDM thread.
+ */
+VMMR3DECL(int) PDMR3ThreadSuspend(PPDMTHREAD pThread)
+{
+ /*
+ * Assert sanity.
+ */
+ AssertPtrReturn(pThread, VERR_INVALID_POINTER);
+ AssertReturn(pThread->u32Version == PDMTHREAD_VERSION, VERR_INVALID_MAGIC);
+ Assert(pThread->Thread != RTThreadSelf());
+
+ /*
+ * This is a noop if the thread is already suspended.
+ */
+ if (pThread->enmState == PDMTHREADSTATE_SUSPENDED)
+ return VINF_SUCCESS;
+
+ /*
+ * Change the state to resuming and kick the thread.
+ */
+ int rc = RTSemEventMultiReset(pThread->Internal.s.BlockEvent);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTThreadUserReset(pThread->Thread);
+ if (RT_SUCCESS(rc))
+ {
+ rc = VERR_WRONG_ORDER;
+ if (pdmR3AtomicCmpXchgState(pThread, PDMTHREADSTATE_SUSPENDING, PDMTHREADSTATE_RUNNING))
+ {
+ rc = pdmR3ThreadWakeUp(pThread);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Wait for the thread to reach the suspended state.
+ */
+ if (pThread->enmState != PDMTHREADSTATE_SUSPENDED)
+ rc = RTThreadUserWait(pThread->Thread, 60*1000);
+ if ( RT_SUCCESS(rc)
+ && pThread->enmState != PDMTHREADSTATE_SUSPENDED)
+ rc = VERR_PDM_THREAD_IPE_2;
+ if (RT_SUCCESS(rc))
+ return rc;
+ }
+ }
+ }
+ }
+
+ /*
+ * Something failed, initialize termination.
+ */
+ AssertMsgFailed(("PDMR3ThreadSuspend -> rc=%Rrc enmState=%d suspending '%s'\n",
+ rc, pThread->enmState, RTThreadGetName(pThread->Thread)));
+ pdmR3ThreadBailOut(pThread);
+ return rc;
+}
+
+
+/**
+ * Suspend all running threads.
+ *
+ * This is called by PDMR3Suspend() and PDMR3PowerOff() after all the devices
+ * and drivers have been notified about the suspend / power off.
+ *
+ * @return VBox status code.
+ * @param pVM The cross context VM structure.
+ */
+int pdmR3ThreadSuspendAll(PVM pVM)
+{
+ PUVM pUVM = pVM->pUVM;
+ RTCritSectEnter(&pUVM->pdm.s.ListCritSect); /* This may cause deadlocks later... */
+ for (PPDMTHREAD pThread = pUVM->pdm.s.pThreads; pThread; pThread = pThread->Internal.s.pNext)
+ switch (pThread->enmState)
+ {
+ case PDMTHREADSTATE_RUNNING:
+ {
+ int rc = PDMR3ThreadSuspend(pThread);
+ AssertLogRelMsgReturnStmt(RT_SUCCESS(rc),
+ ("PDMR3ThreadSuspend -> %Rrc for '%s'\n", rc, RTThreadGetName(pThread->Thread)),
+ RTCritSectLeave(&pUVM->pdm.s.ListCritSect),
+ rc);
+ break;
+ }
+
+ /* suspend -> power off; voluntary suspend. */
+ case PDMTHREADSTATE_SUSPENDED:
+ break;
+
+ default:
+ AssertMsgFailed(("pThread=%p enmState=%d\n", pThread, pThread->enmState));
+ break;
+ }
+ RTCritSectLeave(&pUVM->pdm.s.ListCritSect);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Resumes the thread.
+ *
+ * This can be called the power on / resume notifications to resume the
+ * PDM thread a bit early. The thread will be automatically resumed upon
+ * return from these two notification callbacks (devices/drivers).
+ *
+ * The caller is responsible for serializing the control operations on the
+ * thread. That basically means, always do these calls from the EMT.
+ *
+ * @returns VBox status code.
+ * @param pThread The PDM thread.
+ */
+VMMR3DECL(int) PDMR3ThreadResume(PPDMTHREAD pThread)
+{
+ /*
+ * Assert sanity.
+ */
+ AssertPtrReturn(pThread, VERR_INVALID_POINTER);
+ AssertReturn(pThread->u32Version == PDMTHREAD_VERSION, VERR_INVALID_MAGIC);
+ Assert(pThread->Thread != RTThreadSelf());
+
+ /*
+ * Change the state to resuming and kick the thread.
+ */
+ int rc = RTThreadUserReset(pThread->Thread);
+ if (RT_SUCCESS(rc))
+ {
+ rc = VERR_WRONG_ORDER;
+ if (pdmR3AtomicCmpXchgState(pThread, PDMTHREADSTATE_RESUMING, PDMTHREADSTATE_SUSPENDED))
+ {
+ rc = RTSemEventMultiSignal(pThread->Internal.s.BlockEvent);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Wait for the thread to reach the running state.
+ */
+ rc = RTThreadUserWait(pThread->Thread, 60*1000);
+ if ( RT_SUCCESS(rc)
+ && pThread->enmState != PDMTHREADSTATE_RUNNING)
+ rc = VERR_PDM_THREAD_IPE_2;
+ if (RT_SUCCESS(rc))
+ return rc;
+ }
+ }
+ }
+
+ /*
+ * Something failed, initialize termination.
+ */
+ AssertMsgFailed(("PDMR3ThreadResume -> rc=%Rrc enmState=%d\n", rc, pThread->enmState));
+ pdmR3ThreadBailOut(pThread);
+ return rc;
+}
+
+
+/**
+ * Resumes all threads not running.
+ *
+ * This is called by PDMR3Resume() and PDMR3PowerOn() after all the devices
+ * and drivers have been notified about the resume / power on .
+ *
+ * @return VBox status code.
+ * @param pVM The cross context VM structure.
+ */
+int pdmR3ThreadResumeAll(PVM pVM)
+{
+ PUVM pUVM = pVM->pUVM;
+ RTCritSectEnter(&pUVM->pdm.s.ListCritSect);
+ for (PPDMTHREAD pThread = pUVM->pdm.s.pThreads; pThread; pThread = pThread->Internal.s.pNext)
+ switch (pThread->enmState)
+ {
+ case PDMTHREADSTATE_SUSPENDED:
+ {
+ int rc = PDMR3ThreadResume(pThread);
+ AssertRCReturn(rc, rc);
+ break;
+ }
+
+ default:
+ AssertMsgFailed(("pThread=%p enmState=%d\n", pThread, pThread->enmState));
+ break;
+ }
+ RTCritSectLeave(&pUVM->pdm.s.ListCritSect);
+ return VINF_SUCCESS;
+}
+