1 /* $Id: timer.c,v 1.84 2004/10/24 16:49:49 weiden 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 ***************************************************************/
22 #include <internal/debug.h>
25 /* GLOBALS ****************************************************************/
31 LARGE_INTEGER SystemBootTime
= (LARGE_INTEGER
)0LL;
33 LARGE_INTEGER SystemBootTime
= { 0 };
36 CHAR KiTimerSystemAuditing
= 0;
39 * Number of timer interrupts since initialisation
41 volatile ULONGLONG KeTickCount
= 0;
42 volatile ULONG KiRawTicks
= 0;
45 * The increment in the system clock every timer tick (in system time units)
51 #define CLOCK_INCREMENT (100000)
54 ULONG EXPORTED KeMaximumIncrement
= 100000;
55 ULONG EXPORTED KeMinimumIncrement
= 100000;
57 /* Microsoft-style declarations */
58 EXPORTED ULONG KeMaximumIncrement
= 100000;
59 EXPORTED ULONG KeMinimumIncrement
= 100000;
65 * PURPOSE: List of timers
67 static LIST_ENTRY AbsoluteTimerListHead
;
68 static LIST_ENTRY RelativeTimerListHead
;
69 static KSPIN_LOCK TimerListLock
;
70 static KSPIN_LOCK TimerValueLock
;
71 static KDPC ExpireTimerDpc
;
73 /* must raise IRQL to PROFILE_LEVEL and grab spin lock there, to sync with ISR */
75 extern HANDLE PsIdleThreadHandle
;
77 #define MICROSECONDS_PER_TICK (10000)
78 #define TICKS_TO_CALIBRATE (1)
79 #define CALIBRATE_PERIOD (MICROSECONDS_PER_TICK * TICKS_TO_CALIBRATE)
80 #define SYSTEM_TIME_UNITS_PER_MSEC (10000)
82 static BOOLEAN TimerInitDone
= FALSE
;
84 /* FUNCTIONS **************************************************************/
88 NtQueryTimerResolution(OUT PULONG MinimumResolution
,
89 OUT PULONG MaximumResolution
,
90 OUT PULONG ActualResolution
)
93 return STATUS_NOT_IMPLEMENTED
;
98 NtSetTimerResolution(IN ULONG DesiredResolution
,
99 IN BOOLEAN SetResolution
,
100 OUT PULONG CurrentResolution
)
103 return STATUS_NOT_IMPLEMENTED
;
108 NtQueryPerformanceCounter(IN PLARGE_INTEGER Counter
,
109 IN PLARGE_INTEGER Frequency
)
111 LARGE_INTEGER PerfCounter
;
112 LARGE_INTEGER PerfFrequency
;
115 PerfCounter
= KeQueryPerformanceCounter(&PerfFrequency
);
119 Status
= MmCopyToCaller(&Counter
->QuadPart
, &PerfCounter
.QuadPart
, sizeof(PerfCounter
.QuadPart
));
120 if (!NT_SUCCESS(Status
))
126 if (Frequency
!= NULL
)
128 Status
= MmCopyToCaller(&Frequency
->QuadPart
, &PerfFrequency
.QuadPart
, sizeof(PerfFrequency
.QuadPart
));
129 if (!NT_SUCCESS(Status
))
135 return(STATUS_SUCCESS
);
140 NtDelayExecution(IN ULONG Alertable
,
144 LARGE_INTEGER Timeout
;
146 Status
= MmCopyFromCaller(&Timeout
, Interval
, sizeof(Timeout
));
147 if (!NT_SUCCESS(Status
))
152 Timeout
= *((PLARGE_INTEGER
)Interval
);
153 DPRINT("NtDelayExecution(Alertable %d, Internal %x) IntervalP %x\n",
154 Alertable
, Internal
, Timeout
);
156 DPRINT("Execution delay is %d/%d\n",
157 Timeout
.u
.HighPart
, Timeout
.u
.LowPart
);
158 Status
= KeDelayExecutionThread(UserMode
, (BOOLEAN
)Alertable
, &Timeout
);
167 KeDelayExecutionThread (KPROCESSOR_MODE WaitMode
,
169 PLARGE_INTEGER Interval
)
171 * FUNCTION: Puts the current thread into an alertable or nonalertable
172 * wait state for a given internal
174 * WaitMode = Processor mode in which the caller is waiting
175 * Altertable = Specifies if the wait is alertable
176 * Interval = Specifies the interval to wait
180 PKTHREAD Thread
= KeGetCurrentThread();
182 KeSetTimer(&Thread
->Timer
, *Interval
, NULL
);
183 return (KeWaitForSingleObject(&Thread
->Timer
,
184 (WaitMode
== KernelMode
) ? Executive
: UserRequest
, /* TMN: Was unconditionally Executive */
185 WaitMode
, /* TMN: Was UserMode */
195 KeQueryTimeIncrement(VOID
)
197 * FUNCTION: Gets the increment (in 100-nanosecond units) that is added to
198 * the system clock every time the clock interrupts
199 * RETURNS: The increment
202 return(CLOCK_INCREMENT
);
210 KeQuerySystemTime(PLARGE_INTEGER CurrentTime
)
212 * FUNCTION: Gets the current system time
214 * CurrentTime (OUT) = The routine stores the current time here
215 * NOTE: The time is the number of 100-nanosecond intervals since the
216 * 1st of January, 1601.
221 CurrentTime
->u
.HighPart
= SharedUserData
->SystemTime
.High1Part
;
222 CurrentTime
->u
.LowPart
= SharedUserData
->SystemTime
.LowPart
;
224 while (CurrentTime
->u
.HighPart
!= SharedUserData
->SystemTime
.High2Part
);
228 KeQueryInterruptTime(VOID
)
230 LARGE_INTEGER CurrentTime
;
234 CurrentTime
.u
.HighPart
= SharedUserData
->InterruptTime
.High1Part
;
235 CurrentTime
.u
.LowPart
= SharedUserData
->InterruptTime
.LowPart
;
237 while (CurrentTime
.u
.HighPart
!= SharedUserData
->InterruptTime
.High2Part
);
239 return CurrentTime
.QuadPart
;
249 LARGE_INTEGER TickCount
;
250 KeQueryTickCount(&TickCount
);
251 return TickCount
.u
.LowPart
;
258 KeSetTimer (PKTIMER Timer
,
259 LARGE_INTEGER DueTime
,
262 * FUNCTION: Sets the absolute or relative interval at which a timer object
263 * is to be set to the signaled state and optionally supplies a
264 * CustomTimerDpc to be executed when the timer expires.
266 * Timer = Points to a previously initialized timer object
267 * DueTimer = If positive then absolute time to expire at
268 * If negative then the relative time to expire at
269 * Dpc = If non-NULL then a dpc to be called when the timer expires
270 * RETURNS: True if the timer was already in the system timer queue
274 return(KeSetTimerEx(Timer
, DueTime
, 0, Dpc
));
281 KeSetTimerEx (PKTIMER Timer
,
282 LARGE_INTEGER DueTime
,
286 * FUNCTION: Sets the absolute or relative interval at which a timer object
287 * is to be set to the signaled state and optionally supplies a
288 * CustomTimerDpc to be executed when the timer expires.
290 * Timer = Points to a previously initialized timer object
291 * DueTimer = If positive then absolute time to expire at
292 * If negative then the relative time to expire at
293 * Dpc = If non-NULL then a dpc to be called when the timer expires
294 * RETURNS: True if the timer was already in the system timer queue
300 BOOLEAN AlreadyInList
;
302 DPRINT("KeSetTimerEx(Timer %x), DueTime: \n",Timer
);
304 ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL
);
306 KeAcquireSpinLock(&TimerListLock
, &oldlvl
);
309 if (DueTime
.QuadPart
< 0)
311 Timer
->Header
.Absolute
= 0;
312 Timer
->DueTime
.QuadPart
= KeQueryInterruptTime() - DueTime
.QuadPart
;
316 KeQuerySystemTime(&Time
);
317 Timer
->Header
.Absolute
= 1;
318 if (DueTime
.QuadPart
>= Time
.QuadPart
)
320 Timer
->DueTime
.QuadPart
= DueTime
.QuadPart
;
324 Timer
->DueTime
.QuadPart
= Time
.QuadPart
;
327 Timer
->Period
= Period
;
328 Timer
->Header
.SignalState
= FALSE
;
329 AlreadyInList
= (Timer
->TimerListEntry
.Flink
== NULL
) ? FALSE
: TRUE
;
330 ASSERT((Timer
->TimerListEntry
.Flink
== NULL
&& Timer
->TimerListEntry
.Blink
== NULL
) ||
331 (Timer
->TimerListEntry
.Flink
!= NULL
&& Timer
->TimerListEntry
.Blink
!= NULL
));
334 RemoveEntryList(&Timer
->TimerListEntry
);
336 if (Timer
->Header
.Absolute
)
338 InsertAscendingList(&AbsoluteTimerListHead
,
347 InsertAscendingList(&RelativeTimerListHead
,
355 KeReleaseSpinLock(&TimerListLock
, oldlvl
);
357 return AlreadyInList
;
364 KeCancelTimer (PKTIMER Timer
)
366 * FUNCTION: Removes a timer from the system timer list
368 * Timer = timer to cancel
369 * RETURNS: True if the timer was running
375 DPRINT("KeCancelTimer(Timer %x)\n",Timer
);
377 KeAcquireSpinLock(&TimerListLock
, &oldlvl
);
379 if (Timer
->TimerListEntry
.Flink
== NULL
)
381 KeReleaseSpinLock(&TimerListLock
, oldlvl
);
384 if (Timer
->Header
.Absolute
)
386 ASSERT(&Timer
->TimerListEntry
!= &AbsoluteTimerListHead
);
390 ASSERT(&Timer
->TimerListEntry
!= &RelativeTimerListHead
);
392 ASSERT(Timer
->TimerListEntry
.Flink
!= &Timer
->TimerListEntry
);
393 RemoveEntryList(&Timer
->TimerListEntry
);
394 Timer
->TimerListEntry
.Flink
= Timer
->TimerListEntry
.Blink
= NULL
;
395 KeReleaseSpinLock(&TimerListLock
, oldlvl
);
404 KeReadStateTimer (PKTIMER Timer
)
406 return (BOOLEAN
)(Timer
->Header
.SignalState
);
413 KeInitializeTimer (PKTIMER Timer
)
415 * FUNCTION: Initalizes a kernel timer object
417 * Timer = caller supplied storage for the timer
418 * NOTE: This function initializes a notification timer
421 KeInitializeTimerEx(Timer
, NotificationTimer
);
428 KeInitializeTimerEx (PKTIMER Timer
,
431 * FUNCTION: Initializes a kernel timer object
433 * Timer = caller supplied storage for the timer
434 * Type = the type of timer (notification or synchronization)
435 * NOTE: When a notification type expires all waiting threads are released
436 * and the timer remains signalled until it is explicitly reset. When a
437 * syncrhonization timer expires its state is set to signalled until a
438 * single waiting thread is released and then the timer is reset.
443 if (Type
== NotificationTimer
)
445 IType
= InternalNotificationTimer
;
447 else if (Type
== SynchronizationTimer
)
449 IType
= InternalSynchronizationTimer
;
457 KeInitializeDispatcherHeader(&Timer
->Header
,
459 sizeof(KTIMER
) / sizeof(ULONG
),
461 Timer
->TimerListEntry
.Flink
= Timer
->TimerListEntry
.Blink
= NULL
;
468 KeQueryTickCount(PLARGE_INTEGER TickCount
)
470 * FUNCTION: Returns the number of ticks since the system was booted
472 * TickCount (OUT) = Points to storage for the number of ticks
475 TickCount
->QuadPart
= KeTickCount
;
483 KeQueryRuntimeThread(
488 /* Return the User Time */
489 *UserTime
= Thread
->UserTime
;
491 /* Return the Kernel Time */
492 return Thread
->KernelTime
;
501 IN ULONG MaxIncrement
,
502 IN ULONG MinIncrement
505 /* Set some Internal Variables */
506 /* FIXME: We use a harcoded CLOCK_INCREMENT. That *must* be changed */
507 KeMaximumIncrement
= MaxIncrement
;
508 KeMinimumIncrement
= MinIncrement
;
512 * We enter this function at IRQL DISPATCH_LEVEL, and with the
513 * TimerListLock held.
516 HandleExpiredTimer(PKTIMER Timer
)
518 DPRINT("HandleExpiredTime(Timer %x)\n", Timer
);
519 if (Timer
->Dpc
!= NULL
)
521 DPRINT("Timer->Dpc %x Timer->Dpc->DeferredRoutine %x\n",
522 Timer
->Dpc
, Timer
->Dpc
->DeferredRoutine
);
523 KeInsertQueueDpc(Timer
->Dpc
,
526 DPRINT("Finished dpc routine\n");
529 ASSERT(KeGetCurrentIrql() == DISPATCH_LEVEL
);
531 KeAcquireDispatcherDatabaseLockAtDpcLevel();
532 Timer
->Header
.SignalState
= TRUE
;
533 KeDispatcherObjectWake(&Timer
->Header
);
534 KeReleaseDispatcherDatabaseLockFromDpcLevel();
536 if (Timer
->Period
!= 0)
538 Timer
->DueTime
.QuadPart
+=
539 Timer
->Period
* SYSTEM_TIME_UNITS_PER_MSEC
;
540 if (Timer
->Header
.Absolute
)
542 InsertAscendingList(&AbsoluteTimerListHead
,
550 InsertAscendingList(&RelativeTimerListHead
,
560 KeExpireTimers(PKDPC Dpc
,
565 PLIST_ENTRY current_entry
= NULL
;
566 PKTIMER current
= NULL
;
567 ULONG Eip
= (ULONG
)Arg1
;
568 LARGE_INTEGER InterruptTime
;
569 LARGE_INTEGER SystemTime
;
570 LIST_ENTRY TimerList
;
572 DPRINT("KeExpireTimers()\n");
574 ASSERT(KeGetCurrentIrql() == DISPATCH_LEVEL
);
576 InitializeListHead(&TimerList
);
578 KeAcquireSpinLockAtDpcLevel(&TimerListLock
);
580 InterruptTime
.QuadPart
= KeQueryInterruptTime();
581 KeQuerySystemTime(&SystemTime
);
583 current_entry
= RelativeTimerListHead
.Flink
;
584 ASSERT(current_entry
);
585 while (current_entry
!= &RelativeTimerListHead
)
587 current
= CONTAINING_RECORD(current_entry
, KTIMER
, TimerListEntry
);
589 ASSERT(current_entry
!= &RelativeTimerListHead
);
590 ASSERT(current_entry
->Flink
!= current_entry
);
591 if ((ULONGLONG
)InterruptTime
.QuadPart
< current
->DueTime
.QuadPart
)
595 current_entry
= current_entry
->Flink
;
596 RemoveEntryList(¤t
->TimerListEntry
);
597 InsertTailList(&TimerList
, ¤t
->TimerListEntry
);
600 current_entry
= AbsoluteTimerListHead
.Flink
;
601 ASSERT(current_entry
);
602 while (current_entry
!= &AbsoluteTimerListHead
)
604 current
= CONTAINING_RECORD(current_entry
, KTIMER
, TimerListEntry
);
606 ASSERT(current_entry
!= &AbsoluteTimerListHead
);
607 ASSERT(current_entry
->Flink
!= current_entry
);
608 if ((ULONGLONG
)SystemTime
.QuadPart
< current
->DueTime
.QuadPart
)
612 current_entry
= current_entry
->Flink
;
613 RemoveEntryList(¤t
->TimerListEntry
);
614 InsertTailList(&TimerList
, ¤t
->TimerListEntry
);
617 while (!IsListEmpty(&TimerList
))
619 current_entry
= RemoveHeadList(&TimerList
);
620 current
= CONTAINING_RECORD(current_entry
, KTIMER
, TimerListEntry
);
621 current
->TimerListEntry
.Flink
= current
->TimerListEntry
.Blink
= NULL
;
622 HandleExpiredTimer(current
);
625 KiAddProfileEvent(ProfileTime
, Eip
);
627 KeReleaseSpinLockFromDpcLevel(&TimerListLock
);
632 KiUpdateSystemTime(KIRQL oldIrql
,
635 * FUNCTION: Handles a timer interrupt
640 ASSERT(KeGetCurrentIrql() == PROFILE_LEVEL
);
644 if (TimerInitDone
== FALSE
)
649 * Increment the number of timers ticks
652 SharedUserData
->TickCountLow
++;
653 KiAcquireSpinLock(&TimerValueLock
);
655 Time
.u
.LowPart
= SharedUserData
->InterruptTime
.LowPart
;
656 Time
.u
.HighPart
= SharedUserData
->InterruptTime
.High1Part
;
657 Time
.QuadPart
+= CLOCK_INCREMENT
;
658 SharedUserData
->InterruptTime
.High2Part
= Time
.u
.HighPart
;
659 SharedUserData
->InterruptTime
.LowPart
= Time
.u
.LowPart
;
660 SharedUserData
->InterruptTime
.High1Part
= Time
.u
.HighPart
;
662 Time
.u
.LowPart
= SharedUserData
->SystemTime
.LowPart
;
663 Time
.u
.HighPart
= SharedUserData
->SystemTime
.High1Part
;
664 Time
.QuadPart
+= CLOCK_INCREMENT
;
665 SharedUserData
->SystemTime
.High2Part
= Time
.u
.HighPart
;
666 SharedUserData
->SystemTime
.LowPart
= Time
.u
.LowPart
;
667 SharedUserData
->SystemTime
.High1Part
= Time
.u
.HighPart
;
669 KiReleaseSpinLock(&TimerValueLock
);
671 /* Update process and thread times */
672 KiUpdateProcessThreadTime(oldIrql
, Tf
);
675 * Queue a DPC that will expire timers
677 KeInsertQueueDpc(&ExpireTimerDpc
, (PVOID
)Tf
->Eip
, 0);
682 KeInitializeTimerImpl(VOID
)
684 * FUNCTION: Initializes timer irq handling
685 * NOTE: This is only called once from main()
688 TIME_FIELDS TimeFields
;
690 DPRINT("KeInitializeTimerImpl()\n");
691 InitializeListHead(&AbsoluteTimerListHead
);
692 InitializeListHead(&RelativeTimerListHead
);
693 KeInitializeSpinLock(&TimerListLock
);
694 KeInitializeSpinLock(&TimerValueLock
);
695 KeInitializeDpc(&ExpireTimerDpc
, KeExpireTimers
, 0);
697 * Calculate the starting time for the system clock
699 HalQueryRealTimeClock(&TimeFields
);
700 RtlTimeFieldsToTime(&TimeFields
, &SystemBootTime
);
702 SharedUserData
->TickCountLow
= 0;
703 SharedUserData
->TickCountMultiplier
= 167783691; // 2^24 * 1193182 / 119310
704 SharedUserData
->InterruptTime
.High2Part
= 0;
705 SharedUserData
->InterruptTime
.LowPart
= 0;
706 SharedUserData
->InterruptTime
.High1Part
= 0;
707 SharedUserData
->SystemTime
.High2Part
= SystemBootTime
.u
.HighPart
;
708 SharedUserData
->SystemTime
.LowPart
= SystemBootTime
.u
.LowPart
;
709 SharedUserData
->SystemTime
.High1Part
= SystemBootTime
.u
.HighPart
;
711 TimerInitDone
= TRUE
;
712 DPRINT("Finished KeInitializeTimerImpl()\n");
717 KiUpdateProcessThreadTime(KIRQL oldIrql
, PKIRQ_TRAPFRAME Tf
)
719 PKTHREAD CurrentThread
;
720 PEPROCESS ThreadsProcess
;
723 Pcr
= KeGetCurrentKPCR();
726 * Make sure no counting can take place until Processes and Threads are
729 if ((PsInitialSystemProcess
== NULL
) || (Pcr
->PrcbData
.IdleThread
== NULL
) ||
730 (KiTimerSystemAuditing
== 0))
735 DPRINT("KernelTime %u, UserTime %u \n", Pcr
->PrcbData
.KernelTime
, Pcr
->PrcbData
.UserTime
);
737 if (oldIrql
> DISPATCH_LEVEL
)
739 Pcr
->PrcbData
.InterruptTime
++;
741 else if (oldIrql
== DISPATCH_LEVEL
)
743 Pcr
->PrcbData
.DpcTime
++;
747 CurrentThread
= KeGetCurrentThread();
748 ThreadsProcess
= ((PETHREAD
)CurrentThread
)->ThreadsProcess
;
750 * Cs bit 0 is always set for user mode if we are in protected mode.
751 * V86 mode is counted as user time.
754 Tf
->Eflags
& X86_EFLAGS_VM
)
757 InterlockedIncrement((PLONG
)&ThreadsProcess
->Pcb
.UserTime
);
759 ThreadsProcess
->Pcb
.UserTime
++;
761 CurrentThread
->UserTime
++;
762 Pcr
->PrcbData
.UserTime
++;
767 InterlockedIncrement((PLONG
)&ThreadsProcess
->Pcb
.KernelTime
);
769 ThreadsProcess
->Pcb
.KernelTime
++;
771 CurrentThread
->KernelTime
++;
772 Pcr
->PrcbData
.KernelTime
++;
782 KeSetTimeUpdateNotifyRoutine(
783 IN PTIME_UPDATE_NOTIFY_ROUTINE NotifyRoutine
796 IN PKTRAP_FRAME TrapFrame
799 KIRQL OldIrql
= PASSIVE_LEVEL
;
801 /* These are equivalent... we should just remove the Ki and stick it here... */
802 KiUpdateProcessThreadTime(OldIrql
, (PKIRQ_TRAPFRAME
)TrapFrame
);
811 IN PKTRAP_FRAME TrapFrame
,
815 KIRQL OldIrql
= PASSIVE_LEVEL
;
817 /* These are equivalent... we should just remove the Ki and stick it here... */
818 KiUpdateSystemTime(OldIrql
, (PKIRQ_TRAPFRAME
)TrapFrame
);