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 #if defined (ALLOC_PRAGMA)
18 #pragma alloc_text(INIT, ExpInitializeTimerImplementation)
22 /* TYPES ********************************************************************/
24 /* Executive Timer Object */
25 typedef struct _ETIMER
30 LIST_ENTRY ActiveTimerListEntry
;
32 BOOLEAN ApcAssociated
;
34 LIST_ENTRY WakeTimerListEntry
;
37 /* GLOBALS ******************************************************************/
39 /* Timer Object Type */
40 POBJECT_TYPE ExTimerType
= NULL
;
42 KSPIN_LOCK ExpWakeListLock
;
43 LIST_ENTRY ExpWakeList
;
46 static GENERIC_MAPPING ExpTimerMapping
=
48 STANDARD_RIGHTS_READ
| TIMER_QUERY_STATE
,
49 STANDARD_RIGHTS_WRITE
| TIMER_MODIFY_STATE
,
50 STANDARD_RIGHTS_EXECUTE
| SYNCHRONIZE
,
54 /* Timer Information Classes */
55 static const INFORMATION_CLASS_INFO ExTimerInfoClass
[] =
57 /* TimerBasicInformation */
58 ICI_SQ_SAME( sizeof(TIMER_BASIC_INFORMATION
), sizeof(ULONG
), ICIF_QUERY
),
61 /* FUNCTIONS *****************************************************************/
67 PETHREAD Thread
= PsGetCurrentThread();
69 PLIST_ENTRY CurrentEntry
;
72 /* Lock the Thread's Active Timer List*/
73 KeAcquireSpinLock(&Thread
->ActiveTimerListLock
, &OldIrql
);
75 while (!IsListEmpty(&Thread
->ActiveTimerListHead
))
78 CurrentEntry
= RemoveTailList(&Thread
->ActiveTimerListHead
);
81 Timer
= CONTAINING_RECORD(CurrentEntry
, ETIMER
, ActiveTimerListEntry
);
82 DPRINT("Timer, ThreadList: 0x%p, 0x%p\n", Timer
, Thread
);
84 /* Mark it as deassociated */
85 ASSERT (Timer
->ApcAssociated
);
86 Timer
->ApcAssociated
= FALSE
;
89 KeReleaseSpinLockFromDpcLevel(&Thread
->ActiveTimerListLock
);
92 KeAcquireSpinLockAtDpcLevel(&Timer
->Lock
);
94 /* Cancel the timer and remove its DPC and APC */
95 ASSERT(&Thread
->Tcb
== Timer
->TimerApc
.Thread
);
96 KeCancelTimer(&Timer
->KeTimer
);
97 KeRemoveQueueDpc(&Timer
->TimerDpc
);
98 KeRemoveQueueApc(&Timer
->TimerApc
);
100 /* Unlock the Timer */
101 KeReleaseSpinLock(&Timer
->Lock
, OldIrql
);
104 ObDereferenceObject(Timer
);
107 KeAcquireSpinLock(&Thread
->ActiveTimerListLock
, &OldIrql
);
110 /* Release lock and return */
111 KeReleaseSpinLock(&Thread
->ActiveTimerListLock
, OldIrql
);
117 ExpDeleteTimer(PVOID ObjectBody
)
120 PETIMER Timer
= ObjectBody
;
121 DPRINT("ExpDeleteTimer(Timer: 0x%p)\n", Timer
);
123 /* Check if it has a Wait List */
124 if (Timer
->WakeTimer
)
126 /* Lock the Wake List */
127 KeAcquireSpinLock(&ExpWakeListLock
, &OldIrql
);
129 /* Check again, since it might've changed before we locked */
130 if (Timer
->WakeTimer
)
132 /* Remove it from the Wait List */
133 DPRINT("Removing wake list\n");
134 RemoveEntryList(&Timer
->WakeTimerListEntry
);
135 Timer
->WakeTimer
= FALSE
;
138 /* Release the Wake List */
139 KeReleaseSpinLock(&ExpWakeListLock
, OldIrql
);
142 /* Tell the Kernel to cancel the Timer */
143 DPRINT("Cancelling Timer\n");
144 KeCancelTimer(&Timer
->KeTimer
);
149 ExpTimerDpcRoutine(PKDPC Dpc
,
150 PVOID DeferredContext
,
151 PVOID SystemArgument1
,
152 PVOID SystemArgument2
)
157 DPRINT("ExpTimerDpcRoutine(Dpc: 0x%p)\n", Dpc
);
159 /* Get the Timer Object */
160 Timer
= (PETIMER
)DeferredContext
;
163 KeAcquireSpinLock(&Timer
->Lock
, &OldIrql
);
166 if(Timer
->ApcAssociated
)
168 DPRINT("Queuing APC\n");
169 KeInsertQueueApc(&Timer
->TimerApc
,
175 /* Release the Timer */
176 KeReleaseSpinLock(&Timer
->Lock
, OldIrql
);
183 ExpTimerApcKernelRoutine(PKAPC Apc
,
184 PKNORMAL_ROUTINE
* NormalRoutine
,
185 PVOID
* NormalContext
,
186 PVOID
* SystemArgument1
,
187 PVOID
* SystemArguemnt2
)
191 PETHREAD CurrentThread
= PsGetCurrentThread();
193 /* We need to find out which Timer we are */
194 Timer
= CONTAINING_RECORD(Apc
, ETIMER
, TimerApc
);
195 DPRINT("ExpTimerApcKernelRoutine(Apc: 0x%p. Timer: 0x%p)\n", Apc
, Timer
);
198 KeAcquireSpinLock(&Timer
->Lock
, &OldIrql
);
200 /* Lock the Thread's Active Timer List*/
201 KeAcquireSpinLockAtDpcLevel(&CurrentThread
->ActiveTimerListLock
);
204 * Make sure that the Timer is still valid, and that it belongs to this thread
205 * Remove it if it's not periodic
207 if ((Timer
->ApcAssociated
) &&
208 (&CurrentThread
->Tcb
== Timer
->TimerApc
.Thread
) &&
209 (!Timer
->KeTimer
.Period
))
211 /* Remove it from the Active Timers List */
212 DPRINT("Removing Timer\n");
213 RemoveEntryList(&Timer
->ActiveTimerListEntry
);
216 Timer
->ApcAssociated
= FALSE
;
218 /* Release spinlocks */
219 KeReleaseSpinLockFromDpcLevel(&CurrentThread
->ActiveTimerListLock
);
220 KeReleaseSpinLock(&Timer
->Lock
, OldIrql
);
222 /* Dereference the Timer Object */
223 ObDereferenceObject(Timer
);
227 /* Release spinlocks */
228 KeReleaseSpinLockFromDpcLevel(&CurrentThread
->ActiveTimerListLock
);
229 KeReleaseSpinLock(&Timer
->Lock
, OldIrql
);
235 ExpInitializeTimerImplementation(VOID
)
237 OBJECT_TYPE_INITIALIZER ObjectTypeInitializer
;
240 DPRINT("Creating Timer Object Type\n");
242 /* Create the Event Pair Object Type */
243 RtlZeroMemory(&ObjectTypeInitializer
, sizeof(ObjectTypeInitializer
));
244 RtlInitUnicodeString(&Name
, L
"Timer");
245 ObjectTypeInitializer
.Length
= sizeof(ObjectTypeInitializer
);
246 ObjectTypeInitializer
.InvalidAttributes
= OBJ_OPENLINK
;
247 ObjectTypeInitializer
.DefaultNonPagedPoolCharge
= sizeof(ETIMER
);
248 ObjectTypeInitializer
.GenericMapping
= ExpTimerMapping
;
249 ObjectTypeInitializer
.PoolType
= NonPagedPool
;
250 ObjectTypeInitializer
.ValidAccessMask
= TIMER_ALL_ACCESS
;
251 ObjectTypeInitializer
.DeleteProcedure
= ExpDeleteTimer
;
252 ObpCreateTypeObject(&ObjectTypeInitializer
, &Name
, &ExTimerType
);
254 /* Initialize the Wait List and Lock */
255 KeInitializeSpinLock(&ExpWakeListLock
);
256 InitializeListHead(&ExpWakeList
);
261 NtCancelTimer(IN HANDLE TimerHandle
,
262 OUT PBOOLEAN CurrentState OPTIONAL
)
265 KPROCESSOR_MODE PreviousMode
= ExGetPreviousMode();
268 PETHREAD TimerThread
;
269 BOOLEAN KillTimer
= FALSE
;
270 NTSTATUS Status
= STATUS_SUCCESS
;
272 DPRINT("NtCancelTimer(0x%p, 0x%x)\n", TimerHandle
, CurrentState
);
274 /* Check Parameter Validity */
275 if(CurrentState
&& PreviousMode
!= KernelMode
)
279 ProbeForWriteBoolean(CurrentState
);
281 _SEH_EXCEPT(_SEH_ExSystemExceptionFilter
)
283 Status
= _SEH_GetExceptionCode();
287 if(!NT_SUCCESS(Status
)) return Status
;
290 /* Get the Timer Object */
291 Status
= ObReferenceObjectByHandle(TimerHandle
,
298 /* Check for success */
299 if(NT_SUCCESS(Status
))
301 DPRINT("Timer Referenced: 0x%p\n", Timer
);
304 KeAcquireSpinLock(&Timer
->Lock
, &OldIrql
);
306 /* Check if it's enabled */
307 if (Timer
->ApcAssociated
)
310 * First, remove it from the Thread's Active List
313 TimerThread
= CONTAINING_RECORD(Timer
->TimerApc
.Thread
, ETHREAD
, Tcb
);
314 DPRINT("Removing from Thread: 0x%p\n", TimerThread
);
316 /* Lock its active list */
317 KeAcquireSpinLockAtDpcLevel(&TimerThread
->ActiveTimerListLock
);
320 RemoveEntryList(&TimerThread
->ActiveTimerListHead
);
322 /* Unlock the list */
323 KeReleaseSpinLockFromDpcLevel(&TimerThread
->ActiveTimerListLock
);
325 /* Cancel the Timer */
326 KeCancelTimer(&Timer
->KeTimer
);
327 KeRemoveQueueDpc(&Timer
->TimerDpc
);
328 KeRemoveQueueApc(&Timer
->TimerApc
);
329 Timer
->ApcAssociated
= FALSE
;
334 /* If timer was disabled, we still need to cancel it */
335 DPRINT("APC was not Associated. Cancelling Timer\n");
336 KeCancelTimer(&Timer
->KeTimer
);
339 /* Handle a Wake Timer */
340 if (Timer
->WakeTimer
)
342 /* Lock the Wake List */
343 KeAcquireSpinLockAtDpcLevel(&ExpWakeListLock
);
345 /* Check again, since it might've changed before we locked */
346 if (Timer
->WakeTimer
)
348 /* Remove it from the Wait List */
349 DPRINT("Removing wake list\n");
350 RemoveEntryList(&Timer
->WakeTimerListEntry
);
351 Timer
->WakeTimer
= FALSE
;
354 /* Release the Wake List */
355 KeReleaseSpinLockFromDpcLevel(&ExpWakeListLock
);
358 /* Unlock the Timer */
359 KeReleaseSpinLock(&Timer
->Lock
, OldIrql
);
361 /* Read the old State */
362 State
= KeReadStateTimer(&Timer
->KeTimer
);
364 /* Dereference the Object */
365 ObDereferenceObject(Timer
);
367 /* Dereference if it was previously enabled */
368 if (KillTimer
) ObDereferenceObject(Timer
);
369 DPRINT1("Timer disabled\n");
371 /* Make sure it's safe to write to the handle */
376 *CurrentState
= State
;
378 _SEH_EXCEPT(_SEH_ExSystemExceptionFilter
)
380 Status
= _SEH_GetExceptionCode();
386 /* Return to Caller */
392 NtCreateTimer(OUT PHANDLE TimerHandle
,
393 IN ACCESS_MASK DesiredAccess
,
394 IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL
,
395 IN TIMER_TYPE TimerType
)
399 KPROCESSOR_MODE PreviousMode
= ExGetPreviousMode();
400 NTSTATUS Status
= STATUS_SUCCESS
;
402 DPRINT("NtCreateTimer(Handle: 0x%p, Type: %d)\n", TimerHandle
, TimerType
);
404 /* Check Parameter Validity */
405 if (PreviousMode
!= KernelMode
)
409 ProbeForWriteHandle(TimerHandle
);
411 _SEH_EXCEPT(_SEH_ExSystemExceptionFilter
)
413 Status
= _SEH_GetExceptionCode();
417 if(!NT_SUCCESS(Status
)) return Status
;
420 /* Check for correct timer type */
421 if ((TimerType
!= NotificationTimer
) && (TimerType
!= SynchronizationTimer
))
423 DPRINT1("Invalid Timer Type!\n");
424 return STATUS_INVALID_PARAMETER_4
;
427 /* Create the Object */
428 Status
= ObCreateObject(PreviousMode
,
438 /* Check for Success */
439 if(NT_SUCCESS(Status
))
441 /* Initialize the Kernel Timer */
442 DPRINT("Initializing Timer: 0x%p\n", Timer
);
443 KeInitializeTimerEx(&Timer
->KeTimer
, TimerType
);
445 /* Initialize the Timer Lock */
446 KeInitializeSpinLock(&Timer
->Lock
);
448 /* Initialize the DPC */
449 KeInitializeDpc(&Timer
->TimerDpc
, ExpTimerDpcRoutine
, Timer
);
451 /* Set Initial State */
452 Timer
->ApcAssociated
= FALSE
;
453 Timer
->WakeTimer
= FALSE
;
455 /* Insert the Timer */
456 Status
= ObInsertObject((PVOID
)Timer
,
462 DPRINT("Timer Inserted\n");
464 /* Make sure it's safe to write to the handle */
467 *TimerHandle
= hTimer
;
469 _SEH_EXCEPT(_SEH_ExSystemExceptionFilter
)
471 Status
= _SEH_GetExceptionCode();
476 /* Return to Caller */
482 NtOpenTimer(OUT PHANDLE TimerHandle
,
483 IN ACCESS_MASK DesiredAccess
,
484 IN POBJECT_ATTRIBUTES ObjectAttributes
)
487 KPROCESSOR_MODE PreviousMode
= ExGetPreviousMode();
488 NTSTATUS Status
= STATUS_SUCCESS
;
490 DPRINT("NtOpenTimer(TimerHandle: 0x%p)\n", TimerHandle
);
492 /* Check Parameter Validity */
493 if (PreviousMode
!= KernelMode
)
497 ProbeForWriteHandle(TimerHandle
);
499 _SEH_EXCEPT(_SEH_ExSystemExceptionFilter
)
501 Status
= _SEH_GetExceptionCode();
505 if(!NT_SUCCESS(Status
)) return Status
;
509 Status
= ObOpenObjectByName(ObjectAttributes
,
517 /* Check for success */
518 if(NT_SUCCESS(Status
))
520 /* Make sure it's safe to write to the handle */
523 *TimerHandle
= hTimer
;
525 _SEH_EXCEPT(_SEH_ExSystemExceptionFilter
)
527 Status
= _SEH_GetExceptionCode();
532 /* Return to Caller */
539 NtQueryTimer(IN HANDLE TimerHandle
,
540 IN TIMER_INFORMATION_CLASS TimerInformationClass
,
541 OUT PVOID TimerInformation
,
542 IN ULONG TimerInformationLength
,
543 OUT PULONG ReturnLength OPTIONAL
)
546 KPROCESSOR_MODE PreviousMode
= ExGetPreviousMode();
547 NTSTATUS Status
= STATUS_SUCCESS
;
548 PTIMER_BASIC_INFORMATION BasicInfo
= (PTIMER_BASIC_INFORMATION
)TimerInformation
;
550 DPRINT("NtQueryTimer(TimerHandle: 0x%p, Class: %d)\n", TimerHandle
, TimerInformationClass
);
553 Status
= DefaultQueryInfoBufferCheck(TimerInformationClass
,
555 sizeof(ExTimerInfoClass
) / sizeof(ExTimerInfoClass
[0]),
557 TimerInformationLength
,
560 if(!NT_SUCCESS(Status
))
562 DPRINT1("NtQueryTimer() failed, Status: 0x%x\n", Status
);
566 /* Get the Timer Object */
567 Status
= ObReferenceObjectByHandle(TimerHandle
,
574 /* Check for Success */
575 if(NT_SUCCESS(Status
))
577 /* Return the Basic Information */
580 /* Return the remaining time, corrected */
581 BasicInfo
->TimeRemaining
.QuadPart
= Timer
->KeTimer
.DueTime
.QuadPart
-
582 KeQueryInterruptTime();
584 /* Return the current state */
585 BasicInfo
->SignalState
= KeReadStateTimer(&Timer
->KeTimer
);
587 /* Return the buffer length if requested */
588 if(ReturnLength
!= NULL
) *ReturnLength
= sizeof(TIMER_BASIC_INFORMATION
);
590 DPRINT("Returning Information for Timer: 0x%p. Time Remaining: %I64x\n",
591 Timer
, BasicInfo
->TimeRemaining
.QuadPart
);
593 _SEH_EXCEPT(_SEH_ExSystemExceptionFilter
)
595 Status
= _SEH_GetExceptionCode();
599 /* Dereference Object */
600 ObDereferenceObject(Timer
);
609 NtSetTimer(IN HANDLE TimerHandle
,
610 IN PLARGE_INTEGER DueTime
,
611 IN PTIMER_APC_ROUTINE TimerApcRoutine OPTIONAL
,
612 IN PVOID TimerContext OPTIONAL
,
613 IN BOOLEAN WakeTimer
,
614 IN LONG Period OPTIONAL
,
615 OUT PBOOLEAN PreviousState OPTIONAL
)
620 KPROCESSOR_MODE PreviousMode
= ExGetPreviousMode();
621 PETHREAD CurrentThread
= PsGetCurrentThread();
622 LARGE_INTEGER TimerDueTime
;
623 PETHREAD TimerThread
;
624 BOOLEAN KillTimer
= FALSE
;
625 NTSTATUS Status
= STATUS_SUCCESS
;
627 DPRINT("NtSetTimer(TimerHandle: 0x%p, DueTime: %I64x, Apc: 0x%p, Period: %d)\n",
628 TimerHandle
, DueTime
->QuadPart
, TimerApcRoutine
, Period
);
630 /* Check Parameter Validity */
631 if (PreviousMode
!= KernelMode
)
635 TimerDueTime
= ProbeForReadLargeInteger(DueTime
);
639 ProbeForWriteBoolean(PreviousState
);
642 _SEH_EXCEPT(_SEH_ExSystemExceptionFilter
)
644 Status
= _SEH_GetExceptionCode();
648 if(!NT_SUCCESS(Status
)) return Status
;
651 /* Check for a valid Period */
654 DPRINT1("Invalid Period for timer\n");
655 return STATUS_INVALID_PARAMETER_6
;
658 /* Get the Timer Object */
659 Status
= ObReferenceObjectByHandle(TimerHandle
,
667 * Tell the user we don't support Wake Timers...
668 * when we have the ability to use/detect the Power Management
669 * functionatliy required to support them, make this check dependent
670 * on the actual PM capabilities
672 if (WakeTimer
) Status
= STATUS_TIMER_RESUME_IGNORED
;
675 if (NT_SUCCESS(Status
))
678 DPRINT("Timer Referencced: 0x%p\n", Timer
);
679 KeAcquireSpinLock(&Timer
->Lock
, &OldIrql
);
681 /* Cancel Running Timer */
682 if (Timer
->ApcAssociated
)
685 * First, remove it from the Thread's Active List
688 TimerThread
= CONTAINING_RECORD(Timer
->TimerApc
.Thread
, ETHREAD
, Tcb
);
689 DPRINT("Thread already running. Removing from Thread: 0x%p\n", TimerThread
);
691 /* Lock its active list */
692 KeAcquireSpinLockAtDpcLevel(&TimerThread
->ActiveTimerListLock
);
695 RemoveEntryList(&TimerThread
->ActiveTimerListHead
);
697 /* Unlock the list */
698 KeReleaseSpinLockFromDpcLevel(&TimerThread
->ActiveTimerListLock
);
700 /* Cancel the Timer */
701 KeCancelTimer(&Timer
->KeTimer
);
702 KeRemoveQueueDpc(&Timer
->TimerDpc
);
703 KeRemoveQueueApc(&Timer
->TimerApc
);
704 Timer
->ApcAssociated
= FALSE
;
709 /* If timer was disabled, we still need to cancel it */
710 DPRINT("No APCs. Simply cancelling\n");
711 KeCancelTimer(&Timer
->KeTimer
);
715 State
= KeReadStateTimer(&Timer
->KeTimer
);
717 /* Handle Wake Timers */
718 DPRINT("Doing Wake Semantics\n");
719 KeAcquireSpinLockAtDpcLevel(&ExpWakeListLock
);
720 if (WakeTimer
&& !Timer
->WakeTimer
)
722 /* Insert it into the list */
723 Timer
->WakeTimer
= TRUE
;
724 InsertTailList(&ExpWakeList
, &Timer
->WakeTimerListEntry
);
726 else if (!WakeTimer
&& Timer
->WakeTimer
)
728 /* Remove it from the list */
729 RemoveEntryList(&Timer
->WakeTimerListEntry
);
730 Timer
->WakeTimer
= FALSE
;
732 KeReleaseSpinLockFromDpcLevel(&ExpWakeListLock
);
734 /* Set up the APC Routine if specified */
737 /* Initialize the APC */
738 DPRINT("Initializing APC: 0x%p\n", Timer
->TimerApc
);
739 KeInitializeApc(&Timer
->TimerApc
,
741 CurrentApcEnvironment
,
742 &ExpTimerApcKernelRoutine
,
743 (PKRUNDOWN_ROUTINE
)NULL
,
744 (PKNORMAL_ROUTINE
)TimerApcRoutine
,
748 /* Lock the Thread's Active List and Insert */
749 KeAcquireSpinLockAtDpcLevel(&CurrentThread
->ActiveTimerListLock
);
750 InsertTailList(&CurrentThread
->ActiveTimerListHead
,
751 &Timer
->ActiveTimerListEntry
);
752 KeReleaseSpinLockFromDpcLevel(&CurrentThread
->ActiveTimerListLock
);
756 /* Enable and Set the Timer */
757 DPRINT("Setting Kernel Timer\n");
758 KeSetTimerEx(&Timer
->KeTimer
,
761 TimerApcRoutine
? &Timer
->TimerDpc
: 0);
762 Timer
->ApcAssociated
= TimerApcRoutine
? TRUE
: FALSE
;
764 /* Unlock the Timer */
765 KeReleaseSpinLock(&Timer
->Lock
, OldIrql
);
767 /* Dereference if it was previously enabled */
768 if (!TimerApcRoutine
) ObDereferenceObject(Timer
);
769 if (KillTimer
) ObDereferenceObject(Timer
);
770 DPRINT("Finished Setting the Timer\n");
772 /* Make sure it's safe to write to the handle */
773 if(PreviousState
!= NULL
)
777 *PreviousState
= State
;
779 _SEH_EXCEPT(_SEH_ExSystemExceptionFilter
)
781 Status
= _SEH_GetExceptionCode();
787 /* Return to Caller */