xref: /reactos/ntoskrnl/ex/time.c (revision d5399189)
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 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(RTL_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(PRTL_TIME_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(RTL_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 */
596