VirtualBox

source: vbox/trunk/src/VBox/Runtime/r3/win/timer-win.cpp@ 93115

Last change on this file since 93115 was 93115, checked in by vboxsync, 2 years ago

scm --update-copyright-year

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id Revision
File size: 16.9 KB
Line 
1/* $Id: timer-win.cpp 93115 2022-01-01 11:31:46Z vboxsync $ */
2/** @file
3 * IPRT - Timer.
4 */
5
6/*
7 * Copyright (C) 2006-2022 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 *
17 * The contents of this file may alternatively be used under the terms
18 * of the Common Development and Distribution License Version 1.0
19 * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
20 * VirtualBox OSE distribution, in which case the provisions of the
21 * CDDL are applicable instead of those of the GPL.
22 *
23 * You may elect to license modified versions of this file under the
24 * terms and conditions of either the GPL or the CDDL or both.
25 */
26
27
28/*********************************************************************************************************************************
29* Header Files *
30*********************************************************************************************************************************/
31#define LOG_GROUP RTLOGGROUP_TIMER
32#define _WIN32_WINNT 0x0500
33#include <iprt/win/windows.h>
34
35#include <iprt/timer.h>
36#ifdef USE_CATCH_UP
37# include <iprt/time.h>
38#endif
39#include <iprt/alloc.h>
40#include <iprt/assert.h>
41#include <iprt/thread.h>
42#include <iprt/log.h>
43#include <iprt/asm.h>
44#include <iprt/semaphore.h>
45#include <iprt/err.h>
46#include "internal/magics.h"
47#include "internal-r3-win.h"
48
49
50/** Define the flag for creating a manual reset timer if not available in the SDK we are compiling with. */
51#ifndef CREATE_WAITABLE_TIMER_MANUAL_RESET
52# define CREATE_WAITABLE_TIMER_MANUAL_RESET 0x00000001
53#endif
54/** Define the flag for high resolution timers, available since Windows 10 RS4 if not available. */
55#ifndef CREATE_WAITABLE_TIMER_HIGH_RESOLUTION
56# define CREATE_WAITABLE_TIMER_HIGH_RESOLUTION 0x00000002
57#endif
58
59
60RT_C_DECLS_BEGIN
61/* from sysinternals. */
62NTSYSAPI LONG NTAPI NtSetTimerResolution(IN ULONG DesiredResolution, IN BOOLEAN SetResolution, OUT PULONG CurrentResolution);
63NTSYSAPI LONG NTAPI NtQueryTimerResolution(OUT PULONG MaximumResolution, OUT PULONG MinimumResolution, OUT PULONG CurrentResolution);
64RT_C_DECLS_END
65
66
67/*********************************************************************************************************************************
68* Structures and Typedefs *
69*********************************************************************************************************************************/
70/**
71 * The internal representation of a timer handle.
72 */
73typedef struct RTTIMER
74{
75 /** Magic.
76 * This is RTTIMER_MAGIC, but changes to something else before the timer
77 * is destroyed to indicate clearly that thread should exit. */
78 uint32_t volatile u32Magic;
79 /** Flag indicating the timer is suspended. */
80 bool volatile fSuspended;
81 /** Flag indicating that the timer has been destroyed. */
82 bool volatile fDestroyed;
83 /** User argument. */
84 void *pvUser;
85 /** Callback. */
86 PFNRTTIMER pfnTimer;
87 /** The current tick. */
88 uint64_t iTick;
89 /** The timer interval. 0 if one-shot. */
90 uint64_t u64NanoInterval;
91 /** The first shot interval. 0 if ASAP. */
92 uint64_t volatile u64NanoFirst;
93 /** Time handle. */
94 HANDLE hTimer;
95 /** USE_CATCH_UP: ns time of the next tick.
96 * !USE_CATCH_UP: -uMilliesInterval * 10000 */
97 LARGE_INTEGER llNext;
98 /** The thread handle of the timer thread. */
99 RTTHREAD Thread;
100 /** Event semaphore on which the thread is blocked. */
101 RTSEMEVENT Event;
102 /** The error/status of the timer.
103 * Initially -1, set to 0 when the timer have been successfully started, and
104 * to errno on failure in starting the timer. */
105 volatile int iError;
106} RTTIMER;
107
108
109
110/**
111 * Timer thread.
112 */
113static DECLCALLBACK(int) rttimerCallback(RTTHREAD hThreadSelf, void *pvArg)
114{
115 PRTTIMER pTimer = (PRTTIMER)(void *)pvArg;
116 Assert(pTimer->u32Magic == RTTIMER_MAGIC);
117
118 /*
119 * Bounce our priority up quite a bit.
120 */
121 if (!SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL))
122 {
123 int rc = GetLastError();
124 AssertMsgFailed(("Failed to set priority class lasterror %d.\n", rc));
125 pTimer->iError = RTErrConvertFromWin32(rc);
126 RTThreadUserSignal(hThreadSelf);
127 return rc;
128 }
129
130 /*
131 * The work loop.
132 */
133 RTThreadUserSignal(hThreadSelf);
134
135 while ( !pTimer->fDestroyed
136 && pTimer->u32Magic == RTTIMER_MAGIC)
137 {
138 /*
139 * Wait for a start or destroy event.
140 */
141 if (pTimer->fSuspended)
142 {
143 int rc = RTSemEventWait(pTimer->Event, RT_INDEFINITE_WAIT);
144 if (RT_FAILURE(rc) && rc != VERR_INTERRUPTED)
145 {
146 AssertRC(rc);
147 if (pTimer->fDestroyed)
148 continue;
149 RTThreadSleep(1000); /* Don't cause trouble! */
150 }
151 if ( pTimer->fSuspended
152 || pTimer->fDestroyed)
153 continue;
154 }
155
156 /*
157 * Start the waitable timer.
158 */
159 pTimer->llNext.QuadPart = -(int64_t)pTimer->u64NanoInterval / 100;
160 LARGE_INTEGER ll;
161 if (pTimer->u64NanoFirst)
162 {
163 GetSystemTimeAsFileTime((LPFILETIME)&ll);
164 ll.QuadPart += pTimer->u64NanoFirst / 100;
165 pTimer->u64NanoFirst = 0;
166 }
167 else
168 ll.QuadPart = -(int64_t)pTimer->u64NanoInterval / 100;
169 if (!SetWaitableTimer(pTimer->hTimer, &ll, 0, NULL, NULL, FALSE))
170 {
171 ASMAtomicXchgBool(&pTimer->fSuspended, true);
172 int rc = GetLastError();
173 AssertMsgFailed(("Failed to set timer, lasterr %d.\n", rc));
174 pTimer->iError = RTErrConvertFromWin32(rc);
175 RTThreadUserSignal(hThreadSelf);
176 continue; /* back to suspended mode. */
177 }
178 pTimer->iError = 0;
179 RTThreadUserSignal(hThreadSelf);
180
181 /*
182 * Timer Service Loop.
183 */
184 do
185 {
186 int rc = WaitForSingleObjectEx(pTimer->hTimer, INFINITE, FALSE);
187 if (pTimer->u32Magic != RTTIMER_MAGIC)
188 break;
189 if (rc == WAIT_OBJECT_0)
190 {
191 /*
192 * Callback the handler.
193 */
194 pTimer->pfnTimer(pTimer, pTimer->pvUser, ++pTimer->iTick);
195
196 /*
197 * Rearm the timer handler.
198 */
199 ll = pTimer->llNext;
200 BOOL fRc = SetWaitableTimer(pTimer->hTimer, &ll, 0, NULL, NULL, FALSE);
201 AssertMsg(fRc || pTimer->u32Magic != RTTIMER_MAGIC, ("last error %d\n", GetLastError())); NOREF(fRc);
202 }
203 else
204 {
205 /*
206 * We failed during wait, so just signal the destructor and exit.
207 */
208 int rc2 = GetLastError();
209 RTThreadUserSignal(hThreadSelf);
210 AssertMsgFailed(("Wait on hTimer failed, rc=%d lasterr=%d\n", rc, rc2)); NOREF(rc2);
211 return -1;
212 }
213 } while (RT_LIKELY( !pTimer->fSuspended
214 && !pTimer->fDestroyed
215 && pTimer->u32Magic == RTTIMER_MAGIC));
216
217 /*
218 * Disable the timer.
219 */
220 int rc = CancelWaitableTimer (pTimer->hTimer); RT_NOREF(rc);
221 AssertMsg(rc, ("CancelWaitableTimer lasterr=%d\n", GetLastError()));
222
223 /*
224 * ACK any pending suspend request.
225 */
226 if (!pTimer->fDestroyed)
227 {
228 pTimer->iError = 0;
229 RTThreadUserSignal(hThreadSelf);
230 }
231 }
232
233 /*
234 * Exit.
235 */
236 pTimer->iError = 0;
237 RTThreadUserSignal(hThreadSelf);
238 return VINF_SUCCESS;
239}
240
241
242/**
243 * Tries to set the NT timer resolution to a value matching the given timer interval.
244 *
245 * @returns IPRT status code.
246 * @param u64NanoInterval The timer interval in nano seconds.
247 */
248static int rtTimerNtSetTimerResolution(uint64_t u64NanoInterval)
249{
250 /*
251 * On windows we'll have to set the timer resolution before
252 * we start the timer.
253 */
254 ULONG ulMax = UINT32_MAX;
255 ULONG ulMin = UINT32_MAX;
256 ULONG ulCur = UINT32_MAX;
257 ULONG ulReq = (ULONG)(u64NanoInterval / 100);
258 NtQueryTimerResolution(&ulMax, &ulMin, &ulCur);
259 Log(("NtQueryTimerResolution -> ulMax=%lu00ns ulMin=%lu00ns ulCur=%lu00ns\n", ulMax, ulMin, ulCur));
260 if (ulCur > ulMin && ulCur > ulReq)
261 {
262 ulReq = RT_MIN(ulMin, ulReq);
263 if (NtSetTimerResolution(ulReq, TRUE, &ulCur) >= 0)
264 Log(("Changed timer resolution to %lu*100ns.\n", ulReq));
265 else if (NtSetTimerResolution(10000, TRUE, &ulCur) >= 0)
266 Log(("Changed timer resolution to 1ms.\n"));
267 else if (NtSetTimerResolution(20000, TRUE, &ulCur) >= 0)
268 Log(("Changed timer resolution to 2ms.\n"));
269 else if (NtSetTimerResolution(40000, TRUE, &ulCur) >= 0)
270 Log(("Changed timer resolution to 4ms.\n"));
271 else if (ulMin <= 50000 && NtSetTimerResolution(ulMin, TRUE, &ulCur) >= 0)
272 Log(("Changed timer resolution to %lu *100ns.\n", ulMin));
273 else
274 {
275 AssertMsgFailed(("Failed to configure timer resolution!\n"));
276 return VERR_INTERNAL_ERROR;
277 }
278 }
279
280 return VINF_SUCCESS;
281}
282
283
284RTDECL(int) RTTimerCreateEx(PRTTIMER *ppTimer, uint64_t u64NanoInterval, uint32_t fFlags, PFNRTTIMER pfnTimer, void *pvUser)
285{
286 /*
287 * We don't support the fancy MP features.
288 */
289 if (fFlags & RTTIMER_FLAGS_CPU_SPECIFIC)
290 return VERR_NOT_SUPPORTED;
291
292 /*
293 * Create new timer.
294 */
295 int rc = VERR_IPE_UNINITIALIZED_STATUS;
296 PRTTIMER pTimer = (PRTTIMER)RTMemAlloc(sizeof(*pTimer));
297 if (pTimer)
298 {
299 pTimer->u32Magic = RTTIMER_MAGIC;
300 pTimer->fSuspended = true;
301 pTimer->fDestroyed = false;
302 pTimer->Thread = NIL_RTTHREAD;
303 pTimer->pfnTimer = pfnTimer;
304 pTimer->pvUser = pvUser;
305 pTimer->u64NanoInterval = u64NanoInterval;
306
307 rc = RTSemEventCreate(&pTimer->Event);
308 AssertRC(rc);
309 if (RT_SUCCESS(rc))
310 {
311 /*
312 * Create Win32 waitable timer.
313 * We will first try the undocumented CREATE_WAITABLE_TIMER_HIGH_RESOLUTION which
314 * exists since some Windows 10 version (RS4). If this fails we resort to the old
315 * method of setting the timer resolution before creating a timer which will probably
316 * not give us the accuracy for intervals below the system tick resolution.
317 */
318 pTimer->iError = 0;
319 if (g_pfnCreateWaitableTimerExW)
320 pTimer->hTimer = g_pfnCreateWaitableTimerExW(NULL, NULL,
321 CREATE_WAITABLE_TIMER_MANUAL_RESET | CREATE_WAITABLE_TIMER_HIGH_RESOLUTION,
322 TIMER_ALL_ACCESS);
323 if (!pTimer->hTimer)
324 {
325 rc = rtTimerNtSetTimerResolution(u64NanoInterval);
326 if (RT_SUCCESS(rc))
327 pTimer->hTimer = CreateWaitableTimer(NULL, TRUE, NULL);
328 }
329
330 if (pTimer->hTimer)
331 {
332 /*
333 * Kick off the timer thread.
334 */
335 rc = RTThreadCreate(&pTimer->Thread, rttimerCallback, pTimer, 0, RTTHREADTYPE_TIMER, RTTHREADFLAGS_WAITABLE, "Timer");
336 if (RT_SUCCESS(rc))
337 {
338 /*
339 * Wait for the timer to successfully create the timer
340 * If we don't get a response in 10 secs, then we assume we're screwed.
341 */
342 rc = RTThreadUserWait(pTimer->Thread, 10000);
343 if (RT_SUCCESS(rc))
344 {
345 rc = pTimer->iError;
346 if (RT_SUCCESS(rc))
347 {
348 *ppTimer = pTimer;
349 return VINF_SUCCESS;
350 }
351 }
352
353 /* bail out */
354 ASMAtomicXchgBool(&pTimer->fDestroyed, true);
355 ASMAtomicXchgU32(&pTimer->u32Magic, ~RTTIMER_MAGIC);
356 RTThreadWait(pTimer->Thread, 45*1000, NULL);
357 CancelWaitableTimer(pTimer->hTimer);
358 }
359 CloseHandle(pTimer->hTimer);
360 }
361 else
362 rc = RTErrConvertFromWin32(GetLastError());
363 RTSemEventDestroy(pTimer->Event);
364 pTimer->Event = NIL_RTSEMEVENT;
365 }
366
367 RTMemFree(pTimer);
368 }
369 else
370 rc = VERR_NO_MEMORY;
371 return rc;
372}
373
374
375RTR3DECL(int) RTTimerDestroy(PRTTIMER pTimer)
376{
377 /* NULL is ok. */
378 if (!pTimer)
379 return VINF_SUCCESS;
380
381 int rc = VINF_SUCCESS;
382 AssertPtrReturn(pTimer, VERR_INVALID_HANDLE);
383 AssertReturn(pTimer->u32Magic == RTTIMER_MAGIC, VERR_INVALID_MAGIC);
384 AssertReturn(pTimer->Thread != RTThreadSelf(), VERR_INTERNAL_ERROR);
385
386 /*
387 * Signal that we want the thread to exit.
388 */
389 ASMAtomicWriteBool(&pTimer->fDestroyed, true);
390 ASMAtomicWriteU32(&pTimer->u32Magic, ~RTTIMER_MAGIC);
391
392 /*
393 * Suspend the timer if it's running.
394 */
395 if (!pTimer->fSuspended)
396 {
397 LARGE_INTEGER ll = {0};
398 ll.LowPart = 100;
399 rc = SetWaitableTimer(pTimer->hTimer, &ll, 0, NULL, NULL, FALSE);
400 AssertMsg(rc, ("CancelWaitableTimer lasterr=%d\n", GetLastError()));
401 }
402
403 rc = RTSemEventSignal(pTimer->Event);
404 AssertRC(rc);
405
406 /*
407 * Wait for the thread to exit.
408 * And if it don't wanna exit, we'll get kill it.
409 */
410 rc = RTThreadWait(pTimer->Thread, 30 * 1000, NULL);
411 if (RT_FAILURE(rc))
412 TerminateThread((HANDLE)RTThreadGetNative(pTimer->Thread), UINT32_MAX);
413
414 /*
415 * Free resource.
416 */
417 rc = CloseHandle(pTimer->hTimer);
418 AssertMsg(rc, ("CloseHandle lasterr=%d\n", GetLastError()));
419
420 RTSemEventDestroy(pTimer->Event);
421 pTimer->Event = NIL_RTSEMEVENT;
422
423 RTMemFree(pTimer);
424 return rc;
425}
426
427
428RTDECL(int) RTTimerStart(PRTTIMER pTimer, uint64_t u64First)
429{
430 /*
431 * Validate input.
432 */
433 AssertPtrReturn(pTimer, VERR_INVALID_POINTER);
434 AssertReturn(pTimer->u32Magic == RTTIMER_MAGIC, VERR_INVALID_MAGIC);
435 AssertReturn(pTimer->Thread != RTThreadSelf(), VERR_INTERNAL_ERROR);
436
437 RTThreadUserReset(pTimer->Thread);
438
439 /*
440 * Already running?
441 */
442 if (!ASMAtomicXchgBool(&pTimer->fSuspended, false))
443 return VERR_TIMER_ACTIVE;
444 LogFlow(("RTTimerStart: pTimer=%p u64First=%llu u64NanoInterval=%llu\n", pTimer, u64First, pTimer->u64NanoInterval));
445
446 /*
447 * Tell the thread to start servicing the timer.
448 * Wait for it to ACK the request to avoid reset races.
449 */
450 ASMAtomicUoWriteU64(&pTimer->u64NanoFirst, u64First);
451 ASMAtomicUoWriteU64(&pTimer->iTick, 0);
452 int rc = RTSemEventSignal(pTimer->Event);
453 if (RT_SUCCESS(rc))
454 {
455 rc = RTThreadUserWait(pTimer->Thread, 45*1000);
456 AssertRC(rc);
457 RTThreadUserReset(pTimer->Thread);
458 }
459 else
460 AssertRC(rc);
461
462 if (RT_FAILURE(rc))
463 ASMAtomicXchgBool(&pTimer->fSuspended, true);
464 return rc;
465}
466
467
468RTDECL(int) RTTimerStop(PRTTIMER pTimer)
469{
470 /*
471 * Validate input.
472 */
473 AssertPtrReturn(pTimer, VERR_INVALID_POINTER);
474 AssertReturn(pTimer->u32Magic == RTTIMER_MAGIC, VERR_INVALID_MAGIC);
475
476 RTThreadUserReset(pTimer->Thread);
477
478 /*
479 * Already running?
480 */
481 if (ASMAtomicXchgBool(&pTimer->fSuspended, true))
482 return VERR_TIMER_SUSPENDED;
483 LogFlow(("RTTimerStop: pTimer=%p\n", pTimer));
484
485 /*
486 * Tell the thread to stop servicing the timer.
487 */
488 int rc = VINF_SUCCESS;
489 if (RTThreadSelf() != pTimer->Thread)
490 {
491 LARGE_INTEGER ll = {0};
492 ll.LowPart = 100;
493 rc = SetWaitableTimer(pTimer->hTimer, &ll, 0, NULL, NULL, FALSE);
494 AssertMsg(rc, ("SetWaitableTimer lasterr=%d\n", GetLastError()));
495 rc = RTThreadUserWait(pTimer->Thread, 45*1000);
496 AssertRC(rc);
497 RTThreadUserReset(pTimer->Thread);
498 }
499
500 return rc;
501}
502
503
504RTDECL(int) RTTimerChangeInterval(PRTTIMER pTimer, uint64_t u64NanoInterval)
505{
506 AssertPtrReturn(pTimer, VERR_INVALID_POINTER);
507 AssertReturn(pTimer->u32Magic == RTTIMER_MAGIC, VERR_INVALID_MAGIC);
508 NOREF(u64NanoInterval);
509 return VERR_NOT_SUPPORTED;
510}
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use