[KS]
[reactos.git] / reactos / ntoskrnl / ex / time.c
1 /*
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
7 * Thomas Weidenmueller
8 */
9
10 /* INCLUDES *****************************************************************/
11
12 #include <ntoskrnl.h>
13 #define NDEBUG
14 #include <debug.h>
15
16 #define TICKSPERMINUTE 600000000
17
18 /* GLOBALS ******************************************************************/
19
20 /* Note: Bias[minutes] = UTC - local time */
21 TIME_ZONE_INFORMATION ExpTimeZoneInfo;
22 ULONG ExpLastTimeZoneBias = -1;
23 LARGE_INTEGER ExpTimeZoneBias;
24 ULONG ExpAltTimeZoneBias;
25 ULONG ExpTimeZoneId;
26 ULONG ExpTickCountMultiplier;
27 ERESOURCE ExpTimeRefreshLock;
28 ULONG ExpKernelResolutionCount = 0;
29 ULONG ExpTimerResolutionCount = 0;
30
31 /* FUNCTIONS ****************************************************************/
32
33 /*++
34 * @name ExAcquireTimeRefreshLock
35 *
36 * The ExReleaseTimeRefreshLock routine acquires the system-wide lock used
37 * to synchronize clock interrupt frequency changes.
38 *
39 * @param Wait
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.
43 *
44 * @return Boolean value indicating success or failure of the lock acquisition.
45 *
46 * @remarks None.
47 *
48 *--*/
49 BOOLEAN
50 NTAPI
51 ExAcquireTimeRefreshLock(
52 IN BOOLEAN Wait
53 )
54 {
55 /* Block APCs */
56 KeEnterCriticalRegion();
57
58 /* Attempt lock acquisition */
59 if (!(ExAcquireResourceExclusiveLite(&ExpTimeRefreshLock, Wait)))
60 {
61 /* Lock was not acquired, enable APCs and fail */
62 KeLeaveCriticalRegion();
63 return FALSE;
64 }
65
66 /* Lock has been acquired */
67 return TRUE;
68 }
69
70 /*++
71 * @name ExReleaseTimeRefreshLock
72 *
73 * The ExReleaseTimeRefreshLock routine releases the system-wide lock used
74 * to synchronize clock interrupt frequency changes.
75 *
76 * @param None.
77 *
78 * @return None.
79 *
80 * @remarks None.
81 *
82 *--*/
83 VOID
84 NTAPI
85 ExReleaseTimeRefreshLock(
86 VOID
87 )
88 {
89 /* Release the lock and re-enable APCs */
90 ExReleaseResourceLite(&ExpTimeRefreshLock);
91 KeLeaveCriticalRegion();
92 }
93
94 /*++
95 * @name ExSetTimerResolution
96 * @exported
97 *
98 * The KiInsertQueueApc routine modifies the frequency at which the system
99 * clock interrupts.
100 *
101 * @param DesiredTime
102 * Specifies the amount of time that should elapse between each timer
103 * interrupt, in 100-nanosecond units.
104 *
105 * This parameter is ignored if SetResolution is FALSE.
106 *
107 * @param SetResolution
108 * If TRUE, the call is a request to set the clock interrupt frequency to
109 * the value specified by DesiredTime. If FALSE, the call is a request to
110 * restore the clock interrupt frequency to the system's default value.
111 *
112 * @return New timer resolution, in 100-nanosecond ticks.
113 *
114 * @remarks (1) The clock frequency is changed only if the DesiredTime value is
115 * less than the current setting.
116 *
117 * (2) The routine just returns the current setting if the DesiredTime
118 * value is greater than what is currently set.
119 *
120 * (3) If the DesiredTime value is less than the system clock can
121 * support, the routine uses the smallest resolution the system can
122 * support, and returns that value.
123 *
124 * (4) If multiple drivers have attempted to change the clock interrupt
125 * frequency, the system will only restore the default frequency
126 * once ALL drivers have called the routine with SetResolution set
127 * to FALSE.
128 *
129 * NB. This routine synchronizes with IRP_MJ_POWER requests through the
130 * TimeRefreshLock.
131 *
132 *--*/
133 ULONG
134 NTAPI
135 ExSetTimerResolution(IN ULONG DesiredTime,
136 IN BOOLEAN SetResolution)
137 {
138 ULONG CurrentIncrement;
139
140 /* Wait for clock interrupt frequency and power requests to synchronize */
141 ExAcquireTimeRefreshLock(TRUE);
142
143 /* Obey remark 2*/
144 CurrentIncrement = KeTimeIncrement;
145
146 /* Check the type of operation this is */
147 if (SetResolution)
148 {
149 /*
150 * If this is the first kernel change, bump the timer resolution change
151 * count, then bump the kernel change count as well.
152 *
153 * These two variables are tracked differently since user-mode processes
154 * can also change the timer resolution through the NtSetTimerResolution
155 * system call. A per-process flag in the EPROCESS then stores the per-
156 * process change state.
157 *
158 */
159 if (!ExpKernelResolutionCount++) ExpTimerResolutionCount++;
160
161 /* Obey remark 3 */
162 if (DesiredTime < KeMinimumIncrement) DesiredTime = KeMinimumIncrement;
163
164 /* Obey remark 1 */
165 if (DesiredTime < KeTimeIncrement)
166 {
167 /* Force this thread on CPU zero, since we don't want it to drift */
168 KeSetSystemAffinityThread(1);
169
170 /* Now call the platform driver (HAL) to make the change */
171 CurrentIncrement = HalSetTimeIncrement(DesiredTime);
172
173 /* Put the thread back to its original affinity */
174 KeRevertToUserAffinityThread();
175
176 /* Finally, keep track of the new value in the kernel */
177 KeTimeIncrement = CurrentIncrement;
178 }
179 }
180 else
181 {
182 /* First, make sure that a driver has actually changed the resolution */
183 if (ExpKernelResolutionCount)
184 {
185 /* Obey remark 4 */
186 if (--ExpKernelResolutionCount)
187 {
188 /*
189 * All kernel drivers have requested the original frequency to
190 * be restored, but there might still be user processes with an
191 * ongoing clock interrupt frequency change, so make sure that
192 * this isn't the case.
193 */
194 if (--ExpTimerResolutionCount)
195 {
196 /* Force this thread on one CPU so that it doesn't drift */
197 KeSetSystemAffinityThread(1);
198
199 /* Call the HAL to restore the frequency to its default */
200 CurrentIncrement = HalSetTimeIncrement(KeMaximumIncrement);
201
202 /* Put the thread back to its original affinity */
203 KeRevertToUserAffinityThread();
204
205 /* Finally, keep track of the new value in the kernel */
206 KeTimeIncrement = CurrentIncrement;
207 }
208 }
209 }
210 }
211
212 /* Release the clock interrupt frequency lock since changes are done */
213 ExReleaseTimeRefreshLock();
214
215 /* And return the current value -- which could reflect the new frequency */
216 return CurrentIncrement;
217 }
218
219 VOID
220 NTAPI
221 ExUpdateSystemTimeFromCmos(IN BOOLEAN UpdateInterruptTime,
222 IN ULONG MaxSepInSeconds)
223 {
224 /* FIXME: TODO */
225 return;
226 }
227
228 BOOLEAN
229 NTAPI
230 ExRefreshTimeZoneInformation(IN PLARGE_INTEGER CurrentBootTime)
231 {
232 LARGE_INTEGER CurrentTime;
233 NTSTATUS Status;
234
235 /* Read time zone information from the registry */
236 Status = RtlQueryTimeZoneInformation(&ExpTimeZoneInfo);
237 if (!NT_SUCCESS(Status))
238 {
239 /* Failed, clear all data */
240 RtlZeroMemory(&ExpTimeZoneInfo, sizeof(TIME_ZONE_INFORMATION));
241 ExpTimeZoneBias.QuadPart = (LONGLONG)0;
242 ExpTimeZoneId = TIME_ZONE_ID_UNKNOWN;
243 }
244 else
245 {
246 /* FIXME: Calculate transition dates */
247
248 /* Set bias and ID */
249 ExpTimeZoneBias.QuadPart = ((LONGLONG)(ExpTimeZoneInfo.Bias +
250 ExpTimeZoneInfo.StandardBias)) *
251 TICKSPERMINUTE;
252 ExpTimeZoneId = TIME_ZONE_ID_STANDARD;
253 }
254
255 /* Change it for user-mode applications */
256 SharedUserData->TimeZoneBias.High1Time = ExpTimeZoneBias.u.HighPart;
257 SharedUserData->TimeZoneBias.High2Time = ExpTimeZoneBias.u.HighPart;
258 SharedUserData->TimeZoneBias.LowPart = ExpTimeZoneBias.u.LowPart;
259 SharedUserData->TimeZoneId = ExpTimeZoneId;
260
261 /* Convert boot time from local time to UTC */
262 KeBootTime.QuadPart += ExpTimeZoneBias.QuadPart;
263
264 /* Convert system time from local time to UTC */
265 do
266 {
267 CurrentTime.u.HighPart = SharedUserData->SystemTime.High1Time;
268 CurrentTime.u.LowPart = SharedUserData->SystemTime.LowPart;
269 } while (CurrentTime.u.HighPart != SharedUserData->SystemTime.High2Time);
270
271 /* Change it for user-mode applications */
272 CurrentTime.QuadPart += ExpTimeZoneBias.QuadPart;
273 SharedUserData->SystemTime.LowPart = CurrentTime.u.LowPart;
274 SharedUserData->SystemTime.High1Time = CurrentTime.u.HighPart;
275 SharedUserData->SystemTime.High2Time = CurrentTime.u.HighPart;
276
277 /* Return success */
278 return TRUE;
279 }
280
281 NTSTATUS
282 ExpSetTimeZoneInformation(PTIME_ZONE_INFORMATION TimeZoneInformation)
283 {
284 LARGE_INTEGER LocalTime, SystemTime, OldTime;
285 TIME_FIELDS TimeFields;
286 DPRINT("ExpSetTimeZoneInformation() called\n");
287
288 DPRINT("Old time zone bias: %d minutes\n", ExpTimeZoneInfo.Bias);
289 DPRINT("Old time zone standard bias: %d minutes\n",
290 ExpTimeZoneInfo.StandardBias);
291 DPRINT("New time zone bias: %d minutes\n", TimeZoneInformation->Bias);
292 DPRINT("New time zone standard bias: %d minutes\n",
293 TimeZoneInformation->StandardBias);
294
295 /* Get the local time */
296 HalQueryRealTimeClock(&TimeFields);
297 RtlTimeFieldsToTime(&TimeFields, &LocalTime);
298
299 /* FIXME: Calculate transition dates */
300
301 /* Calculate the bias and set the ID */
302 ExpTimeZoneBias.QuadPart = ((LONGLONG)(TimeZoneInformation->Bias +
303 TimeZoneInformation->StandardBias)) *
304 TICKSPERMINUTE;
305 ExpTimeZoneId = TIME_ZONE_ID_STANDARD;
306
307 /* Copy the timezone information */
308 RtlCopyMemory(&ExpTimeZoneInfo,
309 TimeZoneInformation,
310 sizeof(TIME_ZONE_INFORMATION));
311
312 /* Set the new time zone information */
313 SharedUserData->TimeZoneBias.High1Time = ExpTimeZoneBias.u.HighPart;
314 SharedUserData->TimeZoneBias.High2Time = ExpTimeZoneBias.u.HighPart;
315 SharedUserData->TimeZoneBias.LowPart = ExpTimeZoneBias.u.LowPart;
316 SharedUserData->TimeZoneId = ExpTimeZoneId;
317
318 DPRINT("New time zone bias: %I64d minutes\n",
319 ExpTimeZoneBias.QuadPart / TICKSPERMINUTE);
320
321 /* Calculate the new system time */
322 ExLocalTimeToSystemTime(&LocalTime, &SystemTime);
323
324 /* Set the new system time */
325 KeSetSystemTime(&SystemTime, &OldTime, FALSE, NULL);
326
327 /* Return success */
328 DPRINT("ExpSetTimeZoneInformation() done\n");
329 return STATUS_SUCCESS;
330 }
331
332 /*
333 * FUNCTION: Sets the system time.
334 * PARAMETERS:
335 * NewTime - Points to a variable that specified the new time
336 * of day in the standard time format.
337 * OldTime - Optionally points to a variable that receives the
338 * old time of day in the standard time format.
339 * RETURNS: Status
340 */
341 NTSTATUS
342 NTAPI
343 NtSetSystemTime(IN PLARGE_INTEGER SystemTime,
344 OUT PLARGE_INTEGER PreviousTime OPTIONAL)
345 {
346 LARGE_INTEGER OldSystemTime;
347 LARGE_INTEGER NewSystemTime;
348 LARGE_INTEGER LocalTime;
349 TIME_FIELDS TimeFields;
350 KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
351 NTSTATUS Status = STATUS_SUCCESS;
352 PAGED_CODE();
353
354 /* Check if we were called from user-mode */
355 if (PreviousMode != KernelMode)
356 {
357 _SEH2_TRY
358 {
359 /* Verify the time pointers */
360 NewSystemTime = ProbeForReadLargeInteger(SystemTime);
361 if(PreviousTime) ProbeForWriteLargeInteger(PreviousTime);
362 }
363 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
364 {
365 /* Return the exception code */
366 _SEH2_YIELD(return _SEH2_GetExceptionCode());
367 }
368 _SEH2_END;
369 }
370 else
371 {
372 /* Reuse the pointer */
373 NewSystemTime = *SystemTime;
374 }
375
376 /* Make sure we have permission to change the time */
377 if (!SeSinglePrivilegeCheck(SeSystemtimePrivilege, PreviousMode))
378 {
379 DPRINT1("NtSetSystemTime: Caller requires the "
380 "SeSystemtimePrivilege privilege!\n");
381 return STATUS_PRIVILEGE_NOT_HELD;
382 }
383
384 /* Convert the time and set it in HAL */
385 ExSystemTimeToLocalTime(&NewSystemTime, &LocalTime);
386 RtlTimeToTimeFields(&LocalTime, &TimeFields);
387 HalSetRealTimeClock(&TimeFields);
388
389 /* Now set system time */
390 KeSetSystemTime(&NewSystemTime, &OldSystemTime, FALSE, NULL);
391
392 /* Check if caller wanted previous time */
393 if (PreviousTime)
394 {
395 /* Enter SEH Block for return */
396 _SEH2_TRY
397 {
398 /* Return the previous time */
399 *PreviousTime = OldSystemTime;
400 }
401 _SEH2_EXCEPT(ExSystemExceptionFilter())
402 {
403 /* Get the exception code */
404 Status = _SEH2_GetExceptionCode();
405 }
406 _SEH2_END;
407 }
408
409 /* Return status */
410 return Status;
411 }
412
413 /*
414 * FUNCTION: Retrieves the system time.
415 * PARAMETERS:
416 * CurrentTime - Points to a variable that receives the current
417 * time of day in the standard time format.
418 */
419 NTSTATUS
420 NTAPI
421 NtQuerySystemTime(OUT PLARGE_INTEGER SystemTime)
422 {
423 KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
424 NTSTATUS Status = STATUS_SUCCESS;
425 PAGED_CODE();
426
427 /* Check if we were called from user-mode */
428 if (PreviousMode != KernelMode)
429 {
430 _SEH2_TRY
431 {
432 /* Verify the time pointer */
433 ProbeForWriteLargeInteger(SystemTime);
434
435 /*
436 * It's safe to pass the pointer directly to KeQuerySystemTime as
437 * it's just a basic copy to this pointer. If it raises an
438 * exception nothing dangerous can happen!
439 */
440 KeQuerySystemTime(SystemTime);
441 }
442 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
443 {
444 /* Get the exception code */
445 Status = _SEH2_GetExceptionCode();
446 }
447 _SEH2_END;
448 }
449 else
450 {
451 /* Query the time directly */
452 KeQuerySystemTime(SystemTime);
453 }
454
455 /* Return status to caller */
456 return Status;
457 }
458
459 /*
460 * @implemented
461 */
462 VOID
463 NTAPI
464 ExLocalTimeToSystemTime(PLARGE_INTEGER LocalTime,
465 PLARGE_INTEGER SystemTime)
466 {
467 SystemTime->QuadPart = LocalTime->QuadPart + ExpTimeZoneBias.QuadPart;
468 }
469
470 /*
471 * @implemented
472 */
473 VOID
474 NTAPI
475 ExSystemTimeToLocalTime(PLARGE_INTEGER SystemTime,
476 PLARGE_INTEGER LocalTime)
477 {
478 LocalTime->QuadPart = SystemTime->QuadPart - ExpTimeZoneBias.QuadPart;
479 }
480
481 /* EOF */