Add generic HPET Timer DXE Driver and support libraries
[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, Intel Corporation. All rights reserved.<BR>
5 This program and the accompanying materials
6 are licensed and made available under the terms and conditions of the BSD License
7 which accompanies this distribution. The full text of the license may be found at
8 http://opensource.org/licenses/bsd-license.php
9
10 THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
11 WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
12
13 **/
14
15 #include <PiDxe.h>
16
17 #include <Protocol/Cpu.h>
18 #include <Protocol/Timer.h>
19
20 #include <Library/IoLib.h>
21 #include <Library/PcdLib.h>
22 #include <Library/BaseLib.h>
23 #include <Library/DebugLib.h>
24 #include <Library/UefiBootServicesTableLib.h>
25 #include <Library/LocalApicLib.h>
26 #include <Library/IoApicLib.h>
27
28 #include <Register/LocalApic.h>
29 #include <Register/IoApic.h>
30 #include <Register/Hpet.h>
31
32 ///
33 /// Define value for an invalid HPET Timer index.
34 ///
35 #define HPET_INVALID_TIMER_INDEX 0xff
36
37 ///
38 /// Timer Architectural Protocol function prototypes.
39 ///
40
41 /**
42 This function registers the handler NotifyFunction so it is called every time
43 the timer interrupt fires. It also passes the amount of time since the last
44 handler call to the NotifyFunction. If NotifyFunction is NULL, then the
45 handler is unregistered. If the handler is registered, then EFI_SUCCESS is
46 returned. If the CPU does not support registering a timer interrupt handler,
47 then EFI_UNSUPPORTED is returned. If an attempt is made to register a handler
48 when a handler is already registered, then EFI_ALREADY_STARTED is returned.
49 If an attempt is made to unregister a handler when a handler is not registered,
50 then EFI_INVALID_PARAMETER is returned. If an error occurs attempting to
51 register the NotifyFunction with the timer interrupt, then EFI_DEVICE_ERROR
52 is returned.
53
54 @param This The EFI_TIMER_ARCH_PROTOCOL instance.
55 @param NotifyFunction The function to call when a timer interrupt fires.
56 This function executes at TPL_HIGH_LEVEL. The DXE
57 Core will register a handler for the timer interrupt,
58 so it can know how much time has passed. This
59 information is used to signal timer based events.
60 NULL will unregister the handler.
61
62 @retval EFI_SUCCESS The timer handler was registered.
63 @retval EFI_UNSUPPORTED The platform does not support timer interrupts.
64 @retval EFI_ALREADY_STARTED NotifyFunction is not NULL, and a handler is already
65 registered.
66 @retval EFI_INVALID_PARAMETER NotifyFunction is NULL, and a handler was not
67 previously registered.
68 @retval EFI_DEVICE_ERROR The timer handler could not be registered.
69
70 **/
71 EFI_STATUS
72 EFIAPI
73 TimerDriverRegisterHandler (
74 IN EFI_TIMER_ARCH_PROTOCOL *This,
75 IN EFI_TIMER_NOTIFY NotifyFunction
76 );
77
78 /**
79 This function adjusts the period of timer interrupts to the value specified
80 by TimerPeriod. If the timer period is updated, then the selected timer
81 period is stored in EFI_TIMER.TimerPeriod, and EFI_SUCCESS is returned. If
82 the timer hardware is not programmable, then EFI_UNSUPPORTED is returned.
83 If an error occurs while attempting to update the timer period, then the
84 timer hardware will be put back in its state prior to this call, and
85 EFI_DEVICE_ERROR is returned. If TimerPeriod is 0, then the timer interrupt
86 is disabled. This is not the same as disabling the CPU's interrupts.
87 Instead, it must either turn off the timer hardware, or it must adjust the
88 interrupt controller so that a CPU interrupt is not generated when the timer
89 interrupt fires.
90
91 @param This The EFI_TIMER_ARCH_PROTOCOL instance.
92 @param TimerPeriod The rate to program the timer interrupt in 100 nS units.
93 If the timer hardware is not programmable, then
94 EFI_UNSUPPORTED is returned. If the timer is programmable,
95 then the timer period will be rounded up to the nearest
96 timer period that is supported by the timer hardware.
97 If TimerPeriod is set to 0, then the timer interrupts
98 will be disabled.
99
100 @retval EFI_SUCCESS The timer period was changed.
101 @retval EFI_UNSUPPORTED The platform cannot change the period of the timer interrupt.
102 @retval EFI_DEVICE_ERROR The timer period could not be changed due to a device error.
103
104 **/
105 EFI_STATUS
106 EFIAPI
107 TimerDriverSetTimerPeriod (
108 IN EFI_TIMER_ARCH_PROTOCOL *This,
109 IN UINT64 TimerPeriod
110 );
111
112 /**
113 This function retrieves the period of timer interrupts in 100 ns units,
114 returns that value in TimerPeriod, and returns EFI_SUCCESS. If TimerPeriod
115 is NULL, then EFI_INVALID_PARAMETER is returned. If a TimerPeriod of 0 is
116 returned, then the timer is currently disabled.
117
118 @param This The EFI_TIMER_ARCH_PROTOCOL instance.
119 @param TimerPeriod A pointer to the timer period to retrieve in 100 ns units.
120 If 0 is returned, then the timer is currently disabled.
121
122 @retval EFI_SUCCESS The timer period was returned in TimerPeriod.
123 @retval EFI_INVALID_PARAMETER TimerPeriod is NULL.
124
125 **/
126 EFI_STATUS
127 EFIAPI
128 TimerDriverGetTimerPeriod (
129 IN EFI_TIMER_ARCH_PROTOCOL *This,
130 OUT UINT64 *TimerPeriod
131 );
132
133 /**
134 This function generates a soft timer interrupt. If the platform does not support soft
135 timer interrupts, then EFI_UNSUPPORTED is returned. Otherwise, EFI_SUCCESS is returned.
136 If a handler has been registered through the EFI_TIMER_ARCH_PROTOCOL.RegisterHandler()
137 service, then a soft timer interrupt will be generated. If the timer interrupt is
138 enabled when this service is called, then the registered handler will be invoked. The
139 registered handler should not be able to distinguish a hardware-generated timer
140 interrupt from a software-generated timer interrupt.
141
142 @param This The EFI_TIMER_ARCH_PROTOCOL instance.
143
144 @retval EFI_SUCCESS The soft timer interrupt was generated.
145 @retval EFI_UNSUPPORTEDT The platform does not support the generation of soft
146 timer interrupts.
147
148 **/
149 EFI_STATUS
150 EFIAPI
151 TimerDriverGenerateSoftInterrupt (
152 IN EFI_TIMER_ARCH_PROTOCOL *This
153 );
154
155 ///
156 /// The handle onto which the Timer Architectural Protocol will be installed.
157 ///
158 EFI_HANDLE mTimerHandle = NULL;
159
160 ///
161 /// The Timer Architectural Protocol that this driver produces.
162 ///
163 EFI_TIMER_ARCH_PROTOCOL mTimer = {
164 TimerDriverRegisterHandler,
165 TimerDriverSetTimerPeriod,
166 TimerDriverGetTimerPeriod,
167 TimerDriverGenerateSoftInterrupt
168 };
169
170 ///
171 /// Pointer to the CPU Architectural Protocol instance.
172 ///
173 EFI_CPU_ARCH_PROTOCOL *mCpu = NULL;
174
175 ///
176 /// The notification function to call on every timer interrupt.
177 ///
178 EFI_TIMER_NOTIFY mTimerNotifyFunction = NULL;
179
180 ///
181 /// The current period of the HPET timer interrupt in 100 ns units.
182 ///
183 UINT64 mTimerPeriod = 0;
184
185 ///
186 /// Accumulates HPET timer ticks to account for time passed when the
187 /// HPET timer is disabled or when there is no timer notification function
188 /// registered.
189 ///
190 volatile UINT64 mTimerAccumulator = 0;
191
192 ///
193 /// The index of the HPET timer being managed by this driver.
194 ///
195 UINTN mTimerIndex;
196
197 ///
198 /// The I/O APIC IRQ that the HPET Timer is mapped if I/O APIC mode is used.
199 ///
200 UINT32 mTimerIrq;
201
202 ///
203 /// Cached state of the HPET General Capabilities register managed by this driver.
204 /// Caching the state reduces the number of times the configuration register is read.
205 ///
206 HPET_GENERAL_CAPABILITIES_ID_REGISTER mHpetGeneralCapabilities;
207
208 ///
209 /// Cached state of the HPET General Configuration register managed by this driver.
210 /// Caching the state reduces the number of times the configuration register is read.
211 ///
212 HPET_GENERAL_CONFIGURATION_REGISTER mHpetGeneralConfiguration;
213
214 ///
215 /// Cached state of the Configuration register for the HPET Timer managed by
216 /// this driver. Caching the state reduces the number of times the configuration
217 /// register is read.
218 ///
219 HPET_TIMER_CONFIGURATION_REGISTER mTimerConfiguration;
220
221 ///
222 /// Counts the number of HPET Timer interrupts processed by this driver.
223 /// Only required for debug.
224 ///
225 volatile UINTN mNumTicks;
226
227 /**
228 Read a 64-bit register from the HPET
229
230 @param Offset Specifies the offset of the HPET register to read.
231
232 @return The 64-bit value read from the HPET register specified by Offset.
233 **/
234 UINT64
235 HpetRead (
236 IN UINTN Offset
237 )
238 {
239 return MmioRead64 (PcdGet32 (PcdHpetBaseAddress) + Offset);
240 }
241
242 /**
243 Write a 64-bit HPET register.
244
245 @param Offset Specifies the ofsfert of the HPET register to write.
246 @param Value Specifies the value to write to the HPET register specified by Offset.
247
248 @return The 64-bit value written to HPET register specified by Offset.
249 **/
250 UINT64
251 HpetWrite (
252 IN UINTN Offset,
253 IN UINT64 Value
254 )
255 {
256 return MmioWrite64 (PcdGet32 (PcdHpetBaseAddress) + Offset, Value);
257 }
258
259 /**
260 Enable or disable the main counter in the HPET Timer.
261
262 @param Enable If TRUE, then enable the main counter in the HPET Timer.
263 If FALSE, then disable the main counter in the HPET Timer.
264 **/
265 VOID
266 HpetEnable (
267 IN BOOLEAN Enable
268 )
269 {
270 mHpetGeneralConfiguration.Bits.MainCounterEnable = Enable ? 1 : 0;
271 HpetWrite (HPET_GENERAL_CONFIGURATION_OFFSET, mHpetGeneralConfiguration.Uint64);
272 }
273
274 /**
275 The interrupt handler for the HPET timer. This handler clears the HPET interrupt
276 and computes the amount of time that has passed since the last HPET timer interrupt.
277 If a notification function is registered, then the amount of time since the last
278 HPET interrupt is passed to that notification function in 100 ns units. The HPET
279 time is updated to generate another interrupt in the required time period.
280
281 @param InterruptType The type of interrupt that occured.
282 @param SystemContext A pointer to the system context when the interrupt occured.
283 **/
284 VOID
285 EFIAPI
286 TimerInterruptHandler (
287 IN EFI_EXCEPTION_TYPE InterruptType,
288 IN EFI_SYSTEM_CONTEXT SystemContext
289 )
290 {
291 UINT64 TimerPeriod;
292
293 //
294 // Count number of ticks
295 //
296 DEBUG_CODE (mNumTicks++;);
297
298 //
299 // Clear HPET timer interrupt status
300 //
301 HpetWrite (HPET_GENERAL_INTERRUPT_STATUS_OFFSET, LShiftU64 (1, mTimerIndex));
302
303 //
304 // Local APIC EOI
305 //
306 SendApicEoi ();
307
308 //
309 // Accumulate time from the HPET main counter value
310 //
311 mTimerAccumulator += HpetRead (HPET_MAIN_COUNTER_OFFSET);
312
313 //
314 // Reset HPET main counter to 0
315 //
316 HpetWrite (HPET_MAIN_COUNTER_OFFSET, 0);
317
318 //
319 // Check to see if there is a registered notification function
320 //
321 if (mTimerNotifyFunction != NULL) {
322 //
323 // Compute time since last notification in 100 ns units (10 ^ -7)
324 //
325 TimerPeriod = DivU64x32 (
326 MultU64x32 (
327 mTimerAccumulator,
328 mHpetGeneralCapabilities.Bits.CounterClockPeriod
329 ),
330 100000000
331 );
332 mTimerAccumulator = 0;
333
334 //
335 // Call registered notification function passing in the time since the last
336 // interrupt in 100 ns units.
337 //
338 mTimerNotifyFunction (TimerPeriod);
339 }
340 }
341
342 /**
343 This function registers the handler NotifyFunction so it is called every time
344 the timer interrupt fires. It also passes the amount of time since the last
345 handler call to the NotifyFunction. If NotifyFunction is NULL, then the
346 handler is unregistered. If the handler is registered, then EFI_SUCCESS is
347 returned. If the CPU does not support registering a timer interrupt handler,
348 then EFI_UNSUPPORTED is returned. If an attempt is made to register a handler
349 when a handler is already registered, then EFI_ALREADY_STARTED is returned.
350 If an attempt is made to unregister a handler when a handler is not registered,
351 then EFI_INVALID_PARAMETER is returned. If an error occurs attempting to
352 register the NotifyFunction with the timer interrupt, then EFI_DEVICE_ERROR
353 is returned.
354
355 @param This The EFI_TIMER_ARCH_PROTOCOL instance.
356 @param NotifyFunction The function to call when a timer interrupt fires.
357 This function executes at TPL_HIGH_LEVEL. The DXE
358 Core will register a handler for the timer interrupt,
359 so it can know how much time has passed. This
360 information is used to signal timer based events.
361 NULL will unregister the handler.
362
363 @retval EFI_SUCCESS The timer handler was registered.
364 @retval EFI_UNSUPPORTED The platform does not support timer interrupts.
365 @retval EFI_ALREADY_STARTED NotifyFunction is not NULL, and a handler is already
366 registered.
367 @retval EFI_INVALID_PARAMETER NotifyFunction is NULL, and a handler was not
368 previously registered.
369 @retval EFI_DEVICE_ERROR The timer handler could not be registered.
370
371 **/
372 EFI_STATUS
373 EFIAPI
374 TimerDriverRegisterHandler (
375 IN EFI_TIMER_ARCH_PROTOCOL *This,
376 IN EFI_TIMER_NOTIFY NotifyFunction
377 )
378 {
379 //
380 // Check for invalid parameters
381 //
382 if (NotifyFunction == NULL && mTimerNotifyFunction == NULL) {
383 return EFI_INVALID_PARAMETER;
384 }
385 if (NotifyFunction != NULL && mTimerNotifyFunction != NULL) {
386 return EFI_ALREADY_STARTED;
387 }
388
389 //
390 // Cache the registered notification function
391 //
392 mTimerNotifyFunction = NotifyFunction;
393
394 return EFI_SUCCESS;
395 }
396
397 /**
398 This function adjusts the period of timer interrupts to the value specified
399 by TimerPeriod. If the timer period is updated, then the selected timer
400 period is stored in EFI_TIMER.TimerPeriod, and EFI_SUCCESS is returned. If
401 the timer hardware is not programmable, then EFI_UNSUPPORTED is returned.
402 If an error occurs while attempting to update the timer period, then the
403 timer hardware will be put back in its state prior to this call, and
404 EFI_DEVICE_ERROR is returned. If TimerPeriod is 0, then the timer interrupt
405 is disabled. This is not the same as disabling the CPU's interrupts.
406 Instead, it must either turn off the timer hardware, or it must adjust the
407 interrupt controller so that a CPU interrupt is not generated when the timer
408 interrupt fires.
409
410 @param This The EFI_TIMER_ARCH_PROTOCOL instance.
411 @param TimerPeriod The rate to program the timer interrupt in 100 nS units.
412 If the timer hardware is not programmable, then
413 EFI_UNSUPPORTED is returned. If the timer is programmable,
414 then the timer period will be rounded up to the nearest
415 timer period that is supported by the timer hardware.
416 If TimerPeriod is set to 0, then the timer interrupts
417 will be disabled.
418
419 @retval EFI_SUCCESS The timer period was changed.
420 @retval EFI_UNSUPPORTED The platform cannot change the period of the timer interrupt.
421 @retval EFI_DEVICE_ERROR The timer period could not be changed due to a device error.
422
423 **/
424 EFI_STATUS
425 EFIAPI
426 TimerDriverSetTimerPeriod (
427 IN EFI_TIMER_ARCH_PROTOCOL *This,
428 IN UINT64 TimerPeriod
429 )
430 {
431 UINT64 TimerCount;
432
433 //
434 // Disable HPET timer when adjusting the timer period
435 //
436 HpetEnable (FALSE);
437
438 if (TimerPeriod == 0) {
439 //
440 // If TimerPeriod is 0, then mask HPET Timer interrupts
441 //
442
443 if (mTimerConfiguration.Bits.MsiInterruptCapablity != 0) {
444 //
445 // Disable HPET MSI interrupt generation
446 //
447 mTimerConfiguration.Bits.MsiInterruptEnable = 0;
448 } else {
449 //
450 // Disable I/O APIC Interrupt
451 //
452 IoApicEnableInterrupt (mTimerIrq, FALSE);
453 }
454
455 //
456 // Disable HPET timer interrupt
457 //
458 mTimerConfiguration.Bits.InterruptEnable = 0;
459 HpetWrite (HPET_TIMER_CONFIGURATION_OFFSET + mTimerIndex * HPET_TIMER_STRIDE, mTimerConfiguration.Uint64);
460 } else {
461 //
462 // Convert TimerPeriod to femtoseconds and divide by the number if femtoseconds
463 // per tick of the HPET counter to determine the number of HPET counter ticks
464 // in TimerPeriod 100 ns units.
465 //
466 TimerCount = DivU64x32 (
467 MultU64x32 (TimerPeriod, 100000000),
468 mHpetGeneralCapabilities.Bits.CounterClockPeriod
469 );
470
471 //
472 // Program the HPET Comparator with the number of ticks till the next interrupt
473 //
474 HpetWrite (HPET_TIMER_COMPARATOR_OFFSET + mTimerIndex * HPET_TIMER_STRIDE, TimerCount);
475
476 //
477 // Capture the number of ticks since the last HPET Timer interrupt before
478 // clearing the main counter. This value will be used in the next HPET
479 // timer interrupt handler to compute the total amount of time since the
480 // last HPET timer interrupt
481 //
482 mTimerAccumulator = HpetRead (HPET_MAIN_COUNTER_OFFSET);
483
484 //
485 // If the number of ticks since the last timer interrupt is greater than the
486 // timer period, reduce the number of ticks till the next interrupt to 1, so
487 // a timer interrupt will be generated as soon as the HPET counter is enabled.
488 //
489 if (mTimerAccumulator >= TimerCount) {
490 HpetWrite (HPET_MAIN_COUNTER_OFFSET, TimerCount - 1);
491 //
492 // Adjust the accumulator down by TimerCount ticks because TimerCount
493 // ticks will be added to the accumulator on the next interrupt
494 //
495 mTimerAccumulator -= TimerCount;
496 }
497
498 //
499 // Enable HPET Timer interrupt generation
500 //
501 if (mTimerConfiguration.Bits.MsiInterruptCapablity != 0) {
502 //
503 // Enable HPET MSI Interrupt
504 //
505 mTimerConfiguration.Bits.MsiInterruptEnable = 1;
506 } else {
507 //
508 // Enable timer interrupt through I/O APIC
509 //
510 IoApicEnableInterrupt (mTimerIrq, TRUE);
511 }
512
513 //
514 // Enable HPET Interrupt Generation
515 //
516 mTimerConfiguration.Bits.InterruptEnable = 1;
517 HpetWrite (HPET_TIMER_CONFIGURATION_OFFSET + mTimerIndex * HPET_TIMER_STRIDE, mTimerConfiguration.Uint64);
518 }
519
520 //
521 // Save the new timer period
522 //
523 mTimerPeriod = TimerPeriod;
524
525 //
526 // Enable the HPET counter once new timer period has been established
527 // The HPET counter should run even if the HPET Timer interrupts are
528 // disabled. This is used to account for time passed while the interrupt
529 // is disabled.
530 //
531 HpetEnable (TRUE);
532
533 return EFI_SUCCESS;
534 }
535
536 /**
537 This function retrieves the period of timer interrupts in 100 ns units,
538 returns that value in TimerPeriod, and returns EFI_SUCCESS. If TimerPeriod
539 is NULL, then EFI_INVALID_PARAMETER is returned. If a TimerPeriod of 0 is
540 returned, then the timer is currently disabled.
541
542 @param This The EFI_TIMER_ARCH_PROTOCOL instance.
543 @param TimerPeriod A pointer to the timer period to retrieve in 100 ns units.
544 If 0 is returned, then the timer is currently disabled.
545
546 @retval EFI_SUCCESS The timer period was returned in TimerPeriod.
547 @retval EFI_INVALID_PARAMETER TimerPeriod is NULL.
548
549 **/
550 EFI_STATUS
551 EFIAPI
552 TimerDriverGetTimerPeriod (
553 IN EFI_TIMER_ARCH_PROTOCOL *This,
554 OUT UINT64 *TimerPeriod
555 )
556 {
557 if (TimerPeriod == NULL) {
558 return EFI_INVALID_PARAMETER;
559 }
560
561 *TimerPeriod = mTimerPeriod;
562
563 return EFI_SUCCESS;
564 }
565
566 /**
567 This function generates a soft timer interrupt. If the platform does not support soft
568 timer interrupts, then EFI_UNSUPPORTED is returned. Otherwise, EFI_SUCCESS is returned.
569 If a handler has been registered through the EFI_TIMER_ARCH_PROTOCOL.RegisterHandler()
570 service, then a soft timer interrupt will be generated. If the timer interrupt is
571 enabled when this service is called, then the registered handler will be invoked. The
572 registered handler should not be able to distinguish a hardware-generated timer
573 interrupt from a software-generated timer interrupt.
574
575 @param This The EFI_TIMER_ARCH_PROTOCOL instance.
576
577 @retval EFI_SUCCESS The soft timer interrupt was generated.
578 @retval EFI_UNSUPPORTEDT The platform does not support the generation of soft
579 timer interrupts.
580
581 **/
582 EFI_STATUS
583 EFIAPI
584 TimerDriverGenerateSoftInterrupt (
585 IN EFI_TIMER_ARCH_PROTOCOL *This
586 )
587 {
588 EFI_TPL Tpl;
589 UINT64 TimerPeriod;
590
591 //
592 // Disable interrupts
593 //
594 Tpl = gBS->RaiseTPL (TPL_HIGH_LEVEL);
595
596 //
597 // Read the current HPET main counter value
598 //
599 mTimerAccumulator += HpetRead (HPET_MAIN_COUNTER_OFFSET);
600
601 //
602 // Reset HPET main counter to 0
603 //
604 HpetWrite (HPET_MAIN_COUNTER_OFFSET, 0);
605
606 //
607 // Check to see if there is a registered notification function
608 //
609 if (mTimerNotifyFunction != NULL) {
610 //
611 // Compute time since last interrupt in 100 ns units (10 ^ -7)
612 //
613 TimerPeriod = DivU64x32 (
614 MultU64x32 (
615 mTimerAccumulator,
616 mHpetGeneralCapabilities.Bits.CounterClockPeriod
617 ),
618 100000000
619 );
620 mTimerAccumulator = 0;
621
622 //
623 // Call registered notification function passing in the time since the last
624 // interrupt in 100 ns units.
625 //
626 mTimerNotifyFunction (TimerPeriod);
627 }
628
629 //
630 // Restore interrupts
631 //
632 gBS->RestoreTPL (Tpl);
633
634 return EFI_SUCCESS;
635 }
636
637 /**
638 Initialize the Timer Architectural Protocol driver
639
640 @param ImageHandle ImageHandle of the loaded driver
641 @param SystemTable Pointer to the System Table
642
643 @retval EFI_SUCCESS Timer Architectural Protocol created
644 @retval EFI_OUT_OF_RESOURCES Not enough resources available to initialize driver.
645 @retval EFI_DEVICE_ERROR A device error occured attempting to initialize the driver.
646
647 **/
648 EFI_STATUS
649 EFIAPI
650 TimerDriverInitialize (
651 IN EFI_HANDLE ImageHandle,
652 IN EFI_SYSTEM_TABLE *SystemTable
653 )
654 {
655 EFI_STATUS Status;
656 UINTN TimerIndex;
657 UINTN MsiTimerIndex;
658 HPET_TIMER_MSI_ROUTE_REGISTER HpetTimerMsiRoute;
659
660 DEBUG ((DEBUG_INFO, "Init HPET Timer Driver\n"));
661
662 //
663 // Make sure the Timer Architectural Protocol is not already installed in the system
664 //
665 ASSERT_PROTOCOL_ALREADY_INSTALLED (NULL, &gEfiTimerArchProtocolGuid);
666
667 //
668 // Find the CPU architectural protocol.
669 //
670 Status = gBS->LocateProtocol (&gEfiCpuArchProtocolGuid, NULL, (VOID **) &mCpu);
671 ASSERT_EFI_ERROR (Status);
672
673 //
674 // Retrieve HPET Capabilities and Configuration Information
675 //
676 mHpetGeneralCapabilities.Uint64 = HpetRead (HPET_GENERAL_CAPABILITIES_ID_OFFSET);
677 mHpetGeneralConfiguration.Uint64 = HpetRead (HPET_GENERAL_CONFIGURATION_OFFSET);
678
679 //
680 // If Revision is not valid, then ASSERT() and unload the driver because the HPET
681 // device is not present.
682 //
683 ASSERT (mHpetGeneralCapabilities.Uint64 != 0);
684 ASSERT (mHpetGeneralCapabilities.Uint64 != 0xFFFFFFFFFFFFFFFFULL);
685 if (mHpetGeneralCapabilities.Uint64 == 0 || mHpetGeneralCapabilities.Uint64 == 0xFFFFFFFFFFFFFFFFULL) {
686 DEBUG ((DEBUG_ERROR, "HPET device is not present. Unload HPET driver.\n"));
687 return EFI_DEVICE_ERROR;
688 }
689
690 //
691 // Force the HPET timer to be disabled while setting everything up
692 //
693 HpetEnable (FALSE);
694
695 //
696 // Dump HPET Configuration Information
697 //
698 DEBUG_CODE (
699 DEBUG ((DEBUG_INFO, "HPET Base Address = %08x\n", PcdGet32 (PcdHpetBaseAddress)));
700 DEBUG ((DEBUG_INFO, " HPET_GENERAL_CAPABILITIES_ID = %016lx\n", mHpetGeneralCapabilities));
701 DEBUG ((DEBUG_INFO, " HPET_GENERAL_CONFIGURATION = %016lx\n", mHpetGeneralConfiguration.Uint64));
702 DEBUG ((DEBUG_INFO, " HPET_GENERAL_INTERRUPT_STATUS = %016lx\n", HpetRead (HPET_GENERAL_INTERRUPT_STATUS_OFFSET)));
703 DEBUG ((DEBUG_INFO, " HPET_MAIN_COUNTER = %016lx\n", HpetRead (HPET_MAIN_COUNTER_OFFSET)));
704 DEBUG ((DEBUG_INFO, " HPET Main Counter Period = %d (fs)\n", mHpetGeneralCapabilities.Bits.CounterClockPeriod));
705 for (TimerIndex = 0; TimerIndex <= mHpetGeneralCapabilities.Bits.NumberOfTimers; TimerIndex++) {
706 DEBUG ((DEBUG_INFO, " HPET_TIMER%d_CONFIGURATION = %016lx\n", TimerIndex, HpetRead (HPET_TIMER_CONFIGURATION_OFFSET + TimerIndex * HPET_TIMER_STRIDE)));
707 DEBUG ((DEBUG_INFO, " HPET_TIMER%d_COMPARATOR = %016lx\n", TimerIndex, HpetRead (HPET_TIMER_COMPARATOR_OFFSET + TimerIndex * HPET_TIMER_STRIDE)));
708 DEBUG ((DEBUG_INFO, " HPET_TIMER%d_MSI_ROUTE = %016lx\n", TimerIndex, HpetRead (HPET_TIMER_MSI_ROUTE_OFFSET + TimerIndex * HPET_TIMER_STRIDE)));
709 }
710 );
711
712 //
713 // Determine the interrupt mode to use for the HPET Timer.
714 // Look for MSI first, then unused PIC mode interrupt, then I/O APIC mode interrupt
715 //
716 MsiTimerIndex = HPET_INVALID_TIMER_INDEX;
717 mTimerIndex = HPET_INVALID_TIMER_INDEX;
718 for (TimerIndex = 0; TimerIndex <= mHpetGeneralCapabilities.Bits.NumberOfTimers; TimerIndex++) {
719 //
720 // Read the HPET Timer Capabilities and Configuration register
721 //
722 mTimerConfiguration.Uint64 = HpetRead (HPET_TIMER_CONFIGURATION_OFFSET + TimerIndex * HPET_TIMER_STRIDE);
723
724 //
725 // Check to see if this HPET Timer supports MSI
726 //
727 if (mTimerConfiguration.Bits.MsiInterruptCapablity != 0) {
728 //
729 // Save the index of the first HPET Timer that supports MSI interrupts
730 //
731 if (MsiTimerIndex == HPET_INVALID_TIMER_INDEX) {
732 MsiTimerIndex = TimerIndex;
733 }
734 }
735
736 //
737 // Check to see if this HPET Timer supports I/O APIC interrupts
738 //
739 if (mTimerConfiguration.Bits.InterruptRouteCapability != 0) {
740 //
741 // Save the index of the first HPET Timer that supports I/O APIC interrupts
742 //
743 if (mTimerIndex == HPET_INVALID_TIMER_INDEX) {
744 mTimerIndex = TimerIndex;
745 mTimerIrq = (UINT32)LowBitSet32 (mTimerConfiguration.Bits.InterruptRouteCapability);
746 }
747 }
748 }
749
750 if (FeaturePcdGet (PcdHpetMsiEnable) && MsiTimerIndex != HPET_INVALID_TIMER_INDEX) {
751 //
752 // Use MSI interrupt if supported
753 //
754 mTimerIndex = MsiTimerIndex;
755
756 //
757 // Program MSI Address and MSI Data values in the selected HPET Timer
758 //
759 HpetTimerMsiRoute.Bits.Address = GetApicMsiAddress ();
760 HpetTimerMsiRoute.Bits.Value = (UINT32)GetApicMsiValue (PcdGet8 (PcdHpetLocalApicVector), LOCAL_APIC_DELIVERY_MODE_LOWEST_PRIORITY, FALSE, FALSE);
761 HpetWrite (HPET_TIMER_MSI_ROUTE_OFFSET + mTimerIndex * HPET_TIMER_STRIDE, HpetTimerMsiRoute.Uint64);
762
763 //
764 // Read the HPET Timer Capabilities and Configuration register and initialize for MSI mode
765 // Clear LevelTriggeredInterrupt to use edge triggered interrupts when in MSI mode
766 //
767 mTimerConfiguration.Uint64 = HpetRead (HPET_TIMER_CONFIGURATION_OFFSET + mTimerIndex * HPET_TIMER_STRIDE);
768 mTimerConfiguration.Bits.LevelTriggeredInterrupt = 0;
769 } else {
770 //
771 // If no HPET timers support MSI or I/O APIC modes, then ASSERT() and unload the driver.
772 //
773 ASSERT (mTimerIndex != HPET_INVALID_TIMER_INDEX);
774 if (mTimerIndex == HPET_INVALID_TIMER_INDEX) {
775 DEBUG ((DEBUG_ERROR, "No HPET timers support MSI or I/O APIC mode. Unload HPET driver.\n"));
776 return EFI_DEVICE_ERROR;
777 }
778
779 //
780 // Initialize I/O APIC entry for HPET Timer Interrupt
781 // Fixed Delivery Mode, Level Triggered, Asserted Low
782 //
783 IoApicConfigureInterrupt (mTimerIrq, PcdGet8 (PcdHpetLocalApicVector), IO_APIC_DELIVERY_MODE_LOWEST_PRIORITY, TRUE, FALSE);
784
785 //
786 // Read the HPET Timer Capabilities and Configuration register and initialize for I/O APIC mode
787 // Clear MsiInterruptCapability to force rest of driver to use I/O APIC mode
788 // Set LevelTriggeredInterrupt to use level triggered interrupts when in I/O APIC mode
789 // Set InterruptRoute field based in mTimerIrq
790 //
791 mTimerConfiguration.Uint64 = HpetRead (HPET_TIMER_CONFIGURATION_OFFSET + mTimerIndex * HPET_TIMER_STRIDE);
792 mTimerConfiguration.Bits.MsiInterruptCapablity = 0;
793 mTimerConfiguration.Bits.LevelTriggeredInterrupt = 1;
794 mTimerConfiguration.Bits.InterruptRoute = mTimerIrq;
795 }
796
797 //
798 // Configure the selected HPET Timer with settings common to both MSI mode and I/O APIC mode
799 // Clear InterruptEnable to keep interrupts disabled until full init is complete
800 // Clear PeriodicInterruptEnable to use one-shot mode
801 // Configure as a 32-bit counter
802 //
803 mTimerConfiguration.Bits.InterruptEnable = 0;
804 mTimerConfiguration.Bits.PeriodicInterruptEnable = 0;
805 mTimerConfiguration.Bits.CounterSizeEnable = 0;
806 HpetWrite (HPET_TIMER_CONFIGURATION_OFFSET + mTimerIndex * HPET_TIMER_STRIDE, mTimerConfiguration.Uint64);
807
808 //
809 // Install interrupt handler for selected HPET Timer
810 //
811 Status = mCpu->RegisterInterruptHandler (mCpu, PcdGet8 (PcdHpetLocalApicVector), TimerInterruptHandler);
812 ASSERT_EFI_ERROR (Status);
813 if (EFI_ERROR (Status)) {
814 DEBUG ((DEBUG_ERROR, "Unable to register HPET interrupt with CPU Arch Protocol. Unload HPET driver.\n"));
815 return EFI_DEVICE_ERROR;
816 }
817
818 //
819 // Force the HPET Timer to be enabled at its default period
820 //
821 Status = TimerDriverSetTimerPeriod (&mTimer, PcdGet64 (PcdHpetDefaultTimerPeriod));
822 ASSERT_EFI_ERROR (Status);
823 if (EFI_ERROR (Status)) {
824 DEBUG ((DEBUG_ERROR, "Unable to set HPET default timer rate. Unload HPET driver.\n"));
825 return EFI_DEVICE_ERROR;
826 }
827
828 //
829 // Show state of enabled HPET timer
830 //
831 DEBUG_CODE (
832 if (mTimerConfiguration.Bits.MsiInterruptCapablity != 0) {
833 DEBUG ((DEBUG_INFO, "HPET Interrupt Mode MSI\n"));
834 } else {
835 DEBUG ((DEBUG_INFO, "HPET Interrupt Mode I/O APIC\n"));
836 DEBUG ((DEBUG_INFO, "HPET I/O APIC IRQ = %02x\n", mTimerIrq));
837 }
838 DEBUG ((DEBUG_INFO, "HPET Interrupt Vector = %02x\n", PcdGet8 (PcdHpetLocalApicVector)));
839 DEBUG ((DEBUG_INFO, "HPET_TIMER%d_CONFIGURATION = %016lx\n", mTimerIndex, HpetRead (HPET_TIMER_CONFIGURATION_OFFSET + mTimerIndex * HPET_TIMER_STRIDE)));
840 DEBUG ((DEBUG_INFO, "HPET_TIMER%d_COMPARATOR = %016lx\n", mTimerIndex, HpetRead (HPET_TIMER_COMPARATOR_OFFSET + mTimerIndex * HPET_TIMER_STRIDE)));
841 DEBUG ((DEBUG_INFO, "HPET_TIMER%d_MSI_ROUTE = %016lx\n", mTimerIndex, HpetRead (HPET_TIMER_MSI_ROUTE_OFFSET + mTimerIndex * HPET_TIMER_STRIDE)));
842
843 //
844 // Wait for a few timer interrupts to fire before continuing
845 //
846 while (mNumTicks < 10);
847 );
848
849 //
850 // Install the Timer Architectural Protocol onto a new handle
851 //
852 Status = gBS->InstallMultipleProtocolInterfaces (
853 &mTimerHandle,
854 &gEfiTimerArchProtocolGuid, &mTimer,
855 NULL
856 );
857 ASSERT_EFI_ERROR (Status);
858
859 return Status;
860 }