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