xref: /reactos/ntoskrnl/ex/timer.c (revision d0d86ab5)
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
ExTimerRundown(VOID)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
ExpDeleteTimer(IN PVOID ObjectBody)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 
_Function_class_(KDEFERRED_ROUTINE)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
ExpTimerApcKernelRoutine(IN PKAPC Apc,IN OUT PKNORMAL_ROUTINE * NormalRoutine,IN OUT PVOID * NormalContext,IN OUT PVOID * SystemArgument1,IN OUT PVOID * SystemArguemnt2)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
ExpInitializeTimerImplementation(VOID)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
NtCancelTimer(IN HANDLE TimerHandle,OUT PBOOLEAN CurrentState OPTIONAL)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
NtCreateTimer(OUT PHANDLE TimerHandle,IN ACCESS_MASK DesiredAccess,IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,IN TIMER_TYPE TimerType)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
NtOpenTimer(OUT PHANDLE TimerHandle,IN ACCESS_MASK DesiredAccess,IN POBJECT_ATTRIBUTES ObjectAttributes)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
NtQueryTimer(IN HANDLE TimerHandle,IN TIMER_INFORMATION_CLASS TimerInformationClass,OUT PVOID TimerInformation,IN ULONG TimerInformationLength,OUT PULONG ReturnLength OPTIONAL)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                                          ICIF_PROBE_READ_WRITE,
536                                          TimerInformation,
537                                          TimerInformationLength,
538                                          ReturnLength,
539                                          NULL,
540                                          PreviousMode);
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
NtSetTimer(IN HANDLE TimerHandle,IN PLARGE_INTEGER DueTime,IN PTIMER_APC_ROUTINE TimerApcRoutine OPTIONAL,IN PVOID TimerContext OPTIONAL,IN BOOLEAN WakeTimer,IN LONG Period OPTIONAL,OUT PBOOLEAN PreviousState OPTIONAL)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 (NT_SUCCESS(Status) && WakeTimer)
644     {
645         Status = STATUS_TIMER_RESUME_IGNORED;
646     }
647 
648     /* Check status */
649     if (NT_SUCCESS(Status))
650     {
651         /* Lock the Timer */
652         KeAcquireSpinLock(&Timer->Lock, &OldIrql);
653 
654         /* Cancel Running Timer */
655         if (Timer->ApcAssociated)
656         {
657             /* Get the Thread. */
658             TimerThread = CONTAINING_RECORD(Timer->TimerApc.Thread,
659                                             ETHREAD,
660                                             Tcb);
661 
662             /* Lock its active list */
663             KeAcquireSpinLockAtDpcLevel(&TimerThread->ActiveTimerListLock);
664 
665             /* Remove it */
666             RemoveEntryList(&Timer->ActiveTimerListEntry);
667             Timer->ApcAssociated = FALSE;
668 
669             /* Unlock the list */
670             KeReleaseSpinLockFromDpcLevel(&TimerThread->ActiveTimerListLock);
671 
672             /* Cancel the Timer */
673             KeCancelTimer(&Timer->KeTimer);
674             KeRemoveQueueDpc(&Timer->TimerDpc);
675             if (KeRemoveQueueApc(&Timer->TimerApc)) DerefsToDo++;
676             DerefsToDo++;
677         }
678         else
679         {
680             /* If timer was disabled, we still need to cancel it */
681             KeCancelTimer(&Timer->KeTimer);
682         }
683 
684         /* Read the State */
685         State = KeReadStateTimer(&Timer->KeTimer);
686 
687         /* Handle Wake Timers */
688         Timer->WakeTimer = WakeTimer;
689         KeAcquireSpinLockAtDpcLevel(&ExpWakeListLock);
690         if ((WakeTimer) && !(Timer->WakeTimerListEntry.Flink))
691         {
692             /* Insert it into the list */
693             InsertTailList(&ExpWakeList, &Timer->WakeTimerListEntry);
694         }
695         else if (!(WakeTimer) && (Timer->WakeTimerListEntry.Flink))
696         {
697             /* Remove it from the list */
698             RemoveEntryList(&Timer->WakeTimerListEntry);
699             Timer->WakeTimerListEntry.Flink = NULL;
700         }
701         KeReleaseSpinLockFromDpcLevel(&ExpWakeListLock);
702 
703         /* Set up the APC Routine if specified */
704         Timer->Period = Period;
705         if (TimerApcRoutine)
706         {
707             /* Initialize the APC */
708             KeInitializeApc(&Timer->TimerApc,
709                             &Thread->Tcb,
710                             CurrentApcEnvironment,
711                             ExpTimerApcKernelRoutine,
712                             (PKRUNDOWN_ROUTINE)NULL,
713                             (PKNORMAL_ROUTINE)TimerApcRoutine,
714                             PreviousMode,
715                             TimerContext);
716 
717             /* Lock the Thread's Active List and Insert */
718             KeAcquireSpinLockAtDpcLevel(&Thread->ActiveTimerListLock);
719             InsertTailList(&Thread->ActiveTimerListHead,
720                            &Timer->ActiveTimerListEntry);
721             Timer->ApcAssociated = TRUE;
722             KeReleaseSpinLockFromDpcLevel(&Thread->ActiveTimerListLock);
723 
724             /* One less dereference to do */
725             DerefsToDo--;
726          }
727 
728         /* Enable and Set the Timer */
729         KeSetTimerEx(&Timer->KeTimer,
730                      TimerDueTime,
731                      Period,
732                      TimerApcRoutine ? &Timer->TimerDpc : NULL);
733 
734         /* Unlock the Timer */
735         KeReleaseSpinLock(&Timer->Lock, OldIrql);
736 
737         /* Dereference if it was previously enabled */
738         if (DerefsToDo) ObDereferenceObjectEx(Timer, DerefsToDo);
739 
740         /* Check if we need to return the State */
741         if (PreviousState)
742         {
743             /* Enter SEH */
744             _SEH2_TRY
745             {
746                 /* Return the Timer State */
747                 *PreviousState = State;
748             }
749             _SEH2_EXCEPT(ExSystemExceptionFilter())
750             {
751                 /* Do nothing */
752                 (void)0;
753             }
754             _SEH2_END;
755         }
756     }
757 
758     /* Return to Caller */
759     return Status;
760 }
761