/**
 * PROJECT:         ExectOS
 * COPYRIGHT:       See COPYING.md in the top level directory
 * FILE:            xtoskrnl/ke/timer.cc
 * DESCRIPTION:     Kernel timer object support
 * DEVELOPERS:      Rafal Kupiec <belliash@codingworkshop.eu.org>
 *                  Aiken Harris <harraiken91@gmail.com>
 */

#include <xtos.hh>


/**
 * Cancels the timer.
 *
 * @param Timer
 *        Supplies a pointer to a timer object.
 *
 * @return This routine returns TRUE if the cancelled timer was set, or FALSE otherwise.
 *
 * @since XT 1.0
 */
XTAPI
BOOLEAN
KE::Timer::CancelTimer(IN PKTIMER Timer)
{
    BOOLEAN Result;
    KRUNLEVEL RunLevel;

    /* Set default result value */
    Result = FALSE;

    /* Raise run level and acquire dispatcher lock */
    RunLevel = KE::RunLevel::RaiseRunLevel(SYNC_LEVEL);
    KE::SpinLock::AcquireQueuedSpinLock(DispatcherLock);

    /* Check timer status */
    if(Timer->Header.Inserted)
    {
        /* Remove the timer from the list */
        RemoveTimer(Timer);
        Result = TRUE;
    }

    /* Release dispatcher lock and process the deferred ready list */
    KE::SpinLock::ReleaseQueuedSpinLock(DispatcherLock);
    KE::Dispatcher::ExitDispatcher(RunLevel);

    /* Return result */
    return Result;
}

/**
 * Clears the signal state of the timer.
 *
 * @param Timer
 *        Supplies a pointer to a timer object.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTAPI
VOID
KE::Timer::ClearTimer(IN PKTIMER Timer)
{
    /* Clear signal state */
    Timer->Header.SignalState = 0;
}

/**
 * Reads the current signal state of the given timer.
 *
 * @param Timer
 *        Supplies a pointer to a timer object.
 *
 * @return This routine returns TRUE if the timer is set, or FALSE otherwise.
 *
 * @since XT 1.0
 */
XTAPI
BOOLEAN
KE::Timer::GetState(IN PKTIMER Timer)
{
    /* Return timer state */
    return (BOOLEAN)Timer->Header.SignalState;
}

/**
 * Initializes an extended kernel timer.
 *
 * @param Timer
 *        Supplies a pointer to a timer object.
 *
 * @param Type
 *        Supplies the type of the timer.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTAPI
VOID
KE::Timer::InitializeTimer(OUT PKTIMER Timer,
                           IN KTIMER_TYPE Type)
{
    /* Initialize the header */
    Timer->Header.Type = TimerNotificationObject + (UCHAR)Type;
    Timer->Header.Inserted = 0;
    Timer->Header.SignalState = 0;

    /* Initialize the timer data */
    Timer->DueTime.QuadPart = 0;
    Timer->Period = 0;

    /* Initialize linked lists */
    RTL::LinkedList::InitializeListHead(&Timer->Header.WaitListHead);
    RTL::LinkedList::InitializeListHead(&Timer->TimerListEntry);
}

/**
 * Queries the timer's interrupt due time.
 *
 * @param Timer
 *        Supplies a pointer to a timer object.
 *
 * @return This routine returns the time remaining on the timer, or 0 if the timer is not set.
 *
 * @since XT 1.0
 */
XTAPI
ULONGLONG
KE::Timer::QueryTimer(IN PKTIMER Timer)
{
    KRUNLEVEL RunLevel;
    ULONGLONG DueTime;

    /* Set initial due time */
    DueTime = 0;

    /* Raise run level and acquire dispatcher lock */
    RunLevel = KE::RunLevel::RaiseRunLevel(SYNC_LEVEL);
    KE::SpinLock::AcquireQueuedSpinLock(DispatcherLock);

    /* Check timer status */
    if(Timer->Header.Inserted)
    {
        /* Get timer's due time */
        DueTime = Timer->DueTime.QuadPart;
    }

    /* Release dispatcher lock and process the deferred ready list */
    KE::SpinLock::ReleaseQueuedSpinLock(DispatcherLock);
    KE::Dispatcher::ExitDispatcher(RunLevel);

    /* Return timer's due time */
    return DueTime;
}

/**
 * Removes a specified timer from the timer list.
 *
 * @param Timer
 *        Supplies a pointer to a timer object.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTAPI
VOID
KE::Timer::RemoveTimer(IN OUT PKTIMER Timer)
{
    /* Remove the timer from the list */
    Timer->Header.Inserted = FALSE;
    RTL::LinkedList::RemoveEntryList(&Timer->TimerListEntry);
}

/**
 * Sets the supplied timer to expire at the specified time.
 *
 * @param Timer
 *        Supplies a pointer to a timer object.
 *
 * @param DueTime
 *        Supplies the time at which the timer should expire (both absolute and relative times are supported).
 *
 * @param Period
 *        Supplies the timer period.
 *
 * @param Dpc
 *        Supplies a pointer to a Deferred Procedure Call (DPC) object.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTAPI
VOID
KE::Timer::SetTimer(IN PKTIMER Timer,
                    IN LARGE_INTEGER DueTime,
                    IN LONG Period,
                    IN PKDPC Dpc)
{
    UNIMPLEMENTED;
}

/**
 * Verifies if any system timers have expired for the current tick and requests a software
 * DISPATCH interrupt to process them if necessary.
 *
 * @param Prcb
 *        Supplies a pointer to the current Processor Control Block.
 *
 * @param TrapFrame
 *        Supplies a pointer to the trap frame representing the interrupted context.
 *
 * @param Time
 *        Supplies the current absolute interrupt time.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTAPI
VOID
KE::Timer::VerifySystemTimerExpiration(IN PKPROCESSOR_CONTROL_BLOCK Prcb,
                                       IN PKTRAP_FRAME TrapFrame,
                                       IN LARGE_INTEGER Time)
{
    LARGE_INTEGER TickCount;
    ULONG TimerHand;
    PKTIMER Timer;

    /* Retrieve the current system tick count and calculate the index into the timer table */
    TickCount = KE::SharedData::GetTickCount();
    TimerHand = TickCount.LowPart & (KTIMER_TABLE_SIZE - 1);

    /* Check if there are any active timers scheduled */
    if(!RTL::LinkedList::ListEmpty(&TimerTableListHead[TimerHand]))
    {
        /* Retrieve the first timer object from the list */
        Timer = CONTAIN_RECORD((&TimerTableListHead[TimerHand])->Flink, KTIMER, TimerListEntry);

        /* Check if the timer due time has been reached */
        if(Timer->DueTime.QuadPart <= Time.QuadPart)
        {
            /* Ensure there is no pending timer expiration request */
            if(!Prcb->TimerRequest)
            {
                /* Register the timer expiration request and record a timer slot */
                Prcb->TimerRequest = (ULONG_PTR)TrapFrame;
                Prcb->TimerHand = TimerHand;

                /* Request a DPC to safely retire the timer */
                HL::Irq::SendSoftwareInterrupt(DISPATCH_LEVEL);
            }
        }
    }
}
