]> git.proxmox.com Git - mirror_edk2.git/blob - PcAtChipsetPkg/HpetTimerDxe/HpetTimer.c
UefiCpuPkg: Move AsmRelocateApLoopStart from Mpfuncs.nasm to AmdSev.nasm
[mirror_edk2.git] / PcAtChipsetPkg / HpetTimerDxe / HpetTimer.c
1 /** @file
2 Timer Architectural Protocol module using High Precision 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 offset 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 (
304 mNumTicks++;
305 );
306
307 //
308 // Clear HPET timer interrupt status
309 //
310 HpetWrite (HPET_GENERAL_INTERRUPT_STATUS_OFFSET, LShiftU64 (1, mTimerIndex));
311
312 //
313 // Local APIC EOI
314 //
315 SendApicEoi ();
316
317 //
318 // Disable HPET timer when adjusting the COMPARATOR value to prevent a missed interrupt
319 //
320 HpetEnable (FALSE);
321
322 //
323 // Capture main counter value
324 //
325 MainCounter = HpetRead (HPET_MAIN_COUNTER_OFFSET);
326
327 //
328 // Get the previous comparator counter
329 //
330 mPreviousComparator = HpetRead (HPET_TIMER_COMPARATOR_OFFSET + mTimerIndex * HPET_TIMER_STRIDE);
331
332 //
333 // Set HPET COMPARATOR to the value required for the next timer tick
334 //
335 Comparator = (mPreviousComparator + mTimerCount) & mCounterMask;
336
337 if ((mPreviousMainCounter < MainCounter) && (mPreviousComparator > Comparator)) {
338 //
339 // When comparator overflows
340 //
341 HpetWrite (HPET_TIMER_COMPARATOR_OFFSET + mTimerIndex * HPET_TIMER_STRIDE, Comparator);
342 } else if ((mPreviousMainCounter > MainCounter) && (mPreviousComparator < Comparator)) {
343 //
344 // When main counter overflows
345 //
346 HpetWrite (HPET_TIMER_COMPARATOR_OFFSET + mTimerIndex * HPET_TIMER_STRIDE, (MainCounter + mTimerCount) & mCounterMask);
347 } else {
348 //
349 // When both main counter and comparator do not overflow or both do overflow
350 //
351 if (Comparator > MainCounter) {
352 HpetWrite (HPET_TIMER_COMPARATOR_OFFSET + mTimerIndex * HPET_TIMER_STRIDE, Comparator);
353 } else {
354 HpetWrite (HPET_TIMER_COMPARATOR_OFFSET + mTimerIndex * HPET_TIMER_STRIDE, (MainCounter + mTimerCount) & mCounterMask);
355 }
356 }
357
358 //
359 // Enable the HPET counter once the new COMPARATOR value has been set.
360 //
361 HpetEnable (TRUE);
362
363 //
364 // Check to see if there is a registered notification function
365 //
366 if (mTimerNotifyFunction != NULL) {
367 //
368 // Compute time since last notification in 100 ns units (10 ^ -7)
369 //
370 if (MainCounter > mPreviousMainCounter) {
371 //
372 // Main counter does not overflow
373 //
374 Delta = MainCounter - mPreviousMainCounter;
375 } else {
376 //
377 // Main counter overflows, first usb, then add
378 //
379 Delta = (mCounterMask - mPreviousMainCounter) + MainCounter;
380 }
381
382 TimerPeriod = DivU64x32 (
383 MultU64x32 (
384 Delta & mCounterMask,
385 mHpetGeneralCapabilities.Bits.CounterClockPeriod
386 ),
387 100000000
388 );
389
390 //
391 // Call registered notification function passing in the time since the last
392 // interrupt in 100 ns units.
393 //
394 mTimerNotifyFunction (TimerPeriod);
395 }
396
397 //
398 // Save main counter value
399 //
400 mPreviousMainCounter = MainCounter;
401 }
402
403 /**
404 This function registers the handler NotifyFunction so it is called every time
405 the timer interrupt fires. It also passes the amount of time since the last
406 handler call to the NotifyFunction. If NotifyFunction is NULL, then the
407 handler is unregistered. If the handler is registered, then EFI_SUCCESS is
408 returned. If the CPU does not support registering a timer interrupt handler,
409 then EFI_UNSUPPORTED is returned. If an attempt is made to register a handler
410 when a handler is already registered, then EFI_ALREADY_STARTED is returned.
411 If an attempt is made to unregister a handler when a handler is not registered,
412 then EFI_INVALID_PARAMETER is returned. If an error occurs attempting to
413 register the NotifyFunction with the timer interrupt, then EFI_DEVICE_ERROR
414 is returned.
415
416 @param This The EFI_TIMER_ARCH_PROTOCOL instance.
417 @param NotifyFunction The function to call when a timer interrupt fires.
418 This function executes at TPL_HIGH_LEVEL. The DXE
419 Core will register a handler for the timer interrupt,
420 so it can know how much time has passed. This
421 information is used to signal timer based events.
422 NULL will unregister the handler.
423
424 @retval EFI_SUCCESS The timer handler was registered.
425 @retval EFI_UNSUPPORTED The platform does not support timer interrupts.
426 @retval EFI_ALREADY_STARTED NotifyFunction is not NULL, and a handler is already
427 registered.
428 @retval EFI_INVALID_PARAMETER NotifyFunction is NULL, and a handler was not
429 previously registered.
430 @retval EFI_DEVICE_ERROR The timer handler could not be registered.
431
432 **/
433 EFI_STATUS
434 EFIAPI
435 TimerDriverRegisterHandler (
436 IN EFI_TIMER_ARCH_PROTOCOL *This,
437 IN EFI_TIMER_NOTIFY NotifyFunction
438 )
439 {
440 //
441 // Check for invalid parameters
442 //
443 if ((NotifyFunction == NULL) && (mTimerNotifyFunction == NULL)) {
444 return EFI_INVALID_PARAMETER;
445 }
446
447 if ((NotifyFunction != NULL) && (mTimerNotifyFunction != NULL)) {
448 return EFI_ALREADY_STARTED;
449 }
450
451 //
452 // Cache the registered notification function
453 //
454 mTimerNotifyFunction = NotifyFunction;
455
456 return EFI_SUCCESS;
457 }
458
459 /**
460 This function adjusts the period of timer interrupts to the value specified
461 by TimerPeriod. If the timer period is updated, then the selected timer
462 period is stored in EFI_TIMER.TimerPeriod, and EFI_SUCCESS is returned. If
463 the timer hardware is not programmable, then EFI_UNSUPPORTED is returned.
464 If an error occurs while attempting to update the timer period, then the
465 timer hardware will be put back in its state prior to this call, and
466 EFI_DEVICE_ERROR is returned. If TimerPeriod is 0, then the timer interrupt
467 is disabled. This is not the same as disabling the CPU's interrupts.
468 Instead, it must either turn off the timer hardware, or it must adjust the
469 interrupt controller so that a CPU interrupt is not generated when the timer
470 interrupt fires.
471
472 @param This The EFI_TIMER_ARCH_PROTOCOL instance.
473 @param TimerPeriod The rate to program the timer interrupt in 100 nS units.
474 If the timer hardware is not programmable, then
475 EFI_UNSUPPORTED is returned. If the timer is programmable,
476 then the timer period will be rounded up to the nearest
477 timer period that is supported by the timer hardware.
478 If TimerPeriod is set to 0, then the timer interrupts
479 will be disabled.
480
481 @retval EFI_SUCCESS The timer period was changed.
482 @retval EFI_UNSUPPORTED The platform cannot change the period of the timer interrupt.
483 @retval EFI_DEVICE_ERROR The timer period could not be changed due to a device error.
484
485 **/
486 EFI_STATUS
487 EFIAPI
488 TimerDriverSetTimerPeriod (
489 IN EFI_TIMER_ARCH_PROTOCOL *This,
490 IN UINT64 TimerPeriod
491 )
492 {
493 EFI_TPL Tpl;
494 UINT64 MainCounter;
495 UINT64 Delta;
496 UINT64 CurrentComparator;
497 HPET_TIMER_MSI_ROUTE_REGISTER HpetTimerMsiRoute;
498
499 //
500 // Disable interrupts
501 //
502 Tpl = gBS->RaiseTPL (TPL_HIGH_LEVEL);
503
504 //
505 // Disable HPET timer when adjusting the timer period
506 //
507 HpetEnable (FALSE);
508
509 if (TimerPeriod == 0) {
510 if (mTimerPeriod != 0) {
511 //
512 // Check if there is possibly a pending interrupt
513 //
514 MainCounter = HpetRead (HPET_MAIN_COUNTER_OFFSET);
515 if (MainCounter < mPreviousMainCounter) {
516 Delta = (mCounterMask - mPreviousMainCounter) + MainCounter;
517 } else {
518 Delta = MainCounter - mPreviousMainCounter;
519 }
520
521 if ((Delta & mCounterMask) >= mTimerCount) {
522 //
523 // Interrupt still happens after disable HPET, wait to be processed
524 // Wait until interrupt is processed and comparator is increased
525 //
526 CurrentComparator = HpetRead (HPET_TIMER_COMPARATOR_OFFSET + mTimerIndex * HPET_TIMER_STRIDE);
527 while (CurrentComparator == mPreviousComparator) {
528 CurrentComparator = HpetRead (HPET_TIMER_COMPARATOR_OFFSET + mTimerIndex * HPET_TIMER_STRIDE);
529 CpuPause ();
530 }
531 }
532 }
533
534 //
535 // If TimerPeriod is 0, then mask HPET Timer interrupts
536 //
537
538 if ((mTimerConfiguration.Bits.MsiInterruptCapability != 0) && FeaturePcdGet (PcdHpetMsiEnable)) {
539 //
540 // Disable HPET MSI interrupt generation
541 //
542 mTimerConfiguration.Bits.MsiInterruptEnable = 0;
543 } else {
544 //
545 // Disable I/O APIC Interrupt
546 //
547 IoApicEnableInterrupt (mTimerIrq, FALSE);
548 }
549
550 //
551 // Disable HPET timer interrupt
552 //
553 mTimerConfiguration.Bits.InterruptEnable = 0;
554 HpetWrite (HPET_TIMER_CONFIGURATION_OFFSET + mTimerIndex * HPET_TIMER_STRIDE, mTimerConfiguration.Uint64);
555 } else {
556 //
557 // Convert TimerPeriod to femtoseconds and divide by the number if femtoseconds
558 // per tick of the HPET counter to determine the number of HPET counter ticks
559 // in TimerPeriod 100 ns units.
560 //
561 mTimerCount = DivU64x32 (
562 MultU64x32 (TimerPeriod, 100000000),
563 mHpetGeneralCapabilities.Bits.CounterClockPeriod
564 );
565
566 //
567 // Program the HPET Comparator with the number of ticks till the next interrupt
568 //
569 MainCounter = HpetRead (HPET_MAIN_COUNTER_OFFSET);
570 if (MainCounter > mPreviousMainCounter) {
571 Delta = MainCounter - mPreviousMainCounter;
572 } else {
573 Delta = (mCounterMask - mPreviousMainCounter) + MainCounter;
574 }
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.MsiInterruptCapability != 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
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
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 occurred attempting to initialize the driver.
757
758 **/
759 EFI_STATUS
760 EFIAPI
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_BEGIN ();
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 DEBUG_CODE_END ();
823
824 //
825 // Capture the current HPET main counter value.
826 //
827 mPreviousMainCounter = HpetRead (HPET_MAIN_COUNTER_OFFSET);
828
829 //
830 // Determine the interrupt mode to use for the HPET Timer.
831 // Look for MSI first, then unused PIC mode interrupt, then I/O APIC mode interrupt
832 //
833 MsiTimerIndex = HPET_INVALID_TIMER_INDEX;
834 mTimerIndex = HPET_INVALID_TIMER_INDEX;
835 for (TimerIndex = 0; TimerIndex <= mHpetGeneralCapabilities.Bits.NumberOfTimers; TimerIndex++) {
836 //
837 // Read the HPET Timer Capabilities and Configuration register
838 //
839 mTimerConfiguration.Uint64 = HpetRead (HPET_TIMER_CONFIGURATION_OFFSET + TimerIndex * HPET_TIMER_STRIDE);
840
841 //
842 // Check to see if this HPET Timer supports MSI
843 //
844 if (mTimerConfiguration.Bits.MsiInterruptCapability != 0) {
845 //
846 // Save the index of the first HPET Timer that supports MSI interrupts
847 //
848 if (MsiTimerIndex == HPET_INVALID_TIMER_INDEX) {
849 MsiTimerIndex = TimerIndex;
850 }
851 }
852
853 //
854 // Check to see if this HPET Timer supports I/O APIC interrupts
855 //
856 if (mTimerConfiguration.Bits.InterruptRouteCapability != 0) {
857 //
858 // Save the index of the first HPET Timer that supports I/O APIC interrupts
859 //
860 if (mTimerIndex == HPET_INVALID_TIMER_INDEX) {
861 mTimerIndex = TimerIndex;
862 mTimerIrq = (UINT32)LowBitSet32 (mTimerConfiguration.Bits.InterruptRouteCapability);
863 }
864 }
865 }
866
867 if (FeaturePcdGet (PcdHpetMsiEnable) && (MsiTimerIndex != HPET_INVALID_TIMER_INDEX)) {
868 //
869 // Use MSI interrupt if supported
870 //
871 mTimerIndex = MsiTimerIndex;
872
873 //
874 // Program MSI Address and MSI Data values in the selected HPET Timer
875 //
876 HpetTimerMsiRoute.Bits.Address = GetApicMsiAddress ();
877 HpetTimerMsiRoute.Bits.Value = (UINT32)GetApicMsiValue (PcdGet8 (PcdHpetLocalApicVector), LOCAL_APIC_DELIVERY_MODE_LOWEST_PRIORITY, FALSE, FALSE);
878 HpetWrite (HPET_TIMER_MSI_ROUTE_OFFSET + mTimerIndex * HPET_TIMER_STRIDE, HpetTimerMsiRoute.Uint64);
879
880 //
881 // Read the HPET Timer Capabilities and Configuration register and initialize for MSI mode
882 // Clear LevelTriggeredInterrupt to use edge triggered interrupts when in MSI mode
883 //
884 mTimerConfiguration.Uint64 = HpetRead (HPET_TIMER_CONFIGURATION_OFFSET + mTimerIndex * HPET_TIMER_STRIDE);
885 mTimerConfiguration.Bits.LevelTriggeredInterrupt = 0;
886 } else {
887 //
888 // If no HPET timers support MSI or I/O APIC modes, then ASSERT() and unload the driver.
889 //
890 ASSERT (mTimerIndex != HPET_INVALID_TIMER_INDEX);
891 if (mTimerIndex == HPET_INVALID_TIMER_INDEX) {
892 DEBUG ((DEBUG_ERROR, "No HPET timers support MSI or I/O APIC mode. Unload HPET driver.\n"));
893 return EFI_DEVICE_ERROR;
894 }
895
896 //
897 // Initialize I/O APIC entry for HPET Timer Interrupt
898 // Fixed Delivery Mode, Level Triggered, Asserted Low
899 //
900 IoApicConfigureInterrupt (mTimerIrq, PcdGet8 (PcdHpetLocalApicVector), IO_APIC_DELIVERY_MODE_LOWEST_PRIORITY, TRUE, FALSE);
901
902 //
903 // Read the HPET Timer Capabilities and Configuration register and initialize for I/O APIC mode
904 // Clear MsiInterruptCapability to force rest of driver to use I/O APIC mode
905 // Set LevelTriggeredInterrupt to use level triggered interrupts when in I/O APIC mode
906 // Set InterruptRoute field based in mTimerIrq
907 //
908 mTimerConfiguration.Uint64 = HpetRead (HPET_TIMER_CONFIGURATION_OFFSET + mTimerIndex * HPET_TIMER_STRIDE);
909 mTimerConfiguration.Bits.LevelTriggeredInterrupt = 1;
910 mTimerConfiguration.Bits.InterruptRoute = mTimerIrq;
911 }
912
913 //
914 // Configure the selected HPET Timer with settings common to both MSI mode and I/O APIC mode
915 // Clear InterruptEnable to keep interrupts disabled until full init is complete
916 // Clear PeriodicInterruptEnable to use one-shot mode
917 // Configure as a 32-bit counter
918 //
919 mTimerConfiguration.Bits.InterruptEnable = 0;
920 mTimerConfiguration.Bits.PeriodicInterruptEnable = 0;
921 mTimerConfiguration.Bits.CounterSizeEnable = 1;
922 HpetWrite (HPET_TIMER_CONFIGURATION_OFFSET + mTimerIndex * HPET_TIMER_STRIDE, mTimerConfiguration.Uint64);
923
924 //
925 // Read the HPET Timer Capabilities and Configuration register back again.
926 // CounterSizeEnable will be read back as a 0 if it is a 32-bit only timer
927 //
928 mTimerConfiguration.Uint64 = HpetRead (HPET_TIMER_CONFIGURATION_OFFSET + mTimerIndex * HPET_TIMER_STRIDE);
929 if ((mTimerConfiguration.Bits.CounterSizeEnable == 1) && (sizeof (UINTN) == sizeof (UINT64))) {
930 DEBUG ((DEBUG_INFO, "Choose 64-bit HPET timer.\n"));
931 //
932 // 64-bit BIOS can use 64-bit HPET timer
933 //
934 mCounterMask = 0xffffffffffffffffULL;
935 //
936 // Set timer back to 64-bit
937 //
938 mTimerConfiguration.Bits.CounterSizeEnable = 0;
939 HpetWrite (HPET_TIMER_CONFIGURATION_OFFSET + mTimerIndex * HPET_TIMER_STRIDE, mTimerConfiguration.Uint64);
940 } else {
941 DEBUG ((DEBUG_INFO, "Choose 32-bit HPET timer.\n"));
942 mCounterMask = 0x00000000ffffffffULL;
943 }
944
945 //
946 // Install interrupt handler for selected HPET Timer
947 //
948 Status = mCpu->RegisterInterruptHandler (mCpu, PcdGet8 (PcdHpetLocalApicVector), TimerInterruptHandler);
949 ASSERT_EFI_ERROR (Status);
950 if (EFI_ERROR (Status)) {
951 DEBUG ((DEBUG_ERROR, "Unable to register HPET interrupt with CPU Arch Protocol. Unload HPET driver.\n"));
952 return EFI_DEVICE_ERROR;
953 }
954
955 //
956 // Force the HPET Timer to be enabled at its default period
957 //
958 Status = TimerDriverSetTimerPeriod (&mTimer, PcdGet64 (PcdHpetDefaultTimerPeriod));
959 ASSERT_EFI_ERROR (Status);
960 if (EFI_ERROR (Status)) {
961 DEBUG ((DEBUG_ERROR, "Unable to set HPET default timer rate. Unload HPET driver.\n"));
962 return EFI_DEVICE_ERROR;
963 }
964
965 //
966 // Show state of enabled HPET timer
967 //
968 DEBUG_CODE_BEGIN ();
969 if ((mTimerConfiguration.Bits.MsiInterruptCapability != 0) && FeaturePcdGet (PcdHpetMsiEnable)) {
970 DEBUG ((DEBUG_INFO, "HPET Interrupt Mode MSI\n"));
971 } else {
972 DEBUG ((DEBUG_INFO, "HPET Interrupt Mode I/O APIC\n"));
973 DEBUG ((DEBUG_INFO, "HPET I/O APIC IRQ = 0x%02x\n", mTimerIrq));
974 }
975
976 DEBUG ((DEBUG_INFO, "HPET Interrupt Vector = 0x%02x\n", PcdGet8 (PcdHpetLocalApicVector)));
977 DEBUG ((DEBUG_INFO, "HPET Counter Mask = 0x%016lx\n", mCounterMask));
978 DEBUG ((DEBUG_INFO, "HPET Timer Period = %d\n", mTimerPeriod));
979 DEBUG ((DEBUG_INFO, "HPET Timer Count = 0x%016lx\n", mTimerCount));
980 DEBUG ((DEBUG_INFO, "HPET_TIMER%d_CONFIGURATION = 0x%016lx\n", mTimerIndex, HpetRead (HPET_TIMER_CONFIGURATION_OFFSET + mTimerIndex * HPET_TIMER_STRIDE)));
981 DEBUG ((DEBUG_INFO, "HPET_TIMER%d_COMPARATOR = 0x%016lx\n", mTimerIndex, HpetRead (HPET_TIMER_COMPARATOR_OFFSET + mTimerIndex * HPET_TIMER_STRIDE)));
982 DEBUG ((DEBUG_INFO, "HPET_TIMER%d_MSI_ROUTE = 0x%016lx\n", mTimerIndex, HpetRead (HPET_TIMER_MSI_ROUTE_OFFSET + mTimerIndex * HPET_TIMER_STRIDE)));
983
984 //
985 // Wait for a few timer interrupts to fire before continuing
986 //
987 while (mNumTicks < 10) {
988 }
989
990 DEBUG_CODE_END ();
991
992 //
993 // Install the Timer Architectural Protocol onto a new handle
994 //
995 Status = gBS->InstallMultipleProtocolInterfaces (
996 &mTimerHandle,
997 &gEfiTimerArchProtocolGuid,
998 &mTimer,
999 NULL
1000 );
1001 ASSERT_EFI_ERROR (Status);
1002
1003 return Status;
1004 }