mirror of
https://github.com/reactos/reactos.git
synced 2024-12-29 14:03:32 +08:00
757 lines
22 KiB
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;
|
|
}
|