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