2 * COPYRIGHT: See COPYING in the top level directory
3 * PROJECT: ReactOS kernel
4 * FILE: ntoskrnl/ex/timer.c
5 * PURPOSE: Executive Timer Implementation
7 * PROGRAMMERS: Alex Ionescu (alex@relsoft.net)
8 * David Welch (welch@mcmail.com)
11 /* INCLUDES *****************************************************************/
15 #include <internal/debug.h>
17 /* TYPES ********************************************************************/
19 /* Executive Timer Object */
20 typedef struct _ETIMER
25 LIST_ENTRY ActiveTimerListEntry
;
27 BOOLEAN ApcAssociated
;
29 LIST_ENTRY WakeTimerListEntry
;
32 /* GLOBALS ******************************************************************/
34 /* Timer Object Type */
35 POBJECT_TYPE ExTimerType
= NULL
;
37 KSPIN_LOCK ExpWakeListLock
;
38 LIST_ENTRY ExpWakeList
;
41 static GENERIC_MAPPING ExpTimerMapping
=
43 STANDARD_RIGHTS_READ
| TIMER_QUERY_STATE
,
44 STANDARD_RIGHTS_WRITE
| TIMER_MODIFY_STATE
,
45 STANDARD_RIGHTS_EXECUTE
| SYNCHRONIZE
,
49 /* Timer Information Classes */
50 static const INFORMATION_CLASS_INFO ExTimerInfoClass
[] =
52 /* TimerBasicInformation */
53 ICI_SQ_SAME( sizeof(TIMER_BASIC_INFORMATION
), sizeof(ULONG
), ICIF_QUERY
),
56 /* FUNCTIONS *****************************************************************/
62 PETHREAD Thread
= PsGetCurrentThread();
64 PLIST_ENTRY CurrentEntry
;
67 /* Lock the Thread's Active Timer List*/
68 KeAcquireSpinLock(&Thread
->ActiveTimerListLock
, &OldIrql
);
70 while (!IsListEmpty(&Thread
->ActiveTimerListHead
))
73 CurrentEntry
= RemoveTailList(&Thread
->ActiveTimerListHead
);
76 Timer
= CONTAINING_RECORD(CurrentEntry
, ETIMER
, ActiveTimerListEntry
);
77 DPRINT("Timer, ThreadList: 0x%p, 0x%p\n", Timer
, Thread
);
79 /* Mark it as deassociated */
80 ASSERT (Timer
->ApcAssociated
);
81 Timer
->ApcAssociated
= FALSE
;
84 KeReleaseSpinLockFromDpcLevel(&Thread
->ActiveTimerListLock
);
87 KeAcquireSpinLockAtDpcLevel(&Timer
->Lock
);
89 /* Cancel the timer and remove its DPC and APC */
90 ASSERT(&Thread
->Tcb
== Timer
->TimerApc
.Thread
);
91 KeCancelTimer(&Timer
->KeTimer
);
92 KeRemoveQueueDpc(&Timer
->TimerDpc
);
93 KeRemoveQueueApc(&Timer
->TimerApc
);
95 /* Unlock the Timer */
96 KeReleaseSpinLock(&Timer
->Lock
, OldIrql
);
99 ObDereferenceObject(Timer
);
102 KeAcquireSpinLock(&Thread
->ActiveTimerListLock
, &OldIrql
);
105 /* Release lock and return */
106 KeReleaseSpinLock(&Thread
->ActiveTimerListLock
, OldIrql
);
112 ExpDeleteTimer(PVOID ObjectBody
)
115 PETIMER Timer
= ObjectBody
;
116 DPRINT("ExpDeleteTimer(Timer: 0x%p)\n", Timer
);
118 /* Check if it has a Wait List */
119 if (Timer
->WakeTimer
)
121 /* Lock the Wake List */
122 KeAcquireSpinLock(&ExpWakeListLock
, &OldIrql
);
124 /* Check again, since it might've changed before we locked */
125 if (Timer
->WakeTimer
)
127 /* Remove it from the Wait List */
128 DPRINT("Removing wake list\n");
129 RemoveEntryList(&Timer
->WakeTimerListEntry
);
130 Timer
->WakeTimer
= FALSE
;
133 /* Release the Wake List */
134 KeReleaseSpinLock(&ExpWakeListLock
, OldIrql
);
137 /* Tell the Kernel to cancel the Timer */
138 DPRINT("Cancelling Timer\n");
139 KeCancelTimer(&Timer
->KeTimer
);
144 ExpTimerDpcRoutine(PKDPC Dpc
,
145 PVOID DeferredContext
,
146 PVOID SystemArgument1
,
147 PVOID SystemArgument2
)
152 DPRINT("ExpTimerDpcRoutine(Dpc: 0x%p)\n", Dpc
);
154 /* Get the Timer Object */
155 Timer
= (PETIMER
)DeferredContext
;
158 KeAcquireSpinLock(&Timer
->Lock
, &OldIrql
);
161 if(Timer
->ApcAssociated
)
163 DPRINT("Queuing APC\n");
164 KeInsertQueueApc(&Timer
->TimerApc
,
170 /* Release the Timer */
171 KeReleaseSpinLock(&Timer
->Lock
, OldIrql
);
178 ExpTimerApcKernelRoutine(PKAPC Apc
,
179 PKNORMAL_ROUTINE
* NormalRoutine
,
180 PVOID
* NormalContext
,
181 PVOID
* SystemArgument1
,
182 PVOID
* SystemArguemnt2
)
186 PETHREAD CurrentThread
= PsGetCurrentThread();
188 /* We need to find out which Timer we are */
189 Timer
= CONTAINING_RECORD(Apc
, ETIMER
, TimerApc
);
190 DPRINT("ExpTimerApcKernelRoutine(Apc: 0x%p. Timer: 0x%p)\n", Apc
, Timer
);
193 KeAcquireSpinLock(&Timer
->Lock
, &OldIrql
);
195 /* Lock the Thread's Active Timer List*/
196 KeAcquireSpinLockAtDpcLevel(&CurrentThread
->ActiveTimerListLock
);
199 * Make sure that the Timer is still valid, and that it belongs to this thread
200 * Remove it if it's not periodic
202 if ((Timer
->ApcAssociated
) &&
203 (&CurrentThread
->Tcb
== Timer
->TimerApc
.Thread
) &&
204 (!Timer
->KeTimer
.Period
))
206 /* Remove it from the Active Timers List */
207 DPRINT("Removing Timer\n");
208 RemoveEntryList(&Timer
->ActiveTimerListEntry
);
211 Timer
->ApcAssociated
= FALSE
;
213 /* Release spinlocks */
214 KeReleaseSpinLockFromDpcLevel(&CurrentThread
->ActiveTimerListLock
);
215 KeReleaseSpinLock(&Timer
->Lock
, OldIrql
);
217 /* Dereference the Timer Object */
218 ObDereferenceObject(Timer
);
222 /* Release spinlocks */
223 KeReleaseSpinLockFromDpcLevel(&CurrentThread
->ActiveTimerListLock
);
224 KeReleaseSpinLock(&Timer
->Lock
, OldIrql
);
230 ExpInitializeTimerImplementation(VOID
)
232 OBJECT_TYPE_INITIALIZER ObjectTypeInitializer
;
235 DPRINT("Creating Timer Object Type\n");
237 /* Create the Event Pair Object Type */
238 RtlZeroMemory(&ObjectTypeInitializer
, sizeof(ObjectTypeInitializer
));
239 RtlInitUnicodeString(&Name
, L
"Timer");
240 ObjectTypeInitializer
.Length
= sizeof(ObjectTypeInitializer
);
241 ObjectTypeInitializer
.InvalidAttributes
= OBJ_OPENLINK
;
242 ObjectTypeInitializer
.DefaultNonPagedPoolCharge
= sizeof(ETIMER
);
243 ObjectTypeInitializer
.GenericMapping
= ExpTimerMapping
;
244 ObjectTypeInitializer
.PoolType
= NonPagedPool
;
245 ObjectTypeInitializer
.ValidAccessMask
= TIMER_ALL_ACCESS
;
246 ObjectTypeInitializer
.DeleteProcedure
= ExpDeleteTimer
;
247 ObpCreateTypeObject(&ObjectTypeInitializer
, &Name
, &ExTimerType
);
249 /* Initialize the Wait List and Lock */
250 KeInitializeSpinLock(&ExpWakeListLock
);
251 InitializeListHead(&ExpWakeList
);
256 NtCancelTimer(IN HANDLE TimerHandle
,
257 OUT PBOOLEAN CurrentState OPTIONAL
)
260 KPROCESSOR_MODE PreviousMode
= ExGetPreviousMode();
263 PETHREAD TimerThread
;
264 BOOLEAN KillTimer
= FALSE
;
265 NTSTATUS Status
= STATUS_SUCCESS
;
267 DPRINT("NtCancelTimer(0x%p, 0x%x)\n", TimerHandle
, CurrentState
);
269 /* Check Parameter Validity */
270 if(CurrentState
&& PreviousMode
!= KernelMode
)
274 ProbeForWrite(CurrentState
,
278 _SEH_EXCEPT(_SEH_ExSystemExceptionFilter
)
280 Status
= _SEH_GetExceptionCode();
284 if(!NT_SUCCESS(Status
)) return Status
;
287 /* Get the Timer Object */
288 Status
= ObReferenceObjectByHandle(TimerHandle
,
295 /* Check for success */
296 if(NT_SUCCESS(Status
))
298 DPRINT("Timer Referenced: 0x%p\n", Timer
);
301 KeAcquireSpinLock(&Timer
->Lock
, &OldIrql
);
303 /* Check if it's enabled */
304 if (Timer
->ApcAssociated
)
307 * First, remove it from the Thread's Active List
310 TimerThread
= CONTAINING_RECORD(Timer
->TimerApc
.Thread
, ETHREAD
, Tcb
);
311 DPRINT("Removing from Thread: 0x%p\n", TimerThread
);
313 /* Lock its active list */
314 KeAcquireSpinLockAtDpcLevel(&TimerThread
->ActiveTimerListLock
);
317 RemoveEntryList(&TimerThread
->ActiveTimerListHead
);
319 /* Unlock the list */
320 KeReleaseSpinLockFromDpcLevel(&TimerThread
->ActiveTimerListLock
);
322 /* Cancel the Timer */
323 KeCancelTimer(&Timer
->KeTimer
);
324 KeRemoveQueueDpc(&Timer
->TimerDpc
);
325 KeRemoveQueueApc(&Timer
->TimerApc
);
326 Timer
->ApcAssociated
= FALSE
;
331 /* If timer was disabled, we still need to cancel it */
332 DPRINT("APC was not Associated. Cancelling Timer\n");
333 KeCancelTimer(&Timer
->KeTimer
);
336 /* Handle a Wake Timer */
337 if (Timer
->WakeTimer
)
339 /* Lock the Wake List */
340 KeAcquireSpinLockAtDpcLevel(&ExpWakeListLock
);
342 /* Check again, since it might've changed before we locked */
343 if (Timer
->WakeTimer
)
345 /* Remove it from the Wait List */
346 DPRINT("Removing wake list\n");
347 RemoveEntryList(&Timer
->WakeTimerListEntry
);
348 Timer
->WakeTimer
= FALSE
;
351 /* Release the Wake List */
352 KeReleaseSpinLockFromDpcLevel(&ExpWakeListLock
);
355 /* Unlock the Timer */
356 KeReleaseSpinLock(&Timer
->Lock
, OldIrql
);
358 /* Read the old State */
359 State
= KeReadStateTimer(&Timer
->KeTimer
);
361 /* Dereference the Object */
362 ObDereferenceObject(Timer
);
364 /* Dereference if it was previously enabled */
365 if (KillTimer
) ObDereferenceObject(Timer
);
366 DPRINT1("Timer disabled\n");
368 /* Make sure it's safe to write to the handle */
373 *CurrentState
= State
;
375 _SEH_EXCEPT(_SEH_ExSystemExceptionFilter
)
377 Status
= _SEH_GetExceptionCode();
383 /* Return to Caller */
389 NtCreateTimer(OUT PHANDLE TimerHandle
,
390 IN ACCESS_MASK DesiredAccess
,
391 IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL
,
392 IN TIMER_TYPE TimerType
)
396 KPROCESSOR_MODE PreviousMode
= ExGetPreviousMode();
397 NTSTATUS Status
= STATUS_SUCCESS
;
399 DPRINT("NtCreateTimer(Handle: 0x%p, Type: %d)\n", TimerHandle
, TimerType
);
401 /* Check Parameter Validity */
402 if (PreviousMode
!= KernelMode
)
406 ProbeForWrite(TimerHandle
,
410 _SEH_EXCEPT(_SEH_ExSystemExceptionFilter
)
412 Status
= _SEH_GetExceptionCode();
416 if(!NT_SUCCESS(Status
)) return Status
;
419 /* Check for correct timer type */
420 if ((TimerType
!= NotificationTimer
) && (TimerType
!= SynchronizationTimer
))
422 DPRINT1("Invalid Timer Type!\n");
423 return STATUS_INVALID_PARAMETER_4
;
426 /* Create the Object */
427 Status
= ObCreateObject(PreviousMode
,
437 /* Check for Success */
438 if(NT_SUCCESS(Status
))
440 /* Initialize the Kernel Timer */
441 DPRINT("Initializing Timer: 0x%p\n", Timer
);
442 KeInitializeTimerEx(&Timer
->KeTimer
, TimerType
);
444 /* Initialize the Timer Lock */
445 KeInitializeSpinLock(&Timer
->Lock
);
447 /* Initialize the DPC */
448 KeInitializeDpc(&Timer
->TimerDpc
, ExpTimerDpcRoutine
, Timer
);
450 /* Set Initial State */
451 Timer
->ApcAssociated
= FALSE
;
452 Timer
->WakeTimer
= FALSE
;
454 /* Insert the Timer */
455 Status
= ObInsertObject((PVOID
)Timer
,
461 DPRINT("Timer Inserted\n");
463 /* Make sure it's safe to write to the handle */
466 *TimerHandle
= hTimer
;
468 _SEH_EXCEPT(_SEH_ExSystemExceptionFilter
)
470 Status
= _SEH_GetExceptionCode();
475 /* Return to Caller */
481 NtOpenTimer(OUT PHANDLE TimerHandle
,
482 IN ACCESS_MASK DesiredAccess
,
483 IN POBJECT_ATTRIBUTES ObjectAttributes
)
486 KPROCESSOR_MODE PreviousMode
= ExGetPreviousMode();
487 NTSTATUS Status
= STATUS_SUCCESS
;
489 DPRINT("NtOpenTimer(TimerHandle: 0x%p)\n", TimerHandle
);
491 /* Check Parameter Validity */
492 if (PreviousMode
!= KernelMode
)
496 ProbeForWrite(TimerHandle
,
500 _SEH_EXCEPT(_SEH_ExSystemExceptionFilter
)
502 Status
= _SEH_GetExceptionCode();
506 if(!NT_SUCCESS(Status
)) return Status
;
510 Status
= ObOpenObjectByName(ObjectAttributes
,
518 /* Check for success */
519 if(NT_SUCCESS(Status
))
521 /* Make sure it's safe to write to the handle */
524 *TimerHandle
= hTimer
;
526 _SEH_EXCEPT(_SEH_ExSystemExceptionFilter
)
528 Status
= _SEH_GetExceptionCode();
533 /* Return to Caller */
540 NtQueryTimer(IN HANDLE TimerHandle
,
541 IN TIMER_INFORMATION_CLASS TimerInformationClass
,
542 OUT PVOID TimerInformation
,
543 IN ULONG TimerInformationLength
,
544 OUT PULONG ReturnLength OPTIONAL
)
547 KPROCESSOR_MODE PreviousMode
= ExGetPreviousMode();
548 NTSTATUS Status
= STATUS_SUCCESS
;
549 PTIMER_BASIC_INFORMATION BasicInfo
= (PTIMER_BASIC_INFORMATION
)TimerInformation
;
551 DPRINT("NtQueryTimer(TimerHandle: 0x%p, Class: %d)\n", TimerHandle
, TimerInformationClass
);
554 DefaultQueryInfoBufferCheck(TimerInformationClass
,
557 TimerInformationLength
,
561 if(!NT_SUCCESS(Status
))
563 DPRINT1("NtQueryTimer() failed, Status: 0x%x\n", Status
);
567 /* Get the Timer Object */
568 Status
= ObReferenceObjectByHandle(TimerHandle
,
575 /* Check for Success */
576 if(NT_SUCCESS(Status
))
578 /* Return the Basic Information */
581 /* Return the remaining time, corrected */
582 BasicInfo
->TimeRemaining
.QuadPart
= Timer
->KeTimer
.DueTime
.QuadPart
-
583 KeQueryInterruptTime();
585 /* Return the current state */
586 BasicInfo
->SignalState
= KeReadStateTimer(&Timer
->KeTimer
);
588 /* Return the buffer length if requested */
589 if(ReturnLength
!= NULL
) *ReturnLength
= sizeof(TIMER_BASIC_INFORMATION
);
591 DPRINT("Returning Information for Timer: 0x%p. Time Remaining: %I64x\n",
592 Timer
, BasicInfo
->TimeRemaining
.QuadPart
);
594 _SEH_EXCEPT(_SEH_ExSystemExceptionFilter
)
596 Status
= _SEH_GetExceptionCode();
600 /* Dereference Object */
601 ObDereferenceObject(Timer
);
610 NtSetTimer(IN HANDLE TimerHandle
,
611 IN PLARGE_INTEGER DueTime
,
612 IN PTIMER_APC_ROUTINE TimerApcRoutine OPTIONAL
,
613 IN PVOID TimerContext OPTIONAL
,
614 IN BOOLEAN WakeTimer
,
615 IN LONG Period OPTIONAL
,
616 OUT PBOOLEAN PreviousState OPTIONAL
)
621 KPROCESSOR_MODE PreviousMode
= ExGetPreviousMode();
622 PETHREAD CurrentThread
= PsGetCurrentThread();
623 LARGE_INTEGER TimerDueTime
;
624 PETHREAD TimerThread
;
625 BOOLEAN KillTimer
= FALSE
;
626 NTSTATUS Status
= STATUS_SUCCESS
;
628 DPRINT("NtSetTimer(TimerHandle: 0x%p, DueTime: %I64x, Apc: 0x%p, Period: %d)\n",
629 TimerHandle
, DueTime
->QuadPart
, TimerApcRoutine
, Period
);
631 /* Check Parameter Validity */
632 if (PreviousMode
!= KernelMode
)
636 ProbeForRead(DueTime
,
637 sizeof(LARGE_INTEGER
),
639 TimerDueTime
= *DueTime
;
643 ProbeForWrite(PreviousState
,
648 _SEH_EXCEPT(_SEH_ExSystemExceptionFilter
)
650 Status
= _SEH_GetExceptionCode();
654 if(!NT_SUCCESS(Status
)) return Status
;
657 /* Check for a valid Period */
660 DPRINT1("Invalid Period for timer\n");
661 return STATUS_INVALID_PARAMETER_6
;
664 /* Get the Timer Object */
665 Status
= ObReferenceObjectByHandle(TimerHandle
,
673 * Tell the user we don't support Wake Timers...
674 * when we have the ability to use/detect the Power Management
675 * functionatliy required to support them, make this check dependent
676 * on the actual PM capabilities
678 if (WakeTimer
) Status
= STATUS_TIMER_RESUME_IGNORED
;
681 if (NT_SUCCESS(Status
))
684 DPRINT("Timer Referencced: 0x%p\n", Timer
);
685 KeAcquireSpinLock(&Timer
->Lock
, &OldIrql
);
687 /* Cancel Running Timer */
688 if (Timer
->ApcAssociated
)
691 * First, remove it from the Thread's Active List
694 TimerThread
= CONTAINING_RECORD(Timer
->TimerApc
.Thread
, ETHREAD
, Tcb
);
695 DPRINT("Thread already running. Removing from Thread: 0x%p\n", TimerThread
);
697 /* Lock its active list */
698 KeAcquireSpinLockAtDpcLevel(&TimerThread
->ActiveTimerListLock
);
701 RemoveEntryList(&TimerThread
->ActiveTimerListHead
);
703 /* Unlock the list */
704 KeReleaseSpinLockFromDpcLevel(&TimerThread
->ActiveTimerListLock
);
706 /* Cancel the Timer */
707 KeCancelTimer(&Timer
->KeTimer
);
708 KeRemoveQueueDpc(&Timer
->TimerDpc
);
709 KeRemoveQueueApc(&Timer
->TimerApc
);
710 Timer
->ApcAssociated
= FALSE
;
715 /* If timer was disabled, we still need to cancel it */
716 DPRINT("No APCs. Simply cancelling\n");
717 KeCancelTimer(&Timer
->KeTimer
);
721 State
= KeReadStateTimer(&Timer
->KeTimer
);
723 /* Handle Wake Timers */
724 DPRINT("Doing Wake Semantics\n");
725 KeAcquireSpinLockAtDpcLevel(&ExpWakeListLock
);
726 if (WakeTimer
&& !Timer
->WakeTimer
)
728 /* Insert it into the list */
729 Timer
->WakeTimer
= TRUE
;
730 InsertTailList(&ExpWakeList
, &Timer
->WakeTimerListEntry
);
732 else if (!WakeTimer
&& Timer
->WakeTimer
)
734 /* Remove it from the list */
735 RemoveEntryList(&Timer
->WakeTimerListEntry
);
736 Timer
->WakeTimer
= FALSE
;
738 KeReleaseSpinLockFromDpcLevel(&ExpWakeListLock
);
740 /* Set up the APC Routine if specified */
743 /* Initialize the APC */
744 DPRINT("Initializing APC: 0x%p\n", Timer
->TimerApc
);
745 KeInitializeApc(&Timer
->TimerApc
,
747 CurrentApcEnvironment
,
748 &ExpTimerApcKernelRoutine
,
749 (PKRUNDOWN_ROUTINE
)NULL
,
750 (PKNORMAL_ROUTINE
)TimerApcRoutine
,
754 /* Lock the Thread's Active List and Insert */
755 KeAcquireSpinLockAtDpcLevel(&CurrentThread
->ActiveTimerListLock
);
756 InsertTailList(&CurrentThread
->ActiveTimerListHead
,
757 &Timer
->ActiveTimerListEntry
);
758 KeReleaseSpinLockFromDpcLevel(&CurrentThread
->ActiveTimerListLock
);
762 /* Enable and Set the Timer */
763 DPRINT("Setting Kernel Timer\n");
764 KeSetTimerEx(&Timer
->KeTimer
,
767 TimerApcRoutine
? &Timer
->TimerDpc
: 0);
768 Timer
->ApcAssociated
= TimerApcRoutine
? TRUE
: FALSE
;
770 /* Unlock the Timer */
771 KeReleaseSpinLock(&Timer
->Lock
, OldIrql
);
773 /* Dereference if it was previously enabled */
774 if (!TimerApcRoutine
) ObDereferenceObject(Timer
);
775 if (KillTimer
) ObDereferenceObject(Timer
);
776 DPRINT("Finished Setting the Timer\n");
778 /* Make sure it's safe to write to the handle */
779 if(PreviousState
!= NULL
)
783 *PreviousState
= State
;
785 _SEH_EXCEPT(_SEH_ExSystemExceptionFilter
)
787 Status
= _SEH_GetExceptionCode();
793 /* Return to Caller */