diff options
Diffstat (limited to 'src/VBox/VMM/VMMR3/PDMThread.cpp')
-rw-r--r-- | src/VBox/VMM/VMMR3/PDMThread.cpp | 1093 |
1 files changed, 1093 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..35e5f1f9 --- /dev/null +++ b/src/VBox/VMM/VMMR3/PDMThread.cpp @@ -0,0 +1,1093 @@ +/* $Id: PDMThread.cpp $ */ +/** @file + * PDM Thread - VM Thread Management. + */ + +/* + * Copyright (C) 2007-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* 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; +} + |