1 /*
2 * PROJECT: ReactOS Kernel
3 * LICENSE: GPL - See COPYING in the top level directory
4 * FILE: ntoskrnl/ke/timerobj.c
5 * PURPOSE: Handle Kernel Timers (Kernel-part of Executive Timers)
6 * PROGRAMMERS: Alex Ionescu (alex.ionescu@reactos.org)
7 */
8
9 /* INCLUDES ******************************************************************/
10
11 #include <ntoskrnl.h>
12 #define NDEBUG
13 #include <debug.h>
14
15 /* GLOBALS *******************************************************************/
16
17 KTIMER_TABLE_ENTRY KiTimerTableListHead[TIMER_TABLE_SIZE];
18 LARGE_INTEGER KiTimeIncrementReciprocal;
19 UCHAR KiTimeIncrementShiftCount;
20 BOOLEAN KiEnableTimerWatchdog = FALSE;
21
22 /* PRIVATE FUNCTIONS *********************************************************/
23
24 BOOLEAN
25 FASTCALL
KiInsertTreeTimer(IN PKTIMER Timer,IN LARGE_INTEGER Interval)26 KiInsertTreeTimer(IN PKTIMER Timer,
27 IN LARGE_INTEGER Interval)
28 {
29 BOOLEAN Inserted = FALSE;
30 ULONG Hand = 0;
31 PKSPIN_LOCK_QUEUE LockQueue;
32 DPRINT("KiInsertTreeTimer(): Timer %p, Interval: %I64d\n", Timer, Interval.QuadPart);
33
34 /* Setup the timer's due time */
35 if (KiComputeDueTime(Timer, Interval, &Hand))
36 {
37 /* Acquire the lock */
38 LockQueue = KiAcquireTimerLock(Hand);
39
40 /* Insert the timer */
41 if (KiInsertTimerTable(Timer, Hand))
42 {
43 /* It was already there, remove it */
44 KiRemoveEntryTimer(Timer);
45 Timer->Header.Inserted = FALSE;
46 }
47 else
48 {
49 /* Otherwise, we're now inserted */
50 Inserted = TRUE;
51 }
52
53 /* Release the lock */
54 KiReleaseTimerLock(LockQueue);
55 }
56
57 /* Release the lock and return insert status */
58 return Inserted;
59 }
60
61 BOOLEAN
62 FASTCALL
KiInsertTimerTable(IN PKTIMER Timer,IN ULONG Hand)63 KiInsertTimerTable(IN PKTIMER Timer,
64 IN ULONG Hand)
65 {
66 ULONGLONG InterruptTime;
67 ULONGLONG DueTime = Timer->DueTime.QuadPart;
68 BOOLEAN Expired = FALSE;
69 PLIST_ENTRY ListHead, NextEntry;
70 PKTIMER CurrentTimer;
71 DPRINT("KiInsertTimerTable(): Timer %p, Hand: %lu\n", Timer, Hand);
72
73 /* Check if the period is zero */
74 if (!Timer->Period) Timer->Header.SignalState = FALSE;
75
76 /* Sanity check */
77 ASSERT(Hand == KiComputeTimerTableIndex(DueTime));
78
79 /* Loop the timer list backwards */
80 ListHead = &KiTimerTableListHead[Hand].Entry;
81 NextEntry = ListHead->Blink;
82 while (NextEntry != ListHead)
83 {
84 /* Get the timer */
85 CurrentTimer = CONTAINING_RECORD(NextEntry, KTIMER, TimerListEntry);
86
87 /* Now check if we can fit it before */
88 if ((ULONGLONG)DueTime >= CurrentTimer->DueTime.QuadPart) break;
89
90 /* Keep looping */
91 NextEntry = NextEntry->Blink;
92 }
93
94 /* Looped all the list, insert it here and get the interrupt time again */
95 InsertHeadList(NextEntry, &Timer->TimerListEntry);
96
97 /* Check if we didn't find it in the list */
98 if (NextEntry == ListHead)
99 {
100 /* Set the time */
101 KiTimerTableListHead[Hand].Time.QuadPart = DueTime;
102
103 /* Make sure it hasn't expired already */
104 InterruptTime = KeQueryInterruptTime();
105 if (DueTime <= InterruptTime) Expired = TRUE;
106 }
107
108 /* Return expired state */
109 return Expired;
110 }
111
112 BOOLEAN
113 FASTCALL
KiSignalTimer(IN PKTIMER Timer)114 KiSignalTimer(IN PKTIMER Timer)
115 {
116 BOOLEAN RequestInterrupt = FALSE;
117 PKDPC Dpc = Timer->Dpc;
118 ULONG Period = Timer->Period;
119 LARGE_INTEGER Interval, SystemTime;
120 DPRINT("KiSignalTimer(): Timer %p\n", Timer);
121
122 /* Set default values */
123 Timer->Header.Inserted = FALSE;
124 Timer->Header.SignalState = TRUE;
125
126 /* Check if the timer has waiters */
127 if (!IsListEmpty(&Timer->Header.WaitListHead))
128 {
129 /* Check the type of event */
130 if (Timer->Header.Type == TimerNotificationObject)
131 {
132 /* Unwait the thread */
133 KxUnwaitThread(&Timer->Header, IO_NO_INCREMENT);
134 }
135 else
136 {
137 /* Otherwise unwait the thread and signal the timer */
138 KxUnwaitThreadForEvent((PKEVENT)Timer, IO_NO_INCREMENT);
139 }
140 }
141
142 /* Check if we have a period */
143 if (Period)
144 {
145 /* Calculate the interval and insert the timer */
146 Interval.QuadPart = Int32x32To64(Period, -10000);
147 while (!KiInsertTreeTimer(Timer, Interval));
148 }
149
150 /* Check if we have a DPC */
151 if (Dpc)
152 {
153 /* Insert it in the queue */
154 KeQuerySystemTime(&SystemTime);
155 KeInsertQueueDpc(Dpc,
156 ULongToPtr(SystemTime.LowPart),
157 ULongToPtr(SystemTime.HighPart));
158 RequestInterrupt = TRUE;
159 }
160
161 /* Return whether we need to request a DPC interrupt or not */
162 return RequestInterrupt;
163 }
164
165 VOID
166 FASTCALL
KiCompleteTimer(IN PKTIMER Timer,IN PKSPIN_LOCK_QUEUE LockQueue)167 KiCompleteTimer(IN PKTIMER Timer,
168 IN PKSPIN_LOCK_QUEUE LockQueue)
169 {
170 LIST_ENTRY ListHead;
171 BOOLEAN RequestInterrupt = FALSE;
172 DPRINT("KiCompleteTimer(): Timer %p, LockQueue: %p\n", Timer, LockQueue);
173
174 /* Remove it from the timer list */
175 KiRemoveEntryTimer(Timer);
176
177 /* Link the timer list to our stack */
178 ListHead.Flink = &Timer->TimerListEntry;
179 ListHead.Blink = &Timer->TimerListEntry;
180 Timer->TimerListEntry.Flink = &ListHead;
181 Timer->TimerListEntry.Blink = &ListHead;
182
183 /* Release the timer lock */
184 KiReleaseTimerLock(LockQueue);
185
186 /* Acquire dispatcher lock */
187 KiAcquireDispatcherLockAtSynchLevel();
188
189 /* Signal the timer if it's still on our list */
190 if (!IsListEmpty(&ListHead)) RequestInterrupt = KiSignalTimer(Timer);
191
192 /* Release the dispatcher lock */
193 KiReleaseDispatcherLockFromSynchLevel();
194
195 /* Request a DPC if needed */
196 if (RequestInterrupt) HalRequestSoftwareInterrupt(DISPATCH_LEVEL);
197 }
198
199 /* PUBLIC FUNCTIONS **********************************************************/
200
201 /*
202 * @implemented
203 */
204 BOOLEAN
205 NTAPI
KeCancelTimer(IN OUT PKTIMER Timer)206 KeCancelTimer(IN OUT PKTIMER Timer)
207 {
208 KIRQL OldIrql;
209 BOOLEAN Inserted;
210 ASSERT_TIMER(Timer);
211 ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);
212 DPRINT("KeCancelTimer(): Timer %p\n", Timer);
213
214 /* Lock the Database and Raise IRQL */
215 OldIrql = KiAcquireDispatcherLock();
216
217 /* Check if it's inserted, and remove it if it is */
218 Inserted = Timer->Header.Inserted;
219 if (Inserted) KxRemoveTreeTimer(Timer);
220
221 /* Release Dispatcher Lock */
222 KiReleaseDispatcherLock(OldIrql);
223
224 /* Return the old state */
225 return Inserted;
226 }
227
228 /*
229 * @implemented
230 */
231 VOID
232 NTAPI
KeInitializeTimer(OUT PKTIMER Timer)233 KeInitializeTimer(OUT PKTIMER Timer)
234 {
235 /* Call the New Function */
236 KeInitializeTimerEx(Timer, NotificationTimer);
237 }
238
239 /*
240 * @implemented
241 */
242 VOID
243 NTAPI
KeInitializeTimerEx(OUT PKTIMER Timer,IN TIMER_TYPE Type)244 KeInitializeTimerEx(OUT PKTIMER Timer,
245 IN TIMER_TYPE Type)
246 {
247 DPRINT("KeInitializeTimerEx(): Timer %p, Type %s\n",
248 Timer, (Type == NotificationTimer) ?
249 "NotificationTimer" : "SynchronizationTimer");
250
251 /* Initialize the Dispatch Header */
252 Timer->Header.Type = TimerNotificationObject + Type;
253 //Timer->Header.TimerControlFlags = 0; // win does not init this field
254 Timer->Header.Hand = sizeof(KTIMER) / sizeof(ULONG);
255 Timer->Header.Inserted = 0; // win7: Timer->Header.TimerMiscFlags = 0;
256 Timer->Header.SignalState = 0;
257 InitializeListHead(&(Timer->Header.WaitListHead));
258
259 /* Initialize the Other data */
260 Timer->DueTime.QuadPart = 0;
261 Timer->Period = 0;
262 }
263
264 /*
265 * @implemented
266 */
267 BOOLEAN
268 NTAPI
KeReadStateTimer(IN PKTIMER Timer)269 KeReadStateTimer(IN PKTIMER Timer)
270 {
271 /* Return the Signal State */
272 ASSERT_TIMER(Timer);
273 return (BOOLEAN)Timer->Header.SignalState;
274 }
275
276 /*
277 * @implemented
278 */
279 BOOLEAN
280 NTAPI
KeSetTimer(IN OUT PKTIMER Timer,IN LARGE_INTEGER DueTime,IN PKDPC Dpc OPTIONAL)281 KeSetTimer(IN OUT PKTIMER Timer,
282 IN LARGE_INTEGER DueTime,
283 IN PKDPC Dpc OPTIONAL)
284 {
285 /* Call the newer function and supply a period of 0 */
286 return KeSetTimerEx(Timer, DueTime, 0, Dpc);
287 }
288
289 /*
290 * @implemented
291 */
292 BOOLEAN
293 NTAPI
KeSetTimerEx(IN OUT PKTIMER Timer,IN LARGE_INTEGER DueTime,IN LONG Period,IN PKDPC Dpc OPTIONAL)294 KeSetTimerEx(IN OUT PKTIMER Timer,
295 IN LARGE_INTEGER DueTime,
296 IN LONG Period,
297 IN PKDPC Dpc OPTIONAL)
298 {
299 KIRQL OldIrql;
300 BOOLEAN Inserted;
301 ULONG Hand = 0;
302 BOOLEAN RequestInterrupt = FALSE;
303 ASSERT_TIMER(Timer);
304 ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);
305 DPRINT("KeSetTimerEx(): Timer %p, DueTime %I64d, Period %d, Dpc %p\n",
306 Timer, DueTime.QuadPart, Period, Dpc);
307
308 /* Lock the Database and Raise IRQL */
309 OldIrql = KiAcquireDispatcherLock();
310
311 /* Check if it's inserted, and remove it if it is */
312 Inserted = Timer->Header.Inserted;
313 if (Inserted) KxRemoveTreeTimer(Timer);
314
315 /* Set Default Timer Data */
316 Timer->Dpc = Dpc;
317 Timer->Period = Period;
318 if (!KiComputeDueTime(Timer, DueTime, &Hand))
319 {
320 /* Signal the timer */
321 RequestInterrupt = KiSignalTimer(Timer);
322
323 /* Release the dispatcher lock */
324 KiReleaseDispatcherLockFromSynchLevel();
325
326 /* Check if we need to do an interrupt */
327 if (RequestInterrupt) HalRequestSoftwareInterrupt(DISPATCH_LEVEL);
328 }
329 else
330 {
331 /* Insert the timer */
332 Timer->Header.SignalState = FALSE;
333 KxInsertTimer(Timer, Hand);
334 }
335
336 /* Exit the dispatcher */
337 KiExitDispatcher(OldIrql);
338
339 /* Return old state */
340 return Inserted;
341 }
342
343