diff options
Diffstat (limited to 'src/VBox/Runtime/r3/win/timer-win.cpp')
-rw-r--r-- | src/VBox/Runtime/r3/win/timer-win.cpp | 460 |
1 files changed, 460 insertions, 0 deletions
diff --git a/src/VBox/Runtime/r3/win/timer-win.cpp b/src/VBox/Runtime/r3/win/timer-win.cpp new file mode 100644 index 00000000..0bfb459b --- /dev/null +++ b/src/VBox/Runtime/r3/win/timer-win.cpp @@ -0,0 +1,460 @@ +/* $Id: timer-win.cpp $ */ +/** @file + * IPRT - Timer. + */ + +/* + * Copyright (C) 2006-2019 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. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/* Which code to use is determined here... + * + * The default is to use wait on NT timers directly with no APC since this + * is supposed to give the shortest kernel code paths. + * + * The USE_APC variation will do as above except that an APC routine is + * handling the callback action. + * + * The USE_WINMM version will use the NT timer wrappers in WinMM which may + * result in some 0.1% better correctness in number of delivered ticks. However, + * this codepath have more overhead (it uses APC among other things), and I'm not + * quite sure if it's actually any more correct. + * + * The USE_CATCH_UP will play catch up when the timer lags behind. However this + * requires a monotonous time source. + * + * The default mode which we are using is using relative periods of time and thus + * will never suffer from errors in the time source. Neither will it try catch up + * missed ticks. This suits our current purposes best I'd say. + */ +#undef USE_APC +#undef USE_WINMM +#undef USE_CATCH_UP + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_TIMER +#define _WIN32_WINNT 0x0500 +#include <iprt/win/windows.h> + +#include <iprt/timer.h> +#ifdef USE_CATCH_UP +# include <iprt/time.h> +#endif +#include <iprt/alloc.h> +#include <iprt/assert.h> +#include <iprt/thread.h> +#include <iprt/log.h> +#include <iprt/asm.h> +#include <iprt/semaphore.h> +#include <iprt/err.h> +#include "internal/magics.h" + +RT_C_DECLS_BEGIN +/* from sysinternals. */ +NTSYSAPI LONG NTAPI NtSetTimerResolution(IN ULONG DesiredResolution, IN BOOLEAN SetResolution, OUT PULONG CurrentResolution); +NTSYSAPI LONG NTAPI NtQueryTimerResolution(OUT PULONG MaximumResolution, OUT PULONG MinimumResolution, OUT PULONG CurrentResolution); +RT_C_DECLS_END + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * The internal representation of a timer handle. + */ +typedef struct RTTIMER +{ + /** Magic. + * This is RTTIMER_MAGIC, but changes to something else before the timer + * is destroyed to indicate clearly that thread should exit. */ + volatile uint32_t u32Magic; + /** User argument. */ + void *pvUser; + /** Callback. */ + PFNRTTIMER pfnTimer; + /** The current tick. */ + uint64_t iTick; + /** The interval. */ + unsigned uMilliesInterval; +#ifdef USE_WINMM + /** Win32 timer id. */ + UINT TimerId; +#else + /** Time handle. */ + HANDLE hTimer; +# ifdef USE_APC + /** Handle to wait on. */ + HANDLE hevWait; +# endif + /** USE_CATCH_UP: ns time of the next tick. + * !USE_CATCH_UP: -uMilliesInterval * 10000 */ + LARGE_INTEGER llNext; + /** The thread handle of the timer thread. */ + RTTHREAD Thread; + /** The error/status of the timer. + * Initially -1, set to 0 when the timer have been successfully started, and + * to errno on failure in starting the timer. */ + volatile int iError; +#endif +} RTTIMER; + + + +#ifdef USE_WINMM +/** + * Win32 callback wrapper. + */ +static void CALLBACK rttimerCallback(UINT uTimerID, UINT uMsg, DWORD_PTR dwUser, DWORD_PTR dw1, DWORD_PTR dw2) +{ + PRTTIMER pTimer = (PRTTIMER)(void *)dwUser; + Assert(pTimer->TimerId == uTimerID); + pTimer->pfnTimer(pTimer, pTimer->pvUser, ++pTimer->iTick); + NOREF(uMsg); NOREF(dw1); NOREF(dw2); NOREF(uTimerID); +} +#else /* !USE_WINMM */ + +#ifdef USE_APC +/** + * Async callback. + * + * @param lpArgToCompletionRoutine Pointer to our timer structure. + */ +VOID CALLBACK rttimerAPCProc(LPVOID lpArgToCompletionRoutine, DWORD dwTimerLowValue, DWORD dwTimerHighValue) +{ + PRTTIMER pTimer = (PRTTIMER)lpArgToCompletionRoutine; + + /* + * Check if we're begin destroyed. + */ + if (pTimer->u32Magic != RTTIMER_MAGIC) + return; + + /* + * Callback the handler. + */ + pTimer->pfnTimer(pTimer, pTimer->pvUser, ++pTimer->iTick); + + /* + * Rearm the timer handler. + */ +#ifdef USE_CATCH_UP + pTimer->llNext.QuadPart += (int64_t)pTimer->uMilliesInterval * 10000; + LARGE_INTEGER ll; + ll.QuadPart = RTTimeNanoTS() - pTimer->llNext.QuadPart; + if (ll.QuadPart < -500000) + ll.QuadPart = ll.QuadPart / 100; + else + ll.QuadPart = -500000 / 100; /* need to catch up, do a minimum wait of 0.5ms. */ +#else + LARGE_INTEGER ll = pTimer->llNext; +#endif + BOOL frc = SetWaitableTimer(pTimer->hTimer, &ll, 0, rttimerAPCProc, pTimer, FALSE); + AssertMsg(frc || pTimer->u32Magic != RTTIMER_MAGIC, ("last error %d\n", GetLastError())); +} +#endif /* USE_APC */ + +/** + * Timer thread. + */ +static DECLCALLBACK(int) rttimerCallback(RTTHREAD Thread, void *pvArg) +{ + PRTTIMER pTimer = (PRTTIMER)(void *)pvArg; + Assert(pTimer->u32Magic == RTTIMER_MAGIC); + + /* + * Bounce our priority up quite a bit. + */ + if ( !SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL) + /*&& !SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST)*/) + { + int rc = GetLastError(); + AssertMsgFailed(("Failed to set priority class lasterror %d.\n", rc)); + pTimer->iError = RTErrConvertFromWin32(rc); + return rc; + } + + /* + * Start the waitable timer. + */ + +#ifdef USE_CATCH_UP + const int64_t NSInterval = (int64_t)pTimer->uMilliesInterval * 1000000; + pTimer->llNext.QuadPart = RTTimeNanoTS() + NSInterval; +#else + pTimer->llNext.QuadPart = -(int64_t)pTimer->uMilliesInterval * 10000; +#endif + LARGE_INTEGER ll; + ll.QuadPart = -(int64_t)pTimer->uMilliesInterval * 10000; +#ifdef USE_APC + if (!SetWaitableTimer(pTimer->hTimer, &ll, 0, rttimerAPCProc, pTimer, FALSE)) +#else + if (!SetWaitableTimer(pTimer->hTimer, &ll, 0, NULL, NULL, FALSE)) +#endif + { + int rc = GetLastError(); + AssertMsgFailed(("Failed to set timer, lasterr %d.\n", rc)); + pTimer->iError = RTErrConvertFromWin32(rc); + RTThreadUserSignal(Thread); + return rc; + } + + /* + * Wait for the semaphore to be posted. + */ + RTThreadUserSignal(Thread); + for (;pTimer->u32Magic == RTTIMER_MAGIC;) + { +#ifdef USE_APC + int rc = WaitForSingleObjectEx(pTimer->hevWait, INFINITE, TRUE); + if (rc != WAIT_OBJECT_0 && rc != WAIT_IO_COMPLETION) +#else + int rc = WaitForSingleObjectEx(pTimer->hTimer, INFINITE, FALSE); + if (pTimer->u32Magic != RTTIMER_MAGIC) + break; + if (rc == WAIT_OBJECT_0) + { + /* + * Callback the handler. + */ + pTimer->pfnTimer(pTimer, pTimer->pvUser, ++pTimer->iTick); + + /* + * Rearm the timer handler. + */ +# ifdef USE_CATCH_UP + pTimer->llNext.QuadPart += NSInterval; + LARGE_INTEGER ll; + ll.QuadPart = RTTimeNanoTS() - pTimer->llNext.QuadPart; + if (ll.QuadPart < -500000) + ll.QuadPart = ll.QuadPart / 100; + else + ll.QuadPart = -500000 / 100; /* need to catch up, do a minimum wait of 0.5ms. */ +# else + LARGE_INTEGER ll = pTimer->llNext; +# endif + BOOL fRc = SetWaitableTimer(pTimer->hTimer, &ll, 0, NULL, NULL, FALSE); + AssertMsg(fRc || pTimer->u32Magic != RTTIMER_MAGIC, ("last error %d\n", GetLastError())); NOREF(fRc); + } + else +#endif + { + /* + * We failed during wait, so just signal the destructor and exit. + */ + int rc2 = GetLastError(); + RTThreadUserSignal(Thread); + AssertMsgFailed(("Wait on hTimer failed, rc=%d lasterr=%d\n", rc, rc2)); NOREF(rc2); + return -1; + } + } + + /* + * Exit. + */ + RTThreadUserSignal(Thread); + return 0; +} +#endif /* !USE_WINMM */ + + +RTDECL(int) RTTimerCreate(PRTTIMER *ppTimer, unsigned uMilliesInterval, PFNRTTIMER pfnTimer, void *pvUser) +{ +#ifndef USE_WINMM + /* + * On windows we'll have to set the timer resolution before + * we start the timer. + */ + ULONG ulMax = UINT32_MAX; + ULONG ulMin = UINT32_MAX; + ULONG ulCur = UINT32_MAX; + NtQueryTimerResolution(&ulMax, &ulMin, &ulCur); + Log(("NtQueryTimerResolution -> ulMax=%lu00ns ulMin=%lu00ns ulCur=%lu00ns\n", ulMax, ulMin, ulCur)); + if (ulCur > ulMin && ulCur > 10000 /* = 1ms */) + { + if (NtSetTimerResolution(10000, TRUE, &ulCur) >= 0) + Log(("Changed timer resolution to 1ms.\n")); + else if (NtSetTimerResolution(20000, TRUE, &ulCur) >= 0) + Log(("Changed timer resolution to 2ms.\n")); + else if (NtSetTimerResolution(40000, TRUE, &ulCur) >= 0) + Log(("Changed timer resolution to 4ms.\n")); + else if (ulMin <= 50000 && NtSetTimerResolution(ulMin, TRUE, &ulCur) >= 0) + Log(("Changed timer resolution to %lu *100ns.\n", ulMin)); + else + { + AssertMsgFailed(("Failed to configure timer resolution!\n")); + return VERR_INTERNAL_ERROR; + } + } +#endif /* !USE_WINN */ + + /* + * Create new timer. + */ + int rc = VERR_IPE_UNINITIALIZED_STATUS; + PRTTIMER pTimer = (PRTTIMER)RTMemAlloc(sizeof(*pTimer)); + if (pTimer) + { + pTimer->u32Magic = RTTIMER_MAGIC; + pTimer->pvUser = pvUser; + pTimer->pfnTimer = pfnTimer; + pTimer->iTick = 0; + pTimer->uMilliesInterval = uMilliesInterval; +#ifdef USE_WINMM + /* sync kill doesn't work. */ + pTimer->TimerId = timeSetEvent(uMilliesInterval, 0, rttimerCallback, (DWORD_PTR)pTimer, TIME_PERIODIC | TIME_CALLBACK_FUNCTION); + if (pTimer->TimerId) + { + ULONG ulMax = UINT32_MAX; + ULONG ulMin = UINT32_MAX; + ULONG ulCur = UINT32_MAX; + NtQueryTimerResolution(&ulMax, &ulMin, &ulCur); + Log(("NtQueryTimerResolution -> ulMax=%lu00ns ulMin=%lu00ns ulCur=%lu00ns\n", ulMax, ulMin, ulCur)); + + *ppTimer = pTimer; + return VINF_SUCCESS; + } + rc = VERR_INVALID_PARAMETER; + +#else /* !USE_WINMM */ + + /* + * Create Win32 event semaphore. + */ + pTimer->iError = 0; + pTimer->hTimer = CreateWaitableTimer(NULL, TRUE, NULL); + if (pTimer->hTimer) + { +#ifdef USE_APC + /* + * Create wait semaphore. + */ + pTimer->hevWait = CreateEvent(NULL, FALSE, FALSE, NULL); + if (pTimer->hevWait) +#endif + { + /* + * Kick off the timer thread. + */ + rc = RTThreadCreate(&pTimer->Thread, rttimerCallback, pTimer, 0, RTTHREADTYPE_TIMER, RTTHREADFLAGS_WAITABLE, "Timer"); + if (RT_SUCCESS(rc)) + { + /* + * Wait for the timer to successfully create the timer + * If we don't get a response in 10 secs, then we assume we're screwed. + */ + rc = RTThreadUserWait(pTimer->Thread, 10000); + if (RT_SUCCESS(rc)) + { + rc = pTimer->iError; + if (RT_SUCCESS(rc)) + { + *ppTimer = pTimer; + return VINF_SUCCESS; + } + } + ASMAtomicXchgU32(&pTimer->u32Magic, RTTIMER_MAGIC + 1); + RTThreadWait(pTimer->Thread, 250, NULL); + CancelWaitableTimer(pTimer->hTimer); + } +#ifdef USE_APC + CloseHandle(pTimer->hevWait); +#endif + } + CloseHandle(pTimer->hTimer); + } +#endif /* !USE_WINMM */ + + AssertMsgFailed(("Failed to create timer uMilliesInterval=%d. rc=%d\n", uMilliesInterval, rc)); + RTMemFree(pTimer); + } + else + rc = VERR_NO_MEMORY; + return rc; +} + + +RTR3DECL(int) RTTimerDestroy(PRTTIMER pTimer) +{ + /* NULL is ok. */ + if (!pTimer) + return VINF_SUCCESS; + + /* + * Validate handle first. + */ + int rc; + if ( VALID_PTR(pTimer) + && pTimer->u32Magic == RTTIMER_MAGIC) + { +#ifdef USE_WINMM + /* + * Kill the timer and exit. + */ + rc = timeKillEvent(pTimer->TimerId); + AssertMsg(rc == TIMERR_NOERROR, ("timeKillEvent -> %d\n", rc)); + ASMAtomicXchgU32(&pTimer->u32Magic, RTTIMER_MAGIC + 1); + RTThreadSleep(1); + +#else /* !USE_WINMM */ + + /* + * Signal that we want the thread to exit. + */ + ASMAtomicXchgU32(&pTimer->u32Magic, RTTIMER_MAGIC + 1); +#ifdef USE_APC + SetEvent(pTimer->hevWait); + CloseHandle(pTimer->hevWait); + rc = CancelWaitableTimer(pTimer->hTimer); + AssertMsg(rc, ("CancelWaitableTimer lasterr=%d\n", GetLastError())); +#else + LARGE_INTEGER ll = {0}; + ll.LowPart = 100; + rc = SetWaitableTimer(pTimer->hTimer, &ll, 0, NULL, NULL, FALSE); + AssertMsg(rc, ("CancelWaitableTimer lasterr=%d\n", GetLastError())); +#endif + + /* + * Wait for the thread to exit. + * And if it don't wanna exit, we'll get kill it. + */ + rc = RTThreadWait(pTimer->Thread, 1000, NULL); + if (RT_FAILURE(rc)) + TerminateThread((HANDLE)RTThreadGetNative(pTimer->Thread), UINT32_MAX); + + /* + * Free resource. + */ + rc = CloseHandle(pTimer->hTimer); + AssertMsg(rc, ("CloseHandle lasterr=%d\n", GetLastError())); + +#endif /* !USE_WINMM */ + RTMemFree(pTimer); + return rc; + } + + rc = VERR_INVALID_HANDLE; + AssertMsgFailed(("Failed to destroy timer %p. rc=%d\n", pTimer, rc)); + return rc; +} + |