2003-01-02 Casper S. Hornstrup <chorns@users.sourceforge.net>
[reactos.git] / reactos / ntoskrnl / ke / timer.c
1 /* $Id: timer.c,v 1.55 2003/01/02 23:37:28 chorns 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 HIGH_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 UNIMPLEMENTED;
101 }
102
103
104 NTSTATUS STDCALL
105 NtDelayExecution(IN ULONG Alertable,
106 IN TIME* Interval)
107 {
108 NTSTATUS Status;
109 LARGE_INTEGER Timeout;
110
111 Timeout = *((PLARGE_INTEGER)Interval);
112 DPRINT("NtDelayExecution(Alertable %d, Internal %x) IntervalP %x\n",
113 Alertable, Internal, Timeout);
114
115 DPRINT("Execution delay is %d/%d\n",
116 Timeout.u.HighPart, Timeout.u.LowPart);
117 Status = KeDelayExecutionThread(UserMode, Alertable, &Timeout);
118 return(Status);
119 }
120
121
122 NTSTATUS STDCALL
123 KeDelayExecutionThread (KPROCESSOR_MODE WaitMode,
124 BOOLEAN Alertable,
125 PLARGE_INTEGER Interval)
126 /*
127 * FUNCTION: Puts the current thread into an alertable or nonalertable
128 * wait state for a given internal
129 * ARGUMENTS:
130 * WaitMode = Processor mode in which the caller is waiting
131 * Altertable = Specifies if the wait is alertable
132 * Interval = Specifies the interval to wait
133 * RETURNS: Status
134 */
135 {
136 PKTHREAD Thread = KeGetCurrentThread();
137
138 KeInitializeTimer(&Thread->Timer);
139 KeSetTimer(&Thread->Timer, *Interval, NULL);
140 return (KeWaitForSingleObject(&Thread->Timer,
141 Executive,
142 UserMode,
143 Alertable,
144 NULL));
145 }
146
147
148 ULONG STDCALL
149 KeQueryTimeIncrement(VOID)
150 /*
151 * FUNCTION: Gets the increment (in 100-nanosecond units) that is added to
152 * the system clock every time the clock interrupts
153 * RETURNS: The increment
154 */
155 {
156 return(CLOCK_INCREMENT);
157 }
158
159
160 VOID STDCALL
161 KeQuerySystemTime(PLARGE_INTEGER CurrentTime)
162 /*
163 * FUNCTION: Gets the current system time
164 * ARGUMENTS:
165 * CurrentTime (OUT) = The routine stores the current time here
166 * NOTE: The time is the number of 100-nanosecond intervals since the
167 * 1st of January, 1601.
168 */
169 {
170 KIRQL oldIrql;
171
172 KeRaiseIrql(PROFILE_LEVEL, &oldIrql);
173 KeAcquireSpinLockAtDpcLevel(&TimerValueLock);
174 *CurrentTime = system_time;
175 KeReleaseSpinLock(&TimerValueLock, oldIrql);
176 }
177
178
179 NTSTATUS STDCALL
180 NtGetTickCount (PULONG UpTime)
181 {
182 LARGE_INTEGER TickCount;
183 if (UpTime == NULL)
184 return(STATUS_INVALID_PARAMETER);
185 KeQueryTickCount(&TickCount);
186 *UpTime = TickCount.u.LowPart;
187 return (STATUS_SUCCESS);
188 }
189
190
191 BOOLEAN STDCALL
192 KeSetTimer (PKTIMER Timer,
193 LARGE_INTEGER DueTime,
194 PKDPC Dpc)
195 /*
196 * FUNCTION: Sets the absolute or relative interval at which a timer object
197 * is to be set to the signaled state and optionally supplies a
198 * CustomTimerDpc to be executed when the timer expires.
199 * ARGUMENTS:
200 * Timer = Points to a previously initialized timer object
201 * DueTimer = If positive then absolute time to expire at
202 * If negative then the relative time to expire at
203 * Dpc = If non-NULL then a dpc to be called when the timer expires
204 * RETURNS: True if the timer was already in the system timer queue
205 * False otherwise
206 */
207 {
208 return(KeSetTimerEx(Timer, DueTime, 0, Dpc));
209 }
210
211 BOOLEAN STDCALL
212 KeSetTimerEx (PKTIMER Timer,
213 LARGE_INTEGER DueTime,
214 LONG Period,
215 PKDPC Dpc)
216 /*
217 * FUNCTION: Sets the absolute or relative interval at which a timer object
218 * is to be set to the signaled state and optionally supplies a
219 * CustomTimerDpc to be executed when the timer expires.
220 * ARGUMENTS:
221 * Timer = Points to a previously initialized timer object
222 * DueTimer = If positive then absolute time to expire at
223 * If negative then the relative time to expire at
224 * Dpc = If non-NULL then a dpc to be called when the timer expires
225 * RETURNS: True if the timer was already in the system timer queue
226 * False otherwise
227 */
228 {
229 KIRQL oldlvl;
230 LARGE_INTEGER SystemTime;
231
232 DPRINT("KeSetTimerEx(Timer %x), DueTime: \n",Timer);
233
234 KeRaiseIrql(PROFILE_LEVEL, &oldlvl);
235 KeAcquireSpinLockAtDpcLevel(&TimerValueLock);
236
237 SystemTime = system_time;
238
239 KeReleaseSpinLock(&TimerValueLock, DISPATCH_LEVEL);
240 KeAcquireSpinLockAtDpcLevel(&TimerListLock);
241
242 Timer->Dpc = Dpc;
243 if (DueTime.QuadPart < 0)
244 {
245
246 Timer->DueTime.QuadPart = SystemTime.QuadPart - DueTime.QuadPart;
247 }
248 else
249 {
250 Timer->DueTime.QuadPart = DueTime.QuadPart;
251 }
252 Timer->Period = Period;
253 Timer->Header.SignalState = FALSE;
254 if (Timer->TimerListEntry.Flink != NULL)
255 {
256 KeReleaseSpinLock(&TimerListLock, oldlvl);
257 return(TRUE);
258 }
259 InsertTailList(&TimerListHead,&Timer->TimerListEntry);
260 KeReleaseSpinLock(&TimerListLock, oldlvl);
261
262 return(FALSE);
263 }
264
265 BOOLEAN STDCALL
266 KeCancelTimer (PKTIMER Timer)
267 /*
268 * FUNCTION: Removes a timer from the system timer list
269 * ARGUMENTS:
270 * Timer = timer to cancel
271 * RETURNS: True if the timer was running
272 * False otherwise
273 */
274 {
275 KIRQL oldlvl;
276
277 DPRINT("KeCancelTimer(Timer %x)\n",Timer);
278
279 KeAcquireSpinLock(&TimerListLock, &oldlvl);
280
281 if (Timer->TimerListEntry.Flink == NULL)
282 {
283 KeReleaseSpinLock(&TimerListLock, oldlvl);
284 return(FALSE);
285 }
286 RemoveEntryList(&Timer->TimerListEntry);
287 Timer->TimerListEntry.Flink = Timer->TimerListEntry.Blink = NULL;
288 KeReleaseSpinLock(&TimerListLock, oldlvl);
289
290 return(TRUE);
291 }
292
293 BOOLEAN STDCALL
294 KeReadStateTimer (PKTIMER Timer)
295 {
296 return(Timer->Header.SignalState);
297 }
298
299 VOID STDCALL
300 KeInitializeTimer (PKTIMER Timer)
301 /*
302 * FUNCTION: Initalizes a kernel timer object
303 * ARGUMENTS:
304 * Timer = caller supplied storage for the timer
305 * NOTE: This function initializes a notification timer
306 */
307 {
308 KeInitializeTimerEx(Timer, NotificationTimer);
309 }
310
311 VOID STDCALL
312 KeInitializeTimerEx (PKTIMER Timer,
313 TIMER_TYPE Type)
314 /*
315 * FUNCTION: Initializes a kernel timer object
316 * ARGUMENTS:
317 * Timer = caller supplied storage for the timer
318 * Type = the type of timer (notification or synchronization)
319 * NOTE: When a notification type expires all waiting threads are released
320 * and the timer remains signalled until it is explicitly reset. When a
321 * syncrhonization timer expires its state is set to signalled until a
322 * single waiting thread is released and then the timer is reset.
323 */
324 {
325 ULONG IType;
326
327 if (Type == NotificationTimer)
328 {
329 IType = InternalNotificationTimer;
330 }
331 else if (Type == SynchronizationTimer)
332 {
333 IType = InternalSynchronizationTimer;
334 }
335 else
336 {
337 assert(FALSE);
338 return;
339 }
340
341 KeInitializeDispatcherHeader(&Timer->Header,
342 IType,
343 sizeof(KTIMER) / sizeof(ULONG),
344 FALSE);
345 Timer->TimerListEntry.Flink = Timer->TimerListEntry.Blink = NULL;
346 }
347
348 VOID STDCALL
349 KeQueryTickCount(PLARGE_INTEGER TickCount)
350 /*
351 * FUNCTION: Returns the number of ticks since the system was booted
352 * ARGUMENTS:
353 * TickCount (OUT) = Points to storage for the number of ticks
354 */
355 {
356 TickCount->QuadPart = KeTickCount;
357 }
358
359 STATIC VOID
360 HandleExpiredTimer(PKTIMER current)
361 {
362 DPRINT("HandleExpiredTime(current %x)\n",current);
363 if (current->Dpc != NULL)
364 {
365 DPRINT("current->Dpc %x current->Dpc->DeferredRoutine %x\n",
366 current->Dpc, current->Dpc->DeferredRoutine);
367 KeInsertQueueDpc(current->Dpc,
368 NULL,
369 NULL);
370 DPRINT("Finished dpc routine\n");
371 }
372 KeAcquireDispatcherDatabaseLock(FALSE);
373 current->Header.SignalState = TRUE;
374 KeDispatcherObjectWake(&current->Header);
375 KeReleaseDispatcherDatabaseLock(FALSE);
376 if (current->Period != 0)
377 {
378 current->DueTime.QuadPart +=
379 current->Period * SYSTEM_TIME_UNITS_PER_MSEC;
380 }
381 else
382 {
383 RemoveEntryList(&current->TimerListEntry);
384 current->TimerListEntry.Flink = current->TimerListEntry.Blink = NULL;
385 }
386 }
387
388 VOID STDCALL
389 KeExpireTimers(PKDPC Dpc,
390 PVOID Context1,
391 PVOID Arg1,
392 PVOID Arg2)
393 {
394 PLIST_ENTRY current_entry = NULL;
395 PKTIMER current = NULL;
396 ULONG Eip = (ULONG)Arg1;
397 KIRQL oldIrql;
398 LARGE_INTEGER SystemTime;
399
400 DPRINT("KeExpireTimers()\n");
401
402 KeRaiseIrql(PROFILE_LEVEL, &oldIrql);
403 KeAcquireSpinLockAtDpcLevel(&TimerValueLock);
404
405 SystemTime = system_time;
406
407 KeReleaseSpinLock(&TimerValueLock, oldIrql);
408 KeAcquireSpinLockAtDpcLevel(&TimerListLock);
409
410 if (KeGetCurrentIrql() > DISPATCH_LEVEL)
411 {
412 DPRINT1("-----------------------------\n");
413 KeBugCheck(0);
414 }
415
416
417 current_entry = TimerListHead.Flink;
418
419 while (current_entry != &TimerListHead)
420 {
421 current = CONTAINING_RECORD(current_entry, KTIMER, TimerListEntry);
422
423 current_entry = current_entry->Flink;
424 if ((ULONGLONG) SystemTime.QuadPart >= current->DueTime.QuadPart)
425 {
426 HandleExpiredTimer(current);
427 }
428 }
429
430 KiAddProfileEvent(ProfileTime, Eip);
431
432 KeReleaseSpinLockFromDpcLevel(&TimerListLock);
433 }
434
435
436 VOID
437 KiUpdateSystemTime(KIRQL oldIrql,
438 ULONG Eip)
439 /*
440 * FUNCTION: Handles a timer interrupt
441 */
442 {
443
444 assert(KeGetCurrentIrql() == PROFILE_LEVEL);
445
446 KiRawTicks++;
447
448 if (TimerInitDone == FALSE)
449 {
450 return;
451 }
452 /*
453 * Increment the number of timers ticks
454 */
455 KeTickCount++;
456 SharedUserData->TickCountLow++;
457
458 KeAcquireSpinLockAtDpcLevel(&TimerValueLock);
459 system_time.QuadPart += CLOCK_INCREMENT;
460 KeReleaseSpinLockFromDpcLevel(&TimerValueLock);
461
462 /*
463 * Queue a DPC that will expire timers
464 */
465 KeInsertQueueDpc(&ExpireTimerDpc, (PVOID)Eip, 0);
466 }
467
468
469 VOID
470 KeInitializeTimerImpl(VOID)
471 /*
472 * FUNCTION: Initializes timer irq handling
473 * NOTE: This is only called once from main()
474 */
475 {
476 TIME_FIELDS TimeFields;
477 LARGE_INTEGER SystemBootTime;
478
479 DPRINT("KeInitializeTimerImpl()\n");
480 InitializeListHead(&TimerListHead);
481 KeInitializeSpinLock(&TimerListLock);
482 KeInitializeSpinLock(&TimerValueLock);
483 KeInitializeDpc(&ExpireTimerDpc, KeExpireTimers, 0);
484 TimerInitDone = TRUE;
485 /*
486 * Calculate the starting time for the system clock
487 */
488 HalQueryRealTimeClock(&TimeFields);
489 RtlTimeFieldsToTime(&TimeFields, &SystemBootTime);
490 boot_time=SystemBootTime;
491 system_time=boot_time;
492
493 DPRINT("Finished KeInitializeTimerImpl()\n");
494 }