1 /** @file 2 Timer Architectural Protocol module using High Precesion Event Timer (HPET) 3 4 Copyright (c) 2011 - 2014, Intel Corporation. All rights reserved.<BR> 5 This program and the accompanying materials 6 are licensed and made available under the terms and conditions of the BSD License 7 which accompanies this distribution. The full text of the license may be found at 8 http://opensource.org/licenses/bsd-license.php 9 10 THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, 11 WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. 12 13 **/ 14 15 #include <PiDxe.h> 16 17 #include <Protocol/Cpu.h> 18 #include <Protocol/Timer.h> 19 20 #include <Library/IoLib.h> 21 #include <Library/PcdLib.h> 22 #include <Library/BaseLib.h> 23 #include <Library/DebugLib.h> 24 #include <Library/UefiBootServicesTableLib.h> 25 #include <Library/LocalApicLib.h> 26 #include <Library/IoApicLib.h> 27 28 #include <Register/LocalApic.h> 29 #include <Register/IoApic.h> 30 #include <Register/Hpet.h> 31 32 /// 33 /// Define value for an invalid HPET Timer index. 34 /// 35 #define HPET_INVALID_TIMER_INDEX 0xff 36 37 /// 38 /// Timer Architectural Protocol function prototypes. 39 /// 40 41 /** 42 This function registers the handler NotifyFunction so it is called every time 43 the timer interrupt fires. It also passes the amount of time since the last 44 handler call to the NotifyFunction. If NotifyFunction is NULL, then the 45 handler is unregistered. If the handler is registered, then EFI_SUCCESS is 46 returned. If the CPU does not support registering a timer interrupt handler, 47 then EFI_UNSUPPORTED is returned. If an attempt is made to register a handler 48 when a handler is already registered, then EFI_ALREADY_STARTED is returned. 49 If an attempt is made to unregister a handler when a handler is not registered, 50 then EFI_INVALID_PARAMETER is returned. If an error occurs attempting to 51 register the NotifyFunction with the timer interrupt, then EFI_DEVICE_ERROR 52 is returned. 53 54 @param This The EFI_TIMER_ARCH_PROTOCOL instance. 55 @param NotifyFunction The function to call when a timer interrupt fires. 56 This function executes at TPL_HIGH_LEVEL. The DXE 57 Core will register a handler for the timer interrupt, 58 so it can know how much time has passed. This 59 information is used to signal timer based events. 60 NULL will unregister the handler. 61 62 @retval EFI_SUCCESS The timer handler was registered. 63 @retval EFI_UNSUPPORTED The platform does not support timer interrupts. 64 @retval EFI_ALREADY_STARTED NotifyFunction is not NULL, and a handler is already 65 registered. 66 @retval EFI_INVALID_PARAMETER NotifyFunction is NULL, and a handler was not 67 previously registered. 68 @retval EFI_DEVICE_ERROR The timer handler could not be registered. 69 70 **/ 71 EFI_STATUS 72 EFIAPI 73 TimerDriverRegisterHandler ( 74 IN EFI_TIMER_ARCH_PROTOCOL *This, 75 IN EFI_TIMER_NOTIFY NotifyFunction 76 ); 77 78 /** 79 This function adjusts the period of timer interrupts to the value specified 80 by TimerPeriod. If the timer period is updated, then the selected timer 81 period is stored in EFI_TIMER.TimerPeriod, and EFI_SUCCESS is returned. If 82 the timer hardware is not programmable, then EFI_UNSUPPORTED is returned. 83 If an error occurs while attempting to update the timer period, then the 84 timer hardware will be put back in its state prior to this call, and 85 EFI_DEVICE_ERROR is returned. If TimerPeriod is 0, then the timer interrupt 86 is disabled. This is not the same as disabling the CPU's interrupts. 87 Instead, it must either turn off the timer hardware, or it must adjust the 88 interrupt controller so that a CPU interrupt is not generated when the timer 89 interrupt fires. 90 91 @param This The EFI_TIMER_ARCH_PROTOCOL instance. 92 @param TimerPeriod The rate to program the timer interrupt in 100 nS units. 93 If the timer hardware is not programmable, then 94 EFI_UNSUPPORTED is returned. If the timer is programmable, 95 then the timer period will be rounded up to the nearest 96 timer period that is supported by the timer hardware. 97 If TimerPeriod is set to 0, then the timer interrupts 98 will be disabled. 99 100 @retval EFI_SUCCESS The timer period was changed. 101 @retval EFI_UNSUPPORTED The platform cannot change the period of the timer interrupt. 102 @retval EFI_DEVICE_ERROR The timer period could not be changed due to a device error. 103 104 **/ 105 EFI_STATUS 106 EFIAPI 107 TimerDriverSetTimerPeriod ( 108 IN EFI_TIMER_ARCH_PROTOCOL *This, 109 IN UINT64 TimerPeriod 110 ); 111 112 /** 113 This function retrieves the period of timer interrupts in 100 ns units, 114 returns that value in TimerPeriod, and returns EFI_SUCCESS. If TimerPeriod 115 is NULL, then EFI_INVALID_PARAMETER is returned. If a TimerPeriod of 0 is 116 returned, then the timer is currently disabled. 117 118 @param This The EFI_TIMER_ARCH_PROTOCOL instance. 119 @param TimerPeriod A pointer to the timer period to retrieve in 100 ns units. 120 If 0 is returned, then the timer is currently disabled. 121 122 @retval EFI_SUCCESS The timer period was returned in TimerPeriod. 123 @retval EFI_INVALID_PARAMETER TimerPeriod is NULL. 124 125 **/ 126 EFI_STATUS 127 EFIAPI 128 TimerDriverGetTimerPeriod ( 129 IN EFI_TIMER_ARCH_PROTOCOL *This, 130 OUT UINT64 *TimerPeriod 131 ); 132 133 /** 134 This function generates a soft timer interrupt. If the platform does not support soft 135 timer interrupts, then EFI_UNSUPPORTED is returned. Otherwise, EFI_SUCCESS is returned. 136 If a handler has been registered through the EFI_TIMER_ARCH_PROTOCOL.RegisterHandler() 137 service, then a soft timer interrupt will be generated. If the timer interrupt is 138 enabled when this service is called, then the registered handler will be invoked. The 139 registered handler should not be able to distinguish a hardware-generated timer 140 interrupt from a software-generated timer interrupt. 141 142 @param This The EFI_TIMER_ARCH_PROTOCOL instance. 143 144 @retval EFI_SUCCESS The soft timer interrupt was generated. 145 @retval EFI_UNSUPPORTEDT The platform does not support the generation of soft 146 timer interrupts. 147 148 **/ 149 EFI_STATUS 150 EFIAPI 151 TimerDriverGenerateSoftInterrupt ( 152 IN EFI_TIMER_ARCH_PROTOCOL *This 153 ); 154 155 /// 156 /// The handle onto which the Timer Architectural Protocol will be installed. 157 /// 158 EFI_HANDLE mTimerHandle = NULL; 159 160 /// 161 /// The Timer Architectural Protocol that this driver produces. 162 /// 163 EFI_TIMER_ARCH_PROTOCOL mTimer = { 164 TimerDriverRegisterHandler, 165 TimerDriverSetTimerPeriod, 166 TimerDriverGetTimerPeriod, 167 TimerDriverGenerateSoftInterrupt 168 }; 169 170 /// 171 /// Pointer to the CPU Architectural Protocol instance. 172 /// 173 EFI_CPU_ARCH_PROTOCOL *mCpu = NULL; 174 175 /// 176 /// The notification function to call on every timer interrupt. 177 /// 178 EFI_TIMER_NOTIFY mTimerNotifyFunction = NULL; 179 180 /// 181 /// The current period of the HPET timer interrupt in 100 ns units. 182 /// 183 UINT64 mTimerPeriod = 0; 184 185 /// 186 /// The number of HPET timer ticks required for the current HPET rate specified by mTimerPeriod. 187 /// 188 UINT64 mTimerCount; 189 190 /// 191 /// Mask used for counter and comparator calculations to adjust for a 32-bit or 64-bit counter. 192 /// 193 UINT64 mCounterMask; 194 195 /// 196 /// The HPET main counter value from the most recent HPET timer interrupt. 197 /// 198 volatile UINT64 mPreviousMainCounter; 199 200 volatile UINT64 mPreviousComparator; 201 202 /// 203 /// The index of the HPET timer being managed by this driver. 204 /// 205 UINTN mTimerIndex; 206 207 /// 208 /// The I/O APIC IRQ that the HPET Timer is mapped if I/O APIC mode is used. 209 /// 210 UINT32 mTimerIrq; 211 212 /// 213 /// Cached state of the HPET General Capabilities register managed by this driver. 214 /// Caching the state reduces the number of times the configuration register is read. 215 /// 216 HPET_GENERAL_CAPABILITIES_ID_REGISTER mHpetGeneralCapabilities; 217 218 /// 219 /// Cached state of the HPET General Configuration register managed by this driver. 220 /// Caching the state reduces the number of times the configuration register is read. 221 /// 222 HPET_GENERAL_CONFIGURATION_REGISTER mHpetGeneralConfiguration; 223 224 /// 225 /// Cached state of the Configuration register for the HPET Timer managed by 226 /// this driver. Caching the state reduces the number of times the configuration 227 /// register is read. 228 /// 229 HPET_TIMER_CONFIGURATION_REGISTER mTimerConfiguration; 230 231 /// 232 /// Counts the number of HPET Timer interrupts processed by this driver. 233 /// Only required for debug. 234 /// 235 volatile UINTN mNumTicks; 236 237 /** 238 Read a 64-bit register from the HPET 239 240 @param Offset Specifies the offset of the HPET register to read. 241 242 @return The 64-bit value read from the HPET register specified by Offset. 243 **/ 244 UINT64 245 HpetRead ( 246 IN UINTN Offset 247 ) 248 { 249 return MmioRead64 (PcdGet32 (PcdHpetBaseAddress) + Offset); 250 } 251 252 /** 253 Write a 64-bit HPET register. 254 255 @param Offset Specifies the ofsfert of the HPET register to write. 256 @param Value Specifies the value to write to the HPET register specified by Offset. 257 258 @return The 64-bit value written to HPET register specified by Offset. 259 **/ 260 UINT64 261 HpetWrite ( 262 IN UINTN Offset, 263 IN UINT64 Value 264 ) 265 { 266 return MmioWrite64 (PcdGet32 (PcdHpetBaseAddress) + Offset, Value); 267 } 268 269 /** 270 Enable or disable the main counter in the HPET Timer. 271 272 @param Enable If TRUE, then enable the main counter in the HPET Timer. 273 If FALSE, then disable the main counter in the HPET Timer. 274 **/ 275 VOID 276 HpetEnable ( 277 IN BOOLEAN Enable 278 ) 279 { 280 mHpetGeneralConfiguration.Bits.MainCounterEnable = Enable ? 1 : 0; 281 HpetWrite (HPET_GENERAL_CONFIGURATION_OFFSET, mHpetGeneralConfiguration.Uint64); 282 } 283 284 /** 285 The interrupt handler for the HPET timer. This handler clears the HPET interrupt 286 and computes the amount of time that has passed since the last HPET timer interrupt. 287 If a notification function is registered, then the amount of time since the last 288 HPET interrupt is passed to that notification function in 100 ns units. The HPET 289 time is updated to generate another interrupt in the required time period. 290 291 @param InterruptType The type of interrupt that occured. 292 @param SystemContext A pointer to the system context when the interrupt occured. 293 **/ 294 VOID 295 EFIAPI 296 TimerInterruptHandler ( 297 IN EFI_EXCEPTION_TYPE InterruptType, 298 IN EFI_SYSTEM_CONTEXT SystemContext 299 ) 300 { 301 UINT64 MainCounter; 302 UINT64 Comparator; 303 UINT64 TimerPeriod; 304 UINT64 Delta; 305 306 // 307 // Count number of ticks 308 // 309 DEBUG_CODE (mNumTicks++;); 310 311 // 312 // Clear HPET timer interrupt status 313 // 314 HpetWrite (HPET_GENERAL_INTERRUPT_STATUS_OFFSET, LShiftU64 (1, mTimerIndex)); 315 316 // 317 // Local APIC EOI 318 // 319 SendApicEoi (); 320 321 // 322 // Disable HPET timer when adjusting the COMPARATOR value to prevent a missed interrupt 323 // 324 HpetEnable (FALSE); 325 326 // 327 // Capture main counter value 328 // 329 MainCounter = HpetRead (HPET_MAIN_COUNTER_OFFSET); 330 331 // 332 // Get the previous comparator counter 333 // 334 mPreviousComparator = HpetRead (HPET_TIMER_COMPARATOR_OFFSET + mTimerIndex * HPET_TIMER_STRIDE); 335 336 // 337 // Set HPET COMPARATOR to the value required for the next timer tick 338 // 339 Comparator = (mPreviousComparator + mTimerCount) & mCounterMask; 340 341 if ((mPreviousMainCounter < MainCounter) && (mPreviousComparator > Comparator)) { 342 // 343 // When comparator overflows 344 // 345 HpetWrite (HPET_TIMER_COMPARATOR_OFFSET + mTimerIndex * HPET_TIMER_STRIDE, Comparator); 346 } else if ((mPreviousMainCounter > MainCounter) && (mPreviousComparator < Comparator)) { 347 // 348 // When main counter overflows 349 // 350 HpetWrite (HPET_TIMER_COMPARATOR_OFFSET + mTimerIndex * HPET_TIMER_STRIDE, (MainCounter + mTimerCount) & mCounterMask); 351 } else { 352 // 353 // When both main counter and comparator do not overflow or both do overflow 354 // 355 if (Comparator > MainCounter) { 356 HpetWrite (HPET_TIMER_COMPARATOR_OFFSET + mTimerIndex * HPET_TIMER_STRIDE, Comparator); 357 } else { 358 HpetWrite (HPET_TIMER_COMPARATOR_OFFSET + mTimerIndex * HPET_TIMER_STRIDE, (MainCounter + mTimerCount) & mCounterMask); 359 } 360 } 361 362 // 363 // Enable the HPET counter once the new COMPARATOR value has been set. 364 // 365 HpetEnable (TRUE); 366 367 // 368 // Check to see if there is a registered notification function 369 // 370 if (mTimerNotifyFunction != NULL) { 371 // 372 // Compute time since last notification in 100 ns units (10 ^ -7) 373 // 374 if (MainCounter > mPreviousMainCounter) { 375 // 376 // Main counter does not overflow 377 // 378 Delta = MainCounter - mPreviousMainCounter; 379 } else { 380 // 381 // Main counter overflows, first usb, then add 382 // 383 Delta = (mCounterMask - mPreviousMainCounter) + MainCounter; 384 } 385 TimerPeriod = DivU64x32 ( 386 MultU64x32 ( 387 Delta & mCounterMask, 388 mHpetGeneralCapabilities.Bits.CounterClockPeriod 389 ), 390 100000000 391 ); 392 393 // 394 // Call registered notification function passing in the time since the last 395 // interrupt in 100 ns units. 396 // 397 mTimerNotifyFunction (TimerPeriod); 398 } 399 400 // 401 // Save main counter value 402 // 403 mPreviousMainCounter = MainCounter; 404 } 405 406 /** 407 This function registers the handler NotifyFunction so it is called every time 408 the timer interrupt fires. It also passes the amount of time since the last 409 handler call to the NotifyFunction. If NotifyFunction is NULL, then the 410 handler is unregistered. If the handler is registered, then EFI_SUCCESS is 411 returned. If the CPU does not support registering a timer interrupt handler, 412 then EFI_UNSUPPORTED is returned. If an attempt is made to register a handler 413 when a handler is already registered, then EFI_ALREADY_STARTED is returned. 414 If an attempt is made to unregister a handler when a handler is not registered, 415 then EFI_INVALID_PARAMETER is returned. If an error occurs attempting to 416 register the NotifyFunction with the timer interrupt, then EFI_DEVICE_ERROR 417 is returned. 418 419 @param This The EFI_TIMER_ARCH_PROTOCOL instance. 420 @param NotifyFunction The function to call when a timer interrupt fires. 421 This function executes at TPL_HIGH_LEVEL. The DXE 422 Core will register a handler for the timer interrupt, 423 so it can know how much time has passed. This 424 information is used to signal timer based events. 425 NULL will unregister the handler. 426 427 @retval EFI_SUCCESS The timer handler was registered. 428 @retval EFI_UNSUPPORTED The platform does not support timer interrupts. 429 @retval EFI_ALREADY_STARTED NotifyFunction is not NULL, and a handler is already 430 registered. 431 @retval EFI_INVALID_PARAMETER NotifyFunction is NULL, and a handler was not 432 previously registered. 433 @retval EFI_DEVICE_ERROR The timer handler could not be registered. 434 435 **/ 436 EFI_STATUS 437 EFIAPI 438 TimerDriverRegisterHandler ( 439 IN EFI_TIMER_ARCH_PROTOCOL *This, 440 IN EFI_TIMER_NOTIFY NotifyFunction 441 ) 442 { 443 // 444 // Check for invalid parameters 445 // 446 if (NotifyFunction == NULL && mTimerNotifyFunction == NULL) { 447 return EFI_INVALID_PARAMETER; 448 } 449 if (NotifyFunction != NULL && mTimerNotifyFunction != NULL) { 450 return EFI_ALREADY_STARTED; 451 } 452 453 // 454 // Cache the registered notification function 455 // 456 mTimerNotifyFunction = NotifyFunction; 457 458 return EFI_SUCCESS; 459 } 460 461 /** 462 This function adjusts the period of timer interrupts to the value specified 463 by TimerPeriod. If the timer period is updated, then the selected timer 464 period is stored in EFI_TIMER.TimerPeriod, and EFI_SUCCESS is returned. If 465 the timer hardware is not programmable, then EFI_UNSUPPORTED is returned. 466 If an error occurs while attempting to update the timer period, then the 467 timer hardware will be put back in its state prior to this call, and 468 EFI_DEVICE_ERROR is returned. If TimerPeriod is 0, then the timer interrupt 469 is disabled. This is not the same as disabling the CPU's interrupts. 470 Instead, it must either turn off the timer hardware, or it must adjust the 471 interrupt controller so that a CPU interrupt is not generated when the timer 472 interrupt fires. 473 474 @param This The EFI_TIMER_ARCH_PROTOCOL instance. 475 @param TimerPeriod The rate to program the timer interrupt in 100 nS units. 476 If the timer hardware is not programmable, then 477 EFI_UNSUPPORTED is returned. If the timer is programmable, 478 then the timer period will be rounded up to the nearest 479 timer period that is supported by the timer hardware. 480 If TimerPeriod is set to 0, then the timer interrupts 481 will be disabled. 482 483 @retval EFI_SUCCESS The timer period was changed. 484 @retval EFI_UNSUPPORTED The platform cannot change the period of the timer interrupt. 485 @retval EFI_DEVICE_ERROR The timer period could not be changed due to a device error. 486 487 **/ 488 EFI_STATUS 489 EFIAPI 490 TimerDriverSetTimerPeriod ( 491 IN EFI_TIMER_ARCH_PROTOCOL *This, 492 IN UINT64 TimerPeriod 493 ) 494 { 495 UINT64 MainCounter; 496 UINT64 Delta; 497 UINT64 CurrentComparator; 498 HPET_TIMER_MSI_ROUTE_REGISTER HpetTimerMsiRoute; 499 500 // 501 // Disable HPET timer when adjusting the timer period 502 // 503 HpetEnable (FALSE); 504 505 if (TimerPeriod == 0) { 506 if (mTimerPeriod != 0) { 507 // 508 // Check if there is possibly a pending interrupt 509 // 510 MainCounter = HpetRead (HPET_MAIN_COUNTER_OFFSET); 511 if (MainCounter < mPreviousMainCounter) { 512 Delta = (mCounterMask - mPreviousMainCounter) + MainCounter; 513 } else { 514 Delta = MainCounter - mPreviousMainCounter; 515 } 516 if ((Delta & mCounterMask) >= mTimerCount) { 517 // 518 // Interrupt still happens after disable HPET, wait to be processed 519 // Wait until interrupt is processed and comparator is increased 520 // 521 CurrentComparator = HpetRead (HPET_TIMER_COMPARATOR_OFFSET + mTimerIndex * HPET_TIMER_STRIDE); 522 while (CurrentComparator == mPreviousComparator) { 523 CurrentComparator = HpetRead (HPET_TIMER_COMPARATOR_OFFSET + mTimerIndex * HPET_TIMER_STRIDE); 524 CpuPause(); 525 } 526 } 527 } 528 529 // 530 // If TimerPeriod is 0, then mask HPET Timer interrupts 531 // 532 533 if (mTimerConfiguration.Bits.MsiInterruptCapablity != 0 && FeaturePcdGet (PcdHpetMsiEnable)) { 534 // 535 // Disable HPET MSI interrupt generation 536 // 537 mTimerConfiguration.Bits.MsiInterruptEnable = 0; 538 } else { 539 // 540 // Disable I/O APIC Interrupt 541 // 542 IoApicEnableInterrupt (mTimerIrq, FALSE); 543 } 544 545 // 546 // Disable HPET timer interrupt 547 // 548 mTimerConfiguration.Bits.InterruptEnable = 0; 549 HpetWrite (HPET_TIMER_CONFIGURATION_OFFSET + mTimerIndex * HPET_TIMER_STRIDE, mTimerConfiguration.Uint64); 550 } else { 551 // 552 // Convert TimerPeriod to femtoseconds and divide by the number if femtoseconds 553 // per tick of the HPET counter to determine the number of HPET counter ticks 554 // in TimerPeriod 100 ns units. 555 // 556 mTimerCount = DivU64x32 ( 557 MultU64x32 (TimerPeriod, 100000000), 558 mHpetGeneralCapabilities.Bits.CounterClockPeriod 559 ); 560 561 // 562 // Program the HPET Comparator with the number of ticks till the next interrupt 563 // 564 MainCounter = HpetRead (HPET_MAIN_COUNTER_OFFSET); 565 if (MainCounter > mPreviousMainCounter) { 566 Delta = MainCounter - mPreviousMainCounter; 567 } else { 568 Delta = (mCounterMask - mPreviousMainCounter) + MainCounter; 569 } 570 if ((Delta & mCounterMask) >= mTimerCount) { 571 HpetWrite (HPET_TIMER_COMPARATOR_OFFSET + mTimerIndex * HPET_TIMER_STRIDE, (MainCounter + 1) & mCounterMask); 572 } else { 573 HpetWrite (HPET_TIMER_COMPARATOR_OFFSET + mTimerIndex * HPET_TIMER_STRIDE, (mPreviousMainCounter + mTimerCount) & mCounterMask); 574 } 575 576 // 577 // Enable HPET Timer interrupt generation 578 // 579 if (mTimerConfiguration.Bits.MsiInterruptCapablity != 0 && FeaturePcdGet (PcdHpetMsiEnable)) { 580 // 581 // Program MSI Address and MSI Data values in the selected HPET Timer 582 // Program HPET register with APIC ID of current BSP in case BSP has been switched 583 // 584 HpetTimerMsiRoute.Bits.Address = GetApicMsiAddress (); 585 HpetTimerMsiRoute.Bits.Value = (UINT32)GetApicMsiValue (PcdGet8 (PcdHpetLocalApicVector), LOCAL_APIC_DELIVERY_MODE_LOWEST_PRIORITY, FALSE, FALSE); 586 HpetWrite (HPET_TIMER_MSI_ROUTE_OFFSET + mTimerIndex * HPET_TIMER_STRIDE, HpetTimerMsiRoute.Uint64); 587 // 588 // Enable HPET MSI Interrupt 589 // 590 mTimerConfiguration.Bits.MsiInterruptEnable = 1; 591 } else { 592 // 593 // Enable timer interrupt through I/O APIC 594 // Program IOAPIC register with APIC ID of current BSP in case BSP has been switched 595 // 596 IoApicConfigureInterrupt (mTimerIrq, PcdGet8 (PcdHpetLocalApicVector), IO_APIC_DELIVERY_MODE_LOWEST_PRIORITY, TRUE, FALSE); 597 IoApicEnableInterrupt (mTimerIrq, TRUE); 598 } 599 600 // 601 // Enable HPET Interrupt Generation 602 // 603 mTimerConfiguration.Bits.InterruptEnable = 1; 604 HpetWrite (HPET_TIMER_CONFIGURATION_OFFSET + mTimerIndex * HPET_TIMER_STRIDE, mTimerConfiguration.Uint64); 605 } 606 607 // 608 // Save the new timer period 609 // 610 mTimerPeriod = TimerPeriod; 611 612 // 613 // Enable the HPET counter once new timer period has been established 614 // The HPET counter should run even if the HPET Timer interrupts are 615 // disabled. This is used to account for time passed while the interrupt 616 // is disabled. 617 // 618 HpetEnable (TRUE); 619 620 return EFI_SUCCESS; 621 } 622 623 /** 624 This function retrieves the period of timer interrupts in 100 ns units, 625 returns that value in TimerPeriod, and returns EFI_SUCCESS. If TimerPeriod 626 is NULL, then EFI_INVALID_PARAMETER is returned. If a TimerPeriod of 0 is 627 returned, then the timer is currently disabled. 628 629 @param This The EFI_TIMER_ARCH_PROTOCOL instance. 630 @param TimerPeriod A pointer to the timer period to retrieve in 100 ns units. 631 If 0 is returned, then the timer is currently disabled. 632 633 @retval EFI_SUCCESS The timer period was returned in TimerPeriod. 634 @retval EFI_INVALID_PARAMETER TimerPeriod is NULL. 635 636 **/ 637 EFI_STATUS 638 EFIAPI 639 TimerDriverGetTimerPeriod ( 640 IN EFI_TIMER_ARCH_PROTOCOL *This, 641 OUT UINT64 *TimerPeriod 642 ) 643 { 644 if (TimerPeriod == NULL) { 645 return EFI_INVALID_PARAMETER; 646 } 647 648 *TimerPeriod = mTimerPeriod; 649 650 return EFI_SUCCESS; 651 } 652 653 /** 654 This function generates a soft timer interrupt. If the platform does not support soft 655 timer interrupts, then EFI_UNSUPPORTED is returned. Otherwise, EFI_SUCCESS is returned. 656 If a handler has been registered through the EFI_TIMER_ARCH_PROTOCOL.RegisterHandler() 657 service, then a soft timer interrupt will be generated. If the timer interrupt is 658 enabled when this service is called, then the registered handler will be invoked. The 659 registered handler should not be able to distinguish a hardware-generated timer 660 interrupt from a software-generated timer interrupt. 661 662 @param This The EFI_TIMER_ARCH_PROTOCOL instance. 663 664 @retval EFI_SUCCESS The soft timer interrupt was generated. 665 @retval EFI_UNSUPPORTEDT The platform does not support the generation of soft 666 timer interrupts. 667 668 **/ 669 EFI_STATUS 670 EFIAPI 671 TimerDriverGenerateSoftInterrupt ( 672 IN EFI_TIMER_ARCH_PROTOCOL *This 673 ) 674 { 675 UINT64 MainCounter; 676 EFI_TPL Tpl; 677 UINT64 TimerPeriod; 678 UINT64 Delta; 679 680 // 681 // Disable interrupts 682 // 683 Tpl = gBS->RaiseTPL (TPL_HIGH_LEVEL); 684 685 // 686 // Capture main counter value 687 // 688 MainCounter = HpetRead (HPET_MAIN_COUNTER_OFFSET); 689 690 // 691 // Check to see if there is a registered notification function 692 // 693 if (mTimerNotifyFunction != NULL) { 694 // 695 // Compute time since last interrupt in 100 ns units (10 ^ -7) 696 // 697 if (MainCounter > mPreviousMainCounter) { 698 // 699 // Main counter does not overflow 700 // 701 Delta = MainCounter - mPreviousMainCounter; 702 } else { 703 // 704 // Main counter overflows, first usb, then add 705 // 706 Delta = (mCounterMask - mPreviousMainCounter) + MainCounter; 707 } 708 709 TimerPeriod = DivU64x32 ( 710 MultU64x32 ( 711 Delta & mCounterMask, 712 mHpetGeneralCapabilities.Bits.CounterClockPeriod 713 ), 714 100000000 715 ); 716 717 // 718 // Call registered notification function passing in the time since the last 719 // interrupt in 100 ns units. 720 // 721 mTimerNotifyFunction (TimerPeriod); 722 } 723 724 // 725 // Save main counter value 726 // 727 mPreviousMainCounter = MainCounter; 728 729 // 730 // Restore interrupts 731 // 732 gBS->RestoreTPL (Tpl); 733 734 return EFI_SUCCESS; 735 } 736 737 /** 738 Initialize the Timer Architectural Protocol driver 739 740 @param ImageHandle ImageHandle of the loaded driver 741 @param SystemTable Pointer to the System Table 742 743 @retval EFI_SUCCESS Timer Architectural Protocol created 744 @retval EFI_OUT_OF_RESOURCES Not enough resources available to initialize driver. 745 @retval EFI_DEVICE_ERROR A device error occured attempting to initialize the driver. 746 747 **/ 748 EFI_STATUS 749 EFIAPI 750 TimerDriverInitialize ( 751 IN EFI_HANDLE ImageHandle, 752 IN EFI_SYSTEM_TABLE *SystemTable 753 ) 754 { 755 EFI_STATUS Status; 756 UINTN TimerIndex; 757 UINTN MsiTimerIndex; 758 HPET_TIMER_MSI_ROUTE_REGISTER HpetTimerMsiRoute; 759 760 DEBUG ((DEBUG_INFO, "Init HPET Timer Driver\n")); 761 762 // 763 // Make sure the Timer Architectural Protocol is not already installed in the system 764 // 765 ASSERT_PROTOCOL_ALREADY_INSTALLED (NULL, &gEfiTimerArchProtocolGuid); 766 767 // 768 // Find the CPU architectural protocol. 769 // 770 Status = gBS->LocateProtocol (&gEfiCpuArchProtocolGuid, NULL, (VOID **) &mCpu); 771 ASSERT_EFI_ERROR (Status); 772 773 // 774 // Retrieve HPET Capabilities and Configuration Information 775 // 776 mHpetGeneralCapabilities.Uint64 = HpetRead (HPET_GENERAL_CAPABILITIES_ID_OFFSET); 777 mHpetGeneralConfiguration.Uint64 = HpetRead (HPET_GENERAL_CONFIGURATION_OFFSET); 778 779 // 780 // If Revision is not valid, then ASSERT() and unload the driver because the HPET 781 // device is not present. 782 // 783 ASSERT (mHpetGeneralCapabilities.Uint64 != 0); 784 ASSERT (mHpetGeneralCapabilities.Uint64 != 0xFFFFFFFFFFFFFFFFULL); 785 if (mHpetGeneralCapabilities.Uint64 == 0 || mHpetGeneralCapabilities.Uint64 == 0xFFFFFFFFFFFFFFFFULL) { 786 DEBUG ((DEBUG_ERROR, "HPET device is not present. Unload HPET driver.\n")); 787 return EFI_DEVICE_ERROR; 788 } 789 790 // 791 // Force the HPET timer to be disabled while setting everything up 792 // 793 HpetEnable (FALSE); 794 795 // 796 // Dump HPET Configuration Information 797 // 798 DEBUG_CODE ( 799 DEBUG ((DEBUG_INFO, "HPET Base Address = 0x%08x\n", PcdGet32 (PcdHpetBaseAddress))); 800 DEBUG ((DEBUG_INFO, " HPET_GENERAL_CAPABILITIES_ID = 0x%016lx\n", mHpetGeneralCapabilities)); 801 DEBUG ((DEBUG_INFO, " HPET_GENERAL_CONFIGURATION = 0x%016lx\n", mHpetGeneralConfiguration.Uint64)); 802 DEBUG ((DEBUG_INFO, " HPET_GENERAL_INTERRUPT_STATUS = 0x%016lx\n", HpetRead (HPET_GENERAL_INTERRUPT_STATUS_OFFSET))); 803 DEBUG ((DEBUG_INFO, " HPET_MAIN_COUNTER = 0x%016lx\n", HpetRead (HPET_MAIN_COUNTER_OFFSET))); 804 DEBUG ((DEBUG_INFO, " HPET Main Counter Period = %d (fs)\n", mHpetGeneralCapabilities.Bits.CounterClockPeriod)); 805 for (TimerIndex = 0; TimerIndex <= mHpetGeneralCapabilities.Bits.NumberOfTimers; TimerIndex++) { 806 DEBUG ((DEBUG_INFO, " HPET_TIMER%d_CONFIGURATION = 0x%016lx\n", TimerIndex, HpetRead (HPET_TIMER_CONFIGURATION_OFFSET + TimerIndex * HPET_TIMER_STRIDE))); 807 DEBUG ((DEBUG_INFO, " HPET_TIMER%d_COMPARATOR = 0x%016lx\n", TimerIndex, HpetRead (HPET_TIMER_COMPARATOR_OFFSET + TimerIndex * HPET_TIMER_STRIDE))); 808 DEBUG ((DEBUG_INFO, " HPET_TIMER%d_MSI_ROUTE = 0x%016lx\n", TimerIndex, HpetRead (HPET_TIMER_MSI_ROUTE_OFFSET + TimerIndex * HPET_TIMER_STRIDE))); 809 } 810 ); 811 812 // 813 // Capture the current HPET main counter value. 814 // 815 mPreviousMainCounter = HpetRead (HPET_MAIN_COUNTER_OFFSET); 816 817 // 818 // Determine the interrupt mode to use for the HPET Timer. 819 // Look for MSI first, then unused PIC mode interrupt, then I/O APIC mode interrupt 820 // 821 MsiTimerIndex = HPET_INVALID_TIMER_INDEX; 822 mTimerIndex = HPET_INVALID_TIMER_INDEX; 823 for (TimerIndex = 0; TimerIndex <= mHpetGeneralCapabilities.Bits.NumberOfTimers; TimerIndex++) { 824 // 825 // Read the HPET Timer Capabilities and Configuration register 826 // 827 mTimerConfiguration.Uint64 = HpetRead (HPET_TIMER_CONFIGURATION_OFFSET + TimerIndex * HPET_TIMER_STRIDE); 828 829 // 830 // Check to see if this HPET Timer supports MSI 831 // 832 if (mTimerConfiguration.Bits.MsiInterruptCapablity != 0) { 833 // 834 // Save the index of the first HPET Timer that supports MSI interrupts 835 // 836 if (MsiTimerIndex == HPET_INVALID_TIMER_INDEX) { 837 MsiTimerIndex = TimerIndex; 838 } 839 } 840 841 // 842 // Check to see if this HPET Timer supports I/O APIC interrupts 843 // 844 if (mTimerConfiguration.Bits.InterruptRouteCapability != 0) { 845 // 846 // Save the index of the first HPET Timer that supports I/O APIC interrupts 847 // 848 if (mTimerIndex == HPET_INVALID_TIMER_INDEX) { 849 mTimerIndex = TimerIndex; 850 mTimerIrq = (UINT32)LowBitSet32 (mTimerConfiguration.Bits.InterruptRouteCapability); 851 } 852 } 853 } 854 855 if (FeaturePcdGet (PcdHpetMsiEnable) && MsiTimerIndex != HPET_INVALID_TIMER_INDEX) { 856 // 857 // Use MSI interrupt if supported 858 // 859 mTimerIndex = MsiTimerIndex; 860 861 // 862 // Program MSI Address and MSI Data values in the selected HPET Timer 863 // 864 HpetTimerMsiRoute.Bits.Address = GetApicMsiAddress (); 865 HpetTimerMsiRoute.Bits.Value = (UINT32)GetApicMsiValue (PcdGet8 (PcdHpetLocalApicVector), LOCAL_APIC_DELIVERY_MODE_LOWEST_PRIORITY, FALSE, FALSE); 866 HpetWrite (HPET_TIMER_MSI_ROUTE_OFFSET + mTimerIndex * HPET_TIMER_STRIDE, HpetTimerMsiRoute.Uint64); 867 868 // 869 // Read the HPET Timer Capabilities and Configuration register and initialize for MSI mode 870 // Clear LevelTriggeredInterrupt to use edge triggered interrupts when in MSI mode 871 // 872 mTimerConfiguration.Uint64 = HpetRead (HPET_TIMER_CONFIGURATION_OFFSET + mTimerIndex * HPET_TIMER_STRIDE); 873 mTimerConfiguration.Bits.LevelTriggeredInterrupt = 0; 874 } else { 875 // 876 // If no HPET timers support MSI or I/O APIC modes, then ASSERT() and unload the driver. 877 // 878 ASSERT (mTimerIndex != HPET_INVALID_TIMER_INDEX); 879 if (mTimerIndex == HPET_INVALID_TIMER_INDEX) { 880 DEBUG ((DEBUG_ERROR, "No HPET timers support MSI or I/O APIC mode. Unload HPET driver.\n")); 881 return EFI_DEVICE_ERROR; 882 } 883 884 // 885 // Initialize I/O APIC entry for HPET Timer Interrupt 886 // Fixed Delivery Mode, Level Triggered, Asserted Low 887 // 888 IoApicConfigureInterrupt (mTimerIrq, PcdGet8 (PcdHpetLocalApicVector), IO_APIC_DELIVERY_MODE_LOWEST_PRIORITY, TRUE, FALSE); 889 890 // 891 // Read the HPET Timer Capabilities and Configuration register and initialize for I/O APIC mode 892 // Clear MsiInterruptCapability to force rest of driver to use I/O APIC mode 893 // Set LevelTriggeredInterrupt to use level triggered interrupts when in I/O APIC mode 894 // Set InterruptRoute field based in mTimerIrq 895 // 896 mTimerConfiguration.Uint64 = HpetRead (HPET_TIMER_CONFIGURATION_OFFSET + mTimerIndex * HPET_TIMER_STRIDE); 897 mTimerConfiguration.Bits.LevelTriggeredInterrupt = 1; 898 mTimerConfiguration.Bits.InterruptRoute = mTimerIrq; 899 } 900 901 // 902 // Configure the selected HPET Timer with settings common to both MSI mode and I/O APIC mode 903 // Clear InterruptEnable to keep interrupts disabled until full init is complete 904 // Clear PeriodicInterruptEnable to use one-shot mode 905 // Configure as a 32-bit counter 906 // 907 mTimerConfiguration.Bits.InterruptEnable = 0; 908 mTimerConfiguration.Bits.PeriodicInterruptEnable = 0; 909 mTimerConfiguration.Bits.CounterSizeEnable = 1; 910 HpetWrite (HPET_TIMER_CONFIGURATION_OFFSET + mTimerIndex * HPET_TIMER_STRIDE, mTimerConfiguration.Uint64); 911 912 // 913 // Read the HPET Timer Capabilities and Configuration register back again. 914 // CounterSizeEnable will be read back as a 0 if it is a 32-bit only timer 915 // 916 mTimerConfiguration.Uint64 = HpetRead (HPET_TIMER_CONFIGURATION_OFFSET + mTimerIndex * HPET_TIMER_STRIDE); 917 if ((mTimerConfiguration.Bits.CounterSizeEnable == 1) && (sizeof (UINTN) == sizeof (UINT64))) { 918 DEBUG ((DEBUG_INFO, "Choose 64-bit HPET timer.\n")); 919 // 920 // 64-bit BIOS can use 64-bit HPET timer 921 // 922 mCounterMask = 0xffffffffffffffffULL; 923 // 924 // Set timer back to 64-bit 925 // 926 mTimerConfiguration.Bits.CounterSizeEnable = 0; 927 HpetWrite (HPET_TIMER_CONFIGURATION_OFFSET + mTimerIndex * HPET_TIMER_STRIDE, mTimerConfiguration.Uint64); 928 } else { 929 DEBUG ((DEBUG_INFO, "Choose 32-bit HPET timer.\n")); 930 mCounterMask = 0x00000000ffffffffULL; 931 } 932 933 // 934 // Install interrupt handler for selected HPET Timer 935 // 936 Status = mCpu->RegisterInterruptHandler (mCpu, PcdGet8 (PcdHpetLocalApicVector), TimerInterruptHandler); 937 ASSERT_EFI_ERROR (Status); 938 if (EFI_ERROR (Status)) { 939 DEBUG ((DEBUG_ERROR, "Unable to register HPET interrupt with CPU Arch Protocol. Unload HPET driver.\n")); 940 return EFI_DEVICE_ERROR; 941 } 942 943 // 944 // Force the HPET Timer to be enabled at its default period 945 // 946 Status = TimerDriverSetTimerPeriod (&mTimer, PcdGet64 (PcdHpetDefaultTimerPeriod)); 947 ASSERT_EFI_ERROR (Status); 948 if (EFI_ERROR (Status)) { 949 DEBUG ((DEBUG_ERROR, "Unable to set HPET default timer rate. Unload HPET driver.\n")); 950 return EFI_DEVICE_ERROR; 951 } 952 953 // 954 // Show state of enabled HPET timer 955 // 956 DEBUG_CODE ( 957 if (mTimerConfiguration.Bits.MsiInterruptCapablity != 0 && FeaturePcdGet (PcdHpetMsiEnable)) { 958 DEBUG ((DEBUG_INFO, "HPET Interrupt Mode MSI\n")); 959 } else { 960 DEBUG ((DEBUG_INFO, "HPET Interrupt Mode I/O APIC\n")); 961 DEBUG ((DEBUG_INFO, "HPET I/O APIC IRQ = 0x%02x\n", mTimerIrq)); 962 } 963 DEBUG ((DEBUG_INFO, "HPET Interrupt Vector = 0x%02x\n", PcdGet8 (PcdHpetLocalApicVector))); 964 DEBUG ((DEBUG_INFO, "HPET Counter Mask = 0x%016lx\n", mCounterMask)); 965 DEBUG ((DEBUG_INFO, "HPET Timer Period = %d\n", mTimerPeriod)); 966 DEBUG ((DEBUG_INFO, "HPET Timer Count = 0x%016lx\n", mTimerCount)); 967 DEBUG ((DEBUG_INFO, "HPET_TIMER%d_CONFIGURATION = 0x%016lx\n", mTimerIndex, HpetRead (HPET_TIMER_CONFIGURATION_OFFSET + mTimerIndex * HPET_TIMER_STRIDE))); 968 DEBUG ((DEBUG_INFO, "HPET_TIMER%d_COMPARATOR = 0x%016lx\n", mTimerIndex, HpetRead (HPET_TIMER_COMPARATOR_OFFSET + mTimerIndex * HPET_TIMER_STRIDE))); 969 DEBUG ((DEBUG_INFO, "HPET_TIMER%d_MSI_ROUTE = 0x%016lx\n", mTimerIndex, HpetRead (HPET_TIMER_MSI_ROUTE_OFFSET + mTimerIndex * HPET_TIMER_STRIDE))); 970 971 // 972 // Wait for a few timer interrupts to fire before continuing 973 // 974 while (mNumTicks < 10); 975 ); 976 977 // 978 // Install the Timer Architectural Protocol onto a new handle 979 // 980 Status = gBS->InstallMultipleProtocolInterfaces ( 981 &mTimerHandle, 982 &gEfiTimerArchProtocolGuid, &mTimer, 983 NULL 984 ); 985 ASSERT_EFI_ERROR (Status); 986 987 return Status; 988 } 989