diff options
Diffstat (limited to 'src/VBox/VMM/VMMAll/TMAllVirtual.cpp')
-rw-r--r-- | src/VBox/VMM/VMMAll/TMAllVirtual.cpp | 1154 |
1 files changed, 1154 insertions, 0 deletions
diff --git a/src/VBox/VMM/VMMAll/TMAllVirtual.cpp b/src/VBox/VMM/VMMAll/TMAllVirtual.cpp new file mode 100644 index 00000000..9244bd85 --- /dev/null +++ b/src/VBox/VMM/VMMAll/TMAllVirtual.cpp @@ -0,0 +1,1154 @@ +/* $Id: TMAllVirtual.cpp $ */ +/** @file + * TM - Timeout Manager, Virtual Time, All Contexts. + */ + +/* + * Copyright (C) 2006-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 * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_TM +#include <VBox/vmm/tm.h> +#include <VBox/vmm/dbgftrace.h> +#ifdef IN_RING3 +# include <iprt/thread.h> +#endif +#include "TMInternal.h" +#include <VBox/vmm/vmcc.h> +#include <VBox/vmm/vmm.h> +#include <VBox/err.h> +#include <VBox/log.h> +#include <VBox/sup.h> + +#include <iprt/time.h> +#include <iprt/assert.h> +#include <iprt/asm.h> +#include <iprt/asm-math.h> + + + +/** + * @interface_method_impl{RTTIMENANOTSDATA,pfnBad} + */ +DECLCALLBACK(DECLEXPORT(void)) tmVirtualNanoTSBad(PRTTIMENANOTSDATA pData, uint64_t u64NanoTS, uint64_t u64DeltaPrev, + uint64_t u64PrevNanoTS) +{ + PVMCC pVM = RT_FROM_CPP_MEMBER(pData, VMCC, VMCC_CTX(tm).s.VirtualGetRawData); + pData->cBadPrev++; + if ((int64_t)u64DeltaPrev < 0) + LogRel(("TM: u64DeltaPrev=%RI64 u64PrevNanoTS=0x%016RX64 u64NanoTS=0x%016RX64 pVM=%p\n", + u64DeltaPrev, u64PrevNanoTS, u64NanoTS, pVM)); + else + Log(("TM: u64DeltaPrev=%RI64 u64PrevNanoTS=0x%016RX64 u64NanoTS=0x%016RX64 pVM=%p (debugging?)\n", + u64DeltaPrev, u64PrevNanoTS, u64NanoTS, pVM)); +} + + +#ifdef IN_RING3 +/** + * @callback_method_impl{FNTIMENANOTSINTERNAL, For driverless mode.} + */ +static DECLCALLBACK(uint64_t) tmR3VirtualNanoTSDriverless(PRTTIMENANOTSDATA pData, PRTITMENANOTSEXTRA pExtra) +{ + RT_NOREF(pData); + if (pExtra) + pExtra->uTSCValue = ASMReadTSC(); + return RTTimeSystemNanoTS(); +} +#endif + + +/** + * @interface_method_impl{RTTIMENANOTSDATA,pfnRediscover} + * + * This is the initial worker, so the first call in each context ends up here. + * It is also used should the delta rating of the host CPUs change or if the + * fGetGipCpu feature the current worker relies upon becomes unavailable. The + * last two events may occur as CPUs are taken online. + */ +DECLCALLBACK(DECLEXPORT(uint64_t)) tmVirtualNanoTSRediscover(PRTTIMENANOTSDATA pData, PRTITMENANOTSEXTRA pExtra) +{ + PVMCC pVM = RT_FROM_CPP_MEMBER(pData, VMCC, VMCC_CTX(tm).s.VirtualGetRawData); + PFNTIMENANOTSINTERNAL pfnWorker; + + /* + * We require a valid GIP for the selection below. + * Invalid GIP is fatal, though we have to allow no GIP in driverless mode (ring-3 only). + */ + PSUPGLOBALINFOPAGE pGip = g_pSUPGlobalInfoPage; +#ifdef IN_RING3 + if (pGip) +#endif + { + AssertFatalMsg(RT_VALID_PTR(pGip), ("pVM=%p pGip=%p\n", pVM, pGip)); + AssertFatalMsg(pGip->u32Magic == SUPGLOBALINFOPAGE_MAGIC, ("pVM=%p pGip=%p u32Magic=%#x\n", pVM, pGip, pGip->u32Magic)); + AssertFatalMsg(pGip->u32Mode > SUPGIPMODE_INVALID && pGip->u32Mode < SUPGIPMODE_END, + ("pVM=%p pGip=%p u32Mode=%#x\n", pVM, pGip, pGip->u32Mode)); + + /* + * Determine the new worker. + */ +#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86) + bool const fLFence = RT_BOOL(ASMCpuId_EDX(1) & X86_CPUID_FEATURE_EDX_SSE2); +#endif + switch (pGip->u32Mode) + { +#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86) + case SUPGIPMODE_SYNC_TSC: + case SUPGIPMODE_INVARIANT_TSC: +# ifdef IN_RING0 + if (pGip->enmUseTscDelta <= SUPGIPUSETSCDELTA_ROUGHLY_ZERO) + pfnWorker = fLFence ? RTTimeNanoTSLFenceSyncInvarNoDelta : RTTimeNanoTSLegacySyncInvarNoDelta; + else + pfnWorker = fLFence ? RTTimeNanoTSLFenceSyncInvarWithDelta : RTTimeNanoTSLegacySyncInvarWithDelta; +# else + if (pGip->fGetGipCpu & SUPGIPGETCPU_IDTR_LIMIT_MASK_MAX_SET_CPUS) + pfnWorker = pGip->enmUseTscDelta <= SUPGIPUSETSCDELTA_PRACTICALLY_ZERO + ? fLFence ? RTTimeNanoTSLFenceSyncInvarNoDelta : RTTimeNanoTSLegacySyncInvarNoDelta + : fLFence ? RTTimeNanoTSLFenceSyncInvarWithDeltaUseIdtrLim : RTTimeNanoTSLegacySyncInvarWithDeltaUseIdtrLim; + else if (pGip->fGetGipCpu & SUPGIPGETCPU_RDTSCP_MASK_MAX_SET_CPUS) + pfnWorker = pGip->enmUseTscDelta <= SUPGIPUSETSCDELTA_PRACTICALLY_ZERO + ? fLFence ? RTTimeNanoTSLFenceSyncInvarNoDelta : RTTimeNanoTSLegacySyncInvarNoDelta + : fLFence ? RTTimeNanoTSLFenceSyncInvarWithDeltaUseRdtscp : RTTimeNanoTSLegacySyncInvarWithDeltaUseRdtscp; + else if (pGip->fGetGipCpu & SUPGIPGETCPU_APIC_ID_EXT_0B) + pfnWorker = pGip->enmUseTscDelta <= SUPGIPUSETSCDELTA_ROUGHLY_ZERO + ? fLFence ? RTTimeNanoTSLFenceSyncInvarNoDelta : RTTimeNanoTSLegacySyncInvarNoDelta + : fLFence ? RTTimeNanoTSLFenceSyncInvarWithDeltaUseApicIdExt0B : RTTimeNanoTSLegacySyncInvarWithDeltaUseApicIdExt0B; + else if (pGip->fGetGipCpu & SUPGIPGETCPU_APIC_ID_EXT_8000001E) + pfnWorker = pGip->enmUseTscDelta <= SUPGIPUSETSCDELTA_ROUGHLY_ZERO + ? fLFence ? RTTimeNanoTSLFenceSyncInvarNoDelta : RTTimeNanoTSLegacySyncInvarNoDelta + : fLFence ? RTTimeNanoTSLFenceSyncInvarWithDeltaUseApicIdExt8000001E : RTTimeNanoTSLegacySyncInvarWithDeltaUseApicIdExt8000001E; + else + pfnWorker = pGip->enmUseTscDelta <= SUPGIPUSETSCDELTA_ROUGHLY_ZERO + ? fLFence ? RTTimeNanoTSLFenceSyncInvarNoDelta : RTTimeNanoTSLegacySyncInvarNoDelta + : fLFence ? RTTimeNanoTSLFenceSyncInvarWithDeltaUseApicId : RTTimeNanoTSLegacySyncInvarWithDeltaUseApicId; +# endif + break; + + case SUPGIPMODE_ASYNC_TSC: +# ifdef IN_RING0 + pfnWorker = fLFence ? RTTimeNanoTSLFenceAsync : RTTimeNanoTSLegacyAsync; +# else + if (pGip->fGetGipCpu & SUPGIPGETCPU_IDTR_LIMIT_MASK_MAX_SET_CPUS) + pfnWorker = fLFence ? RTTimeNanoTSLFenceAsyncUseIdtrLim : RTTimeNanoTSLegacyAsyncUseIdtrLim; + else if (pGip->fGetGipCpu & SUPGIPGETCPU_RDTSCP_MASK_MAX_SET_CPUS) + pfnWorker = fLFence ? RTTimeNanoTSLFenceAsyncUseRdtscp : RTTimeNanoTSLegacyAsyncUseRdtscp; + else if (pGip->fGetGipCpu & SUPGIPGETCPU_RDTSCP_GROUP_IN_CH_NUMBER_IN_CL) + pfnWorker = fLFence ? RTTimeNanoTSLFenceAsyncUseRdtscpGroupChNumCl : RTTimeNanoTSLegacyAsyncUseRdtscpGroupChNumCl; + else if (pGip->fGetGipCpu & SUPGIPGETCPU_APIC_ID_EXT_0B) + pfnWorker = fLFence ? RTTimeNanoTSLFenceAsyncUseApicIdExt0B : RTTimeNanoTSLegacyAsyncUseApicIdExt0B; + else if (pGip->fGetGipCpu & SUPGIPGETCPU_APIC_ID_EXT_8000001E) + pfnWorker = fLFence ? RTTimeNanoTSLFenceAsyncUseApicIdExt8000001E : RTTimeNanoTSLegacyAsyncUseApicIdExt8000001E; + else + pfnWorker = fLFence ? RTTimeNanoTSLFenceAsyncUseApicId : RTTimeNanoTSLegacyAsyncUseApicId; +# endif + break; +#endif + default: + AssertFatalMsgFailed(("pVM=%p pGip=%p u32Mode=%#x\n", pVM, pGip, pGip->u32Mode)); + } + } +#ifdef IN_RING3 + else + pfnWorker = tmR3VirtualNanoTSDriverless; +#endif + + /* + * Update the pfnVirtualGetRaw pointer and call the worker we selected. + */ + ASMAtomicWritePtr((void * volatile *)&pVM->VMCC_CTX(tm).s.pfnVirtualGetRaw, (void *)(uintptr_t)pfnWorker); + return pfnWorker(pData, pExtra); +} + + +/** + * @interface_method_impl{RTTIMENANOTSDATA,pfnBadCpuIndex} + */ +DECLCALLBACK(DECLEXPORT(uint64_t)) tmVirtualNanoTSBadCpuIndex(PRTTIMENANOTSDATA pData, PRTITMENANOTSEXTRA pExtra, + uint16_t idApic, uint16_t iCpuSet, uint16_t iGipCpu) +{ + PVMCC pVM = RT_FROM_CPP_MEMBER(pData, VMCC, VMCC_CTX(tm).s.VirtualGetRawData); + AssertFatalMsgFailed(("pVM=%p idApic=%#x iCpuSet=%#x iGipCpu=%#x pExtra=%p\n", pVM, idApic, iCpuSet, iGipCpu, pExtra)); +#ifndef _MSC_VER + return UINT64_MAX; +#endif +} + + +/** + * Wrapper around the IPRT GIP time methods. + */ +DECLINLINE(uint64_t) tmVirtualGetRawNanoTS(PVMCC pVM) +{ +#ifdef IN_RING3 + uint64_t u64 = pVM->tm.s.pfnVirtualGetRaw(&pVM->tm.s.VirtualGetRawData, NULL /*pExtra*/); +#elif defined(IN_RING0) + uint32_t cPrevSteps = pVM->tmr0.s.VirtualGetRawData.c1nsSteps; + uint64_t u64 = pVM->tmr0.s.pfnVirtualGetRaw(&pVM->tmr0.s.VirtualGetRawData, NULL /*pExtra*/); + if (cPrevSteps != pVM->tmr0.s.VirtualGetRawData.c1nsSteps) + VMCPU_FF_SET(VMMGetCpu(pVM), VMCPU_FF_TO_R3); +#else +# error "unsupported context" +#endif + /*DBGFTRACE_POS_U64(pVM, u64);*/ + return u64; +} + + +/** + * Wrapper around the IPRT GIP time methods, extended version. + */ +DECLINLINE(uint64_t) tmVirtualGetRawNanoTSEx(PVMCC pVM, uint64_t *puTscNow) +{ + RTITMENANOTSEXTRA Extra; +#ifdef IN_RING3 + uint64_t u64 = pVM->tm.s.pfnVirtualGetRaw(&pVM->tm.s.VirtualGetRawData, &Extra); +#elif defined(IN_RING0) + uint32_t cPrevSteps = pVM->tmr0.s.VirtualGetRawData.c1nsSteps; + uint64_t u64 = pVM->tmr0.s.pfnVirtualGetRaw(&pVM->tmr0.s.VirtualGetRawData, &Extra); + if (cPrevSteps != pVM->tmr0.s.VirtualGetRawData.c1nsSteps) + VMCPU_FF_SET(VMMGetCpu(pVM), VMCPU_FF_TO_R3); +#else +# error "unsupported context" +#endif + if (puTscNow) + *puTscNow = Extra.uTSCValue; + /*DBGFTRACE_POS_U64(pVM, u64);*/ + return u64; +} + + +/** + * Get the time when we're not running at 100% + * + * @returns The timestamp. + * @param pVM The cross context VM structure. + * @param puTscNow Where to return the TSC corresponding to the returned + * timestamp (delta adjusted). Optional. + */ +static uint64_t tmVirtualGetRawNonNormal(PVMCC pVM, uint64_t *puTscNow) +{ + /* + * Recalculate the RTTimeNanoTS() value for the period where + * warp drive has been enabled. + */ + uint64_t u64 = tmVirtualGetRawNanoTSEx(pVM, puTscNow); + u64 -= pVM->tm.s.u64VirtualWarpDriveStart; + u64 *= pVM->tm.s.u32VirtualWarpDrivePercentage; + u64 /= 100; + u64 += pVM->tm.s.u64VirtualWarpDriveStart; + + /* + * Now we apply the virtual time offset. + * (Which is the negated tmVirtualGetRawNanoTS() value for when the virtual + * machine started if it had been running continuously without any suspends.) + */ + u64 -= pVM->tm.s.u64VirtualOffset; + return u64; +} + + +/** + * Get the raw virtual time. + * + * @returns The current time stamp. + * @param pVM The cross context VM structure. + */ +DECLINLINE(uint64_t) tmVirtualGetRaw(PVMCC pVM) +{ + if (RT_LIKELY(!pVM->tm.s.fVirtualWarpDrive)) + return tmVirtualGetRawNanoTS(pVM) - pVM->tm.s.u64VirtualOffset; + return tmVirtualGetRawNonNormal(pVM, NULL /*puTscNow*/); +} + + +/** + * Get the raw virtual time, extended version. + * + * @returns The current time stamp. + * @param pVM The cross context VM structure. + * @param puTscNow Where to return the TSC corresponding to the returned + * timestamp (delta adjusted). Optional. + */ +DECLINLINE(uint64_t) tmVirtualGetRawEx(PVMCC pVM, uint64_t *puTscNow) +{ + if (RT_LIKELY(!pVM->tm.s.fVirtualWarpDrive)) + return tmVirtualGetRawNanoTSEx(pVM, puTscNow) - pVM->tm.s.u64VirtualOffset; + return tmVirtualGetRawNonNormal(pVM, puTscNow); +} + + +/** + * Inlined version of tmVirtualGetEx. + */ +DECLINLINE(uint64_t) tmVirtualGet(PVMCC pVM, bool fCheckTimers) +{ + uint64_t u64; + if (RT_LIKELY(pVM->tm.s.cVirtualTicking)) + { + STAM_COUNTER_INC(&pVM->tm.s.StatVirtualGet); + u64 = tmVirtualGetRaw(pVM); + + /* + * Use the chance to check for expired timers. + */ + if (fCheckTimers) + { + PVMCPUCC pVCpuDst = VMCC_GET_CPU(pVM, pVM->tm.s.idTimerCpu); + if ( !VMCPU_FF_IS_SET(pVCpuDst, VMCPU_FF_TIMER) + && !pVM->tm.s.fRunningQueues + && ( pVM->tm.s.aTimerQueues[TMCLOCK_VIRTUAL].u64Expire <= u64 + || ( pVM->tm.s.fVirtualSyncTicking + && pVM->tm.s.aTimerQueues[TMCLOCK_VIRTUAL_SYNC].u64Expire <= u64 - pVM->tm.s.offVirtualSync + ) + ) + && !pVM->tm.s.fRunningQueues + ) + { + STAM_COUNTER_INC(&pVM->tm.s.StatVirtualGetSetFF); + Log5(("TMAllVirtual(%u): FF: %d -> 1\n", __LINE__, VMCPU_FF_IS_SET(pVCpuDst, VMCPU_FF_TIMER))); + VMCPU_FF_SET(pVCpuDst, VMCPU_FF_TIMER); +#ifdef IN_RING3 + VMR3NotifyCpuFFU(pVCpuDst->pUVCpu, VMNOTIFYFF_FLAGS_DONE_REM); +#endif + } + } + } + else + u64 = pVM->tm.s.u64Virtual; + return u64; +} + + +/** + * Gets the current TMCLOCK_VIRTUAL time + * + * @returns The timestamp. + * @param pVM The cross context VM structure. + * + * @remark While the flow of time will never go backwards, the speed of the + * progress varies due to inaccurate RTTimeNanoTS and TSC. The latter can be + * influenced by power saving (SpeedStep, PowerNow!), while the former + * makes use of TSC and kernel timers. + */ +VMM_INT_DECL(uint64_t) TMVirtualGet(PVMCC pVM) +{ + return tmVirtualGet(pVM, true /*fCheckTimers*/); +} + + +/** + * Gets the current TMCLOCK_VIRTUAL time without checking + * timers or anything. + * + * Meaning, this has no side effect on FFs like TMVirtualGet may have. + * + * @returns The timestamp. + * @param pVM The cross context VM structure. + * + * @remarks See TMVirtualGet. + */ +VMM_INT_DECL(uint64_t) TMVirtualGetNoCheck(PVMCC pVM) +{ + return tmVirtualGet(pVM, false /*fCheckTimers*/); +} + + +/** + * Converts the dead line interval from TMCLOCK_VIRTUAL to host nano seconds. + * + * @returns Host nano second count. + * @param pVM The cross context VM structure. + * @param cVirtTicksToDeadline The TMCLOCK_VIRTUAL interval. + */ +DECLINLINE(uint64_t) tmVirtualVirtToNsDeadline(PVM pVM, uint64_t cVirtTicksToDeadline) +{ + if (RT_UNLIKELY(pVM->tm.s.fVirtualWarpDrive)) + return ASMMultU64ByU32DivByU32(cVirtTicksToDeadline, 100, pVM->tm.s.u32VirtualWarpDrivePercentage); + return cVirtTicksToDeadline; +} + + +/** + * tmVirtualSyncGetLocked worker for handling catch-up when owning the lock. + * + * @returns The timestamp. + * @param pVM The cross context VM structure. + * @param u64 raw virtual time. + * @param off offVirtualSync. + * @param pcNsToDeadline Where to return the number of nano seconds to + * the next virtual sync timer deadline. Can be + * NULL. + * @param pnsAbsDeadline Where to return the absolute deadline. + * Optional. + */ +DECLINLINE(uint64_t) tmVirtualSyncGetHandleCatchUpLocked(PVMCC pVM, uint64_t u64, uint64_t off, + uint64_t *pcNsToDeadline, uint64_t *pnsAbsDeadline) +{ + STAM_COUNTER_INC(&pVM->tm.s.StatVirtualSyncGetLocked); + + /* + * Don't make updates until we've check the timer queue. + */ + bool fUpdatePrev = true; + bool fUpdateOff = true; + bool fStop = false; + const uint64_t u64Prev = pVM->tm.s.u64VirtualSyncCatchUpPrev; + uint64_t u64Delta = u64 - u64Prev; + if (RT_LIKELY(!(u64Delta >> 32))) + { + uint64_t u64Sub = ASMMultU64ByU32DivByU32(u64Delta, pVM->tm.s.u32VirtualSyncCatchUpPercentage, 100); + if (off > u64Sub + pVM->tm.s.offVirtualSyncGivenUp) + { + off -= u64Sub; + Log4(("TM: %'RU64/-%'8RU64: sub %RU32 [vsghcul]\n", u64 - off, off - pVM->tm.s.offVirtualSyncGivenUp, u64Sub)); + } + else + { + /* we've completely caught up. */ + STAM_PROFILE_ADV_STOP(&pVM->tm.s.StatVirtualSyncCatchup, c); + off = pVM->tm.s.offVirtualSyncGivenUp; + fStop = true; + Log4(("TM: %'RU64/0: caught up [vsghcul]\n", u64)); + } + } + else + { + /* More than 4 seconds since last time (or negative), ignore it. */ + fUpdateOff = false; + fUpdatePrev = !(u64Delta & RT_BIT_64(63)); + Log(("TMVirtualGetSync: u64Delta=%RX64\n", u64Delta)); + } + + /* + * Complete the calculation of the current TMCLOCK_VIRTUAL_SYNC time. The current + * approach is to never pass the head timer. So, when we do stop the clock and + * set the timer pending flag. + */ + u64 -= off; + + uint64_t u64Last = ASMAtomicUoReadU64(&pVM->tm.s.u64VirtualSync); + if (u64Last > u64) + { + u64 = u64Last + 1; + STAM_COUNTER_INC(&pVM->tm.s.StatVirtualSyncGetAdjLast); + } + + uint64_t u64Expire = ASMAtomicReadU64(&pVM->tm.s.aTimerQueues[TMCLOCK_VIRTUAL_SYNC].u64Expire); + if (pnsAbsDeadline) + *pnsAbsDeadline = u64Expire; /* Always return the unadjusted absolute deadline, or HM will waste time going + thru this code over an over again even if there aren't any timer changes. */ + if (u64 < u64Expire) + { + ASMAtomicWriteU64(&pVM->tm.s.u64VirtualSync, u64); + if (fUpdateOff) + ASMAtomicWriteU64(&pVM->tm.s.offVirtualSync, off); + if (fStop) + ASMAtomicWriteBool(&pVM->tm.s.fVirtualSyncCatchUp, false); + if (fUpdatePrev) + ASMAtomicWriteU64(&pVM->tm.s.u64VirtualSyncCatchUpPrev, u64); + if (pcNsToDeadline) + { + uint64_t cNsToDeadline = u64Expire - u64; + if (pVM->tm.s.fVirtualSyncCatchUp) + cNsToDeadline = ASMMultU64ByU32DivByU32(cNsToDeadline, 100, + pVM->tm.s.u32VirtualSyncCatchUpPercentage + 100); + *pcNsToDeadline = tmVirtualVirtToNsDeadline(pVM, cNsToDeadline); + } + PDMCritSectLeave(pVM, &pVM->tm.s.VirtualSyncLock); + } + else + { + u64 = u64Expire; + ASMAtomicWriteU64(&pVM->tm.s.u64VirtualSync, u64); + ASMAtomicWriteBool(&pVM->tm.s.fVirtualSyncTicking, false); + + VM_FF_SET(pVM, VM_FF_TM_VIRTUAL_SYNC); + PVMCPUCC pVCpuDst = VMCC_GET_CPU(pVM, pVM->tm.s.idTimerCpu); + VMCPU_FF_SET(pVCpuDst, VMCPU_FF_TIMER); + Log5(("TMAllVirtual(%u): FF: %d -> 1\n", __LINE__, VMCPU_FF_IS_SET(pVCpuDst, VMCPU_FF_TIMER))); + Log4(("TM: %'RU64/-%'8RU64: exp tmr=>ff [vsghcul]\n", u64, pVM->tm.s.offVirtualSync - pVM->tm.s.offVirtualSyncGivenUp)); + PDMCritSectLeave(pVM, &pVM->tm.s.VirtualSyncLock); + + if (pcNsToDeadline) + *pcNsToDeadline = 0; +#ifdef IN_RING3 + VMR3NotifyCpuFFU(pVCpuDst->pUVCpu, VMNOTIFYFF_FLAGS_DONE_REM); +#endif + STAM_COUNTER_INC(&pVM->tm.s.StatVirtualSyncGetSetFF); + STAM_COUNTER_INC(&pVM->tm.s.StatVirtualSyncGetExpired); + } + STAM_COUNTER_INC(&pVM->tm.s.StatVirtualSyncGetLocked); + + Log6(("tmVirtualSyncGetHandleCatchUpLocked -> %'RU64\n", u64)); + DBGFTRACE_U64_TAG(pVM, u64, "tmVirtualSyncGetHandleCatchUpLocked"); + return u64; +} + + +/** + * tmVirtualSyncGetEx worker for when we get the lock. + * + * @returns timesamp. + * @param pVM The cross context VM structure. + * @param u64 The virtual clock timestamp. + * @param pcNsToDeadline Where to return the number of nano seconds to + * the next virtual sync timer deadline. Can be + * NULL. + * @param pnsAbsDeadline Where to return the absolute deadline. + * Optional. + */ +DECLINLINE(uint64_t) tmVirtualSyncGetLocked(PVMCC pVM, uint64_t u64, uint64_t *pcNsToDeadline, uint64_t *pnsAbsDeadline) +{ + /* + * Not ticking? + */ + if (!pVM->tm.s.fVirtualSyncTicking) + { + u64 = ASMAtomicUoReadU64(&pVM->tm.s.u64VirtualSync); + PDMCritSectLeave(pVM, &pVM->tm.s.VirtualSyncLock); + if (pcNsToDeadline) + *pcNsToDeadline = 0; + if (pnsAbsDeadline) + *pnsAbsDeadline = u64; + STAM_COUNTER_INC(&pVM->tm.s.StatVirtualSyncGetLocked); + Log6(("tmVirtualSyncGetLocked -> %'RU64 [stopped]\n", u64)); + DBGFTRACE_U64_TAG(pVM, u64, "tmVirtualSyncGetLocked-stopped"); + return u64; + } + + /* + * Handle catch up in a separate function. + */ + uint64_t off = ASMAtomicUoReadU64(&pVM->tm.s.offVirtualSync); + if (ASMAtomicUoReadBool(&pVM->tm.s.fVirtualSyncCatchUp)) + return tmVirtualSyncGetHandleCatchUpLocked(pVM, u64, off, pcNsToDeadline, pnsAbsDeadline); + + /* + * Complete the calculation of the current TMCLOCK_VIRTUAL_SYNC time. The current + * approach is to never pass the head timer. So, when we do stop the clock and + * set the timer pending flag. + */ + u64 -= off; + + uint64_t u64Last = ASMAtomicUoReadU64(&pVM->tm.s.u64VirtualSync); + if (u64Last > u64) + { + u64 = u64Last + 1; + STAM_COUNTER_INC(&pVM->tm.s.StatVirtualSyncGetAdjLast); + } + + uint64_t u64Expire = ASMAtomicReadU64(&pVM->tm.s.aTimerQueues[TMCLOCK_VIRTUAL_SYNC].u64Expire); + if (pnsAbsDeadline) + *pnsAbsDeadline = u64Expire; + if (u64 < u64Expire) + { + ASMAtomicWriteU64(&pVM->tm.s.u64VirtualSync, u64); + PDMCritSectLeave(pVM, &pVM->tm.s.VirtualSyncLock); + if (pcNsToDeadline) + *pcNsToDeadline = tmVirtualVirtToNsDeadline(pVM, u64Expire - u64); + } + else + { + u64 = u64Expire; + ASMAtomicWriteU64(&pVM->tm.s.u64VirtualSync, u64); + ASMAtomicWriteBool(&pVM->tm.s.fVirtualSyncTicking, false); + + VM_FF_SET(pVM, VM_FF_TM_VIRTUAL_SYNC); + PVMCPUCC pVCpuDst = VMCC_GET_CPU(pVM, pVM->tm.s.idTimerCpu); + VMCPU_FF_SET(pVCpuDst, VMCPU_FF_TIMER); + Log5(("TMAllVirtual(%u): FF: %d -> 1\n", __LINE__, VMCPU_FF_IS_SET(pVCpuDst, VMCPU_FF_TIMER))); + Log4(("TM: %'RU64/-%'8RU64: exp tmr=>ff [vsgl]\n", u64, pVM->tm.s.offVirtualSync - pVM->tm.s.offVirtualSyncGivenUp)); + PDMCritSectLeave(pVM, &pVM->tm.s.VirtualSyncLock); + +#ifdef IN_RING3 + VMR3NotifyCpuFFU(pVCpuDst->pUVCpu, VMNOTIFYFF_FLAGS_DONE_REM); +#endif + if (pcNsToDeadline) + *pcNsToDeadline = 0; + STAM_COUNTER_INC(&pVM->tm.s.StatVirtualSyncGetSetFF); + STAM_COUNTER_INC(&pVM->tm.s.StatVirtualSyncGetExpired); + } + STAM_COUNTER_INC(&pVM->tm.s.StatVirtualSyncGetLocked); + Log6(("tmVirtualSyncGetLocked -> %'RU64\n", u64)); + DBGFTRACE_U64_TAG(pVM, u64, "tmVirtualSyncGetLocked"); + return u64; +} + + +/** + * Gets the current TMCLOCK_VIRTUAL_SYNC time. + * + * @returns The timestamp. + * @param pVM The cross context VM structure. + * @param fCheckTimers Check timers or not + * @param pcNsToDeadline Where to return the number of nano seconds to + * the next virtual sync timer deadline. Can be + * NULL. + * @param pnsAbsDeadline Where to return the absolute deadline. + * Optional. + * @param puTscNow Where to return the TSC corresponding to the + * returned timestamp (delta adjusted). Optional. + * @thread EMT. + */ +DECLINLINE(uint64_t) tmVirtualSyncGetEx(PVMCC pVM, bool fCheckTimers, uint64_t *pcNsToDeadline, + uint64_t *pnsAbsDeadline, uint64_t *puTscNow) +{ + STAM_COUNTER_INC(&pVM->tm.s.StatVirtualSyncGet); + + uint64_t u64; + if (!pVM->tm.s.fVirtualSyncTicking) + { + if (pcNsToDeadline) + *pcNsToDeadline = 0; + u64 = pVM->tm.s.u64VirtualSync; + DBGFTRACE_U64_TAG(pVM, u64, "tmVirtualSyncGetEx-stopped1"); + return u64; + } + + /* + * Query the virtual clock and do the usual expired timer check. + */ + Assert(pVM->tm.s.cVirtualTicking); + u64 = tmVirtualGetRawEx(pVM, puTscNow); + if (fCheckTimers) + { + PVMCPUCC pVCpuDst = VMCC_GET_CPU(pVM, pVM->tm.s.idTimerCpu); + if ( !VMCPU_FF_IS_SET(pVCpuDst, VMCPU_FF_TIMER) + && pVM->tm.s.aTimerQueues[TMCLOCK_VIRTUAL].u64Expire <= u64) + { + Log5(("TMAllVirtual(%u): FF: 0 -> 1\n", __LINE__)); + VMCPU_FF_SET(pVCpuDst, VMCPU_FF_TIMER); +#ifdef IN_RING3 + VMR3NotifyCpuFFU(pVCpuDst->pUVCpu, VMNOTIFYFF_FLAGS_DONE_REM /** @todo |VMNOTIFYFF_FLAGS_POKE*/); +#endif + STAM_COUNTER_INC(&pVM->tm.s.StatVirtualSyncGetSetFF); + } + } + + /* + * If we can get the lock, get it. The result is much more reliable. + * + * Note! This is where all clock source devices branch off because they + * will be owning the lock already. The 'else' is taken by code + * which is less picky or hasn't been adjusted yet + */ + /** @todo switch this around, have the tmVirtualSyncGetLocked code inlined + * here and the remainder of this function in a static worker. */ + if (PDMCritSectTryEnter(pVM, &pVM->tm.s.VirtualSyncLock) == VINF_SUCCESS) + return tmVirtualSyncGetLocked(pVM, u64, pcNsToDeadline, pnsAbsDeadline); + + /* + * When the clock is ticking, not doing catch ups and not running into an + * expired time, we can get away without locking. Try this first. + */ + uint64_t off; + if (ASMAtomicUoReadBool(&pVM->tm.s.fVirtualSyncTicking)) + { + if (!ASMAtomicUoReadBool(&pVM->tm.s.fVirtualSyncCatchUp)) + { + off = ASMAtomicReadU64(&pVM->tm.s.offVirtualSync); + if (RT_LIKELY( ASMAtomicUoReadBool(&pVM->tm.s.fVirtualSyncTicking) + && !ASMAtomicUoReadBool(&pVM->tm.s.fVirtualSyncCatchUp) + && off == ASMAtomicReadU64(&pVM->tm.s.offVirtualSync))) + { + off = u64 - off; + uint64_t const u64Expire = ASMAtomicReadU64(&pVM->tm.s.aTimerQueues[TMCLOCK_VIRTUAL_SYNC].u64Expire); + if (off < u64Expire) + { + if (pnsAbsDeadline) + *pnsAbsDeadline = u64Expire; + if (pcNsToDeadline) + *pcNsToDeadline = tmVirtualVirtToNsDeadline(pVM, u64Expire - off); + STAM_COUNTER_INC(&pVM->tm.s.StatVirtualSyncGetLockless); + Log6(("tmVirtualSyncGetEx -> %'RU64 [lockless]\n", off)); + DBGFTRACE_U64_TAG(pVM, off, "tmVirtualSyncGetEx-lockless"); + return off; + } + } + } + } + else + { + off = ASMAtomicReadU64(&pVM->tm.s.u64VirtualSync); + if (RT_LIKELY(!ASMAtomicReadBool(&pVM->tm.s.fVirtualSyncTicking))) + { + if (pcNsToDeadline) + *pcNsToDeadline = 0; + if (pnsAbsDeadline) + *pnsAbsDeadline = off; + STAM_COUNTER_INC(&pVM->tm.s.StatVirtualSyncGetLockless); + Log6(("tmVirtualSyncGetEx -> %'RU64 [lockless/stopped]\n", off)); + DBGFTRACE_U64_TAG(pVM, off, "tmVirtualSyncGetEx-stopped2"); + return off; + } + } + + /* + * Read the offset and adjust if we're playing catch-up. + * + * The catch-up adjusting work by us decrementing the offset by a percentage of + * the time elapsed since the previous TMVirtualGetSync call. + * + * It's possible to get a very long or even negative interval between two read + * for the following reasons: + * - Someone might have suspended the process execution, frequently the case when + * debugging the process. + * - We might be on a different CPU which TSC isn't quite in sync with the + * other CPUs in the system. + * - Another thread is racing us and we might have been preempted while inside + * this function. + * + * Assuming nano second virtual time, we can simply ignore any intervals which has + * any of the upper 32 bits set. + */ + AssertCompile(TMCLOCK_FREQ_VIRTUAL == 1000000000); + int cOuterTries = 42; + for (;; cOuterTries--) + { + /* Try grab the lock, things get simpler when owning the lock. */ + int rcLock = PDMCritSectTryEnter(pVM, &pVM->tm.s.VirtualSyncLock); + if (RT_SUCCESS_NP(rcLock)) + return tmVirtualSyncGetLocked(pVM, u64, pcNsToDeadline, pnsAbsDeadline); + + /* Re-check the ticking flag. */ + if (!ASMAtomicReadBool(&pVM->tm.s.fVirtualSyncTicking)) + { + off = ASMAtomicReadU64(&pVM->tm.s.u64VirtualSync); + if ( ASMAtomicReadBool(&pVM->tm.s.fVirtualSyncTicking) + && cOuterTries > 0) + continue; + if (pcNsToDeadline) + *pcNsToDeadline = 0; + if (pnsAbsDeadline) + *pnsAbsDeadline = off; + Log6(("tmVirtualSyncGetEx -> %'RU64 [stopped]\n", off)); + DBGFTRACE_U64_TAG(pVM, off, "tmVirtualSyncGetEx-stopped3"); + return off; + } + + off = ASMAtomicReadU64(&pVM->tm.s.offVirtualSync); + if (ASMAtomicReadBool(&pVM->tm.s.fVirtualSyncCatchUp)) + { + /* No changes allowed, try get a consistent set of parameters. */ + uint64_t const u64Prev = ASMAtomicReadU64(&pVM->tm.s.u64VirtualSyncCatchUpPrev); + uint64_t const offGivenUp = ASMAtomicReadU64(&pVM->tm.s.offVirtualSyncGivenUp); + uint32_t const u32Pct = ASMAtomicReadU32(&pVM->tm.s.u32VirtualSyncCatchUpPercentage); + if ( ( u64Prev == ASMAtomicReadU64(&pVM->tm.s.u64VirtualSyncCatchUpPrev) + && offGivenUp == ASMAtomicReadU64(&pVM->tm.s.offVirtualSyncGivenUp) + && u32Pct == ASMAtomicReadU32(&pVM->tm.s.u32VirtualSyncCatchUpPercentage) + && ASMAtomicReadBool(&pVM->tm.s.fVirtualSyncCatchUp)) + || cOuterTries <= 0) + { + uint64_t u64Delta = u64 - u64Prev; + if (RT_LIKELY(!(u64Delta >> 32))) + { + uint64_t u64Sub = ASMMultU64ByU32DivByU32(u64Delta, u32Pct, 100); + if (off > u64Sub + offGivenUp) + { + off -= u64Sub; + Log4(("TM: %'RU64/-%'8RU64: sub %RU32 [NoLock]\n", u64 - off, pVM->tm.s.offVirtualSync - offGivenUp, u64Sub)); + } + else + { + /* we've completely caught up. */ + STAM_PROFILE_ADV_STOP(&pVM->tm.s.StatVirtualSyncCatchup, c); + off = offGivenUp; + Log4(("TM: %'RU64/0: caught up [NoLock]\n", u64)); + } + } + else + /* More than 4 seconds since last time (or negative), ignore it. */ + Log(("TMVirtualGetSync: u64Delta=%RX64 (NoLock)\n", u64Delta)); + + /* Check that we're still running and in catch up. */ + if ( ASMAtomicUoReadBool(&pVM->tm.s.fVirtualSyncTicking) + && ASMAtomicReadBool(&pVM->tm.s.fVirtualSyncCatchUp)) + break; + if (cOuterTries <= 0) + break; /* enough */ + } + } + else if ( off == ASMAtomicReadU64(&pVM->tm.s.offVirtualSync) + && !ASMAtomicReadBool(&pVM->tm.s.fVirtualSyncCatchUp)) + break; /* Got an consistent offset */ + else if (cOuterTries <= 0) + break; /* enough */ + } + if (cOuterTries <= 0) + STAM_COUNTER_INC(&pVM->tm.s.StatVirtualSyncGetELoop); + + /* + * Complete the calculation of the current TMCLOCK_VIRTUAL_SYNC time. The current + * approach is to never pass the head timer. So, when we do stop the clock and + * set the timer pending flag. + */ + u64 -= off; +/** @todo u64VirtualSyncLast */ + uint64_t u64Expire = ASMAtomicReadU64(&pVM->tm.s.aTimerQueues[TMCLOCK_VIRTUAL_SYNC].u64Expire); + if (pnsAbsDeadline) + *pnsAbsDeadline = u64Expire; + if (u64 >= u64Expire) + { + PVMCPUCC pVCpuDst = VMCC_GET_CPU(pVM, pVM->tm.s.idTimerCpu); + if (!VMCPU_FF_IS_SET(pVCpuDst, VMCPU_FF_TIMER)) + { + Log5(("TMAllVirtual(%u): FF: %d -> 1 (NoLock)\n", __LINE__, VMCPU_FF_IS_SET(pVCpuDst, VMCPU_FF_TIMER))); + VM_FF_SET(pVM, VM_FF_TM_VIRTUAL_SYNC); /* Hmm? */ + VMCPU_FF_SET(pVCpuDst, VMCPU_FF_TIMER); +#ifdef IN_RING3 + VMR3NotifyCpuFFU(pVCpuDst->pUVCpu, VMNOTIFYFF_FLAGS_DONE_REM); +#endif + STAM_COUNTER_INC(&pVM->tm.s.StatVirtualSyncGetSetFF); + Log4(("TM: %'RU64/-%'8RU64: exp tmr=>ff [NoLock]\n", u64, pVM->tm.s.offVirtualSync - pVM->tm.s.offVirtualSyncGivenUp)); + } + else + Log4(("TM: %'RU64/-%'8RU64: exp tmr [NoLock]\n", u64, pVM->tm.s.offVirtualSync - pVM->tm.s.offVirtualSyncGivenUp)); + if (pcNsToDeadline) + *pcNsToDeadline = 0; + STAM_COUNTER_INC(&pVM->tm.s.StatVirtualSyncGetExpired); + } + else if (pcNsToDeadline) + { + uint64_t cNsToDeadline = u64Expire - u64; + if (ASMAtomicReadBool(&pVM->tm.s.fVirtualSyncCatchUp)) + cNsToDeadline = ASMMultU64ByU32DivByU32(cNsToDeadline, 100, + ASMAtomicReadU32(&pVM->tm.s.u32VirtualSyncCatchUpPercentage) + 100); + *pcNsToDeadline = tmVirtualVirtToNsDeadline(pVM, cNsToDeadline); + } + + Log6(("tmVirtualSyncGetEx -> %'RU64\n", u64)); + DBGFTRACE_U64_TAG(pVM, u64, "tmVirtualSyncGetEx-nolock"); + return u64; +} + + +/** + * Gets the current TMCLOCK_VIRTUAL_SYNC time. + * + * @returns The timestamp. + * @param pVM The cross context VM structure. + * @thread EMT. + * @remarks May set the timer and virtual sync FFs. + */ +VMM_INT_DECL(uint64_t) TMVirtualSyncGet(PVMCC pVM) +{ + return tmVirtualSyncGetEx(pVM, true /*fCheckTimers*/, NULL /*pcNsToDeadline*/, NULL /*pnsAbsDeadline*/, NULL /*puTscNow*/); +} + + +/** + * Gets the current TMCLOCK_VIRTUAL_SYNC time without checking timers running on + * TMCLOCK_VIRTUAL. + * + * @returns The timestamp. + * @param pVM The cross context VM structure. + * @thread EMT. + * @remarks May set the timer and virtual sync FFs. + */ +VMM_INT_DECL(uint64_t) TMVirtualSyncGetNoCheck(PVMCC pVM) +{ + return tmVirtualSyncGetEx(pVM, false /*fCheckTimers*/, NULL /*pcNsToDeadline*/, NULL /*pnsAbsDeadline*/, NULL /*puTscNow*/); +} + + +/** + * Gets the current TMCLOCK_VIRTUAL_SYNC time without checking timers running on + * TMCLOCK_VIRTUAL, also returning corresponding TSC value. + * + * @returns The timestamp. + * @param pVM The cross context VM structure. + * @param puTscNow Where to return the TSC value that the return + * value is relative to. This is delta adjusted. + * @thread EMT. + * @remarks May set the timer and virtual sync FFs. + */ +VMM_INT_DECL(uint64_t) TMVirtualSyncGetNoCheckWithTsc(PVMCC pVM, uint64_t *puTscNow) +{ + return tmVirtualSyncGetEx(pVM, false /*fCheckTimers*/, NULL /*pcNsToDeadline*/, NULL /*pnsAbsDeadline*/, puTscNow); +} + + +/** + * Gets the current TMCLOCK_VIRTUAL_SYNC time. + * + * @returns The timestamp. + * @param pVM The cross context VM structure. + * @param fCheckTimers Check timers on the virtual clock or not. + * @thread EMT. + * @remarks May set the timer and virtual sync FFs. + */ +VMM_INT_DECL(uint64_t) TMVirtualSyncGetEx(PVMCC pVM, bool fCheckTimers) +{ + return tmVirtualSyncGetEx(pVM, fCheckTimers, NULL /*pcNsToDeadline*/, NULL /*pnsAbsDeadline*/, NULL /*puTscNow*/); +} + + +/** + * Gets the current TMCLOCK_VIRTUAL_SYNC time and ticks to the next deadline + * without checking timers running on TMCLOCK_VIRTUAL. + * + * @returns The timestamp. + * @param pVM The cross context VM structure. + * @param pcNsToDeadline Where to return the number of nano seconds to + * the next virtual sync timer deadline. + * @param puTscNow Where to return the TSC value that the return + * value is relative to. This is delta adjusted. + * @param puDeadlineVersion Where to return the deadline "version" number. + * Use with TMVirtualSyncIsCurrentDeadlineVersion() + * to check if the absolute deadline is still up to + * date and the caller can skip calling this + * function. + * @thread EMT. + * @remarks May set the timer and virtual sync FFs. + */ +VMM_INT_DECL(uint64_t) TMVirtualSyncGetWithDeadlineNoCheck(PVMCC pVM, uint64_t *pcNsToDeadline, + uint64_t *puDeadlineVersion, uint64_t *puTscNow) +{ + uint64_t cNsToDeadlineTmp; /* try convince the compiler to skip the if tests. */ + uint64_t u64Now = tmVirtualSyncGetEx(pVM, false /*fCheckTimers*/, &cNsToDeadlineTmp, puDeadlineVersion, puTscNow); + *pcNsToDeadline = cNsToDeadlineTmp; + return u64Now; +} + + +/** + * Gets the number of nano seconds to the next virtual sync deadline. + * + * @returns The number of TMCLOCK_VIRTUAL ticks. + * @param pVM The cross context VM structure. + * @param puTscNow Where to return the TSC value that the return + * value is relative to. This is delta adjusted. + * @param puDeadlineVersion Where to return the deadline "version" number. + * Use with TMVirtualSyncIsCurrentDeadlineVersion() + * to check if the absolute deadline is still up to + * date and the caller can skip calling this + * function. + * @thread EMT. + * @remarks May set the timer and virtual sync FFs. + */ +VMMDECL(uint64_t) TMVirtualSyncGetNsToDeadline(PVMCC pVM, uint64_t *puDeadlineVersion, uint64_t *puTscNow) +{ + uint64_t cNsToDeadline; + tmVirtualSyncGetEx(pVM, false /*fCheckTimers*/, &cNsToDeadline, puDeadlineVersion, puTscNow); + return cNsToDeadline; +} + + +/** + * Checks if the given deadline is still current. + * + * @retval true if the deadline is still current. + * @retval false if the deadline is outdated. + * @param pVM The cross context VM structure. + * @param uDeadlineVersion The deadline version to check. + */ +VMM_INT_DECL(bool) TMVirtualSyncIsCurrentDeadlineVersion(PVMCC pVM, uint64_t uDeadlineVersion) +{ + /** @todo Try use ASMAtomicUoReadU64 instead. */ + uint64_t u64Expire = ASMAtomicReadU64(&pVM->tm.s.aTimerQueues[TMCLOCK_VIRTUAL_SYNC].u64Expire); + return u64Expire == uDeadlineVersion; +} + + +/** + * Gets the current lag of the synchronous virtual clock (relative to the virtual clock). + * + * @return The current lag. + * @param pVM The cross context VM structure. + */ +VMM_INT_DECL(uint64_t) TMVirtualSyncGetLag(PVMCC pVM) +{ + return pVM->tm.s.offVirtualSync - pVM->tm.s.offVirtualSyncGivenUp; +} + + +/** + * Get the current catch-up percent. + * + * @return The current catch0up percent. 0 means running at the same speed as the virtual clock. + * @param pVM The cross context VM structure. + */ +VMM_INT_DECL(uint32_t) TMVirtualSyncGetCatchUpPct(PVMCC pVM) +{ + if (pVM->tm.s.fVirtualSyncCatchUp) + return pVM->tm.s.u32VirtualSyncCatchUpPercentage; + return 0; +} + + +/** + * Gets the current TMCLOCK_VIRTUAL frequency. + * + * @returns The frequency. + * @param pVM The cross context VM structure. + */ +VMM_INT_DECL(uint64_t) TMVirtualGetFreq(PVM pVM) +{ + NOREF(pVM); + return TMCLOCK_FREQ_VIRTUAL; +} + + +/** + * Worker for TMR3PauseClocks. + * + * @returns VINF_SUCCESS or VERR_TM_VIRTUAL_TICKING_IPE (asserted). + * @param pVM The cross context VM structure. + */ +int tmVirtualPauseLocked(PVMCC pVM) +{ + uint32_t c = ASMAtomicDecU32(&pVM->tm.s.cVirtualTicking); + AssertMsgReturn(c < pVM->cCpus, ("%u vs %u\n", c, pVM->cCpus), VERR_TM_VIRTUAL_TICKING_IPE); + if (c == 0) + { + STAM_COUNTER_INC(&pVM->tm.s.StatVirtualPause); + pVM->tm.s.u64Virtual = tmVirtualGetRaw(pVM); + ASMAtomicWriteBool(&pVM->tm.s.fVirtualSyncTicking, false); + } + return VINF_SUCCESS; +} + + +/** + * Worker for TMR3ResumeClocks. + * + * @returns VINF_SUCCESS or VERR_TM_VIRTUAL_TICKING_IPE (asserted). + * @param pVM The cross context VM structure. + */ +int tmVirtualResumeLocked(PVMCC pVM) +{ + uint32_t c = ASMAtomicIncU32(&pVM->tm.s.cVirtualTicking); + AssertMsgReturn(c <= pVM->cCpus, ("%u vs %u\n", c, pVM->cCpus), VERR_TM_VIRTUAL_TICKING_IPE); + if (c == 1) + { + STAM_COUNTER_INC(&pVM->tm.s.StatVirtualResume); + pVM->tm.s.u64VirtualRawPrev = 0; + pVM->tm.s.u64VirtualWarpDriveStart = tmVirtualGetRawNanoTS(pVM); + pVM->tm.s.u64VirtualOffset = pVM->tm.s.u64VirtualWarpDriveStart - pVM->tm.s.u64Virtual; + ASMAtomicWriteBool(&pVM->tm.s.fVirtualSyncTicking, true); + } + return VINF_SUCCESS; +} + + +/** + * Converts from virtual ticks to nanoseconds. + * + * @returns nanoseconds. + * @param pVM The cross context VM structure. + * @param u64VirtualTicks The virtual ticks to convert. + * @remark There could be rounding errors here. We just do a simple integer divide + * without any adjustments. + */ +VMM_INT_DECL(uint64_t) TMVirtualToNano(PVM pVM, uint64_t u64VirtualTicks) +{ + NOREF(pVM); + AssertCompile(TMCLOCK_FREQ_VIRTUAL == 1000000000); + return u64VirtualTicks; +} + + +/** + * Converts from virtual ticks to microseconds. + * + * @returns microseconds. + * @param pVM The cross context VM structure. + * @param u64VirtualTicks The virtual ticks to convert. + * @remark There could be rounding errors here. We just do a simple integer divide + * without any adjustments. + */ +VMM_INT_DECL(uint64_t) TMVirtualToMicro(PVM pVM, uint64_t u64VirtualTicks) +{ + NOREF(pVM); + AssertCompile(TMCLOCK_FREQ_VIRTUAL == 1000000000); + return u64VirtualTicks / 1000; +} + + +/** + * Converts from virtual ticks to milliseconds. + * + * @returns milliseconds. + * @param pVM The cross context VM structure. + * @param u64VirtualTicks The virtual ticks to convert. + * @remark There could be rounding errors here. We just do a simple integer divide + * without any adjustments. + */ +VMM_INT_DECL(uint64_t) TMVirtualToMilli(PVM pVM, uint64_t u64VirtualTicks) +{ + NOREF(pVM); + AssertCompile(TMCLOCK_FREQ_VIRTUAL == 1000000000); + return u64VirtualTicks / 1000000; +} + + +/** + * Converts from nanoseconds to virtual ticks. + * + * @returns virtual ticks. + * @param pVM The cross context VM structure. + * @param u64NanoTS The nanosecond value ticks to convert. + * @remark There could be rounding and overflow errors here. + */ +VMM_INT_DECL(uint64_t) TMVirtualFromNano(PVM pVM, uint64_t u64NanoTS) +{ + NOREF(pVM); + AssertCompile(TMCLOCK_FREQ_VIRTUAL == 1000000000); + return u64NanoTS; +} + + +/** + * Converts from microseconds to virtual ticks. + * + * @returns virtual ticks. + * @param pVM The cross context VM structure. + * @param u64MicroTS The microsecond value ticks to convert. + * @remark There could be rounding and overflow errors here. + */ +VMM_INT_DECL(uint64_t) TMVirtualFromMicro(PVM pVM, uint64_t u64MicroTS) +{ + NOREF(pVM); + AssertCompile(TMCLOCK_FREQ_VIRTUAL == 1000000000); + return u64MicroTS * 1000; +} + + +/** + * Converts from milliseconds to virtual ticks. + * + * @returns virtual ticks. + * @param pVM The cross context VM structure. + * @param u64MilliTS The millisecond value ticks to convert. + * @remark There could be rounding and overflow errors here. + */ +VMM_INT_DECL(uint64_t) TMVirtualFromMilli(PVM pVM, uint64_t u64MilliTS) +{ + NOREF(pVM); + AssertCompile(TMCLOCK_FREQ_VIRTUAL == 1000000000); + return u64MilliTS * 1000000; +} + |