Reactos
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
17KTIMER_TABLE_ENTRY KiTimerTableListHead[TIMER_TABLE_SIZE];
18LARGE_INTEGER KiTimeIncrementReciprocal;
19UCHAR KiTimeIncrementShiftCount;
20BOOLEAN KiEnableTimerWatchdog = FALSE;
21
22/* PRIVATE FUNCTIONS *********************************************************/
23
24BOOLEAN
25FASTCALL
26KiInsertTreeTimer(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
61BOOLEAN
62FASTCALL
63KiInsertTimerTable(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
112BOOLEAN
113FASTCALL
114KiSignalTimer(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
165VOID
166FASTCALL
167KiCompleteTimer(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 */
204BOOLEAN
205NTAPI
206KeCancelTimer(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 */
231VOID
232NTAPI
233KeInitializeTimer(OUT PKTIMER Timer)
234{
235 /* Call the New Function */
236 KeInitializeTimerEx(Timer, NotificationTimer);
237}
238
239/*
240 * @implemented
241 */
242VOID
243NTAPI
244KeInitializeTimerEx(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 ASSERT((Type == NotificationTimer) || (Type == SynchronizationTimer));
253 Timer->Header.Type = TimerNotificationObject + Type;
254 //Timer->Header.TimerControlFlags = 0; // win does not init this field
255 Timer->Header.Hand = sizeof(KTIMER) / sizeof(ULONG);
256 Timer->Header.Inserted = 0; // win7: Timer->Header.TimerMiscFlags = 0;
257 Timer->Header.SignalState = 0;
258 InitializeListHead(&(Timer->Header.WaitListHead));
259
260 /* Initialize the Other data */
261 Timer->DueTime.QuadPart = 0;
262 Timer->Period = 0;
263}
264
265/*
266 * @implemented
267 */
268BOOLEAN
269NTAPI
270KeReadStateTimer(IN PKTIMER Timer)
271{
272 /* Return the Signal State */
273 ASSERT_TIMER(Timer);
274 return (BOOLEAN)Timer->Header.SignalState;
275}
276
277/*
278 * @implemented
279 */
280BOOLEAN
281NTAPI
282KeSetTimer(IN OUT PKTIMER Timer,
283 IN LARGE_INTEGER DueTime,
284 IN PKDPC Dpc OPTIONAL)
285{
286 /* Call the newer function and supply a period of 0 */
287 return KeSetTimerEx(Timer, DueTime, 0, Dpc);
288}
289
290/*
291 * @implemented
292 */
293BOOLEAN
294NTAPI
295KeSetTimerEx(IN OUT PKTIMER Timer,
296 IN LARGE_INTEGER DueTime,
297 IN LONG Period,
298 IN PKDPC Dpc OPTIONAL)
299{
300 KIRQL OldIrql;
301 BOOLEAN Inserted;
302 ULONG Hand = 0;
303 BOOLEAN RequestInterrupt = FALSE;
304 ASSERT_TIMER(Timer);
305 ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);
306 DPRINT("KeSetTimerEx(): Timer %p, DueTime %I64d, Period %d, Dpc %p\n",
307 Timer, DueTime.QuadPart, Period, Dpc);
308
309 /* Lock the Database and Raise IRQL */
310 OldIrql = KiAcquireDispatcherLock();
311
312 /* Check if it's inserted, and remove it if it is */
313 Inserted = Timer->Header.Inserted;
314 if (Inserted) KxRemoveTreeTimer(Timer);
315
316 /* Set Default Timer Data */
317 Timer->Dpc = Dpc;
318 Timer->Period = Period;
319 if (!KiComputeDueTime(Timer, DueTime, &Hand))
320 {
321 /* Signal the timer */
322 RequestInterrupt = KiSignalTimer(Timer);
323
324 /* Release the dispatcher lock */
325 KiReleaseDispatcherLockFromSynchLevel();
326
327 /* Check if we need to do an interrupt */
328 if (RequestInterrupt) HalRequestSoftwareInterrupt(DISPATCH_LEVEL);
329 }
330 else
331 {
332 /* Insert the timer */
333 Timer->Header.SignalState = FALSE;
334 KxInsertTimer(Timer, Hand);
335 }
336
337 /* Exit the dispatcher */
338 KiExitDispatcher(OldIrql);
339
340 /* Return old state */
341 return Inserted;
342}
343