/*
 * COPYRIGHT:       See COPYING in the top level directory
 * PROJECT:         ReactOS Kernel
 * FILE:            ntoskrnl/ex/time.c
 * PURPOSE:         Time and Timezone Management
 * PROGRAMMERS:     Eric Kohl
 *                  Thomas Weidenmueller
 */

/* INCLUDES *****************************************************************/

#include <ntoskrnl.h>
#define NDEBUG
#include <debug.h>

#define TICKSPERMINUTE  600000000

/* GLOBALS ******************************************************************/

/* Note: Bias[minutes] = UTC - local time */
RTL_TIME_ZONE_INFORMATION ExpTimeZoneInfo;
ULONG ExpLastTimeZoneBias = -1;
LARGE_INTEGER ExpTimeZoneBias;
ULONG ExpAltTimeZoneBias;
ULONG ExpTimeZoneId;
ULONG ExpTickCountMultiplier;
ERESOURCE ExpTimeRefreshLock;
ULONG ExpKernelResolutionCount = 0;
ULONG ExpTimerResolutionCount = 0;

/* FUNCTIONS ****************************************************************/

/*++
 * @name ExAcquireTimeRefreshLock
 *
 *     The ExReleaseTimeRefreshLock routine acquires the system-wide lock used
 *     to synchronize clock interrupt frequency changes.
 *
 * @param Wait
 *        If TRUE, the system will block the caller thread waiting for the lock
 *        to become available. If FALSE, the routine will fail if the lock has
 *        already been acquired.
 *
 * @return Boolean value indicating success or failure of the lock acquisition.
 *
 * @remarks None.
 *
 *--*/
BOOLEAN
NTAPI
ExAcquireTimeRefreshLock(IN BOOLEAN Wait)
{
    /* Block APCs */
    KeEnterCriticalRegion();

    /* Attempt lock acquisition */
    if (!(ExAcquireResourceExclusiveLite(&ExpTimeRefreshLock, Wait)))
    {
        /* Lock was not acquired, enable APCs and fail */
        KeLeaveCriticalRegion();
        return FALSE;
    }

    /* Lock has been acquired */
    return TRUE;
}

/*++
 * @name ExReleaseTimeRefreshLock
 *
 *     The ExReleaseTimeRefreshLock routine releases the system-wide lock used
 *     to synchronize clock interrupt frequency changes.
 *
 * @param None.
 *
 * @return None.
 *
 * @remarks None.
 *
 *--*/
VOID
NTAPI
ExReleaseTimeRefreshLock(VOID)
{
    /* Release the lock and re-enable APCs */
    ExReleaseResourceLite(&ExpTimeRefreshLock);
    KeLeaveCriticalRegion();
}

/*++
 * @name ExSetTimerResolution
 * @exported
 *
 *     The KiInsertQueueApc routine modifies the frequency at which the system
 *     clock interrupts.
 *
 * @param DesiredTime
 *        Specifies the amount of time that should elapse between each timer
 *        interrupt, in 100-nanosecond units.
 *
 *        This parameter is ignored if SetResolution is FALSE.
 *
 * @param SetResolution
 *        If TRUE, the call is a request to set the clock interrupt frequency to
 *        the value specified by DesiredTime. If FALSE, the call is a request to
 *        restore the clock interrupt frequency to the system's default value.
 *
 * @return New timer resolution, in 100-nanosecond ticks.
 *
 * @remarks (1) The clock frequency is changed only if the DesiredTime value is
 *              less than the current setting.
 *
 *          (2) The routine just returns the current setting if the DesiredTime
 *              value is greater than what is currently set.
 *
 *          (3) If the DesiredTime value is less than the system clock can
 *              support, the routine uses the smallest resolution the system can
 *              support, and returns that value.
 *
 *          (4) If multiple drivers have attempted to change the clock interrupt
 *              frequency, the system will only restore the default frequency
 *              once ALL drivers have called the routine with SetResolution set
 *              to FALSE.
 *
 *          NB. This routine synchronizes with IRP_MJ_POWER requests through the
 *              TimeRefreshLock.
 *
 *--*/
