1 /* -*- tab-width: 8; c-basic-offset: 4 -*- */
4 * MMSYSTEM time functions
6 * Copyright 1993 Martin Ayotte
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License as published by the Free Software Foundation; either
11 * version 2.1 of the License, or (at your option) any later version.
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with this library; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
33 #include "wine/debug.h"
35 WINE_DEFAULT_DEBUG_CHANNEL(mmtime
);
37 typedef struct tagWINE_TIMERENTRY
{
40 LPTIMECALLBACK lpFunc
; /* can be lots of things */
45 } WINE_TIMERENTRY
, *LPWINE_TIMERENTRY
;
47 static WINE_TIMERENTRY timers
[16];
48 static UINT timers_created
;
50 static CRITICAL_SECTION TIME_cbcrst
;
51 static CRITICAL_SECTION_DEBUG critsect_debug
=
54 { &critsect_debug
.ProcessLocksList
, &critsect_debug
.ProcessLocksList
},
55 0, 0, { (DWORD_PTR
)(__FILE__
": TIME_cbcrst") }
57 static CRITICAL_SECTION TIME_cbcrst
= { &critsect_debug
, -1, 0, 0, 0, 0 };
59 static HANDLE TIME_hMMTimer
;
60 static CONDITION_VARIABLE TIME_cv
;
63 * Some observations on the behavior of winmm on Windows.
65 * First, the call to timeBeginPeriod(xx) can never be used to
66 * lower the timer resolution (i.e. increase the update
67 * interval), only to increase the timer resolution (i.e. lower
68 * the update interval).
70 * Second, a brief survey of a variety of Win 2k and Win X
71 * machines showed that a 'standard' (aka default) timer
72 * resolution was 1 ms (Win9x is documented as being 1). However, one
73 * machine had a standard timer resolution of 10 ms.
75 * Further, timeBeginPeriod(xx) also affects the resolution of
76 * wait calls such as NtDelayExecution() and
77 * NtWaitForMultipleObjects() which by default round up their
78 * timeout to the nearest multiple of 15.625ms across all Windows
79 * versions. In Wine all of those currently work with sub-1ms
82 * Effective time resolution is a global value that is the max
83 * of the resolutions (i.e. min of update intervals) requested by
84 * all the processes. A lot of programs seem to do
85 * timeBeginPeriod(1) forcing it onto everyone else.
87 * Defaulting to 1ms accuracy in winmm should be safe.
89 * Additionally, a survey of Event behaviors shows that
90 * if we request a Periodic event every 50 ms, then Windows
91 * makes sure to trigger that event 20 times in the next
92 * second. If delays prevent that from happening on exact
93 * schedule, Windows will trigger the events as close
94 * to the original schedule as is possible, and will eventually
95 * bring the event triggers back onto a schedule that is
96 * consistent with what would have happened if there were
99 * Jeremy White, October 2004
100 * Arkadiusz Hiler, August 2020
102 #define MMSYSTIME_MININTERVAL (1)
103 #define MMSYSTIME_MAXINTERVAL (65535)
105 /**************************************************************************
106 * TIME_MMSysTimeCallback
108 static int TIME_MMSysTimeCallback(void)
110 WINE_TIMERENTRY
*timer
, copy
;
113 /* since timeSetEvent() and timeKillEvent() can be called
114 * from 16 bit code, there are cases where win16 lock is
115 * locked upon entering timeSetEvent(), and then the mm timer
116 * critical section is locked. This function cannot call the
117 * timer callback with the crit sect locked (because callback
118 * may need to acquire Win16 lock, thus providing a deadlock
120 * To cope with that, we just copy the WINE_TIMERENTRY struct
121 * that need to trigger the callback, and call it without the
122 * mm timer crit sect locked.
127 for (i
= 0; i
< ARRAY_SIZE(timers
); i
++)
128 if (timers
[i
].wTimerID
) break;
129 if (i
== ARRAY_SIZE(timers
)) return -1;
131 for (i
++; i
< ARRAY_SIZE(timers
); i
++)
133 if (!timers
[i
].wTimerID
) continue;
134 if (timers
[i
].dwTriggerTime
< timer
->dwTriggerTime
)
138 delta_time
= timer
->dwTriggerTime
- timeGetTime();
139 if (delta_time
> 0) break;
141 if (timer
->wFlags
& TIME_PERIODIC
)
143 timer
->dwTriggerTime
+= timer
->wDelay
;
152 switch(timer
->wFlags
& (TIME_CALLBACK_EVENT_SET
|TIME_CALLBACK_EVENT_PULSE
))
154 case TIME_CALLBACK_EVENT_SET
:
155 SetEvent(timer
->lpFunc
);
157 case TIME_CALLBACK_EVENT_PULSE
:
158 PulseEvent(timer
->lpFunc
);
160 case TIME_CALLBACK_FUNCTION
:
162 DWORD_PTR user
= timer
->dwUser
;
163 UINT16 id
= timer
->wTimerID
;
164 UINT16 flags
= timer
->wFlags
;
165 LPTIMECALLBACK func
= timer
->lpFunc
;
167 if (flags
& TIME_KILL_SYNCHRONOUS
) EnterCriticalSection(&TIME_cbcrst
);
168 LeaveCriticalSection(&WINMM_cs
);
170 func(id
, 0, user
, 0, 0);
172 EnterCriticalSection(&WINMM_cs
);
173 if (flags
& TIME_KILL_SYNCHRONOUS
) LeaveCriticalSection(&TIME_cbcrst
);
181 /**************************************************************************
182 * TIME_MMSysTimeThread
184 static DWORD CALLBACK
TIME_MMSysTimeThread(LPVOID arg
)
189 TRACE("Starting main winmm thread\n");
191 EnterCriticalSection(&WINMM_cs
);
194 sleep_time
= TIME_MMSysTimeCallback();
201 ret
= SleepConditionVariableCS(&TIME_cv
, &WINMM_cs
, sleep_time
);
202 if (!ret
&& GetLastError() != ERROR_TIMEOUT
)
204 ERR("Unexpected error in poll: %s(%d)\n", strerror(errno
), errno
);
208 CloseHandle(TIME_hMMTimer
);
209 TIME_hMMTimer
= NULL
;
210 LeaveCriticalSection(&WINMM_cs
);
211 TRACE("Exiting main winmm thread\n");
212 FreeLibraryAndExitThread(arg
, 0);
216 /**************************************************************************
219 static void TIME_MMTimeStart(void)
222 if (TIME_hMMTimer
) return;
224 GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS
, (LPCWSTR
)TIME_MMSysTimeThread
, &mod
);
225 TIME_hMMTimer
= CreateThread(NULL
, 0, TIME_MMSysTimeThread
, mod
, 0, NULL
);
226 SetThreadPriority(TIME_hMMTimer
, THREAD_PRIORITY_TIME_CRITICAL
);
229 /**************************************************************************
232 void TIME_MMTimeStop(void)
235 EnterCriticalSection(&WINMM_cs
);
237 ERR("Timer still active?!\n");
238 CloseHandle(TIME_hMMTimer
);
240 DeleteCriticalSection(&TIME_cbcrst
);
244 /**************************************************************************
245 * timeGetSystemTime [WINMM.@]
247 MMRESULT WINAPI
timeGetSystemTime(LPMMTIME lpTime
, UINT wSize
)
249 if (wSize
>= sizeof(*lpTime
)) {
250 lpTime
->wType
= TIME_MS
;
251 lpTime
->u
.ms
= timeGetTime();
257 /**************************************************************************
258 * timeGetTime [WINMM.@]
260 DWORD WINAPI
timeGetTime(void)
262 LARGE_INTEGER now
, freq
;
264 QueryPerformanceCounter(&now
);
265 QueryPerformanceFrequency(&freq
);
267 return (now
.QuadPart
* 1000) / freq
.QuadPart
;
270 /**************************************************************************
271 * timeSetEvent [WINMM.@]
273 MMRESULT WINAPI
timeSetEvent(UINT wDelay
, UINT wResol
, LPTIMECALLBACK lpFunc
,
274 DWORD_PTR dwUser
, UINT wFlags
)
279 TRACE("(%u, %u, %p, %08lX, %04X);\n", wDelay
, wResol
, lpFunc
, dwUser
, wFlags
);
281 if (wDelay
< MMSYSTIME_MININTERVAL
|| wDelay
> MMSYSTIME_MAXINTERVAL
)
284 EnterCriticalSection(&WINMM_cs
);
286 for (i
= 0; i
< ARRAY_SIZE(timers
); i
++)
287 if (!timers
[i
].wTimerID
) break;
288 if (i
== ARRAY_SIZE(timers
))
290 LeaveCriticalSection(&WINMM_cs
);
294 new_id
= ARRAY_SIZE(timers
)*(++timers_created
) + i
;
295 if (!new_id
) new_id
= ARRAY_SIZE(timers
)*(++timers_created
) + i
;
297 timers
[i
].wDelay
= wDelay
;
298 timers
[i
].dwTriggerTime
= timeGetTime() + wDelay
;
300 /* FIXME - wResol is not respected, although it is not clear
301 that we could change our precision meaningfully */
302 timers
[i
].wResol
= wResol
;
303 timers
[i
].lpFunc
= lpFunc
;
304 timers
[i
].dwUser
= dwUser
;
305 timers
[i
].wFlags
= wFlags
;
306 timers
[i
].wTimerID
= new_id
;
310 LeaveCriticalSection(&WINMM_cs
);
312 /* Wake the service thread in case there is work to be done */
313 WakeConditionVariable(&TIME_cv
);
315 TRACE("=> %u\n", new_id
);
320 /**************************************************************************
321 * timeKillEvent [WINMM.@]
323 MMRESULT WINAPI
timeKillEvent(UINT wID
)
325 WINE_TIMERENTRY
*timer
;
328 TRACE("(%u)\n", wID
);
329 EnterCriticalSection(&WINMM_cs
);
331 timer
= &timers
[wID
% ARRAY_SIZE(timers
)];
332 if (timer
->wTimerID
!= wID
)
334 LeaveCriticalSection(&WINMM_cs
);
335 WARN("wID=%u is not a valid timer ID\n", wID
);
336 return TIMERR_NOCANDO
;
340 flags
= timer
->wFlags
;
341 LeaveCriticalSection(&WINMM_cs
);
343 if (flags
& TIME_KILL_SYNCHRONOUS
)
345 EnterCriticalSection(&TIME_cbcrst
);
346 LeaveCriticalSection(&TIME_cbcrst
);
348 WakeConditionVariable(&TIME_cv
);
349 return TIMERR_NOERROR
;
352 /**************************************************************************
353 * timeGetDevCaps [WINMM.@]
355 MMRESULT WINAPI
timeGetDevCaps(LPTIMECAPS lpCaps
, UINT wSize
)
357 TRACE("(%p, %u)\n", lpCaps
, wSize
);
360 WARN("invalid lpCaps\n");
361 return TIMERR_NOCANDO
;
364 if (wSize
< sizeof(TIMECAPS
)) {
365 WARN("invalid wSize\n");
366 return TIMERR_NOCANDO
;
369 lpCaps
->wPeriodMin
= MMSYSTIME_MININTERVAL
;
370 lpCaps
->wPeriodMax
= MMSYSTIME_MAXINTERVAL
;
371 return TIMERR_NOERROR
;
374 /**************************************************************************
375 * timeBeginPeriod [WINMM.@]
377 MMRESULT WINAPI
timeBeginPeriod(UINT wPeriod
)
379 if (wPeriod
< MMSYSTIME_MININTERVAL
|| wPeriod
> MMSYSTIME_MAXINTERVAL
)
380 return TIMERR_NOCANDO
;
382 if (wPeriod
> MMSYSTIME_MININTERVAL
)
384 WARN("Stub; we set our timer resolution at minimum\n");
390 /**************************************************************************
391 * timeEndPeriod [WINMM.@]
393 MMRESULT WINAPI
timeEndPeriod(UINT wPeriod
)
395 if (wPeriod
< MMSYSTIME_MININTERVAL
|| wPeriod
> MMSYSTIME_MAXINTERVAL
)
396 return TIMERR_NOCANDO
;
398 if (wPeriod
> MMSYSTIME_MININTERVAL
)
400 WARN("Stub; we set our timer resolution at minimum\n");