xref: /reactos/ntoskrnl/ex/time.c (revision 10e7643c)
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 and notify the system */
376     KeSetSystemTime(&SystemTime, &OldTime, FALSE, NULL);
377     PoNotifySystemTimeSet();
378 
379     /* Return success */
380     DPRINT("ExpSetTimeZoneInformation() done\n");
381     return STATUS_SUCCESS;
382 }
383 
384 /*
385  * FUNCTION: Sets the system time.
386  * PARAMETERS:
387  *        NewTime - Points to a variable that specified the new time
388  *        of day in the standard time format.
389  *        OldTime - Optionally points to a variable that receives the
390  *        old time of day in the standard time format.
391  * RETURNS: Status
392  */
393 NTSTATUS
394 NTAPI
395 NtSetSystemTime(IN PLARGE_INTEGER SystemTime,
396                 OUT PLARGE_INTEGER PreviousTime OPTIONAL)
397 {
398     LARGE_INTEGER OldSystemTime;
399     LARGE_INTEGER NewSystemTime;
400     LARGE_INTEGER LocalTime;
401     TIME_FIELDS TimeFields;
402     KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
403     NTSTATUS Status = STATUS_SUCCESS;
404 
405     PAGED_CODE();
406 
407     // TODO: Handle the case when SystemTime == NULL, which means:
408     // "update system time using the current time-zone information".
409     if (!SystemTime)
410     {
411         UNIMPLEMENTED;
412         return STATUS_NOT_IMPLEMENTED;
413     }
414 
415     /* Check if we were called from user-mode */
416     if (PreviousMode != KernelMode)
417     {
418         _SEH2_TRY
419         {
420             /* Verify the time pointers */
421             NewSystemTime = ProbeForReadLargeInteger(SystemTime);
422             if (PreviousTime) ProbeForWriteLargeInteger(PreviousTime);
423         }
424         _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
425         {
426             /* Return the exception code */
427             _SEH2_YIELD(return _SEH2_GetExceptionCode());
428         }
429         _SEH2_END;
430     }
431     else
432     {
433         /* Reuse the pointer */
434         NewSystemTime = *SystemTime;
435     }
436 
437     /* Make sure we have permission to change the time */
438     if (!SeSinglePrivilegeCheck(SeSystemtimePrivilege, PreviousMode))
439     {
440         DPRINT1("NtSetSystemTime: Caller requires the "
441                 "SeSystemtimePrivilege privilege!\n");
442         return STATUS_PRIVILEGE_NOT_HELD;
443     }
444 
445     /* Convert the time and set it in HAL */
446     ExSystemTimeToLocalTime(&NewSystemTime, &LocalTime);
447     RtlTimeToTimeFields(&LocalTime, &TimeFields);
448     HalSetRealTimeClock(&TimeFields);
449 
450     /* Now set the system time and notify the system */
451     KeSetSystemTime(&NewSystemTime, &OldSystemTime, FALSE, NULL);
452     PoNotifySystemTimeSet();
453 
454     /* Check if caller wanted previous time */
455     if (PreviousTime)
456     {
457         /* Enter SEH Block for return */
458         _SEH2_TRY
459         {
460             /* Return the previous time */
461             *PreviousTime = OldSystemTime;
462         }
463         _SEH2_EXCEPT(ExSystemExceptionFilter())
464         {
465             /* Get the exception code */
466             Status = _SEH2_GetExceptionCode();
467         }
468         _SEH2_END;
469     }
470 
471     /* Return status */
472     return Status;
473 }
474 
475 /*
476  * FUNCTION: Retrieves the system time.
477  * PARAMETERS:
478  *          CurrentTime - Points to a variable that receives the current
479  *          time of day in the standard time format.
480  */
481 NTSTATUS
482 NTAPI
483 NtQuerySystemTime(OUT PLARGE_INTEGER SystemTime)
484 {
485     KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
486     PAGED_CODE();
487 
488     /* Check if we were called from user-mode */
489     if (PreviousMode != KernelMode)
490     {
491         _SEH2_TRY
492         {
493             /* Verify the time pointer */
494             ProbeForWriteLargeInteger(SystemTime);
495 
496             /*
497              * It's safe to pass the pointer directly to KeQuerySystemTime
498              * as it's just a basic copy to this pointer. If it raises an
499              * exception nothing dangerous can happen!
500              */
501             KeQuerySystemTime(SystemTime);
502         }
503         _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
504         {
505             /* Return the exception code */
506             _SEH2_YIELD(return _SEH2_GetExceptionCode());
507         }
508         _SEH2_END;
509     }
510     else
511     {
512         /* Query the time directly */
513         KeQuerySystemTime(SystemTime);
514     }
515 
516     /* Return success */
517     return STATUS_SUCCESS;
518 }
519 
520 /*
521  * @implemented
522  */
523 VOID
524 NTAPI
525 ExLocalTimeToSystemTime(PLARGE_INTEGER LocalTime,
526                         PLARGE_INTEGER SystemTime)
527 {
528     SystemTime->QuadPart = LocalTime->QuadPart + ExpTimeZoneBias.QuadPart;
529 }
530 
531 /*
532  * @implemented
533  */
534 VOID
535 NTAPI
536 ExSystemTimeToLocalTime(PLARGE_INTEGER SystemTime,
537                         PLARGE_INTEGER LocalTime)
538 {
539     LocalTime->QuadPart = SystemTime->QuadPart - ExpTimeZoneBias.QuadPart;
540 }
541 
542 /*
543  * @implemented
544  */
545 NTSTATUS
546 NTAPI
547 NtQueryTimerResolution(OUT PULONG MinimumResolution,
548                        OUT PULONG MaximumResolution,
549                        OUT PULONG ActualResolution)
550 {
551     KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
552 
553     /* Check if the call came from user-mode */
554     if (PreviousMode != KernelMode)
555     {
556         _SEH2_TRY
557         {
558             /* Probe the parameters */
559             ProbeForWriteUlong(MinimumResolution);
560             ProbeForWriteUlong(MaximumResolution);
561             ProbeForWriteUlong(ActualResolution);
562 
563             /*
564              * Set the parameters to the actual values.
565              *
566              * NOTE:
567              * MinimumResolution corresponds to the biggest time increment and
568              * MaximumResolution corresponds to the smallest time increment.
569              */
570             *MinimumResolution = KeMaximumIncrement;
571             *MaximumResolution = KeMinimumIncrement;
572             *ActualResolution  = KeTimeIncrement;
573         }
574         _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
575         {
576             /* Return the exception code */
577             _SEH2_YIELD(return _SEH2_GetExceptionCode());
578         }
579         _SEH2_END;
580     }
581     else
582     {
583         /* Set the parameters to the actual values */
584         *MinimumResolution = KeMaximumIncrement;
585         *MaximumResolution = KeMinimumIncrement;
586         *ActualResolution  = KeTimeIncrement;
587     }
588 
589     /* Return success */
590     return STATUS_SUCCESS;
591 }
592 
593 /*
594  * @implemented
595  */
596 NTSTATUS
597 NTAPI
598 NtSetTimerResolution(IN ULONG DesiredResolution,
599                      IN BOOLEAN SetResolution,
600                      OUT PULONG CurrentResolution)
601 {
602     NTSTATUS Status;
603     KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
604     PEPROCESS Process = PsGetCurrentProcess();
605     ULONG NewResolution;
606 
607     /* Check if the call came from user-mode */
608     if (PreviousMode != KernelMode)
609     {
610         _SEH2_TRY
611         {
612             /* Probe the parameter */
613             ProbeForWriteUlong(CurrentResolution);
614         }
615         _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
616         {
617             /* Return the exception code */
618             _SEH2_YIELD(return _SEH2_GetExceptionCode());
619         }
620         _SEH2_END;
621     }
622 
623     /* Set and return the new resolution */
624     NewResolution = ExSetTimerResolution(DesiredResolution, SetResolution);
625 
626     if (PreviousMode != KernelMode)
627     {
628         _SEH2_TRY
629         {
630             *CurrentResolution = NewResolution;
631         }
632         _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
633         {
634             /* Return the exception code */
635             _SEH2_YIELD(return _SEH2_GetExceptionCode());
636         }
637         _SEH2_END;
638     }
639     else
640     {
641         *CurrentResolution = NewResolution;
642     }
643 
644     if (SetResolution || Process->SetTimerResolution)
645     {
646         /* The resolution has been changed now or in an earlier call */
647         Status = STATUS_SUCCESS;
648     }
649     else
650     {
651         /* The resolution hasn't been changed */
652         Status = STATUS_TIMER_RESOLUTION_NOT_SET;
653     }
654 
655     /* Update the flag */
656     Process->SetTimerResolution = SetResolution;
657 
658     return Status;
659 }
660 
661 /* EOF */
662