1 /* $Id: timer.c,v 1.67 2004/01/18 22:32:47 gdalsnes Exp $
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)
10 * 12/3/99: Phillip Susi: enabled the timers, fixed spin lock
13 /* NOTES ******************************************************************/
15 * System time units are 100-nanosecond intervals
18 /* INCLUDES ***************************************************************/
21 #include <ddk/ntddk.h>
22 #include <internal/ke.h>
23 #include <internal/id.h>
24 #include <internal/ps.h>
25 #include <internal/safe.h>
28 #include <internal/debug.h>
30 /* GLOBALS ****************************************************************/
36 static LARGE_INTEGER SystemBootTime
= (LARGE_INTEGER
)0LL;
38 static LARGE_INTEGER SystemBootTime
= { 0 };
42 * Number of timer interrupts since initialisation
44 volatile ULONGLONG KeTickCount
= 0;
45 volatile ULONG KiRawTicks
= 0;
48 * The increment in the system clock every timer tick (in system time units)
54 #define CLOCK_INCREMENT (100000)
57 * PURPOSE: List of timers
59 static LIST_ENTRY AbsoluteTimerListHead
;
60 static LIST_ENTRY RelativeTimerListHead
;
61 static KSPIN_LOCK TimerListLock
;
62 static KSPIN_LOCK TimerValueLock
;
63 static KDPC ExpireTimerDpc
;
65 /* must raise IRQL to PROFILE_LEVEL and grab spin lock there, to sync with ISR */
67 extern ULONG PiNrRunnableThreads
;
69 #define MICROSECONDS_PER_TICK (10000)
70 #define TICKS_TO_CALIBRATE (1)
71 #define CALIBRATE_PERIOD (MICROSECONDS_PER_TICK * TICKS_TO_CALIBRATE)
72 #define SYSTEM_TIME_UNITS_PER_MSEC (10000)
74 static BOOLEAN TimerInitDone
= FALSE
;
76 /* FUNCTIONS **************************************************************/
80 NtQueryTimerResolution(OUT PULONG MinimumResolution
,
81 OUT PULONG MaximumResolution
,
82 OUT PULONG ActualResolution
)
85 return STATUS_NOT_IMPLEMENTED
;
90 NtSetTimerResolution(IN ULONG RequestedResolution
,
92 OUT PULONG ActualResolution
)
95 return STATUS_NOT_IMPLEMENTED
;
100 NtQueryPerformanceCounter(IN PLARGE_INTEGER Counter
,
101 IN PLARGE_INTEGER Frequency
)
103 LARGE_INTEGER PerfCounter
;
104 LARGE_INTEGER PerfFrequency
;
107 PerfCounter
= KeQueryPerformanceCounter(&PerfFrequency
);
111 Status
= MmCopyToCaller(&Counter
->QuadPart
, &PerfCounter
.QuadPart
, sizeof(PerfCounter
.QuadPart
));
112 if (!NT_SUCCESS(Status
))
118 if (Frequency
!= NULL
)
120 Status
= MmCopyToCaller(&Frequency
->QuadPart
, &PerfFrequency
.QuadPart
, sizeof(PerfFrequency
.QuadPart
));
121 if (!NT_SUCCESS(Status
))
127 return(STATUS_SUCCESS
);
132 NtDelayExecution(IN ULONG Alertable
,
136 LARGE_INTEGER Timeout
;
138 Status
= MmCopyFromCaller(&Timeout
, Interval
, sizeof(Timeout
));
139 if (!NT_SUCCESS(Status
))
144 Timeout
= *((PLARGE_INTEGER
)Interval
);
145 DPRINT("NtDelayExecution(Alertable %d, Internal %x) IntervalP %x\n",
146 Alertable
, Internal
, Timeout
);
148 DPRINT("Execution delay is %d/%d\n",
149 Timeout
.u
.HighPart
, Timeout
.u
.LowPart
);
150 Status
= KeDelayExecutionThread(UserMode
, (BOOLEAN
)Alertable
, &Timeout
);
159 KeDelayExecutionThread (KPROCESSOR_MODE WaitMode
,
161 PLARGE_INTEGER Interval
)
163 * FUNCTION: Puts the current thread into an alertable or nonalertable
164 * wait state for a given internal
166 * WaitMode = Processor mode in which the caller is waiting
167 * Altertable = Specifies if the wait is alertable
168 * Interval = Specifies the interval to wait
172 PKTHREAD Thread
= KeGetCurrentThread();
174 KeSetTimer(&Thread
->Timer
, *Interval
, NULL
);
175 return (KeWaitForSingleObject(&Thread
->Timer
,
176 (WaitMode
== KernelMode
) ? Executive
: UserRequest
, /* TMN: Was unconditionally Executive */
177 WaitMode
, /* TMN: Was UserMode */
187 KeQueryTimeIncrement(VOID
)
189 * FUNCTION: Gets the increment (in 100-nanosecond units) that is added to
190 * the system clock every time the clock interrupts
191 * RETURNS: The increment
194 return(CLOCK_INCREMENT
);
202 KeQuerySystemTime(PLARGE_INTEGER CurrentTime
)
204 * FUNCTION: Gets the current system time
206 * CurrentTime (OUT) = The routine stores the current time here
207 * NOTE: The time is the number of 100-nanosecond intervals since the
208 * 1st of January, 1601.
213 CurrentTime
->u
.HighPart
= SharedUserData
->SystemTime
.High1Part
;
214 CurrentTime
->u
.LowPart
= SharedUserData
->SystemTime
.LowPart
;
216 while (CurrentTime
->u
.HighPart
!= SharedUserData
->SystemTime
.High2Part
);
220 KeQueryInterruptTime(PLARGE_INTEGER CurrentTime
)
224 CurrentTime
->u
.HighPart
= SharedUserData
->InterruptTime
.High1Part
;
225 CurrentTime
->u
.LowPart
= SharedUserData
->InterruptTime
.LowPart
;
227 while (CurrentTime
->u
.HighPart
!= SharedUserData
->InterruptTime
.High2Part
);
233 NtGetTickCount (PULONG UpTime
)
235 LARGE_INTEGER TickCount
;
238 return(STATUS_INVALID_PARAMETER
);
241 KeQueryTickCount(&TickCount
);
242 return(MmCopyToCaller(UpTime
, &TickCount
.u
.LowPart
, sizeof(*UpTime
)));
250 KeSetTimer (PKTIMER Timer
,
251 LARGE_INTEGER DueTime
,
254 * FUNCTION: Sets the absolute or relative interval at which a timer object
255 * is to be set to the signaled state and optionally supplies a
256 * CustomTimerDpc to be executed when the timer expires.
258 * Timer = Points to a previously initialized timer object
259 * DueTimer = If positive then absolute time to expire at
260 * If negative then the relative time to expire at
261 * Dpc = If non-NULL then a dpc to be called when the timer expires
262 * RETURNS: True if the timer was already in the system timer queue
266 return(KeSetTimerEx(Timer
, DueTime
, 0, Dpc
));
273 KeSetTimerEx (PKTIMER Timer
,
274 LARGE_INTEGER DueTime
,
278 * FUNCTION: Sets the absolute or relative interval at which a timer object
279 * is to be set to the signaled state and optionally supplies a
280 * CustomTimerDpc to be executed when the timer expires.
282 * Timer = Points to a previously initialized timer object
283 * DueTimer = If positive then absolute time to expire at
284 * If negative then the relative time to expire at
285 * Dpc = If non-NULL then a dpc to be called when the timer expires
286 * RETURNS: True if the timer was already in the system timer queue
292 BOOLEAN AlreadyInList
;
294 DPRINT("KeSetTimerEx(Timer %x), DueTime: \n",Timer
);
296 assert(KeGetCurrentIrql() <= DISPATCH_LEVEL
);
298 KeAcquireSpinLock(&TimerListLock
, &oldlvl
);
301 if (DueTime
.QuadPart
< 0)
303 KeQueryInterruptTime(&Time
);
304 Timer
->Header
.Absolute
= 0;
305 Timer
->DueTime
.QuadPart
= Time
.QuadPart
- DueTime
.QuadPart
;
309 KeQuerySystemTime(&Time
);
310 Timer
->Header
.Absolute
= 1;
311 if (DueTime
.QuadPart
>= Time
.QuadPart
)
313 Timer
->DueTime
.QuadPart
= DueTime
.QuadPart
;
317 Timer
->DueTime
.QuadPart
= Time
.QuadPart
;
320 Timer
->Period
= Period
;
321 Timer
->Header
.SignalState
= FALSE
;
322 AlreadyInList
= (Timer
->TimerListEntry
.Flink
== NULL
) ? FALSE
: TRUE
;
323 assert((Timer
->TimerListEntry
.Flink
== NULL
&& Timer
->TimerListEntry
.Blink
== NULL
) ||
324 (Timer
->TimerListEntry
.Flink
!= NULL
&& Timer
->TimerListEntry
.Blink
!= NULL
));
327 RemoveEntryList(&Timer
->TimerListEntry
);
329 if (Timer
->Header
.Absolute
)
331 InsertAscendingList(&AbsoluteTimerListHead
,
340 InsertAscendingList(&RelativeTimerListHead
,
348 KeReleaseSpinLock(&TimerListLock
, oldlvl
);
350 return AlreadyInList
;
357 KeCancelTimer (PKTIMER Timer
)
359 * FUNCTION: Removes a timer from the system timer list
361 * Timer = timer to cancel
362 * RETURNS: True if the timer was running
368 DPRINT("KeCancelTimer(Timer %x)\n",Timer
);
370 KeAcquireSpinLock(&TimerListLock
, &oldlvl
);
372 if (Timer
->TimerListEntry
.Flink
== NULL
)
374 KeReleaseSpinLock(&TimerListLock
, oldlvl
);
377 if (Timer
->Header
.Absolute
)
379 assert(&Timer
->TimerListEntry
!= &AbsoluteTimerListHead
);
383 assert(&Timer
->TimerListEntry
!= &RelativeTimerListHead
);
385 assert(Timer
->TimerListEntry
.Flink
!= &Timer
->TimerListEntry
);
386 RemoveEntryList(&Timer
->TimerListEntry
);
387 Timer
->TimerListEntry
.Flink
= Timer
->TimerListEntry
.Blink
= NULL
;
388 KeReleaseSpinLock(&TimerListLock
, oldlvl
);
397 KeReadStateTimer (PKTIMER Timer
)
399 return (BOOLEAN
)(Timer
->Header
.SignalState
);
406 KeInitializeTimer (PKTIMER Timer
)
408 * FUNCTION: Initalizes a kernel timer object
410 * Timer = caller supplied storage for the timer
411 * NOTE: This function initializes a notification timer
414 KeInitializeTimerEx(Timer
, NotificationTimer
);
421 KeInitializeTimerEx (PKTIMER Timer
,
424 * FUNCTION: Initializes a kernel timer object
426 * Timer = caller supplied storage for the timer
427 * Type = the type of timer (notification or synchronization)
428 * NOTE: When a notification type expires all waiting threads are released
429 * and the timer remains signalled until it is explicitly reset. When a
430 * syncrhonization timer expires its state is set to signalled until a
431 * single waiting thread is released and then the timer is reset.
436 if (Type
== NotificationTimer
)
438 IType
= InternalNotificationTimer
;
440 else if (Type
== SynchronizationTimer
)
442 IType
= InternalSynchronizationTimer
;
450 KeInitializeDispatcherHeader(&Timer
->Header
,
452 sizeof(KTIMER
) / sizeof(ULONG
),
454 Timer
->TimerListEntry
.Flink
= Timer
->TimerListEntry
.Blink
= NULL
;
461 KeQueryTickCount(PLARGE_INTEGER TickCount
)
463 * FUNCTION: Returns the number of ticks since the system was booted
465 * TickCount (OUT) = Points to storage for the number of ticks
468 TickCount
->QuadPart
= KeTickCount
;
472 * We enter this function at IRQL DISPATCH_LEVEL, and with the
473 * TimerListLock held.
476 HandleExpiredTimer(PKTIMER Timer
)
478 DPRINT("HandleExpiredTime(Timer %x)\n", Timer
);
479 if (Timer
->Dpc
!= NULL
)
481 DPRINT("Timer->Dpc %x Timer->Dpc->DeferredRoutine %x\n",
482 Timer
->Dpc
, Timer
->Dpc
->DeferredRoutine
);
483 KeInsertQueueDpc(Timer
->Dpc
,
486 DPRINT("Finished dpc routine\n");
489 assert(KeGetCurrentIrql() == DISPATCH_LEVEL
);
491 KeAcquireDispatcherDatabaseLockAtDpcLevel();
492 Timer
->Header
.SignalState
= TRUE
;
493 KeDispatcherObjectWake(&Timer
->Header
);
494 KeReleaseDispatcherDatabaseLockFromDpcLevel();
496 if (Timer
->Period
!= 0)
498 Timer
->DueTime
.QuadPart
+=
499 Timer
->Period
* SYSTEM_TIME_UNITS_PER_MSEC
;
500 if (Timer
->Header
.Absolute
)
502 InsertAscendingList(&AbsoluteTimerListHead
,
510 InsertAscendingList(&RelativeTimerListHead
,
520 KeExpireTimers(PKDPC Dpc
,
525 PLIST_ENTRY current_entry
= NULL
;
526 PKTIMER current
= NULL
;
527 ULONG Eip
= (ULONG
)Arg1
;
528 LARGE_INTEGER InterruptTime
;
529 LARGE_INTEGER SystemTime
;
530 LIST_ENTRY TimerList
;
532 DPRINT("KeExpireTimers()\n");
534 assert(KeGetCurrentIrql() == DISPATCH_LEVEL
);
536 InitializeListHead(&TimerList
);
538 KeAcquireSpinLockAtDpcLevel(&TimerListLock
);
540 KeQueryInterruptTime(&InterruptTime
);
541 KeQuerySystemTime(&SystemTime
);
543 current_entry
= RelativeTimerListHead
.Flink
;
544 assert(current_entry
);
545 while (current_entry
!= &RelativeTimerListHead
)
547 current
= CONTAINING_RECORD(current_entry
, KTIMER
, TimerListEntry
);
549 assert(current_entry
!= &RelativeTimerListHead
);
550 assert(current_entry
->Flink
!= current_entry
);
551 if ((ULONGLONG
)InterruptTime
.QuadPart
< current
->DueTime
.QuadPart
)
555 current_entry
= current_entry
->Flink
;
556 RemoveEntryList(¤t
->TimerListEntry
);
557 InsertTailList(&TimerList
, ¤t
->TimerListEntry
);
560 current_entry
= AbsoluteTimerListHead
.Flink
;
561 assert(current_entry
);
562 while (current_entry
!= &AbsoluteTimerListHead
)
564 current
= CONTAINING_RECORD(current_entry
, KTIMER
, TimerListEntry
);
566 assert(current_entry
!= &AbsoluteTimerListHead
);
567 assert(current_entry
->Flink
!= current_entry
);
568 if ((ULONGLONG
)SystemTime
.QuadPart
< current
->DueTime
.QuadPart
)
572 current_entry
= current_entry
->Flink
;
573 RemoveEntryList(¤t
->TimerListEntry
);
574 InsertTailList(&TimerList
, ¤t
->TimerListEntry
);
577 while (!IsListEmpty(&TimerList
))
579 current_entry
= RemoveHeadList(&TimerList
);
580 current
= CONTAINING_RECORD(current_entry
, KTIMER
, TimerListEntry
);
581 current
->TimerListEntry
.Flink
= current
->TimerListEntry
.Blink
= NULL
;
582 HandleExpiredTimer(current
);
585 KiAddProfileEvent(ProfileTime
, Eip
);
587 KeReleaseSpinLockFromDpcLevel(&TimerListLock
);
592 KiUpdateSystemTime(KIRQL oldIrql
,
595 * FUNCTION: Handles a timer interrupt
599 assert(KeGetCurrentIrql() == PROFILE_LEVEL
);
603 if (TimerInitDone
== FALSE
)
608 * Increment the number of timers ticks
611 SharedUserData
->TickCountLow
++;
613 KiAcquireSpinLock(&TimerValueLock
);
615 Time
.u
.LowPart
= SharedUserData
->InterruptTime
.LowPart
;
616 Time
.u
.HighPart
= SharedUserData
->InterruptTime
.High1Part
;
617 Time
.QuadPart
+= CLOCK_INCREMENT
;
618 SharedUserData
->InterruptTime
.High2Part
= Time
.u
.HighPart
;
619 SharedUserData
->InterruptTime
.LowPart
= Time
.u
.LowPart
;
620 SharedUserData
->InterruptTime
.High1Part
= Time
.u
.HighPart
;
622 Time
.u
.LowPart
= SharedUserData
->SystemTime
.LowPart
;
623 Time
.u
.HighPart
= SharedUserData
->SystemTime
.High1Part
;
624 Time
.QuadPart
+= CLOCK_INCREMENT
;
625 SharedUserData
->SystemTime
.High2Part
= Time
.u
.HighPart
;
626 SharedUserData
->SystemTime
.LowPart
= Time
.u
.LowPart
;
627 SharedUserData
->SystemTime
.High1Part
= Time
.u
.HighPart
;
629 KiReleaseSpinLock(&TimerValueLock
);
632 * Queue a DPC that will expire timers
634 KeInsertQueueDpc(&ExpireTimerDpc
, (PVOID
)Eip
, 0);
639 KeInitializeTimerImpl(VOID
)
641 * FUNCTION: Initializes timer irq handling
642 * NOTE: This is only called once from main()
645 TIME_FIELDS TimeFields
;
647 DPRINT("KeInitializeTimerImpl()\n");
648 InitializeListHead(&AbsoluteTimerListHead
);
649 InitializeListHead(&RelativeTimerListHead
);
650 KeInitializeSpinLock(&TimerListLock
);
651 KeInitializeSpinLock(&TimerValueLock
);
652 KeInitializeDpc(&ExpireTimerDpc
, KeExpireTimers
, 0);
654 * Calculate the starting time for the system clock
656 HalQueryRealTimeClock(&TimeFields
);
657 RtlTimeFieldsToTime(&TimeFields
, &SystemBootTime
);
659 SharedUserData
->TickCountLow
= 0;
660 SharedUserData
->TickCountMultiplier
= 167783691; // 2^24 * 1193182 / 119310
661 SharedUserData
->InterruptTime
.High2Part
= 0;
662 SharedUserData
->InterruptTime
.LowPart
= 0;
663 SharedUserData
->InterruptTime
.High1Part
= 0;
664 SharedUserData
->SystemTime
.High2Part
= SystemBootTime
.u
.HighPart
;
665 SharedUserData
->SystemTime
.LowPart
= SystemBootTime
.u
.LowPart
;
666 SharedUserData
->SystemTime
.High1Part
= SystemBootTime
.u
.HighPart
;
668 TimerInitDone
= TRUE
;
669 DPRINT("Finished KeInitializeTimerImpl()\n");