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