Reimplemented dispatcher database lock and synchronization primitives.
[reactos.git] / reactos / ntoskrnl / ke / timer.c
1 /* $Id: timer.c,v 1.63 2003/11/02 01:15:15 ekohl Exp $
2 *
3 * COPYRIGHT: See COPYING in the top level directory
4 * PROJECT: ReactOS kernel
5 * FILE: ntoskrnl/ke/timer.c
6 * PURPOSE: Handle timers
7 * PROGRAMMER: David Welch (welch@mcmail.com)
8 * UPDATE HISTORY:
9 * 28/05/98: Created
10 * 12/3/99: Phillip Susi: enabled the timers, fixed spin lock
11 */
12
13 /* NOTES ******************************************************************/
14 /*
15 * System time units are 100-nanosecond intervals
16 */
17
18 /* INCLUDES ***************************************************************/
19
20 #include <limits.h>
21 #include <ddk/ntddk.h>
22 #include <internal/ke.h>
23 #include <internal/id.h>
24 #include <internal/ps.h>
25
26 #define NDEBUG
27 #include <internal/debug.h>
28
29 /* TYPES *****************************************************************/
30
31 #define TIMER_IRQ 0
32
33 /* GLOBALS ****************************************************************/
34
35 /*
36 * Current time
37 */
38 static LARGE_INTEGER boot_time = (LARGE_INTEGER)0LL;
39 static LARGE_INTEGER system_time = (LARGE_INTEGER)0LL;
40
41 /*
42 * Number of timer interrupts since initialisation
43 */
44 volatile ULONGLONG KeTickCount = 0;
45 volatile ULONG KiRawTicks = 0;
46
47 /*
48 * The increment in the system clock every timer tick (in system time units)
49 *
50 * = (1/18.2)*10^9
51 *
52 * RJJ was 54945055
53 */
54 #define CLOCK_INCREMENT (100000)
55
56 /*
57 * PURPOSE: List of timers
58 */
59 static LIST_ENTRY TimerListHead;
60 static KSPIN_LOCK TimerListLock;
61 static KSPIN_LOCK TimerValueLock;
62 static KDPC ExpireTimerDpc;
63
64 /* must raise IRQL to PROFILE_LEVEL and grab spin lock there, to sync with ISR */
65
66 extern ULONG PiNrRunnableThreads;
67
68 #define MICROSECONDS_PER_TICK (10000)
69 #define TICKS_TO_CALIBRATE (1)
70 #define CALIBRATE_PERIOD (MICROSECONDS_PER_TICK * TICKS_TO_CALIBRATE)
71 #define SYSTEM_TIME_UNITS_PER_MSEC (10000)
72
73 static BOOLEAN TimerInitDone = FALSE;
74
75 /* FUNCTIONS **************************************************************/
76
77
78 NTSTATUS STDCALL
79 NtQueryTimerResolution(OUT PULONG MinimumResolution,
80 OUT PULONG MaximumResolution,
81 OUT PULONG ActualResolution)
82 {
83 UNIMPLEMENTED;
84 }
85
86
87 NTSTATUS STDCALL
88 NtSetTimerResolution(IN ULONG RequestedResolution,
89 IN BOOL SetOrUnset,
90 OUT PULONG ActualResolution)
91 {
92 UNIMPLEMENTED;
93 }
94
95
96 NTSTATUS STDCALL
97 NtQueryPerformanceCounter(IN PLARGE_INTEGER Counter,
98 IN PLARGE_INTEGER Frequency)
99 {
100 LARGE_INTEGER PerfCounter;
101 LARGE_INTEGER PerfFrequency;
102
103 PerfCounter = KeQueryPerformanceCounter(&PerfFrequency);
104
105 if (Counter != NULL)
106 Counter->QuadPart = PerfCounter.QuadPart;
107
108 if (Frequency != NULL)
109 Frequency->QuadPart = PerfFrequency.QuadPart;
110
111 return(STATUS_SUCCESS);
112 }
113
114
115 NTSTATUS STDCALL
116 NtDelayExecution(IN ULONG Alertable,
117 IN TIME* Interval)
118 {
119 NTSTATUS Status;
120 LARGE_INTEGER Timeout;
121
122 Timeout = *((PLARGE_INTEGER)Interval);
123 DPRINT("NtDelayExecution(Alertable %d, Internal %x) IntervalP %x\n",
124 Alertable, Internal, Timeout);
125
126 DPRINT("Execution delay is %d/%d\n",
127 Timeout.u.HighPart, Timeout.u.LowPart);
128 Status = KeDelayExecutionThread(UserMode, Alertable, &Timeout);
129 return(Status);
130 }
131
132
133 /*
134 * @implemented
135 */
136 NTSTATUS STDCALL
137 KeDelayExecutionThread (KPROCESSOR_MODE WaitMode,
138 BOOLEAN Alertable,
139 PLARGE_INTEGER Interval)
140 /*
141 * FUNCTION: Puts the current thread into an alertable or nonalertable
142 * wait state for a given internal
143 * ARGUMENTS:
144 * WaitMode = Processor mode in which the caller is waiting
145 * Altertable = Specifies if the wait is alertable
146 * Interval = Specifies the interval to wait
147 * RETURNS: Status
148 */
149 {
150 PKTHREAD Thread = KeGetCurrentThread();
151
152 KeInitializeTimer(&Thread->Timer);
153 KeSetTimer(&Thread->Timer, *Interval, NULL);
154 return (KeWaitForSingleObject(&Thread->Timer,
155 Executive,
156 UserMode,
157 Alertable,
158 NULL));
159 }
160
161
162 /*
163 * @implemented
164 */
165 ULONG STDCALL
166 KeQueryTimeIncrement(VOID)
167 /*
168 * FUNCTION: Gets the increment (in 100-nanosecond units) that is added to
169 * the system clock every time the clock interrupts
170 * RETURNS: The increment
171 */
172 {
173 return(CLOCK_INCREMENT);
174 }
175
176
177 /*
178 * @implemented
179 */
180 VOID STDCALL
181 KeQuerySystemTime(PLARGE_INTEGER CurrentTime)
182 /*
183 * FUNCTION: Gets the current system time
184 * ARGUMENTS:
185 * CurrentTime (OUT) = The routine stores the current time here
186 * NOTE: The time is the number of 100-nanosecond intervals since the
187 * 1st of January, 1601.
188 */
189 {
190 KIRQL oldIrql;
191
192 KeRaiseIrql(PROFILE_LEVEL, &oldIrql);
193 KeAcquireSpinLockAtDpcLevel(&TimerValueLock);
194 *CurrentTime = system_time;
195 KeReleaseSpinLock(&TimerValueLock, oldIrql);
196 }
197
198
199 NTSTATUS STDCALL
200 NtGetTickCount (PULONG UpTime)
201 {
202 LARGE_INTEGER TickCount;
203 if (UpTime == NULL)
204 return(STATUS_INVALID_PARAMETER);
205 KeQueryTickCount(&TickCount);
206 *UpTime = TickCount.u.LowPart;
207 return (STATUS_SUCCESS);
208 }
209
210
211 /*
212 * @implemented
213 */
214 BOOLEAN STDCALL
215 KeSetTimer (PKTIMER Timer,
216 LARGE_INTEGER DueTime,
217 PKDPC Dpc)
218 /*
219 * FUNCTION: Sets the absolute or relative interval at which a timer object
220 * is to be set to the signaled state and optionally supplies a
221 * CustomTimerDpc to be executed when the timer expires.
222 * ARGUMENTS:
223 * Timer = Points to a previously initialized timer object
224 * DueTimer = If positive then absolute time to expire at
225 * If negative then the relative time to expire at
226 * Dpc = If non-NULL then a dpc to be called when the timer expires
227 * RETURNS: True if the timer was already in the system timer queue
228 * False otherwise
229 */
230 {
231 return(KeSetTimerEx(Timer, DueTime, 0, Dpc));
232 }
233
234 /*
235 * @implemented
236 */
237 BOOLEAN STDCALL
238 KeSetTimerEx (PKTIMER Timer,
239 LARGE_INTEGER DueTime,
240 LONG Period,
241 PKDPC Dpc)
242 /*
243 * FUNCTION: Sets the absolute or relative interval at which a timer object
244 * is to be set to the signaled state and optionally supplies a
245 * CustomTimerDpc to be executed when the timer expires.
246 * ARGUMENTS:
247 * Timer = Points to a previously initialized timer object
248 * DueTimer = If positive then absolute time to expire at
249 * If negative then the relative time to expire at
250 * Dpc = If non-NULL then a dpc to be called when the timer expires
251 * RETURNS: True if the timer was already in the system timer queue
252 * False otherwise
253 */
254 {
255 KIRQL oldlvl;
256 LARGE_INTEGER SystemTime;
257
258 DPRINT("KeSetTimerEx(Timer %x), DueTime: \n",Timer);
259
260 KeRaiseIrql(PROFILE_LEVEL, &oldlvl);
261 KeAcquireSpinLockAtDpcLevel(&TimerValueLock);
262
263 SystemTime = system_time;
264
265 KeReleaseSpinLock(&TimerValueLock, DISPATCH_LEVEL);
266 KeAcquireSpinLockAtDpcLevel(&TimerListLock);
267
268 Timer->Dpc = Dpc;
269 if (DueTime.QuadPart < 0)
270 {
271
272 Timer->DueTime.QuadPart = SystemTime.QuadPart - DueTime.QuadPart;
273 }
274 else
275 {
276 Timer->DueTime.QuadPart = DueTime.QuadPart;
277 }
278 Timer->Period = Period;
279 Timer->Header.SignalState = FALSE;
280 if (Timer->TimerListEntry.Flink != NULL)
281 {
282 KeReleaseSpinLock(&TimerListLock, oldlvl);
283 return(TRUE);
284 }
285 InsertTailList(&TimerListHead,&Timer->TimerListEntry);
286 KeReleaseSpinLock(&TimerListLock, oldlvl);
287
288 return(FALSE);
289 }
290
291 /*
292 * @implemented
293 */
294 BOOLEAN STDCALL
295 KeCancelTimer (PKTIMER Timer)
296 /*
297 * FUNCTION: Removes a timer from the system timer list
298 * ARGUMENTS:
299 * Timer = timer to cancel
300 * RETURNS: True if the timer was running
301 * False otherwise
302 */
303 {
304 KIRQL oldlvl;
305
306 DPRINT("KeCancelTimer(Timer %x)\n",Timer);
307
308 KeAcquireSpinLock(&TimerListLock, &oldlvl);
309
310 if (Timer->TimerListEntry.Flink == NULL)
311 {
312 KeReleaseSpinLock(&TimerListLock, oldlvl);
313 return(FALSE);
314 }
315 RemoveEntryList(&Timer->TimerListEntry);
316 Timer->TimerListEntry.Flink = Timer->TimerListEntry.Blink = NULL;
317 KeReleaseSpinLock(&TimerListLock, oldlvl);
318
319 return(TRUE);
320 }
321
322 /*
323 * @implemented
324 */
325 BOOLEAN STDCALL
326 KeReadStateTimer (PKTIMER Timer)
327 {
328 return(Timer->Header.SignalState);
329 }
330
331 /*
332 * @implemented
333 */
334 VOID STDCALL
335 KeInitializeTimer (PKTIMER Timer)
336 /*
337 * FUNCTION: Initalizes a kernel timer object
338 * ARGUMENTS:
339 * Timer = caller supplied storage for the timer
340 * NOTE: This function initializes a notification timer
341 */
342 {
343 KeInitializeTimerEx(Timer, NotificationTimer);
344 }
345
346 /*
347 * @implemented
348 */
349 VOID STDCALL
350 KeInitializeTimerEx (PKTIMER Timer,
351 TIMER_TYPE Type)
352 /*
353 * FUNCTION: Initializes a kernel timer object
354 * ARGUMENTS:
355 * Timer = caller supplied storage for the timer
356 * Type = the type of timer (notification or synchronization)
357 * NOTE: When a notification type expires all waiting threads are released
358 * and the timer remains signalled until it is explicitly reset. When a
359 * syncrhonization timer expires its state is set to signalled until a
360 * single waiting thread is released and then the timer is reset.
361 */
362 {
363 ULONG IType;
364
365 if (Type == NotificationTimer)
366 {
367 IType = InternalNotificationTimer;
368 }
369 else if (Type == SynchronizationTimer)
370 {
371 IType = InternalSynchronizationTimer;
372 }
373 else
374 {
375 assert(FALSE);
376 return;
377 }
378
379 KeInitializeDispatcherHeader(&Timer->Header,
380 IType,
381 sizeof(KTIMER) / sizeof(ULONG),
382 FALSE);
383 Timer->TimerListEntry.Flink = Timer->TimerListEntry.Blink = NULL;
384 }
385
386 /*
387 * @implemented
388 */
389 VOID STDCALL
390 KeQueryTickCount(PLARGE_INTEGER TickCount)
391 /*
392 * FUNCTION: Returns the number of ticks since the system was booted
393 * ARGUMENTS:
394 * TickCount (OUT) = Points to storage for the number of ticks
395 */
396 {
397 TickCount->QuadPart = KeTickCount;
398 }
399
400 STATIC VOID
401 HandleExpiredTimer(PKTIMER current)
402 {
403 KIRQL OldIrql;
404
405 DPRINT("HandleExpiredTime(current %x)\n",current);
406 if (current->Dpc != NULL)
407 {
408 DPRINT("current->Dpc %x current->Dpc->DeferredRoutine %x\n",
409 current->Dpc, current->Dpc->DeferredRoutine);
410 KeInsertQueueDpc(current->Dpc,
411 NULL,
412 NULL);
413 DPRINT("Finished dpc routine\n");
414 }
415 OldIrql = KeAcquireDispatcherDatabaseLock ();
416 current->Header.SignalState = TRUE;
417 KeDispatcherObjectWake(&current->Header);
418 KeReleaseDispatcherDatabaseLock (OldIrql);
419 if (current->Period != 0)
420 {
421 current->DueTime.QuadPart +=
422 current->Period * SYSTEM_TIME_UNITS_PER_MSEC;
423 }
424 else
425 {
426 RemoveEntryList(&current->TimerListEntry);
427 current->TimerListEntry.Flink = current->TimerListEntry.Blink = NULL;
428 }
429 }
430
431 VOID STDCALL
432 KeExpireTimers(PKDPC Dpc,
433 PVOID Context1,
434 PVOID Arg1,
435 PVOID Arg2)
436 {
437 PLIST_ENTRY current_entry = NULL;
438 PKTIMER current = NULL;
439 ULONG Eip = (ULONG)Arg1;
440 KIRQL oldIrql;
441 LARGE_INTEGER SystemTime;
442
443 DPRINT("KeExpireTimers()\n");
444
445 KeRaiseIrql(PROFILE_LEVEL, &oldIrql);
446 KeAcquireSpinLockAtDpcLevel(&TimerValueLock);
447
448 SystemTime = system_time;
449
450 KeReleaseSpinLock(&TimerValueLock, oldIrql);
451 KeAcquireSpinLockAtDpcLevel(&TimerListLock);
452
453 if (KeGetCurrentIrql() > DISPATCH_LEVEL)
454 {
455 DPRINT1("-----------------------------\n");
456 KEBUGCHECK(0);
457 }
458
459
460 current_entry = TimerListHead.Flink;
461
462 while (current_entry != &TimerListHead)
463 {
464 current = CONTAINING_RECORD(current_entry, KTIMER, TimerListEntry);
465
466 current_entry = current_entry->Flink;
467 if ((ULONGLONG) SystemTime.QuadPart >= current->DueTime.QuadPart)
468 {
469 HandleExpiredTimer(current);
470 }
471 }
472
473 KiAddProfileEvent(ProfileTime, Eip);
474
475 KeReleaseSpinLockFromDpcLevel(&TimerListLock);
476 }
477
478
479 VOID
480 KiUpdateSystemTime(KIRQL oldIrql,
481 ULONG Eip)
482 /*
483 * FUNCTION: Handles a timer interrupt
484 */
485 {
486
487 assert(KeGetCurrentIrql() == PROFILE_LEVEL);
488
489 KiRawTicks++;
490
491 if (TimerInitDone == FALSE)
492 {
493 return;
494 }
495 /*
496 * Increment the number of timers ticks
497 */
498 KeTickCount++;
499 SharedUserData->TickCountLow++;
500
501 KeAcquireSpinLockAtDpcLevel(&TimerValueLock);
502 system_time.QuadPart += CLOCK_INCREMENT;
503 KeReleaseSpinLockFromDpcLevel(&TimerValueLock);
504
505 /*
506 * Queue a DPC that will expire timers
507 */
508 KeInsertQueueDpc(&ExpireTimerDpc, (PVOID)Eip, 0);
509 }
510
511
512 VOID INIT_FUNCTION
513 KeInitializeTimerImpl(VOID)
514 /*
515 * FUNCTION: Initializes timer irq handling
516 * NOTE: This is only called once from main()
517 */
518 {
519 TIME_FIELDS TimeFields;
520 LARGE_INTEGER SystemBootTime;
521
522 DPRINT("KeInitializeTimerImpl()\n");
523 InitializeListHead(&TimerListHead);
524 KeInitializeSpinLock(&TimerListLock);
525 KeInitializeSpinLock(&TimerValueLock);
526 KeInitializeDpc(&ExpireTimerDpc, KeExpireTimers, 0);
527 TimerInitDone = TRUE;
528 /*
529 * Calculate the starting time for the system clock
530 */
531 HalQueryRealTimeClock(&TimeFields);
532 RtlTimeFieldsToTime(&TimeFields, &SystemBootTime);
533 boot_time=SystemBootTime;
534 system_time=boot_time;
535
536 SharedUserData->TickCountLow = 0;
537 SharedUserData->TickCountMultiplier = 167783691; // 2^24 * 1193182 / 119310
538
539 DPRINT("Finished KeInitializeTimerImpl()\n");
540 }