Sync with trunk r63383 .
[reactos.git] / 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(IN BOOLEAN Wait)
52 {
53 /* Block APCs */
54 KeEnterCriticalRegion();
55
56 /* Attempt lock acquisition */
57 if (!(ExAcquireResourceExclusiveLite(&ExpTimeRefreshLock, Wait)))
58 {
59 /* Lock was not acquired, enable APCs and fail */
60 KeLeaveCriticalRegion();
61 return FALSE;
62 }
63
64 /* Lock has been acquired */
65 return TRUE;
66 }
67
68 /*++
69 * @name ExReleaseTimeRefreshLock
70 *
71 * The ExReleaseTimeRefreshLock routine releases the system-wide lock used
72 * to synchronize clock interrupt frequency changes.
73 *
74 * @param None.
75 *
76 * @return None.
77 *
78 * @remarks None.
79 *
80 *--*/
81 VOID
82 NTAPI
83 ExReleaseTimeRefreshLock(VOID)
84 {
85 /* Release the lock and re-enable APCs */
86 ExReleaseResourceLite(&ExpTimeRefreshLock);
87 KeLeaveCriticalRegion();
88 }
89
90 /*++
91 * @name ExSetTimerResolution
92 * @exported
93 *
94 * The KiInsertQueueApc routine modifies the frequency at which the system
95 * clock interrupts.
96 *
97 * @param DesiredTime
98 * Specifies the amount of time that should elapse between each timer
99 * interrupt, in 100-nanosecond units.
100 *
101 * This parameter is ignored if SetResolution is FALSE.
102 *
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.
107 *
108 * @return New timer resolution, in 100-nanosecond ticks.
109 *
110 * @remarks (1) The clock frequency is changed only if the DesiredTime value is
111 * less than the current setting.
112 *
113 * (2) The routine just returns the current setting if the DesiredTime
114 * value is greater than what is currently set.
115 *
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.
119 *
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
123 * to FALSE.
124 *
125 * NB. This routine synchronizes with IRP_MJ_POWER requests through the
126 * TimeRefreshLock.
127 *
128 *--*/
129 ULONG
130 NTAPI
131 ExSetTimerResolution(IN ULONG DesiredTime,
132 IN BOOLEAN SetResolution)
133 {
134 ULONG CurrentIncrement;
135
136 /* Wait for clock interrupt frequency and power requests to synchronize */
137 ExAcquireTimeRefreshLock(TRUE);
138
139 /* Obey remark 2*/
140 CurrentIncrement = KeTimeIncrement;
141
142 /* Check the type of operation this is */
143 if (SetResolution)
144 {
145 /*
146 * If this is the first kernel change, bump the timer resolution change
147 * count, then bump the kernel change count as well.
148 *
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.
153 *
154 */
155 if (!ExpKernelResolutionCount++) ExpTimerResolutionCount++;
156
157 /* Obey remark 3 */
158 if (DesiredTime < KeMinimumIncrement) DesiredTime = KeMinimumIncrement;
159
160 /* Obey remark 1 */
161 if (DesiredTime < KeTimeIncrement)
162 {
163 /* Force this thread on CPU zero, since we don't want it to drift */
164 KeSetSystemAffinityThread(1);
165
166 /* Now call the platform driver (HAL) to make the change */
167 CurrentIncrement = HalSetTimeIncrement(DesiredTime);
168
169 /* Put the thread back to its original affinity */
170 KeRevertToUserAffinityThread();
171
172 /* Finally, keep track of the new value in the kernel */
173 KeTimeIncrement = CurrentIncrement;
174 }
175 }
176 else
177 {
178 /* First, make sure that a driver has actually changed the resolution */
179 if (ExpKernelResolutionCount)
180 {
181 /* Obey remark 4 */
182 if (--ExpKernelResolutionCount)
183 {
184 /*
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.
189 */
190 if (--ExpTimerResolutionCount)
191 {
192 /* Force this thread on one CPU so that it doesn't drift */
193 KeSetSystemAffinityThread(1);
194
195 /* Call the HAL to restore the frequency to its default */
196 CurrentIncrement = HalSetTimeIncrement(KeMaximumIncrement);
197
198 /* Put the thread back to its original affinity */
199 KeRevertToUserAffinityThread();
200
201 /* Finally, keep track of the new value in the kernel */
202 KeTimeIncrement = CurrentIncrement;
203 }
204 }
205 }
206 }
207
208 /* Release the clock interrupt frequency lock since changes are done */
209 ExReleaseTimeRefreshLock();
210
211 /* And return the current value -- which could reflect the new frequency */
212 return CurrentIncrement;
213 }
214
215 VOID
216 NTAPI
217 ExUpdateSystemTimeFromCmos(IN BOOLEAN UpdateInterruptTime,
218 IN ULONG MaxSepInSeconds)
219 {
220 /* FIXME: TODO */
221 return;
222 }
223
224 BOOLEAN
225 NTAPI
226 ExRefreshTimeZoneInformation(IN PLARGE_INTEGER CurrentBootTime)
227 {
228 LARGE_INTEGER CurrentTime;
229 NTSTATUS Status;
230
231 /* Read time zone information from the registry */
232 Status = RtlQueryTimeZoneInformation(&ExpTimeZoneInfo);
233 if (!NT_SUCCESS(Status))
234 {
235 /* Failed, clear all data */
236 RtlZeroMemory(&ExpTimeZoneInfo, sizeof(TIME_ZONE_INFORMATION));
237 ExpTimeZoneBias.QuadPart = (LONGLONG)0;
238 ExpTimeZoneId = TIME_ZONE_ID_UNKNOWN;
239 }
240 else
241 {
242 /* FIXME: Calculate transition dates */
243
244 /* Set bias and ID */
245 ExpTimeZoneBias.QuadPart = ((LONGLONG)(ExpTimeZoneInfo.Bias +
246 ExpTimeZoneInfo.StandardBias)) *
247 TICKSPERMINUTE;
248 ExpTimeZoneId = TIME_ZONE_ID_STANDARD;
249 }
250
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;
256
257 /* Convert boot time from local time to UTC */
258 KeBootTime.QuadPart += ExpTimeZoneBias.QuadPart;
259
260 /* Convert system time from local time to UTC */
261 do
262 {
263 CurrentTime.u.HighPart = SharedUserData->SystemTime.High1Time;
264 CurrentTime.u.LowPart = SharedUserData->SystemTime.LowPart;
265 } while (CurrentTime.u.HighPart != SharedUserData->SystemTime.High2Time);
266
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;
272
273 /* Return success */
274 return TRUE;
275 }
276
277 NTSTATUS
278 ExpSetTimeZoneInformation(PTIME_ZONE_INFORMATION TimeZoneInformation)
279 {
280 LARGE_INTEGER LocalTime, SystemTime, OldTime;
281 TIME_FIELDS TimeFields;
282 DPRINT("ExpSetTimeZoneInformation() called\n");
283
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);
290
291 /* Get the local time */
292 HalQueryRealTimeClock(&TimeFields);
293 RtlTimeFieldsToTime(&TimeFields, &LocalTime);
294
295 /* FIXME: Calculate transition dates */
296
297 /* Calculate the bias and set the ID */
298 ExpTimeZoneBias.QuadPart = ((LONGLONG)(TimeZoneInformation->Bias +
299 TimeZoneInformation->StandardBias)) *
300 TICKSPERMINUTE;
301 ExpTimeZoneId = TIME_ZONE_ID_STANDARD;
302
303 /* Copy the timezone information */
304 RtlCopyMemory(&ExpTimeZoneInfo,
305 TimeZoneInformation,
306 sizeof(TIME_ZONE_INFORMATION));
307
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;
313
314 DPRINT("New time zone bias: %I64d minutes\n",
315 ExpTimeZoneBias.QuadPart / TICKSPERMINUTE);
316
317 /* Calculate the new system time */
318 ExLocalTimeToSystemTime(&LocalTime, &SystemTime);
319
320 /* Set the new system time */
321 KeSetSystemTime(&SystemTime, &OldTime, FALSE, NULL);
322
323 /* Return success */
324 DPRINT("ExpSetTimeZoneInformation() done\n");
325 return STATUS_SUCCESS;
326 }
327
328 /*
329 * FUNCTION: Sets the system time.
330 * PARAMETERS:
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.
335 * RETURNS: Status
336 */
337 NTSTATUS
338 NTAPI
339 NtSetSystemTime(IN PLARGE_INTEGER SystemTime,
340 OUT PLARGE_INTEGER PreviousTime OPTIONAL)
341 {
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;
348 PAGED_CODE();
349
350 /* Check if we were called from user-mode */
351 if (PreviousMode != KernelMode)
352 {
353 _SEH2_TRY
354 {
355 /* Verify the time pointers */
356 NewSystemTime = ProbeForReadLargeInteger(SystemTime);
357 if(PreviousTime) ProbeForWriteLargeInteger(PreviousTime);
358 }
359 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
360 {
361 /* Return the exception code */
362 _SEH2_YIELD(return _SEH2_GetExceptionCode());
363 }
364 _SEH2_END;
365 }
366 else
367 {
368 /* Reuse the pointer */
369 NewSystemTime = *SystemTime;
370 }
371
372 /* Make sure we have permission to change the time */
373 if (!SeSinglePrivilegeCheck(SeSystemtimePrivilege, PreviousMode))
374 {
375 DPRINT1("NtSetSystemTime: Caller requires the "
376 "SeSystemtimePrivilege privilege!\n");
377 return STATUS_PRIVILEGE_NOT_HELD;
378 }
379
380 /* Convert the time and set it in HAL */
381 ExSystemTimeToLocalTime(&NewSystemTime, &LocalTime);
382 RtlTimeToTimeFields(&LocalTime, &TimeFields);
383 HalSetRealTimeClock(&TimeFields);
384
385 /* Now set system time */
386 KeSetSystemTime(&NewSystemTime, &OldSystemTime, FALSE, NULL);
387
388 /* Check if caller wanted previous time */
389 if (PreviousTime)
390 {
391 /* Enter SEH Block for return */
392 _SEH2_TRY
393 {
394 /* Return the previous time */
395 *PreviousTime = OldSystemTime;
396 }
397 _SEH2_EXCEPT(ExSystemExceptionFilter())
398 {
399 /* Get the exception code */
400 Status = _SEH2_GetExceptionCode();
401 }
402 _SEH2_END;
403 }
404
405 /* Return status */
406 return Status;
407 }
408
409 /*
410 * FUNCTION: Retrieves the system time.
411 * PARAMETERS:
412 * CurrentTime - Points to a variable that receives the current
413 * time of day in the standard time format.
414 */
415 NTSTATUS
416 NTAPI
417 NtQuerySystemTime(OUT PLARGE_INTEGER SystemTime)
418 {
419 KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
420 PAGED_CODE();
421
422 /* Check if we were called from user-mode */
423 if (PreviousMode != KernelMode)
424 {
425 _SEH2_TRY
426 {
427 /* Verify the time pointer */
428 ProbeForWriteLargeInteger(SystemTime);
429
430 /*
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!
434 */
435 KeQuerySystemTime(SystemTime);
436 }
437 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
438 {
439 /* Return the exception code */
440 _SEH2_YIELD(return _SEH2_GetExceptionCode());
441 }
442 _SEH2_END;
443 }
444 else
445 {
446 /* Query the time directly */
447 KeQuerySystemTime(SystemTime);
448 }
449
450 /* Return success */
451 return STATUS_SUCCESS;
452 }
453
454 /*
455 * @implemented
456 */
457 VOID
458 NTAPI
459 ExLocalTimeToSystemTime(PLARGE_INTEGER LocalTime,
460 PLARGE_INTEGER SystemTime)
461 {
462 SystemTime->QuadPart = LocalTime->QuadPart + ExpTimeZoneBias.QuadPart;
463 }
464
465 /*
466 * @implemented
467 */
468 VOID
469 NTAPI
470 ExSystemTimeToLocalTime(PLARGE_INTEGER SystemTime,
471 PLARGE_INTEGER LocalTime)
472 {
473 LocalTime->QuadPart = SystemTime->QuadPart - ExpTimeZoneBias.QuadPart;
474 }
475
476 /*
477 * @implemented
478 */
479 NTSTATUS
480 NTAPI
481 NtQueryTimerResolution(OUT PULONG MinimumResolution,
482 OUT PULONG MaximumResolution,
483 OUT PULONG ActualResolution)
484 {
485 KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
486
487 /* Check if the call came from user-mode */
488 if (PreviousMode != KernelMode)
489 {
490 _SEH2_TRY
491 {
492 /* Probe the parameters */
493 ProbeForWriteUlong(MinimumResolution);
494 ProbeForWriteUlong(MaximumResolution);
495 ProbeForWriteUlong(ActualResolution);
496
497 /*
498 * Set the parameters to the actual values.
499 *
500 * NOTE:
501 * MinimumResolution corresponds to the biggest time increment and
502 * MaximumResolution corresponds to the smallest time increment.
503 */
504 *MinimumResolution = KeMaximumIncrement;
505 *MaximumResolution = KeMinimumIncrement;
506 *ActualResolution = KeTimeIncrement;
507 }
508 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
509 {
510 /* Return the exception code */
511 _SEH2_YIELD(return _SEH2_GetExceptionCode());
512 }
513 _SEH2_END;
514 }
515 else
516 {
517 /* Set the parameters to the actual values */
518 *MinimumResolution = KeMaximumIncrement;
519 *MaximumResolution = KeMinimumIncrement;
520 *ActualResolution = KeTimeIncrement;
521 }
522
523 /* Return success */
524 return STATUS_SUCCESS;
525 }
526
527 /*
528 * @implemented
529 */
530 NTSTATUS
531 NTAPI
532 NtSetTimerResolution(IN ULONG DesiredResolution,
533 IN BOOLEAN SetResolution,
534 OUT PULONG CurrentResolution)
535 {
536 NTSTATUS Status;
537 KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
538 PEPROCESS Process = PsGetCurrentProcess();
539 ULONG NewResolution;
540
541 /* Check if the call came from user-mode */
542 if (PreviousMode != KernelMode)
543 {
544 _SEH2_TRY
545 {
546 /* Probe the parameter */
547 ProbeForWriteUlong(CurrentResolution);
548 }
549 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
550 {
551 /* Return the exception code */
552 _SEH2_YIELD(return _SEH2_GetExceptionCode());
553 }
554 _SEH2_END;
555 }
556
557 /* Set and return the new resolution */
558 NewResolution = ExSetTimerResolution(DesiredResolution, SetResolution);
559
560 if (PreviousMode != KernelMode)
561 {
562 _SEH2_TRY
563 {
564 *CurrentResolution = NewResolution;
565 }
566 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
567 {
568 /* Return the exception code */
569 _SEH2_YIELD(return _SEH2_GetExceptionCode());
570 }
571 _SEH2_END;
572 }
573 else
574 {
575 *CurrentResolution = NewResolution;
576 }
577
578 if (SetResolution || Process->SetTimerResolution)
579 {
580 /* The resolution has been changed now or in an earlier call */
581 Status = STATUS_SUCCESS;
582 }
583 else
584 {
585 /* The resolution hasn't been changed */
586 Status = STATUS_TIMER_RESOLUTION_NOT_SET;
587 }
588
589 /* Update the flag */
590 Process->SetTimerResolution = SetResolution;
591
592 return Status;
593 }
594
595 /* EOF */