ULONG
NTAPI
ExSetTimerResolution(IN ULONG DesiredTime,
                     IN BOOLEAN SetResolution)
{
    ULONG CurrentIncrement;

    /* Wait for clock interrupt frequency and power requests to synchronize */
    ExAcquireTimeRefreshLock(TRUE);

    /* Obey remark 2*/
    CurrentIncrement = KeTimeIncrement;

    /* Check the type of operation this is */
    if (SetResolution)
    {
        /*
         * If this is the first kernel change, bump the timer resolution change
         * count, then bump the kernel change count as well.
         *
         * These two variables are tracked differently since user-mode processes
         * can also change the timer resolution through the NtSetTimerResolution
         * system call. A per-process flag in the EPROCESS then stores the per-
         * process change state.
         *
         */
        if (!ExpKernelResolutionCount++) ExpTimerResolutionCount++;

        /* Obey remark 3 */
        if (DesiredTime < KeMinimumIncrement) DesiredTime = KeMinimumIncrement;

        /* Obey remark 1 */
        if (DesiredTime < KeTimeIncrement)
        {
            /* Force this thread on CPU zero, since we don't want it to drift */
            KeSetSystemAffinityThread(1);

            /* Now call the platform driver (HAL) to make the change */
            CurrentIncrement = HalSetTimeIncrement(DesiredTime);

            /* Put the thread back to its original affinity */
            KeRevertToUserAffinityThread();

            /* Finally, keep track of the new value in the kernel */
            KeTimeIncrement = CurrentIncrement;
        }
    }
    else
    {
        /* First, make sure that a driver has actually changed the resolution */
        if (ExpKernelResolutionCount)
        {
            /* Obey remark 4 */
            if (--ExpKernelResolutionCount)
            {
                /*
                 * All kernel drivers have requested the original frequency to
                 * be restored, but there might still be user processes with an
                 * ongoing clock interrupt frequency change, so make sure that
                 * this isn't the case.
                 */
                if (--ExpTimerResolutionCount)
                {
                    /* Force this thread on one CPU so that it doesn't drift */
                    KeSetSystemAffinityThread(1);

                    /* Call the HAL to restore the frequency to its default */
                    CurrentIncrement = HalSetTimeIncrement(KeMaximumIncrement);

                    /* Put the thread back to its original affinity */
                    KeRevertToUserAffinityThread();

                    /* Finally, keep track of the new value in the kernel */
                    KeTimeIncrement = CurrentIncrement;
                }
            }
        }
    }

    /* Release the clock interrupt frequency lock since changes are done */
    ExReleaseTimeRefreshLock();

    /* And return the current value -- which could reflect the new frequency */
    return CurrentIncrement;
}

VOID
NTAPI
ExUpdateSystemTimeFromCmos(IN BOOLEAN UpdateInterruptTime,
                           IN ULONG MaxSepInSeconds)
{
    /* FIXME: TODO */
    return;
}

BOOLEAN
NTAPI
ExRefreshTimeZoneInformation(IN PLARGE_INTEGER CurrentBootTime)
{
    LARGE_INTEGER CurrentTime;
    NTSTATUS Status;

    /* Read time zone information from the registry */
    Status = RtlQueryTimeZoneInformation(&ExpTimeZoneInfo);
    if (!NT_SUCCESS(Status))
    {
        /* Failed, clear all data */
        RtlZeroMemory(&ExpTimeZoneInfo, sizeof(RTL_TIME_ZONE_INFORMATION));
        ExpTimeZoneBias.QuadPart = (LONGLONG)0;
        ExpTimeZoneId = TIME_ZONE_ID_UNKNOWN;
    }
    else
    {
        /* FIXME: Calculate transition dates */

        /* Set bias and ID */
        ExpTimeZoneBias.QuadPart = ((LONGLONG)(ExpTimeZoneInfo.Bias +
            ExpTimeZoneInfo.StandardBias)) *
            TICKSPERMINUTE;
        ExpTimeZoneId = TIME_ZONE_ID_STANDARD;
    }

    /* Change it for user-mode applications */
    SharedUserData->TimeZoneBias.High1Time = ExpTimeZoneBias.u.HighPart;
    SharedUserData->TimeZoneBias.High2Time = ExpTimeZoneBias.u.HighPart;
    SharedUserData->TimeZoneBias.LowPart = ExpTimeZoneBias.u.LowPart;
    SharedUserData->TimeZoneId = ExpTimeZoneId;

    /* Convert boot time from local time to UTC */
    KeBootTime.QuadPart += ExpTimeZoneBias.QuadPart;

    /* Convert system time from local time to UTC */
    do
    {
        CurrentTime.u.HighPart = SharedUserData->SystemTime.High1Time;
        CurrentTime.u.LowPart = SharedUserData->SystemTime.LowPart;
    } while (CurrentTime.u.HighPart != SharedUserData->SystemTime.High2Time);

    /* Change it for user-mode applications */
    CurrentTime.QuadPart += ExpTimeZoneBias.QuadPart;
    SharedUserData->SystemTime.LowPart = CurrentTime.u.LowPart;
    SharedUserData->SystemTime.High1Time = CurrentTime.u.HighPart;
    SharedUserData->SystemTime.High2Time = CurrentTime.u.HighPart;

    /* Return success */
    return TRUE;
}

