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)
9 /* INCLUDES ******************************************************************/
15 /* GLOBALS *******************************************************************/
17 /* Timer Object Type */
18 POBJECT_TYPE ExTimerType
= NULL
;
20 KSPIN_LOCK ExpWakeListLock
;
21 LIST_ENTRY ExpWakeList
;
24 static GENERIC_MAPPING ExpTimerMapping
=
26 STANDARD_RIGHTS_READ
| TIMER_QUERY_STATE
,
27 STANDARD_RIGHTS_WRITE
| TIMER_MODIFY_STATE
,
28 STANDARD_RIGHTS_EXECUTE
| SYNCHRONIZE
,
32 /* Timer Information Classes */
33 static const INFORMATION_CLASS_INFO ExTimerInfoClass
[] =
35 /* TimerBasicInformation */
36 ICI_SQ_SAME(sizeof(TIMER_BASIC_INFORMATION
), sizeof(ULONG
), ICIF_QUERY
),
39 /* PRIVATE FUNCTIONS *********************************************************/
45 PETHREAD Thread
= PsGetCurrentThread();
47 PLIST_ENTRY CurrentEntry
;
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
)
57 Timer
= CONTAINING_RECORD(CurrentEntry
, ETIMER
, ActiveTimerListEntry
);
60 ObReferenceObject(Timer
);
64 KeReleaseSpinLock(&Thread
->ActiveTimerListLock
, OldIrql
);
67 KeAcquireSpinLock(&Timer
->Lock
, &OldIrql
);
69 /* Lock the list again */
70 KeAcquireSpinLockAtDpcLevel(&Thread
->ActiveTimerListLock
);
72 /* Make sure that the timer is valid */
73 if ((Timer
->ApcAssociated
) && (&Thread
->Tcb
== Timer
->TimerApc
.Thread
))
75 /* Remove it from the list */
76 RemoveEntryList(&Timer
->ActiveTimerListEntry
);
77 Timer
->ApcAssociated
= FALSE
;
79 /* Cancel the timer and remove its DPC and APC */
80 KeCancelTimer(&Timer
->KeTimer
);
81 KeRemoveQueueDpc(&Timer
->TimerDpc
);
82 if (KeRemoveQueueApc(&Timer
->TimerApc
)) DerefsToDo
++;
84 /* Add another dereference to do */
89 KeReleaseSpinLockFromDpcLevel(&Thread
->ActiveTimerListLock
);
91 /* Unlock the Timer */
92 KeReleaseSpinLock(&Timer
->Lock
, OldIrql
);
95 ObDereferenceObjectEx(Timer
, DerefsToDo
);
98 KeAcquireSpinLock(&Thread
->ActiveTimerListLock
, &OldIrql
);
99 CurrentEntry
= Thread
->ActiveTimerListHead
.Flink
;
102 /* Release lock and return */
103 KeReleaseSpinLock(&Thread
->ActiveTimerListLock
, OldIrql
);
108 ExpDeleteTimer(IN PVOID ObjectBody
)
111 PETIMER Timer
= ObjectBody
;
113 /* Check if it has a Wait List */
114 if (Timer
->WakeTimerListEntry
.Flink
)
116 /* Lock the Wake List */
117 KeAcquireSpinLock(&ExpWakeListLock
, &OldIrql
);
119 /* Check again, since it might've changed before we locked */
120 if (Timer
->WakeTimerListEntry
.Flink
)
122 /* Remove it from the Wait List */
123 RemoveEntryList(&Timer
->WakeTimerListEntry
);
124 Timer
->WakeTimerListEntry
.Flink
= NULL
;
127 /* Release the Wake List */
128 KeReleaseSpinLock(&ExpWakeListLock
, OldIrql
);
131 /* Tell the Kernel to cancel the Timer and flush all queued DPCs */
132 KeCancelTimer(&Timer
->KeTimer
);
138 ExpTimerDpcRoutine(IN PKDPC Dpc
,
139 IN PVOID DeferredContext
,
140 IN PVOID SystemArgument1
,
141 IN PVOID SystemArgument2
)
143 PETIMER Timer
= DeferredContext
;
144 BOOLEAN Inserted
= FALSE
;
146 /* Reference the timer */
147 if (!ObReferenceObjectSafe(Timer
)) return;
150 KeAcquireSpinLockAtDpcLevel(&Timer
->Lock
);
152 /* Check if the timer is associated */
153 if (Timer
->ApcAssociated
)
156 Inserted
= KeInsertQueueApc(&Timer
->TimerApc
,
162 /* Release the Timer */
163 KeReleaseSpinLockFromDpcLevel(&Timer
->Lock
);
165 /* Dereference it if we couldn't queue the APC */
166 if (!Inserted
) ObDereferenceObject(Timer
);
171 ExpTimerApcKernelRoutine(IN PKAPC Apc
,
172 IN OUT PKNORMAL_ROUTINE
* NormalRoutine
,
173 IN OUT PVOID
* NormalContext
,
174 IN OUT PVOID
* SystemArgument1
,
175 IN OUT PVOID
* SystemArguemnt2
)
179 ULONG DerefsToDo
= 1;
180 PETHREAD Thread
= PsGetCurrentThread();
182 /* We need to find out which Timer we are */
183 Timer
= CONTAINING_RECORD(Apc
, ETIMER
, TimerApc
);
186 KeAcquireSpinLock(&Timer
->Lock
, &OldIrql
);
188 /* Lock the Thread's Active Timer List*/
189 KeAcquireSpinLockAtDpcLevel(&Thread
->ActiveTimerListLock
);
191 /* Make sure that the Timer is valid, and that it belongs to this thread */
192 if ((Timer
->ApcAssociated
) && (&Thread
->Tcb
== Timer
->TimerApc
.Thread
))
194 /* Check if it's not periodic */
197 /* Remove it from the Active Timers List */
198 RemoveEntryList(&Timer
->ActiveTimerListEntry
);
201 Timer
->ApcAssociated
= FALSE
;
207 /* Clear the normal routine */
208 *NormalRoutine
= NULL
;
212 KeReleaseSpinLockFromDpcLevel(&Thread
->ActiveTimerListLock
);
213 KeReleaseSpinLock(&Timer
->Lock
, OldIrql
);
215 /* Dereference as needed */
216 ObDereferenceObjectEx(Timer
, DerefsToDo
);
222 ExpInitializeTimerImplementation(VOID
)
224 OBJECT_TYPE_INITIALIZER ObjectTypeInitializer
;
227 /* Create the Timer Object Type */
228 RtlZeroMemory(&ObjectTypeInitializer
, sizeof(ObjectTypeInitializer
));
229 RtlInitUnicodeString(&Name
, L
"Timer");
230 ObjectTypeInitializer
.Length
= sizeof(ObjectTypeInitializer
);
231 ObjectTypeInitializer
.InvalidAttributes
= OBJ_OPENLINK
;
232 ObjectTypeInitializer
.DefaultNonPagedPoolCharge
= sizeof(ETIMER
);
233 ObjectTypeInitializer
.GenericMapping
= ExpTimerMapping
;
234 ObjectTypeInitializer
.PoolType
= NonPagedPool
;
235 ObjectTypeInitializer
.ValidAccessMask
= TIMER_ALL_ACCESS
;
236 ObjectTypeInitializer
.DeleteProcedure
= ExpDeleteTimer
;
237 ObCreateObjectType(&Name
, &ObjectTypeInitializer
, NULL
, &ExTimerType
);
239 /* Initialize the Wait List and Lock */
240 KeInitializeSpinLock(&ExpWakeListLock
);
241 InitializeListHead(&ExpWakeList
);
244 /* PUBLIC FUNCTIONS **********************************************************/
248 NtCancelTimer(IN HANDLE TimerHandle
,
249 OUT PBOOLEAN CurrentState OPTIONAL
)
252 KPROCESSOR_MODE PreviousMode
= ExGetPreviousMode();
255 PETHREAD TimerThread
;
256 ULONG DerefsToDo
= 1;
257 NTSTATUS Status
= STATUS_SUCCESS
;
260 /* Check Parameter Validity */
261 if ((CurrentState
) && (PreviousMode
!= KernelMode
))
265 ProbeForWriteBoolean(CurrentState
);
267 _SEH2_EXCEPT(ExSystemExceptionFilter())
269 Status
= _SEH2_GetExceptionCode();
272 if(!NT_SUCCESS(Status
)) return Status
;
275 /* Get the Timer Object */
276 Status
= ObReferenceObjectByHandle(TimerHandle
,
282 if (NT_SUCCESS(Status
))
285 KeAcquireSpinLock(&Timer
->Lock
, &OldIrql
);
287 /* Check if it's enabled */
288 if (Timer
->ApcAssociated
)
290 /* Get the Thread. */
291 TimerThread
= CONTAINING_RECORD(Timer
->TimerApc
.Thread
,
295 /* Lock its active list */
296 KeAcquireSpinLockAtDpcLevel(&TimerThread
->ActiveTimerListLock
);
299 RemoveEntryList(&TimerThread
->ActiveTimerListHead
);
300 Timer
->ApcAssociated
= FALSE
;
302 /* Unlock the list */
303 KeReleaseSpinLockFromDpcLevel(&TimerThread
->ActiveTimerListLock
);
305 /* Cancel the Timer */
306 KeCancelTimer(&Timer
->KeTimer
);
307 KeRemoveQueueDpc(&Timer
->TimerDpc
);
308 if (KeRemoveQueueApc(&Timer
->TimerApc
)) DerefsToDo
++;
313 /* If timer was disabled, we still need to cancel it */
314 KeCancelTimer(&Timer
->KeTimer
);
317 /* Handle a Wake Timer */
318 if (Timer
->WakeTimerListEntry
.Flink
)
320 /* Lock the Wake List */
321 KeAcquireSpinLockAtDpcLevel(&ExpWakeListLock
);
323 /* Check again, since it might've changed before we locked */
324 if (Timer
->WakeTimerListEntry
.Flink
)
326 /* Remove it from the Wait List */
327 RemoveEntryList(&Timer
->WakeTimerListEntry
);
328 Timer
->WakeTimerListEntry
.Flink
= NULL
;
331 /* Release the Wake List */
332 KeReleaseSpinLockFromDpcLevel(&ExpWakeListLock
);
335 /* Unlock the Timer */
336 KeReleaseSpinLock(&Timer
->Lock
, OldIrql
);
338 /* Read the old State */
339 State
= KeReadStateTimer(&Timer
->KeTimer
);
341 /* Dereference the Object */
342 ObDereferenceObjectEx(Timer
, DerefsToDo
);
344 /* Make sure it's safe to write to the handle */
349 *CurrentState
= State
;
351 _SEH2_EXCEPT(ExSystemExceptionFilter())
359 /* Return to Caller */
365 NtCreateTimer(OUT PHANDLE TimerHandle
,
366 IN ACCESS_MASK DesiredAccess
,
367 IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL
,
368 IN TIMER_TYPE TimerType
)
372 KPROCESSOR_MODE PreviousMode
= ExGetPreviousMode();
373 NTSTATUS Status
= STATUS_SUCCESS
;
376 /* Check for correct timer type */
377 if ((TimerType
!= NotificationTimer
) &&
378 (TimerType
!= SynchronizationTimer
))
381 return STATUS_INVALID_PARAMETER_4
;
384 /* Check Parameter Validity */
385 if (PreviousMode
!= KernelMode
)
389 ProbeForWriteHandle(TimerHandle
);
391 _SEH2_EXCEPT(ExSystemExceptionFilter())
393 Status
= _SEH2_GetExceptionCode();
396 if(!NT_SUCCESS(Status
)) return Status
;
399 /* Create the Object */
400 Status
= ObCreateObject(PreviousMode
,
409 if (NT_SUCCESS(Status
))
411 /* Initialize the DPC */
412 KeInitializeDpc(&Timer
->TimerDpc
, ExpTimerDpcRoutine
, Timer
);
414 /* Initialize the Kernel Timer */
415 KeInitializeTimerEx(&Timer
->KeTimer
, TimerType
);
417 /* Initialize the timer fields */
418 KeInitializeSpinLock(&Timer
->Lock
);
419 Timer
->ApcAssociated
= FALSE
;
420 Timer
->WakeTimer
= FALSE
;
421 Timer
->WakeTimerListEntry
.Flink
= NULL
;
423 /* Insert the Timer */
424 Status
= ObInsertObject((PVOID
)Timer
,
431 /* Check for success */
432 if (NT_SUCCESS(Status
))
434 /* Make sure it's safe to write to the handle */
437 *TimerHandle
= hTimer
;
439 _SEH2_EXCEPT(ExSystemExceptionFilter())
447 /* Return to Caller */
453 NtOpenTimer(OUT PHANDLE TimerHandle
,
454 IN ACCESS_MASK DesiredAccess
,
455 IN POBJECT_ATTRIBUTES ObjectAttributes
)
458 KPROCESSOR_MODE PreviousMode
= ExGetPreviousMode();
459 NTSTATUS Status
= STATUS_SUCCESS
;
462 /* Check Parameter Validity */
463 if (PreviousMode
!= KernelMode
)
467 ProbeForWriteHandle(TimerHandle
);
469 _SEH2_EXCEPT(ExSystemExceptionFilter())
471 Status
= _SEH2_GetExceptionCode();
474 if(!NT_SUCCESS(Status
)) return Status
;
478 Status
= ObOpenObjectByName(ObjectAttributes
,
485 if (NT_SUCCESS(Status
))
487 /* Make sure it's safe to write to the handle */
490 *TimerHandle
= hTimer
;
492 _SEH2_EXCEPT(ExSystemExceptionFilter())
499 /* Return to Caller */
505 NtQueryTimer(IN HANDLE TimerHandle
,
506 IN TIMER_INFORMATION_CLASS TimerInformationClass
,
507 OUT PVOID TimerInformation
,
508 IN ULONG TimerInformationLength
,
509 OUT PULONG ReturnLength OPTIONAL
)
512 KPROCESSOR_MODE PreviousMode
= ExGetPreviousMode();
514 PTIMER_BASIC_INFORMATION BasicInfo
= TimerInformation
;
518 Status
= DefaultQueryInfoBufferCheck(TimerInformationClass
,
520 sizeof(ExTimerInfoClass
) /
521 sizeof(ExTimerInfoClass
[0]),
523 TimerInformationLength
,
527 if(!NT_SUCCESS(Status
)) return Status
;
529 /* Get the Timer Object */
530 Status
= ObReferenceObjectByHandle(TimerHandle
,
536 if(NT_SUCCESS(Status
))
538 /* Return the Basic Information */
541 /* Return the remaining time, corrected */
542 BasicInfo
->TimeRemaining
.QuadPart
= Timer
->
543 KeTimer
.DueTime
.QuadPart
-
544 KeQueryInterruptTime();
546 /* Return the current state */
547 BasicInfo
->SignalState
= KeReadStateTimer(&Timer
->KeTimer
);
549 /* Return the buffer length if requested */
550 if (ReturnLength
) *ReturnLength
= sizeof(TIMER_BASIC_INFORMATION
);
552 _SEH2_EXCEPT(ExSystemExceptionFilter())
554 Status
= _SEH2_GetExceptionCode();
558 /* Dereference Object */
559 ObDereferenceObject(Timer
);
568 NtSetTimer(IN HANDLE TimerHandle
,
569 IN PLARGE_INTEGER DueTime
,
570 IN PTIMER_APC_ROUTINE TimerApcRoutine OPTIONAL
,
571 IN PVOID TimerContext OPTIONAL
,
572 IN BOOLEAN WakeTimer
,
573 IN LONG Period OPTIONAL
,
574 OUT PBOOLEAN PreviousState OPTIONAL
)
579 KPROCESSOR_MODE PreviousMode
= ExGetPreviousMode();
580 PETHREAD Thread
= PsGetCurrentThread();
581 LARGE_INTEGER TimerDueTime
;
582 PETHREAD TimerThread
;
583 ULONG DerefsToDo
= 1;
584 NTSTATUS Status
= STATUS_SUCCESS
;
587 /* Check for a valid Period */
588 if (Period
< 0) return STATUS_INVALID_PARAMETER_6
;
590 /* Check Parameter Validity */
591 if (PreviousMode
!= KernelMode
)
595 TimerDueTime
= ProbeForReadLargeInteger(DueTime
);
596 if (PreviousState
) ProbeForWriteBoolean(PreviousState
);
598 _SEH2_EXCEPT(ExSystemExceptionFilter())
600 Status
= _SEH2_GetExceptionCode();
603 if(!NT_SUCCESS(Status
)) return Status
;
607 /* Capture the time directly */
608 TimerDueTime
= *DueTime
;
611 /* Get the Timer Object */
612 Status
= ObReferenceObjectByHandle(TimerHandle
,
620 * Tell the user we don't support Wake Timers...
621 * when we have the ability to use/detect the Power Management
622 * functionality required to support them, make this check dependent
623 * on the actual PM capabilities
625 if (WakeTimer
) Status
= STATUS_TIMER_RESUME_IGNORED
;
628 if (NT_SUCCESS(Status
))
631 KeAcquireSpinLock(&Timer
->Lock
, &OldIrql
);
633 /* Cancel Running Timer */
634 if (Timer
->ApcAssociated
)
636 /* Get the Thread. */
637 TimerThread
= CONTAINING_RECORD(Timer
->TimerApc
.Thread
,
641 /* Lock its active list */
642 KeAcquireSpinLockAtDpcLevel(&TimerThread
->ActiveTimerListLock
);
645 RemoveEntryList(&TimerThread
->ActiveTimerListHead
);
646 Timer
->ApcAssociated
= FALSE
;
648 /* Unlock the list */
649 KeReleaseSpinLockFromDpcLevel(&TimerThread
->ActiveTimerListLock
);
651 /* Cancel the Timer */
652 KeCancelTimer(&Timer
->KeTimer
);
653 KeRemoveQueueDpc(&Timer
->TimerDpc
);
654 if (KeRemoveQueueApc(&Timer
->TimerApc
)) DerefsToDo
++;
659 /* If timer was disabled, we still need to cancel it */
660 KeCancelTimer(&Timer
->KeTimer
);
664 State
= KeReadStateTimer(&Timer
->KeTimer
);
666 /* Handle Wake Timers */
667 Timer
->WakeTimer
= WakeTimer
;
668 KeAcquireSpinLockAtDpcLevel(&ExpWakeListLock
);
669 if ((WakeTimer
) && !(Timer
->WakeTimerListEntry
.Flink
))
671 /* Insert it into the list */
672 InsertTailList(&ExpWakeList
, &Timer
->WakeTimerListEntry
);
674 else if (!(WakeTimer
) && (Timer
->WakeTimerListEntry
.Flink
))
676 /* Remove it from the list */
677 RemoveEntryList(&Timer
->WakeTimerListEntry
);
678 Timer
->WakeTimerListEntry
.Flink
= NULL
;
680 KeReleaseSpinLockFromDpcLevel(&ExpWakeListLock
);
682 /* Set up the APC Routine if specified */
683 Timer
->Period
= Period
;
686 /* Initialize the APC */
687 KeInitializeApc(&Timer
->TimerApc
,
689 CurrentApcEnvironment
,
690 ExpTimerApcKernelRoutine
,
691 (PKRUNDOWN_ROUTINE
)NULL
,
692 (PKNORMAL_ROUTINE
)TimerApcRoutine
,
696 /* Lock the Thread's Active List and Insert */
697 KeAcquireSpinLockAtDpcLevel(&Thread
->ActiveTimerListLock
);
698 InsertTailList(&Thread
->ActiveTimerListHead
,
699 &Timer
->ActiveTimerListEntry
);
700 Timer
->ApcAssociated
= TRUE
;
701 KeReleaseSpinLockFromDpcLevel(&Thread
->ActiveTimerListLock
);
703 /* One less dereference to do */
707 /* Enable and Set the Timer */
708 KeSetTimerEx(&Timer
->KeTimer
,
711 TimerApcRoutine
? &Timer
->TimerDpc
: NULL
);
713 /* Unlock the Timer */
714 KeReleaseSpinLock(&Timer
->Lock
, OldIrql
);
716 /* Dereference if it was previously enabled */
717 if (DerefsToDo
) ObDereferenceObjectEx(Timer
, DerefsToDo
);
719 /* Make sure it's safe to write to the handle */
724 *PreviousState
= State
;
726 _SEH2_EXCEPT(ExSystemExceptionFilter())
733 /* Return to Caller */