1 /* $Id: timer.c,v 1.90 2004/11/21 18:33:54 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 ***************************************************************/
23 #include <internal/debug.h>
26 /* GLOBALS ****************************************************************/
32 LARGE_INTEGER SystemBootTime
= (LARGE_INTEGER
)0LL;
34 LARGE_INTEGER SystemBootTime
= { 0 };
37 CHAR KiTimerSystemAuditing
= 0;
40 * Number of timer interrupts since initialisation
42 volatile ULONGLONG KeTickCount
= 0;
43 volatile ULONG KiRawTicks
= 0;
46 * The increment in the system clock every timer tick (in system time units)
52 #define CLOCK_INCREMENT (100000)
55 ULONG EXPORTED KeMaximumIncrement
= 100000;
56 ULONG EXPORTED KeMinimumIncrement
= 100000;
58 /* Microsoft-style declarations */
59 EXPORTED ULONG KeMaximumIncrement
= 100000;
60 EXPORTED ULONG KeMinimumIncrement
= 100000;
66 * PURPOSE: List of timers
68 static LIST_ENTRY AbsoluteTimerListHead
;
69 static LIST_ENTRY RelativeTimerListHead
;
70 static KSPIN_LOCK TimerListLock
;
71 static KSPIN_LOCK TimerValueLock
;
72 static KDPC ExpireTimerDpc
;
74 /* must raise IRQL to PROFILE_LEVEL and grab spin lock there, to sync with ISR */
76 extern HANDLE PsIdleThreadHandle
;
78 #define MICROSECONDS_PER_TICK (10000)
79 #define TICKS_TO_CALIBRATE (1)
80 #define CALIBRATE_PERIOD (MICROSECONDS_PER_TICK * TICKS_TO_CALIBRATE)
81 #define SYSTEM_TIME_UNITS_PER_MSEC (10000)
83 static BOOLEAN TimerInitDone
= FALSE
;
85 /* FUNCTIONS **************************************************************/
89 NtQueryTimerResolution(OUT PULONG MinimumResolution
,
90 OUT PULONG MaximumResolution
,
91 OUT PULONG ActualResolution
)
94 return STATUS_NOT_IMPLEMENTED
;
99 NtSetTimerResolution(IN ULONG DesiredResolution
,
100 IN BOOLEAN SetResolution
,
101 OUT PULONG CurrentResolution
)
104 return STATUS_NOT_IMPLEMENTED
;
109 NtQueryPerformanceCounter(IN PLARGE_INTEGER Counter
,
110 IN PLARGE_INTEGER Frequency
)
112 LARGE_INTEGER PerfCounter
;
113 LARGE_INTEGER PerfFrequency
;
116 PerfCounter
= KeQueryPerformanceCounter(&PerfFrequency
);
120 Status
= MmCopyToCaller(&Counter
->QuadPart
, &PerfCounter
.QuadPart
, sizeof(PerfCounter
.QuadPart
));
121 if (!NT_SUCCESS(Status
))
127 if (Frequency
!= NULL
)
129 Status
= MmCopyToCaller(&Frequency
->QuadPart
, &PerfFrequency
.QuadPart
, sizeof(PerfFrequency
.QuadPart
));
130 if (!NT_SUCCESS(Status
))
136 return(STATUS_SUCCESS
);
141 NtDelayExecution(IN ULONG Alertable
,
145 LARGE_INTEGER Timeout
;
147 Status
= MmCopyFromCaller(&Timeout
, Interval
, sizeof(Timeout
));
148 if (!NT_SUCCESS(Status
))
153 Timeout
= *((PLARGE_INTEGER
)Interval
);
154 DPRINT("NtDelayExecution(Alertable %d, Internal %x) IntervalP %x\n",
155 Alertable
, Internal
, Timeout
);
157 DPRINT("Execution delay is %d/%d\n",
158 Timeout
.u
.HighPart
, Timeout
.u
.LowPart
);
159 Status
= KeDelayExecutionThread(UserMode
, (BOOLEAN
)Alertable
, &Timeout
);
168 KeDelayExecutionThread (KPROCESSOR_MODE WaitMode
,
170 PLARGE_INTEGER Interval
)
172 * FUNCTION: Puts the current thread into an alertable or nonalertable
173 * wait state for a given internal
175 * WaitMode = Processor mode in which the caller is waiting
176 * Altertable = Specifies if the wait is alertable
177 * Interval = Specifies the interval to wait
181 PKTHREAD Thread
= KeGetCurrentThread();
183 KeSetTimer(&Thread
->Timer
, *Interval
, NULL
);
184 return (KeWaitForSingleObject(&Thread
->Timer
,
185 (WaitMode
== KernelMode
) ? Executive
: UserRequest
, /* TMN: Was unconditionally Executive */
186 WaitMode
, /* TMN: Was UserMode */
196 KeQueryTimeIncrement(VOID
)
198 * FUNCTION: Gets the increment (in 100-nanosecond units) that is added to
199 * the system clock every time the clock interrupts
200 * RETURNS: The increment
203 return(CLOCK_INCREMENT
);
208 * FUNCTION: Gets the current system time
210 * CurrentTime (OUT) = The routine stores the current time here
211 * NOTE: The time is the number of 100-nanosecond intervals since the
212 * 1st of January, 1601.
217 KeQuerySystemTime(PLARGE_INTEGER CurrentTime
)
221 CurrentTime
->u
.HighPart
= SharedUserData
->SystemTime
.High1Time
;
222 CurrentTime
->u
.LowPart
= SharedUserData
->SystemTime
.LowPart
;
224 while (CurrentTime
->u
.HighPart
!= SharedUserData
->SystemTime
.High2Time
);
228 KeQueryInterruptTime(VOID
)
230 LARGE_INTEGER CurrentTime
;
234 CurrentTime
.u
.HighPart
= SharedUserData
->InterruptTime
.High1Time
;
235 CurrentTime
.u
.LowPart
= SharedUserData
->InterruptTime
.LowPart
;
237 while (CurrentTime
.u
.HighPart
!= SharedUserData
->InterruptTime
.High2Time
);
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_IRQL_EQUAL(DISPATCH_LEVEL
);
531 KeAcquireDispatcherDatabaseLockAtDpcLevel();
532 Timer
->Header
.SignalState
= TRUE
;
533 KiDispatcherObjectWake(&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_IRQL_EQUAL(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 KeInitializeTimerImpl(VOID
)
634 * FUNCTION: Initializes timer irq handling
635 * NOTE: This is only called once from main()
638 TIME_FIELDS TimeFields
;
640 DPRINT("KeInitializeTimerImpl()\n");
641 InitializeListHead(&AbsoluteTimerListHead
);
642 InitializeListHead(&RelativeTimerListHead
);
643 KeInitializeSpinLock(&TimerListLock
);
644 KeInitializeSpinLock(&TimerValueLock
);
645 KeInitializeDpc(&ExpireTimerDpc
, KeExpireTimers
, 0);
647 * Calculate the starting time for the system clock
649 HalQueryRealTimeClock(&TimeFields
);
650 RtlTimeFieldsToTime(&TimeFields
, &SystemBootTime
);
652 SharedUserData
->TickCountLowDeprecated
= 0;
653 SharedUserData
->TickCountMultiplier
= 167783691; // 2^24 * 1193182 / 119310
654 SharedUserData
->InterruptTime
.High2Time
= 0;
655 SharedUserData
->InterruptTime
.LowPart
= 0;
656 SharedUserData
->InterruptTime
.High1Time
= 0;
657 SharedUserData
->SystemTime
.High2Time
= SystemBootTime
.u
.HighPart
;
658 SharedUserData
->SystemTime
.LowPart
= SystemBootTime
.u
.LowPart
;
659 SharedUserData
->SystemTime
.High1Time
= SystemBootTime
.u
.HighPart
;
661 TimerInitDone
= TRUE
;
662 DPRINT("Finished KeInitializeTimerImpl()\n");
670 KeSetTimeUpdateNotifyRoutine(
671 IN PTIME_UPDATE_NOTIFY_ROUTINE NotifyRoutine
679 * NOTE: On Windows this function takes exactly one parameter and EBP is
680 * guaranteed to point to KTRAP_FRAME. The function is used only
681 * by HAL, so there's no point in keeping that prototype.
688 IN PKTRAP_FRAME TrapFrame
,
693 PKTHREAD CurrentThread
;
694 PKPROCESS CurrentProcess
;
699 Pcr
= KeGetCurrentKPCR();
701 /* Make sure we don't go further if we're in early boot phase. */
702 if (Pcr
== NULL
|| Pcr
->PrcbData
.CurrentThread
== NULL
)
705 DPRINT("KernelTime %u, UserTime %u \n", Pcr
->PrcbData
.KernelTime
, Pcr
->PrcbData
.UserTime
);
707 CurrentThread
= Pcr
->PrcbData
.CurrentThread
;
708 CurrentProcess
= CurrentThread
->ApcState
.Process
;
711 * Cs bit 0 is always set for user mode if we are in protected mode.
712 * V86 mode is counted as user time.
714 if (TrapFrame
->Cs
& 0x1 ||
715 TrapFrame
->Eflags
& X86_EFLAGS_VM
)
717 InterlockedIncrement((PLONG
)&CurrentThread
->UserTime
);
718 InterlockedIncrement((PLONG
)&CurrentProcess
->UserTime
);
719 InterlockedIncrement((PLONG
)&Pcr
->PrcbData
.UserTime
);
723 if (Irql
> DISPATCH_LEVEL
)
725 Pcr
->PrcbData
.InterruptTime
++;
727 else if (Irql
== DISPATCH_LEVEL
)
729 Pcr
->PrcbData
.DpcTime
++;
733 InterlockedIncrement((PLONG
)&CurrentThread
->KernelTime
);
734 InterlockedIncrement((PLONG
)&CurrentProcess
->KernelTime
);
739 DpcLastCount
= Pcr
->PrcbData
.DpcLastCount
;
740 Pcr
->PrcbData
.DpcLastCount
= Pcr
->PrcbData
.DpcCount
;
741 Pcr
->PrcbData
.DpcRequestRate
= ((Pcr
->PrcbData
.DpcCount
- DpcLastCount
) +
742 Pcr
->PrcbData
.DpcRequestRate
) / 2;
745 if (Pcr
->PrcbData
.DpcData
[0].DpcQueueDepth
> 0 &&
746 Pcr
->PrcbData
.DpcRoutineActive
== FALSE
&&
747 Pcr
->PrcbData
.DpcInterruptRequested
== FALSE
)
749 HalRequestSoftwareInterrupt(DISPATCH_LEVEL
);
752 /* FIXME: Do DPC rate adjustments */
755 * If we're at end of quantum request software interrupt. The rest
756 * is handled in KiDispatchInterrupt.
758 if ((CurrentThread
->Quantum
-= 3) <= 0)
760 Pcr
->PrcbData
.QuantumEnd
= TRUE
;
761 HalRequestSoftwareInterrupt(DISPATCH_LEVEL
);
767 * NOTE: On Windows this function takes exactly zero parameters and EBP is
768 * guaranteed to point to KTRAP_FRAME. Also [esp+0] contains an IRQL.
769 * The function is used only by HAL, so there's no point in keeping
777 IN PKTRAP_FRAME TrapFrame
,
781 * FUNCTION: Handles a timer interrupt
786 ASSERT(KeGetCurrentIrql() == PROFILE_LEVEL
);
790 if (TimerInitDone
== FALSE
)
795 * Increment the number of timers ticks
798 SharedUserData
->TickCountLowDeprecated
++;
799 KiAcquireSpinLock(&TimerValueLock
);
801 Time
.u
.LowPart
= SharedUserData
->InterruptTime
.LowPart
;
802 Time
.u
.HighPart
= SharedUserData
->InterruptTime
.High1Time
;
803 Time
.QuadPart
+= CLOCK_INCREMENT
;
804 SharedUserData
->InterruptTime
.High2Time
= Time
.u
.HighPart
;
805 SharedUserData
->InterruptTime
.LowPart
= Time
.u
.LowPart
;
806 SharedUserData
->InterruptTime
.High1Time
= Time
.u
.HighPart
;
808 Time
.u
.LowPart
= SharedUserData
->SystemTime
.LowPart
;
809 Time
.u
.HighPart
= SharedUserData
->SystemTime
.High1Time
;
810 Time
.QuadPart
+= CLOCK_INCREMENT
;
811 SharedUserData
->SystemTime
.High2Time
= Time
.u
.HighPart
;
812 SharedUserData
->SystemTime
.LowPart
= Time
.u
.LowPart
;
813 SharedUserData
->SystemTime
.High1Time
= Time
.u
.HighPart
;
815 /* FIXME: Here we should check for remote debugger break-ins */
817 KiReleaseSpinLock(&TimerValueLock
);
819 /* Update process and thread times */
820 KeUpdateRunTime(TrapFrame
, Irql
);
823 * Queue a DPC that will expire timers
825 KeInsertQueueDpc(&ExpireTimerDpc
, (PVOID
)TrapFrame
->Eip
, 0);