NTSTATUS
ExpSetTimeZoneInformation(PRTL_TIME_ZONE_INFORMATION TimeZoneInformation)
{
    LARGE_INTEGER LocalTime, SystemTime, OldTime;
    TIME_FIELDS TimeFields;
    DPRINT("ExpSetTimeZoneInformation() called\n");

    DPRINT("Old time zone bias: %d minutes\n", ExpTimeZoneInfo.Bias);
    DPRINT("Old time zone standard bias: %d minutes\n",
            ExpTimeZoneInfo.StandardBias);
    DPRINT("New time zone bias: %d minutes\n", TimeZoneInformation->Bias);
    DPRINT("New time zone standard bias: %d minutes\n",
            TimeZoneInformation->StandardBias);

    /* Get the local time */
    HalQueryRealTimeClock(&TimeFields);
    RtlTimeFieldsToTime(&TimeFields, &LocalTime);

    /* FIXME: Calculate transition dates */

    /* Calculate the bias and set the ID */
    ExpTimeZoneBias.QuadPart = ((LONGLONG)(TimeZoneInformation->Bias +
                                           TimeZoneInformation->StandardBias)) *
                                           TICKSPERMINUTE;
    ExpTimeZoneId = TIME_ZONE_ID_STANDARD;

    /* Copy the timezone information */
    RtlCopyMemory(&ExpTimeZoneInfo,
                  TimeZoneInformation,
                  sizeof(RTL_TIME_ZONE_INFORMATION));

    /* Set the new time zone information */
    SharedUserData->TimeZoneBias.High1Time = ExpTimeZoneBias.u.HighPart;
    SharedUserData->TimeZoneBias.High2Time = ExpTimeZoneBias.u.HighPart;
    SharedUserData->TimeZoneBias.LowPart = ExpTimeZoneBias.u.LowPart;
    SharedUserData->TimeZoneId = ExpTimeZoneId;

    DPRINT("New time zone bias: %I64d minutes\n",
            ExpTimeZoneBias.QuadPart / TICKSPERMINUTE);

    /* Calculate the new system time */
    ExLocalTimeToSystemTime(&LocalTime, &SystemTime);

    /* Set the new system time */
    KeSetSystemTime(&SystemTime, &OldTime, FALSE, NULL);

    /* Return success */
    DPRINT("ExpSetTimeZoneInformation() done\n");
    return STATUS_SUCCESS;
}

/*
 * FUNCTION: Sets the system time.
 * PARAMETERS:
 *        NewTime - Points to a variable that specified the new time
 *        of day in the standard time format.
 *        OldTime - Optionally points to a variable that receives the
 *        old time of day in the standard time format.
 * RETURNS: Status
 */
NTSTATUS
NTAPI
NtSetSystemTime(IN PLARGE_INTEGER SystemTime,
                OUT PLARGE_INTEGER PreviousTime OPTIONAL)
{
    LARGE_INTEGER OldSystemTime;
    LARGE_INTEGER NewSystemTime;
    LARGE_INTEGER LocalTime;
    TIME_FIELDS TimeFields;
    KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
    NTSTATUS Status = STATUS_SUCCESS;
    PAGED_CODE();

    /* Check if we were called from user-mode */
    if (PreviousMode != KernelMode)
    {
        _SEH2_TRY
        {
            /* Verify the time pointers */
            NewSystemTime = ProbeForReadLargeInteger(SystemTime);
            if(PreviousTime) ProbeForWriteLargeInteger(PreviousTime);
        }
        _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
        {
            /* Return the exception code */
            _SEH2_YIELD(return _SEH2_GetExceptionCode());
        }
        _SEH2_END;
    }
    else
    {
        /* Reuse the pointer */
        NewSystemTime = *SystemTime;
    }

    /* Make sure we have permission to change the time */
    if (!SeSinglePrivilegeCheck(SeSystemtimePrivilege, PreviousMode))
    {
        DPRINT1("NtSetSystemTime: Caller requires the "
                "SeSystemtimePrivilege privilege!\n");
        return STATUS_PRIVILEGE_NOT_HELD;
    }

    /* Convert the time and set it in HAL */
    ExSystemTimeToLocalTime(&NewSystemTime, &LocalTime);
    RtlTimeToTimeFields(&LocalTime, &TimeFields);
    HalSetRealTimeClock(&TimeFields);

    /* Now set system time */
    KeSetSystemTime(&NewSystemTime, &OldSystemTime, FALSE, NULL);

    /* Check if caller wanted previous time */
    if (PreviousTime)
    {
        /* Enter SEH Block for return */
        _SEH2_TRY
        {
            /* Return the previous time */
            *PreviousTime = OldSystemTime;
        }
        _SEH2_EXCEPT(ExSystemExceptionFilter())
        {
            /* Get the exception code */
            Status = _SEH2_GetExceptionCode();
        }
        _SEH2_END;
    }

    /* Return status */
    return Status;
}

