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
;
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
)
142 timer
->dwTriggerTime
+= timer
->wDelay
;
144 switch(timer
->wFlags
& (TIME_CALLBACK_EVENT_SET
|TIME_CALLBACK_EVENT_PULSE
))
146 case TIME_CALLBACK_EVENT_SET
:
147 SetEvent(timer
->lpFunc
);
149 case TIME_CALLBACK_EVENT_PULSE
:
150 PulseEvent(timer
->lpFunc
);
152 case TIME_CALLBACK_FUNCTION
:
154 DWORD_PTR user
= timer
->dwUser
;
155 UINT16 id
= timer
->wTimerID
;
156 UINT16 flags
= timer
->wFlags
;
157 LPTIMECALLBACK func
= timer
->lpFunc
;
159 if (flags
& TIME_KILL_SYNCHRONOUS
) EnterCriticalSection(&TIME_cbcrst
);
160 LeaveCriticalSection(&WINMM_cs
);
162 func(id
, 0, user
, 0, 0);
164 EnterCriticalSection(&WINMM_cs
);
165 if (flags
& TIME_KILL_SYNCHRONOUS
) LeaveCriticalSection(&TIME_cbcrst
);
166 if (id
!= timer
->wTimerID
) timer
= NULL
;
170 if (timer
&& !(timer
->wFlags
& TIME_PERIODIC
))
176 /**************************************************************************
177 * TIME_MMSysTimeThread
179 static DWORD CALLBACK
TIME_MMSysTimeThread(LPVOID arg
)
184 TRACE("Starting main winmm thread\n");
186 EnterCriticalSection(&WINMM_cs
);
189 sleep_time
= TIME_MMSysTimeCallback();
196 ret
= SleepConditionVariableCS(&TIME_cv
, &WINMM_cs
, sleep_time
);
197 if (!ret
&& GetLastError() != ERROR_TIMEOUT
)
199 ERR("Unexpected error in poll: %s(%d)\n", strerror(errno
), errno
);
203 CloseHandle(TIME_hMMTimer
);
204 TIME_hMMTimer
= NULL
;
205 LeaveCriticalSection(&WINMM_cs
);
206 TRACE("Exiting main winmm thread\n");
207 FreeLibraryAndExitThread(arg
, 0);
211 /**************************************************************************
214 static void TIME_MMTimeStart(void)
217 if (TIME_hMMTimer
) return;
219 GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS
, (LPCWSTR
)TIME_MMSysTimeThread
, &mod
);
220 TIME_hMMTimer
= CreateThread(NULL
, 0, TIME_MMSysTimeThread
, mod
, 0, NULL
);
221 SetThreadPriority(TIME_hMMTimer
, THREAD_PRIORITY_TIME_CRITICAL
);
224 /**************************************************************************
227 void TIME_MMTimeStop(void)
230 EnterCriticalSection(&WINMM_cs
);
232 ERR("Timer still active?!\n");
233 CloseHandle(TIME_hMMTimer
);
235 DeleteCriticalSection(&TIME_cbcrst
);
239 /**************************************************************************
240 * timeGetSystemTime [WINMM.@]
242 MMRESULT WINAPI
timeGetSystemTime(LPMMTIME lpTime
, UINT wSize
)
244 if (wSize
>= sizeof(*lpTime
)) {
245 lpTime
->wType
= TIME_MS
;
246 lpTime
->u
.ms
= timeGetTime();
252 /**************************************************************************
253 * timeGetTime [WINMM.@]
255 DWORD WINAPI
timeGetTime(void)
257 LARGE_INTEGER now
, freq
;
259 QueryPerformanceCounter(&now
);
260 QueryPerformanceFrequency(&freq
);
262 return (now
.QuadPart
* 1000) / freq
.QuadPart
;
265 /**************************************************************************
266 * timeSetEvent [WINMM.@]
268 MMRESULT WINAPI
timeSetEvent(UINT wDelay
, UINT wResol
, LPTIMECALLBACK lpFunc
,
269 DWORD_PTR dwUser
, UINT wFlags
)
274 TRACE("(%u, %u, %p, %08IX, %04X);\n", wDelay
, wResol
, lpFunc
, dwUser
, wFlags
);
276 if (wDelay
< MMSYSTIME_MININTERVAL
|| wDelay
> MMSYSTIME_MAXINTERVAL
)
279 EnterCriticalSection(&WINMM_cs
);
281 for (i
= 0; i
< ARRAY_SIZE(timers
); i
++)
282 if (!timers
[i
].wTimerID
) break;
283 if (i
== ARRAY_SIZE(timers
))
285 LeaveCriticalSection(&WINMM_cs
);
289 new_id
= ARRAY_SIZE(timers
)*(++timers_created
) + i
;
290 if (!new_id
) new_id
= ARRAY_SIZE(timers
)*(++timers_created
) + i
;
292 timers
[i
].wDelay
= wDelay
;
293 timers
[i
].dwTriggerTime
= timeGetTime() + wDelay
;
295 /* FIXME - wResol is not respected, although it is not clear
296 that we could change our precision meaningfully */
297 timers
[i
].wResol
= wResol
;
298 timers
[i
].lpFunc
= lpFunc
;
299 timers
[i
].dwUser
= dwUser
;
300 timers
[i
].wFlags
= wFlags
;
301 timers
[i
].wTimerID
= new_id
;
305 LeaveCriticalSection(&WINMM_cs
);
307 /* Wake the service thread in case there is work to be done */
308 WakeConditionVariable(&TIME_cv
);
310 TRACE("=> %u\n", new_id
);
315 /**************************************************************************
316 * timeKillEvent [WINMM.@]
318 MMRESULT WINAPI
timeKillEvent(UINT wID
)
320 WINE_TIMERENTRY
*timer
;
323 TRACE("(%u)\n", wID
);
324 EnterCriticalSection(&WINMM_cs
);
326 timer
= &timers
[wID
% ARRAY_SIZE(timers
)];
327 if (timer
->wTimerID
!= wID
)
329 LeaveCriticalSection(&WINMM_cs
);
330 WARN("wID=%u is not a valid timer ID\n", wID
);
331 return TIMERR_NOCANDO
;
335 flags
= timer
->wFlags
;
336 LeaveCriticalSection(&WINMM_cs
);
338 if (flags
& TIME_KILL_SYNCHRONOUS
)
340 EnterCriticalSection(&TIME_cbcrst
);
341 LeaveCriticalSection(&TIME_cbcrst
);
343 WakeConditionVariable(&TIME_cv
);
344 return TIMERR_NOERROR
;
347 /**************************************************************************
348 * timeGetDevCaps [WINMM.@]
350 MMRESULT WINAPI
timeGetDevCaps(LPTIMECAPS lpCaps
, UINT wSize
)
352 TRACE("(%p, %u)\n", lpCaps
, wSize
);
355 WARN("invalid lpCaps\n");
356 return TIMERR_NOCANDO
;
359 if (wSize
< sizeof(TIMECAPS
)) {
360 WARN("invalid wSize\n");
361 return TIMERR_NOCANDO
;
364 lpCaps
->wPeriodMin
= MMSYSTIME_MININTERVAL
;
365 lpCaps
->wPeriodMax
= MMSYSTIME_MAXINTERVAL
;
366 return TIMERR_NOERROR
;
369 /**************************************************************************
370 * timeBeginPeriod [WINMM.@]
372 MMRESULT WINAPI
timeBeginPeriod(UINT wPeriod
)
374 if (wPeriod
< MMSYSTIME_MININTERVAL
|| wPeriod
> MMSYSTIME_MAXINTERVAL
)
375 return TIMERR_NOCANDO
;
377 if (wPeriod
> MMSYSTIME_MININTERVAL
)
379 WARN("Stub; we set our timer resolution at minimum\n");
385 /**************************************************************************
386 * timeEndPeriod [WINMM.@]
388 MMRESULT WINAPI
timeEndPeriod(UINT wPeriod
)
390 if (wPeriod
< MMSYSTIME_MININTERVAL
|| wPeriod
> MMSYSTIME_MAXINTERVAL
)
391 return TIMERR_NOCANDO
;
393 if (wPeriod
> MMSYSTIME_MININTERVAL
)
395 WARN("Stub; we set our timer resolution at minimum\n");