• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /** @file
2   Timer Architectural Protocol module using High Precesion Event Timer (HPET)
3 
4   Copyright (c) 2011 - 2016, 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_UNSUPPORTED   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
HpetRead(IN UINTN Offset)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
HpetWrite(IN UINTN Offset,IN UINT64 Value)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
HpetEnable(IN BOOLEAN Enable)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
TimerInterruptHandler(IN EFI_EXCEPTION_TYPE InterruptType,IN EFI_SYSTEM_CONTEXT SystemContext)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
TimerDriverRegisterHandler(IN EFI_TIMER_ARCH_PROTOCOL * This,IN EFI_TIMER_NOTIFY NotifyFunction)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
TimerDriverSetTimerPeriod(IN EFI_TIMER_ARCH_PROTOCOL * This,IN UINT64 TimerPeriod)490 TimerDriverSetTimerPeriod (
491   IN EFI_TIMER_ARCH_PROTOCOL  *This,
492   IN UINT64                   TimerPeriod
493   )
494 {
495   EFI_TPL                        Tpl;
496   UINT64                         MainCounter;
497   UINT64                         Delta;
498   UINT64                         CurrentComparator;
499   HPET_TIMER_MSI_ROUTE_REGISTER  HpetTimerMsiRoute;
500 
501   //
502   // Disable interrupts
503   //
504   Tpl = gBS->RaiseTPL (TPL_HIGH_LEVEL);
505 
506   //
507   // Disable HPET timer when adjusting the timer period
508   //
509   HpetEnable (FALSE);
510 
511   if (TimerPeriod == 0) {
512     if (mTimerPeriod != 0) {
513       //
514       // Check if there is possibly a pending interrupt
515       //
516       MainCounter = HpetRead (HPET_MAIN_COUNTER_OFFSET);
517       if (MainCounter < mPreviousMainCounter) {
518         Delta = (mCounterMask - mPreviousMainCounter) + MainCounter;
519       } else {
520         Delta = MainCounter - mPreviousMainCounter;
521       }
522       if ((Delta & mCounterMask) >= mTimerCount) {
523         //
524         // Interrupt still happens after disable HPET, wait to be processed
525         // Wait until interrupt is processed and comparator is increased
526         //
527         CurrentComparator = HpetRead (HPET_TIMER_COMPARATOR_OFFSET + mTimerIndex * HPET_TIMER_STRIDE);
528         while (CurrentComparator == mPreviousComparator) {
529           CurrentComparator = HpetRead (HPET_TIMER_COMPARATOR_OFFSET + mTimerIndex * HPET_TIMER_STRIDE);
530           CpuPause();
531         }
532       }
533     }
534 
535     //
536     // If TimerPeriod is 0, then mask HPET Timer interrupts
537     //
538 
539     if (mTimerConfiguration.Bits.MsiInterruptCapablity != 0 && FeaturePcdGet (PcdHpetMsiEnable)) {
540       //
541       // Disable HPET MSI interrupt generation
542       //
543       mTimerConfiguration.Bits.MsiInterruptEnable = 0;
544     } else {
545       //
546       // Disable I/O APIC Interrupt
547       //
548       IoApicEnableInterrupt (mTimerIrq, FALSE);
549     }
550 
551     //
552     // Disable HPET timer interrupt
553     //
554     mTimerConfiguration.Bits.InterruptEnable = 0;
555     HpetWrite (HPET_TIMER_CONFIGURATION_OFFSET + mTimerIndex * HPET_TIMER_STRIDE, mTimerConfiguration.Uint64);
556   } else {
557     //
558     // Convert TimerPeriod to femtoseconds and divide by the number if femtoseconds
559     // per tick of the HPET counter to determine the number of HPET counter ticks
560     // in TimerPeriod 100 ns units.
561     //
562     mTimerCount = DivU64x32 (
563                     MultU64x32 (TimerPeriod, 100000000),
564                     mHpetGeneralCapabilities.Bits.CounterClockPeriod
565                     );
566 
567     //
568     // Program the HPET Comparator with the number of ticks till the next interrupt
569     //
570     MainCounter = HpetRead (HPET_MAIN_COUNTER_OFFSET);
571     if (MainCounter > mPreviousMainCounter) {
572       Delta = MainCounter - mPreviousMainCounter;
573     } else {
574       Delta = (mCounterMask - mPreviousMainCounter) + MainCounter;
575     }
576     if ((Delta & mCounterMask) >= mTimerCount) {
577       HpetWrite (HPET_TIMER_COMPARATOR_OFFSET + mTimerIndex * HPET_TIMER_STRIDE, (MainCounter + 1) & mCounterMask);
578     } else {
579       HpetWrite (HPET_TIMER_COMPARATOR_OFFSET + mTimerIndex * HPET_TIMER_STRIDE, (mPreviousMainCounter + mTimerCount) & mCounterMask);
580     }
581 
582     //
583     // Enable HPET Timer interrupt generation
584     //
585     if (mTimerConfiguration.Bits.MsiInterruptCapablity != 0 && FeaturePcdGet (PcdHpetMsiEnable)) {
586       //
587       // Program MSI Address and MSI Data values in the selected HPET Timer
588       // Program HPET register with APIC ID of current BSP in case BSP has been switched
589       //
590       HpetTimerMsiRoute.Bits.Address = GetApicMsiAddress ();
591       HpetTimerMsiRoute.Bits.Value   = (UINT32)GetApicMsiValue (PcdGet8 (PcdHpetLocalApicVector), LOCAL_APIC_DELIVERY_MODE_LOWEST_PRIORITY, FALSE, FALSE);
592       HpetWrite (HPET_TIMER_MSI_ROUTE_OFFSET + mTimerIndex * HPET_TIMER_STRIDE, HpetTimerMsiRoute.Uint64);
593       //
594       // Enable HPET MSI Interrupt
595       //
596       mTimerConfiguration.Bits.MsiInterruptEnable = 1;
597     } else {
598       //
599       // Enable timer interrupt through I/O APIC
600       // Program IOAPIC register with APIC ID of current BSP in case BSP has been switched
601       //
602       IoApicConfigureInterrupt (mTimerIrq, PcdGet8 (PcdHpetLocalApicVector), IO_APIC_DELIVERY_MODE_LOWEST_PRIORITY, TRUE, FALSE);
603       IoApicEnableInterrupt (mTimerIrq, TRUE);
604     }
605 
606     //
607     // Enable HPET Interrupt Generation
608     //
609     mTimerConfiguration.Bits.InterruptEnable = 1;
610     HpetWrite (HPET_TIMER_CONFIGURATION_OFFSET + mTimerIndex * HPET_TIMER_STRIDE, mTimerConfiguration.Uint64);
611   }
612 
613   //
614   // Save the new timer period
615   //
616   mTimerPeriod = TimerPeriod;
617 
618   //
619   // Enable the HPET counter once new timer period has been established
620   // The HPET counter should run even if the HPET Timer interrupts are
621   // disabled.  This is used to account for time passed while the interrupt
622   // is disabled.
623   //
624   HpetEnable (TRUE);
625 
626   //
627   // Restore interrupts
628   //
629   gBS->RestoreTPL (Tpl);
630 
631   return EFI_SUCCESS;
632 }
633 
634 /**
635   This function retrieves the period of timer interrupts in 100 ns units,
636   returns that value in TimerPeriod, and returns EFI_SUCCESS.  If TimerPeriod
637   is NULL, then EFI_INVALID_PARAMETER is returned.  If a TimerPeriod of 0 is
638   returned, then the timer is currently disabled.
639 
640   @param  This         The EFI_TIMER_ARCH_PROTOCOL instance.
641   @param  TimerPeriod  A pointer to the timer period to retrieve in 100 ns units.
642                        If 0 is returned, then the timer is currently disabled.
643 
644   @retval  EFI_SUCCESS            The timer period was returned in TimerPeriod.
645   @retval  EFI_INVALID_PARAMETER  TimerPeriod is NULL.
646 
647 **/
648 EFI_STATUS
649 EFIAPI
TimerDriverGetTimerPeriod(IN EFI_TIMER_ARCH_PROTOCOL * This,OUT UINT64 * TimerPeriod)650 TimerDriverGetTimerPeriod (
651   IN EFI_TIMER_ARCH_PROTOCOL   *This,
652   OUT UINT64                   *TimerPeriod
653   )
654 {
655   if (TimerPeriod == NULL) {
656     return EFI_INVALID_PARAMETER;
657   }
658 
659   *TimerPeriod = mTimerPeriod;
660 
661   return EFI_SUCCESS;
662 }
663 
664 /**
665   This function generates a soft timer interrupt. If the platform does not support soft
666   timer interrupts, then EFI_UNSUPPORTED is returned. Otherwise, EFI_SUCCESS is returned.
667   If a handler has been registered through the EFI_TIMER_ARCH_PROTOCOL.RegisterHandler()
668   service, then a soft timer interrupt will be generated. If the timer interrupt is
669   enabled when this service is called, then the registered handler will be invoked. The
670   registered handler should not be able to distinguish a hardware-generated timer
671   interrupt from a software-generated timer interrupt.
672 
673   @param  This  The EFI_TIMER_ARCH_PROTOCOL instance.
674 
675   @retval  EFI_SUCCESS       The soft timer interrupt was generated.
676   @retval  EFI_UNSUPPORTED   The platform does not support the generation of soft
677                              timer interrupts.
678 
679 **/
680 EFI_STATUS
681 EFIAPI
TimerDriverGenerateSoftInterrupt(IN EFI_TIMER_ARCH_PROTOCOL * This)682 TimerDriverGenerateSoftInterrupt (
683   IN EFI_TIMER_ARCH_PROTOCOL  *This
684   )
685 {
686   UINT64   MainCounter;
687   EFI_TPL  Tpl;
688   UINT64   TimerPeriod;
689   UINT64   Delta;
690 
691   //
692   // Disable interrupts
693   //
694   Tpl = gBS->RaiseTPL (TPL_HIGH_LEVEL);
695 
696   //
697   // Capture main counter value
698   //
699   MainCounter = HpetRead (HPET_MAIN_COUNTER_OFFSET);
700 
701   //
702   // Check to see if there is a registered notification function
703   //
704   if (mTimerNotifyFunction != NULL) {
705     //
706     // Compute time since last interrupt in 100 ns units (10 ^ -7)
707     //
708     if (MainCounter > mPreviousMainCounter) {
709       //
710       // Main counter does not overflow
711       //
712       Delta = MainCounter - mPreviousMainCounter;
713     } else {
714       //
715       // Main counter overflows, first usb, then add
716       //
717       Delta = (mCounterMask - mPreviousMainCounter) + MainCounter;
718     }
719 
720     TimerPeriod = DivU64x32 (
721                     MultU64x32 (
722                       Delta & mCounterMask,
723                       mHpetGeneralCapabilities.Bits.CounterClockPeriod
724                       ),
725                     100000000
726                     );
727 
728     //
729     // Call registered notification function passing in the time since the last
730     // interrupt in 100 ns units.
731     //
732     mTimerNotifyFunction (TimerPeriod);
733   }
734 
735   //
736   // Save main counter value
737   //
738   mPreviousMainCounter = MainCounter;
739 
740   //
741   // Restore interrupts
742   //
743   gBS->RestoreTPL (Tpl);
744 
745   return EFI_SUCCESS;
746 }
747 
748 /**
749   Initialize the Timer Architectural Protocol driver
750 
751   @param  ImageHandle  ImageHandle of the loaded driver
752   @param  SystemTable  Pointer to the System Table
753 
754   @retval  EFI_SUCCESS           Timer Architectural Protocol created
755   @retval  EFI_OUT_OF_RESOURCES  Not enough resources available to initialize driver.
756   @retval  EFI_DEVICE_ERROR      A device error occured attempting to initialize the driver.
757 
758 **/
759 EFI_STATUS
760 EFIAPI
TimerDriverInitialize(IN EFI_HANDLE ImageHandle,IN EFI_SYSTEM_TABLE * SystemTable)761 TimerDriverInitialize (
762   IN EFI_HANDLE        ImageHandle,
763   IN EFI_SYSTEM_TABLE  *SystemTable
764   )
765 {
766   EFI_STATUS                             Status;
767   UINTN                                  TimerIndex;
768   UINTN                                  MsiTimerIndex;
769   HPET_TIMER_MSI_ROUTE_REGISTER          HpetTimerMsiRoute;
770 
771   DEBUG ((DEBUG_INFO, "Init HPET Timer Driver\n"));
772 
773   //
774   // Make sure the Timer Architectural Protocol is not already installed in the system
775   //
776   ASSERT_PROTOCOL_ALREADY_INSTALLED (NULL, &gEfiTimerArchProtocolGuid);
777 
778   //
779   // Find the CPU architectural protocol.
780   //
781   Status = gBS->LocateProtocol (&gEfiCpuArchProtocolGuid, NULL, (VOID **) &mCpu);
782   ASSERT_EFI_ERROR (Status);
783 
784   //
785   // Retrieve HPET Capabilities and Configuration Information
786   //
787   mHpetGeneralCapabilities.Uint64  = HpetRead (HPET_GENERAL_CAPABILITIES_ID_OFFSET);
788   mHpetGeneralConfiguration.Uint64 = HpetRead (HPET_GENERAL_CONFIGURATION_OFFSET);
789 
790   //
791   // If Revision is not valid, then ASSERT() and unload the driver because the HPET
792   // device is not present.
793   //
794   ASSERT (mHpetGeneralCapabilities.Uint64 != 0);
795   ASSERT (mHpetGeneralCapabilities.Uint64 != 0xFFFFFFFFFFFFFFFFULL);
796   if (mHpetGeneralCapabilities.Uint64 == 0 || mHpetGeneralCapabilities.Uint64 == 0xFFFFFFFFFFFFFFFFULL) {
797     DEBUG ((DEBUG_ERROR, "HPET device is not present.  Unload HPET driver.\n"));
798     return EFI_DEVICE_ERROR;
799   }
800 
801   //
802   // Force the HPET timer to be disabled while setting everything up
803   //
804   HpetEnable (FALSE);
805 
806   //
807   // Dump HPET Configuration Information
808   //
809   DEBUG_CODE (
810     DEBUG ((DEBUG_INFO, "HPET Base Address = 0x%08x\n", PcdGet32 (PcdHpetBaseAddress)));
811     DEBUG ((DEBUG_INFO, "  HPET_GENERAL_CAPABILITIES_ID  = 0x%016lx\n", mHpetGeneralCapabilities));
812     DEBUG ((DEBUG_INFO, "  HPET_GENERAL_CONFIGURATION    = 0x%016lx\n", mHpetGeneralConfiguration.Uint64));
813     DEBUG ((DEBUG_INFO, "  HPET_GENERAL_INTERRUPT_STATUS = 0x%016lx\n", HpetRead (HPET_GENERAL_INTERRUPT_STATUS_OFFSET)));
814     DEBUG ((DEBUG_INFO, "  HPET_MAIN_COUNTER             = 0x%016lx\n", HpetRead (HPET_MAIN_COUNTER_OFFSET)));
815     DEBUG ((DEBUG_INFO, "  HPET Main Counter Period      = %d (fs)\n", mHpetGeneralCapabilities.Bits.CounterClockPeriod));
816     for (TimerIndex = 0; TimerIndex <= mHpetGeneralCapabilities.Bits.NumberOfTimers; TimerIndex++) {
817       DEBUG ((DEBUG_INFO, "  HPET_TIMER%d_CONFIGURATION     = 0x%016lx\n", TimerIndex, HpetRead (HPET_TIMER_CONFIGURATION_OFFSET + TimerIndex * HPET_TIMER_STRIDE)));
818       DEBUG ((DEBUG_INFO, "  HPET_TIMER%d_COMPARATOR        = 0x%016lx\n", TimerIndex, HpetRead (HPET_TIMER_COMPARATOR_OFFSET    + TimerIndex * HPET_TIMER_STRIDE)));
819       DEBUG ((DEBUG_INFO, "  HPET_TIMER%d_MSI_ROUTE         = 0x%016lx\n", TimerIndex, HpetRead (HPET_TIMER_MSI_ROUTE_OFFSET     + TimerIndex * HPET_TIMER_STRIDE)));
820     }
821   );
822 
823   //
824   // Capture the current HPET main counter value.
825   //
826   mPreviousMainCounter = HpetRead (HPET_MAIN_COUNTER_OFFSET);
827 
828   //
829   // Determine the interrupt mode to use for the HPET Timer.
830   // Look for MSI first, then unused PIC mode interrupt, then I/O APIC mode interrupt
831   //
832   MsiTimerIndex = HPET_INVALID_TIMER_INDEX;
833   mTimerIndex   = HPET_INVALID_TIMER_INDEX;
834   for (TimerIndex = 0; TimerIndex <= mHpetGeneralCapabilities.Bits.NumberOfTimers; TimerIndex++) {
835     //
836     // Read the HPET Timer Capabilities and Configuration register
837     //
838     mTimerConfiguration.Uint64 = HpetRead (HPET_TIMER_CONFIGURATION_OFFSET + TimerIndex * HPET_TIMER_STRIDE);
839 
840     //
841     // Check to see if this HPET Timer supports MSI
842     //
843     if (mTimerConfiguration.Bits.MsiInterruptCapablity != 0) {
844       //
845       // Save the index of the first HPET Timer that supports MSI interrupts
846       //
847       if (MsiTimerIndex == HPET_INVALID_TIMER_INDEX) {
848         MsiTimerIndex = TimerIndex;
849       }
850     }
851 
852     //
853     // Check to see if this HPET Timer supports I/O APIC interrupts
854     //
855     if (mTimerConfiguration.Bits.InterruptRouteCapability != 0) {
856       //
857       // Save the index of the first HPET Timer that supports I/O APIC interrupts
858       //
859       if (mTimerIndex == HPET_INVALID_TIMER_INDEX) {
860         mTimerIndex = TimerIndex;
861         mTimerIrq   = (UINT32)LowBitSet32 (mTimerConfiguration.Bits.InterruptRouteCapability);
862       }
863     }
864   }
865 
866   if (FeaturePcdGet (PcdHpetMsiEnable) && MsiTimerIndex != HPET_INVALID_TIMER_INDEX) {
867     //
868     // Use MSI interrupt if supported
869     //
870     mTimerIndex  = MsiTimerIndex;
871 
872     //
873     // Program MSI Address and MSI Data values in the selected HPET Timer
874     //
875     HpetTimerMsiRoute.Bits.Address = GetApicMsiAddress ();
876     HpetTimerMsiRoute.Bits.Value   = (UINT32)GetApicMsiValue (PcdGet8 (PcdHpetLocalApicVector), LOCAL_APIC_DELIVERY_MODE_LOWEST_PRIORITY, FALSE, FALSE);
877     HpetWrite (HPET_TIMER_MSI_ROUTE_OFFSET + mTimerIndex * HPET_TIMER_STRIDE, HpetTimerMsiRoute.Uint64);
878 
879     //
880     // Read the HPET Timer Capabilities and Configuration register and initialize for MSI mode
881     //   Clear LevelTriggeredInterrupt to use edge triggered interrupts when in MSI mode
882     //
883     mTimerConfiguration.Uint64 = HpetRead (HPET_TIMER_CONFIGURATION_OFFSET + mTimerIndex * HPET_TIMER_STRIDE);
884     mTimerConfiguration.Bits.LevelTriggeredInterrupt = 0;
885   } else {
886     //
887     // If no HPET timers support MSI or I/O APIC modes, then ASSERT() and unload the driver.
888     //
889     ASSERT (mTimerIndex != HPET_INVALID_TIMER_INDEX);
890     if (mTimerIndex == HPET_INVALID_TIMER_INDEX) {
891       DEBUG ((DEBUG_ERROR, "No HPET timers support MSI or I/O APIC mode.  Unload HPET driver.\n"));
892       return EFI_DEVICE_ERROR;
893     }
894 
895     //
896     // Initialize I/O APIC entry for HPET Timer Interrupt
897     //   Fixed Delivery Mode, Level Triggered, Asserted Low
898     //
899     IoApicConfigureInterrupt (mTimerIrq, PcdGet8 (PcdHpetLocalApicVector), IO_APIC_DELIVERY_MODE_LOWEST_PRIORITY, TRUE, FALSE);
900 
901     //
902     // Read the HPET Timer Capabilities and Configuration register and initialize for I/O APIC mode
903     //   Clear MsiInterruptCapability to force rest of driver to use I/O APIC mode
904     //   Set LevelTriggeredInterrupt to use level triggered interrupts when in I/O APIC mode
905     //   Set InterruptRoute field based in mTimerIrq
906     //
907     mTimerConfiguration.Uint64 = HpetRead (HPET_TIMER_CONFIGURATION_OFFSET + mTimerIndex * HPET_TIMER_STRIDE);
908     mTimerConfiguration.Bits.LevelTriggeredInterrupt = 1;
909     mTimerConfiguration.Bits.InterruptRoute          = mTimerIrq;
910   }
911 
912   //
913   // Configure the selected HPET Timer with settings common to both MSI mode and I/O APIC mode
914   //   Clear InterruptEnable to keep interrupts disabled until full init is complete
915   //   Clear PeriodicInterruptEnable to use one-shot mode
916   //   Configure as a 32-bit counter
917   //
918   mTimerConfiguration.Bits.InterruptEnable         = 0;
919   mTimerConfiguration.Bits.PeriodicInterruptEnable = 0;
920   mTimerConfiguration.Bits.CounterSizeEnable       = 1;
921   HpetWrite (HPET_TIMER_CONFIGURATION_OFFSET + mTimerIndex * HPET_TIMER_STRIDE, mTimerConfiguration.Uint64);
922 
923   //
924   // Read the HPET Timer Capabilities and Configuration register back again.
925   // CounterSizeEnable will be read back as a 0 if it is a 32-bit only timer
926   //
927   mTimerConfiguration.Uint64 = HpetRead (HPET_TIMER_CONFIGURATION_OFFSET + mTimerIndex * HPET_TIMER_STRIDE);
928   if ((mTimerConfiguration.Bits.CounterSizeEnable == 1) && (sizeof (UINTN) == sizeof (UINT64))) {
929     DEBUG ((DEBUG_INFO, "Choose 64-bit HPET timer.\n"));
930     //
931     // 64-bit BIOS can use 64-bit HPET timer
932     //
933     mCounterMask = 0xffffffffffffffffULL;
934     //
935     // Set timer back to 64-bit
936     //
937     mTimerConfiguration.Bits.CounterSizeEnable = 0;
938     HpetWrite (HPET_TIMER_CONFIGURATION_OFFSET + mTimerIndex * HPET_TIMER_STRIDE, mTimerConfiguration.Uint64);
939   } else {
940     DEBUG ((DEBUG_INFO, "Choose 32-bit HPET timer.\n"));
941     mCounterMask = 0x00000000ffffffffULL;
942   }
943 
944   //
945   // Install interrupt handler for selected HPET Timer
946   //
947   Status = mCpu->RegisterInterruptHandler (mCpu, PcdGet8 (PcdHpetLocalApicVector), TimerInterruptHandler);
948   ASSERT_EFI_ERROR (Status);
949   if (EFI_ERROR (Status)) {
950     DEBUG ((DEBUG_ERROR, "Unable to register HPET interrupt with CPU Arch Protocol.  Unload HPET driver.\n"));
951     return EFI_DEVICE_ERROR;
952   }
953 
954   //
955   // Force the HPET Timer to be enabled at its default period
956   //
957   Status = TimerDriverSetTimerPeriod (&mTimer, PcdGet64 (PcdHpetDefaultTimerPeriod));
958   ASSERT_EFI_ERROR (Status);
959   if (EFI_ERROR (Status)) {
960     DEBUG ((DEBUG_ERROR, "Unable to set HPET default timer rate.  Unload HPET driver.\n"));
961     return EFI_DEVICE_ERROR;
962   }
963 
964   //
965   // Show state of enabled HPET timer
966   //
967   DEBUG_CODE (
968     if (mTimerConfiguration.Bits.MsiInterruptCapablity != 0 && FeaturePcdGet (PcdHpetMsiEnable)) {
969       DEBUG ((DEBUG_INFO, "HPET Interrupt Mode MSI\n"));
970     } else {
971       DEBUG ((DEBUG_INFO, "HPET Interrupt Mode I/O APIC\n"));
972       DEBUG ((DEBUG_INFO, "HPET I/O APIC IRQ         = 0x%02x\n",  mTimerIrq));
973     }
974     DEBUG ((DEBUG_INFO, "HPET Interrupt Vector     = 0x%02x\n",    PcdGet8 (PcdHpetLocalApicVector)));
975     DEBUG ((DEBUG_INFO, "HPET Counter Mask         = 0x%016lx\n",  mCounterMask));
976     DEBUG ((DEBUG_INFO, "HPET Timer Period         = %d\n",        mTimerPeriod));
977     DEBUG ((DEBUG_INFO, "HPET Timer Count          = 0x%016lx\n",  mTimerCount));
978     DEBUG ((DEBUG_INFO, "HPET_TIMER%d_CONFIGURATION = 0x%016lx\n", mTimerIndex, HpetRead (HPET_TIMER_CONFIGURATION_OFFSET + mTimerIndex * HPET_TIMER_STRIDE)));
979     DEBUG ((DEBUG_INFO, "HPET_TIMER%d_COMPARATOR    = 0x%016lx\n", mTimerIndex, HpetRead (HPET_TIMER_COMPARATOR_OFFSET    + mTimerIndex * HPET_TIMER_STRIDE)));
980     DEBUG ((DEBUG_INFO, "HPET_TIMER%d_MSI_ROUTE     = 0x%016lx\n", mTimerIndex, HpetRead (HPET_TIMER_MSI_ROUTE_OFFSET     + mTimerIndex * HPET_TIMER_STRIDE)));
981 
982     //
983     // Wait for a few timer interrupts to fire before continuing
984     //
985     while (mNumTicks < 10);
986   );
987 
988   //
989   // Install the Timer Architectural Protocol onto a new handle
990   //
991   Status = gBS->InstallMultipleProtocolInterfaces (
992                   &mTimerHandle,
993                   &gEfiTimerArchProtocolGuid, &mTimer,
994                   NULL
995                   );
996   ASSERT_EFI_ERROR (Status);
997 
998   return Status;
999 }
1000