xref: /reactos/ntoskrnl/ex/timer.c (revision 2196a06f)
1 /*
2  * PROJECT:         ReactOS Kernel
3  * LICENSE:         GPL - See COPYING in the top level directory
4  * FILE:            ntoskrnl/ex/timer.c
5  * PURPOSE:         Executive Timer Implementation
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 /* Timer Object Type */
18 POBJECT_TYPE ExTimerType = NULL;
19 
20 KSPIN_LOCK ExpWakeListLock;
21 LIST_ENTRY ExpWakeList;
22 
23 /* Timer Mapping */
24 static GENERIC_MAPPING ExpTimerMapping =
25 {
26     STANDARD_RIGHTS_READ    | TIMER_QUERY_STATE,
27     STANDARD_RIGHTS_WRITE   | TIMER_MODIFY_STATE,
28     STANDARD_RIGHTS_EXECUTE | SYNCHRONIZE,
29     TIMER_ALL_ACCESS
30 };
31 
32 /* Timer Information Classes */
33 static const INFORMATION_CLASS_INFO ExTimerInfoClass[] =
34 {
35     /* TimerBasicInformation */
36     IQS_SAME(TIMER_BASIC_INFORMATION, ULONG, ICIF_QUERY),
37 };
38 
39 /* PRIVATE FUNCTIONS *********************************************************/
40 
41 VOID
42 NTAPI
43 ExTimerRundown(VOID)
44 {
45     PETHREAD Thread = PsGetCurrentThread();
46     KIRQL OldIrql;
47     PLIST_ENTRY CurrentEntry;
48     PETIMER Timer;
49     ULONG DerefsToDo;
50 
51     /* Lock the Thread's Active Timer List and loop it */
52     KeAcquireSpinLock(&Thread->ActiveTimerListLock, &OldIrql);
53     CurrentEntry = Thread->ActiveTimerListHead.Flink;
54     while (CurrentEntry != &Thread->ActiveTimerListHead)
55     {
56         /* Get the timer */
57         Timer = CONTAINING_RECORD(CurrentEntry, ETIMER, ActiveTimerListEntry);
58 
59         /* Reference it */
60         ObReferenceObject(Timer);
61         DerefsToDo = 1;
62 
63         /* Unlock the list */
64         KeReleaseSpinLock(&Thread->ActiveTimerListLock, OldIrql);
65 
66         /* Lock the Timer */
67         KeAcquireSpinLock(&Timer->Lock, &OldIrql);
68 
69         /* Lock the list again */
70         KeAcquireSpinLockAtDpcLevel(&Thread->ActiveTimerListLock);
71 
72         /* Make sure that the timer is valid */
73         if ((Timer->ApcAssociated) && (&Thread->Tcb == Timer->TimerApc.Thread))
74         {
75             /* Remove it from the list */
76             RemoveEntryList(&Timer->ActiveTimerListEntry);
77             Timer->ApcAssociated = FALSE;
78 
79             /* Cancel the timer and remove its DPC and APC */
80             KeCancelTimer(&Timer->KeTimer);
81             KeRemoveQueueDpc(&Timer->TimerDpc);
82             if (KeRemoveQueueApc(&Timer->TimerApc)) DerefsToDo++;
83 
84             /* Add another dereference to do */
85             DerefsToDo++;
86         }
87 
88         /* Unlock the list */
89         KeReleaseSpinLockFromDpcLevel(&Thread->ActiveTimerListLock);
90 
91         /* Unlock the Timer */
92         KeReleaseSpinLock(&Timer->Lock, OldIrql);
93 
94         /* Dereference it */
95         ObDereferenceObjectEx(Timer, DerefsToDo);
96 
97         /* Loop again */
98         KeAcquireSpinLock(&Thread->ActiveTimerListLock, &OldIrql);
99         CurrentEntry = Thread->ActiveTimerListHead.Flink;
100     }
101 
102     /* Release lock and return */
103     KeReleaseSpinLock(&Thread->ActiveTimerListLock, OldIrql);
104 }
105 
106 VOID
107 NTAPI
108 ExpDeleteTimer(IN PVOID ObjectBody)
109 {
110     KIRQL OldIrql;
111     PETIMER Timer = ObjectBody;
112 
113     /* Check if it has a Wait List */
114     if (Timer->WakeTimerListEntry.Flink)
115     {
116         /* Lock the Wake List */
117         KeAcquireSpinLock(&ExpWakeListLock, &OldIrql);
118 
119         /* Check again, since it might've changed before we locked */
120         if (Timer->WakeTimerListEntry.Flink)
121         {
122             /* Remove it from the Wait List */
123             RemoveEntryList(&Timer->WakeTimerListEntry);
124             Timer->WakeTimerListEntry.Flink = NULL;
125         }
126 
127         /* Release the Wake List */
128         KeReleaseSpinLock(&ExpWakeListLock, OldIrql);
129     }
130 
131     /* Tell the Kernel to cancel the Timer and flush all queued DPCs */
132     KeCancelTimer(&Timer->KeTimer);
133     KeFlushQueuedDpcs();
134 }
135 
136 _Function_class_(KDEFERRED_ROUTINE)
137 VOID
138 NTAPI
139 ExpTimerDpcRoutine(IN PKDPC Dpc,
140                    IN PVOID DeferredContext,
141                    IN PVOID SystemArgument1,
142                    IN PVOID SystemArgument2)
143 {
144     PETIMER Timer = DeferredContext;
145     BOOLEAN Inserted = FALSE;
146 
147     /* Reference the timer */
148     if (!ObReferenceObjectSafe(Timer)) return;
149 
150     /* Lock the Timer */
151     KeAcquireSpinLockAtDpcLevel(&Timer->Lock);
152 
153     /* Check if the timer is associated */
154     if (Timer->ApcAssociated)
155     {
156         /* Queue the APC */
157         Inserted = KeInsertQueueApc(&Timer->TimerApc,
158                                     SystemArgument1,
159                                     SystemArgument2,
160                                     IO_NO_INCREMENT);
161     }
162 
163     /* Release the Timer */
164     KeReleaseSpinLockFromDpcLevel(&Timer->Lock);
165 
166     /* Dereference it if we couldn't queue the APC */
167     if (!Inserted) ObDereferenceObject(Timer);
168 }
169 
170 VOID
171 NTAPI
172 ExpTimerApcKernelRoutine(IN PKAPC Apc,
173                          IN OUT PKNORMAL_ROUTINE* NormalRoutine,
174                          IN OUT PVOID* NormalContext,
175                          IN OUT PVOID* SystemArgument1,
176                          IN OUT PVOID* SystemArguemnt2)
177 {
178     PETIMER Timer;
179     KIRQL OldIrql;
180     ULONG DerefsToDo = 1;
181     PETHREAD Thread = PsGetCurrentThread();
182 
183     /* We need to find out which Timer we are */
184     Timer = CONTAINING_RECORD(Apc, ETIMER, TimerApc);
185 
186     /* Lock the Timer */
187     KeAcquireSpinLock(&Timer->Lock, &OldIrql);
188 
189     /* Lock the Thread's Active Timer List*/
190     KeAcquireSpinLockAtDpcLevel(&Thread->ActiveTimerListLock);
191 
192     /* Make sure that the Timer is valid, and that it belongs to this thread */
193     if ((Timer->ApcAssociated) && (&Thread->Tcb == Timer->TimerApc.Thread))
194     {
195         /* Check if it's not periodic */
196         if (!Timer->Period)
197         {
198             /* Remove it from the Active Timers List */
199             RemoveEntryList(&Timer->ActiveTimerListEntry);
200 
201             /* Disable it */
202             Timer->ApcAssociated = FALSE;
203             DerefsToDo++;
204         }
205     }
206     else
207     {
208         /* Clear the normal routine */
209         *NormalRoutine = NULL;
210     }
211 
212     /* Release locks */
213     KeReleaseSpinLockFromDpcLevel(&Thread->ActiveTimerListLock);
214     KeReleaseSpinLock(&Timer->Lock, OldIrql);
215 
216     /* Dereference as needed */
217     ObDereferenceObjectEx(Timer, DerefsToDo);
218 }
219 
220 CODE_SEG("INIT")
221 BOOLEAN
222 NTAPI
223 ExpInitializeTimerImplementation(VOID)
224 {
225     OBJECT_TYPE_INITIALIZER ObjectTypeInitializer;
226     UNICODE_STRING Name;
227     NTSTATUS Status;
228 
229     /* Create the Timer Object Type */
230     RtlZeroMemory(&ObjectTypeInitializer, sizeof(ObjectTypeInitializer));
231     RtlInitUnicodeString(&Name, L"Timer");
232     ObjectTypeInitializer.Length = sizeof(ObjectTypeInitializer);
233     ObjectTypeInitializer.InvalidAttributes = OBJ_OPENLINK;
234     ObjectTypeInitializer.DefaultNonPagedPoolCharge = sizeof(ETIMER);
235     ObjectTypeInitializer.GenericMapping = ExpTimerMapping;
236     ObjectTypeInitializer.PoolType = NonPagedPool;
237     ObjectTypeInitializer.ValidAccessMask = TIMER_ALL_ACCESS;
238     ObjectTypeInitializer.DeleteProcedure = ExpDeleteTimer;
239     Status = ObCreateObjectType(&Name, &ObjectTypeInitializer, NULL, &ExTimerType);
240     if (!NT_SUCCESS(Status)) return FALSE;
241 
242     /* Initialize the Wait List and Lock */
243     KeInitializeSpinLock(&ExpWakeListLock);
244     InitializeListHead(&ExpWakeList);
245     return TRUE;
246 }
247 
248 /* PUBLIC FUNCTIONS **********************************************************/
249 
250 NTSTATUS
251 NTAPI
252 NtCancelTimer(IN HANDLE TimerHandle,
253               OUT PBOOLEAN CurrentState OPTIONAL)
254 {
255     PETIMER Timer;
256     KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
257     BOOLEAN State;
258     KIRQL OldIrql;
259     PETHREAD TimerThread;
260     ULONG DerefsToDo = 1;
261     NTSTATUS Status;
262     PAGED_CODE();
263 
264     /* Check if we need to probe */
265     if ((CurrentState) && (PreviousMode != KernelMode))
266     {
267         _SEH2_TRY
268         {
269             /* Make sure the pointer is valid */
270             ProbeForWriteBoolean(CurrentState);
271         }
272         _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
273         {
274             /* Return the exception code */
275             _SEH2_YIELD(return _SEH2_GetExceptionCode());
276         }
277         _SEH2_END;
278     }
279 
280     /* Get the Timer Object */
281     Status = ObReferenceObjectByHandle(TimerHandle,
282                                        TIMER_MODIFY_STATE,
283                                        ExTimerType,
284                                        PreviousMode,
285                                        (PVOID*)&Timer,
286                                        NULL);
287     if (NT_SUCCESS(Status))
288     {
289         /* Lock the Timer */
290         KeAcquireSpinLock(&Timer->Lock, &OldIrql);
291 
292         /* Check if it's enabled */
293         if (Timer->ApcAssociated)
294         {
295             /* Get the Thread. */
296             TimerThread = CONTAINING_RECORD(Timer->TimerApc.Thread,
297                                             ETHREAD,
298                                             Tcb);
299 
300             /* Lock its active list */
301             KeAcquireSpinLockAtDpcLevel(&TimerThread->ActiveTimerListLock);
302 
303             /* Remove it */
304             RemoveEntryList(&Timer->ActiveTimerListEntry);
305             Timer->ApcAssociated = FALSE;
306 
307             /* Unlock the list */
308             KeReleaseSpinLockFromDpcLevel(&TimerThread->ActiveTimerListLock);
309 
310             /* Cancel the Timer */
311             KeCancelTimer(&Timer->KeTimer);
312             KeRemoveQueueDpc(&Timer->TimerDpc);
313             if (KeRemoveQueueApc(&Timer->TimerApc)) DerefsToDo++;
314             DerefsToDo++;
315         }
316         else
317         {
318             /* If timer was disabled, we still need to cancel it */
319             KeCancelTimer(&Timer->KeTimer);
320         }
321 
322         /* Handle a Wake Timer */
323         if (Timer->WakeTimerListEntry.Flink)
324         {
325             /* Lock the Wake List */
326             KeAcquireSpinLockAtDpcLevel(&ExpWakeListLock);
327 
328             /* Check again, since it might've changed before we locked */
329             if (Timer->WakeTimerListEntry.Flink)
330             {
331                 /* Remove it from the Wait List */
332                 RemoveEntryList(&Timer->WakeTimerListEntry);
333                 Timer->WakeTimerListEntry.Flink = NULL;
334             }
335 
336             /* Release the Wake List */
337             KeReleaseSpinLockFromDpcLevel(&ExpWakeListLock);
338         }
339 
340         /* Unlock the Timer */
341         KeReleaseSpinLock(&Timer->Lock, OldIrql);
342 
343         /* Read the old State */
344         State = KeReadStateTimer(&Timer->KeTimer);
345 
346         /* Dereference the Object */
347         ObDereferenceObjectEx(Timer, DerefsToDo);
348 
349         /* Check if caller wants the state */
350         if (CurrentState)
351         {
352             _SEH2_TRY
353             {
354                 /* Return the Timer State */
355                 *CurrentState = State;
356             }
357             _SEH2_EXCEPT(ExSystemExceptionFilter())
358             {
359                 /* Do nothing */
360                 (void)0;
361             }
362             _SEH2_END;
363         }
364     }
365 
366     /* Return to Caller */
367     return Status;
368 }
369 
370 NTSTATUS
371 NTAPI
372 NtCreateTimer(OUT PHANDLE TimerHandle,
373               IN ACCESS_MASK DesiredAccess,
374               IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,
375               IN TIMER_TYPE TimerType)
376 {
377     PETIMER Timer;
378     HANDLE hTimer;
379     KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
380     NTSTATUS Status;
381     PAGED_CODE();
382 
383     /* Check for correct timer type */
384     if ((TimerType != NotificationTimer) &&
385         (TimerType != SynchronizationTimer))
386     {
387         /* Fail */
388         return STATUS_INVALID_PARAMETER_4;
389     }
390 
391     /* Check if we need to probe */
392     if (PreviousMode != KernelMode)
393     {
394         _SEH2_TRY
395         {
396             /* Make sure the pointer is valid */
397             ProbeForWriteHandle(TimerHandle);
398         }
399         _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
400         {
401             /* Return the exception code */
402             _SEH2_YIELD(return _SEH2_GetExceptionCode());
403         }
404         _SEH2_END;
405     }
406 
407     /* Create the Object */
408     Status = ObCreateObject(PreviousMode,
409                             ExTimerType,
410                             ObjectAttributes,
411                             PreviousMode,
412                             NULL,
413                             sizeof(ETIMER),
414                             0,
415                             0,
416                             (PVOID*)&Timer);
417     if (NT_SUCCESS(Status))
418     {
419         /* Initialize the DPC */
420         KeInitializeDpc(&Timer->TimerDpc, ExpTimerDpcRoutine, Timer);
421 
422         /* Initialize the Kernel Timer */
423         KeInitializeTimerEx(&Timer->KeTimer, TimerType);
424 
425         /* Initialize the timer fields */
426         KeInitializeSpinLock(&Timer->Lock);
427         Timer->ApcAssociated = FALSE;
428         Timer->WakeTimer = FALSE;
429         Timer->WakeTimerListEntry.Flink = NULL;
430 
431         /* Insert the Timer */
432         Status = ObInsertObject((PVOID)Timer,
433                                 NULL,
434                                 DesiredAccess,
435                                 0,
436                                 NULL,
437                                 &hTimer);
438 
439         /* Check for success */
440         if (NT_SUCCESS(Status))
441         {
442             /* Enter SEH */
443             _SEH2_TRY
444             {
445                 /* Return the Timer Handle */
446                 *TimerHandle = hTimer;
447             }
448             _SEH2_EXCEPT(ExSystemExceptionFilter())
449             {
450                 /* Do nothing */
451                 (void)0;
452             }
453             _SEH2_END;
454         }
455     }
456 
457     /* Return to Caller */
458     return Status;
459 }
460 
461 NTSTATUS
462 NTAPI
463 NtOpenTimer(OUT PHANDLE TimerHandle,
464             IN ACCESS_MASK DesiredAccess,
465             IN POBJECT_ATTRIBUTES ObjectAttributes)
466 {
467     HANDLE hTimer;
468     KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
469     NTSTATUS Status;
470     PAGED_CODE();
471 
472     /* Check Parameter Validity */
473     if (PreviousMode != KernelMode)
474     {
475         _SEH2_TRY
476         {
477             /* Make sure the pointer is valid */
478             ProbeForWriteHandle(TimerHandle);
479         }
480         _SEH2_EXCEPT(ExSystemExceptionFilter())
481         {
482             /* Return the exception code */
483             _SEH2_YIELD(return _SEH2_GetExceptionCode());
484         }
485         _SEH2_END;
486     }
487 
488     /* Open the Timer */
489     Status = ObOpenObjectByName(ObjectAttributes,
490                                 ExTimerType,
491                                 PreviousMode,
492                                 NULL,
493                                 DesiredAccess,
494                                 NULL,
495                                 &hTimer);
496     if (NT_SUCCESS(Status))
497     {
498         /* Enter SEH */
499         _SEH2_TRY
500         {
501             /* Return the Timer Handle */
502             *TimerHandle = hTimer;
503         }
504         _SEH2_EXCEPT(ExSystemExceptionFilter())
505         {
506             /* Do nothing */
507             (void)0;
508         }
509         _SEH2_END;
510     }
511 
512     /* Return to Caller */
513     return Status;
514 }
515 
516 NTSTATUS
517 NTAPI
518 NtQueryTimer(IN HANDLE TimerHandle,
519              IN TIMER_INFORMATION_CLASS TimerInformationClass,
520              OUT PVOID TimerInformation,
521              IN ULONG TimerInformationLength,
522              OUT PULONG ReturnLength OPTIONAL)
523 {
524     PETIMER Timer;
525     KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
526     NTSTATUS Status;
527     PTIMER_BASIC_INFORMATION BasicInfo = TimerInformation;
528     PAGED_CODE();
529 
530     /* Check Validity */
531     Status = DefaultQueryInfoBufferCheck(TimerInformationClass,
532                                          ExTimerInfoClass,
533                                          sizeof(ExTimerInfoClass) /
534                                          sizeof(ExTimerInfoClass[0]),
535                                          TimerInformation,
536                                          TimerInformationLength,
537                                          ReturnLength,
538                                          NULL,
539                                          PreviousMode,
540                                          TRUE);
541     if (!NT_SUCCESS(Status)) return Status;
542 
543     /* Get the Timer Object */
544     Status = ObReferenceObjectByHandle(TimerHandle,
545                                        TIMER_QUERY_STATE,
546                                        ExTimerType,
547                                        PreviousMode,
548                                        (PVOID*)&Timer,
549                                        NULL);
550     if (NT_SUCCESS(Status))
551     {
552         /* Return the Basic Information */
553         _SEH2_TRY
554         {
555             /* Return the remaining time, corrected */
556             BasicInfo->TimeRemaining.QuadPart = Timer->
557                                                 KeTimer.DueTime.QuadPart -
558                                                 KeQueryInterruptTime();
559 
560             /* Return the current state */
561             BasicInfo->SignalState = KeReadStateTimer(&Timer->KeTimer);
562 
563             /* Return the buffer length if requested */
564             if (ReturnLength) *ReturnLength = sizeof(TIMER_BASIC_INFORMATION);
565         }
566         _SEH2_EXCEPT(ExSystemExceptionFilter())
567         {
568             /* Get the exception code */
569             Status = _SEH2_GetExceptionCode();
570         }
571         _SEH2_END;
572 
573         /* Dereference Object */
574         ObDereferenceObject(Timer);
575     }
576 
577     /* Return Status */
578     return Status;
579 }
580 
581 NTSTATUS
582 NTAPI
583 NtSetTimer(IN HANDLE TimerHandle,
584            IN PLARGE_INTEGER DueTime,
585            IN PTIMER_APC_ROUTINE TimerApcRoutine OPTIONAL,
586            IN PVOID TimerContext OPTIONAL,
587            IN BOOLEAN WakeTimer,
588            IN LONG Period OPTIONAL,
589            OUT PBOOLEAN PreviousState OPTIONAL)
590 {
591     PETIMER Timer;
592     KIRQL OldIrql;
593     BOOLEAN State;
594     KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
595     PETHREAD Thread = PsGetCurrentThread();
596     LARGE_INTEGER TimerDueTime;
597     PETHREAD TimerThread;
598     ULONG DerefsToDo = 1;
599     NTSTATUS Status = STATUS_SUCCESS;
600     PAGED_CODE();
601 
602     /* Check for a valid Period */
603     if (Period < 0) return STATUS_INVALID_PARAMETER_6;
604 
605     /* Check if we need to probe */
606     if (PreviousMode != KernelMode)
607     {
608         _SEH2_TRY
609         {
610             /* Probe and capture the due time */
611             TimerDueTime = ProbeForReadLargeInteger(DueTime);
612 
613             /* Probe the state pointer if one was passed */
614             if (PreviousState) ProbeForWriteBoolean(PreviousState);
615         }
616         _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
617         {
618             /* Return the exception code */
619             _SEH2_YIELD(return _SEH2_GetExceptionCode());
620         }
621         _SEH2_END;
622     }
623     else
624     {
625         /* Capture the time directly */
626         TimerDueTime = *DueTime;
627     }
628 
629     /* Get the Timer Object */
630     Status = ObReferenceObjectByHandle(TimerHandle,
631                                        TIMER_MODIFY_STATE,
632                                        ExTimerType,
633                                        PreviousMode,
634                                        (PVOID*)&Timer,
635                                        NULL);
636 
637     /*
638      * Tell the user we don't support Wake Timers...
639      * when we have the ability to use/detect the Power Management
640      * functionality required to support them, make this check dependent
641      * on the actual PM capabilities
642      */
643     if (WakeTimer) Status = STATUS_TIMER_RESUME_IGNORED;
644 
645     /* Check status */
646     if (NT_SUCCESS(Status))
647     {
648         /* Lock the Timer */
649         KeAcquireSpinLock(&Timer->Lock, &OldIrql);
650 
651         /* Cancel Running Timer */
652         if (Timer->ApcAssociated)
653         {
654             /* Get the Thread. */
655             TimerThread = CONTAINING_RECORD(Timer->TimerApc.Thread,
656                                             ETHREAD,
657                                             Tcb);
658 
659             /* Lock its active list */
660             KeAcquireSpinLockAtDpcLevel(&TimerThread->ActiveTimerListLock);
661 
662             /* Remove it */
663             RemoveEntryList(&Timer->ActiveTimerListEntry);
664             Timer->ApcAssociated = FALSE;
665 
666             /* Unlock the list */
667             KeReleaseSpinLockFromDpcLevel(&TimerThread->ActiveTimerListLock);
668 
669             /* Cancel the Timer */
670             KeCancelTimer(&Timer->KeTimer);
671             KeRemoveQueueDpc(&Timer->TimerDpc);
672             if (KeRemoveQueueApc(&Timer->TimerApc)) DerefsToDo++;
673             DerefsToDo++;
674         }
675         else
676         {
677             /* If timer was disabled, we still need to cancel it */
678             KeCancelTimer(&Timer->KeTimer);
679         }
680 
681         /* Read the State */
682         State = KeReadStateTimer(&Timer->KeTimer);
683 
684         /* Handle Wake Timers */
685         Timer->WakeTimer = WakeTimer;
686         KeAcquireSpinLockAtDpcLevel(&ExpWakeListLock);
687         if ((WakeTimer) && !(Timer->WakeTimerListEntry.Flink))
688         {
689             /* Insert it into the list */
690             InsertTailList(&ExpWakeList, &Timer->WakeTimerListEntry);
691         }
692         else if (!(WakeTimer) && (Timer->WakeTimerListEntry.Flink))
693         {
694             /* Remove it from the list */
695             RemoveEntryList(&Timer->WakeTimerListEntry);
696             Timer->WakeTimerListEntry.Flink = NULL;
697         }
698         KeReleaseSpinLockFromDpcLevel(&ExpWakeListLock);
699 
700         /* Set up the APC Routine if specified */
701         Timer->Period = Period;
702         if (TimerApcRoutine)
703         {
704             /* Initialize the APC */
705             KeInitializeApc(&Timer->TimerApc,
706                             &Thread->Tcb,
707                             CurrentApcEnvironment,
708                             ExpTimerApcKernelRoutine,
709                             (PKRUNDOWN_ROUTINE)NULL,
710                             (PKNORMAL_ROUTINE)TimerApcRoutine,
711                             PreviousMode,
712                             TimerContext);
713 
714             /* Lock the Thread's Active List and Insert */
715             KeAcquireSpinLockAtDpcLevel(&Thread->ActiveTimerListLock);
716             InsertTailList(&Thread->ActiveTimerListHead,
717                            &Timer->ActiveTimerListEntry);
718             Timer->ApcAssociated = TRUE;
719             KeReleaseSpinLockFromDpcLevel(&Thread->ActiveTimerListLock);
720 
721             /* One less dereference to do */
722             DerefsToDo--;
723          }
724 
725         /* Enable and Set the Timer */
726         KeSetTimerEx(&Timer->KeTimer,
727                      TimerDueTime,
728                      Period,
729                      TimerApcRoutine ? &Timer->TimerDpc : NULL);
730 
731         /* Unlock the Timer */
732         KeReleaseSpinLock(&Timer->Lock, OldIrql);
733 
734         /* Dereference if it was previously enabled */
735         if (DerefsToDo) ObDereferenceObjectEx(Timer, DerefsToDo);
736 
737         /* Check if we need to return the State */
738         if (PreviousState)
739         {
740             /* Enter SEH */
741             _SEH2_TRY
742             {
743                 /* Return the Timer State */
744                 *PreviousState = State;
745             }
746             _SEH2_EXCEPT(ExSystemExceptionFilter())
747             {
748                 /* Do nothing */
749                 (void)0;
750             }
751             _SEH2_END;
752         }
753     }
754 
755     /* Return to Caller */
756     return Status;
757 }
758