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