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