/* $Id: timer-win32.cpp 197 2007-01-20 01:22:45Z vboxsync $ */ /** @file * InnoTek Portable Runtime - Timer. */ /* * Copyright (C) 2006 InnoTek Systemberatung 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. * * If you received this file as part of a commercial VirtualBox * distribution, then only the terms of your commercial VirtualBox * license agreement apply instead of the previous paragraph. */ /* 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 #include #ifdef USE_CATCH_UP # include #endif #include #include #include #include #include #include #include #include __BEGIN_DECLS /* from sysinternals. */ NTSYSAPI LONG NTAPI NtSetTimerResolution(IN ULONG DesiredResolution, IN BOOLEAN SetResolution, OUT PULONG CurrentResolution); NTSYSAPI LONG NTAPI NtQueryTimerResolution(OUT PULONG MinimumResolution, OUT PULONG MaximumResolution, OUT PULONG CurrentResolution); __END_DECLS /******************************************************************************* * 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 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; /** Timer handle magic. */ #define RTTIMER_MAGIC 0x42424242 #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); 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); /* * 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); /* * 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())); } 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)); return -1; } } /* * Exit. */ RTThreadUserSignal(Thread); return 0; } #endif /* !USE_WINMM */ /** * Create a recurring timer. * * @returns iprt status code. * @param ppTimer Where to store the timer handle. * @param uMilliesInterval Milliseconds between the timer ticks. * This is rounded up to the system granularity. * @param pfnTimer Callback function which shall be scheduled for execution * on every timer tick. * @param pvUser User argument for the callback. */ RTR3DECL(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 Min = ~0; ULONG Max = ~0; ULONG Cur = ~0; NtQueryTimerResolution(&Min, &Max, &Cur); Log(("NtQueryTimerResolution -> Min=%lu Max=%lu Cur=%lu (100ns)\n", Min, Max, Cur)); if (Cur > Max && Cur > 10000 /* = 1ms */) { if (NtSetTimerResolution(10000, TRUE, &Cur) >= 0) Log(("Changed timer resolution to 1ms.\n")); else if (NtSetTimerResolution(20000, TRUE, &Cur) >= 0) Log(("Changed timer resolution to 2ms.\n")); else if (NtSetTimerResolution(40000, TRUE, &Cur) >= 0) Log(("Changed timer resolution to 4ms.\n")); else if (Max <= 50000 && NtSetTimerResolution(Max, TRUE, &Cur) >= 0) Log(("Changed timer resolution to %lu *100ns.\n", Max)); else { AssertMsgFailed(("Failed to configure timer resolution!\n")); return VERR_INTERNAL_ERROR; } } #endif /* !USE_WINN */ /* * Create new timer. */ int rc; PRTTIMER pTimer = (PRTTIMER)RTMemAlloc(sizeof(*pTimer)); if (pTimer) { pTimer->u32Magic = RTTIMER_MAGIC; pTimer->pvUser = pvUser; pTimer->pfnTimer = pfnTimer; 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 Min = ~0; ULONG Max = ~0; ULONG Cur = ~0; NtQueryTimerResolution(&Min, &Max, &Cur); Log(("NtQueryTimerResolution -> Min=%lu Max=%lu Cur=%lu (100ns)\n", Min, Max, Cur)); *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; } /** * Stops and destroys a running timer. * * @returns iprt status code. * @param pTimer Timer to stop and destroy. */ 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), -1); /* * 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; }