mirror of
https://github.com/reactos/reactos.git
synced 2024-11-18 13:01:40 +00:00
595 lines
17 KiB
C
595 lines
17 KiB
C
/*
|
|
* 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 */
|
|
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(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(PTIME_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(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 */
|