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
39 * The statement in the DDK for KeCancelTimer that "if a DPC object is
40 * associated with the timer, it too is canceled" is wrong -- nothing is
41 * done with the DPC object when the timer is removed from the system
42 * queue. So its very likely that the DPC will run after you have canceled
44 * For what it's worth, calling KeRemoveQueueDpc after KeCancelTimer would
45 * be sufficient to prevent any problems associated with destroying the DPC
46 * object, at least as the OS is currently implemented. This is because the
47 * DPC dispatcher doesn't need access to the object once the DPC is
48 * dequeued, and the dequeuing happens before the DPC routine gets called."
49 * -Gunnar (article by Walter Oney)
53 KeCancelTimer(PKTIMER Timer
)
56 BOOLEAN Inserted
= FALSE
;
58 DPRINT("KeCancelTimer(Timer %x)\n",Timer
);
60 /* Lock the Database and Raise IRQL */
61 OldIrql
= KeAcquireDispatcherDatabaseLock();
63 /* Check if it's inserted, and remove it if it is */
64 if ((Inserted
= Timer
->Header
.Inserted
)) {
66 /* Remove from list */
67 DPRINT("Timer was inserted, removing\n");
68 RemoveEntryList(&Timer
->TimerListEntry
);
69 Timer
->Header
.Inserted
= FALSE
;
73 DPRINT("Timer was not inserted\n");
76 /* Release Dispatcher Lock */
77 KeReleaseDispatcherDatabaseLock(OldIrql
);
79 /* Return the old state */
80 DPRINT("Old State: %d\n", Inserted
);
87 * FUNCTION: Initalizes a kernel timer object
89 * Timer = caller supplied storage for the timer
90 * NOTE: This function initializes a notification timer
94 KeInitializeTimer (PKTIMER Timer
)
97 /* Call the New Function */
98 KeInitializeTimerEx(Timer
, NotificationTimer
);
104 * FUNCTION: Initializes a kernel timer object
106 * Timer = caller supplied storage for the timer
107 * Type = the type of timer (notification or synchronization)
108 * NOTE: When a notification type expires all waiting threads are released
109 * and the timer remains signalled until it is explicitly reset. When a
110 * syncrhonization timer expires its state is set to signalled until a
111 * single waiting thread is released and then the timer is reset.
115 KeInitializeTimerEx (PKTIMER Timer
,
119 DPRINT("KeInitializeTimerEx(%x, %d)\n", Timer
, Type
);
121 /* Initialize the Dispatch Header */
122 KeInitializeDispatcherHeader(&Timer
->Header
,
123 TimerNotificationObject
+ Type
,
124 sizeof(KTIMER
) / sizeof(ULONG
),
127 /* Initalize the Other data */
128 Timer
->DueTime
.QuadPart
= 0;
138 KeReadStateTimer (PKTIMER Timer
)
140 /* Return the Signal State */
141 return (BOOLEAN
)Timer
->Header
.SignalState
;
147 * FUNCTION: Sets the absolute or relative interval at which a timer object
148 * is to be set to the signaled state and optionally supplies a
149 * CustomTimerDpc to be executed when the timer expires.
151 * Timer = Points to a previously initialized timer object
152 * DueTimer = If positive then absolute time to expire at
153 * If negative then the relative time to expire at
154 * Dpc = If non-NULL then a dpc to be called when the timer expires
155 * RETURNS: True if the timer was already in the system timer queue
160 KeSetTimer (PKTIMER Timer
,
161 LARGE_INTEGER DueTime
,
164 /* Call the newer function and supply a period of 0 */
165 return KeSetTimerEx(Timer
, DueTime
, 0, Dpc
);
171 * FUNCTION: Sets the absolute or relative interval at which a timer object
172 * is to be set to the signaled state and optionally supplies a
173 * CustomTimerDpc to be executed when the timer expires.
175 * Timer = Points to a previously initialized timer object
176 * DueTimer = If positive then absolute time to expire at
177 * If negative then the relative time to expire at
178 * Dpc = If non-NULL then a dpc to be called when the timer expires
179 * RETURNS: True if the timer was already in the system timer queue
184 KeSetTimerEx (PKTIMER Timer
,
185 LARGE_INTEGER DueTime
,
192 DPRINT("KeSetTimerEx(Timer %x, DueTime %I64d, Period %d, Dpc %x)\n", Timer
, DueTime
.QuadPart
, Period
, Dpc
);
193 ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL
);
195 /* Lock the Database and Raise IRQL */
196 OldIrql
= KeAcquireDispatcherDatabaseLock();
198 /* Check if it's inserted, and remove it if it is */
199 if ((Inserted
= Timer
->Header
.Inserted
)) {
201 /* Remove from list */
202 DPRINT("Timer was already inserted\n");
203 RemoveEntryList(&Timer
->TimerListEntry
);
204 Timer
->Header
.Inserted
= FALSE
;
207 /* Set Default Timer Data */
209 Timer
->Period
= Period
;
210 Timer
->Header
.SignalState
= FALSE
;
213 if (!KiInsertTimer(Timer
, DueTime
)) {
215 KiHandleExpiredTimer(Timer
);
218 /* Release Dispatcher Lock */
219 KeReleaseDispatcherDatabaseLock(OldIrql
);
221 /* Return old state */
222 DPRINT("Old State: %d\n", Inserted
);
228 KiExpireTimers(PKDPC Dpc
,
229 PVOID DeferredContext
,
230 PVOID SystemArgument1
,
231 PVOID SystemArgument2
)
234 ULONGLONG InterruptTime
;
235 LIST_ENTRY ExpiredTimerList
;
236 PLIST_ENTRY CurrentEntry
= NULL
;
239 DPRINT("KiExpireTimers(Dpc: %x)\n", Dpc
);
241 /* Initialize the Expired Timer List */
242 InitializeListHead(&ExpiredTimerList
);
244 /* Lock the Database and Raise IRQL */
245 OldIrql
= KeAcquireDispatcherDatabaseLock();
247 /* Query Interrupt Times */
248 InterruptTime
= KeQueryInterruptTime();
250 /* Loop through the Timer List and remove Expired Timers. Insert them into the Expired Listhead */
251 LIST_FOR_EACH_SAFE(Timer
, tmp
, &KiTimerListHead
, KTIMER
, TimerListEntry
)
253 DPRINT("Looping for Timer: %x. Duetime: %I64d. InterruptTime %I64d \n", Timer
, Timer
->DueTime
.QuadPart
, InterruptTime
);
255 /* Check if we have to Expire it */
256 if (InterruptTime
< Timer
->DueTime
.QuadPart
) break;
258 /* Remove it from the Timer List, add it to the Expired List */
259 RemoveEntryList(&Timer
->TimerListEntry
);
260 InsertTailList(&ExpiredTimerList
, &Timer
->TimerListEntry
);
263 /* Expire the Timers */
264 while (!IsListEmpty(&ExpiredTimerList
)) {
266 CurrentEntry
= RemoveHeadList(&ExpiredTimerList
);
269 Timer
= CONTAINING_RECORD(CurrentEntry
, KTIMER
, TimerListEntry
);
270 DPRINT("Expiring Timer: %x\n", Timer
);
273 KiHandleExpiredTimer(Timer
);
276 DPRINT("Timers expired\n");
278 /* Release Dispatcher Lock */
279 KeReleaseDispatcherDatabaseLock(OldIrql
);
283 * We enter this function at IRQL DISPATCH_LEVEL, and with the
284 * Dispatcher Lock held!
288 KiHandleExpiredTimer(PKTIMER Timer
)
291 LARGE_INTEGER DueTime
;
292 DPRINT("HandleExpiredTime(Timer %x)\n", Timer
);
294 if(Timer
->Header
.Inserted
) {
296 /* First of all, remove the Timer */
297 Timer
->Header
.Inserted
= FALSE
;
298 RemoveEntryList(&Timer
->TimerListEntry
);
301 /* Set it as Signaled */
302 DPRINT("Setting Timer as Signaled\n");
303 Timer
->Header
.SignalState
= TRUE
;
304 KiWaitTest(&Timer
->Header
, IO_NO_INCREMENT
);
306 /* If the Timer is periodic, reinsert the timer with the new due time */
309 /* Reinsert the Timer */
310 DueTime
.QuadPart
= Timer
->Period
* -SYSTEM_TIME_UNITS_PER_MSEC
;
311 if (!KiInsertTimer(Timer
, DueTime
)) {
313 /* FIXME: I will think about how to handle this and fix it ASAP -- Alex */
314 DPRINT("CRITICAL UNHANDLED CASE: TIMER ALREADY EXPIRED!!!\n");
318 /* Check if the Timer has a DPC */
321 DPRINT("Timer->Dpc %x Timer->Dpc->DeferredRoutine %x\n", Timer
->Dpc
, Timer
->Dpc
->DeferredRoutine
);
324 KeInsertQueueDpc(Timer
->Dpc
,
328 DPRINT("Finished dpc routine\n");
333 * Note: This function is called with the Dispatcher Lock held.
337 KiInsertTimer(PKTIMER Timer
,
338 LARGE_INTEGER DueTime
)
340 LARGE_INTEGER SystemTime
;
341 LARGE_INTEGER DifferenceTime
;
342 ULONGLONG InterruptTime
;
344 DPRINT("KiInsertTimer(Timer %x DueTime %I64d)\n", Timer
, DueTime
.QuadPart
);
346 /* Set default data */
347 Timer
->Header
.Inserted
= TRUE
;
348 Timer
->Header
.Absolute
= FALSE
;
349 if (!Timer
->Period
) Timer
->Header
.SignalState
= FALSE
;
351 /* Convert to relative time if needed */
352 if (DueTime
.u
.HighPart
>= 0) {
354 /* Get System Time */
355 KeQuerySystemTime(&SystemTime
);
357 /* Do the conversion */
358 DifferenceTime
.QuadPart
= SystemTime
.QuadPart
- DueTime
.QuadPart
;
359 DPRINT("Time Difference is: %I64d\n", DifferenceTime
.QuadPart
);
361 /* Make sure it hasn't already expired */
362 if (DifferenceTime
.u
.HighPart
>= 0) {
364 /* Cancel everything */
365 DPRINT("Timer already expired: %d\n", DifferenceTime
.u
.HighPart
);
366 Timer
->Header
.SignalState
= TRUE
;
367 Timer
->Header
.Inserted
= FALSE
;
371 /* Set the time as Absolute */
372 Timer
->Header
.Absolute
= TRUE
;
373 DueTime
= DifferenceTime
;
376 /* Get the Interrupt Time */
377 InterruptTime
= KeQueryInterruptTime();
379 /* Set the Final Due Time */
380 Timer
->DueTime
.QuadPart
= InterruptTime
- DueTime
.QuadPart
;
381 DPRINT("Final Due Time is: %I64d\n", Timer
->DueTime
.QuadPart
);
383 /* Now insert it into the Timer List */
384 DPRINT("Inserting Timer into list\n");
385 InsertAscendingList(&KiTimerListHead
,