/* $Id: TMAll.cpp 4917 2007-09-20 10:06:48Z vboxsync $ */ /** @file * TM - Timeout Manager, all contexts. */ /* * Copyright (C) 2006-2007 innotek GmbH * * 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 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 * *******************************************************************************/ #define LOG_GROUP LOG_GROUP_TM #include #include #ifdef IN_RING3 # include #endif #include "TMInternal.h" #include #include #include #include #include #include #include #include #ifdef IN_RING3 # include #endif /** * Schedule the queue which was changed. */ DECLINLINE(void) tmSchedule(PTMTIMER pTimer) { PVM pVM = pTimer->CTXALLSUFF(pVM); if (VM_IS_EMT(pVM)) { STAM_PROFILE_START(&pVM->tm.s.CTXALLSUFF(StatScheduleOne), a); PTMTIMERQUEUE pQueue = &pVM->tm.s.CTXALLSUFF(paTimerQueues)[pTimer->enmClock]; Log3(("tmSchedule: tmTimerQueueSchedule\n")); tmTimerQueueSchedule(pVM, pQueue); #ifdef VBOX_STRICT tmTimerQueuesSanityChecks(pVM, "tmSchedule"); #endif STAM_PROFILE_STOP(&pVM->tm.s.CTXALLSUFF(StatScheduleOne), a); } else if (!VM_FF_ISSET(pVM, VM_FF_TIMER)) /**@todo only do this when arming the timer. */ { STAM_COUNTER_INC(&pVM->tm.s.StatScheduleSetFF); VM_FF_SET(pVM, VM_FF_TIMER); #ifdef IN_RING3 REMR3NotifyTimerPending(pVM); VMR3NotifyFF(pVM, true); #endif } } /** * Try change the state to enmStateNew from enmStateOld * and link the timer into the scheduling queue. * * @returns Success indicator. * @param pTimer Timer in question. * @param enmStateNew The new timer state. * @param enmStateOld The old timer state. */ DECLINLINE(bool) tmTimerTry(PTMTIMER pTimer, TMTIMERSTATE enmStateNew, TMTIMERSTATE enmStateOld) { /* * Attempt state change. */ bool fRc; TM_TRY_SET_STATE(pTimer, enmStateNew, enmStateOld, fRc); return fRc; } /** * Links the timer onto the scheduling queue. * * @param pQueue The timer queue the timer belongs to. * @param pTimer The timer. */ DECLINLINE(void) tmTimerLink(PTMTIMERQUEUE pQueue, PTMTIMER pTimer) { Assert(!pTimer->offScheduleNext); const int32_t offHeadNew = (intptr_t)pTimer - (intptr_t)pQueue; int32_t offHead; do { offHead = pQueue->offSchedule; if (offHead) pTimer->offScheduleNext = ((intptr_t)pQueue + offHead) - (intptr_t)pTimer; else pTimer->offScheduleNext = 0; } while (!ASMAtomicCmpXchgS32(&pQueue->offSchedule, offHeadNew, offHead)); } /** * Try change the state to enmStateNew from enmStateOld * and link the timer into the scheduling queue. * * @returns Success indicator. * @param pTimer Timer in question. * @param enmStateNew The new timer state. * @param enmStateOld The old timer state. */ DECLINLINE(bool) tmTimerTryWithLink(PTMTIMER pTimer, TMTIMERSTATE enmStateNew, TMTIMERSTATE enmStateOld) { if (tmTimerTry(pTimer, enmStateNew, enmStateOld)) { tmTimerLink(&pTimer->CTXALLSUFF(pVM)->tm.s.CTXALLSUFF(paTimerQueues)[pTimer->enmClock], pTimer); return true; } return false; } #ifdef VBOX_HIGH_RES_TIMERS_HACK /** * Set FF if we've passed the next virtual event. * * This function is called before FFs are checked in the inner execution EM loops. * * @returns Virtual timer ticks to the next event. * @thread The emulation thread. */ TMDECL(uint64_t) TMTimerPoll(PVM pVM) { /* * Return straight away if the timer FF is already set. */ if (VM_FF_ISSET(pVM, VM_FF_TIMER)) { STAM_COUNTER_INC(&pVM->tm.s.StatPollAlreadySet); return 0; } /* * Get current time and check the expire times of the two relevant queues. */ const uint64_t u64Now = TMVirtualGet(pVM); /* * TMCLOCK_VIRTUAL */ const uint64_t u64Expire1 = pVM->tm.s.CTXALLSUFF(paTimerQueues)[TMCLOCK_VIRTUAL].u64Expire; const int64_t i64Delta1 = u64Expire1 - u64Now; if (i64Delta1 <= 0) { LogFlow(("TMTimerPoll: expire1=%RU64 <= now=%RU64\n", u64Expire1, u64Now)); STAM_COUNTER_INC(&pVM->tm.s.StatPollVirtual); VM_FF_SET(pVM, VM_FF_TIMER); #ifdef IN_RING3 REMR3NotifyTimerPending(pVM); #endif return 0; } /* * TMCLOCK_VIRTUAL_SYNC * This isn't quite as stright forward if in a catch-up, not only do * we have to adjust the 'now' but when have to adjust the delta as well. */ const uint64_t u64Expire2 = pVM->tm.s.CTXALLSUFF(paTimerQueues)[TMCLOCK_VIRTUAL_SYNC].u64Expire; uint64_t u64VirtualSyncNow; if (!pVM->tm.s.fVirtualSyncTicking) u64VirtualSyncNow = pVM->tm.s.u64VirtualSync; else { if (!pVM->tm.s.fVirtualSyncCatchUp) u64VirtualSyncNow = u64Now - pVM->tm.s.offVirtualSync; else { uint64_t off = pVM->tm.s.offVirtualSync; uint64_t u64Delta = u64Now - pVM->tm.s.u64VirtualSyncCatchUpPrev; if (RT_LIKELY(!(u64Delta >> 32))) { uint64_t u64Sub = ASMMultU64ByU32DivByU32(u64Delta, pVM->tm.s.u32VirtualSyncCatchUpPercentage, 100); if (off > u64Sub + pVM->tm.s.offVirtualSyncGivenUp) off -= u64Sub; else off = pVM->tm.s.offVirtualSyncGivenUp; } u64VirtualSyncNow = u64Now - off; } } int64_t i64Delta2 = u64Expire2 - u64VirtualSyncNow; if (i64Delta2 <= 0) { LogFlow(("TMTimerPoll: expire2=%RU64 <= now=%RU64\n", u64Expire2, u64Now)); STAM_COUNTER_INC(&pVM->tm.s.StatPollVirtualSync); VM_FF_SET(pVM, VM_FF_TIMER); #ifdef IN_RING3 REMR3NotifyTimerPending(pVM); #endif return 0; } if (pVM->tm.s.fVirtualSyncCatchUp) i64Delta2 = ASMMultU64ByU32DivByU32(i64Delta2, 100, pVM->tm.s.u32VirtualSyncCatchUpPercentage + 100); /* * Return the time left to the next event. */ STAM_COUNTER_INC(&pVM->tm.s.StatPollMiss); return RT_MIN(i64Delta1, i64Delta2); } #endif /** * Gets the host context ring-3 pointer of the timer. * * @returns HC R3 pointer. * @param pTimer Timer handle as returned by one of the create functions. */ TMDECL(PTMTIMERR3) TMTimerR3Ptr(PTMTIMER pTimer) { return (PTMTIMERR3)MMHyperCCToR3(pTimer->CTXALLSUFF(pVM), pTimer); } /** * Gets the host context ring-0 pointer of the timer. * * @returns HC R0 pointer. * @param pTimer Timer handle as returned by one of the create functions. */ TMDECL(PTMTIMERR0) TMTimerR0Ptr(PTMTIMER pTimer) { return (PTMTIMERR0)MMHyperCCToR0(pTimer->CTXALLSUFF(pVM), pTimer); } /** * Gets the GC pointer of the timer. * * @returns GC pointer. * @param pTimer Timer handle as returned by one of the create functions. */ TMDECL(PTMTIMERGC) TMTimerGCPtr(PTMTIMER pTimer) { return (PTMTIMERGC)MMHyperCCToGC(pTimer->CTXALLSUFF(pVM), pTimer); } /** * Destroy a timer * * @returns VBox status. * @param pTimer Timer handle as returned by one of the create functions. */ TMDECL(int) TMTimerDestroy(PTMTIMER pTimer) { int cRetries = 1000; do { /* * Change to any of the DESTROY states if valid. */ TMTIMERSTATE enmState = pTimer->enmState; Log2(("TMTimerDestroy: pTimer=%p:{.enmState=%s, .pszDesc='%s'} cRetries=%d\n", pTimer, tmTimerState(enmState), HCSTRING(pTimer->pszDesc), cRetries)); switch (enmState) { case TMTIMERSTATE_EXPIRED: if (!VM_IS_EMT(pTimer->CTXALLSUFF(pVM))) { AssertMsgFailed(("Attempted timer destruction from other thread while expire pending! (%s)\n", HCSTRING(pTimer->pszDesc))); return VERR_INVALID_PARAMETER; } /* fall thru */ case TMTIMERSTATE_STOPPED: if (tmTimerTryWithLink(pTimer, TMTIMERSTATE_PENDING_DESTROY, enmState)) { tmSchedule(pTimer); return VINF_SUCCESS; } break; case TMTIMERSTATE_ACTIVE: if (tmTimerTryWithLink(pTimer, TMTIMERSTATE_PENDING_STOP_DESTROY, enmState)) { tmSchedule(pTimer); return VINF_SUCCESS; } break; case TMTIMERSTATE_PENDING_STOP: case TMTIMERSTATE_PENDING_STOP_SCHEDULE: if (tmTimerTry(pTimer, TMTIMERSTATE_PENDING_STOP_DESTROY, enmState)) { tmSchedule(pTimer); return VINF_SUCCESS; } break; case TMTIMERSTATE_PENDING_DESTROY: case TMTIMERSTATE_PENDING_STOP_DESTROY: AssertMsgFailed(("How many times do you think you can destroy the same timer... (%s)\n", HCSTRING(pTimer->pszDesc))); return VERR_INVALID_PARAMETER; case TMTIMERSTATE_PENDING_RESCHEDULE: if (tmTimerTry(pTimer, TMTIMERSTATE_PENDING_STOP_DESTROY, enmState)) { tmSchedule(pTimer); return VINF_SUCCESS; } break; case TMTIMERSTATE_PENDING_SCHEDULE: if (tmTimerTry(pTimer, TMTIMERSTATE_PENDING_DESTROY, enmState)) { tmSchedule(pTimer); return VINF_SUCCESS; } break; case TMTIMERSTATE_PENDING_SCHEDULE_SET_EXPIRE: case TMTIMERSTATE_PENDING_RESCHEDULE_SET_EXPIRE: #ifdef IN_RING3 if (!RTThreadYield()) RTThreadSleep(1); #endif break; /* * Invalid states. */ case TMTIMERSTATE_FREE: AssertMsgFailed(("Invalid timer state %d (%s)\n", enmState, HCSTRING(pTimer->pszDesc))); return VERR_TM_INVALID_STATE; default: AssertMsgFailed(("Unknown timer state %d (%s)\n", enmState, HCSTRING(pTimer->pszDesc))); return VERR_TM_UNKNOWN_STATE; } } while (cRetries-- > 0); AssertMsgFailed(("Failed waiting for stable state. state=%d (%s)\n", pTimer->enmState, HCSTRING(pTimer->pszDesc))); return VERR_INTERNAL_ERROR; } /** * Arm a timer with a (new) expire time. * * @returns VBox status. * @param pTimer Timer handle as returned by one of the create functions. * @param u64Expire New expire time. */ TMDECL(int) TMTimerSet(PTMTIMER pTimer, uint64_t u64Expire) { STAM_PROFILE_START(&pTimer->CTXALLSUFF(pVM)->tm.s.CTXALLSUFF(StatTimerSet), a); /** @todo find the most frequently used paths and make them skip tmSchedule and tmTimerTryWithLink. */ int cRetries = 1000; do { /* * Change to any of the SET_EXPIRE states if valid and then to SCHEDULE or RESCHEDULE. */ TMTIMERSTATE enmState = pTimer->enmState; Log2(("TMTimerSet: pTimer=%p:{.enmState=%s, .pszDesc='%s'} cRetries=%d u64Expire=%llu\n", pTimer, tmTimerState(enmState), HCSTRING(pTimer->pszDesc), cRetries, u64Expire)); switch (enmState) { case TMTIMERSTATE_EXPIRED: case TMTIMERSTATE_STOPPED: if (tmTimerTryWithLink(pTimer, TMTIMERSTATE_PENDING_SCHEDULE_SET_EXPIRE, enmState)) { Assert(!pTimer->offPrev); Assert(!pTimer->offNext); AssertMsg( pTimer->enmClock != TMCLOCK_VIRTUAL_SYNC || pTimer->CTXALLSUFF(pVM)->tm.s.fVirtualSyncTicking || u64Expire >= pTimer->CTXALLSUFF(pVM)->tm.s.u64VirtualSync, ("%RU64 < %RU64 %s\n", u64Expire, pTimer->CTXALLSUFF(pVM)->tm.s.u64VirtualSync, R3STRING(pTimer->pszDesc))); pTimer->u64Expire = u64Expire; TM_SET_STATE(pTimer, TMTIMERSTATE_PENDING_SCHEDULE); tmSchedule(pTimer); STAM_PROFILE_STOP(&pTimer->CTXALLSUFF(pVM)->tm.s.CTXALLSUFF(StatTimerSet), a); return VINF_SUCCESS; } break; case TMTIMERSTATE_PENDING_SCHEDULE: case TMTIMERSTATE_PENDING_STOP_SCHEDULE: if (tmTimerTry(pTimer, TMTIMERSTATE_PENDING_SCHEDULE_SET_EXPIRE, enmState)) { Assert(!pTimer->offPrev); Assert(!pTimer->offNext); pTimer->u64Expire = u64Expire; TM_SET_STATE(pTimer, TMTIMERSTATE_PENDING_SCHEDULE); tmSchedule(pTimer); STAM_PROFILE_STOP(&pTimer->CTXALLSUFF(pVM)->tm.s.CTXALLSUFF(StatTimerSet), a); return VINF_SUCCESS; } break; case TMTIMERSTATE_ACTIVE: if (tmTimerTryWithLink(pTimer, TMTIMERSTATE_PENDING_RESCHEDULE_SET_EXPIRE, enmState)) { pTimer->u64Expire = u64Expire; TM_SET_STATE(pTimer, TMTIMERSTATE_PENDING_RESCHEDULE); tmSchedule(pTimer); STAM_PROFILE_STOP(&pTimer->CTXALLSUFF(pVM)->tm.s.CTXALLSUFF(StatTimerSet), a); return VINF_SUCCESS; } break; case TMTIMERSTATE_PENDING_RESCHEDULE: case TMTIMERSTATE_PENDING_STOP: if (tmTimerTry(pTimer, TMTIMERSTATE_PENDING_RESCHEDULE_SET_EXPIRE, enmState)) { pTimer->u64Expire = u64Expire; TM_SET_STATE(pTimer, TMTIMERSTATE_PENDING_RESCHEDULE); tmSchedule(pTimer); STAM_PROFILE_STOP(&pTimer->CTXALLSUFF(pVM)->tm.s.CTXALLSUFF(StatTimerSet), a); return VINF_SUCCESS; } break; case TMTIMERSTATE_PENDING_SCHEDULE_SET_EXPIRE: case TMTIMERSTATE_PENDING_RESCHEDULE_SET_EXPIRE: #ifdef IN_RING3 if (!RTThreadYield()) RTThreadSleep(1); #else /** @todo call host context and yield after a couple of iterations */ #endif break; /* * Invalid states. */ case TMTIMERSTATE_PENDING_DESTROY: case TMTIMERSTATE_PENDING_STOP_DESTROY: case TMTIMERSTATE_FREE: AssertMsgFailed(("Invalid timer state %d (%s)\n", enmState, HCSTRING(pTimer->pszDesc))); return VERR_TM_INVALID_STATE; default: AssertMsgFailed(("Unknown timer state %d (%s)\n", enmState, HCSTRING(pTimer->pszDesc))); return VERR_TM_UNKNOWN_STATE; } } while (cRetries-- > 0); AssertMsgFailed(("Failed waiting for stable state. state=%d (%s)\n", pTimer->enmState, HCSTRING(pTimer->pszDesc))); STAM_PROFILE_STOP(&pTimer->CTXALLSUFF(pVM)->tm.s.CTXALLSUFF(StatTimerSet), a); return VERR_INTERNAL_ERROR; } /** * Arm a timer with a (new) expire time relative to current clock. * * @returns VBox status. * @param pTimer Timer handle as returned by one of the create functions. * @param cMilliesToNext Number of millieseconds to the next tick. */ TMDECL(int) TMTimerSetMillies(PTMTIMER pTimer, uint32_t cMilliesToNext) { PVM pVM = pTimer->CTXALLSUFF(pVM); switch (pTimer->enmClock) { case TMCLOCK_VIRTUAL: return TMTimerSet(pTimer, cMilliesToNext * (uint64_t)TMCLOCK_FREQ_VIRTUAL / 1000 + TMVirtualGet(pVM)); case TMCLOCK_VIRTUAL_SYNC: return TMTimerSet(pTimer, cMilliesToNext * (uint64_t)TMCLOCK_FREQ_VIRTUAL / 1000 + TMVirtualSyncGet(pVM)); case TMCLOCK_REAL: AssertCompile(TMCLOCK_FREQ_REAL == 1000); return TMTimerSet(pTimer, cMilliesToNext + TMRealGet(pVM)); case TMCLOCK_TSC: return TMTimerSet(pTimer, cMilliesToNext * pVM->tm.s.cTSCTicksPerSecond / 1000 + TMCpuTickGet(pVM)); default: AssertMsgFailed(("Invalid enmClock=%d\n", pTimer->enmClock)); return VERR_INTERNAL_ERROR; } } /** * Stop the timer. * Use TMR3TimerArm() to "un-stop" the timer. * * @returns VBox status. * @param pTimer Timer handle as returned by one of the create functions. */ TMDECL(int) TMTimerStop(PTMTIMER pTimer) { STAM_PROFILE_START(&pTimer->CTXALLSUFF(pVM)->tm.s.CTXALLSUFF(StatTimerStop), a); /** @todo see if this function needs optimizing. */ int cRetries = 1000; do { /* * Change to any of the SET_EXPIRE states if valid and then to SCHEDULE or RESCHEDULE. */ TMTIMERSTATE enmState = pTimer->enmState; Log2(("TMTimerStop: pTimer=%p:{.enmState=%s, .pszDesc='%s'} cRetries=%d\n", pTimer, tmTimerState(enmState), HCSTRING(pTimer->pszDesc), cRetries)); switch (enmState) { case TMTIMERSTATE_EXPIRED: //AssertMsgFailed(("You don't stop an expired timer dude!\n")); return VERR_INVALID_PARAMETER; case TMTIMERSTATE_STOPPED: case TMTIMERSTATE_PENDING_STOP: case TMTIMERSTATE_PENDING_STOP_SCHEDULE: STAM_PROFILE_STOP(&pTimer->CTXALLSUFF(pVM)->tm.s.CTXALLSUFF(StatTimerStop), a); return VINF_SUCCESS; case TMTIMERSTATE_PENDING_SCHEDULE: if (tmTimerTry(pTimer, TMTIMERSTATE_PENDING_STOP_SCHEDULE, enmState)) { Assert(!pTimer->offPrev); Assert(!pTimer->offNext); tmSchedule(pTimer); STAM_PROFILE_STOP(&pTimer->CTXALLSUFF(pVM)->tm.s.CTXALLSUFF(StatTimerStop), a); return VINF_SUCCESS; } case TMTIMERSTATE_PENDING_RESCHEDULE: if (tmTimerTry(pTimer, TMTIMERSTATE_PENDING_STOP, enmState)) { tmSchedule(pTimer); STAM_PROFILE_STOP(&pTimer->CTXALLSUFF(pVM)->tm.s.CTXALLSUFF(StatTimerStop), a); return VINF_SUCCESS; } break; case TMTIMERSTATE_ACTIVE: if (tmTimerTryWithLink(pTimer, TMTIMERSTATE_PENDING_STOP, enmState)) { tmSchedule(pTimer); STAM_PROFILE_STOP(&pTimer->CTXALLSUFF(pVM)->tm.s.CTXALLSUFF(StatTimerStop), a); return VINF_SUCCESS; } break; case TMTIMERSTATE_PENDING_SCHEDULE_SET_EXPIRE: case TMTIMERSTATE_PENDING_RESCHEDULE_SET_EXPIRE: #ifdef IN_RING3 if (!RTThreadYield()) RTThreadSleep(1); #else /**@todo call host and yield cpu after a while. */ #endif break; /* * Invalid states. */ case TMTIMERSTATE_PENDING_DESTROY: case TMTIMERSTATE_PENDING_STOP_DESTROY: case TMTIMERSTATE_FREE: AssertMsgFailed(("Invalid timer state %d (%s)\n", enmState, HCSTRING(pTimer->pszDesc))); return VERR_TM_INVALID_STATE; default: AssertMsgFailed(("Unknown timer state %d (%s)\n", enmState, HCSTRING(pTimer->pszDesc))); return VERR_TM_UNKNOWN_STATE; } } while (cRetries-- > 0); AssertMsgFailed(("Failed waiting for stable state. state=%d (%s)\n", pTimer->enmState, HCSTRING(pTimer->pszDesc))); STAM_PROFILE_STOP(&pTimer->CTXALLSUFF(pVM)->tm.s.CTXALLSUFF(StatTimerStop), a); return VERR_INTERNAL_ERROR; } /** * Get the current clock time. * Handy for calculating the new expire time. * * @returns Current clock time. * @param pTimer Timer handle as returned by one of the create functions. */ TMDECL(uint64_t) TMTimerGet(PTMTIMER pTimer) { uint64_t u64; PVM pVM = pTimer->CTXALLSUFF(pVM); switch (pTimer->enmClock) { case TMCLOCK_VIRTUAL: u64 = TMVirtualGet(pVM); break; case TMCLOCK_VIRTUAL_SYNC: u64 = TMVirtualSyncGet(pVM); break; case TMCLOCK_REAL: u64 = TMRealGet(pVM); break; case TMCLOCK_TSC: u64 = TMCpuTickGet(pVM); break; default: AssertMsgFailed(("Invalid enmClock=%d\n", pTimer->enmClock)); return ~(uint64_t)0; } //Log2(("TMTimerGet: returns %llu (pTimer=%p:{.enmState=%s, .pszDesc='%s'})\n", // u64, pTimer, tmTimerState(pTimer->enmState), HCSTRING(pTimer->pszDesc))); return u64; } /** * Get the freqency of the timer clock. * * @returns Clock frequency (as Hz of course). * @param pTimer Timer handle as returned by one of the create functions. */ TMDECL(uint64_t) TMTimerGetFreq(PTMTIMER pTimer) { switch (pTimer->enmClock) { case TMCLOCK_VIRTUAL: case TMCLOCK_VIRTUAL_SYNC: return TMCLOCK_FREQ_VIRTUAL; case TMCLOCK_REAL: return TMCLOCK_FREQ_REAL; case TMCLOCK_TSC: return TMCpuTicksPerSecond(pTimer->CTXALLSUFF(pVM)); default: AssertMsgFailed(("Invalid enmClock=%d\n", pTimer->enmClock)); return 0; } } /** * Get the current clock time as nanoseconds. * * @returns The timer clock as nanoseconds. * @param pTimer Timer handle as returned by one of the create functions. */ TMDECL(uint64_t) TMTimerGetNano(PTMTIMER pTimer) { return TMTimerToNano(pTimer, TMTimerGet(pTimer)); } /** * Get the current clock time as microseconds. * * @returns The timer clock as microseconds. * @param pTimer Timer handle as returned by one of the create functions. */ TMDECL(uint64_t) TMTimerGetMicro(PTMTIMER pTimer) { return TMTimerToMicro(pTimer, TMTimerGet(pTimer)); } /** * Get the current clock time as milliseconds. * * @returns The timer clock as milliseconds. * @param pTimer Timer handle as returned by one of the create functions. */ TMDECL(uint64_t) TMTimerGetMilli(PTMTIMER pTimer) { return TMTimerToMilli(pTimer, TMTimerGet(pTimer)); } /** * Converts the specified timer clock time to nanoseconds. * * @returns nanoseconds. * @param pTimer Timer handle as returned by one of the create functions. * @param u64Ticks The clock ticks. * @remark There could be rounding errors here. We just do a simple integere divide * without any adjustments. */ TMDECL(uint64_t) TMTimerToNano(PTMTIMER pTimer, uint64_t u64Ticks) { PVM pVM = pTimer->CTXALLSUFF(pVM); NOREF(pVM); switch (pTimer->enmClock) { case TMCLOCK_VIRTUAL: case TMCLOCK_VIRTUAL_SYNC: AssertCompile(TMCLOCK_FREQ_VIRTUAL == 1000000000); return u64Ticks; case TMCLOCK_REAL: AssertCompile(TMCLOCK_FREQ_REAL == 1000); return u64Ticks * 1000000; case TMCLOCK_TSC: AssertReleaseMsgFailed(("TMCLOCK_TSC conversions are not implemented\n")); return 0; default: AssertMsgFailed(("Invalid enmClock=%d\n", pTimer->enmClock)); return 0; } } /** * Converts the specified timer clock time to microseconds. * * @returns microseconds. * @param pTimer Timer handle as returned by one of the create functions. * @param u64Ticks The clock ticks. * @remark There could be rounding errors here. We just do a simple integere divide * without any adjustments. */ TMDECL(uint64_t) TMTimerToMicro(PTMTIMER pTimer, uint64_t u64Ticks) { PVM pVM = pTimer->CTXALLSUFF(pVM); NOREF(pVM); switch (pTimer->enmClock) { case TMCLOCK_VIRTUAL: case TMCLOCK_VIRTUAL_SYNC: AssertCompile(TMCLOCK_FREQ_VIRTUAL == 1000000000); return u64Ticks / 1000; case TMCLOCK_REAL: AssertCompile(TMCLOCK_FREQ_REAL == 1000); return u64Ticks * 1000; case TMCLOCK_TSC: AssertReleaseMsgFailed(("TMCLOCK_TSC conversions are not implemented\n")); return 0; default: AssertMsgFailed(("Invalid enmClock=%d\n", pTimer->enmClock)); return 0; } } /** * Converts the specified timer clock time to milliseconds. * * @returns milliseconds. * @param pTimer Timer handle as returned by one of the create functions. * @param u64Ticks The clock ticks. * @remark There could be rounding errors here. We just do a simple integere divide * without any adjustments. */ TMDECL(uint64_t) TMTimerToMilli(PTMTIMER pTimer, uint64_t u64Ticks) { PVM pVM = pTimer->CTXALLSUFF(pVM); NOREF(pVM); switch (pTimer->enmClock) { case TMCLOCK_VIRTUAL: case TMCLOCK_VIRTUAL_SYNC: AssertCompile(TMCLOCK_FREQ_VIRTUAL == 1000000000); return u64Ticks / 1000000; case TMCLOCK_REAL: AssertCompile(TMCLOCK_FREQ_REAL == 1000); return u64Ticks; case TMCLOCK_TSC: AssertReleaseMsgFailed(("TMCLOCK_TSC conversions are not implemented\n")); return 0; default: AssertMsgFailed(("Invalid enmClock=%d\n", pTimer->enmClock)); return 0; } } /** * Converts the specified nanosecond timestamp to timer clock ticks. * * @returns timer clock ticks. * @param pTimer Timer handle as returned by one of the create functions. * @param u64NanoTS The nanosecond value ticks to convert. * @remark There could be rounding and overflow errors here. */ TMDECL(uint64_t) TMTimerFromNano(PTMTIMER pTimer, uint64_t u64NanoTS) { PVM pVM = pTimer->CTXALLSUFF(pVM); NOREF(pVM); switch (pTimer->enmClock) { case TMCLOCK_VIRTUAL: case TMCLOCK_VIRTUAL_SYNC: AssertCompile(TMCLOCK_FREQ_VIRTUAL == 1000000000); return u64NanoTS; case TMCLOCK_REAL: AssertCompile(TMCLOCK_FREQ_REAL == 1000); return u64NanoTS / 1000000; case TMCLOCK_TSC: AssertReleaseMsgFailed(("TMCLOCK_TSC conversions are not implemented\n")); return 0; default: AssertMsgFailed(("Invalid enmClock=%d\n", pTimer->enmClock)); return 0; } } /** * Converts the specified microsecond timestamp to timer clock ticks. * * @returns timer clock ticks. * @param pTimer Timer handle as returned by one of the create functions. * @param u64MicroTS The microsecond value ticks to convert. * @remark There could be rounding and overflow errors here. */ TMDECL(uint64_t) TMTimerFromMicro(PTMTIMER pTimer, uint64_t u64MicroTS) { PVM pVM = pTimer->CTXALLSUFF(pVM); NOREF(pVM); switch (pTimer->enmClock) { case TMCLOCK_VIRTUAL: case TMCLOCK_VIRTUAL_SYNC: AssertCompile(TMCLOCK_FREQ_VIRTUAL == 1000000000); return u64MicroTS * 1000; case TMCLOCK_REAL: AssertCompile(TMCLOCK_FREQ_REAL == 1000); return u64MicroTS / 1000; case TMCLOCK_TSC: AssertReleaseMsgFailed(("TMCLOCK_TSC conversions are not implemented\n")); return 0; default: AssertMsgFailed(("Invalid enmClock=%d\n", pTimer->enmClock)); return 0; } } /** * Converts the specified millisecond timestamp to timer clock ticks. * * @returns timer clock ticks. * @param pTimer Timer handle as returned by one of the create functions. * @param u64MilliTS The millisecond value ticks to convert. * @remark There could be rounding and overflow errors here. */ TMDECL(uint64_t) TMTimerFromMilli(PTMTIMER pTimer, uint64_t u64MilliTS) { PVM pVM = pTimer->CTXALLSUFF(pVM); NOREF(pVM); switch (pTimer->enmClock) { case TMCLOCK_VIRTUAL: case TMCLOCK_VIRTUAL_SYNC: AssertCompile(TMCLOCK_FREQ_VIRTUAL == 1000000000); return u64MilliTS * 1000000; case TMCLOCK_REAL: AssertCompile(TMCLOCK_FREQ_REAL == 1000); return u64MilliTS; case TMCLOCK_TSC: AssertReleaseMsgFailed(("TMCLOCK_TSC conversions are not implemented\n")); return 0; default: AssertMsgFailed(("Invalid enmClock=%d\n", pTimer->enmClock)); return 0; } } /** * Get the expire time of the timer. * Only valid for active timers. * * @returns Expire time of the timer. * @param pTimer Timer handle as returned by one of the create functions. */ TMDECL(uint64_t) TMTimerGetExpire(PTMTIMER pTimer) { int cRetries = 1000; do { TMTIMERSTATE enmState = pTimer->enmState; switch (enmState) { case TMTIMERSTATE_EXPIRED: case TMTIMERSTATE_STOPPED: case TMTIMERSTATE_PENDING_STOP: case TMTIMERSTATE_PENDING_STOP_SCHEDULE: Log2(("TMTimerGetExpire: returns ~0 (pTimer=%p:{.enmState=%s, .pszDesc='%s'})\n", pTimer, tmTimerState(pTimer->enmState), HCSTRING(pTimer->pszDesc))); return ~(uint64_t)0; case TMTIMERSTATE_ACTIVE: case TMTIMERSTATE_PENDING_RESCHEDULE: case TMTIMERSTATE_PENDING_SCHEDULE: Log2(("TMTimerGetExpire: returns %llu (pTimer=%p:{.enmState=%s, .pszDesc='%s'})\n", pTimer->u64Expire, pTimer, tmTimerState(pTimer->enmState), HCSTRING(pTimer->pszDesc))); return pTimer->u64Expire; case TMTIMERSTATE_PENDING_SCHEDULE_SET_EXPIRE: case TMTIMERSTATE_PENDING_RESCHEDULE_SET_EXPIRE: #ifdef IN_RING3 if (!RTThreadYield()) RTThreadSleep(1); #endif break; /* * Invalid states. */ case TMTIMERSTATE_PENDING_DESTROY: case TMTIMERSTATE_PENDING_STOP_DESTROY: case TMTIMERSTATE_FREE: AssertMsgFailed(("Invalid timer state %d (%s)\n", enmState, HCSTRING(pTimer->pszDesc))); Log2(("TMTimerGetExpire: returns ~0 (pTimer=%p:{.enmState=%s, .pszDesc='%s'})\n", pTimer, tmTimerState(pTimer->enmState), HCSTRING(pTimer->pszDesc))); return ~(uint64_t)0; default: AssertMsgFailed(("Unknown timer state %d (%s)\n", enmState, HCSTRING(pTimer->pszDesc))); return ~(uint64_t)0; } } while (cRetries-- > 0); AssertMsgFailed(("Failed waiting for stable state. state=%d (%s)\n", pTimer->enmState, HCSTRING(pTimer->pszDesc))); Log2(("TMTimerGetExpire: returns ~0 (pTimer=%p:{.enmState=%s, .pszDesc='%s'})\n", pTimer, tmTimerState(pTimer->enmState), HCSTRING(pTimer->pszDesc))); return ~(uint64_t)0; } /** * Checks if a timer is active or not. * * @returns True if active. * @returns False if not active. * @param pTimer Timer handle as returned by one of the create functions. */ TMDECL(bool) TMTimerIsActive(PTMTIMER pTimer) { TMTIMERSTATE enmState = pTimer->enmState; switch (enmState) { case TMTIMERSTATE_STOPPED: case TMTIMERSTATE_EXPIRED: case TMTIMERSTATE_PENDING_STOP: case TMTIMERSTATE_PENDING_STOP_SCHEDULE: Log2(("TMTimerIsActive: returns false (pTimer=%p:{.enmState=%s, .pszDesc='%s'})\n", pTimer, tmTimerState(pTimer->enmState), HCSTRING(pTimer->pszDesc))); return false; case TMTIMERSTATE_ACTIVE: case TMTIMERSTATE_PENDING_RESCHEDULE: case TMTIMERSTATE_PENDING_SCHEDULE: case TMTIMERSTATE_PENDING_SCHEDULE_SET_EXPIRE: case TMTIMERSTATE_PENDING_RESCHEDULE_SET_EXPIRE: Log2(("TMTimerIsActive: returns true (pTimer=%p:{.enmState=%s, .pszDesc='%s'})\n", pTimer, tmTimerState(pTimer->enmState), HCSTRING(pTimer->pszDesc))); return true; /* * Invalid states. */ case TMTIMERSTATE_PENDING_DESTROY: case TMTIMERSTATE_PENDING_STOP_DESTROY: case TMTIMERSTATE_FREE: AssertMsgFailed(("Invalid timer state %s (%s)\n", tmTimerState(enmState), HCSTRING(pTimer->pszDesc))); Log2(("TMTimerIsActive: returns false (pTimer=%p:{.enmState=%s, .pszDesc='%s'})\n", pTimer, tmTimerState(pTimer->enmState), HCSTRING(pTimer->pszDesc))); return false; default: AssertMsgFailed(("Unknown timer state %d (%s)\n", enmState, HCSTRING(pTimer->pszDesc))); return false; } } /** * Convert state to string. * * @returns Readonly status name. * @param enmState State. */ const char *tmTimerState(TMTIMERSTATE enmState) { switch (enmState) { #define CASE(state) case state: return #state + sizeof("TMTIMERSTATE_") - 1 CASE(TMTIMERSTATE_STOPPED); CASE(TMTIMERSTATE_ACTIVE); CASE(TMTIMERSTATE_EXPIRED); CASE(TMTIMERSTATE_PENDING_STOP); CASE(TMTIMERSTATE_PENDING_STOP_SCHEDULE); CASE(TMTIMERSTATE_PENDING_SCHEDULE_SET_EXPIRE); CASE(TMTIMERSTATE_PENDING_SCHEDULE); CASE(TMTIMERSTATE_PENDING_RESCHEDULE_SET_EXPIRE); CASE(TMTIMERSTATE_PENDING_RESCHEDULE); CASE(TMTIMERSTATE_PENDING_STOP_DESTROY); CASE(TMTIMERSTATE_PENDING_DESTROY); CASE(TMTIMERSTATE_FREE); default: AssertMsgFailed(("Invalid state enmState=%d\n", enmState)); return "Invalid state!"; #undef CASE } } /** * Schedules the given timer on the given queue. * * @param pQueue The timer queue. * @param pTimer The timer that needs scheduling. */ DECLINLINE(void) tmTimerQueueScheduleOne(PTMTIMERQUEUE pQueue, PTMTIMER pTimer) { /* * Processing. */ switch (pTimer->enmState) { /* * Reschedule timer (in the active list). */ case TMTIMERSTATE_PENDING_RESCHEDULE: { const PTMTIMER pPrev = TMTIMER_GET_PREV(pTimer); const PTMTIMER pNext = TMTIMER_GET_NEXT(pTimer); if (pPrev) TMTIMER_SET_NEXT(pPrev, pNext); else { TMTIMER_SET_HEAD(pQueue, pNext); pQueue->u64Expire = pNext ? pNext->u64Expire : INT64_MAX; } if (pNext) TMTIMER_SET_PREV(pNext, pPrev); pTimer->offNext = 0; pTimer->offPrev = 0; /* fall thru */ } /* * Schedule timer (insert into the active list). */ case TMTIMERSTATE_PENDING_SCHEDULE: { Assert(!pTimer->offNext); Assert(!pTimer->offPrev); TM_SET_STATE(pTimer, TMTIMERSTATE_ACTIVE); PTMTIMER pCur = TMTIMER_GET_HEAD(pQueue); if (pCur) { const uint64_t u64Expire = pTimer->u64Expire; for (;; pCur = TMTIMER_GET_NEXT(pCur)) { if (pCur->u64Expire > u64Expire) { const PTMTIMER pPrev = TMTIMER_GET_PREV(pCur); TMTIMER_SET_NEXT(pTimer, pCur); TMTIMER_SET_PREV(pTimer, pPrev); if (pPrev) TMTIMER_SET_NEXT(pPrev, pTimer); else { TMTIMER_SET_HEAD(pQueue, pTimer); pQueue->u64Expire = u64Expire; } TMTIMER_SET_PREV(pCur, pTimer); break; } else if (!pCur->offNext) { TMTIMER_SET_NEXT(pCur, pTimer); TMTIMER_SET_PREV(pTimer, pCur); break; } } } else { TMTIMER_SET_HEAD(pQueue, pTimer); pQueue->u64Expire = pTimer->u64Expire; } break; } /* * Stop the timer in active list. */ case TMTIMERSTATE_PENDING_STOP: { const PTMTIMER pPrev = TMTIMER_GET_PREV(pTimer); const PTMTIMER pNext = TMTIMER_GET_NEXT(pTimer); if (pPrev) TMTIMER_SET_NEXT(pPrev, pNext); else { TMTIMER_SET_HEAD(pQueue, pNext); pQueue->u64Expire = pNext ? pNext->u64Expire : INT64_MAX; } if (pNext) TMTIMER_SET_PREV(pNext, pPrev); pTimer->offNext = 0; pTimer->offPrev = 0; /* fall thru */ } /* * Stop the timer (not on the active list). */ case TMTIMERSTATE_PENDING_STOP_SCHEDULE: Assert(!pTimer->offNext); Assert(!pTimer->offPrev); TM_SET_STATE(pTimer, TMTIMERSTATE_STOPPED); break; /* * Stop & destroy the timer. */ case TMTIMERSTATE_PENDING_STOP_DESTROY: { const PTMTIMER pPrev = TMTIMER_GET_PREV(pTimer); const PTMTIMER pNext = TMTIMER_GET_NEXT(pTimer); if (pPrev) TMTIMER_SET_NEXT(pPrev, pNext); else { TMTIMER_SET_HEAD(pQueue, pNext); pQueue->u64Expire = pNext ? pNext->u64Expire : INT64_MAX; } if (pNext) TMTIMER_SET_PREV(pNext, pPrev); pTimer->offNext = 0; pTimer->offPrev = 0; /* fall thru */ } /* * Destroy the timer. */ case TMTIMERSTATE_PENDING_DESTROY: { Assert(!pTimer->offNext); Assert(!pTimer->offPrev); PVM pVM = pTimer->CTXALLSUFF(pVM); const PTMTIMER pBigPrev = (PTMTIMER)(pTimer->pBigPrev ? MMHyperR3ToCC(pVM, pTimer->pBigPrev) : NULL); const PTMTIMER pBigNext = (PTMTIMER)(pTimer->pBigNext ? MMHyperR3ToCC(pVM, pTimer->pBigNext) : NULL); /* unlink from created list */ if (pBigPrev) pBigPrev->pBigNext = pTimer->pBigNext; else pVM->tm.s.pCreated = pTimer->pBigNext; if (pBigNext) pBigNext->pBigPrev = pTimer->pBigPrev; pTimer->pBigNext = 0; pTimer->pBigPrev = 0; /* free */ Log2(("TM: Inserting %p into the free list ahead of %p!\n", pTimer, pVM->tm.s.pFree)); pTimer->pBigNext = pVM->tm.s.pFree; pVM->tm.s.pFree = (PTMTIMERR3)MMHyperCCToR3(pVM, pTimer); TM_SET_STATE(pTimer, TMTIMERSTATE_FREE); break; } /* * Postpone these until they get into the right state. */ case TMTIMERSTATE_PENDING_RESCHEDULE_SET_EXPIRE: case TMTIMERSTATE_PENDING_SCHEDULE_SET_EXPIRE: tmTimerLink(pQueue, pTimer); STAM_COUNTER_INC(&pTimer->CTXALLSUFF(pVM)->tm.s.CTXALLSUFF(StatPostponed)); break; /* * None of these can be in the schedule. */ case TMTIMERSTATE_FREE: case TMTIMERSTATE_STOPPED: case TMTIMERSTATE_ACTIVE: case TMTIMERSTATE_EXPIRED: AssertMsgFailed(("Timer (%p) in the scheduling list has an invalid state %s (%d)!", pTimer, tmTimerState(pTimer->enmState), pTimer->enmState)); break; } } /** * Schedules the specified timer queue. * * @param pVM The VM to run the timers for. * @param pQueue The queue to schedule. */ void tmTimerQueueSchedule(PVM pVM, PTMTIMERQUEUE pQueue) { VM_ASSERT_EMT(pVM); /* * Dequeue the scheduling list and iterate it. */ int32_t offNext = ASMAtomicXchgS32(&pQueue->offSchedule, 0); Log2(("tmTimerQueueSchedule: pQueue=%p:{.enmClock=%d, offNext=%RI32}\n", pQueue, pQueue->enmClock, offNext)); if (!offNext) return; PTMTIMER pNext = (PTMTIMER)((intptr_t)pQueue + offNext); while (pNext) { /* * Unlink the head timer and find the next one. */ PTMTIMER pTimer = pNext; pNext = pNext->offScheduleNext ? (PTMTIMER)((intptr_t)pNext + pNext->offScheduleNext) : NULL; pTimer->offScheduleNext = 0; /* * Do the scheduling. */ Log2(("tmTimerQueueSchedule: pTimer=%p:{.enmState=%s, .enmClock=%d, .enmType=%d, .pszDesc=%s}\n", pTimer, tmTimerState(pTimer->enmState), pTimer->enmClock, pTimer->enmType, HCSTRING(pTimer->pszDesc))); tmTimerQueueScheduleOne(pQueue, pTimer); Log2(("tmTimerQueueSchedule: new %s\n", tmTimerState(pTimer->enmState))); } /* foreach timer in current schedule batch. */ } #ifdef VBOX_STRICT /** * Checks that the timer queues are sane. * * @param pVM VM handle. */ void tmTimerQueuesSanityChecks(PVM pVM, const char *pszWhere) { /* * Check the linking of the active lists. */ for (int i = 0; i < TMCLOCK_MAX; i++) { PTMTIMERQUEUE pQueue = &pVM->tm.s.CTXALLSUFF(paTimerQueues)[i]; Assert((int)pQueue->enmClock == i); PTMTIMER pPrev = NULL; for (PTMTIMER pCur = TMTIMER_GET_HEAD(pQueue); pCur; pPrev = pCur, pCur = TMTIMER_GET_NEXT(pCur)) { AssertMsg((int)pCur->enmClock == i, ("%s: %d != %d\n", pszWhere, pCur->enmClock, i)); AssertMsg(TMTIMER_GET_PREV(pCur) == pPrev, ("%s: %p != %p\n", pszWhere, TMTIMER_GET_PREV(pCur), pPrev)); TMTIMERSTATE enmState = pCur->enmState; switch (enmState) { case TMTIMERSTATE_ACTIVE: AssertMsg(!pCur->offScheduleNext, ("%s: %RI32\n", pszWhere, pCur->offScheduleNext)); break; case TMTIMERSTATE_PENDING_STOP: case TMTIMERSTATE_PENDING_STOP_DESTROY: case TMTIMERSTATE_PENDING_RESCHEDULE: break; default: AssertMsgFailed(("%s: Invalid state enmState=%d %s\n", pszWhere, enmState, tmTimerState(enmState))); break; } } } # ifdef IN_RING3 /* * Do the big list and check that active timers all are in the active lists. */ PTMTIMERR3 pPrev = NULL; for (PTMTIMERR3 pCur = pVM->tm.s.pCreated; pCur; pPrev = pCur, pCur = pCur->pBigNext) { Assert(pCur->pBigPrev == pPrev); Assert((unsigned)pCur->enmClock < (unsigned)TMCLOCK_MAX); TMTIMERSTATE enmState = pCur->enmState; switch (enmState) { case TMTIMERSTATE_ACTIVE: case TMTIMERSTATE_PENDING_STOP: case TMTIMERSTATE_PENDING_STOP_DESTROY: case TMTIMERSTATE_PENDING_RESCHEDULE: case TMTIMERSTATE_PENDING_RESCHEDULE_SET_EXPIRE: { PTMTIMERR3 pCurAct = TMTIMER_GET_HEAD(&pVM->tm.s.CTXALLSUFF(paTimerQueues)[pCur->enmClock]); Assert(pCur->offPrev || pCur == pCurAct); while (pCurAct && pCurAct != pCur) pCurAct = TMTIMER_GET_NEXT(pCurAct); Assert(pCurAct == pCur); break; } case TMTIMERSTATE_PENDING_DESTROY: case TMTIMERSTATE_PENDING_SCHEDULE: case TMTIMERSTATE_PENDING_STOP_SCHEDULE: case TMTIMERSTATE_STOPPED: case TMTIMERSTATE_EXPIRED: { Assert(!pCur->offNext); Assert(!pCur->offPrev); for (PTMTIMERR3 pCurAct = TMTIMER_GET_HEAD(&pVM->tm.s.CTXALLSUFF(paTimerQueues)[pCur->enmClock]); pCurAct; pCurAct = TMTIMER_GET_NEXT(pCurAct)) { Assert(pCurAct != pCur); Assert(TMTIMER_GET_NEXT(pCurAct) != pCur); Assert(TMTIMER_GET_PREV(pCurAct) != pCur); } break; } /* ignore */ case TMTIMERSTATE_PENDING_SCHEDULE_SET_EXPIRE: break; /* shouldn't get here! */ default: AssertMsgFailed(("Invalid state enmState=%d %s\n", enmState, tmTimerState(enmState))); break; } } # endif /* IN_RING3 */ } #endif /* !VBOX_STRICT */