/*
 * FUNCTION: Retrieves the system time.
 * PARAMETERS:
 *          CurrentTime - Points to a variable that receives the current
 *          time of day in the standard time format.
 */
NTSTATUS
NTAPI
NtQuerySystemTime(OUT PLARGE_INTEGER SystemTime)
{
    KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
    PAGED_CODE();

    /* Check if we were called from user-mode */
    if (PreviousMode != KernelMode)
    {
        _SEH2_TRY
        {
            /* Verify the time pointer */
            ProbeForWriteLargeInteger(SystemTime);

            /*
             * It's safe to pass the pointer directly to KeQuerySystemTime
             * as it's just a basic copy to this pointer. If it raises an
             * exception nothing dangerous can happen!
             */
            KeQuerySystemTime(SystemTime);
        }
        _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
        {
            /* Return the exception code */
            _SEH2_YIELD(return _SEH2_GetExceptionCode());
        }
        _SEH2_END;
    }
    else
    {
        /* Query the time directly */
        KeQuerySystemTime(SystemTime);
    }

    /* Return success */
    return STATUS_SUCCESS;
}

/*
 * @implemented
 */
VOID
NTAPI
ExLocalTimeToSystemTime(PLARGE_INTEGER LocalTime,
                        PLARGE_INTEGER SystemTime)
{
    SystemTime->QuadPart = LocalTime->QuadPart + ExpTimeZoneBias.QuadPart;
}

/*
 * @implemented
 */
VOID
NTAPI
ExSystemTimeToLocalTime(PLARGE_INTEGER SystemTime,
                        PLARGE_INTEGER LocalTime)
{
    LocalTime->QuadPart = SystemTime->QuadPart - ExpTimeZoneBias.QuadPart;
}

/*
 * @implemented
 */
NTSTATUS
NTAPI
NtQueryTimerResolution(OUT PULONG MinimumResolution,
                       OUT PULONG MaximumResolution,
                       OUT PULONG ActualResolution)
{
    KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();

    /* Check if the call came from user-mode */
    if (PreviousMode != KernelMode)
    {
        _SEH2_TRY
        {
            /* Probe the parameters */
            ProbeForWriteUlong(MinimumResolution);
            ProbeForWriteUlong(MaximumResolution);
            ProbeForWriteUlong(ActualResolution);

            /*
             * Set the parameters to the actual values.
             *
             * NOTE:
             * MinimumResolution corresponds to the biggest time increment and
             * MaximumResolution corresponds to the smallest time increment.
             */
            *MinimumResolution = KeMaximumIncrement;
            *MaximumResolution = KeMinimumIncrement;
            *ActualResolution  = KeTimeIncrement;
        }
        _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
        {
            /* Return the exception code */
            _SEH2_YIELD(return _SEH2_GetExceptionCode());
        }
        _SEH2_END;
    }
    else
    {
        /* Set the parameters to the actual values */
        *MinimumResolution = KeMaximumIncrement;
        *MaximumResolution = KeMinimumIncrement;
        *ActualResolution  = KeTimeIncrement;
    }

    /* Return success */
    return STATUS_SUCCESS;
}

/*
 * @implemented
 */
NTSTATUS
NTAPI
NtSetTimerResolution(IN ULONG DesiredResolution,
                     IN BOOLEAN SetResolution,
                     OUT PULONG CurrentResolution)
{
    NTSTATUS Status;
    KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
    PEPROCESS Process = PsGetCurrentProcess();
    ULONG NewResolution;

    /* Check if the call came from user-mode */
    if (PreviousMode != KernelMode)
    {
        _SEH2_TRY
        {
            /* Probe the parameter */
            ProbeForWriteUlong(CurrentResolution);
        }
        _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
        {
            /* Return the exception code */
            _SEH2_YIELD(return _SEH2_GetExceptionCode());
        }
        _SEH2_END;
    }

    /* Set and return the new resolution */
    NewResolution = ExSetTimerResolution(DesiredResolution, SetResolution);

    if (PreviousMode != KernelMode)
    {
        _SEH2_TRY
        {
            *CurrentResolution = NewResolution;
        }
        _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
        {
            /* Return the exception code */
            _SEH2_YIELD(return _SEH2_GetExceptionCode());
        }
        _SEH2_END;
    }
    else
    {
        *CurrentResolution = NewResolution;
    }

    if (SetResolution || Process->SetTimerResolution)
    {
        /* The resolution has been changed now or in an earlier call */
        Status = STATUS_SUCCESS;
    }
    else
    {
        /* The resolution hasn't been changed */
        Status = STATUS_TIMER_RESOLUTION_NOT_SET;
    }

    /* Update the flag */
    Process->SetTimerResolution = SetResolution;

    return Status;
}

/* EOF */