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