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 ProbeForWriteBoolean(CurrentState
);
276 _SEH_EXCEPT(_SEH_ExSystemExceptionFilter
)
278 Status
= _SEH_GetExceptionCode();
282 if(!NT_SUCCESS(Status
)) return Status
;
285 /* Get the Timer Object */
286 Status
= ObReferenceObjectByHandle(TimerHandle
,
293 /* Check for success */
294 if(NT_SUCCESS(Status
))
296 DPRINT("Timer Referenced: 0x%p\n", Timer
);
299 KeAcquireSpinLock(&Timer
->Lock
, &OldIrql
);
301 /* Check if it's enabled */
302 if (Timer
->ApcAssociated
)
305 * First, remove it from the Thread's Active List
308 TimerThread
= CONTAINING_RECORD(Timer
->TimerApc
.Thread
, ETHREAD
, Tcb
);
309 DPRINT("Removing from Thread: 0x%p\n", TimerThread
);
311 /* Lock its active list */
312 KeAcquireSpinLockAtDpcLevel(&TimerThread
->ActiveTimerListLock
);
315 RemoveEntryList(&TimerThread
->ActiveTimerListHead
);
317 /* Unlock the list */
318 KeReleaseSpinLockFromDpcLevel(&TimerThread
->ActiveTimerListLock
);
320 /* Cancel the Timer */
321 KeCancelTimer(&Timer
->KeTimer
);
322 KeRemoveQueueDpc(&Timer
->TimerDpc
);
323 KeRemoveQueueApc(&Timer
->TimerApc
);
324 Timer
->ApcAssociated
= FALSE
;
329 /* If timer was disabled, we still need to cancel it */
330 DPRINT("APC was not Associated. Cancelling Timer\n");
331 KeCancelTimer(&Timer
->KeTimer
);
334 /* Handle a Wake Timer */
335 if (Timer
->WakeTimer
)
337 /* Lock the Wake List */
338 KeAcquireSpinLockAtDpcLevel(&ExpWakeListLock
);
340 /* Check again, since it might've changed before we locked */
341 if (Timer
->WakeTimer
)
343 /* Remove it from the Wait List */
344 DPRINT("Removing wake list\n");
345 RemoveEntryList(&Timer
->WakeTimerListEntry
);
346 Timer
->WakeTimer
= FALSE
;
349 /* Release the Wake List */
350 KeReleaseSpinLockFromDpcLevel(&ExpWakeListLock
);
353 /* Unlock the Timer */
354 KeReleaseSpinLock(&Timer
->Lock
, OldIrql
);
356 /* Read the old State */
357 State
= KeReadStateTimer(&Timer
->KeTimer
);
359 /* Dereference the Object */
360 ObDereferenceObject(Timer
);
362 /* Dereference if it was previously enabled */
363 if (KillTimer
) ObDereferenceObject(Timer
);
364 DPRINT1("Timer disabled\n");
366 /* Make sure it's safe to write to the handle */
371 *CurrentState
= State
;
373 _SEH_EXCEPT(_SEH_ExSystemExceptionFilter
)
375 Status
= _SEH_GetExceptionCode();
381 /* Return to Caller */
387 NtCreateTimer(OUT PHANDLE TimerHandle
,
388 IN ACCESS_MASK DesiredAccess
,
389 IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL
,
390 IN TIMER_TYPE TimerType
)
394 KPROCESSOR_MODE PreviousMode
= ExGetPreviousMode();
395 NTSTATUS Status
= STATUS_SUCCESS
;
397 DPRINT("NtCreateTimer(Handle: 0x%p, Type: %d)\n", TimerHandle
, TimerType
);
399 /* Check Parameter Validity */
400 if (PreviousMode
!= KernelMode
)
404 ProbeForWriteHandle(TimerHandle
);
406 _SEH_EXCEPT(_SEH_ExSystemExceptionFilter
)
408 Status
= _SEH_GetExceptionCode();
412 if(!NT_SUCCESS(Status
)) return Status
;
415 /* Check for correct timer type */
416 if ((TimerType
!= NotificationTimer
) && (TimerType
!= SynchronizationTimer
))
418 DPRINT1("Invalid Timer Type!\n");
419 return STATUS_INVALID_PARAMETER_4
;
422 /* Create the Object */
423 Status
= ObCreateObject(PreviousMode
,
433 /* Check for Success */
434 if(NT_SUCCESS(Status
))
436 /* Initialize the Kernel Timer */
437 DPRINT("Initializing Timer: 0x%p\n", Timer
);
438 KeInitializeTimerEx(&Timer
->KeTimer
, TimerType
);
440 /* Initialize the Timer Lock */
441 KeInitializeSpinLock(&Timer
->Lock
);
443 /* Initialize the DPC */
444 KeInitializeDpc(&Timer
->TimerDpc
, ExpTimerDpcRoutine
, Timer
);
446 /* Set Initial State */
447 Timer
->ApcAssociated
= FALSE
;
448 Timer
->WakeTimer
= FALSE
;
450 /* Insert the Timer */
451 Status
= ObInsertObject((PVOID
)Timer
,
457 DPRINT("Timer Inserted\n");
459 /* Make sure it's safe to write to the handle */
462 *TimerHandle
= hTimer
;
464 _SEH_EXCEPT(_SEH_ExSystemExceptionFilter
)
466 Status
= _SEH_GetExceptionCode();
471 /* Return to Caller */
477 NtOpenTimer(OUT PHANDLE TimerHandle
,
478 IN ACCESS_MASK DesiredAccess
,
479 IN POBJECT_ATTRIBUTES ObjectAttributes
)
482 KPROCESSOR_MODE PreviousMode
= ExGetPreviousMode();
483 NTSTATUS Status
= STATUS_SUCCESS
;
485 DPRINT("NtOpenTimer(TimerHandle: 0x%p)\n", TimerHandle
);
487 /* Check Parameter Validity */
488 if (PreviousMode
!= KernelMode
)
492 ProbeForWriteHandle(TimerHandle
);
494 _SEH_EXCEPT(_SEH_ExSystemExceptionFilter
)
496 Status
= _SEH_GetExceptionCode();
500 if(!NT_SUCCESS(Status
)) return Status
;
504 Status
= ObOpenObjectByName(ObjectAttributes
,
512 /* Check for success */
513 if(NT_SUCCESS(Status
))
515 /* Make sure it's safe to write to the handle */
518 *TimerHandle
= hTimer
;
520 _SEH_EXCEPT(_SEH_ExSystemExceptionFilter
)
522 Status
= _SEH_GetExceptionCode();
527 /* Return to Caller */
534 NtQueryTimer(IN HANDLE TimerHandle
,
535 IN TIMER_INFORMATION_CLASS TimerInformationClass
,
536 OUT PVOID TimerInformation
,
537 IN ULONG TimerInformationLength
,
538 OUT PULONG ReturnLength OPTIONAL
)
541 KPROCESSOR_MODE PreviousMode
= ExGetPreviousMode();
542 NTSTATUS Status
= STATUS_SUCCESS
;
543 PTIMER_BASIC_INFORMATION BasicInfo
= (PTIMER_BASIC_INFORMATION
)TimerInformation
;
545 DPRINT("NtQueryTimer(TimerHandle: 0x%p, Class: %d)\n", TimerHandle
, TimerInformationClass
);
548 Status
= DefaultQueryInfoBufferCheck(TimerInformationClass
,
550 sizeof(ExTimerInfoClass
) / sizeof(ExTimerInfoClass
[0]),
552 TimerInformationLength
,
555 if(!NT_SUCCESS(Status
))
557 DPRINT1("NtQueryTimer() failed, Status: 0x%x\n", Status
);
561 /* Get the Timer Object */
562 Status
= ObReferenceObjectByHandle(TimerHandle
,
569 /* Check for Success */
570 if(NT_SUCCESS(Status
))
572 /* Return the Basic Information */
575 /* Return the remaining time, corrected */
576 BasicInfo
->TimeRemaining
.QuadPart
= Timer
->KeTimer
.DueTime
.QuadPart
-
577 KeQueryInterruptTime();
579 /* Return the current state */
580 BasicInfo
->SignalState
= KeReadStateTimer(&Timer
->KeTimer
);
582 /* Return the buffer length if requested */
583 if(ReturnLength
!= NULL
) *ReturnLength
= sizeof(TIMER_BASIC_INFORMATION
);
585 DPRINT("Returning Information for Timer: 0x%p. Time Remaining: %I64x\n",
586 Timer
, BasicInfo
->TimeRemaining
.QuadPart
);
588 _SEH_EXCEPT(_SEH_ExSystemExceptionFilter
)
590 Status
= _SEH_GetExceptionCode();
594 /* Dereference Object */
595 ObDereferenceObject(Timer
);
604 NtSetTimer(IN HANDLE TimerHandle
,
605 IN PLARGE_INTEGER DueTime
,
606 IN PTIMER_APC_ROUTINE TimerApcRoutine OPTIONAL
,
607 IN PVOID TimerContext OPTIONAL
,
608 IN BOOLEAN WakeTimer
,
609 IN LONG Period OPTIONAL
,
610 OUT PBOOLEAN PreviousState OPTIONAL
)
615 KPROCESSOR_MODE PreviousMode
= ExGetPreviousMode();
616 PETHREAD CurrentThread
= PsGetCurrentThread();
617 LARGE_INTEGER TimerDueTime
;
618 PETHREAD TimerThread
;
619 BOOLEAN KillTimer
= FALSE
;
620 NTSTATUS Status
= STATUS_SUCCESS
;
622 DPRINT("NtSetTimer(TimerHandle: 0x%p, DueTime: %I64x, Apc: 0x%p, Period: %d)\n",
623 TimerHandle
, DueTime
->QuadPart
, TimerApcRoutine
, Period
);
625 /* Check Parameter Validity */
626 if (PreviousMode
!= KernelMode
)
630 TimerDueTime
= ProbeForReadLargeInteger(DueTime
);
634 ProbeForWriteBoolean(PreviousState
);
637 _SEH_EXCEPT(_SEH_ExSystemExceptionFilter
)
639 Status
= _SEH_GetExceptionCode();
643 if(!NT_SUCCESS(Status
)) return Status
;
646 /* Check for a valid Period */
649 DPRINT1("Invalid Period for timer\n");
650 return STATUS_INVALID_PARAMETER_6
;
653 /* Get the Timer Object */
654 Status
= ObReferenceObjectByHandle(TimerHandle
,
662 * Tell the user we don't support Wake Timers...
663 * when we have the ability to use/detect the Power Management
664 * functionatliy required to support them, make this check dependent
665 * on the actual PM capabilities
667 if (WakeTimer
) Status
= STATUS_TIMER_RESUME_IGNORED
;
670 if (NT_SUCCESS(Status
))
673 DPRINT("Timer Referencced: 0x%p\n", Timer
);
674 KeAcquireSpinLock(&Timer
->Lock
, &OldIrql
);
676 /* Cancel Running Timer */
677 if (Timer
->ApcAssociated
)
680 * First, remove it from the Thread's Active List
683 TimerThread
= CONTAINING_RECORD(Timer
->TimerApc
.Thread
, ETHREAD
, Tcb
);
684 DPRINT("Thread already running. Removing from Thread: 0x%p\n", TimerThread
);
686 /* Lock its active list */
687 KeAcquireSpinLockAtDpcLevel(&TimerThread
->ActiveTimerListLock
);
690 RemoveEntryList(&TimerThread
->ActiveTimerListHead
);
692 /* Unlock the list */
693 KeReleaseSpinLockFromDpcLevel(&TimerThread
->ActiveTimerListLock
);
695 /* Cancel the Timer */
696 KeCancelTimer(&Timer
->KeTimer
);
697 KeRemoveQueueDpc(&Timer
->TimerDpc
);
698 KeRemoveQueueApc(&Timer
->TimerApc
);
699 Timer
->ApcAssociated
= FALSE
;
704 /* If timer was disabled, we still need to cancel it */
705 DPRINT("No APCs. Simply cancelling\n");
706 KeCancelTimer(&Timer
->KeTimer
);
710 State
= KeReadStateTimer(&Timer
->KeTimer
);
712 /* Handle Wake Timers */
713 DPRINT("Doing Wake Semantics\n");
714 KeAcquireSpinLockAtDpcLevel(&ExpWakeListLock
);
715 if (WakeTimer
&& !Timer
->WakeTimer
)
717 /* Insert it into the list */
718 Timer
->WakeTimer
= TRUE
;
719 InsertTailList(&ExpWakeList
, &Timer
->WakeTimerListEntry
);
721 else if (!WakeTimer
&& Timer
->WakeTimer
)
723 /* Remove it from the list */
724 RemoveEntryList(&Timer
->WakeTimerListEntry
);
725 Timer
->WakeTimer
= FALSE
;
727 KeReleaseSpinLockFromDpcLevel(&ExpWakeListLock
);
729 /* Set up the APC Routine if specified */
732 /* Initialize the APC */
733 DPRINT("Initializing APC: 0x%p\n", Timer
->TimerApc
);
734 KeInitializeApc(&Timer
->TimerApc
,
736 CurrentApcEnvironment
,
737 &ExpTimerApcKernelRoutine
,
738 (PKRUNDOWN_ROUTINE
)NULL
,
739 (PKNORMAL_ROUTINE
)TimerApcRoutine
,
743 /* Lock the Thread's Active List and Insert */
744 KeAcquireSpinLockAtDpcLevel(&CurrentThread
->ActiveTimerListLock
);
745 InsertTailList(&CurrentThread
->ActiveTimerListHead
,
746 &Timer
->ActiveTimerListEntry
);
747 KeReleaseSpinLockFromDpcLevel(&CurrentThread
->ActiveTimerListLock
);
751 /* Enable and Set the Timer */
752 DPRINT("Setting Kernel Timer\n");
753 KeSetTimerEx(&Timer
->KeTimer
,
756 TimerApcRoutine
? &Timer
->TimerDpc
: 0);
757 Timer
->ApcAssociated
= TimerApcRoutine
? TRUE
: FALSE
;
759 /* Unlock the Timer */
760 KeReleaseSpinLock(&Timer
->Lock
, OldIrql
);
762 /* Dereference if it was previously enabled */
763 if (!TimerApcRoutine
) ObDereferenceObject(Timer
);
764 if (KillTimer
) ObDereferenceObject(Timer
);
765 DPRINT("Finished Setting the Timer\n");
767 /* Make sure it's safe to write to the handle */
768 if(PreviousState
!= NULL
)
772 *PreviousState
= State
;
774 _SEH_EXCEPT(_SEH_ExSystemExceptionFilter
)
776 Status
= _SEH_GetExceptionCode();
782 /* Return to Caller */