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;
260 /* Check if we need to probe */
261 if ((CurrentState
) && (PreviousMode
!= KernelMode
))
265 /* Make sure the pointer is valid */
266 ProbeForWriteBoolean(CurrentState
);
268 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER
)
270 /* Return the exception code */
271 _SEH2_YIELD(return _SEH2_GetExceptionCode());
276 /* Get the Timer Object */
277 Status
= ObReferenceObjectByHandle(TimerHandle
,
283 if (NT_SUCCESS(Status
))
286 KeAcquireSpinLock(&Timer
->Lock
, &OldIrql
);
288 /* Check if it's enabled */
289 if (Timer
->ApcAssociated
)
291 /* Get the Thread. */
292 TimerThread
= CONTAINING_RECORD(Timer
->TimerApc
.Thread
,
296 /* Lock its active list */
297 KeAcquireSpinLockAtDpcLevel(&TimerThread
->ActiveTimerListLock
);
300 RemoveEntryList(&TimerThread
->ActiveTimerListHead
);
301 Timer
->ApcAssociated
= FALSE
;
303 /* Unlock the list */
304 KeReleaseSpinLockFromDpcLevel(&TimerThread
->ActiveTimerListLock
);
306 /* Cancel the Timer */
307 KeCancelTimer(&Timer
->KeTimer
);
308 KeRemoveQueueDpc(&Timer
->TimerDpc
);
309 if (KeRemoveQueueApc(&Timer
->TimerApc
)) DerefsToDo
++;
314 /* If timer was disabled, we still need to cancel it */
315 KeCancelTimer(&Timer
->KeTimer
);
318 /* Handle a Wake Timer */
319 if (Timer
->WakeTimerListEntry
.Flink
)
321 /* Lock the Wake List */
322 KeAcquireSpinLockAtDpcLevel(&ExpWakeListLock
);
324 /* Check again, since it might've changed before we locked */
325 if (Timer
->WakeTimerListEntry
.Flink
)
327 /* Remove it from the Wait List */
328 RemoveEntryList(&Timer
->WakeTimerListEntry
);
329 Timer
->WakeTimerListEntry
.Flink
= NULL
;
332 /* Release the Wake List */
333 KeReleaseSpinLockFromDpcLevel(&ExpWakeListLock
);
336 /* Unlock the Timer */
337 KeReleaseSpinLock(&Timer
->Lock
, OldIrql
);
339 /* Read the old State */
340 State
= KeReadStateTimer(&Timer
->KeTimer
);
342 /* Dereference the Object */
343 ObDereferenceObjectEx(Timer
, DerefsToDo
);
345 /* Check if caller wants the state */
350 /* Return the Timer State */
351 *CurrentState
= State
;
353 _SEH2_EXCEPT(ExSystemExceptionFilter())
361 /* Return to Caller */
367 NtCreateTimer(OUT PHANDLE TimerHandle
,
368 IN ACCESS_MASK DesiredAccess
,
369 IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL
,
370 IN TIMER_TYPE TimerType
)
374 KPROCESSOR_MODE PreviousMode
= ExGetPreviousMode();
378 /* Check for correct timer type */
379 if ((TimerType
!= NotificationTimer
) &&
380 (TimerType
!= SynchronizationTimer
))
383 return STATUS_INVALID_PARAMETER_4
;
386 /* Check if we need to probe */
387 if (PreviousMode
!= KernelMode
)
391 /* Make sure the pointer is valid */
392 ProbeForWriteHandle(TimerHandle
);
394 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER
)
396 /* Return the exception code */
397 _SEH2_YIELD(return _SEH2_GetExceptionCode());
402 /* Create the Object */
403 Status
= ObCreateObject(PreviousMode
,
412 if (NT_SUCCESS(Status
))
414 /* Initialize the DPC */
415 KeInitializeDpc(&Timer
->TimerDpc
, ExpTimerDpcRoutine
, Timer
);
417 /* Initialize the Kernel Timer */
418 KeInitializeTimerEx(&Timer
->KeTimer
, TimerType
);
420 /* Initialize the timer fields */
421 KeInitializeSpinLock(&Timer
->Lock
);
422 Timer
->ApcAssociated
= FALSE
;
423 Timer
->WakeTimer
= FALSE
;
424 Timer
->WakeTimerListEntry
.Flink
= NULL
;
426 /* Insert the Timer */
427 Status
= ObInsertObject((PVOID
)Timer
,
434 /* Check for success */
435 if (NT_SUCCESS(Status
))
440 /* Return the Timer Handle */
441 *TimerHandle
= hTimer
;
443 _SEH2_EXCEPT(ExSystemExceptionFilter())
451 /* Return to Caller */
457 NtOpenTimer(OUT PHANDLE TimerHandle
,
458 IN ACCESS_MASK DesiredAccess
,
459 IN POBJECT_ATTRIBUTES ObjectAttributes
)
462 KPROCESSOR_MODE PreviousMode
= ExGetPreviousMode();
466 /* Check Parameter Validity */
467 if (PreviousMode
!= KernelMode
)
471 /* Make sure the pointer is valid */
472 ProbeForWriteHandle(TimerHandle
);
474 _SEH2_EXCEPT(ExSystemExceptionFilter())
476 /* Return the exception code */
477 _SEH2_YIELD(return _SEH2_GetExceptionCode());
483 Status
= ObOpenObjectByName(ObjectAttributes
,
490 if (NT_SUCCESS(Status
))
495 /* Return the Timer Handle */
496 *TimerHandle
= hTimer
;
498 _SEH2_EXCEPT(ExSystemExceptionFilter())
505 /* Return to Caller */
511 NtQueryTimer(IN HANDLE TimerHandle
,
512 IN TIMER_INFORMATION_CLASS TimerInformationClass
,
513 OUT PVOID TimerInformation
,
514 IN ULONG TimerInformationLength
,
515 OUT PULONG ReturnLength OPTIONAL
)
518 KPROCESSOR_MODE PreviousMode
= ExGetPreviousMode();
520 PTIMER_BASIC_INFORMATION BasicInfo
= TimerInformation
;
524 Status
= DefaultQueryInfoBufferCheck(TimerInformationClass
,
526 sizeof(ExTimerInfoClass
) /
527 sizeof(ExTimerInfoClass
[0]),
529 TimerInformationLength
,
532 if (!NT_SUCCESS(Status
)) return Status
;
534 /* Get the Timer Object */
535 Status
= ObReferenceObjectByHandle(TimerHandle
,
541 if (NT_SUCCESS(Status
))
543 /* Return the Basic Information */
546 /* Return the remaining time, corrected */
547 BasicInfo
->TimeRemaining
.QuadPart
= Timer
->
548 KeTimer
.DueTime
.QuadPart
-
549 KeQueryInterruptTime();
551 /* Return the current state */
552 BasicInfo
->SignalState
= KeReadStateTimer(&Timer
->KeTimer
);
554 /* Return the buffer length if requested */
555 if (ReturnLength
) *ReturnLength
= sizeof(TIMER_BASIC_INFORMATION
);
557 _SEH2_EXCEPT(ExSystemExceptionFilter())
559 /* Get the exception code */
560 Status
= _SEH2_GetExceptionCode();
564 /* Dereference Object */
565 ObDereferenceObject(Timer
);
574 NtSetTimer(IN HANDLE TimerHandle
,
575 IN PLARGE_INTEGER DueTime
,
576 IN PTIMER_APC_ROUTINE TimerApcRoutine OPTIONAL
,
577 IN PVOID TimerContext OPTIONAL
,
578 IN BOOLEAN WakeTimer
,
579 IN LONG Period OPTIONAL
,
580 OUT PBOOLEAN PreviousState OPTIONAL
)
585 KPROCESSOR_MODE PreviousMode
= ExGetPreviousMode();
586 PETHREAD Thread
= PsGetCurrentThread();
587 LARGE_INTEGER TimerDueTime
;
588 PETHREAD TimerThread
;
589 ULONG DerefsToDo
= 1;
590 NTSTATUS Status
= STATUS_SUCCESS
;
593 /* Check for a valid Period */
594 if (Period
< 0) return STATUS_INVALID_PARAMETER_6
;
596 /* Check if we need to probe */
597 if (PreviousMode
!= KernelMode
)
601 /* Probe and capture the due time */
602 TimerDueTime
= ProbeForReadLargeInteger(DueTime
);
604 /* Probe the state pointer if one was passed */
605 if (PreviousState
) ProbeForWriteBoolean(PreviousState
);
607 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER
)
609 /* Return the exception code */
610 _SEH2_YIELD(return _SEH2_GetExceptionCode());
616 /* Capture the time directly */
617 TimerDueTime
= *DueTime
;
620 /* Get the Timer Object */
621 Status
= ObReferenceObjectByHandle(TimerHandle
,
629 * Tell the user we don't support Wake Timers...
630 * when we have the ability to use/detect the Power Management
631 * functionality required to support them, make this check dependent
632 * on the actual PM capabilities
634 if (WakeTimer
) Status
= STATUS_TIMER_RESUME_IGNORED
;
637 if (NT_SUCCESS(Status
))
640 KeAcquireSpinLock(&Timer
->Lock
, &OldIrql
);
642 /* Cancel Running Timer */
643 if (Timer
->ApcAssociated
)
645 /* Get the Thread. */
646 TimerThread
= CONTAINING_RECORD(Timer
->TimerApc
.Thread
,
650 /* Lock its active list */
651 KeAcquireSpinLockAtDpcLevel(&TimerThread
->ActiveTimerListLock
);
654 RemoveEntryList(&TimerThread
->ActiveTimerListHead
);
655 Timer
->ApcAssociated
= FALSE
;
657 /* Unlock the list */
658 KeReleaseSpinLockFromDpcLevel(&TimerThread
->ActiveTimerListLock
);
660 /* Cancel the Timer */
661 KeCancelTimer(&Timer
->KeTimer
);
662 KeRemoveQueueDpc(&Timer
->TimerDpc
);
663 if (KeRemoveQueueApc(&Timer
->TimerApc
)) DerefsToDo
++;
668 /* If timer was disabled, we still need to cancel it */
669 KeCancelTimer(&Timer
->KeTimer
);
673 State
= KeReadStateTimer(&Timer
->KeTimer
);
675 /* Handle Wake Timers */
676 Timer
->WakeTimer
= WakeTimer
;
677 KeAcquireSpinLockAtDpcLevel(&ExpWakeListLock
);
678 if ((WakeTimer
) && !(Timer
->WakeTimerListEntry
.Flink
))
680 /* Insert it into the list */
681 InsertTailList(&ExpWakeList
, &Timer
->WakeTimerListEntry
);
683 else if (!(WakeTimer
) && (Timer
->WakeTimerListEntry
.Flink
))
685 /* Remove it from the list */
686 RemoveEntryList(&Timer
->WakeTimerListEntry
);
687 Timer
->WakeTimerListEntry
.Flink
= NULL
;
689 KeReleaseSpinLockFromDpcLevel(&ExpWakeListLock
);
691 /* Set up the APC Routine if specified */
692 Timer
->Period
= Period
;
695 /* Initialize the APC */
696 KeInitializeApc(&Timer
->TimerApc
,
698 CurrentApcEnvironment
,
699 ExpTimerApcKernelRoutine
,
700 (PKRUNDOWN_ROUTINE
)NULL
,
701 (PKNORMAL_ROUTINE
)TimerApcRoutine
,
705 /* Lock the Thread's Active List and Insert */
706 KeAcquireSpinLockAtDpcLevel(&Thread
->ActiveTimerListLock
);
707 InsertTailList(&Thread
->ActiveTimerListHead
,
708 &Timer
->ActiveTimerListEntry
);
709 Timer
->ApcAssociated
= TRUE
;
710 KeReleaseSpinLockFromDpcLevel(&Thread
->ActiveTimerListLock
);
712 /* One less dereference to do */
716 /* Enable and Set the Timer */
717 KeSetTimerEx(&Timer
->KeTimer
,
720 TimerApcRoutine
? &Timer
->TimerDpc
: NULL
);
722 /* Unlock the Timer */
723 KeReleaseSpinLock(&Timer
->Lock
, OldIrql
);
725 /* Dereference if it was previously enabled */
726 if (DerefsToDo
) ObDereferenceObjectEx(Timer
, DerefsToDo
);
728 /* Check if we need to return the State */
734 /* Return the Timer State */
735 *PreviousState
= State
;
737 _SEH2_EXCEPT(ExSystemExceptionFilter())
745 /* Return to Caller */