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