Home | History | Annotate | Download | only in HpetTimerDxe
      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