xref: /reactos/ntoskrnl/ex/time.c (revision 3e1f4074)
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 RTL_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 StandardTime;
229     LARGE_INTEGER DaylightTime;
230     LARGE_INTEGER CurrentTime;
231     NTSTATUS Status;
232 
233     /* Read time zone information from the registry */
234     Status = RtlQueryTimeZoneInformation(&ExpTimeZoneInfo);
235     if (!NT_SUCCESS(Status))
236     {
237         DPRINT1("RtlQueryTimeZoneInformation() failed (Status 0x%08lx)\n", Status);
238         return FALSE;
239     }
240 
241     /* Get the default bias */
242     ExpTimeZoneBias.QuadPart = (LONGLONG)ExpTimeZoneInfo.Bias * TICKSPERMINUTE;
243 
244     if (ExpTimeZoneInfo.StandardDate.Month != 0 &&
245         ExpTimeZoneInfo.DaylightDate.Month != 0)
246     {
247         /* Get this years standard start time */
248         if (!RtlCutoverTimeToSystemTime(&ExpTimeZoneInfo.StandardDate,
249                                         &StandardTime,
250                                         CurrentBootTime,
251                                         TRUE))
252         {
253             DPRINT1("RtlCutoverTimeToSystemTime() for StandardDate failed!\n");
254             return FALSE;
255         }
256 
257         /* Get this years daylight start time */
258         if (!RtlCutoverTimeToSystemTime(&ExpTimeZoneInfo.DaylightDate,
259                                         &DaylightTime,
260                                         CurrentBootTime,
261                                         TRUE))
262         {
263             DPRINT1("RtlCutoverTimeToSystemTime() for DaylightDate failed!\n");
264             return FALSE;
265         }
266 
267         /* Determine the time zone id and update the time zone bias */
268         if (DaylightTime.QuadPart < StandardTime.QuadPart)
269         {
270             if ((CurrentBootTime->QuadPart >= DaylightTime.QuadPart) &&
271                 (CurrentBootTime->QuadPart < StandardTime.QuadPart))
272             {
273                 DPRINT("Daylight time!\n");
274                 ExpTimeZoneId = TIME_ZONE_ID_DAYLIGHT;
275                 ExpTimeZoneBias.QuadPart += (LONGLONG)ExpTimeZoneInfo.DaylightBias * TICKSPERMINUTE;
276             }
277             else
278             {
279                 DPRINT("Standard time!\n");
280                 ExpTimeZoneId = TIME_ZONE_ID_STANDARD;
281                 ExpTimeZoneBias.QuadPart += (LONGLONG)ExpTimeZoneInfo.StandardBias * TICKSPERMINUTE;
282             }
283         }
284         else
285         {
286             if ((CurrentBootTime->QuadPart >= StandardTime.QuadPart) &&
287                 (CurrentBootTime->QuadPart < DaylightTime.QuadPart))
288             {
289                 DPRINT("Standard time!\n");
290                 ExpTimeZoneId = TIME_ZONE_ID_STANDARD;
291                 ExpTimeZoneBias.QuadPart += (LONGLONG)ExpTimeZoneInfo.StandardBias * TICKSPERMINUTE;
292             }
293             else
294             {
295                 DPRINT("Daylight time!\n");
296                 ExpTimeZoneId = TIME_ZONE_ID_DAYLIGHT;
297                 ExpTimeZoneBias.QuadPart += (LONGLONG)ExpTimeZoneInfo.DaylightBias * TICKSPERMINUTE;
298             }
299         }
300     }
301     else
302     {
303         ExpTimeZoneId = TIME_ZONE_ID_UNKNOWN;
304     }
305 
306     /* Change it for user-mode applications */
307     SharedUserData->TimeZoneBias.High1Time = ExpTimeZoneBias.u.HighPart;
308     SharedUserData->TimeZoneBias.High2Time = ExpTimeZoneBias.u.HighPart;
309     SharedUserData->TimeZoneBias.LowPart = ExpTimeZoneBias.u.LowPart;
310     SharedUserData->TimeZoneId = ExpTimeZoneId;
311 
312     /* Convert boot time from local time to UTC */
313     KeBootTime.QuadPart += ExpTimeZoneBias.QuadPart;
314 
315     /* Convert system time from local time to UTC */
316     do
317     {
318         CurrentTime.u.HighPart = SharedUserData->SystemTime.High1Time;
319         CurrentTime.u.LowPart = SharedUserData->SystemTime.LowPart;
320     } while (CurrentTime.u.HighPart != SharedUserData->SystemTime.High2Time);
321 
322     /* Change it for user-mode applications */
323     CurrentTime.QuadPart += ExpTimeZoneBias.QuadPart;
324     SharedUserData->SystemTime.LowPart = CurrentTime.u.LowPart;
325     SharedUserData->SystemTime.High1Time = CurrentTime.u.HighPart;
326     SharedUserData->SystemTime.High2Time = CurrentTime.u.HighPart;
327 
328     /* Return success */
329     return TRUE;
330 }
331 
332 NTSTATUS
333 ExpSetTimeZoneInformation(PRTL_TIME_ZONE_INFORMATION TimeZoneInformation)
334 {
335     LARGE_INTEGER LocalTime, SystemTime, OldTime;
336     TIME_FIELDS TimeFields;
337     DPRINT("ExpSetTimeZoneInformation() called\n");
338 
339     DPRINT("Old time zone bias: %d minutes\n", ExpTimeZoneInfo.Bias);
340     DPRINT("Old time zone standard bias: %d minutes\n",
341             ExpTimeZoneInfo.StandardBias);
342     DPRINT("New time zone bias: %d minutes\n", TimeZoneInformation->Bias);
343     DPRINT("New time zone standard bias: %d minutes\n",
344             TimeZoneInformation->StandardBias);
345 
346     /* Get the local time */
347     HalQueryRealTimeClock(&TimeFields);
348     RtlTimeFieldsToTime(&TimeFields, &LocalTime);
349 
350     /* FIXME: Calculate transition dates */
351 
352     /* Calculate the bias and set the ID */
353     ExpTimeZoneBias.QuadPart = ((LONGLONG)(TimeZoneInformation->Bias +
354                                            TimeZoneInformation->StandardBias)) *
355                                            TICKSPERMINUTE;
356     ExpTimeZoneId = TIME_ZONE_ID_STANDARD;
357 
358     /* Copy the timezone information */
359     RtlCopyMemory(&ExpTimeZoneInfo,
360                   TimeZoneInformation,
361                   sizeof(RTL_TIME_ZONE_INFORMATION));
362 
363     /* Set the new time zone information */
364     SharedUserData->TimeZoneBias.High1Time = ExpTimeZoneBias.u.HighPart;
365     SharedUserData->TimeZoneBias.High2Time = ExpTimeZoneBias.u.HighPart;
366     SharedUserData->TimeZoneBias.LowPart = ExpTimeZoneBias.u.LowPart;
367     SharedUserData->TimeZoneId = ExpTimeZoneId;
368 
369     DPRINT("New time zone bias: %I64d minutes\n",
370             ExpTimeZoneBias.QuadPart / TICKSPERMINUTE);
371 
372     /* Calculate the new system time */
373     ExLocalTimeToSystemTime(&LocalTime, &SystemTime);
374 
375     /* Set the new system time */
376     KeSetSystemTime(&SystemTime, &OldTime, FALSE, NULL);
377 
378     /* Return success */
379     DPRINT("ExpSetTimeZoneInformation() done\n");
380     return STATUS_SUCCESS;
381 }
382 
383 /*
384  * FUNCTION: Sets the system time.
385  * PARAMETERS:
386  *        NewTime - Points to a variable that specified the new time
387  *        of day in the standard time format.
388  *        OldTime - Optionally points to a variable that receives the
389  *        old time of day in the standard time format.
390  * RETURNS: Status
391  */
392 NTSTATUS
393 NTAPI
394 NtSetSystemTime(IN PLARGE_INTEGER SystemTime,
395                 OUT PLARGE_INTEGER PreviousTime OPTIONAL)
396 {
397     LARGE_INTEGER OldSystemTime;
398     LARGE_INTEGER NewSystemTime;
399     LARGE_INTEGER LocalTime;
400     TIME_FIELDS TimeFields;
401     KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
402     NTSTATUS Status = STATUS_SUCCESS;
403     PAGED_CODE();
404 
405     /* Check if we were called from user-mode */
406     if (PreviousMode != KernelMode)
407     {
408         _SEH2_TRY
409         {
410             /* Verify the time pointers */
411             NewSystemTime = ProbeForReadLargeInteger(SystemTime);
412             if(PreviousTime) ProbeForWriteLargeInteger(PreviousTime);
413         }
414         _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
415         {
416             /* Return the exception code */
417             _SEH2_YIELD(return _SEH2_GetExceptionCode());
418         }
419         _SEH2_END;
420     }
421     else
422     {
423         /* Reuse the pointer */
424         NewSystemTime = *SystemTime;
425     }
426 
427     /* Make sure we have permission to change the time */
428     if (!SeSinglePrivilegeCheck(SeSystemtimePrivilege, PreviousMode))
429     {
430         DPRINT1("NtSetSystemTime: Caller requires the "
431                 "SeSystemtimePrivilege privilege!\n");
432         return STATUS_PRIVILEGE_NOT_HELD;
433     }
434 
435     /* Convert the time and set it in HAL */
436     ExSystemTimeToLocalTime(&NewSystemTime, &LocalTime);
437     RtlTimeToTimeFields(&LocalTime, &TimeFields);
438     HalSetRealTimeClock(&TimeFields);
439 
440     /* Now set system time */
441     KeSetSystemTime(&NewSystemTime, &OldSystemTime, FALSE, NULL);
442 
443     /* Check if caller wanted previous time */
444     if (PreviousTime)
445     {
446         /* Enter SEH Block for return */
447         _SEH2_TRY
448         {
449             /* Return the previous time */
450             *PreviousTime = OldSystemTime;
451         }
452         _SEH2_EXCEPT(ExSystemExceptionFilter())
453         {
454             /* Get the exception code */
455             Status = _SEH2_GetExceptionCode();
456         }
457         _SEH2_END;
458     }
459 
460     /* Return status */
461     return Status;
462 }
463 
464 /*
465  * FUNCTION: Retrieves the system time.
466  * PARAMETERS:
467  *          CurrentTime - Points to a variable that receives the current
468  *          time of day in the standard time format.
469  */
470 NTSTATUS
471 NTAPI
472 NtQuerySystemTime(OUT PLARGE_INTEGER SystemTime)
473 {
474     KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
475     PAGED_CODE();
476 
477     /* Check if we were called from user-mode */
478     if (PreviousMode != KernelMode)
479     {
480         _SEH2_TRY
481         {
482             /* Verify the time pointer */
483             ProbeForWriteLargeInteger(SystemTime);
484 
485             /*
486              * It's safe to pass the pointer directly to KeQuerySystemTime
487              * as it's just a basic copy to this pointer. If it raises an
488              * exception nothing dangerous can happen!
489              */
490             KeQuerySystemTime(SystemTime);
491         }
492         _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
493         {
494             /* Return the exception code */
495             _SEH2_YIELD(return _SEH2_GetExceptionCode());
496         }
497         _SEH2_END;
498     }
499     else
500     {
501         /* Query the time directly */
502         KeQuerySystemTime(SystemTime);
503     }
504 
505     /* Return success */
506     return STATUS_SUCCESS;
507 }
508 
509 /*
510  * @implemented
511  */
512 VOID
513 NTAPI
514 ExLocalTimeToSystemTime(PLARGE_INTEGER LocalTime,
515                         PLARGE_INTEGER SystemTime)
516 {
517     SystemTime->QuadPart = LocalTime->QuadPart + ExpTimeZoneBias.QuadPart;
518 }
519 
520 /*
521  * @implemented
522  */
523 VOID
524 NTAPI
525 ExSystemTimeToLocalTime(PLARGE_INTEGER SystemTime,
526                         PLARGE_INTEGER LocalTime)
527 {
528     LocalTime->QuadPart = SystemTime->QuadPart - ExpTimeZoneBias.QuadPart;
529 }
530 
531 /*
532  * @implemented
533  */
534 NTSTATUS
535 NTAPI
536 NtQueryTimerResolution(OUT PULONG MinimumResolution,
537                        OUT PULONG MaximumResolution,
538                        OUT PULONG ActualResolution)
539 {
540     KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
541 
542     /* Check if the call came from user-mode */
543     if (PreviousMode != KernelMode)
544     {
545         _SEH2_TRY
546         {
547             /* Probe the parameters */
548             ProbeForWriteUlong(MinimumResolution);
549             ProbeForWriteUlong(MaximumResolution);
550             ProbeForWriteUlong(ActualResolution);
551 
552             /*
553              * Set the parameters to the actual values.
554              *
555              * NOTE:
556              * MinimumResolution corresponds to the biggest time increment and
557              * MaximumResolution corresponds to the smallest time increment.
558              */
559             *MinimumResolution = KeMaximumIncrement;
560             *MaximumResolution = KeMinimumIncrement;
561             *ActualResolution  = KeTimeIncrement;
562         }
563         _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
564         {
565             /* Return the exception code */
566             _SEH2_YIELD(return _SEH2_GetExceptionCode());
567         }
568         _SEH2_END;
569     }
570     else
571     {
572         /* Set the parameters to the actual values */
573         *MinimumResolution = KeMaximumIncrement;
574         *MaximumResolution = KeMinimumIncrement;
575         *ActualResolution  = KeTimeIncrement;
576     }
577 
578     /* Return success */
579     return STATUS_SUCCESS;
580 }
581 
582 /*
583  * @implemented
584  */
585 NTSTATUS
586 NTAPI
587 NtSetTimerResolution(IN ULONG DesiredResolution,
588                      IN BOOLEAN SetResolution,
589                      OUT PULONG CurrentResolution)
590 {
591     NTSTATUS Status;
592     KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
593     PEPROCESS Process = PsGetCurrentProcess();
594     ULONG NewResolution;
595 
596     /* Check if the call came from user-mode */
597     if (PreviousMode != KernelMode)
598     {
599         _SEH2_TRY
600         {
601             /* Probe the parameter */
602             ProbeForWriteUlong(CurrentResolution);
603         }
604         _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
605         {
606             /* Return the exception code */
607             _SEH2_YIELD(return _SEH2_GetExceptionCode());
608         }
609         _SEH2_END;
610     }
611 
612     /* Set and return the new resolution */
613     NewResolution = ExSetTimerResolution(DesiredResolution, SetResolution);
614 
615     if (PreviousMode != KernelMode)
616     {
617         _SEH2_TRY
618         {
619             *CurrentResolution = NewResolution;
620         }
621         _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
622         {
623             /* Return the exception code */
624             _SEH2_YIELD(return _SEH2_GetExceptionCode());
625         }
626         _SEH2_END;
627     }
628     else
629     {
630         *CurrentResolution = NewResolution;
631     }
632 
633     if (SetResolution || Process->SetTimerResolution)
634     {
635         /* The resolution has been changed now or in an earlier call */
636         Status = STATUS_SUCCESS;
637     }
638     else
639     {
640         /* The resolution hasn't been changed */
641         Status = STATUS_TIMER_RESOLUTION_NOT_SET;
642     }
643 
644     /* Update the flag */
645     Process->SetTimerResolution = SetResolution;
646 
647     return Status;
648 }
649 
650 /* EOF */
651