- Make the NDK compatible with the MSDDK again.
[reactos.git] / reactos / ntoskrnl / ke / timer.c
1 /*
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)
6 *
7 * PROGRAMMERS: Alex Ionescu (alex@relsoft.net) - Reimplemented some parts, fixed many bugs.
8 * David Welch (welch@mcmail.com) & Phillip Susi - Original Implementation.
9 */
10
11 /* INCLUDES ***************************************************************/
12
13 #include <ntoskrnl.h>
14
15 #define NDEBUG
16 #include <internal/debug.h>
17
18 /* GLOBALS ****************************************************************/
19
20 LIST_ENTRY KiTimerListHead;
21
22 #define SYSTEM_TIME_UNITS_PER_MSEC (10000)
23
24 /* FUNCTIONS **************************************************************/
25
26 BOOLEAN STDCALL KiInsertTimer(PKTIMER Timer, LARGE_INTEGER DueTime);
27 VOID STDCALL KiHandleExpiredTimer(PKTIMER Timer);
28
29 /*
30 * @implemented
31 *
32 * FUNCTION: Removes a timer from the system timer list
33 * ARGUMENTS:
34 * Timer = timer to cancel
35 * RETURNS: True if the timer was running
36 * False otherwise
37 *
38 * DANGER!
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
43 * the timer!
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)
50 */
51 BOOLEAN
52 STDCALL
53 KeCancelTimer(PKTIMER Timer)
54 {
55 KIRQL OldIrql;
56 BOOLEAN Inserted = FALSE;
57
58 DPRINT("KeCancelTimer(Timer %x)\n",Timer);
59
60 /* Lock the Database and Raise IRQL */
61 OldIrql = KeAcquireDispatcherDatabaseLock();
62
63 /* Check if it's inserted, and remove it if it is */
64 if ((Inserted = Timer->Header.Inserted)) {
65
66 /* Remove from list */
67 DPRINT("Timer was inserted, removing\n");
68 RemoveEntryList(&Timer->TimerListEntry);
69 Timer->Header.Inserted = FALSE;
70
71 } else {
72
73 DPRINT("Timer was not inserted\n");
74 }
75
76 /* Release Dispatcher Lock */
77 KeReleaseDispatcherDatabaseLock(OldIrql);
78
79 /* Return the old state */
80 DPRINT("Old State: %d\n", Inserted);
81 return(Inserted);
82 }
83
84 /*
85 * @implemented
86 *
87 * FUNCTION: Initalizes a kernel timer object
88 * ARGUMENTS:
89 * Timer = caller supplied storage for the timer
90 * NOTE: This function initializes a notification timer
91 */
92 VOID
93 STDCALL
94 KeInitializeTimer (PKTIMER Timer)
95
96 {
97 /* Call the New Function */
98 KeInitializeTimerEx(Timer, NotificationTimer);
99 }
100
101 /*
102 * @implemented
103 *
104 * FUNCTION: Initializes a kernel timer object
105 * ARGUMENTS:
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.
112 */
113 VOID
114 STDCALL
115 KeInitializeTimerEx (PKTIMER Timer,
116 TIMER_TYPE Type)
117 {
118
119 DPRINT("KeInitializeTimerEx(%x, %d)\n", Timer, Type);
120
121 /* Initialize the Dispatch Header */
122 KeInitializeDispatcherHeader(&Timer->Header,
123 TimerNotificationObject + Type,
124 sizeof(KTIMER) / sizeof(ULONG),
125 FALSE);
126
127 /* Initalize the Other data */
128 Timer->DueTime.QuadPart = 0;
129 Timer->Period = 0;
130 }
131
132
133 /*
134 * @implemented
135 */
136 BOOLEAN
137 STDCALL
138 KeReadStateTimer (PKTIMER Timer)
139 {
140 /* Return the Signal State */
141 return (BOOLEAN)Timer->Header.SignalState;
142 }
143
144 /*
145 * @implemented
146 *
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.
150 * ARGUMENTS:
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
156 * False otherwise
157 */
158 BOOLEAN
159 STDCALL
160 KeSetTimer (PKTIMER Timer,
161 LARGE_INTEGER DueTime,
162 PKDPC Dpc)
163 {
164 /* Call the newer function and supply a period of 0 */
165 return KeSetTimerEx(Timer, DueTime, 0, Dpc);
166 }
167
168 /*
169 * @implemented
170 *
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.
174 * ARGUMENTS:
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
180 * False otherwise
181 */
182 BOOLEAN
183 STDCALL
184 KeSetTimerEx (PKTIMER Timer,
185 LARGE_INTEGER DueTime,
186 LONG Period,
187 PKDPC Dpc)
188 {
189 KIRQL OldIrql;
190 BOOLEAN Inserted;
191
192 DPRINT("KeSetTimerEx(Timer %x, DueTime %I64d, Period %d, Dpc %x)\n", Timer, DueTime.QuadPart, Period, Dpc);
193 ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);
194
195 /* Lock the Database and Raise IRQL */
196 OldIrql = KeAcquireDispatcherDatabaseLock();
197
198 /* Check if it's inserted, and remove it if it is */
199 if ((Inserted = Timer->Header.Inserted)) {
200
201 /* Remove from list */
202 DPRINT("Timer was already inserted\n");
203 RemoveEntryList(&Timer->TimerListEntry);
204 Timer->Header.Inserted = FALSE;
205 }
206
207 /* Set Default Timer Data */
208 Timer->Dpc = Dpc;
209 Timer->Period = Period;
210 Timer->Header.SignalState = FALSE;
211
212 /* Insert it */
213 if (!KiInsertTimer(Timer, DueTime)) {
214
215 KiHandleExpiredTimer(Timer);
216 };
217
218 /* Release Dispatcher Lock */
219 KeReleaseDispatcherDatabaseLock(OldIrql);
220
221 /* Return old state */
222 DPRINT("Old State: %d\n", Inserted);
223 return Inserted;
224 }
225
226 VOID
227 STDCALL
228 KiExpireTimers(PKDPC Dpc,
229 PVOID DeferredContext,
230 PVOID SystemArgument1,
231 PVOID SystemArgument2)
232 {
233 PKTIMER Timer, tmp;
234 ULONGLONG InterruptTime;
235 LIST_ENTRY ExpiredTimerList;
236 PLIST_ENTRY CurrentEntry = NULL;
237 KIRQL OldIrql;
238
239 DPRINT("KiExpireTimers(Dpc: %x)\n", Dpc);
240
241 /* Initialize the Expired Timer List */
242 InitializeListHead(&ExpiredTimerList);
243
244 /* Lock the Database and Raise IRQL */
245 OldIrql = KeAcquireDispatcherDatabaseLock();
246
247 /* Query Interrupt Times */
248 InterruptTime = KeQueryInterruptTime();
249
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)
252 {
253 DPRINT("Looping for Timer: %x. Duetime: %I64d. InterruptTime %I64d \n", Timer, Timer->DueTime.QuadPart, InterruptTime);
254
255 /* Check if we have to Expire it */
256 if (InterruptTime < Timer->DueTime.QuadPart) break;
257
258 /* Remove it from the Timer List, add it to the Expired List */
259 RemoveEntryList(&Timer->TimerListEntry);
260 InsertTailList(&ExpiredTimerList, &Timer->TimerListEntry);
261 }
262
263 /* Expire the Timers */
264 while (!IsListEmpty(&ExpiredTimerList)) {
265
266 CurrentEntry = RemoveHeadList(&ExpiredTimerList);
267
268 /* Get the Timer */
269 Timer = CONTAINING_RECORD(CurrentEntry, KTIMER, TimerListEntry);
270 DPRINT("Expiring Timer: %x\n", Timer);
271
272 /* Expire it */
273 KiHandleExpiredTimer(Timer);
274 }
275
276 DPRINT("Timers expired\n");
277
278 /* Release Dispatcher Lock */
279 KeReleaseDispatcherDatabaseLock(OldIrql);
280 }
281
282 /*
283 * We enter this function at IRQL DISPATCH_LEVEL, and with the
284 * Dispatcher Lock held!
285 */
286 VOID
287 STDCALL
288 KiHandleExpiredTimer(PKTIMER Timer)
289 {
290
291 LARGE_INTEGER DueTime;
292 DPRINT("HandleExpiredTime(Timer %x)\n", Timer);
293
294 if(Timer->Header.Inserted) {
295
296 /* First of all, remove the Timer */
297 Timer->Header.Inserted = FALSE;
298 RemoveEntryList(&Timer->TimerListEntry);
299 }
300
301 /* Set it as Signaled */
302 DPRINT("Setting Timer as Signaled\n");
303 Timer->Header.SignalState = TRUE;
304 KiWaitTest(&Timer->Header, IO_NO_INCREMENT);
305
306 /* If the Timer is periodic, reinsert the timer with the new due time */
307 if (Timer->Period) {
308
309 /* Reinsert the Timer */
310 DueTime.QuadPart = Timer->Period * -SYSTEM_TIME_UNITS_PER_MSEC;
311 if (!KiInsertTimer(Timer, DueTime)) {
312
313 /* FIXME: I will think about how to handle this and fix it ASAP -- Alex */
314 DPRINT("CRITICAL UNHANDLED CASE: TIMER ALREADY EXPIRED!!!\n");
315 };
316 }
317
318 /* Check if the Timer has a DPC */
319 if (Timer->Dpc) {
320
321 DPRINT("Timer->Dpc %x Timer->Dpc->DeferredRoutine %x\n", Timer->Dpc, Timer->Dpc->DeferredRoutine);
322
323 /* Insert the DPC */
324 KeInsertQueueDpc(Timer->Dpc,
325 NULL,
326 NULL);
327
328 DPRINT("Finished dpc routine\n");
329 }
330 }
331
332 /*
333 * Note: This function is called with the Dispatcher Lock held.
334 */
335 BOOLEAN
336 STDCALL
337 KiInsertTimer(PKTIMER Timer,
338 LARGE_INTEGER DueTime)
339 {
340 LARGE_INTEGER SystemTime;
341 LARGE_INTEGER DifferenceTime;
342 ULONGLONG InterruptTime;
343
344 DPRINT("KiInsertTimer(Timer %x DueTime %I64d)\n", Timer, DueTime.QuadPart);
345
346 /* Set default data */
347 Timer->Header.Inserted = TRUE;
348 Timer->Header.Absolute = FALSE;
349 if (!Timer->Period) Timer->Header.SignalState = FALSE;
350
351 /* Convert to relative time if needed */
352 if (DueTime.u.HighPart >= 0) {
353
354 /* Get System Time */
355 KeQuerySystemTime(&SystemTime);
356
357 /* Do the conversion */
358 DifferenceTime.QuadPart = SystemTime.QuadPart - DueTime.QuadPart;
359 DPRINT("Time Difference is: %I64d\n", DifferenceTime.QuadPart);
360
361 /* Make sure it hasn't already expired */
362 if (DifferenceTime.u.HighPart >= 0) {
363
364 /* Cancel everything */
365 DPRINT("Timer already expired: %d\n", DifferenceTime.u.HighPart);
366 Timer->Header.SignalState = TRUE;
367 Timer->Header.Inserted = FALSE;
368 return FALSE;
369 }
370
371 /* Set the time as Absolute */
372 Timer->Header.Absolute = TRUE;
373 DueTime = DifferenceTime;
374 }
375
376 /* Get the Interrupt Time */
377 InterruptTime = KeQueryInterruptTime();
378
379 /* Set the Final Due Time */
380 Timer->DueTime.QuadPart = InterruptTime - DueTime.QuadPart;
381 DPRINT("Final Due Time is: %I64d\n", Timer->DueTime.QuadPart);
382
383 /* Now insert it into the Timer List */
384 DPRINT("Inserting Timer into list\n");
385 InsertAscendingList(&KiTimerListHead,
386 Timer,
387 KTIMER,
388 TimerListEntry,
389 DueTime.QuadPart);
390
391 return TRUE;
392 }
393 /* EOF */