reactos/ntoskrnl/ex/timer.c

757 lines
22 KiB
C

/*
* PROJECT: ReactOS Kernel
* LICENSE: GPL - See COPYING in the top level directory
* FILE: ntoskrnl/ex/timer.c
* PURPOSE: Executive Timer Implementation
* PROGRAMMERS: Alex Ionescu (alex.ionescu@reactos.org)
*/
/* INCLUDES ******************************************************************/
#include <ntoskrnl.h>
#define NDEBUG
#include <debug.h>
/* GLOBALS *******************************************************************/
/* Timer Object Type */
POBJECT_TYPE ExTimerType = NULL;
KSPIN_LOCK ExpWakeListLock;
LIST_ENTRY ExpWakeList;
/* Timer Mapping */
static GENERIC_MAPPING ExpTimerMapping =
{
STANDARD_RIGHTS_READ | TIMER_QUERY_STATE,
STANDARD_RIGHTS_WRITE | TIMER_MODIFY_STATE,
STANDARD_RIGHTS_EXECUTE | SYNCHRONIZE,
TIMER_ALL_ACCESS
};
/* Timer Information Classes */
static const INFORMATION_CLASS_INFO ExTimerInfoClass[] =
{
/* TimerBasicInformation */
ICI_SQ_SAME(sizeof(TIMER_BASIC_INFORMATION), sizeof(ULONG), ICIF_QUERY),
};
/* PRIVATE FUNCTIONS *********************************************************/
VOID
NTAPI
ExTimerRundown(VOID)
{
PETHREAD Thread = PsGetCurrentThread();
KIRQL OldIrql;
PLIST_ENTRY CurrentEntry;
PETIMER Timer;
ULONG DerefsToDo;
/* Lock the Thread's Active Timer List and loop it */
KeAcquireSpinLock(&Thread->ActiveTimerListLock, &OldIrql);
CurrentEntry = Thread->ActiveTimerListHead.Flink;
while (CurrentEntry != &Thread->ActiveTimerListHead)
{
/* Get the timer */
Timer = CONTAINING_RECORD(CurrentEntry, ETIMER, ActiveTimerListEntry);
/* Reference it */
ObReferenceObject(Timer);
DerefsToDo = 1;
/* Unlock the list */
KeReleaseSpinLock(&Thread->ActiveTimerListLock, OldIrql);
/* Lock the Timer */
KeAcquireSpinLock(&Timer->Lock, &OldIrql);
/* Lock the list again */
KeAcquireSpinLockAtDpcLevel(&Thread->ActiveTimerListLock);
/* Make sure that the timer is valid */
if ((Timer->ApcAssociated) && (&Thread->Tcb == Timer->TimerApc.Thread))
{
/* Remove it from the list */
RemoveEntryList(&Timer->ActiveTimerListEntry);
Timer->ApcAssociated = FALSE;
/* Cancel the timer and remove its DPC and APC */
KeCancelTimer(&Timer->KeTimer);
KeRemoveQueueDpc(&Timer->TimerDpc);
if (KeRemoveQueueApc(&Timer->TimerApc)) DerefsToDo++;
/* Add another dereference to do */
DerefsToDo++;
}
/* Unlock the list */
KeReleaseSpinLockFromDpcLevel(&Thread->ActiveTimerListLock);
/* Unlock the Timer */
KeReleaseSpinLock(&Timer->Lock, OldIrql);
/* Dereference it */
ObDereferenceObjectEx(Timer, DerefsToDo);
/* Loop again */
KeAcquireSpinLock(&Thread->ActiveTimerListLock, &OldIrql);
CurrentEntry = Thread->ActiveTimerListHead.Flink;
}
/* Release lock and return */
KeReleaseSpinLock(&Thread->ActiveTimerListLock, OldIrql);
}
VOID
NTAPI
ExpDeleteTimer(IN PVOID ObjectBody)
{
KIRQL OldIrql;
PETIMER Timer = ObjectBody;
/* Check if it has a Wait List */
if (Timer->WakeTimerListEntry.Flink)
{
/* Lock the Wake List */
KeAcquireSpinLock(&ExpWakeListLock, &OldIrql);
/* Check again, since it might've changed before we locked */
if (Timer->WakeTimerListEntry.Flink)
{
/* Remove it from the Wait List */
RemoveEntryList(&Timer->WakeTimerListEntry);
Timer->WakeTimerListEntry.Flink = NULL;
}
/* Release the Wake List */
KeReleaseSpinLock(&ExpWakeListLock, OldIrql);
}
/* Tell the Kernel to cancel the Timer and flush all queued DPCs */
KeCancelTimer(&Timer->KeTimer);
KeFlushQueuedDpcs();
}
_Function_class_(KDEFERRED_ROUTINE)
VOID
NTAPI
ExpTimerDpcRoutine(IN PKDPC Dpc,
IN PVOID DeferredContext,
IN PVOID SystemArgument1,
IN PVOID SystemArgument2)
{
PETIMER Timer = DeferredContext;
BOOLEAN Inserted = FALSE;
/* Reference the timer */
if (!ObReferenceObjectSafe(Timer)) return;
/* Lock the Timer */
KeAcquireSpinLockAtDpcLevel(&Timer->Lock);
/* Check if the timer is associated */
if (Timer->ApcAssociated)
{
/* Queue the APC */
Inserted = KeInsertQueueApc(&Timer->TimerApc,
SystemArgument1,
SystemArgument2,
IO_NO_INCREMENT);
}
/* Release the Timer */
KeReleaseSpinLockFromDpcLevel(&Timer->Lock);
/* Dereference it if we couldn't queue the APC */
if (!Inserted) ObDereferenceObject(Timer);
}
VOID
NTAPI
ExpTimerApcKernelRoutine(IN PKAPC Apc,
IN OUT PKNORMAL_ROUTINE* NormalRoutine,
IN OUT PVOID* NormalContext,
IN OUT PVOID* SystemArgument1,
IN OUT PVOID* SystemArguemnt2)
{
PETIMER Timer;
KIRQL OldIrql;
ULONG DerefsToDo = 1;
PETHREAD Thread = PsGetCurrentThread();
/* We need to find out which Timer we are */
Timer = CONTAINING_RECORD(Apc, ETIMER, TimerApc);
/* Lock the Timer */
KeAcquireSpinLock(&Timer->Lock, &OldIrql);
/* Lock the Thread's Active Timer List*/
KeAcquireSpinLockAtDpcLevel(&Thread->ActiveTimerListLock);
/* Make sure that the Timer is valid, and that it belongs to this thread */
if ((Timer->ApcAssociated) && (&Thread->Tcb == Timer->TimerApc.Thread))
{
/* Check if it's not periodic */
if (!Timer->Period)
{
/* Remove it from the Active Timers List */
RemoveEntryList(&Timer->ActiveTimerListEntry);
/* Disable it */
Timer->ApcAssociated = FALSE;
DerefsToDo++;
}
}
else
{
/* Clear the normal routine */
*NormalRoutine = NULL;
}
/* Release locks */
KeReleaseSpinLockFromDpcLevel(&Thread->ActiveTimerListLock);
KeReleaseSpinLock(&Timer->Lock, OldIrql);
/* Dereference as needed */
ObDereferenceObjectEx(Timer, DerefsToDo);
}
BOOLEAN
INIT_FUNCTION
NTAPI
ExpInitializeTimerImplementation(VOID)
{
OBJECT_TYPE_INITIALIZER ObjectTypeInitializer;
UNICODE_STRING Name;
NTSTATUS Status;
/* Create the Timer Object Type */
RtlZeroMemory(&ObjectTypeInitializer, sizeof(ObjectTypeInitializer));
RtlInitUnicodeString(&Name, L"Timer");
ObjectTypeInitializer.Length = sizeof(ObjectTypeInitializer);
ObjectTypeInitializer.InvalidAttributes = OBJ_OPENLINK;
ObjectTypeInitializer.DefaultNonPagedPoolCharge = sizeof(ETIMER);
ObjectTypeInitializer.GenericMapping = ExpTimerMapping;
ObjectTypeInitializer.PoolType = NonPagedPool;
ObjectTypeInitializer.ValidAccessMask = TIMER_ALL_ACCESS;
ObjectTypeInitializer.DeleteProcedure = ExpDeleteTimer;
Status = ObCreateObjectType(&Name, &ObjectTypeInitializer, NULL, &ExTimerType);
if (!NT_SUCCESS(Status)) return FALSE;
/* Initialize the Wait List and Lock */
KeInitializeSpinLock(&ExpWakeListLock);
InitializeListHead(&ExpWakeList);
return TRUE;
}
/* PUBLIC FUNCTIONS **********************************************************/
NTSTATUS
NTAPI
NtCancelTimer(IN HANDLE TimerHandle,
OUT PBOOLEAN CurrentState OPTIONAL)
{
PETIMER Timer;
KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
BOOLEAN State;
KIRQL OldIrql;
PETHREAD TimerThread;
ULONG DerefsToDo = 1;
NTSTATUS Status;
PAGED_CODE();
/* Check if we need to probe */
if ((CurrentState) && (PreviousMode != KernelMode))
{
_SEH2_TRY
{
/* Make sure the pointer is valid */
ProbeForWriteBoolean(CurrentState);
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
/* Return the exception code */
_SEH2_YIELD(return _SEH2_GetExceptionCode());
}
_SEH2_END;
}
/* Get the Timer Object */
Status = ObReferenceObjectByHandle(TimerHandle,
TIMER_MODIFY_STATE,
ExTimerType,
PreviousMode,
(PVOID*)&Timer,
NULL);
if (NT_SUCCESS(Status))
{
/* Lock the Timer */
KeAcquireSpinLock(&Timer->Lock, &OldIrql);
/* Check if it's enabled */
if (Timer->ApcAssociated)
{
/* Get the Thread. */
TimerThread = CONTAINING_RECORD(Timer->TimerApc.Thread,
ETHREAD,
Tcb);
/* Lock its active list */
KeAcquireSpinLockAtDpcLevel(&TimerThread->ActiveTimerListLock);
/* Remove it */
RemoveEntryList(&Timer->ActiveTimerListEntry);
Timer->ApcAssociated = FALSE;
/* Unlock the list */
KeReleaseSpinLockFromDpcLevel(&TimerThread->ActiveTimerListLock);
/* Cancel the Timer */
KeCancelTimer(&Timer->KeTimer);
KeRemoveQueueDpc(&Timer->TimerDpc);
if (KeRemoveQueueApc(&Timer->TimerApc)) DerefsToDo++;
DerefsToDo++;
}
else
{
/* If timer was disabled, we still need to cancel it */
KeCancelTimer(&Timer->KeTimer);
}
/* Handle a Wake Timer */
if (Timer->WakeTimerListEntry.Flink)
{
/* Lock the Wake List */
KeAcquireSpinLockAtDpcLevel(&ExpWakeListLock);
/* Check again, since it might've changed before we locked */
if (Timer->WakeTimerListEntry.Flink)
{
/* Remove it from the Wait List */
RemoveEntryList(&Timer->WakeTimerListEntry);
Timer->WakeTimerListEntry.Flink = NULL;
}
/* Release the Wake List */
KeReleaseSpinLockFromDpcLevel(&ExpWakeListLock);
}
/* Unlock the Timer */
KeReleaseSpinLock(&Timer->Lock, OldIrql);
/* Read the old State */
State = KeReadStateTimer(&Timer->KeTimer);
/* Dereference the Object */
ObDereferenceObjectEx(Timer, DerefsToDo);
/* Check if caller wants the state */
if (CurrentState)
{
_SEH2_TRY
{
/* Return the Timer State */
*CurrentState = State;
}
_SEH2_EXCEPT(ExSystemExceptionFilter())
{
/* Do nothing */
(void)0;
}
_SEH2_END;
}
}
/* Return to Caller */
return Status;
}
NTSTATUS
NTAPI
NtCreateTimer(OUT PHANDLE TimerHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,
IN TIMER_TYPE TimerType)
{
PETIMER Timer;
HANDLE hTimer;
KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
NTSTATUS Status;
PAGED_CODE();
/* Check for correct timer type */
if ((TimerType != NotificationTimer) &&
(TimerType != SynchronizationTimer))
{
/* Fail */
return STATUS_INVALID_PARAMETER_4;
}
/* Check if we need to probe */
if (PreviousMode != KernelMode)
{
_SEH2_TRY
{
/* Make sure the pointer is valid */
ProbeForWriteHandle(TimerHandle);
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
/* Return the exception code */
_SEH2_YIELD(return _SEH2_GetExceptionCode());
}
_SEH2_END;
}
/* Create the Object */
Status = ObCreateObject(PreviousMode,
ExTimerType,
ObjectAttributes,
PreviousMode,
NULL,
sizeof(ETIMER),
0,
0,
(PVOID*)&Timer);
if (NT_SUCCESS(Status))
{
/* Initialize the DPC */
KeInitializeDpc(&Timer->TimerDpc, ExpTimerDpcRoutine, Timer);
/* Initialize the Kernel Timer */
KeInitializeTimerEx(&Timer->KeTimer, TimerType);
/* Initialize the timer fields */
KeInitializeSpinLock(&Timer->Lock);
Timer->ApcAssociated = FALSE;
Timer->WakeTimer = FALSE;
Timer->WakeTimerListEntry.Flink = NULL;
/* Insert the Timer */
Status = ObInsertObject((PVOID)Timer,
NULL,
DesiredAccess,
0,
NULL,
&hTimer);
/* Check for success */
if (NT_SUCCESS(Status))
{
/* Enter SEH */
_SEH2_TRY
{
/* Return the Timer Handle */
*TimerHandle = hTimer;
}
_SEH2_EXCEPT(ExSystemExceptionFilter())
{
/* Do nothing */
(void)0;
}
_SEH2_END;
}
}
/* Return to Caller */
return Status;
}
NTSTATUS
NTAPI
NtOpenTimer(OUT PHANDLE TimerHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes)
{
HANDLE hTimer;
KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
NTSTATUS Status;
PAGED_CODE();
/* Check Parameter Validity */
if (PreviousMode != KernelMode)
{
_SEH2_TRY
{
/* Make sure the pointer is valid */
ProbeForWriteHandle(TimerHandle);
}
_SEH2_EXCEPT(ExSystemExceptionFilter())
{
/* Return the exception code */
_SEH2_YIELD(return _SEH2_GetExceptionCode());
}
_SEH2_END;
}
/* Open the Timer */
Status = ObOpenObjectByName(ObjectAttributes,
ExTimerType,
PreviousMode,
NULL,
DesiredAccess,
NULL,
&hTimer);
if (NT_SUCCESS(Status))
{
/* Enter SEH */
_SEH2_TRY
{
/* Return the Timer Handle */
*TimerHandle = hTimer;
}
_SEH2_EXCEPT(ExSystemExceptionFilter())
{
/* Do nothing */
(void)0;
}
_SEH2_END;
}
/* Return to Caller */
return Status;
}
NTSTATUS
NTAPI
NtQueryTimer(IN HANDLE TimerHandle,
IN TIMER_INFORMATION_CLASS TimerInformationClass,
OUT PVOID TimerInformation,
IN ULONG TimerInformationLength,
OUT PULONG ReturnLength OPTIONAL)
{
PETIMER Timer;
KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
NTSTATUS Status;
PTIMER_BASIC_INFORMATION BasicInfo = TimerInformation;
PAGED_CODE();
/* Check Validity */
Status = DefaultQueryInfoBufferCheck(TimerInformationClass,
ExTimerInfoClass,
sizeof(ExTimerInfoClass) /
sizeof(ExTimerInfoClass[0]),
TimerInformation,
TimerInformationLength,
ReturnLength,
NULL,
PreviousMode);
if (!NT_SUCCESS(Status)) return Status;
/* Get the Timer Object */
Status = ObReferenceObjectByHandle(TimerHandle,
TIMER_QUERY_STATE,
ExTimerType,
PreviousMode,
(PVOID*)&Timer,
NULL);
if (NT_SUCCESS(Status))
{
/* Return the Basic Information */
_SEH2_TRY
{
/* Return the remaining time, corrected */
BasicInfo->TimeRemaining.QuadPart = Timer->
KeTimer.DueTime.QuadPart -
KeQueryInterruptTime();
/* Return the current state */
BasicInfo->SignalState = KeReadStateTimer(&Timer->KeTimer);
/* Return the buffer length if requested */
if (ReturnLength) *ReturnLength = sizeof(TIMER_BASIC_INFORMATION);
}
_SEH2_EXCEPT(ExSystemExceptionFilter())
{
/* Get the exception code */
Status = _SEH2_GetExceptionCode();
}
_SEH2_END;
/* Dereference Object */
ObDereferenceObject(Timer);
}
/* Return Status */
return Status;
}
NTSTATUS
NTAPI
NtSetTimer(IN HANDLE TimerHandle,
IN PLARGE_INTEGER DueTime,
IN PTIMER_APC_ROUTINE TimerApcRoutine OPTIONAL,
IN PVOID TimerContext OPTIONAL,
IN BOOLEAN WakeTimer,
IN LONG Period OPTIONAL,
OUT PBOOLEAN PreviousState OPTIONAL)
{
PETIMER Timer;
KIRQL OldIrql;
BOOLEAN State;
KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
PETHREAD Thread = PsGetCurrentThread();
LARGE_INTEGER TimerDueTime;
PETHREAD TimerThread;
ULONG DerefsToDo = 1;
NTSTATUS Status = STATUS_SUCCESS;
PAGED_CODE();
/* Check for a valid Period */
if (Period < 0) return STATUS_INVALID_PARAMETER_6;
/* Check if we need to probe */
if (PreviousMode != KernelMode)
{
_SEH2_TRY
{
/* Probe and capture the due time */
TimerDueTime = ProbeForReadLargeInteger(DueTime);
/* Probe the state pointer if one was passed */
if (PreviousState) ProbeForWriteBoolean(PreviousState);
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
/* Return the exception code */
_SEH2_YIELD(return _SEH2_GetExceptionCode());
}
_SEH2_END;
}
else
{
/* Capture the time directly */
TimerDueTime = *DueTime;
}
/* Get the Timer Object */
Status = ObReferenceObjectByHandle(TimerHandle,
TIMER_MODIFY_STATE,
ExTimerType,
PreviousMode,
(PVOID*)&Timer,
NULL);
/*
* Tell the user we don't support Wake Timers...
* when we have the ability to use/detect the Power Management
* functionality required to support them, make this check dependent
* on the actual PM capabilities
*/
if (WakeTimer) Status = STATUS_TIMER_RESUME_IGNORED;
/* Check status */
if (NT_SUCCESS(Status))
{
/* Lock the Timer */
KeAcquireSpinLock(&Timer->Lock, &OldIrql);
/* Cancel Running Timer */
if (Timer->ApcAssociated)
{
/* Get the Thread. */
TimerThread = CONTAINING_RECORD(Timer->TimerApc.Thread,
ETHREAD,
Tcb);
/* Lock its active list */
KeAcquireSpinLockAtDpcLevel(&TimerThread->ActiveTimerListLock);
/* Remove it */
RemoveEntryList(&Timer->ActiveTimerListEntry);
Timer->ApcAssociated = FALSE;
/* Unlock the list */
KeReleaseSpinLockFromDpcLevel(&TimerThread->ActiveTimerListLock);
/* Cancel the Timer */
KeCancelTimer(&Timer->KeTimer);
KeRemoveQueueDpc(&Timer->TimerDpc);
if (KeRemoveQueueApc(&Timer->TimerApc)) DerefsToDo++;
DerefsToDo++;
}
else
{
/* If timer was disabled, we still need to cancel it */
KeCancelTimer(&Timer->KeTimer);
}
/* Read the State */
State = KeReadStateTimer(&Timer->KeTimer);
/* Handle Wake Timers */
Timer->WakeTimer = WakeTimer;
KeAcquireSpinLockAtDpcLevel(&ExpWakeListLock);
if ((WakeTimer) && !(Timer->WakeTimerListEntry.Flink))
{
/* Insert it into the list */
InsertTailList(&ExpWakeList, &Timer->WakeTimerListEntry);
}
else if (!(WakeTimer) && (Timer->WakeTimerListEntry.Flink))
{
/* Remove it from the list */
RemoveEntryList(&Timer->WakeTimerListEntry);
Timer->WakeTimerListEntry.Flink = NULL;
}
KeReleaseSpinLockFromDpcLevel(&ExpWakeListLock);
/* Set up the APC Routine if specified */
Timer->Period = Period;
if (TimerApcRoutine)
{
/* Initialize the APC */
KeInitializeApc(&Timer->TimerApc,
&Thread->Tcb,
CurrentApcEnvironment,
ExpTimerApcKernelRoutine,
(PKRUNDOWN_ROUTINE)NULL,
(PKNORMAL_ROUTINE)TimerApcRoutine,
PreviousMode,
TimerContext);
/* Lock the Thread's Active List and Insert */
KeAcquireSpinLockAtDpcLevel(&Thread->ActiveTimerListLock);
InsertTailList(&Thread->ActiveTimerListHead,
&Timer->ActiveTimerListEntry);
Timer->ApcAssociated = TRUE;
KeReleaseSpinLockFromDpcLevel(&Thread->ActiveTimerListLock);
/* One less dereference to do */
DerefsToDo--;
}
/* Enable and Set the Timer */
KeSetTimerEx(&Timer->KeTimer,
TimerDueTime,
Period,
TimerApcRoutine ? &Timer->TimerDpc : NULL);
/* Unlock the Timer */
KeReleaseSpinLock(&Timer->Lock, OldIrql);
/* Dereference if it was previously enabled */
if (DerefsToDo) ObDereferenceObjectEx(Timer, DerefsToDo);
/* Check if we need to return the State */
if (PreviousState)
{
/* Enter SEH */
_SEH2_TRY
{
/* Return the Timer State */
*PreviousState = State;
}
_SEH2_EXCEPT(ExSystemExceptionFilter())
{
/* Do nothing */
(void)0;
}
_SEH2_END;
}
}
/* Return to Caller */
return Status;
}