* 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
*/
#include <ntoskrnl.h>
#define NDEBUG
#include <debug.h>
#define TICKSPERMINUTE 600000000
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;
* If successful, this function sets the following global variable:
* ExpTimeZoneInfo
*--*/
static
BOOLEAN
ExpGetTimeZoneId(
_In_ PLARGE_INTEGER TimeNow,
_Out_ PULONG TimeZoneId,
_Out_ PLARGE_INTEGER NewTimeZoneBias)
{
LARGE_INTEGER StandardTime;
LARGE_INTEGER DaylightTime;
LARGE_INTEGER LocalTimeNow = *TimeNow;
NTSTATUS Status;
DPRINT("ExpGetTimeZoneId\n");
Status = RtlQueryTimeZoneInformation(&ExpTimeZoneInfo);
if (!NT_SUCCESS(Status))
{
DPRINT1("RtlQueryTimeZoneInformation failed (Status 0x%08lx)\n", Status);
return FALSE;
}
NewTimeZoneBias->QuadPart = (LONGLONG)ExpTimeZoneInfo.Bias * TICKSPERMINUTE;
if (ExpTimeZoneInfo.StandardDate.Month != 0 &&
ExpTimeZoneInfo.DaylightDate.Month != 0)
{
if (!RtlCutoverTimeToSystemTime(&ExpTimeZoneInfo.StandardDate,
&StandardTime,
&LocalTimeNow,
TRUE))
{
DPRINT1("RtlCutoverTimeToSystemTime for StandardDate failed\n");
return FALSE;
}
if (!RtlCutoverTimeToSystemTime(&ExpTimeZoneInfo.DaylightDate,
&DaylightTime,
&LocalTimeNow,
TRUE))
{
DPRINT1("RtlCutoverTimeToSystemTime for DaylightDate failed\n");
return FALSE;
}
if (DaylightTime.QuadPart < StandardTime.QuadPart)
{
if ((LocalTimeNow.QuadPart >= DaylightTime.QuadPart) &&
(LocalTimeNow.QuadPart < StandardTime.QuadPart))
{
DPRINT("Daylight time\n");
*TimeZoneId = TIME_ZONE_ID_DAYLIGHT;
NewTimeZoneBias->QuadPart += (LONGLONG)ExpTimeZoneInfo.DaylightBias * TICKSPERMINUTE;
}
else
{
DPRINT("Standard time\n");
*TimeZoneId = TIME_ZONE_ID_STANDARD;
NewTimeZoneBias->QuadPart += (LONGLONG)ExpTimeZoneInfo.StandardBias * TICKSPERMINUTE;
}
}
else
{
if ((LocalTimeNow.QuadPart >= StandardTime.QuadPart) &&
(LocalTimeNow.QuadPart < DaylightTime.QuadPart))
{
DPRINT("Standard time\n");
*TimeZoneId = TIME_ZONE_ID_STANDARD;
NewTimeZoneBias->QuadPart += (LONGLONG)ExpTimeZoneInfo.StandardBias * TICKSPERMINUTE;
}
else
{
DPRINT("Daylight time\n");
*TimeZoneId = TIME_ZONE_ID_DAYLIGHT;
NewTimeZoneBias->QuadPart += (LONGLONG)ExpTimeZoneInfo.DaylightBias * TICKSPERMINUTE;
}
}
}
else
{
*TimeZoneId = TIME_ZONE_ID_UNKNOWN;
}
return TRUE;
}
* @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)
{
KeEnterCriticalRegion();
if (!(ExAcquireResourceExclusiveLite(&ExpTimeRefreshLock, Wait)))
{
KeLeaveCriticalRegion();
return FALSE;
}
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)
{
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;
ExAcquireTimeRefreshLock(TRUE);
CurrentIncrement = KeTimeIncrement;
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++;
if (DesiredTime < KeMinimumIncrement) DesiredTime = KeMinimumIncrement;
if (DesiredTime < KeTimeIncrement)
{
KeSetSystemAffinityThread(1);
CurrentIncrement = HalSetTimeIncrement(DesiredTime);
KeRevertToUserAffinityThread();
KeTimeIncrement = CurrentIncrement;
}
}
else
{
if (ExpKernelResolutionCount)
{
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)
{
KeSetSystemAffinityThread(1);
CurrentIncrement = HalSetTimeIncrement(KeMaximumIncrement);
KeRevertToUserAffinityThread();
KeTimeIncrement = CurrentIncrement;
}
}
}
}
ExReleaseTimeRefreshLock();
return CurrentIncrement;
}
VOID
NTAPI
ExUpdateSystemTimeFromCmos(IN BOOLEAN UpdateInterruptTime,
IN ULONG MaxSepInSeconds)
{
return;
}
BOOLEAN
NTAPI
ExRefreshTimeZoneInformation(IN PLARGE_INTEGER CurrentBootTime)
{
LARGE_INTEGER CurrentTime, NewTimeZoneBias;
BOOLEAN Success;
DPRINT("ExRefreshTimeZoneInformation\n");
Success = ExpGetTimeZoneId(CurrentBootTime, &ExpTimeZoneId, &NewTimeZoneBias);
if (!Success)
{
DPRINT1("ExpGetTimeZoneId failed\n");
return FALSE;
}
DPRINT("ExpTimeZoneId is %lu\n", ExpTimeZoneId);
ExpTimeZoneBias = NewTimeZoneBias;
KiWriteSystemTime(&SharedUserData->TimeZoneBias, ExpTimeZoneBias);
SharedUserData->TimeZoneId = ExpTimeZoneId;
KeBootTime.QuadPart += ExpTimeZoneBias.QuadPart;
KeQuerySystemTime(&CurrentTime);;
CurrentTime.QuadPart += ExpTimeZoneBias.QuadPart;
KiWriteSystemTime(&SharedUserData->SystemTime, CurrentTime);
return TRUE;
}
NTSTATUS
ExpSetTimeZoneInformation(PRTL_TIME_ZONE_INFORMATION TimeZoneInformation)
{
LARGE_INTEGER LocalTime, SystemTime, OldTime, NewTimeZoneBias;
TIME_FIELDS TimeFields;
BOOLEAN Success;
NTSTATUS Status;
LARGE_INTEGER LocalTimeNow;
DPRINT("ExpSetTimeZoneInformation called\n");
Status = RtlQueryTimeZoneInformation(&ExpTimeZoneInfo);
if (!NT_SUCCESS(Status))
{
DPRINT1("RtlQueryTimeZoneInformation failed (Status 0x%08lx)\n", Status);
return FALSE;
}
NewTimeZoneBias.QuadPart = (LONGLONG)ExpTimeZoneInfo.Bias * TICKSPERMINUTE;
KeQuerySystemTime(&SystemTime);
LocalTimeNow = SystemTime;
LocalTimeNow.QuadPart -= NewTimeZoneBias.QuadPart;
Success = ExpGetTimeZoneId(&LocalTimeNow, &ExpTimeZoneId, &NewTimeZoneBias);
if (!Success)
{
DPRINT1("ExpGetTimeZoneId failed\n");
return STATUS_UNSUCCESSFUL;
}
DPRINT("ExpTimeZoneId is %lu\n", ExpTimeZoneId);
ExpTimeZoneBias = NewTimeZoneBias;
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);
HalQueryRealTimeClock(&TimeFields);
RtlTimeFieldsToTime(&TimeFields, &LocalTime);
ExpTimeZoneBias.QuadPart = ((LONGLONG)(TimeZoneInformation->Bias +
TimeZoneInformation->StandardBias)) *
TICKSPERMINUTE;
if (ExpTimeZoneId == TIME_ZONE_ID_DAYLIGHT)
ExpTimeZoneBias.QuadPart += (LONGLONG)ExpTimeZoneInfo.DaylightBias * TICKSPERMINUTE;
RtlCopyMemory(&ExpTimeZoneInfo,
TimeZoneInformation,
sizeof(RTL_TIME_ZONE_INFORMATION));
KiWriteSystemTime(&SharedUserData->TimeZoneBias, ExpTimeZoneBias);
SharedUserData->TimeZoneId = ExpTimeZoneId;
DPRINT("New time zone bias: %I64d minutes\n",
ExpTimeZoneBias.QuadPart / TICKSPERMINUTE);
ExLocalTimeToSystemTime(&LocalTime, &SystemTime);
KeSetSystemTime(&SystemTime, &OldTime, FALSE, NULL);
PoNotifySystemTimeSet();
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;
RTL_TIME_ZONE_INFORMATION TimeZoneInformation = { 0 };
ULONG TimeZoneIdSave;
PAGED_CODE();
if (!SystemTime)
{
UNIMPLEMENTED;
return STATUS_NOT_IMPLEMENTED;
}
if (PreviousMode != KernelMode)
{
_SEH2_TRY
{
NewSystemTime = ProbeForReadLargeInteger(SystemTime);
if (PreviousTime) ProbeForWriteLargeInteger(PreviousTime);
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
_SEH2_YIELD(return _SEH2_GetExceptionCode());
}
_SEH2_END;
}
else
{
NewSystemTime = *SystemTime;
}
if (!SeSinglePrivilegeCheck(SeSystemtimePrivilege, PreviousMode))
{
DPRINT1("NtSetSystemTime: Caller requires the "
"SeSystemtimePrivilege privilege!\n");
return STATUS_PRIVILEGE_NOT_HELD;
}
ExSystemTimeToLocalTime(&NewSystemTime, &LocalTime);
RtlTimeToTimeFields(&LocalTime, &TimeFields);
HalSetRealTimeClock(&TimeFields);
KeSetSystemTime(&NewSystemTime, &OldSystemTime, FALSE, NULL);
PoNotifySystemTimeSet();
if (PreviousTime)
{
_SEH2_TRY
{
*PreviousTime = OldSystemTime;
}
_SEH2_EXCEPT(ExSystemExceptionFilter())
{
Status = _SEH2_GetExceptionCode();
}
_SEH2_END;
}
Status = RtlQueryTimeZoneInformation(&TimeZoneInformation);
if (!NT_SUCCESS(Status))
{
DPRINT1("RtlQueryTimeZoneInformation failed (Status 0x%08lx)\n", Status);
}
TimeZoneIdSave = ExpTimeZoneId;
ExpSetTimeZoneInformation(&TimeZoneInformation);
if (ExpTimeZoneId != TimeZoneIdSave)
{
DPRINT("Daylight Time and Standard Time are switching\n");
KeSetSystemTime(&NewSystemTime, &OldSystemTime, FALSE, NULL);
PoNotifySystemTimeSet();
}
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();
if (PreviousMode != KernelMode)
{
_SEH2_TRY
{
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)
{
_SEH2_YIELD(return _SEH2_GetExceptionCode());
}
_SEH2_END;
}
else
{
KeQuerySystemTime(SystemTime);
}
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();
if (PreviousMode != KernelMode)
{
_SEH2_TRY
{
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)
{
_SEH2_YIELD(return _SEH2_GetExceptionCode());
}
_SEH2_END;
}
else
{
*MinimumResolution = KeMaximumIncrement;
*MaximumResolution = KeMinimumIncrement;
*ActualResolution = KeTimeIncrement;
}
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;
if (PreviousMode != KernelMode)
{
_SEH2_TRY
{
ProbeForWriteUlong(CurrentResolution);
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
_SEH2_YIELD(return _SEH2_GetExceptionCode());
}
_SEH2_END;
}
NewResolution = ExSetTimerResolution(DesiredResolution, SetResolution);
if (PreviousMode != KernelMode)
{
_SEH2_TRY
{
*CurrentResolution = NewResolution;
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
_SEH2_YIELD(return _SEH2_GetExceptionCode());
}
_SEH2_END;
}
else
{
*CurrentResolution = NewResolution;
}
if (SetResolution || Process->SetTimerResolution)
{
Status = STATUS_SUCCESS;
}
else
{
Status = STATUS_TIMER_RESOLUTION_NOT_SET;
}
Process->SetTimerResolution = SetResolution;
return Status;
}