2 * COPYRIGHT: See COPYING in the top level directory
3 * PROJECT: ReactOS Kernel
4 * FILE: ntoskrnl/ex/time.c
5 * PURPOSE: Time and Timezone Management
6 * PROGRAMMERS: Eric Kohl
10 /* INCLUDES *****************************************************************/
16 #define TICKSPERMINUTE 600000000
18 /* GLOBALS ******************************************************************/
20 /* Note: Bias[minutes] = UTC - local time */
21 TIME_ZONE_INFORMATION ExpTimeZoneInfo
;
22 ULONG ExpLastTimeZoneBias
= -1;
23 LARGE_INTEGER ExpTimeZoneBias
;
24 ULONG ExpAltTimeZoneBias
;
26 ULONG ExpTickCountMultiplier
;
27 ERESOURCE ExpTimeRefreshLock
;
28 ULONG ExpKernelResolutionCount
= 0;
29 ULONG ExpTimerResolutionCount
= 0;
31 /* FUNCTIONS ****************************************************************/
34 * @name ExAcquireTimeRefreshLock
36 * The ExReleaseTimeRefreshLock routine acquires the system-wide lock used
37 * to synchronize clock interrupt frequency changes.
40 * If TRUE, the system will block the caller thread waiting for the lock
41 * to become available. If FALSE, the routine will fail if the lock has
42 * already been acquired.
44 * @return Boolean value indicating success or failure of the lock acquisition.
51 ExAcquireTimeRefreshLock(IN BOOLEAN Wait
)
54 KeEnterCriticalRegion();
56 /* Attempt lock acquisition */
57 if (!(ExAcquireResourceExclusiveLite(&ExpTimeRefreshLock
, Wait
)))
59 /* Lock was not acquired, enable APCs and fail */
60 KeLeaveCriticalRegion();
64 /* Lock has been acquired */
69 * @name ExReleaseTimeRefreshLock
71 * The ExReleaseTimeRefreshLock routine releases the system-wide lock used
72 * to synchronize clock interrupt frequency changes.
83 ExReleaseTimeRefreshLock(VOID
)
85 /* Release the lock and re-enable APCs */
86 ExReleaseResourceLite(&ExpTimeRefreshLock
);
87 KeLeaveCriticalRegion();
91 * @name ExSetTimerResolution
94 * The KiInsertQueueApc routine modifies the frequency at which the system
98 * Specifies the amount of time that should elapse between each timer
99 * interrupt, in 100-nanosecond units.
101 * This parameter is ignored if SetResolution is FALSE.
103 * @param SetResolution
104 * If TRUE, the call is a request to set the clock interrupt frequency to
105 * the value specified by DesiredTime. If FALSE, the call is a request to
106 * restore the clock interrupt frequency to the system's default value.
108 * @return New timer resolution, in 100-nanosecond ticks.
110 * @remarks (1) The clock frequency is changed only if the DesiredTime value is
111 * less than the current setting.
113 * (2) The routine just returns the current setting if the DesiredTime
114 * value is greater than what is currently set.
116 * (3) If the DesiredTime value is less than the system clock can
117 * support, the routine uses the smallest resolution the system can
118 * support, and returns that value.
120 * (4) If multiple drivers have attempted to change the clock interrupt
121 * frequency, the system will only restore the default frequency
122 * once ALL drivers have called the routine with SetResolution set
125 * NB. This routine synchronizes with IRP_MJ_POWER requests through the
131 ExSetTimerResolution(IN ULONG DesiredTime
,
132 IN BOOLEAN SetResolution
)
134 ULONG CurrentIncrement
;
136 /* Wait for clock interrupt frequency and power requests to synchronize */
137 ExAcquireTimeRefreshLock(TRUE
);
140 CurrentIncrement
= KeTimeIncrement
;
142 /* Check the type of operation this is */
146 * If this is the first kernel change, bump the timer resolution change
147 * count, then bump the kernel change count as well.
149 * These two variables are tracked differently since user-mode processes
150 * can also change the timer resolution through the NtSetTimerResolution
151 * system call. A per-process flag in the EPROCESS then stores the per-
152 * process change state.
155 if (!ExpKernelResolutionCount
++) ExpTimerResolutionCount
++;
158 if (DesiredTime
< KeMinimumIncrement
) DesiredTime
= KeMinimumIncrement
;
161 if (DesiredTime
< KeTimeIncrement
)
163 /* Force this thread on CPU zero, since we don't want it to drift */
164 KeSetSystemAffinityThread(1);
166 /* Now call the platform driver (HAL) to make the change */
167 CurrentIncrement
= HalSetTimeIncrement(DesiredTime
);
169 /* Put the thread back to its original affinity */
170 KeRevertToUserAffinityThread();
172 /* Finally, keep track of the new value in the kernel */
173 KeTimeIncrement
= CurrentIncrement
;
178 /* First, make sure that a driver has actually changed the resolution */
179 if (ExpKernelResolutionCount
)
182 if (--ExpKernelResolutionCount
)
185 * All kernel drivers have requested the original frequency to
186 * be restored, but there might still be user processes with an
187 * ongoing clock interrupt frequency change, so make sure that
188 * this isn't the case.
190 if (--ExpTimerResolutionCount
)
192 /* Force this thread on one CPU so that it doesn't drift */
193 KeSetSystemAffinityThread(1);
195 /* Call the HAL to restore the frequency to its default */
196 CurrentIncrement
= HalSetTimeIncrement(KeMaximumIncrement
);
198 /* Put the thread back to its original affinity */
199 KeRevertToUserAffinityThread();
201 /* Finally, keep track of the new value in the kernel */
202 KeTimeIncrement
= CurrentIncrement
;
208 /* Release the clock interrupt frequency lock since changes are done */
209 ExReleaseTimeRefreshLock();
211 /* And return the current value -- which could reflect the new frequency */
212 return CurrentIncrement
;
217 ExUpdateSystemTimeFromCmos(IN BOOLEAN UpdateInterruptTime
,
218 IN ULONG MaxSepInSeconds
)
226 ExRefreshTimeZoneInformation(IN PLARGE_INTEGER CurrentBootTime
)
228 LARGE_INTEGER CurrentTime
;
231 /* Read time zone information from the registry */
232 Status
= RtlQueryTimeZoneInformation(&ExpTimeZoneInfo
);
233 if (!NT_SUCCESS(Status
))
235 /* Failed, clear all data */
236 RtlZeroMemory(&ExpTimeZoneInfo
, sizeof(TIME_ZONE_INFORMATION
));
237 ExpTimeZoneBias
.QuadPart
= (LONGLONG
)0;
238 ExpTimeZoneId
= TIME_ZONE_ID_UNKNOWN
;
242 /* FIXME: Calculate transition dates */
244 /* Set bias and ID */
245 ExpTimeZoneBias
.QuadPart
= ((LONGLONG
)(ExpTimeZoneInfo
.Bias
+
246 ExpTimeZoneInfo
.StandardBias
)) *
248 ExpTimeZoneId
= TIME_ZONE_ID_STANDARD
;
251 /* Change it for user-mode applications */
252 SharedUserData
->TimeZoneBias
.High1Time
= ExpTimeZoneBias
.u
.HighPart
;
253 SharedUserData
->TimeZoneBias
.High2Time
= ExpTimeZoneBias
.u
.HighPart
;
254 SharedUserData
->TimeZoneBias
.LowPart
= ExpTimeZoneBias
.u
.LowPart
;
255 SharedUserData
->TimeZoneId
= ExpTimeZoneId
;
257 /* Convert boot time from local time to UTC */
258 KeBootTime
.QuadPart
+= ExpTimeZoneBias
.QuadPart
;
260 /* Convert system time from local time to UTC */
263 CurrentTime
.u
.HighPart
= SharedUserData
->SystemTime
.High1Time
;
264 CurrentTime
.u
.LowPart
= SharedUserData
->SystemTime
.LowPart
;
265 } while (CurrentTime
.u
.HighPart
!= SharedUserData
->SystemTime
.High2Time
);
267 /* Change it for user-mode applications */
268 CurrentTime
.QuadPart
+= ExpTimeZoneBias
.QuadPart
;
269 SharedUserData
->SystemTime
.LowPart
= CurrentTime
.u
.LowPart
;
270 SharedUserData
->SystemTime
.High1Time
= CurrentTime
.u
.HighPart
;
271 SharedUserData
->SystemTime
.High2Time
= CurrentTime
.u
.HighPart
;
278 ExpSetTimeZoneInformation(PTIME_ZONE_INFORMATION TimeZoneInformation
)
280 LARGE_INTEGER LocalTime
, SystemTime
, OldTime
;
281 TIME_FIELDS TimeFields
;
282 DPRINT("ExpSetTimeZoneInformation() called\n");
284 DPRINT("Old time zone bias: %d minutes\n", ExpTimeZoneInfo
.Bias
);
285 DPRINT("Old time zone standard bias: %d minutes\n",
286 ExpTimeZoneInfo
.StandardBias
);
287 DPRINT("New time zone bias: %d minutes\n", TimeZoneInformation
->Bias
);
288 DPRINT("New time zone standard bias: %d minutes\n",
289 TimeZoneInformation
->StandardBias
);
291 /* Get the local time */
292 HalQueryRealTimeClock(&TimeFields
);
293 RtlTimeFieldsToTime(&TimeFields
, &LocalTime
);
295 /* FIXME: Calculate transition dates */
297 /* Calculate the bias and set the ID */
298 ExpTimeZoneBias
.QuadPart
= ((LONGLONG
)(TimeZoneInformation
->Bias
+
299 TimeZoneInformation
->StandardBias
)) *
301 ExpTimeZoneId
= TIME_ZONE_ID_STANDARD
;
303 /* Copy the timezone information */
304 RtlCopyMemory(&ExpTimeZoneInfo
,
306 sizeof(TIME_ZONE_INFORMATION
));
308 /* Set the new time zone information */
309 SharedUserData
->TimeZoneBias
.High1Time
= ExpTimeZoneBias
.u
.HighPart
;
310 SharedUserData
->TimeZoneBias
.High2Time
= ExpTimeZoneBias
.u
.HighPart
;
311 SharedUserData
->TimeZoneBias
.LowPart
= ExpTimeZoneBias
.u
.LowPart
;
312 SharedUserData
->TimeZoneId
= ExpTimeZoneId
;
314 DPRINT("New time zone bias: %I64d minutes\n",
315 ExpTimeZoneBias
.QuadPart
/ TICKSPERMINUTE
);
317 /* Calculate the new system time */
318 ExLocalTimeToSystemTime(&LocalTime
, &SystemTime
);
320 /* Set the new system time */
321 KeSetSystemTime(&SystemTime
, &OldTime
, FALSE
, NULL
);
324 DPRINT("ExpSetTimeZoneInformation() done\n");
325 return STATUS_SUCCESS
;
329 * FUNCTION: Sets the system time.
331 * NewTime - Points to a variable that specified the new time
332 * of day in the standard time format.
333 * OldTime - Optionally points to a variable that receives the
334 * old time of day in the standard time format.
339 NtSetSystemTime(IN PLARGE_INTEGER SystemTime
,
340 OUT PLARGE_INTEGER PreviousTime OPTIONAL
)
342 LARGE_INTEGER OldSystemTime
;
343 LARGE_INTEGER NewSystemTime
;
344 LARGE_INTEGER LocalTime
;
345 TIME_FIELDS TimeFields
;
346 KPROCESSOR_MODE PreviousMode
= ExGetPreviousMode();
347 NTSTATUS Status
= STATUS_SUCCESS
;
350 /* Check if we were called from user-mode */
351 if (PreviousMode
!= KernelMode
)
355 /* Verify the time pointers */
356 NewSystemTime
= ProbeForReadLargeInteger(SystemTime
);
357 if(PreviousTime
) ProbeForWriteLargeInteger(PreviousTime
);
359 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER
)
361 /* Return the exception code */
362 _SEH2_YIELD(return _SEH2_GetExceptionCode());
368 /* Reuse the pointer */
369 NewSystemTime
= *SystemTime
;
372 /* Make sure we have permission to change the time */
373 if (!SeSinglePrivilegeCheck(SeSystemtimePrivilege
, PreviousMode
))
375 DPRINT1("NtSetSystemTime: Caller requires the "
376 "SeSystemtimePrivilege privilege!\n");
377 return STATUS_PRIVILEGE_NOT_HELD
;
380 /* Convert the time and set it in HAL */
381 ExSystemTimeToLocalTime(&NewSystemTime
, &LocalTime
);
382 RtlTimeToTimeFields(&LocalTime
, &TimeFields
);
383 HalSetRealTimeClock(&TimeFields
);
385 /* Now set system time */
386 KeSetSystemTime(&NewSystemTime
, &OldSystemTime
, FALSE
, NULL
);
388 /* Check if caller wanted previous time */
391 /* Enter SEH Block for return */
394 /* Return the previous time */
395 *PreviousTime
= OldSystemTime
;
397 _SEH2_EXCEPT(ExSystemExceptionFilter())
399 /* Get the exception code */
400 Status
= _SEH2_GetExceptionCode();
410 * FUNCTION: Retrieves the system time.
412 * CurrentTime - Points to a variable that receives the current
413 * time of day in the standard time format.
417 NtQuerySystemTime(OUT PLARGE_INTEGER SystemTime
)
419 KPROCESSOR_MODE PreviousMode
= ExGetPreviousMode();
422 /* Check if we were called from user-mode */
423 if (PreviousMode
!= KernelMode
)
427 /* Verify the time pointer */
428 ProbeForWriteLargeInteger(SystemTime
);
431 * It's safe to pass the pointer directly to KeQuerySystemTime
432 * as it's just a basic copy to this pointer. If it raises an
433 * exception nothing dangerous can happen!
435 KeQuerySystemTime(SystemTime
);
437 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER
)
439 /* Return the exception code */
440 _SEH2_YIELD(return _SEH2_GetExceptionCode());
446 /* Query the time directly */
447 KeQuerySystemTime(SystemTime
);
451 return STATUS_SUCCESS
;
459 ExLocalTimeToSystemTime(PLARGE_INTEGER LocalTime
,
460 PLARGE_INTEGER SystemTime
)
462 SystemTime
->QuadPart
= LocalTime
->QuadPart
+ ExpTimeZoneBias
.QuadPart
;
470 ExSystemTimeToLocalTime(PLARGE_INTEGER SystemTime
,
471 PLARGE_INTEGER LocalTime
)
473 LocalTime
->QuadPart
= SystemTime
->QuadPart
- ExpTimeZoneBias
.QuadPart
;
481 NtQueryTimerResolution(OUT PULONG MinimumResolution
,
482 OUT PULONG MaximumResolution
,
483 OUT PULONG ActualResolution
)
485 KPROCESSOR_MODE PreviousMode
= ExGetPreviousMode();
487 /* Check if the call came from user-mode */
488 if (PreviousMode
!= KernelMode
)
492 /* Probe the parameters */
493 ProbeForWriteUlong(MinimumResolution
);
494 ProbeForWriteUlong(MaximumResolution
);
495 ProbeForWriteUlong(ActualResolution
);
498 * Set the parameters to the actual values.
501 * MinimumResolution corresponds to the biggest time increment and
502 * MaximumResolution corresponds to the smallest time increment.
504 *MinimumResolution
= KeMaximumIncrement
;
505 *MaximumResolution
= KeMinimumIncrement
;
506 *ActualResolution
= KeTimeIncrement
;
508 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER
)
510 /* Return the exception code */
511 _SEH2_YIELD(return _SEH2_GetExceptionCode());
517 /* Set the parameters to the actual values */
518 *MinimumResolution
= KeMaximumIncrement
;
519 *MaximumResolution
= KeMinimumIncrement
;
520 *ActualResolution
= KeTimeIncrement
;
524 return STATUS_SUCCESS
;
532 NtSetTimerResolution(IN ULONG DesiredResolution
,
533 IN BOOLEAN SetResolution
,
534 OUT PULONG CurrentResolution
)
537 KPROCESSOR_MODE PreviousMode
= ExGetPreviousMode();
538 PEPROCESS Process
= PsGetCurrentProcess();
541 /* Check if the call came from user-mode */
542 if (PreviousMode
!= KernelMode
)
546 /* Probe the parameter */
547 ProbeForWriteUlong(CurrentResolution
);
549 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER
)
551 /* Return the exception code */
552 _SEH2_YIELD(return _SEH2_GetExceptionCode());
557 /* Set and return the new resolution */
558 NewResolution
= ExSetTimerResolution(DesiredResolution
, SetResolution
);
560 if (PreviousMode
!= KernelMode
)
564 *CurrentResolution
= NewResolution
;
566 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER
)
568 /* Return the exception code */
569 _SEH2_YIELD(return _SEH2_GetExceptionCode());
575 *CurrentResolution
= NewResolution
;
578 if (SetResolution
|| Process
->SetTimerResolution
)
580 /* The resolution has been changed now or in an earlier call */
581 Status
= STATUS_SUCCESS
;
585 /* The resolution hasn't been changed */
586 Status
= STATUS_TIMER_RESOLUTION_NOT_SET
;
589 /* Update the flag */
590 Process
->SetTimerResolution
= SetResolution
;