KD System Rewrite:
[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 BOOLEAN
39 STDCALL
40 KeCancelTimer(PKTIMER Timer)
41 {
42 KIRQL OldIrql;
43 BOOLEAN Inserted = FALSE;
44
45 DPRINT("KeCancelTimer(Timer %x)\n",Timer);
46
47 /* Lock the Database and Raise IRQL */
48 OldIrql = KeAcquireDispatcherDatabaseLock();
49
50 /* Check if it's inserted, and remove it if it is */
51 if ((Inserted = Timer->Header.Inserted)) {
52
53 /* Remove from list */
54 DPRINT("Timer was inserted, removing\n");
55 RemoveEntryList(&Timer->TimerListEntry);
56 Timer->Header.Inserted = FALSE;
57
58 } else {
59
60 DPRINT("Timer was not inserted\n");
61 }
62
63 /* Release Dispatcher Lock */
64 KeReleaseDispatcherDatabaseLock(OldIrql);
65
66 /* Return the old state */
67 DPRINT("Old State: %d\n", Inserted);
68 return(Inserted);
69 }
70
71 /*
72 * @implemented
73 *
74 * FUNCTION: Initalizes a kernel timer object
75 * ARGUMENTS:
76 * Timer = caller supplied storage for the timer
77 * NOTE: This function initializes a notification timer
78 */
79 VOID
80 STDCALL
81 KeInitializeTimer (PKTIMER Timer)
82
83 {
84 /* Call the New Function */
85 KeInitializeTimerEx(Timer, NotificationTimer);
86 }
87
88 /*
89 * @implemented
90 *
91 * FUNCTION: Initializes a kernel timer object
92 * ARGUMENTS:
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.
99 */
100 VOID
101 STDCALL
102 KeInitializeTimerEx (PKTIMER Timer,
103 TIMER_TYPE Type)
104 {
105
106 DPRINT("KeInitializeTimerEx(%x, %d)\n", Timer, Type);
107
108 /* Initialize the Dispatch Header */
109 KeInitializeDispatcherHeader(&Timer->Header,
110 TimerNotificationObject + Type,
111 sizeof(KTIMER) / sizeof(ULONG),
112 FALSE);
113
114 /* Initalize the Other data */
115 Timer->DueTime.QuadPart = 0;
116 Timer->Period = 0;
117 }
118
119
120 /*
121 * @implemented
122 */
123 BOOLEAN
124 STDCALL
125 KeReadStateTimer (PKTIMER Timer)
126 {
127 /* Return the Signal State */
128 return (BOOLEAN)Timer->Header.SignalState;
129 }
130
131 /*
132 * @implemented
133 *
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.
137 * ARGUMENTS:
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
143 * False otherwise
144 */
145 BOOLEAN
146 STDCALL
147 KeSetTimer (PKTIMER Timer,
148 LARGE_INTEGER DueTime,
149 PKDPC Dpc)
150 {
151 /* Call the newer function and supply a period of 0 */
152 return KeSetTimerEx(Timer, DueTime, 0, Dpc);
153 }
154
155 /*
156 * @implemented
157 *
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.
161 * ARGUMENTS:
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
167 * False otherwise
168 */
169 BOOLEAN
170 STDCALL
171 KeSetTimerEx (PKTIMER Timer,
172 LARGE_INTEGER DueTime,
173 LONG Period,
174 PKDPC Dpc)
175 {
176 KIRQL OldIrql;
177 BOOLEAN Inserted;
178
179 DPRINT("KeSetTimerEx(Timer %x, DueTime %I64d, Period %d, Dpc %x)\n", Timer, DueTime.QuadPart, Period, Dpc);
180 ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);
181
182 /* Lock the Database and Raise IRQL */
183 OldIrql = KeAcquireDispatcherDatabaseLock();
184
185 /* Check if it's inserted, and remove it if it is */
186 if ((Inserted = Timer->Header.Inserted)) {
187
188 /* Remove from list */
189 DPRINT("Timer was already inserted\n");
190 RemoveEntryList(&Timer->TimerListEntry);
191 Timer->Header.Inserted = FALSE;
192 }
193
194 /* Set Default Timer Data */
195 Timer->Dpc = Dpc;
196 Timer->Period = Period;
197 Timer->Header.SignalState = FALSE;
198
199 /* Insert it */
200 if (!KiInsertTimer(Timer, DueTime)) {
201
202 KiHandleExpiredTimer(Timer);
203 };
204
205 /* Release Dispatcher Lock */
206 KeReleaseDispatcherDatabaseLock(OldIrql);
207
208 /* Return old state */
209 DPRINT("Old State: %d\n", Inserted);
210 return Inserted;
211 }
212
213 VOID
214 STDCALL
215 KiExpireTimers(PKDPC Dpc,
216 PVOID DeferredContext,
217 PVOID SystemArgument1,
218 PVOID SystemArgument2)
219 {
220 PKTIMER Timer;
221 ULONGLONG InterruptTime;
222 LIST_ENTRY ExpiredTimerList;
223 PLIST_ENTRY CurrentEntry = NULL;
224 KIRQL OldIrql;
225
226 DPRINT("KiExpireTimers(Dpc: %x)\n", Dpc);
227
228 /* Initialize the Expired Timer List */
229 InitializeListHead(&ExpiredTimerList);
230
231 /* Lock the Database and Raise IRQL */
232 OldIrql = KeAcquireDispatcherDatabaseLock();
233
234 /* Query Interrupt Times */
235 InterruptTime = KeQueryInterruptTime();
236
237 /* Loop through the Timer List and remove Expired Timers. Insert them into the Expired Listhead */
238 CurrentEntry = KiTimerListHead.Flink;
239 while (CurrentEntry != &KiTimerListHead) {
240
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);
244
245 /* Check if we have to Expire it */
246 if (InterruptTime < Timer->DueTime.QuadPart) break;
247
248 CurrentEntry = CurrentEntry->Flink;
249
250 /* Remove it from the Timer List, add it to the Expired List */
251 RemoveEntryList(&Timer->TimerListEntry);
252 InsertTailList(&ExpiredTimerList, &Timer->TimerListEntry);
253 }
254
255 /* Expire the Timers */
256 while ((CurrentEntry = RemoveHeadList(&ExpiredTimerList)) != &ExpiredTimerList) {
257
258 /* Get the Timer */
259 Timer = CONTAINING_RECORD(CurrentEntry, KTIMER, TimerListEntry);
260 DPRINT("Expiring Timer: %x\n", Timer);
261
262 /* Expire it */
263 KiHandleExpiredTimer(Timer);
264 }
265
266 DPRINT("Timers expired\n");
267
268 /* Release Dispatcher Lock */
269 KeReleaseDispatcherDatabaseLock(OldIrql);
270 }
271
272 /*
273 * We enter this function at IRQL DISPATCH_LEVEL, and with the
274 * Dispatcher Lock held!
275 */
276 VOID
277 STDCALL
278 KiHandleExpiredTimer(PKTIMER Timer)
279 {
280
281 LARGE_INTEGER DueTime;
282 DPRINT("HandleExpiredTime(Timer %x)\n", Timer);
283
284 if(Timer->Header.Inserted) {
285
286 /* First of all, remove the Timer */
287 Timer->Header.Inserted = FALSE;
288 RemoveEntryList(&Timer->TimerListEntry);
289 }
290
291 /* Set it as Signaled */
292 DPRINT("Setting Timer as Signaled\n");
293 Timer->Header.SignalState = TRUE;
294 KiWaitTest(&Timer->Header, IO_NO_INCREMENT);
295
296 /* If the Timer is periodic, reinsert the timer with the new due time */
297 if (Timer->Period) {
298
299 /* Reinsert the Timer */
300 DueTime.QuadPart = Timer->Period * -SYSTEM_TIME_UNITS_PER_MSEC;
301 if (!KiInsertTimer(Timer, DueTime)) {
302
303 /* FIXME: I will think about how to handle this and fix it ASAP -- Alex */
304 DPRINT("CRITICAL UNHANDLED CASE: TIMER ALREADY EXPIRED!!!\n");
305 };
306 }
307
308 /* Check if the Timer has a DPC */
309 if (Timer->Dpc) {
310
311 DPRINT("Timer->Dpc %x Timer->Dpc->DeferredRoutine %x\n", Timer->Dpc, Timer->Dpc->DeferredRoutine);
312
313 /* Insert the DPC */
314 KeInsertQueueDpc(Timer->Dpc,
315 NULL,
316 NULL);
317
318 DPRINT("Finished dpc routine\n");
319 }
320 }
321
322 /*
323 * Note: This function is called with the Dispatcher Lock held.
324 */
325 BOOLEAN
326 STDCALL
327 KiInsertTimer(PKTIMER Timer,
328 LARGE_INTEGER DueTime)
329 {
330 LARGE_INTEGER SystemTime;
331 LARGE_INTEGER DifferenceTime;
332 ULONGLONG InterruptTime;
333
334 DPRINT("KiInsertTimer(Timer %x DueTime %I64d)\n", Timer, DueTime.QuadPart);
335
336 /* Set default data */
337 Timer->Header.Inserted = TRUE;
338 Timer->Header.Absolute = FALSE;
339 if (!Timer->Period) Timer->Header.SignalState = FALSE;
340
341 /* Convert to relative time if needed */
342 if (DueTime.u.HighPart >= 0) {
343
344 /* Get System Time */
345 KeQuerySystemTime(&SystemTime);
346
347 /* Do the conversion */
348 DifferenceTime.QuadPart = SystemTime.QuadPart - DueTime.QuadPart;
349 DPRINT("Time Difference is: %I64d\n", DifferenceTime.QuadPart);
350
351 /* Make sure it hasn't already expired */
352 if (DifferenceTime.u.HighPart >= 0) {
353
354 /* Cancel everything */
355 DPRINT("Timer already expired: %d\n", DifferenceTime.u.HighPart);
356 Timer->Header.SignalState = TRUE;
357 Timer->Header.Inserted = FALSE;
358 return FALSE;
359 }
360
361 /* Set the time as Absolute */
362 Timer->Header.Absolute = TRUE;
363 DueTime = DifferenceTime;
364 }
365
366 /* Get the Interrupt Time */
367 InterruptTime = KeQueryInterruptTime();
368
369 /* Set the Final Due Time */
370 Timer->DueTime.QuadPart = InterruptTime - DueTime.QuadPart;
371 DPRINT("Final Due Time is: %I64d\n", Timer->DueTime.QuadPart);
372
373 /* Now insert it into the Timer List */
374 DPRINT("Inserting Timer into list\n");
375 InsertAscendingList(&KiTimerListHead,
376 KTIMER,
377 TimerListEntry,
378 Timer,
379 DueTime.QuadPart);
380
381 return TRUE;
382 }
383 /* EOF */