2 * COPYRIGHT: See COPYING in the top level directory
3 * PROJECT: ReactOS kernel
4 * FILE: ntoskrnl/ke/timer.c
5 * PURPOSE: Handle Kernel Timers (Kernel-part of Executive Timers)
7 * PROGRAMMERS: Alex Ionescu (alex@relsoft.net) - Reimplemented some parts, fixed many bugs.
8 * David Welch (welch@mcmail.com) & Phillip Susi - Original Implementation.
11 /* INCLUDES ***************************************************************/
16 #include <internal/debug.h>
18 /* GLOBALS ****************************************************************/
20 LIST_ENTRY KiTimerListHead
;
22 #define SYSTEM_TIME_UNITS_PER_MSEC (10000)
24 /* FUNCTIONS **************************************************************/
26 BOOLEAN STDCALL
KiInsertTimer(PKTIMER Timer
, LARGE_INTEGER DueTime
);
27 VOID STDCALL
KiHandleExpiredTimer(PKTIMER Timer
);
32 * FUNCTION: Removes a timer from the system timer list
34 * Timer = timer to cancel
35 * RETURNS: True if the timer was running
40 KeCancelTimer(PKTIMER Timer
)
43 BOOLEAN Inserted
= FALSE
;
45 DPRINT("KeCancelTimer(Timer %x)\n",Timer
);
47 /* Lock the Database and Raise IRQL */
48 OldIrql
= KeAcquireDispatcherDatabaseLock();
50 /* Check if it's inserted, and remove it if it is */
51 if ((Inserted
= Timer
->Header
.Inserted
)) {
53 /* Remove from list */
54 DPRINT("Timer was inserted, removing\n");
55 RemoveEntryList(&Timer
->TimerListEntry
);
56 Timer
->Header
.Inserted
= FALSE
;
60 DPRINT("Timer was not inserted\n");
63 /* Release Dispatcher Lock */
64 KeReleaseDispatcherDatabaseLock(OldIrql
);
66 /* Return the old state */
67 DPRINT("Old State: %d\n", Inserted
);
74 * FUNCTION: Initalizes a kernel timer object
76 * Timer = caller supplied storage for the timer
77 * NOTE: This function initializes a notification timer
81 KeInitializeTimer (PKTIMER Timer
)
84 /* Call the New Function */
85 KeInitializeTimerEx(Timer
, NotificationTimer
);
91 * FUNCTION: Initializes a kernel timer object
93 * Timer = caller supplied storage for the timer
94 * Type = the type of timer (notification or synchronization)
95 * NOTE: When a notification type expires all waiting threads are released
96 * and the timer remains signalled until it is explicitly reset. When a
97 * syncrhonization timer expires its state is set to signalled until a
98 * single waiting thread is released and then the timer is reset.
102 KeInitializeTimerEx (PKTIMER Timer
,
106 DPRINT("KeInitializeTimerEx(%x, %d)\n", Timer
, Type
);
108 /* Initialize the Dispatch Header */
109 KeInitializeDispatcherHeader(&Timer
->Header
,
110 TimerNotificationObject
+ Type
,
111 sizeof(KTIMER
) / sizeof(ULONG
),
114 /* Initalize the Other data */
115 Timer
->DueTime
.QuadPart
= 0;
125 KeReadStateTimer (PKTIMER Timer
)
127 /* Return the Signal State */
128 return (BOOLEAN
)Timer
->Header
.SignalState
;
134 * FUNCTION: Sets the absolute or relative interval at which a timer object
135 * is to be set to the signaled state and optionally supplies a
136 * CustomTimerDpc to be executed when the timer expires.
138 * Timer = Points to a previously initialized timer object
139 * DueTimer = If positive then absolute time to expire at
140 * If negative then the relative time to expire at
141 * Dpc = If non-NULL then a dpc to be called when the timer expires
142 * RETURNS: True if the timer was already in the system timer queue
147 KeSetTimer (PKTIMER Timer
,
148 LARGE_INTEGER DueTime
,
151 /* Call the newer function and supply a period of 0 */
152 return KeSetTimerEx(Timer
, DueTime
, 0, Dpc
);
158 * FUNCTION: Sets the absolute or relative interval at which a timer object
159 * is to be set to the signaled state and optionally supplies a
160 * CustomTimerDpc to be executed when the timer expires.
162 * Timer = Points to a previously initialized timer object
163 * DueTimer = If positive then absolute time to expire at
164 * If negative then the relative time to expire at
165 * Dpc = If non-NULL then a dpc to be called when the timer expires
166 * RETURNS: True if the timer was already in the system timer queue
171 KeSetTimerEx (PKTIMER Timer
,
172 LARGE_INTEGER DueTime
,
179 DPRINT("KeSetTimerEx(Timer %x, DueTime %I64d, Period %d, Dpc %x)\n", Timer
, DueTime
.QuadPart
, Period
, Dpc
);
180 ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL
);
182 /* Lock the Database and Raise IRQL */
183 OldIrql
= KeAcquireDispatcherDatabaseLock();
185 /* Check if it's inserted, and remove it if it is */
186 if ((Inserted
= Timer
->Header
.Inserted
)) {
188 /* Remove from list */
189 DPRINT("Timer was already inserted\n");
190 RemoveEntryList(&Timer
->TimerListEntry
);
191 Timer
->Header
.Inserted
= FALSE
;
194 /* Set Default Timer Data */
196 Timer
->Period
= Period
;
197 Timer
->Header
.SignalState
= FALSE
;
200 if (!KiInsertTimer(Timer
, DueTime
)) {
202 KiHandleExpiredTimer(Timer
);
205 /* Release Dispatcher Lock */
206 KeReleaseDispatcherDatabaseLock(OldIrql
);
208 /* Return old state */
209 DPRINT("Old State: %d\n", Inserted
);
215 KiExpireTimers(PKDPC Dpc
,
216 PVOID DeferredContext
,
217 PVOID SystemArgument1
,
218 PVOID SystemArgument2
)
221 ULONGLONG InterruptTime
;
222 LIST_ENTRY ExpiredTimerList
;
223 PLIST_ENTRY CurrentEntry
= NULL
;
226 DPRINT("KiExpireTimers(Dpc: %x)\n", Dpc
);
228 /* Initialize the Expired Timer List */
229 InitializeListHead(&ExpiredTimerList
);
231 /* Lock the Database and Raise IRQL */
232 OldIrql
= KeAcquireDispatcherDatabaseLock();
234 /* Query Interrupt Times */
235 InterruptTime
= KeQueryInterruptTime();
237 /* Loop through the Timer List and remove Expired Timers. Insert them into the Expired Listhead */
238 CurrentEntry
= KiTimerListHead
.Flink
;
239 while (CurrentEntry
!= &KiTimerListHead
) {
241 /* Get the Current Timer */
242 Timer
= CONTAINING_RECORD(CurrentEntry
, KTIMER
, TimerListEntry
);
243 DPRINT("Looping for Timer: %x. Duetime: %I64d. InterruptTime %I64d \n", Timer
, Timer
->DueTime
.QuadPart
, InterruptTime
);
245 /* Check if we have to Expire it */
246 if (InterruptTime
< Timer
->DueTime
.QuadPart
) break;
248 CurrentEntry
= CurrentEntry
->Flink
;
250 /* Remove it from the Timer List, add it to the Expired List */
251 RemoveEntryList(&Timer
->TimerListEntry
);
252 InsertTailList(&ExpiredTimerList
, &Timer
->TimerListEntry
);
255 /* Expire the Timers */
256 while ((CurrentEntry
= RemoveHeadList(&ExpiredTimerList
)) != &ExpiredTimerList
) {
259 Timer
= CONTAINING_RECORD(CurrentEntry
, KTIMER
, TimerListEntry
);
260 DPRINT("Expiring Timer: %x\n", Timer
);
263 KiHandleExpiredTimer(Timer
);
266 DPRINT("Timers expired\n");
268 /* Release Dispatcher Lock */
269 KeReleaseDispatcherDatabaseLock(OldIrql
);
273 * We enter this function at IRQL DISPATCH_LEVEL, and with the
274 * Dispatcher Lock held!
278 KiHandleExpiredTimer(PKTIMER Timer
)
281 LARGE_INTEGER DueTime
;
282 DPRINT("HandleExpiredTime(Timer %x)\n", Timer
);
284 if(Timer
->Header
.Inserted
) {
286 /* First of all, remove the Timer */
287 Timer
->Header
.Inserted
= FALSE
;
288 RemoveEntryList(&Timer
->TimerListEntry
);
291 /* Set it as Signaled */
292 DPRINT("Setting Timer as Signaled\n");
293 Timer
->Header
.SignalState
= TRUE
;
294 KiWaitTest(&Timer
->Header
, IO_NO_INCREMENT
);
296 /* If the Timer is periodic, reinsert the timer with the new due time */
299 /* Reinsert the Timer */
300 DueTime
.QuadPart
= Timer
->Period
* -SYSTEM_TIME_UNITS_PER_MSEC
;
301 if (!KiInsertTimer(Timer
, DueTime
)) {
303 /* FIXME: I will think about how to handle this and fix it ASAP -- Alex */
304 DPRINT("CRITICAL UNHANDLED CASE: TIMER ALREADY EXPIRED!!!\n");
308 /* Check if the Timer has a DPC */
311 DPRINT("Timer->Dpc %x Timer->Dpc->DeferredRoutine %x\n", Timer
->Dpc
, Timer
->Dpc
->DeferredRoutine
);
314 KeInsertQueueDpc(Timer
->Dpc
,
318 DPRINT("Finished dpc routine\n");
323 * Note: This function is called with the Dispatcher Lock held.
327 KiInsertTimer(PKTIMER Timer
,
328 LARGE_INTEGER DueTime
)
330 LARGE_INTEGER SystemTime
;
331 LARGE_INTEGER DifferenceTime
;
332 ULONGLONG InterruptTime
;
334 DPRINT("KiInsertTimer(Timer %x DueTime %I64d)\n", Timer
, DueTime
.QuadPart
);
336 /* Set default data */
337 Timer
->Header
.Inserted
= TRUE
;
338 Timer
->Header
.Absolute
= FALSE
;
339 if (!Timer
->Period
) Timer
->Header
.SignalState
= FALSE
;
341 /* Convert to relative time if needed */
342 if (DueTime
.u
.HighPart
>= 0) {
344 /* Get System Time */
345 KeQuerySystemTime(&SystemTime
);
347 /* Do the conversion */
348 DifferenceTime
.QuadPart
= SystemTime
.QuadPart
- DueTime
.QuadPart
;
349 DPRINT("Time Difference is: %I64d\n", DifferenceTime
.QuadPart
);
351 /* Make sure it hasn't already expired */
352 if (DifferenceTime
.u
.HighPart
>= 0) {
354 /* Cancel everything */
355 DPRINT("Timer already expired: %d\n", DifferenceTime
.u
.HighPart
);
356 Timer
->Header
.SignalState
= TRUE
;
357 Timer
->Header
.Inserted
= FALSE
;
361 /* Set the time as Absolute */
362 Timer
->Header
.Absolute
= TRUE
;
363 DueTime
= DifferenceTime
;
366 /* Get the Interrupt Time */
367 InterruptTime
= KeQueryInterruptTime();
369 /* Set the Final Due Time */
370 Timer
->DueTime
.QuadPart
= InterruptTime
- DueTime
.QuadPart
;
371 DPRINT("Final Due Time is: %I64d\n", Timer
->DueTime
.QuadPart
);
373 /* Now insert it into the Timer List */
374 DPRINT("Inserting Timer into list\n");
375 InsertAscendingList(&KiTimerListHead
,