/* * 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 #define NDEBUG #include #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 */