/*
 * 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;
}