- implementation of RtlReg* (read access), RtlEvent*, RtlSemaphore*,
[wine] / dlls / winmm / time.c
1 /* -*- tab-width: 8; c-basic-offset: 4 -*- */
2
3 /*
4  * MMSYTEM time functions
5  *
6  * Copyright 1993 Martin Ayotte
7  */
8
9 #include <time.h>
10 #include <sys/time.h>
11 #include "winuser.h"
12 #include "winemm.h"
13 #include "services.h"
14 #include "syslevel.h"
15 #include "debugtools.h"
16
17 DEFAULT_DEBUG_CHANNEL(mmtime)
18
19 /*
20  * FIXME
21  * We're using "1" as the mininum resolution to the timer,
22  * as Windows 95 does, according to the docs. Maybe it should
23  * depend on the computers resources!
24  */
25 #define MMSYSTIME_MININTERVAL (1)
26 #define MMSYSTIME_MAXINTERVAL (65535)
27
28 /* ### start build ### */
29 extern WORD CALLBACK TIME_CallTo16_word_wwlll(FARPROC16,WORD,WORD,LONG,LONG,LONG);
30 /* ### stop build ### */
31
32 static  void    TIME_TriggerCallBack(LPWINE_TIMERENTRY lpTimer)
33 {
34     TRACE("before CallBack => lpFunc=%p wTimerID=%04X dwUser=%08lX !\n",
35           lpTimer->lpFunc, lpTimer->wTimerID, lpTimer->dwUser);
36         
37     /* - TimeProc callback that is called here is something strange, under Windows 3.1x it is called 
38      *          during interrupt time,  is allowed to execute very limited number of API calls (like
39      *          PostMessage), and must reside in DLL (therefore uses stack of active application). So I 
40      *       guess current implementation via SetTimer has to be improved upon.         
41      */
42     switch (lpTimer->wFlags & 0x30) {
43     case TIME_CALLBACK_FUNCTION:
44         if (lpTimer->wFlags & WINE_TIMER_IS32)
45             ((LPTIMECALLBACK)lpTimer->lpFunc)(lpTimer->wTimerID, 0, lpTimer->dwUser, 0, 0);
46         else
47             TIME_CallTo16_word_wwlll(lpTimer->lpFunc, lpTimer->wTimerID, 0,
48                                      lpTimer->dwUser, 0, 0);
49         break;
50     case TIME_CALLBACK_EVENT_SET:
51         SetEvent((HANDLE)lpTimer->lpFunc);
52         break;
53     case TIME_CALLBACK_EVENT_PULSE:
54         PulseEvent((HANDLE)lpTimer->lpFunc);
55         break;
56     default:
57         FIXME("Unknown callback type 0x%04x for mmtime callback (%p), ignored.\n", 
58               lpTimer->wFlags, lpTimer->lpFunc);
59         break;
60     }
61     TRACE("after CallBack !\n");
62 }
63
64 /**************************************************************************
65  *           TIME_MMSysTimeCallback
66  */
67 static void CALLBACK TIME_MMSysTimeCallback(ULONG_PTR ptr_)
68 {
69     LPWINE_TIMERENTRY   lpTimer, lpNextTimer;
70     LPWINE_MM_IDATA     iData = (LPWINE_MM_IDATA)ptr_;
71     DWORD               delta = GetTickCount() - iData->mmSysTimeMS;
72     int                 idx;
73
74     TRACE("Time delta: %ld\n", delta);
75
76     while (delta >= MMSYSTIME_MININTERVAL) {
77         delta -= MMSYSTIME_MININTERVAL;
78         iData->mmSysTimeMS += MMSYSTIME_MININTERVAL;
79         
80         /* since timeSetEvent() and timeKillEvent() can be called
81          * from 16 bit code, there are cases where win16 lock is
82          * locked upon entering timeSetEvent(), and then the mm timer 
83          * critical section is locked. This function cannot call the
84          * timer callback with the crit sect locked (because callback
85          * may need to acquire Win16 lock, thus providing a deadlock
86          * situation).
87          * To cope with that, we just copy the WINE_TIMERENTRY struct
88          * that need to trigger the callback, and call it without the
89          * mm timer crit sect locked. The bad side of this 
90          * implementation is that, in some cases, the callback may be
91          * invoked *after* a timer has been destroyed...
92          * EPP 99/07/13
93          */
94         idx = 0;
95         
96         EnterCriticalSection(&iData->cs);
97         for (lpTimer = iData->lpTimerList; lpTimer != NULL; ) {
98             lpNextTimer = lpTimer->lpNext;
99             if (lpTimer->uCurTime < MMSYSTIME_MININTERVAL) {
100                 /* since lpTimer->wDelay is >= MININTERVAL, wCurTime value
101                  * shall be correct (>= 0)
102                  */
103                 lpTimer->uCurTime += lpTimer->wDelay - MMSYSTIME_MININTERVAL;
104                 if (lpTimer->lpFunc) {
105                     if (idx == iData->nSizeLpTimers) {
106                         iData->lpTimers = (LPWINE_TIMERENTRY)
107                             HeapReAlloc(GetProcessHeap(), 0, 
108                                         iData->lpTimers, 
109                                         ++iData->nSizeLpTimers * sizeof(WINE_TIMERENTRY));
110                     }
111                     iData->lpTimers[idx++] = *lpTimer;
112                 }
113                 /* TIME_ONESHOT is defined as 0 */
114                 if (!(lpTimer->wFlags & TIME_PERIODIC))
115                     timeKillEvent(lpTimer->wTimerID);
116             } else {
117                 lpTimer->uCurTime -= MMSYSTIME_MININTERVAL;
118             }
119             lpTimer = lpNextTimer;
120         }
121         LeaveCriticalSection(&iData->cs);
122         
123         while (idx > 0) {
124             TIME_TriggerCallBack(&iData->lpTimers[--idx]);
125         }
126     }
127 }
128
129 /**************************************************************************
130  *                              MULTIMEDIA_MMTimeStart          [internal]
131  */
132 static  LPWINE_MM_IDATA MULTIMEDIA_MMTimeStart(void)
133 {
134     LPWINE_MM_IDATA     iData = MULTIMEDIA_GetIData();
135
136     if (IsBadWritePtr(iData, sizeof(WINE_MM_IDATA))) {
137         ERR("iData is not correctly set, please report. Expect failure.\n");
138         return 0;
139     }
140     /* FIXME: the service timer is never killed, even when process is
141      * detached from this DLL
142      */
143     /* one could think it's possible to stop the service thread activity when no more
144      * mm timers are active, but this would require to keep mmSysTimeMS up-to-date
145      * without being incremented within the service thread callback.
146      */
147     if (!iData->hMMTimer) {
148         iData->mmSysTimeMS = GetTickCount();
149         iData->lpTimerList = NULL;
150         /* 10ms seems a reasonable value ?? */
151         iData->hMMTimer = SERVICE_AddTimer(10*1000L, TIME_MMSysTimeCallback, (DWORD)iData);
152     }
153
154     return iData;
155 }
156
157 /**************************************************************************
158  *                              timeGetSystemTime       [WINMM.140]
159  */
160 MMRESULT WINAPI timeGetSystemTime(LPMMTIME lpTime, UINT wSize)
161 {
162     TRACE("(%p, %u);\n", lpTime, wSize);
163
164     if (wSize >= sizeof(*lpTime)) {
165         lpTime->wType = TIME_MS;
166         lpTime->u.ms = MULTIMEDIA_MMTimeStart()->mmSysTimeMS;
167
168         TRACE("=> %lu\n", lpTime->u.ms);
169     }
170
171     return 0;
172 }
173
174 /**************************************************************************
175  *                              timeGetSystemTime       [MMSYSTEM.601]
176  */
177 MMRESULT16 WINAPI timeGetSystemTime16(LPMMTIME16 lpTime, UINT16 wSize)
178 {
179     TRACE("(%p, %u);\n", lpTime, wSize);
180
181     if (wSize >= sizeof(*lpTime)) {
182         lpTime->wType = TIME_MS;
183         lpTime->u.ms = MULTIMEDIA_MMTimeStart()->mmSysTimeMS;
184
185         TRACE("=> %lu\n", lpTime->u.ms);
186     }
187
188     return 0;
189 }
190
191 /**************************************************************************
192  *                              timeSetEventInternal    [internal]
193  */
194 static  WORD    timeSetEventInternal(UINT wDelay, UINT wResol,
195                                      FARPROC16 lpFunc, DWORD dwUser, UINT wFlags)
196 {
197     WORD                wNewID = 0;
198     LPWINE_TIMERENTRY   lpNewTimer;
199     LPWINE_TIMERENTRY   lpTimer;
200     LPWINE_MM_IDATA     iData;
201
202     TRACE("(%u, %u, %p, %08lX, %04X);\n", wDelay, wResol, lpFunc, dwUser, wFlags);
203
204     lpNewTimer = (LPWINE_TIMERENTRY)HeapAlloc(GetProcessHeap(), 0, sizeof(WINE_TIMERENTRY));
205     if (lpNewTimer == NULL)
206         return 0;
207
208     if (wDelay < MMSYSTIME_MININTERVAL || wDelay > MMSYSTIME_MAXINTERVAL)
209         return 0;
210
211     iData = MULTIMEDIA_MMTimeStart();
212
213     lpNewTimer->uCurTime = wDelay;
214     lpNewTimer->wDelay = wDelay;
215     lpNewTimer->wResol = wResol;
216     lpNewTimer->lpFunc = lpFunc;
217     lpNewTimer->dwUser = dwUser;
218     lpNewTimer->wFlags = wFlags;
219
220     EnterCriticalSection(&iData->cs);
221
222     for (lpTimer = iData->lpTimerList; lpTimer != NULL; lpTimer = lpTimer->lpNext) {
223         wNewID = MAX(wNewID, lpTimer->wTimerID);
224     }
225     
226     lpNewTimer->lpNext = iData->lpTimerList;
227     iData->lpTimerList = lpNewTimer;
228     lpNewTimer->wTimerID = wNewID + 1;
229
230     LeaveCriticalSection(&iData->cs);
231
232     TRACE("=> %u\n", wNewID + 1);
233
234     return wNewID + 1;
235 }
236
237 /**************************************************************************
238  *                              timeSetEvent            [MMSYSTEM.602]
239  */
240 MMRESULT WINAPI timeSetEvent(UINT wDelay, UINT wResol, LPTIMECALLBACK lpFunc,
241                              DWORD dwUser, UINT wFlags)
242 {
243     if (wFlags & WINE_TIMER_IS32)
244         WARN("Unknown windows flag... wine internally used.. ooch\n");
245
246     return timeSetEventInternal(wDelay, wResol, (FARPROC16)lpFunc, 
247                                 dwUser, wFlags|WINE_TIMER_IS32);
248 }
249
250 /**************************************************************************
251  *                              timeSetEvent            [MMSYSTEM.602]
252  */
253 MMRESULT16 WINAPI timeSetEvent16(UINT16 wDelay, UINT16 wResol, LPTIMECALLBACK16 lpFunc, 
254                                  DWORD dwUser, UINT16 wFlags)
255 {
256     if (wFlags & WINE_TIMER_IS32)
257         WARN("Unknown windows flag... wine internally used.. ooch\n");
258
259     return timeSetEventInternal(wDelay, wResol, (FARPROC16)lpFunc, 
260                                 dwUser, wFlags & ~WINE_TIMER_IS32);
261 }
262
263 /**************************************************************************
264  *                              timeKillEvent           [WINMM.142]
265  */
266 MMRESULT WINAPI timeKillEvent(UINT wID)
267 {
268     LPWINE_TIMERENTRY*  lpTimer;
269     LPWINE_MM_IDATA     iData = MULTIMEDIA_GetIData();
270     MMRESULT            ret = MMSYSERR_INVALPARAM;
271
272     TRACE("(%u)\n", wID);
273     EnterCriticalSection(&iData->cs);
274     /* remove WINE_TIMERENTRY from list */
275     for (lpTimer = &iData->lpTimerList; *lpTimer; lpTimer = &(*lpTimer)->lpNext) {
276         if (wID == (*lpTimer)->wTimerID) {
277             break;
278         }
279     }
280     LeaveCriticalSection(&iData->cs);
281     
282     if (*lpTimer) {
283         LPWINE_TIMERENTRY       lpTemp = *lpTimer;
284
285         /* unlink timer of id 'wID' */
286         *lpTimer = (*lpTimer)->lpNext;
287         HeapFree(GetProcessHeap(), 0, lpTemp);
288         ret = TIMERR_NOERROR;
289     } else {
290         WARN("wID=%u is not a valid timer ID\n", wID);
291     }
292
293     return ret;
294 }
295
296 /**************************************************************************
297  *                              timeKillEvent           [MMSYSTEM.603]
298  */
299 MMRESULT16 WINAPI timeKillEvent16(UINT16 wID)
300 {
301     return timeKillEvent(wID);
302 }
303
304 /**************************************************************************
305  *                              timeGetDevCaps          [WINMM.139]
306  */
307 MMRESULT WINAPI timeGetDevCaps(LPTIMECAPS lpCaps, UINT wSize)
308 {
309     TRACE("(%p, %u) !\n", lpCaps, wSize);
310
311     lpCaps->wPeriodMin = MMSYSTIME_MININTERVAL;
312     lpCaps->wPeriodMax = MMSYSTIME_MAXINTERVAL;
313     return 0;
314 }
315
316 /**************************************************************************
317  *                              timeGetDevCaps          [MMSYSTEM.604]
318  */
319 MMRESULT16 WINAPI timeGetDevCaps16(LPTIMECAPS16 lpCaps, UINT16 wSize)
320 {
321     TRACE("(%p, %u) !\n", lpCaps, wSize);
322
323     lpCaps->wPeriodMin = MMSYSTIME_MININTERVAL;
324     lpCaps->wPeriodMax = MMSYSTIME_MAXINTERVAL;
325     return 0;
326 }
327
328 /**************************************************************************
329  *                              timeBeginPeriod         [WINMM.137]
330  */
331 MMRESULT WINAPI timeBeginPeriod(UINT wPeriod)
332 {
333     TRACE("(%u) !\n", wPeriod);
334
335     if (wPeriod < MMSYSTIME_MININTERVAL || wPeriod > MMSYSTIME_MAXINTERVAL) 
336         return TIMERR_NOCANDO;
337     return 0;
338 }
339
340 /**************************************************************************
341  *                              timeBeginPeriod16       [MMSYSTEM.605]
342  */
343 MMRESULT16 WINAPI timeBeginPeriod16(UINT16 wPeriod)
344 {
345     TRACE("(%u) !\n", wPeriod);
346
347     if (wPeriod < MMSYSTIME_MININTERVAL || wPeriod > MMSYSTIME_MAXINTERVAL) 
348         return TIMERR_NOCANDO;
349     return 0;
350 }
351
352 /**************************************************************************
353  *                              timeEndPeriod           [WINMM.138]
354  */
355 MMRESULT WINAPI timeEndPeriod(UINT wPeriod)
356 {
357     TRACE("(%u) !\n", wPeriod);
358
359     if (wPeriod < MMSYSTIME_MININTERVAL || wPeriod > MMSYSTIME_MAXINTERVAL) 
360         return TIMERR_NOCANDO;
361     return 0;
362 }
363
364 /**************************************************************************
365  *                              timeEndPeriod16         [MMSYSTEM.606]
366  */
367 MMRESULT16 WINAPI timeEndPeriod16(UINT16 wPeriod)
368 {
369     TRACE("(%u) !\n", wPeriod);
370
371     if (wPeriod < MMSYSTIME_MININTERVAL || wPeriod > MMSYSTIME_MAXINTERVAL) 
372         return TIMERR_NOCANDO;
373     return 0;
374 }
375
376 /**************************************************************************
377  *                              timeGetTime    [MMSYSTEM.607][WINMM.141]
378  */
379 DWORD WINAPI timeGetTime(void)
380 {
381     /* FIXME: releasing the win16 lock here is a temporary hack (I hope)
382      * that lets mciavi.drv run correctly
383      */
384     if ( _ConfirmWin16Lock() ) {
385         SYSLEVEL_ReleaseWin16Lock();
386         SYSLEVEL_RestoreWin16Lock();
387     }
388
389     return MULTIMEDIA_MMTimeStart()->mmSysTimeMS;
390 }