xref: /reactos/dll/win32/winmm/time.c (revision 88300ec3)
1 /* -*- tab-width: 8; c-basic-offset: 4 -*- */
2 
3 /*
4  * MMSYSTEM time functions
5  *
6  * Copyright 1993 Martin Ayotte
7  *
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.
12  *
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.
17  *
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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21  */
22 
23 #include "winemm.h"
24 
25 #ifdef HAVE_SYS_TIME_H
26 # include <sys/time.h>
27 #endif
28 #ifdef HAVE_UNISTD_H
29 # include <unistd.h>
30 #endif
31 
32 WINE_DEFAULT_DEBUG_CHANNEL(mmtime);
33 
34 typedef struct tagWINE_TIMERENTRY {
35     UINT			wDelay;
36     UINT			wResol;
37     LPTIMECALLBACK              lpFunc; /* can be lots of things */
38     DWORD			dwUser;
39     UINT16			wFlags;
40     UINT16			wTimerID;
41     DWORD			dwTriggerTime;
42     struct tagWINE_TIMERENTRY*	lpNext;
43 } WINE_TIMERENTRY, *LPWINE_TIMERENTRY;
44 
45 static    HANDLE                TIME_hMMTimer;
46 static    LPWINE_TIMERENTRY 	TIME_TimersList;
47 static    HANDLE                TIME_hKillEvent;
48 static    HANDLE                TIME_hWakeEvent;
49 static    BOOL                  TIME_TimeToDie = TRUE;
50 static    LARGE_INTEGER         TIME_qpcFreq;
51 
52 /*
53  * Some observations on the behavior of winmm on Windows.
54  * First, the call to timeBeginPeriod(xx) can never be used
55  * to raise the timer resolution, only lower it.
56  *
57  * Second, a brief survey of a variety of Win 2k and Win X
58  * machines showed that a 'standard' (aka default) timer
59  * resolution was 1 ms (Win9x is documented as being 1).  However, one
60  * machine had a standard timer resolution of 10 ms.
61  *
62  * Further, if we set our default resolution to 1,
63  * the implementation of timeGetTime becomes GetTickCount(),
64  * and we can optimize the code to reduce overhead.
65  *
66  * Additionally, a survey of Event behaviors shows that
67  * if we request a Periodic event every 50 ms, then Windows
68  * makes sure to trigger that event 20 times in the next
69  * second.  If delays prevent that from happening on exact
70  * schedule, Windows will trigger the events as close
71  * to the original schedule as is possible, and will eventually
72  * bring the event triggers back onto a schedule that is
73  * consistent with what would have happened if there were
74  * no delays.
75  *
76  *   Jeremy White, October 2004
77  */
78 #define MMSYSTIME_MININTERVAL (1)
79 #define MMSYSTIME_MAXINTERVAL (65535)
80 
81 
TIME_TriggerCallBack(LPWINE_TIMERENTRY lpTimer)82 static	void	TIME_TriggerCallBack(LPWINE_TIMERENTRY lpTimer)
83 {
84     TRACE("%04lx:CallBack => lpFunc=%p wTimerID=%04X dwUser=%08lX dwTriggerTime %ld(delta %ld)\n",
85 	  GetCurrentThreadId(), lpTimer->lpFunc, lpTimer->wTimerID, lpTimer->dwUser,
86           lpTimer->dwTriggerTime, GetTickCount() - lpTimer->dwTriggerTime);
87 
88     /* - TimeProc callback that is called here is something strange, under Windows 3.1x it is called
89      * 		during interrupt time,  is allowed to execute very limited number of API calls (like
90      *	    	PostMessage), and must reside in DLL (therefore uses stack of active application). So I
91      *       guess current implementation via SetTimer has to be improved upon.
92      */
93     switch (lpTimer->wFlags & 0x30) {
94     case TIME_CALLBACK_FUNCTION:
95 	    (lpTimer->lpFunc)(lpTimer->wTimerID, 0, lpTimer->dwUser, 0, 0);
96 	break;
97     case TIME_CALLBACK_EVENT_SET:
98 	SetEvent((HANDLE)lpTimer->lpFunc);
99 	break;
100     case TIME_CALLBACK_EVENT_PULSE:
101 	PulseEvent((HANDLE)lpTimer->lpFunc);
102 	break;
103     default:
104 	FIXME("Unknown callback type 0x%04x for mmtime callback (%p), ignored.\n",
105 	      lpTimer->wFlags, lpTimer->lpFunc);
106 	break;
107     }
108 }
109 
110 /**************************************************************************
111  *           TIME_MMSysTimeCallback
112  */
TIME_MMSysTimeCallback()113 static DWORD CALLBACK TIME_MMSysTimeCallback()
114 {
115 static    int				nSizeLpTimers;
116 static    LPWINE_TIMERENTRY		lpTimers;
117 
118     LPWINE_TIMERENTRY   timer, *ptimer, *next_ptimer;
119     int			idx;
120     DWORD               cur_time;
121     DWORD               delta_time;
122     DWORD               ret_time = INFINITE;
123     DWORD               adjust_time;
124 
125 
126     /* optimize for the most frequent case  - no events */
127     if (! TIME_TimersList)
128         return(ret_time);
129 
130     /* since timeSetEvent() and timeKillEvent() can be called
131      * from 16 bit code, there are cases where win16 lock is
132      * locked upon entering timeSetEvent(), and then the mm timer
133      * critical section is locked. This function cannot call the
134      * timer callback with the crit sect locked (because callback
135      * may need to acquire Win16 lock, thus providing a deadlock
136      * situation).
137      * To cope with that, we just copy the WINE_TIMERENTRY struct
138      * that need to trigger the callback, and call it without the
139      * mm timer crit sect locked.
140      * the hKillTimeEvent is used to mark the section where we
141      * handle the callbacks so we can do synchronous kills.
142      * EPP 99/07/13, updated 04/01/10
143      */
144     idx = 0;
145     cur_time = GetTickCount();
146 
147     EnterCriticalSection(&WINMM_cs);
148     for (ptimer = &TIME_TimersList; *ptimer != NULL; ) {
149         timer = *ptimer;
150         next_ptimer = &timer->lpNext;
151         if (cur_time >= timer->dwTriggerTime)
152         {
153             if (timer->lpFunc) {
154                 if (idx == nSizeLpTimers) {
155                     if (lpTimers)
156                         lpTimers = (LPWINE_TIMERENTRY)
157                             HeapReAlloc(GetProcessHeap(), 0, lpTimers,
158                                         ++nSizeLpTimers * sizeof(WINE_TIMERENTRY));
159                     else
160                         lpTimers = (LPWINE_TIMERENTRY)
161                         HeapAlloc(GetProcessHeap(), 0,
162                                   ++nSizeLpTimers * sizeof(WINE_TIMERENTRY));
163                 }
164                 lpTimers[idx++] = *timer;
165 
166             }
167 
168             /* Update the time after we make the copy to preserve
169                the original trigger time    */
170             timer->dwTriggerTime += timer->wDelay;
171 
172             /* TIME_ONESHOT is defined as 0 */
173             if (!(timer->wFlags & TIME_PERIODIC))
174             {
175                 /* unlink timer from timers list */
176                 *ptimer = *next_ptimer;
177                 HeapFree(GetProcessHeap(), 0, timer);
178 
179                 /* We don't need to trigger oneshots again */
180                 delta_time = INFINITE;
181             }
182             else
183             {
184                 /* Compute when this event needs this function
185                     to be called again */
186                 if (timer->dwTriggerTime <= cur_time)
187                     delta_time = 0;
188                 else
189                     delta_time = timer->dwTriggerTime - cur_time;
190             }
191         }
192         else
193             delta_time = timer->dwTriggerTime - cur_time;
194 
195         /* Determine when we need to return to this function */
196         ret_time = min(ret_time, delta_time);
197 
198         ptimer = next_ptimer;
199     }
200     if (TIME_hKillEvent) ResetEvent(TIME_hKillEvent);
201     LeaveCriticalSection(&WINMM_cs);
202 
203     while (idx > 0) TIME_TriggerCallBack(&lpTimers[--idx]);
204     if (TIME_hKillEvent) SetEvent(TIME_hKillEvent);
205 
206     /* Finally, adjust the recommended wait time downward
207        by the amount of time the processing routines
208        actually took */
209     adjust_time = GetTickCount() - cur_time;
210     if (adjust_time > ret_time)
211         ret_time = 0;
212     else
213         ret_time -= adjust_time;
214 
215     /* We return the amount of time our caller should sleep
216        before needing to check in on us again       */
217     return(ret_time);
218 }
219 
220 /**************************************************************************
221  *           TIME_MMSysTimeThread
222  */
TIME_MMSysTimeThread(LPVOID arg)223 static DWORD CALLBACK TIME_MMSysTimeThread(LPVOID arg)
224 {
225     DWORD sleep_time;
226     DWORD rc;
227 
228     TRACE("Starting main winmm thread\n");
229 
230     /* FIXME:  As an optimization, we could have
231                this thread die when there are no more requests
232                pending, and then get recreated on the first
233                new event; it's not clear if that would be worth
234                it or not.                 */
235 
236     while (! TIME_TimeToDie)
237     {
238 	sleep_time = TIME_MMSysTimeCallback();
239 
240         if (sleep_time == 0)
241             continue;
242 
243         rc = WaitForSingleObject(TIME_hWakeEvent, sleep_time);
244         if (rc != WAIT_TIMEOUT && rc != WAIT_OBJECT_0)
245         {
246             FIXME("Unexpected error %ld(%ld) in timer thread\n", rc, GetLastError());
247             break;
248         }
249     }
250     TRACE("Exiting main winmm thread\n");
251     return 0;
252 }
253 
254 /**************************************************************************
255  * 				TIME_MMTimeStart
256  */
TIME_MMTimeStart(void)257 void	TIME_MMTimeStart(void)
258 {
259     if (!TIME_hMMTimer) {
260 	TIME_TimersList = NULL;
261         TIME_hWakeEvent = CreateEventW(NULL, FALSE, FALSE, NULL);
262         TIME_TimeToDie = FALSE;
263 	TIME_hMMTimer = CreateThread(NULL, 0, TIME_MMSysTimeThread, NULL, 0, NULL);
264         SetThreadPriority(TIME_hMMTimer, THREAD_PRIORITY_TIME_CRITICAL);
265     }
266 }
267 
268 /**************************************************************************
269  * 				TIME_MMTimeStop
270  */
TIME_MMTimeStop(void)271 void	TIME_MMTimeStop(void)
272 {
273     if (TIME_hMMTimer) {
274 
275         TIME_TimeToDie = TRUE;
276         SetEvent(TIME_hWakeEvent);
277 
278         /* FIXME: in the worst case, we're going to wait 65 seconds here :-( */
279 	WaitForSingleObject(TIME_hMMTimer, INFINITE);
280 
281 	CloseHandle(TIME_hMMTimer);
282 	CloseHandle(TIME_hWakeEvent);
283 	TIME_hMMTimer = 0;
284         TIME_TimersList = NULL;
285     }
286 }
287 
288 /**************************************************************************
289  * 				timeGetSystemTime	[WINMM.@]
290  */
timeGetSystemTime(LPMMTIME lpTime,UINT wSize)291 MMRESULT WINAPI timeGetSystemTime(LPMMTIME lpTime, UINT wSize)
292 {
293 
294     if (wSize >= sizeof(*lpTime)) {
295 	lpTime->wType = TIME_MS;
296 	lpTime->u.ms = GetTickCount();
297 
298     }
299 
300     return 0;
301 }
302 
303 /**************************************************************************
304  * 				TIME_SetEventInternal	[internal]
305  */
TIME_SetEventInternal(UINT wDelay,UINT wResol,LPTIMECALLBACK lpFunc,DWORD dwUser,UINT wFlags)306 WORD	TIME_SetEventInternal(UINT wDelay, UINT wResol,
307                               LPTIMECALLBACK lpFunc, DWORD dwUser, UINT wFlags)
308 {
309     WORD 		wNewID = 0;
310     LPWINE_TIMERENTRY	lpNewTimer;
311     LPWINE_TIMERENTRY	lpTimer;
312 
313     TRACE("(%u, %u, %p, %08lX, %04X);\n", wDelay, wResol, lpFunc, dwUser, wFlags);
314 
315     if (wDelay < MMSYSTIME_MININTERVAL || wDelay > MMSYSTIME_MAXINTERVAL)
316 	return 0;
317 
318     lpNewTimer = HeapAlloc(GetProcessHeap(), 0, sizeof(WINE_TIMERENTRY));
319     if (lpNewTimer == NULL)
320 	return 0;
321 
322     TIME_MMTimeStart();
323 
324     lpNewTimer->wDelay = wDelay;
325     lpNewTimer->dwTriggerTime = GetTickCount() + wDelay;
326 
327     /* FIXME - wResol is not respected, although it is not clear
328                that we could change our precision meaningfully  */
329     lpNewTimer->wResol = wResol;
330     lpNewTimer->lpFunc = lpFunc;
331     lpNewTimer->dwUser = dwUser;
332     lpNewTimer->wFlags = wFlags;
333 
334     EnterCriticalSection(&WINMM_cs);
335 
336     if ((wFlags & TIME_KILL_SYNCHRONOUS) && !TIME_hKillEvent)
337         TIME_hKillEvent = CreateEventW(NULL, TRUE, TRUE, NULL);
338 
339     for (lpTimer = TIME_TimersList; lpTimer != NULL; lpTimer = lpTimer->lpNext) {
340 	wNewID = max(wNewID, lpTimer->wTimerID);
341     }
342 
343     lpNewTimer->lpNext = TIME_TimersList;
344     TIME_TimersList = lpNewTimer;
345     lpNewTimer->wTimerID = wNewID + 1;
346 
347     LeaveCriticalSection(&WINMM_cs);
348 
349     /* Wake the service thread in case there is work to be done */
350     SetEvent(TIME_hWakeEvent);
351 
352     TRACE("=> %u\n", wNewID + 1);
353 
354     return wNewID + 1;
355 }
356 
357 /**************************************************************************
358  * 				timeSetEvent		[WINMM.@]
359  */
timeSetEvent(UINT wDelay,UINT wResol,LPTIMECALLBACK lpFunc,DWORD_PTR dwUser,UINT wFlags)360 MMRESULT WINAPI timeSetEvent(UINT wDelay, UINT wResol, LPTIMECALLBACK lpFunc,
361                             DWORD_PTR dwUser, UINT wFlags)
362 {
363     return TIME_SetEventInternal(wDelay, wResol, lpFunc,
364                                  dwUser, wFlags);
365 }
366 
367 /**************************************************************************
368  * 				timeKillEvent		[WINMM.@]
369  */
timeKillEvent(UINT wID)370 MMRESULT WINAPI timeKillEvent(UINT wID)
371 {
372     LPWINE_TIMERENTRY   lpSelf = NULL, *lpTimer;
373 
374     TRACE("(%u)\n", wID);
375     EnterCriticalSection(&WINMM_cs);
376     /* remove WINE_TIMERENTRY from list */
377     for (lpTimer = &TIME_TimersList; *lpTimer; lpTimer = &(*lpTimer)->lpNext) {
378 	if (wID == (*lpTimer)->wTimerID) {
379             lpSelf = *lpTimer;
380             /* unlink timer of id 'wID' */
381             *lpTimer = (*lpTimer)->lpNext;
382 	    break;
383 	}
384     }
385     LeaveCriticalSection(&WINMM_cs);
386 
387     if (!lpSelf)
388     {
389         WARN("wID=%u is not a valid timer ID\n", wID);
390         return MMSYSERR_INVALPARAM;
391     }
392     if (lpSelf->wFlags & TIME_KILL_SYNCHRONOUS)
393         WaitForSingleObject(TIME_hKillEvent, INFINITE);
394     HeapFree(GetProcessHeap(), 0, lpSelf);
395     return TIMERR_NOERROR;
396 }
397 
398 /**************************************************************************
399  * 				timeGetDevCaps		[WINMM.@]
400  */
timeGetDevCaps(LPTIMECAPS lpCaps,UINT wSize)401 MMRESULT WINAPI timeGetDevCaps(LPTIMECAPS lpCaps, UINT wSize)
402 {
403     TRACE("(%p, %u)\n", lpCaps, wSize);
404 
405     if (lpCaps == 0) {
406         WARN("invalid lpCaps\n");
407         return TIMERR_NOCANDO;
408     }
409 
410     if (wSize < sizeof(TIMECAPS)) {
411         WARN("invalid wSize\n");
412         return TIMERR_NOCANDO;
413     }
414 
415     lpCaps->wPeriodMin = MMSYSTIME_MININTERVAL;
416     lpCaps->wPeriodMax = MMSYSTIME_MAXINTERVAL;
417     return TIMERR_NOERROR;
418 }
419 
420 /**************************************************************************
421  * 				timeBeginPeriod		[WINMM.@]
422  */
timeBeginPeriod(UINT wPeriod)423 MMRESULT WINAPI timeBeginPeriod(UINT wPeriod)
424 {
425     if (wPeriod < MMSYSTIME_MININTERVAL || wPeriod > MMSYSTIME_MAXINTERVAL)
426         return TIMERR_NOCANDO;
427 
428     /*  High resolution timer requested, use QPC */
429     if (wPeriod <= 5 && TIME_qpcFreq.QuadPart == 0)
430     {
431         if (QueryPerformanceFrequency(&TIME_qpcFreq))
432             TIME_qpcFreq.QuadPart /= 1000;
433     }
434 
435     if (wPeriod > MMSYSTIME_MININTERVAL)
436     {
437         WARN("Stub; we set our timer resolution at minimum\n");
438     }
439 
440     return 0;
441 }
442 
443 /**************************************************************************
444  * 				timeEndPeriod		[WINMM.@]
445  */
timeEndPeriod(UINT wPeriod)446 MMRESULT WINAPI timeEndPeriod(UINT wPeriod)
447 {
448     if (wPeriod < MMSYSTIME_MININTERVAL || wPeriod > MMSYSTIME_MAXINTERVAL)
449         return TIMERR_NOCANDO;
450 
451     /*  High resolution timer no longer requested, stop using QPC */
452     if (wPeriod <= 5 && TIME_qpcFreq.QuadPart != 0)
453         TIME_qpcFreq.QuadPart = 0;
454 
455     if (wPeriod > MMSYSTIME_MININTERVAL)
456     {
457         WARN("Stub; we set our timer resolution at minimum\n");
458     }
459     return 0;
460 }
461 
462 /**************************************************************************
463  * 				timeGetTime    [MMSYSTEM.607]
464  * 				timeGetTime    [WINMM.@]
465  */
timeGetTime(void)466 DWORD WINAPI timeGetTime(void)
467 {
468     LARGE_INTEGER perfCount;
469 #if defined(COMMENTOUTPRIORTODELETING)
470     DWORD       count;
471 
472     /* FIXME: releasing the win16 lock here is a temporary hack (I hope)
473      * that lets mciavi.drv run correctly
474      */
475     if (pFnReleaseThunkLock) pFnReleaseThunkLock(&count);
476     if (pFnRestoreThunkLock) pFnRestoreThunkLock(count);
477 #endif
478     /* Use QPC if a high-resolution timer was requested (<= 5ms) */
479     if (TIME_qpcFreq.QuadPart != 0)
480     {
481         QueryPerformanceCounter(&perfCount);
482         return (DWORD)(perfCount.QuadPart / TIME_qpcFreq.QuadPart);
483     }
484     /* Otherwise continue using GetTickCount */
485     return GetTickCount();
486 }
487