]> git.proxmox.com Git - mirror_edk2.git/blob - PcAtChipsetPkg/HpetTimerDxe/HpetTimer.c
PcAtChipsetPkg: Define FixePCD's for RTC register values
[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 - 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
245 HpetRead (
246 IN UINTN Offset
247 )
248 {
249 return MmioRead64 (PcdGet32 (PcdHpetBaseAddress) + Offset);
250 }
251
252 /**
253 Write a 64-bit HPET register.
254
255 @param Offset Specifies the ofsfert of the HPET register to write.
256 @param Value Specifies the value to write to the HPET register specified by Offset.
257
258 @return The 64-bit value written to HPET register specified by Offset.
259 **/
260 UINT64
261 HpetWrite (
262 IN UINTN Offset,
263 IN UINT64 Value
264 )
265 {
266 return MmioWrite64 (PcdGet32 (PcdHpetBaseAddress) + Offset, Value);
267 }
268
269 /**
270 Enable or disable the main counter in the HPET Timer.
271
272 @param Enable If TRUE, then enable the main counter in the HPET Timer.
273 If FALSE, then disable the main counter in the HPET Timer.
274 **/
275 VOID
276 HpetEnable (
277 IN BOOLEAN Enable
278 )
279 {
280 mHpetGeneralConfiguration.Bits.MainCounterEnable = Enable ? 1 : 0;
281 HpetWrite (HPET_GENERAL_CONFIGURATION_OFFSET, mHpetGeneralConfiguration.Uint64);
282 }
283
284 /**
285 The interrupt handler for the HPET timer. This handler clears the HPET interrupt
286 and computes the amount of time that has passed since the last HPET timer interrupt.
287 If a notification function is registered, then the amount of time since the last
288 HPET interrupt is passed to that notification function in 100 ns units. The HPET
289 time is updated to generate another interrupt in the required time period.
290
291 @param InterruptType The type of interrupt that occurred.
292 @param SystemContext A pointer to the system context when the interrupt occurred.
293 **/
294 VOID
295 EFIAPI
296 TimerInterruptHandler (
297 IN EFI_EXCEPTION_TYPE InterruptType,
298 IN EFI_SYSTEM_CONTEXT SystemContext
299 )
300 {
301 UINT64 MainCounter;
302 UINT64 Comparator;
303 UINT64 TimerPeriod;
304 UINT64 Delta;
305
306 //
307 // Count number of ticks
308 //
309 DEBUG_CODE (mNumTicks++;);
310
311 //
312 // Clear HPET timer interrupt status
313 //
314 HpetWrite (HPET_GENERAL_INTERRUPT_STATUS_OFFSET, LShiftU64 (1, mTimerIndex));
315
316 //
317 // Local APIC EOI
318 //
319 SendApicEoi ();
320
321 //
322 // Disable HPET timer when adjusting the COMPARATOR value to prevent a missed interrupt
323 //
324 HpetEnable (FALSE);
325
326 //
327 // Capture main counter value
328 //
329 MainCounter = HpetRead (HPET_MAIN_COUNTER_OFFSET);
330
331 //
332 // Get the previous comparator counter
333 //
334 mPreviousComparator = HpetRead (HPET_TIMER_COMPARATOR_OFFSET + mTimerIndex * HPET_TIMER_STRIDE);
335
336 //
337 // Set HPET COMPARATOR to the value required for the next timer tick
338 //
339 Comparator = (mPreviousComparator + mTimerCount) & mCounterMask;
340
341 if ((mPreviousMainCounter < MainCounter) && (mPreviousComparator > Comparator)) {
342 //
343 // When comparator overflows
344 //
345 HpetWrite (HPET_TIMER_COMPARATOR_OFFSET + mTimerIndex * HPET_TIMER_STRIDE, Comparator);
346 } else if ((mPreviousMainCounter > MainCounter) && (mPreviousComparator < Comparator)) {
347 //
348 // When main counter overflows
349 //
350 HpetWrite (HPET_TIMER_COMPARATOR_OFFSET + mTimerIndex * HPET_TIMER_STRIDE, (MainCounter + mTimerCount) & mCounterMask);
351 } else {
352 //
353 // When both main counter and comparator do not overflow or both do overflow
354 //
355 if (Comparator > MainCounter) {
356 HpetWrite (HPET_TIMER_COMPARATOR_OFFSET + mTimerIndex * HPET_TIMER_STRIDE, Comparator);
357 } else {
358 HpetWrite (HPET_TIMER_COMPARATOR_OFFSET + mTimerIndex * HPET_TIMER_STRIDE, (MainCounter + mTimerCount) & mCounterMask);
359 }
360 }
361
362 //
363 // Enable the HPET counter once the new COMPARATOR value has been set.
364 //
365 HpetEnable (TRUE);
366
367 //
368 // Check to see if there is a registered notification function
369 //
370 if (mTimerNotifyFunction != NULL) {
371 //
372 // Compute time since last notification in 100 ns units (10 ^ -7)
373 //
374 if (MainCounter > mPreviousMainCounter) {
375 //
376 // Main counter does not overflow
377 //
378 Delta = MainCounter - mPreviousMainCounter;
379 } else {
380 //
381 // Main counter overflows, first usb, then add
382 //
383 Delta = (mCounterMask - mPreviousMainCounter) + MainCounter;
384 }
385 TimerPeriod = DivU64x32 (
386 MultU64x32 (
387 Delta & mCounterMask,
388 mHpetGeneralCapabilities.Bits.CounterClockPeriod
389 ),
390 100000000
391 );
392
393 //
394 // Call registered notification function passing in the time since the last
395 // interrupt in 100 ns units.
396 //
397 mTimerNotifyFunction (TimerPeriod);
398 }
399
400 //
401 // Save main counter value
402 //
403 mPreviousMainCounter = MainCounter;
404 }
405
406 /**
407 This function registers the handler NotifyFunction so it is called every time
408 the timer interrupt fires. It also passes the amount of time since the last
409 handler call to the NotifyFunction. If NotifyFunction is NULL, then the
410 handler is unregistered. If the handler is registered, then EFI_SUCCESS is
411 returned. If the CPU does not support registering a timer interrupt handler,
412 then EFI_UNSUPPORTED is returned. If an attempt is made to register a handler
413 when a handler is already registered, then EFI_ALREADY_STARTED is returned.
414 If an attempt is made to unregister a handler when a handler is not registered,
415 then EFI_INVALID_PARAMETER is returned. If an error occurs attempting to
416 register the NotifyFunction with the timer interrupt, then EFI_DEVICE_ERROR
417 is returned.
418
419 @param This The EFI_TIMER_ARCH_PROTOCOL instance.
420 @param NotifyFunction The function to call when a timer interrupt fires.
421 This function executes at TPL_HIGH_LEVEL. The DXE
422 Core will register a handler for the timer interrupt,
423 so it can know how much time has passed. This
424 information is used to signal timer based events.
425 NULL will unregister the handler.
426
427 @retval EFI_SUCCESS The timer handler was registered.
428 @retval EFI_UNSUPPORTED The platform does not support timer interrupts.
429 @retval EFI_ALREADY_STARTED NotifyFunction is not NULL, and a handler is already
430 registered.
431 @retval EFI_INVALID_PARAMETER NotifyFunction is NULL, and a handler was not
432 previously registered.
433 @retval EFI_DEVICE_ERROR The timer handler could not be registered.
434
435 **/
436 EFI_STATUS
437 EFIAPI
438 TimerDriverRegisterHandler (
439 IN EFI_TIMER_ARCH_PROTOCOL *This,
440 IN EFI_TIMER_NOTIFY NotifyFunction
441 )
442 {
443 //
444 // Check for invalid parameters
445 //
446 if (NotifyFunction == NULL && mTimerNotifyFunction == NULL) {
447 return EFI_INVALID_PARAMETER;
448 }
449 if (NotifyFunction != NULL && mTimerNotifyFunction != NULL) {
450 return EFI_ALREADY_STARTED;
451 }
452
453 //
454 // Cache the registered notification function
455 //
456 mTimerNotifyFunction = NotifyFunction;
457
458 return EFI_SUCCESS;
459 }
460
461 /**
462 This function adjusts the period of timer interrupts to the value specified
463 by TimerPeriod. If the timer period is updated, then the selected timer
464 period is stored in EFI_TIMER.TimerPeriod, and EFI_SUCCESS is returned. If
465 the timer hardware is not programmable, then EFI_UNSUPPORTED is returned.
466 If an error occurs while attempting to update the timer period, then the
467 timer hardware will be put back in its state prior to this call, and
468 EFI_DEVICE_ERROR is returned. If TimerPeriod is 0, then the timer interrupt
469 is disabled. This is not the same as disabling the CPU's interrupts.
470 Instead, it must either turn off the timer hardware, or it must adjust the
471 interrupt controller so that a CPU interrupt is not generated when the timer
472 interrupt fires.
473
474 @param This The EFI_TIMER_ARCH_PROTOCOL instance.
475 @param TimerPeriod The rate to program the timer interrupt in 100 nS units.
476 If the timer hardware is not programmable, then
477 EFI_UNSUPPORTED is returned. If the timer is programmable,
478 then the timer period will be rounded up to the nearest
479 timer period that is supported by the timer hardware.
480 If TimerPeriod is set to 0, then the timer interrupts
481 will be disabled.
482
483 @retval EFI_SUCCESS The timer period was changed.
484 @retval EFI_UNSUPPORTED The platform cannot change the period of the timer interrupt.
485 @retval EFI_DEVICE_ERROR The timer period could not be changed due to a device error.
486
487 **/
488 EFI_STATUS
489 EFIAPI
490 TimerDriverSetTimerPeriod (
491 IN EFI_TIMER_ARCH_PROTOCOL *This,
492 IN UINT64 TimerPeriod
493 )
494 {
495 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
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 (
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 }