xref: /reactos/ntoskrnl/ke/timerobj.c (revision c2c66aff)
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
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
63 KiInsertTimerTable(IN PKTIMER Timer,
64                    IN ULONG Hand)
65 {
66     LARGE_INTEGER InterruptTime;
67     LONGLONG 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.QuadPart = KeQueryInterruptTime();
105         if (DueTime <= InterruptTime.QuadPart) Expired = TRUE;
106     }
107 
108     /* Return expired state */
109     return Expired;
110 }
111 
112 BOOLEAN
113 FASTCALL
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
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     KiAcquireDispatcherLockAtDpcLevel();
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     KiReleaseDispatcherLockFromDpcLevel();
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
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
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
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
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
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
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         KiReleaseDispatcherLockFromDpcLevel();
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