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
;
228 /* Create the Timer Object Type */
229 RtlZeroMemory(&ObjectTypeInitializer
, sizeof(ObjectTypeInitializer
));
230 RtlInitUnicodeString(&Name
, L
"Timer");
231 ObjectTypeInitializer
.Length
= sizeof(ObjectTypeInitializer
);
232 ObjectTypeInitializer
.InvalidAttributes
= OBJ_OPENLINK
;
233 ObjectTypeInitializer
.DefaultNonPagedPoolCharge
= sizeof(ETIMER
);
234 ObjectTypeInitializer
.GenericMapping
= ExpTimerMapping
;
235 ObjectTypeInitializer
.PoolType
= NonPagedPool
;
236 ObjectTypeInitializer
.ValidAccessMask
= TIMER_ALL_ACCESS
;
237 ObjectTypeInitializer
.DeleteProcedure
= ExpDeleteTimer
;
238 Status
= ObCreateObjectType(&Name
, &ObjectTypeInitializer
, NULL
, &ExTimerType
);
239 if (!NT_SUCCESS(Status
)) return FALSE
;
241 /* Initialize the Wait List and Lock */
242 KeInitializeSpinLock(&ExpWakeListLock
);
243 InitializeListHead(&ExpWakeList
);
247 /* PUBLIC FUNCTIONS **********************************************************/
251 NtCancelTimer(IN HANDLE TimerHandle
,
252 OUT PBOOLEAN CurrentState OPTIONAL
)
255 KPROCESSOR_MODE PreviousMode
= ExGetPreviousMode();
258 PETHREAD TimerThread
;
259 ULONG DerefsToDo
= 1;
263 /* Check if we need to probe */
264 if ((CurrentState
) && (PreviousMode
!= KernelMode
))
268 /* Make sure the pointer is valid */
269 ProbeForWriteBoolean(CurrentState
);
271 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER
)
273 /* Return the exception code */
274 _SEH2_YIELD(return _SEH2_GetExceptionCode());
279 /* Get the Timer Object */
280 Status
= ObReferenceObjectByHandle(TimerHandle
,
286 if (NT_SUCCESS(Status
))
289 KeAcquireSpinLock(&Timer
->Lock
, &OldIrql
);
291 /* Check if it's enabled */
292 if (Timer
->ApcAssociated
)
294 /* Get the Thread. */
295 TimerThread
= CONTAINING_RECORD(Timer
->TimerApc
.Thread
,
299 /* Lock its active list */
300 KeAcquireSpinLockAtDpcLevel(&TimerThread
->ActiveTimerListLock
);
303 RemoveEntryList(&TimerThread
->ActiveTimerListHead
);
304 Timer
->ApcAssociated
= FALSE
;
306 /* Unlock the list */
307 KeReleaseSpinLockFromDpcLevel(&TimerThread
->ActiveTimerListLock
);
309 /* Cancel the Timer */
310 KeCancelTimer(&Timer
->KeTimer
);
311 KeRemoveQueueDpc(&Timer
->TimerDpc
);
312 if (KeRemoveQueueApc(&Timer
->TimerApc
)) DerefsToDo
++;
317 /* If timer was disabled, we still need to cancel it */
318 KeCancelTimer(&Timer
->KeTimer
);
321 /* Handle a Wake Timer */
322 if (Timer
->WakeTimerListEntry
.Flink
)
324 /* Lock the Wake List */
325 KeAcquireSpinLockAtDpcLevel(&ExpWakeListLock
);
327 /* Check again, since it might've changed before we locked */
328 if (Timer
->WakeTimerListEntry
.Flink
)
330 /* Remove it from the Wait List */
331 RemoveEntryList(&Timer
->WakeTimerListEntry
);
332 Timer
->WakeTimerListEntry
.Flink
= NULL
;
335 /* Release the Wake List */
336 KeReleaseSpinLockFromDpcLevel(&ExpWakeListLock
);
339 /* Unlock the Timer */
340 KeReleaseSpinLock(&Timer
->Lock
, OldIrql
);
342 /* Read the old State */
343 State
= KeReadStateTimer(&Timer
->KeTimer
);
345 /* Dereference the Object */
346 ObDereferenceObjectEx(Timer
, DerefsToDo
);
348 /* Check if caller wants the state */
353 /* Return the Timer State */
354 *CurrentState
= State
;
356 _SEH2_EXCEPT(ExSystemExceptionFilter())
364 /* Return to Caller */
370 NtCreateTimer(OUT PHANDLE TimerHandle
,
371 IN ACCESS_MASK DesiredAccess
,
372 IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL
,
373 IN TIMER_TYPE TimerType
)
377 KPROCESSOR_MODE PreviousMode
= ExGetPreviousMode();
381 /* Check for correct timer type */
382 if ((TimerType
!= NotificationTimer
) &&
383 (TimerType
!= SynchronizationTimer
))
386 return STATUS_INVALID_PARAMETER_4
;
389 /* Check if we need to probe */
390 if (PreviousMode
!= KernelMode
)
394 /* Make sure the pointer is valid */
395 ProbeForWriteHandle(TimerHandle
);
397 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER
)
399 /* Return the exception code */
400 _SEH2_YIELD(return _SEH2_GetExceptionCode());
405 /* Create the Object */
406 Status
= ObCreateObject(PreviousMode
,
415 if (NT_SUCCESS(Status
))
417 /* Initialize the DPC */
418 KeInitializeDpc(&Timer
->TimerDpc
, ExpTimerDpcRoutine
, Timer
);
420 /* Initialize the Kernel Timer */
421 KeInitializeTimerEx(&Timer
->KeTimer
, TimerType
);
423 /* Initialize the timer fields */
424 KeInitializeSpinLock(&Timer
->Lock
);
425 Timer
->ApcAssociated
= FALSE
;
426 Timer
->WakeTimer
= FALSE
;
427 Timer
->WakeTimerListEntry
.Flink
= NULL
;
429 /* Insert the Timer */
430 Status
= ObInsertObject((PVOID
)Timer
,
437 /* Check for success */
438 if (NT_SUCCESS(Status
))
443 /* Return the Timer Handle */
444 *TimerHandle
= hTimer
;
446 _SEH2_EXCEPT(ExSystemExceptionFilter())
454 /* Return to Caller */
460 NtOpenTimer(OUT PHANDLE TimerHandle
,
461 IN ACCESS_MASK DesiredAccess
,
462 IN POBJECT_ATTRIBUTES ObjectAttributes
)
465 KPROCESSOR_MODE PreviousMode
= ExGetPreviousMode();
469 /* Check Parameter Validity */
470 if (PreviousMode
!= KernelMode
)
474 /* Make sure the pointer is valid */
475 ProbeForWriteHandle(TimerHandle
);
477 _SEH2_EXCEPT(ExSystemExceptionFilter())
479 /* Return the exception code */
480 _SEH2_YIELD(return _SEH2_GetExceptionCode());
486 Status
= ObOpenObjectByName(ObjectAttributes
,
493 if (NT_SUCCESS(Status
))
498 /* Return the Timer Handle */
499 *TimerHandle
= hTimer
;
501 _SEH2_EXCEPT(ExSystemExceptionFilter())
508 /* Return to Caller */
514 NtQueryTimer(IN HANDLE TimerHandle
,
515 IN TIMER_INFORMATION_CLASS TimerInformationClass
,
516 OUT PVOID TimerInformation
,
517 IN ULONG TimerInformationLength
,
518 OUT PULONG ReturnLength OPTIONAL
)
521 KPROCESSOR_MODE PreviousMode
= ExGetPreviousMode();
523 PTIMER_BASIC_INFORMATION BasicInfo
= TimerInformation
;
527 Status
= DefaultQueryInfoBufferCheck(TimerInformationClass
,
529 sizeof(ExTimerInfoClass
) /
530 sizeof(ExTimerInfoClass
[0]),
532 TimerInformationLength
,
536 if (!NT_SUCCESS(Status
)) return Status
;
538 /* Get the Timer Object */
539 Status
= ObReferenceObjectByHandle(TimerHandle
,
545 if (NT_SUCCESS(Status
))
547 /* Return the Basic Information */
550 /* Return the remaining time, corrected */
551 BasicInfo
->TimeRemaining
.QuadPart
= Timer
->
552 KeTimer
.DueTime
.QuadPart
-
553 KeQueryInterruptTime();
555 /* Return the current state */
556 BasicInfo
->SignalState
= KeReadStateTimer(&Timer
->KeTimer
);
558 /* Return the buffer length if requested */
559 if (ReturnLength
) *ReturnLength
= sizeof(TIMER_BASIC_INFORMATION
);
561 _SEH2_EXCEPT(ExSystemExceptionFilter())
563 /* Get the exception code */
564 Status
= _SEH2_GetExceptionCode();
568 /* Dereference Object */
569 ObDereferenceObject(Timer
);
578 NtSetTimer(IN HANDLE TimerHandle
,
579 IN PLARGE_INTEGER DueTime
,
580 IN PTIMER_APC_ROUTINE TimerApcRoutine OPTIONAL
,
581 IN PVOID TimerContext OPTIONAL
,
582 IN BOOLEAN WakeTimer
,
583 IN LONG Period OPTIONAL
,
584 OUT PBOOLEAN PreviousState OPTIONAL
)
589 KPROCESSOR_MODE PreviousMode
= ExGetPreviousMode();
590 PETHREAD Thread
= PsGetCurrentThread();
591 LARGE_INTEGER TimerDueTime
;
592 PETHREAD TimerThread
;
593 ULONG DerefsToDo
= 1;
594 NTSTATUS Status
= STATUS_SUCCESS
;
597 /* Check for a valid Period */
598 if (Period
< 0) return STATUS_INVALID_PARAMETER_6
;
600 /* Check if we need to probe */
601 if (PreviousMode
!= KernelMode
)
605 /* Probe and capture the due time */
606 TimerDueTime
= ProbeForReadLargeInteger(DueTime
);
608 /* Probe the state pointer if one was passed */
609 if (PreviousState
) ProbeForWriteBoolean(PreviousState
);
611 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER
)
613 /* Return the exception code */
614 _SEH2_YIELD(return _SEH2_GetExceptionCode());
620 /* Capture the time directly */
621 TimerDueTime
= *DueTime
;
624 /* Get the Timer Object */
625 Status
= ObReferenceObjectByHandle(TimerHandle
,
633 * Tell the user we don't support Wake Timers...
634 * when we have the ability to use/detect the Power Management
635 * functionality required to support them, make this check dependent
636 * on the actual PM capabilities
638 if (WakeTimer
) Status
= STATUS_TIMER_RESUME_IGNORED
;
641 if (NT_SUCCESS(Status
))
644 KeAcquireSpinLock(&Timer
->Lock
, &OldIrql
);
646 /* Cancel Running Timer */
647 if (Timer
->ApcAssociated
)
649 /* Get the Thread. */
650 TimerThread
= CONTAINING_RECORD(Timer
->TimerApc
.Thread
,
654 /* Lock its active list */
655 KeAcquireSpinLockAtDpcLevel(&TimerThread
->ActiveTimerListLock
);
658 RemoveEntryList(&TimerThread
->ActiveTimerListHead
);
659 Timer
->ApcAssociated
= FALSE
;
661 /* Unlock the list */
662 KeReleaseSpinLockFromDpcLevel(&TimerThread
->ActiveTimerListLock
);
664 /* Cancel the Timer */
665 KeCancelTimer(&Timer
->KeTimer
);
666 KeRemoveQueueDpc(&Timer
->TimerDpc
);
667 if (KeRemoveQueueApc(&Timer
->TimerApc
)) DerefsToDo
++;
672 /* If timer was disabled, we still need to cancel it */
673 KeCancelTimer(&Timer
->KeTimer
);
677 State
= KeReadStateTimer(&Timer
->KeTimer
);
679 /* Handle Wake Timers */
680 Timer
->WakeTimer
= WakeTimer
;
681 KeAcquireSpinLockAtDpcLevel(&ExpWakeListLock
);
682 if ((WakeTimer
) && !(Timer
->WakeTimerListEntry
.Flink
))
684 /* Insert it into the list */
685 InsertTailList(&ExpWakeList
, &Timer
->WakeTimerListEntry
);
687 else if (!(WakeTimer
) && (Timer
->WakeTimerListEntry
.Flink
))
689 /* Remove it from the list */
690 RemoveEntryList(&Timer
->WakeTimerListEntry
);
691 Timer
->WakeTimerListEntry
.Flink
= NULL
;
693 KeReleaseSpinLockFromDpcLevel(&ExpWakeListLock
);
695 /* Set up the APC Routine if specified */
696 Timer
->Period
= Period
;
699 /* Initialize the APC */
700 KeInitializeApc(&Timer
->TimerApc
,
702 CurrentApcEnvironment
,
703 ExpTimerApcKernelRoutine
,
704 (PKRUNDOWN_ROUTINE
)NULL
,
705 (PKNORMAL_ROUTINE
)TimerApcRoutine
,
709 /* Lock the Thread's Active List and Insert */
710 KeAcquireSpinLockAtDpcLevel(&Thread
->ActiveTimerListLock
);
711 InsertTailList(&Thread
->ActiveTimerListHead
,
712 &Timer
->ActiveTimerListEntry
);
713 Timer
->ApcAssociated
= TRUE
;
714 KeReleaseSpinLockFromDpcLevel(&Thread
->ActiveTimerListLock
);
716 /* One less dereference to do */
720 /* Enable and Set the Timer */
721 KeSetTimerEx(&Timer
->KeTimer
,
724 TimerApcRoutine
? &Timer
->TimerDpc
: NULL
);
726 /* Unlock the Timer */
727 KeReleaseSpinLock(&Timer
->Lock
, OldIrql
);
729 /* Dereference if it was previously enabled */
730 if (DerefsToDo
) ObDereferenceObjectEx(Timer
, DerefsToDo
);
732 /* Check if we need to return the State */
738 /* Return the Timer State */
739 *PreviousState
= State
;
741 _SEH2_EXCEPT(ExSystemExceptionFilter())
749 /* Return to Caller */