xref: /reactos/ntoskrnl/ex/timer.c (revision 6b700c6a)
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     ICI_SQ_SAME(sizeof(TIMER_BASIC_INFORMATION), sizeof(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     if (!NT_SUCCESS(Status)) return Status;
541 
542     /* Get the Timer Object */
543     Status = ObReferenceObjectByHandle(TimerHandle,
544                                        TIMER_QUERY_STATE,
545                                        ExTimerType,
546                                        PreviousMode,
547                                        (PVOID*)&Timer,
548                                        NULL);
549     if (NT_SUCCESS(Status))
550     {
551         /* Return the Basic Information */
552         _SEH2_TRY
553         {
554             /* Return the remaining time, corrected */
555             BasicInfo->TimeRemaining.QuadPart = Timer->
556                                                 KeTimer.DueTime.QuadPart -
557                                                 KeQueryInterruptTime();
558 
559             /* Return the current state */
560             BasicInfo->SignalState = KeReadStateTimer(&Timer->KeTimer);
561 
562             /* Return the buffer length if requested */
563             if (ReturnLength) *ReturnLength = sizeof(TIMER_BASIC_INFORMATION);
564         }
565         _SEH2_EXCEPT(ExSystemExceptionFilter())
566         {
567             /* Get the exception code */
568             Status = _SEH2_GetExceptionCode();
569         }
570         _SEH2_END;
571 
572         /* Dereference Object */
573         ObDereferenceObject(Timer);
574     }
575 
576     /* Return Status */
577     return Status;
578 }
579 
580 NTSTATUS
581 NTAPI
582 NtSetTimer(IN HANDLE TimerHandle,
583            IN PLARGE_INTEGER DueTime,
584            IN PTIMER_APC_ROUTINE TimerApcRoutine OPTIONAL,
585            IN PVOID TimerContext OPTIONAL,
586            IN BOOLEAN WakeTimer,
587            IN LONG Period OPTIONAL,
588            OUT PBOOLEAN PreviousState OPTIONAL)
589 {
590     PETIMER Timer;
591     KIRQL OldIrql;
592     BOOLEAN State;
593     KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
594     PETHREAD Thread = PsGetCurrentThread();
595     LARGE_INTEGER TimerDueTime;
596     PETHREAD TimerThread;
597     ULONG DerefsToDo = 1;
598     NTSTATUS Status = STATUS_SUCCESS;
599     PAGED_CODE();
600 
601     /* Check for a valid Period */
602     if (Period < 0) return STATUS_INVALID_PARAMETER_6;
603 
604     /* Check if we need to probe */
605     if (PreviousMode != KernelMode)
606     {
607         _SEH2_TRY
608         {
609             /* Probe and capture the due time */
610             TimerDueTime = ProbeForReadLargeInteger(DueTime);
611 
612             /* Probe the state pointer if one was passed */
613             if (PreviousState) ProbeForWriteBoolean(PreviousState);
614         }
615         _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
616         {
617             /* Return the exception code */
618             _SEH2_YIELD(return _SEH2_GetExceptionCode());
619         }
620         _SEH2_END;
621     }
622     else
623     {
624         /* Capture the time directly */
625         TimerDueTime = *DueTime;
626     }
627 
628     /* Get the Timer Object */
629     Status = ObReferenceObjectByHandle(TimerHandle,
630                                        TIMER_MODIFY_STATE,
631                                        ExTimerType,
632                                        PreviousMode,
633                                        (PVOID*)&Timer,
634                                        NULL);
635 
636     /*
637      * Tell the user we don't support Wake Timers...
638      * when we have the ability to use/detect the Power Management
639      * functionality required to support them, make this check dependent
640      * on the actual PM capabilities
641      */
642     if (WakeTimer) Status = STATUS_TIMER_RESUME_IGNORED;
643 
644     /* Check status */
645     if (NT_SUCCESS(Status))
646     {
647         /* Lock the Timer */
648         KeAcquireSpinLock(&Timer->Lock, &OldIrql);
649 
650         /* Cancel Running Timer */
651         if (Timer->ApcAssociated)
652         {
653             /* Get the Thread. */
654             TimerThread = CONTAINING_RECORD(Timer->TimerApc.Thread,
655                                             ETHREAD,
656                                             Tcb);
657 
658             /* Lock its active list */
659             KeAcquireSpinLockAtDpcLevel(&TimerThread->ActiveTimerListLock);
660 
661             /* Remove it */
662             RemoveEntryList(&Timer->ActiveTimerListEntry);
663             Timer->ApcAssociated = FALSE;
664 
665             /* Unlock the list */
666             KeReleaseSpinLockFromDpcLevel(&TimerThread->ActiveTimerListLock);
667 
668             /* Cancel the Timer */
669             KeCancelTimer(&Timer->KeTimer);
670             KeRemoveQueueDpc(&Timer->TimerDpc);
671             if (KeRemoveQueueApc(&Timer->TimerApc)) DerefsToDo++;
672             DerefsToDo++;
673         }
674         else
675         {
676             /* If timer was disabled, we still need to cancel it */
677             KeCancelTimer(&Timer->KeTimer);
678         }
679 
680         /* Read the State */
681         State = KeReadStateTimer(&Timer->KeTimer);
682 
683         /* Handle Wake Timers */
684         Timer->WakeTimer = WakeTimer;
685         KeAcquireSpinLockAtDpcLevel(&ExpWakeListLock);
686         if ((WakeTimer) && !(Timer->WakeTimerListEntry.Flink))
687         {
688             /* Insert it into the list */
689             InsertTailList(&ExpWakeList, &Timer->WakeTimerListEntry);
690         }
691         else if (!(WakeTimer) && (Timer->WakeTimerListEntry.Flink))
692         {
693             /* Remove it from the list */
694             RemoveEntryList(&Timer->WakeTimerListEntry);
695             Timer->WakeTimerListEntry.Flink = NULL;
696         }
697         KeReleaseSpinLockFromDpcLevel(&ExpWakeListLock);
698 
699         /* Set up the APC Routine if specified */
700         Timer->Period = Period;
701         if (TimerApcRoutine)
702         {
703             /* Initialize the APC */
704             KeInitializeApc(&Timer->TimerApc,
705                             &Thread->Tcb,
706                             CurrentApcEnvironment,
707                             ExpTimerApcKernelRoutine,
708                             (PKRUNDOWN_ROUTINE)NULL,
709                             (PKNORMAL_ROUTINE)TimerApcRoutine,
710                             PreviousMode,
711                             TimerContext);
712 
713             /* Lock the Thread's Active List and Insert */
714             KeAcquireSpinLockAtDpcLevel(&Thread->ActiveTimerListLock);
715             InsertTailList(&Thread->ActiveTimerListHead,
716                            &Timer->ActiveTimerListEntry);
717             Timer->ApcAssociated = TRUE;
718             KeReleaseSpinLockFromDpcLevel(&Thread->ActiveTimerListLock);
719 
720             /* One less dereference to do */
721             DerefsToDo--;
722          }
723 
724         /* Enable and Set the Timer */
725         KeSetTimerEx(&Timer->KeTimer,
726                      TimerDueTime,
727                      Period,
728                      TimerApcRoutine ? &Timer->TimerDpc : NULL);
729 
730         /* Unlock the Timer */
731         KeReleaseSpinLock(&Timer->Lock, OldIrql);
732 
733         /* Dereference if it was previously enabled */
734         if (DerefsToDo) ObDereferenceObjectEx(Timer, DerefsToDo);
735 
736         /* Check if we need to return the State */
737         if (PreviousState)
738         {
739             /* Enter SEH */
740             _SEH2_TRY
741             {
742                 /* Return the Timer State */
743                 *PreviousState = State;
744             }
745             _SEH2_EXCEPT(ExSystemExceptionFilter())
746             {
747                 /* Do nothing */
748                 (void)0;
749             }
750             _SEH2_END;
751         }
752     }
753 
754     /* Return to Caller */
755     return Status;
756 }
757