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 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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