10cf2b994b21c24c84860ffa0512ebb0d5f447f8
[reactos.git] / reactos / ntoskrnl / ex / timer.c
1 /*
2 * COPYRIGHT: See COPYING in the top level directory
3 * PROJECT: ReactOS kernel
4 * FILE: ntoskrnl/ex/timer.c
5 * PURPOSE: User-mode timers
6 *
7 * PROGRAMMERS: Alex Ionescu (alex@relsoft.net) - Reimplemented
8 * David Welch (welch@mcmail.com)
9 */
10
11 /* INCLUDES *****************************************************************/
12
13 #include <ntoskrnl.h>
14 #include <internal/debug.h>
15
16 /* TYPES ********************************************************************/
17
18 /* Executive Timer Object */
19 typedef struct _ETIMER {
20 KTIMER KeTimer;
21 KAPC TimerApc;
22 KDPC TimerDpc;
23 LIST_ENTRY ActiveTimerListEntry;
24 KSPIN_LOCK Lock;
25 LONG Period;
26 BOOLEAN ApcAssociated;
27 BOOLEAN WakeTimer;
28 LIST_ENTRY WakeTimerListEntry;
29 } ETIMER, *PETIMER;
30
31 /* GLOBALS ******************************************************************/
32
33 /* Timer Object Type */
34 POBJECT_TYPE ExTimerType = NULL;
35
36 KSPIN_LOCK ExpWakeListLock;
37 LIST_ENTRY ExpWakeList;
38
39 /* Timer Mapping */
40 static GENERIC_MAPPING ExpTimerMapping = {
41 STANDARD_RIGHTS_READ | TIMER_QUERY_STATE,
42 STANDARD_RIGHTS_WRITE | TIMER_MODIFY_STATE,
43 STANDARD_RIGHTS_EXECUTE | SYNCHRONIZE,
44 TIMER_ALL_ACCESS
45 };
46
47 /* Timer Information Classes */
48 static const INFORMATION_CLASS_INFO ExTimerInfoClass[] = {
49
50 /* TimerBasicInformation */
51 ICI_SQ_SAME( sizeof(TIMER_BASIC_INFORMATION), sizeof(ULONG), ICIF_QUERY ),
52 };
53
54 /* FUNCTIONS *****************************************************************/
55
56 VOID
57 STDCALL
58 ExpDeleteTimer(PVOID ObjectBody)
59 {
60 KIRQL OldIrql;
61 PETIMER Timer = ObjectBody;
62
63 DPRINT("ExpDeleteTimer(Timer: %x)\n", Timer);
64
65 /* Lock the Wake List */
66 KeAcquireSpinLock(&ExpWakeListLock, &OldIrql);
67
68 /* Check if it has a Wait List */
69 if (!IsListEmpty(&Timer->WakeTimerListEntry)) {
70
71 /* Remove it from the Wait List */
72 DPRINT("Removing wake list\n");
73 RemoveEntryList(&Timer->WakeTimerListEntry);
74 }
75
76 /* Release the Wake List */
77 KeReleaseSpinLock(&ExpWakeListLock, OldIrql);
78
79 /* Tell the Kernel to cancel the Timer */
80 DPRINT("Cancelling Timer\n");
81 KeCancelTimer(&Timer->KeTimer);
82 }
83
84 VOID
85 STDCALL
86 ExpTimerDpcRoutine(PKDPC Dpc,
87 PVOID DeferredContext,
88 PVOID SystemArgument1,
89 PVOID SystemArgument2)
90 {
91 PETIMER Timer;
92 KIRQL OldIrql;
93
94 DPRINT("ExpTimerDpcRoutine(Dpc: %x)\n", Dpc);
95
96 /* Get the Timer Object */
97 Timer = (PETIMER)DeferredContext;
98
99 /* Lock the Timer */
100 KeAcquireSpinLock(&Timer->Lock, &OldIrql);
101
102 /* Queue the APC */
103 if(Timer->ApcAssociated) {
104
105 DPRINT("Queuing APC\n");
106 KeInsertQueueApc(&Timer->TimerApc,
107 SystemArgument1,
108 SystemArgument2,
109 IO_NO_INCREMENT);
110 }
111
112 /* Release the Timer */
113 KeReleaseSpinLock(&Timer->Lock, OldIrql);
114 }
115
116
117 VOID
118 STDCALL
119 ExpTimerApcKernelRoutine(PKAPC Apc,
120 PKNORMAL_ROUTINE* NormalRoutine,
121 PVOID* NormalContext,
122 PVOID* SystemArgument1,
123 PVOID* SystemArguemnt2)
124 {
125 PETIMER Timer;
126 KIRQL OldIrql;
127 PETHREAD CurrentThread = PsGetCurrentThread();
128
129 /* We need to find out which Timer we are */
130 Timer = CONTAINING_RECORD(Apc, ETIMER, TimerApc);
131 DPRINT("ExpTimerApcKernelRoutine(Apc: %x. Timer: %x)\n", Apc, Timer);
132
133 /* Lock the Timer */
134 KeAcquireSpinLock(&Timer->Lock, &OldIrql);
135
136 /* Lock the Thread's Active Timer List*/
137 KeAcquireSpinLockAtDpcLevel(&CurrentThread->ActiveTimerListLock);
138
139 /*
140 * Make sure that the Timer is still valid, and that it belongs to this thread
141 * Remove it if it's not periodic
142 */
143 if ((Timer->ApcAssociated) &&
144 (&CurrentThread->Tcb == Timer->TimerApc.Thread) &&
145 (!Timer->Period)) {
146
147 /* Remove it from the Active Timers List */
148 DPRINT("Removing Timer\n");
149 RemoveEntryList(&Timer->ActiveTimerListEntry);
150
151 /* Disable it */
152 Timer->ApcAssociated = FALSE;
153
154 /* Release spinlocks */
155 KeReleaseSpinLockFromDpcLevel(&CurrentThread->ActiveTimerListLock);
156 KeReleaseSpinLock(&Timer->Lock, OldIrql);
157
158 /* Dereference the Timer Object */
159 ObDereferenceObject(Timer);
160 return;
161 }
162
163 /* Release spinlocks */
164 KeReleaseSpinLockFromDpcLevel(&CurrentThread->ActiveTimerListLock);
165 KeReleaseSpinLock(&Timer->Lock, OldIrql);
166 }
167
168 VOID
169 INIT_FUNCTION
170 ExpInitializeTimerImplementation(VOID)
171 {
172 DPRINT("ExpInitializeTimerImplementation()\n");
173
174 /* Allocate Memory for the Timer */
175 ExTimerType = ExAllocatePool(NonPagedPool, sizeof(OBJECT_TYPE));
176
177 /* Create the Executive Timer Object */
178 RtlInitUnicodeString(&ExTimerType->TypeName, L"Timer");
179 ExTimerType->Tag = TAG('T', 'I', 'M', 'T');
180 ExTimerType->PeakObjects = 0;
181 ExTimerType->PeakHandles = 0;
182 ExTimerType->TotalObjects = 0;
183 ExTimerType->TotalHandles = 0;
184 ExTimerType->PagedPoolCharge = 0;
185 ExTimerType->NonpagedPoolCharge = sizeof(ETIMER);
186 ExTimerType->Mapping = &ExpTimerMapping;
187 ExTimerType->Dump = NULL;
188 ExTimerType->Open = NULL;
189 ExTimerType->Close = NULL;
190 ExTimerType->Delete = ExpDeleteTimer;
191 ExTimerType->Parse = NULL;
192 ExTimerType->Security = NULL;
193 ExTimerType->QueryName = NULL;
194 ExTimerType->OkayToClose = NULL;
195 ExTimerType->Create = NULL;
196 ExTimerType->DuplicationNotify = NULL;
197 ObpCreateTypeObject(ExTimerType);
198
199 /* Initialize the Wait List and Lock */
200 KeInitializeSpinLock(&ExpWakeListLock);
201 InitializeListHead(&ExpWakeList);
202 }
203
204
205 NTSTATUS
206 STDCALL
207 NtCancelTimer(IN HANDLE TimerHandle,
208 OUT PBOOLEAN CurrentState OPTIONAL)
209 {
210 PETIMER Timer;
211 KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
212 BOOLEAN State;
213 KIRQL OldIrql;
214 PETHREAD TimerThread;
215 BOOLEAN KillTimer = FALSE;
216 NTSTATUS Status = STATUS_SUCCESS;
217
218 PAGED_CODE();
219 DPRINT("NtCancelTimer(0x%x, 0x%x)\n", TimerHandle, CurrentState);
220
221 /* Check Parameter Validity */
222 if(CurrentState != NULL && PreviousMode != KernelMode) {
223 _SEH_TRY {
224 ProbeForWrite(CurrentState,
225 sizeof(BOOLEAN),
226 sizeof(BOOLEAN));
227 } _SEH_HANDLE {
228 Status = _SEH_GetExceptionCode();
229 } _SEH_END;
230
231 if(!NT_SUCCESS(Status)) {
232 return Status;
233 }
234 }
235
236 /* Get the Timer Object */
237 Status = ObReferenceObjectByHandle(TimerHandle,
238 TIMER_ALL_ACCESS,
239 ExTimerType,
240 PreviousMode,
241 (PVOID*)&Timer,
242 NULL);
243
244 /* Check for success */
245 if(NT_SUCCESS(Status)) {
246
247 DPRINT("Timer Referencced: %x\n", Timer);
248
249 /* Lock the Timer */
250 KeAcquireSpinLock(&Timer->Lock, &OldIrql);
251
252 /* Check if it's enabled */
253 if (Timer->ApcAssociated) {
254
255 /*
256 * First, remove it from the Thread's Active List
257 * Get the Thread.
258 */
259 TimerThread = CONTAINING_RECORD(Timer->TimerApc.Thread, ETHREAD, Tcb);
260 DPRINT("Removing from Thread: %x\n", TimerThread);
261
262 /* Lock its active list */
263 KeAcquireSpinLockAtDpcLevel(&TimerThread->ActiveTimerListLock);
264
265 /* Remove it */
266 RemoveEntryList(&TimerThread->ActiveTimerListHead);
267
268 /* Unlock the list */
269 KeReleaseSpinLockFromDpcLevel(&TimerThread->ActiveTimerListLock);
270
271 /* Cancel the Timer */
272 KeCancelTimer(&Timer->KeTimer);
273 KeRemoveQueueDpc(&Timer->TimerDpc);
274 KeRemoveQueueApc(&Timer->TimerApc);
275 Timer->ApcAssociated = FALSE;
276 KillTimer = TRUE;
277
278 } else {
279
280 /* If timer was disabled, we still need to cancel it */
281 DPRINT("APC was not Associated. Cancelling Timer\n");
282 KeCancelTimer(&Timer->KeTimer);
283 }
284
285 /* Read the old State */
286 State = KeReadStateTimer(&Timer->KeTimer);
287
288 /* Dereference the Object */
289 ObDereferenceObject(Timer);
290
291 /* Unlock the Timer */
292 KeReleaseSpinLock(&Timer->Lock, OldIrql);
293
294 /* Dereference if it was previously enabled */
295 if (KillTimer) ObDereferenceObject(Timer);
296 DPRINT1("Timer disabled\n");
297
298 /* Make sure it's safe to write to the handle */
299 if(CurrentState != NULL) {
300
301 _SEH_TRY {
302
303 *CurrentState = State;
304
305 } _SEH_HANDLE {
306
307 Status = _SEH_GetExceptionCode();
308
309 } _SEH_END;
310 }
311 }
312
313 /* Return to Caller */
314 return Status;
315 }
316
317
318 NTSTATUS
319 STDCALL
320 NtCreateTimer(OUT PHANDLE TimerHandle,
321 IN ACCESS_MASK DesiredAccess,
322 IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,
323 IN TIMER_TYPE TimerType)
324 {
325 PETIMER Timer;
326 HANDLE hTimer;
327 KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
328 NTSTATUS Status = STATUS_SUCCESS;
329
330 PAGED_CODE();
331 DPRINT("NtCreateTimer(Handle: %x, Type: %d)\n", TimerHandle, TimerType);
332
333 /* Check Parameter Validity */
334 if (PreviousMode != KernelMode) {
335
336 _SEH_TRY {
337
338 ProbeForWrite(TimerHandle,
339 sizeof(HANDLE),
340 sizeof(ULONG));
341 } _SEH_HANDLE {
342
343 Status = _SEH_GetExceptionCode();
344
345 } _SEH_END;
346
347 if(!NT_SUCCESS(Status)) return Status;
348 }
349
350 /* Create the Object */
351 Status = ObCreateObject(PreviousMode,
352 ExTimerType,
353 ObjectAttributes,
354 PreviousMode,
355 NULL,
356 sizeof(ETIMER),
357 0,
358 0,
359 (PVOID*)&Timer);
360
361 /* Check for Success */
362 if(NT_SUCCESS(Status)) {
363
364 /* Initialize the Kernel Timer */
365 DPRINT("Initializing Timer: %x\n", Timer);
366 KeInitializeTimerEx(&Timer->KeTimer, TimerType);
367
368 /* Initialize the Timer Lock */
369 KeInitializeSpinLock(&Timer->Lock);
370
371 /* Initialize the DPC */
372 KeInitializeDpc(&Timer->TimerDpc, ExpTimerDpcRoutine, Timer);
373
374 /* Set Initial State */
375 Timer->ApcAssociated = FALSE;
376 InitializeListHead(&Timer->WakeTimerListEntry);
377 Timer->WakeTimer = FALSE;
378
379 /* Insert the Timer */
380 Status = ObInsertObject((PVOID)Timer,
381 NULL,
382 DesiredAccess,
383 0,
384 NULL,
385 &hTimer);
386 DPRINT("Timer Inserted\n");
387
388
389 /* Make sure it's safe to write to the handle */
390 _SEH_TRY {
391
392 *TimerHandle = hTimer;
393
394 } _SEH_HANDLE {
395
396 Status = _SEH_GetExceptionCode();
397
398 } _SEH_END;
399 }
400
401 /* Return to Caller */
402 return Status;
403 }
404
405
406 NTSTATUS
407 STDCALL
408 NtOpenTimer(OUT PHANDLE TimerHandle,
409 IN ACCESS_MASK DesiredAccess,
410 IN POBJECT_ATTRIBUTES ObjectAttributes)
411 {
412 HANDLE hTimer;
413 KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
414 NTSTATUS Status = STATUS_SUCCESS;
415
416 PAGED_CODE();
417 DPRINT("NtOpenTimer(TimerHandle: %x)\n", TimerHandle);
418
419 /* Check Parameter Validity */
420 if (PreviousMode != KernelMode) {
421
422 _SEH_TRY {
423
424 ProbeForWrite(TimerHandle,
425 sizeof(HANDLE),
426 sizeof(ULONG));
427
428 } _SEH_HANDLE {
429
430 Status = _SEH_GetExceptionCode();
431
432 } _SEH_END;
433
434 if(!NT_SUCCESS(Status)) return Status;
435 }
436
437 /* Open the Timer */
438 Status = ObOpenObjectByName(ObjectAttributes,
439 ExTimerType,
440 NULL,
441 PreviousMode,
442 DesiredAccess,
443 NULL,
444 &hTimer);
445
446 /* Check for success */
447 if(NT_SUCCESS(Status)) {
448
449 /* Make sure it's safe to write to the handle */
450 _SEH_TRY {
451
452 *TimerHandle = hTimer;
453
454 } _SEH_HANDLE {
455
456 Status = _SEH_GetExceptionCode();
457
458 } _SEH_END;
459 }
460
461 /* Return to Caller */
462 return Status;
463 }
464
465
466 NTSTATUS
467 STDCALL
468 NtQueryTimer(IN HANDLE TimerHandle,
469 IN TIMER_INFORMATION_CLASS TimerInformationClass,
470 OUT PVOID TimerInformation,
471 IN ULONG TimerInformationLength,
472 OUT PULONG ReturnLength OPTIONAL)
473 {
474 PETIMER Timer;
475 KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
476 NTSTATUS Status = STATUS_SUCCESS;
477 PTIMER_BASIC_INFORMATION BasicInfo = (PTIMER_BASIC_INFORMATION)TimerInformation;
478
479 PAGED_CODE();
480 DPRINT("NtQueryTimer(TimerHandle: %x, Class: %d)\n", TimerHandle, TimerInformationClass);
481
482 /* Check Validity */
483 DefaultQueryInfoBufferCheck(TimerInformationClass,
484 ExTimerInfoClass,
485 TimerInformation,
486 TimerInformationLength,
487 ReturnLength,
488 PreviousMode,
489 &Status);
490 if(!NT_SUCCESS(Status)) {
491
492 DPRINT1("NtQueryTimer() failed, Status: 0x%x\n", Status);
493 return Status;
494 }
495
496 /* Get the Timer Object */
497 Status = ObReferenceObjectByHandle(TimerHandle,
498 TIMER_QUERY_STATE,
499 ExTimerType,
500 PreviousMode,
501 (PVOID*)&Timer,
502 NULL);
503
504 /* Check for Success */
505 if(NT_SUCCESS(Status)) {
506
507 /* Return the Basic Information */
508 _SEH_TRY {
509
510 /* FIXME: Interrupt correction based on Interrupt Time */
511 DPRINT("Returning Information for Timer: %x. Time Remaining: %d\n", Timer, Timer->KeTimer.DueTime.QuadPart);
512 BasicInfo->TimeRemaining.QuadPart = Timer->KeTimer.DueTime.QuadPart;
513 BasicInfo->SignalState = KeReadStateTimer(&Timer->KeTimer);
514
515 if(ReturnLength != NULL) *ReturnLength = sizeof(TIMER_BASIC_INFORMATION);
516
517 } _SEH_HANDLE {
518
519 Status = _SEH_GetExceptionCode();
520
521 } _SEH_END;
522
523 /* Dereference Object */
524 ObDereferenceObject(Timer);
525 }
526
527 /* Return Status */
528 return Status;
529 }
530
531 NTSTATUS
532 STDCALL
533 NtSetTimer(IN HANDLE TimerHandle,
534 IN PLARGE_INTEGER DueTime,
535 IN PTIMER_APC_ROUTINE TimerApcRoutine OPTIONAL,
536 IN PVOID TimerContext OPTIONAL,
537 IN BOOLEAN WakeTimer,
538 IN LONG Period OPTIONAL,
539 OUT PBOOLEAN PreviousState OPTIONAL)
540 {
541 PETIMER Timer;
542 KIRQL OldIrql;
543 BOOLEAN State;
544 KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
545 PETHREAD CurrentThread = PsGetCurrentThread();
546 LARGE_INTEGER TimerDueTime;
547 PETHREAD TimerThread;
548 BOOLEAN KillTimer = FALSE;
549 NTSTATUS Status = STATUS_SUCCESS;
550
551 PAGED_CODE();
552 DPRINT("NtSetTimer(TimerHandle: %x, DueTime: %d, Apc: %x, Period: %d)\n", TimerHandle, DueTime->QuadPart, TimerApcRoutine, Period);
553
554 /* Check Parameter Validity */
555 if (PreviousMode != KernelMode) {
556
557 _SEH_TRY {
558
559 ProbeForRead(DueTime,
560 sizeof(LARGE_INTEGER),
561 sizeof(ULONG));
562 TimerDueTime = *DueTime;
563
564 if(PreviousState != NULL) {
565
566 ProbeForWrite(PreviousState,
567 sizeof(BOOLEAN),
568 sizeof(BOOLEAN));
569 }
570
571 } _SEH_HANDLE {
572
573 Status = _SEH_GetExceptionCode();
574
575 } _SEH_END;
576
577 if(!NT_SUCCESS(Status)) return Status;
578 }
579
580 /* Get the Timer Object */
581 Status = ObReferenceObjectByHandle(TimerHandle,
582 TIMER_ALL_ACCESS,
583 ExTimerType,
584 PreviousMode,
585 (PVOID*)&Timer,
586 NULL);
587
588 /* Check status */
589 if (NT_SUCCESS(Status)) {
590
591 /* Lock the Timer */
592 DPRINT("Timer Referencced: %x\n", Timer);
593 KeAcquireSpinLock(&Timer->Lock, &OldIrql);
594
595 /* Cancel Running Timer */
596 if (Timer->ApcAssociated) {
597
598 /*
599 * First, remove it from the Thread's Active List
600 * Get the Thread.
601 */
602 TimerThread = CONTAINING_RECORD(Timer->TimerApc.Thread, ETHREAD, Tcb);
603 DPRINT("Thread already running. Removing from Thread: %x\n", TimerThread);
604
605 /* Lock its active list */
606 KeAcquireSpinLockAtDpcLevel(&TimerThread->ActiveTimerListLock);
607
608 /* Remove it */
609 RemoveEntryList(&TimerThread->ActiveTimerListHead);
610
611 /* Unlock the list */
612 KeReleaseSpinLockFromDpcLevel(&TimerThread->ActiveTimerListLock);
613
614 /* Cancel the Timer */
615 KeCancelTimer(&Timer->KeTimer);
616 KeRemoveQueueDpc(&Timer->TimerDpc);
617 KeRemoveQueueApc(&Timer->TimerApc);
618 Timer->ApcAssociated = FALSE;
619 KillTimer = TRUE;
620
621 } else {
622
623 /* If timer was disabled, we still need to cancel it */
624 DPRINT("No APCs. Simply cancelling\n");
625 KeCancelTimer(&Timer->KeTimer);
626 }
627
628 /* Read the State */
629 State = KeReadStateTimer(&Timer->KeTimer);
630
631 /* Handle Wake Timers */
632 DPRINT("Doing Wake Semantics\n");
633 KeAcquireSpinLockAtDpcLevel(&ExpWakeListLock);
634 if (WakeTimer) {
635
636 /* Insert it into the list */
637 InsertTailList(&ExpWakeList, &Timer->WakeTimerListEntry);
638
639 } else {
640
641 /* Remove it from the list */
642 RemoveEntryList(&Timer->WakeTimerListEntry);
643 Timer->WakeTimerListEntry.Flink = NULL;
644 }
645 KeReleaseSpinLockFromDpcLevel(&ExpWakeListLock);
646
647 /* Set up the APC Routine if specified */
648 if (TimerApcRoutine) {
649
650 /* Initialize the APC */
651 DPRINT("Initializing APC: %x\n", Timer->TimerApc);
652 KeInitializeApc(&Timer->TimerApc,
653 &CurrentThread->Tcb,
654 CurrentApcEnvironment,
655 &ExpTimerApcKernelRoutine,
656 (PKRUNDOWN_ROUTINE)NULL,
657 (PKNORMAL_ROUTINE)TimerApcRoutine,
658 PreviousMode,
659 TimerContext);
660
661 /* Lock the Thread's Active List and Insert */
662 KeAcquireSpinLockAtDpcLevel(&CurrentThread->ActiveTimerListLock);
663 InsertTailList(&CurrentThread->ActiveTimerListHead,
664 &Timer->ActiveTimerListEntry);
665 KeReleaseSpinLockFromDpcLevel(&CurrentThread->ActiveTimerListLock);
666
667 }
668
669 /* Enable and Set the Timer */
670 DPRINT("Setting Kernel Timer\n");
671 KeSetTimerEx(&Timer->KeTimer,
672 TimerDueTime,
673 Period,
674 TimerApcRoutine ? &Timer->TimerDpc : 0);
675 Timer->ApcAssociated = TimerApcRoutine ? TRUE : FALSE;
676
677 /* Unlock the Timer */
678 KeReleaseSpinLock(&Timer->Lock, OldIrql);
679
680 /* Dereference the Object */
681 ObDereferenceObject(Timer);
682
683 /* Unlock the Timer */
684 KeReleaseSpinLock(&Timer->Lock, OldIrql);
685
686 /* Dereference if it was previously enabled */
687 if (!TimerApcRoutine) ObDereferenceObject(Timer);
688 if (KillTimer) ObDereferenceObject(Timer);
689 DPRINT("Finished Setting the Timer\n");
690
691 /* Make sure it's safe to write to the handle */
692 if(PreviousState != NULL) {
693
694 _SEH_TRY {
695
696 *PreviousState = State;
697
698 } _SEH_HANDLE {
699
700 Status = _SEH_GetExceptionCode();
701
702 } _SEH_END;
703 }
704 }
705
706 /* Return to Caller */
707 return Status;
708 }
709
710 /* EOF */