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
);
136 _Function_class_(KDEFERRED_ROUTINE
)
139 ExpTimerDpcRoutine(IN PKDPC Dpc
,
140 IN PVOID DeferredContext
,
141 IN PVOID SystemArgument1
,
142 IN PVOID SystemArgument2
)
144 PETIMER Timer
= DeferredContext
;
145 BOOLEAN Inserted
= FALSE
;
147 /* Reference the timer */
148 if (!ObReferenceObjectSafe(Timer
)) return;
151 KeAcquireSpinLockAtDpcLevel(&Timer
->Lock
);
153 /* Check if the timer is associated */
154 if (Timer
->ApcAssociated
)
157 Inserted
= KeInsertQueueApc(&Timer
->TimerApc
,
163 /* Release the Timer */
164 KeReleaseSpinLockFromDpcLevel(&Timer
->Lock
);
166 /* Dereference it if we couldn't queue the APC */
167 if (!Inserted
) ObDereferenceObject(Timer
);
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
)
180 ULONG DerefsToDo
= 1;
181 PETHREAD Thread
= PsGetCurrentThread();
183 /* We need to find out which Timer we are */
184 Timer
= CONTAINING_RECORD(Apc
, ETIMER
, TimerApc
);
187 KeAcquireSpinLock(&Timer
->Lock
, &OldIrql
);
189 /* Lock the Thread's Active Timer List*/
190 KeAcquireSpinLockAtDpcLevel(&Thread
->ActiveTimerListLock
);
192 /* Make sure that the Timer is valid, and that it belongs to this thread */
193 if ((Timer
->ApcAssociated
) && (&Thread
->Tcb
== Timer
->TimerApc
.Thread
))
195 /* Check if it's not periodic */
198 /* Remove it from the Active Timers List */
199 RemoveEntryList(&Timer
->ActiveTimerListEntry
);
202 Timer
->ApcAssociated
= FALSE
;
208 /* Clear the normal routine */
209 *NormalRoutine
= NULL
;
213 KeReleaseSpinLockFromDpcLevel(&Thread
->ActiveTimerListLock
);
214 KeReleaseSpinLock(&Timer
->Lock
, OldIrql
);
216 /* Dereference as needed */
217 ObDereferenceObjectEx(Timer
, DerefsToDo
);
223 ExpInitializeTimerImplementation(VOID
)
225 OBJECT_TYPE_INITIALIZER ObjectTypeInitializer
;
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
;
242 /* Initialize the Wait List and Lock */
243 KeInitializeSpinLock(&ExpWakeListLock
);
244 InitializeListHead(&ExpWakeList
);
248 /* PUBLIC FUNCTIONS **********************************************************/
252 NtCancelTimer(IN HANDLE TimerHandle
,
253 OUT PBOOLEAN CurrentState OPTIONAL
)
256 KPROCESSOR_MODE PreviousMode
= ExGetPreviousMode();
259 PETHREAD TimerThread
;
260 ULONG DerefsToDo
= 1;
264 /* Check if we need to probe */
265 if ((CurrentState
) && (PreviousMode
!= KernelMode
))
269 /* Make sure the pointer is valid */
270 ProbeForWriteBoolean(CurrentState
);
272 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER
)
274 /* Return the exception code */
275 _SEH2_YIELD(return _SEH2_GetExceptionCode());
280 /* Get the Timer Object */
281 Status
= ObReferenceObjectByHandle(TimerHandle
,
287 if (NT_SUCCESS(Status
))
290 KeAcquireSpinLock(&Timer
->Lock
, &OldIrql
);
292 /* Check if it's enabled */
293 if (Timer
->ApcAssociated
)
295 /* Get the Thread. */
296 TimerThread
= CONTAINING_RECORD(Timer
->TimerApc
.Thread
,
300 /* Lock its active list */
301 KeAcquireSpinLockAtDpcLevel(&TimerThread
->ActiveTimerListLock
);
304 RemoveEntryList(&TimerThread
->ActiveTimerListHead
);
305 Timer
->ApcAssociated
= FALSE
;
307 /* Unlock the list */
308 KeReleaseSpinLockFromDpcLevel(&TimerThread
->ActiveTimerListLock
);
310 /* Cancel the Timer */
311 KeCancelTimer(&Timer
->KeTimer
);
312 KeRemoveQueueDpc(&Timer
->TimerDpc
);
313 if (KeRemoveQueueApc(&Timer
->TimerApc
)) DerefsToDo
++;
318 /* If timer was disabled, we still need to cancel it */
319 KeCancelTimer(&Timer
->KeTimer
);
322 /* Handle a Wake Timer */
323 if (Timer
->WakeTimerListEntry
.Flink
)
325 /* Lock the Wake List */
326 KeAcquireSpinLockAtDpcLevel(&ExpWakeListLock
);
328 /* Check again, since it might've changed before we locked */
329 if (Timer
->WakeTimerListEntry
.Flink
)
331 /* Remove it from the Wait List */
332 RemoveEntryList(&Timer
->WakeTimerListEntry
);
333 Timer
->WakeTimerListEntry
.Flink
= NULL
;
336 /* Release the Wake List */
337 KeReleaseSpinLockFromDpcLevel(&ExpWakeListLock
);
340 /* Unlock the Timer */
341 KeReleaseSpinLock(&Timer
->Lock
, OldIrql
);
343 /* Read the old State */
344 State
= KeReadStateTimer(&Timer
->KeTimer
);
346 /* Dereference the Object */
347 ObDereferenceObjectEx(Timer
, DerefsToDo
);
349 /* Check if caller wants the state */
354 /* Return the Timer State */
355 *CurrentState
= State
;
357 _SEH2_EXCEPT(ExSystemExceptionFilter())
366 /* Return to Caller */
372 NtCreateTimer(OUT PHANDLE TimerHandle
,
373 IN ACCESS_MASK DesiredAccess
,
374 IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL
,
375 IN TIMER_TYPE TimerType
)
379 KPROCESSOR_MODE PreviousMode
= ExGetPreviousMode();
383 /* Check for correct timer type */
384 if ((TimerType
!= NotificationTimer
) &&
385 (TimerType
!= SynchronizationTimer
))
388 return STATUS_INVALID_PARAMETER_4
;
391 /* Check if we need to probe */
392 if (PreviousMode
!= KernelMode
)
396 /* Make sure the pointer is valid */
397 ProbeForWriteHandle(TimerHandle
);
399 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER
)
401 /* Return the exception code */
402 _SEH2_YIELD(return _SEH2_GetExceptionCode());
407 /* Create the Object */
408 Status
= ObCreateObject(PreviousMode
,
417 if (NT_SUCCESS(Status
))
419 /* Initialize the DPC */
420 KeInitializeDpc(&Timer
->TimerDpc
, ExpTimerDpcRoutine
, Timer
);
422 /* Initialize the Kernel Timer */
423 KeInitializeTimerEx(&Timer
->KeTimer
, TimerType
);
425 /* Initialize the timer fields */
426 KeInitializeSpinLock(&Timer
->Lock
);
427 Timer
->ApcAssociated
= FALSE
;
428 Timer
->WakeTimer
= FALSE
;
429 Timer
->WakeTimerListEntry
.Flink
= NULL
;
431 /* Insert the Timer */
432 Status
= ObInsertObject((PVOID
)Timer
,
439 /* Check for success */
440 if (NT_SUCCESS(Status
))
445 /* Return the Timer Handle */
446 *TimerHandle
= hTimer
;
448 _SEH2_EXCEPT(ExSystemExceptionFilter())
457 /* Return to Caller */
463 NtOpenTimer(OUT PHANDLE TimerHandle
,
464 IN ACCESS_MASK DesiredAccess
,
465 IN POBJECT_ATTRIBUTES ObjectAttributes
)
468 KPROCESSOR_MODE PreviousMode
= ExGetPreviousMode();
472 /* Check Parameter Validity */
473 if (PreviousMode
!= KernelMode
)
477 /* Make sure the pointer is valid */
478 ProbeForWriteHandle(TimerHandle
);
480 _SEH2_EXCEPT(ExSystemExceptionFilter())
482 /* Return the exception code */
483 _SEH2_YIELD(return _SEH2_GetExceptionCode());
489 Status
= ObOpenObjectByName(ObjectAttributes
,
496 if (NT_SUCCESS(Status
))
501 /* Return the Timer Handle */
502 *TimerHandle
= hTimer
;
504 _SEH2_EXCEPT(ExSystemExceptionFilter())
512 /* Return to Caller */
518 NtQueryTimer(IN HANDLE TimerHandle
,
519 IN TIMER_INFORMATION_CLASS TimerInformationClass
,
520 OUT PVOID TimerInformation
,
521 IN ULONG TimerInformationLength
,
522 OUT PULONG ReturnLength OPTIONAL
)
525 KPROCESSOR_MODE PreviousMode
= ExGetPreviousMode();
527 PTIMER_BASIC_INFORMATION BasicInfo
= TimerInformation
;
531 Status
= DefaultQueryInfoBufferCheck(TimerInformationClass
,
533 sizeof(ExTimerInfoClass
) /
534 sizeof(ExTimerInfoClass
[0]),
536 TimerInformationLength
,
540 if (!NT_SUCCESS(Status
)) return Status
;
542 /* Get the Timer Object */
543 Status
= ObReferenceObjectByHandle(TimerHandle
,
549 if (NT_SUCCESS(Status
))
551 /* Return the Basic Information */
554 /* Return the remaining time, corrected */
555 BasicInfo
->TimeRemaining
.QuadPart
= Timer
->
556 KeTimer
.DueTime
.QuadPart
-
557 KeQueryInterruptTime();
559 /* Return the current state */
560 BasicInfo
->SignalState
= KeReadStateTimer(&Timer
->KeTimer
);
562 /* Return the buffer length if requested */
563 if (ReturnLength
) *ReturnLength
= sizeof(TIMER_BASIC_INFORMATION
);
565 _SEH2_EXCEPT(ExSystemExceptionFilter())
567 /* Get the exception code */
568 Status
= _SEH2_GetExceptionCode();
572 /* Dereference Object */
573 ObDereferenceObject(Timer
);
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
)
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
;
601 /* Check for a valid Period */
602 if (Period
< 0) return STATUS_INVALID_PARAMETER_6
;
604 /* Check if we need to probe */
605 if (PreviousMode
!= KernelMode
)
609 /* Probe and capture the due time */
610 TimerDueTime
= ProbeForReadLargeInteger(DueTime
);
612 /* Probe the state pointer if one was passed */
613 if (PreviousState
) ProbeForWriteBoolean(PreviousState
);
615 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER
)
617 /* Return the exception code */
618 _SEH2_YIELD(return _SEH2_GetExceptionCode());
624 /* Capture the time directly */
625 TimerDueTime
= *DueTime
;
628 /* Get the Timer Object */
629 Status
= ObReferenceObjectByHandle(TimerHandle
,
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
642 if (WakeTimer
) Status
= STATUS_TIMER_RESUME_IGNORED
;
645 if (NT_SUCCESS(Status
))
648 KeAcquireSpinLock(&Timer
->Lock
, &OldIrql
);
650 /* Cancel Running Timer */
651 if (Timer
->ApcAssociated
)
653 /* Get the Thread. */
654 TimerThread
= CONTAINING_RECORD(Timer
->TimerApc
.Thread
,
658 /* Lock its active list */
659 KeAcquireSpinLockAtDpcLevel(&TimerThread
->ActiveTimerListLock
);
662 RemoveEntryList(&TimerThread
->ActiveTimerListHead
);
663 Timer
->ApcAssociated
= FALSE
;
665 /* Unlock the list */
666 KeReleaseSpinLockFromDpcLevel(&TimerThread
->ActiveTimerListLock
);
668 /* Cancel the Timer */
669 KeCancelTimer(&Timer
->KeTimer
);
670 KeRemoveQueueDpc(&Timer
->TimerDpc
);
671 if (KeRemoveQueueApc(&Timer
->TimerApc
)) DerefsToDo
++;
676 /* If timer was disabled, we still need to cancel it */
677 KeCancelTimer(&Timer
->KeTimer
);
681 State
= KeReadStateTimer(&Timer
->KeTimer
);
683 /* Handle Wake Timers */
684 Timer
->WakeTimer
= WakeTimer
;
685 KeAcquireSpinLockAtDpcLevel(&ExpWakeListLock
);
686 if ((WakeTimer
) && !(Timer
->WakeTimerListEntry
.Flink
))
688 /* Insert it into the list */
689 InsertTailList(&ExpWakeList
, &Timer
->WakeTimerListEntry
);
691 else if (!(WakeTimer
) && (Timer
->WakeTimerListEntry
.Flink
))
693 /* Remove it from the list */
694 RemoveEntryList(&Timer
->WakeTimerListEntry
);
695 Timer
->WakeTimerListEntry
.Flink
= NULL
;
697 KeReleaseSpinLockFromDpcLevel(&ExpWakeListLock
);
699 /* Set up the APC Routine if specified */
700 Timer
->Period
= Period
;
703 /* Initialize the APC */
704 KeInitializeApc(&Timer
->TimerApc
,
706 CurrentApcEnvironment
,
707 ExpTimerApcKernelRoutine
,
708 (PKRUNDOWN_ROUTINE
)NULL
,
709 (PKNORMAL_ROUTINE
)TimerApcRoutine
,
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
);
720 /* One less dereference to do */
724 /* Enable and Set the Timer */
725 KeSetTimerEx(&Timer
->KeTimer
,
728 TimerApcRoutine
? &Timer
->TimerDpc
: NULL
);
730 /* Unlock the Timer */
731 KeReleaseSpinLock(&Timer
->Lock
, OldIrql
);
733 /* Dereference if it was previously enabled */
734 if (DerefsToDo
) ObDereferenceObjectEx(Timer
, DerefsToDo
);
736 /* Check if we need to return the State */
742 /* Return the Timer State */
743 *PreviousState
= State
;
745 _SEH2_EXCEPT(ExSystemExceptionFilter())
754 /* Return to Caller */