--- /dev/null
+/** @file\r
+ Timer Architectural Protocol module using High Precesion Event Timer (HPET)\r
+\r
+ Copyright (c) 2011, Intel Corporation. All rights reserved.<BR>\r
+ This program and the accompanying materials\r
+ are licensed and made available under the terms and conditions of the BSD License\r
+ which accompanies this distribution. The full text of the license may be found at\r
+ http://opensource.org/licenses/bsd-license.php\r
+\r
+ THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,\r
+ WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.\r
+\r
+**/\r
+\r
+#include <PiDxe.h>\r
+\r
+#include <Protocol/Cpu.h>\r
+#include <Protocol/Timer.h>\r
+\r
+#include <Library/IoLib.h>\r
+#include <Library/PcdLib.h>\r
+#include <Library/BaseLib.h>\r
+#include <Library/DebugLib.h>\r
+#include <Library/UefiBootServicesTableLib.h>\r
+#include <Library/LocalApicLib.h>\r
+#include <Library/IoApicLib.h>\r
+\r
+#include <Register/LocalApic.h>\r
+#include <Register/IoApic.h>\r
+#include <Register/Hpet.h>\r
+\r
+///\r
+/// Define value for an invalid HPET Timer index.\r
+///\r
+#define HPET_INVALID_TIMER_INDEX 0xff\r
+\r
+///\r
+/// Timer Architectural Protocol function prototypes.\r
+///\r
+\r
+/**\r
+ This function registers the handler NotifyFunction so it is called every time\r
+ the timer interrupt fires. It also passes the amount of time since the last\r
+ handler call to the NotifyFunction. If NotifyFunction is NULL, then the\r
+ handler is unregistered. If the handler is registered, then EFI_SUCCESS is\r
+ returned. If the CPU does not support registering a timer interrupt handler,\r
+ then EFI_UNSUPPORTED is returned. If an attempt is made to register a handler\r
+ when a handler is already registered, then EFI_ALREADY_STARTED is returned.\r
+ If an attempt is made to unregister a handler when a handler is not registered,\r
+ then EFI_INVALID_PARAMETER is returned. If an error occurs attempting to\r
+ register the NotifyFunction with the timer interrupt, then EFI_DEVICE_ERROR\r
+ is returned.\r
+\r
+ @param This The EFI_TIMER_ARCH_PROTOCOL instance.\r
+ @param NotifyFunction The function to call when a timer interrupt fires. \r
+ This function executes at TPL_HIGH_LEVEL. The DXE \r
+ Core will register a handler for the timer interrupt, \r
+ so it can know how much time has passed. This \r
+ information is used to signal timer based events. \r
+ NULL will unregister the handler.\r
+\r
+ @retval EFI_SUCCESS The timer handler was registered.\r
+ @retval EFI_UNSUPPORTED The platform does not support timer interrupts.\r
+ @retval EFI_ALREADY_STARTED NotifyFunction is not NULL, and a handler is already\r
+ registered.\r
+ @retval EFI_INVALID_PARAMETER NotifyFunction is NULL, and a handler was not\r
+ previously registered.\r
+ @retval EFI_DEVICE_ERROR The timer handler could not be registered.\r
+\r
+**/\r
+EFI_STATUS\r
+EFIAPI\r
+TimerDriverRegisterHandler (\r
+ IN EFI_TIMER_ARCH_PROTOCOL *This,\r
+ IN EFI_TIMER_NOTIFY NotifyFunction\r
+ );\r
+\r
+/**\r
+ This function adjusts the period of timer interrupts to the value specified\r
+ by TimerPeriod. If the timer period is updated, then the selected timer\r
+ period is stored in EFI_TIMER.TimerPeriod, and EFI_SUCCESS is returned. If\r
+ the timer hardware is not programmable, then EFI_UNSUPPORTED is returned.\r
+ If an error occurs while attempting to update the timer period, then the\r
+ timer hardware will be put back in its state prior to this call, and\r
+ EFI_DEVICE_ERROR is returned. If TimerPeriod is 0, then the timer interrupt\r
+ is disabled. This is not the same as disabling the CPU's interrupts.\r
+ Instead, it must either turn off the timer hardware, or it must adjust the\r
+ interrupt controller so that a CPU interrupt is not generated when the timer\r
+ interrupt fires.\r
+\r
+ @param This The EFI_TIMER_ARCH_PROTOCOL instance.\r
+ @param TimerPeriod The rate to program the timer interrupt in 100 nS units.\r
+ If the timer hardware is not programmable, then \r
+ EFI_UNSUPPORTED is returned. If the timer is programmable, \r
+ then the timer period will be rounded up to the nearest \r
+ timer period that is supported by the timer hardware. \r
+ If TimerPeriod is set to 0, then the timer interrupts \r
+ will be disabled.\r
+\r
+ @retval EFI_SUCCESS The timer period was changed.\r
+ @retval EFI_UNSUPPORTED The platform cannot change the period of the timer interrupt.\r
+ @retval EFI_DEVICE_ERROR The timer period could not be changed due to a device error.\r
+\r
+**/\r
+EFI_STATUS\r
+EFIAPI\r
+TimerDriverSetTimerPeriod (\r
+ IN EFI_TIMER_ARCH_PROTOCOL *This,\r
+ IN UINT64 TimerPeriod\r
+ );\r
+\r
+/**\r
+ This function retrieves the period of timer interrupts in 100 ns units,\r
+ returns that value in TimerPeriod, and returns EFI_SUCCESS. If TimerPeriod\r
+ is NULL, then EFI_INVALID_PARAMETER is returned. If a TimerPeriod of 0 is\r
+ returned, then the timer is currently disabled.\r
+\r
+ @param This The EFI_TIMER_ARCH_PROTOCOL instance.\r
+ @param TimerPeriod A pointer to the timer period to retrieve in 100 ns units.\r
+ If 0 is returned, then the timer is currently disabled.\r
+\r
+ @retval EFI_SUCCESS The timer period was returned in TimerPeriod.\r
+ @retval EFI_INVALID_PARAMETER TimerPeriod is NULL.\r
+\r
+**/\r
+EFI_STATUS\r
+EFIAPI\r
+TimerDriverGetTimerPeriod (\r
+ IN EFI_TIMER_ARCH_PROTOCOL *This,\r
+ OUT UINT64 *TimerPeriod\r
+ );\r
+\r
+/**\r
+ This function generates a soft timer interrupt. If the platform does not support soft\r
+ timer interrupts, then EFI_UNSUPPORTED is returned. Otherwise, EFI_SUCCESS is returned.\r
+ If a handler has been registered through the EFI_TIMER_ARCH_PROTOCOL.RegisterHandler()\r
+ service, then a soft timer interrupt will be generated. If the timer interrupt is\r
+ enabled when this service is called, then the registered handler will be invoked. The\r
+ registered handler should not be able to distinguish a hardware-generated timer\r
+ interrupt from a software-generated timer interrupt.\r
+\r
+ @param This The EFI_TIMER_ARCH_PROTOCOL instance.\r
+\r
+ @retval EFI_SUCCESS The soft timer interrupt was generated.\r
+ @retval EFI_UNSUPPORTEDT The platform does not support the generation of soft \r
+ timer interrupts.\r
+\r
+**/\r
+EFI_STATUS\r
+EFIAPI\r
+TimerDriverGenerateSoftInterrupt (\r
+ IN EFI_TIMER_ARCH_PROTOCOL *This\r
+ );\r
+ \r
+///\r
+/// The handle onto which the Timer Architectural Protocol will be installed.\r
+///\r
+EFI_HANDLE mTimerHandle = NULL;\r
+\r
+///\r
+/// The Timer Architectural Protocol that this driver produces.\r
+///\r
+EFI_TIMER_ARCH_PROTOCOL mTimer = {\r
+ TimerDriverRegisterHandler,\r
+ TimerDriverSetTimerPeriod,\r
+ TimerDriverGetTimerPeriod,\r
+ TimerDriverGenerateSoftInterrupt\r
+};\r
+\r
+///\r
+/// Pointer to the CPU Architectural Protocol instance.\r
+///\r
+EFI_CPU_ARCH_PROTOCOL *mCpu = NULL;\r
+\r
+///\r
+/// The notification function to call on every timer interrupt.\r
+///\r
+EFI_TIMER_NOTIFY mTimerNotifyFunction = NULL;\r
+\r
+///\r
+/// The current period of the HPET timer interrupt in 100 ns units.\r
+///\r
+UINT64 mTimerPeriod = 0;\r
+\r
+///\r
+/// Accumulates HPET timer ticks to account for time passed when the \r
+/// HPET timer is disabled or when there is no timer notification function\r
+/// registered.\r
+///\r
+volatile UINT64 mTimerAccumulator = 0;\r
+\r
+///\r
+/// The index of the HPET timer being managed by this driver.\r
+///\r
+UINTN mTimerIndex;\r
+\r
+///\r
+/// The I/O APIC IRQ that the HPET Timer is mapped if I/O APIC mode is used.\r
+///\r
+UINT32 mTimerIrq;\r
+\r
+///\r
+/// Cached state of the HPET General Capabilities register managed by this driver.\r
+/// Caching the state reduces the number of times the configuration register is read.\r
+///\r
+HPET_GENERAL_CAPABILITIES_ID_REGISTER mHpetGeneralCapabilities;\r
+\r
+///\r
+/// Cached state of the HPET General Configuration register managed by this driver.\r
+/// Caching the state reduces the number of times the configuration register is read.\r
+///\r
+HPET_GENERAL_CONFIGURATION_REGISTER mHpetGeneralConfiguration;\r
+\r
+///\r
+/// Cached state of the Configuration register for the HPET Timer managed by \r
+/// this driver. Caching the state reduces the number of times the configuration\r
+/// register is read.\r
+///\r
+HPET_TIMER_CONFIGURATION_REGISTER mTimerConfiguration;\r
+\r
+///\r
+/// Counts the number of HPET Timer interrupts processed by this driver.\r
+/// Only required for debug.\r
+///\r
+volatile UINTN mNumTicks;\r
+\r
+/**\r
+ Read a 64-bit register from the HPET\r
+\r
+ @param Offset Specifies the offset of the HPET register to read.\r
+\r
+ @return The 64-bit value read from the HPET register specified by Offset.\r
+**/\r
+UINT64\r
+HpetRead (\r
+ IN UINTN Offset\r
+ )\r
+{\r
+ return MmioRead64 (PcdGet32 (PcdHpetBaseAddress) + Offset);\r
+}\r
+\r
+/**\r
+ Write a 64-bit HPET register.\r
+\r
+ @param Offset Specifies the ofsfert of the HPET register to write.\r
+ @param Value Specifies the value to write to the HPET register specified by Offset.\r
+\r
+ @return The 64-bit value written to HPET register specified by Offset.\r
+**/\r
+UINT64\r
+HpetWrite (\r
+ IN UINTN Offset,\r
+ IN UINT64 Value\r
+ )\r
+{\r
+ return MmioWrite64 (PcdGet32 (PcdHpetBaseAddress) + Offset, Value);\r
+}\r
+\r
+/**\r
+ Enable or disable the main counter in the HPET Timer.\r
+\r
+ @param Enable If TRUE, then enable the main counter in the HPET Timer.\r
+ If FALSE, then disable the main counter in the HPET Timer.\r
+**/\r
+VOID\r
+HpetEnable (\r
+ IN BOOLEAN Enable\r
+ )\r
+{\r
+ mHpetGeneralConfiguration.Bits.MainCounterEnable = Enable ? 1 : 0; \r
+ HpetWrite (HPET_GENERAL_CONFIGURATION_OFFSET, mHpetGeneralConfiguration.Uint64);\r
+}\r
+\r
+/**\r
+ The interrupt handler for the HPET timer. This handler clears the HPET interrupt\r
+ and computes the amount of time that has passed since the last HPET timer interrupt.\r
+ If a notification function is registered, then the amount of time since the last\r
+ HPET interrupt is passed to that notification function in 100 ns units. The HPET\r
+ time is updated to generate another interrupt in the required time period. \r
+\r
+ @param InterruptType The type of interrupt that occured.\r
+ @param SystemContext A pointer to the system context when the interrupt occured.\r
+**/\r
+VOID\r
+EFIAPI\r
+TimerInterruptHandler (\r
+ IN EFI_EXCEPTION_TYPE InterruptType,\r
+ IN EFI_SYSTEM_CONTEXT SystemContext\r
+ )\r
+{\r
+ UINT64 TimerPeriod;\r
+ \r
+ //\r
+ // Count number of ticks\r
+ //\r
+ DEBUG_CODE (mNumTicks++;);\r
+\r
+ //\r
+ // Clear HPET timer interrupt status\r
+ //\r
+ HpetWrite (HPET_GENERAL_INTERRUPT_STATUS_OFFSET, LShiftU64 (1, mTimerIndex));\r
+\r
+ //\r
+ // Local APIC EOI\r
+ //\r
+ SendApicEoi ();\r
+\r
+ //\r
+ // Accumulate time from the HPET main counter value\r
+ //\r
+ mTimerAccumulator += HpetRead (HPET_MAIN_COUNTER_OFFSET);\r
+\r
+ //\r
+ // Reset HPET main counter to 0\r
+ //\r
+ HpetWrite (HPET_MAIN_COUNTER_OFFSET, 0);\r
+\r
+ //\r
+ // Check to see if there is a registered notification function\r
+ //\r
+ if (mTimerNotifyFunction != NULL) {\r
+ //\r
+ // Compute time since last notification in 100 ns units (10 ^ -7) \r
+ //\r
+ TimerPeriod = DivU64x32 (\r
+ MultU64x32 (\r
+ mTimerAccumulator, \r
+ mHpetGeneralCapabilities.Bits.CounterClockPeriod\r
+ ), \r
+ 100000000\r
+ );\r
+ mTimerAccumulator = 0;\r
+ \r
+ //\r
+ // Call registered notification function passing in the time since the last\r
+ // interrupt in 100 ns units.\r
+ // \r
+ mTimerNotifyFunction (TimerPeriod);\r
+ }\r
+}\r
+\r
+/**\r
+ This function registers the handler NotifyFunction so it is called every time\r
+ the timer interrupt fires. It also passes the amount of time since the last\r
+ handler call to the NotifyFunction. If NotifyFunction is NULL, then the\r
+ handler is unregistered. If the handler is registered, then EFI_SUCCESS is\r
+ returned. If the CPU does not support registering a timer interrupt handler,\r
+ then EFI_UNSUPPORTED is returned. If an attempt is made to register a handler\r
+ when a handler is already registered, then EFI_ALREADY_STARTED is returned.\r
+ If an attempt is made to unregister a handler when a handler is not registered,\r
+ then EFI_INVALID_PARAMETER is returned. If an error occurs attempting to\r
+ register the NotifyFunction with the timer interrupt, then EFI_DEVICE_ERROR\r
+ is returned.\r
+\r
+ @param This The EFI_TIMER_ARCH_PROTOCOL instance.\r
+ @param NotifyFunction The function to call when a timer interrupt fires. \r
+ This function executes at TPL_HIGH_LEVEL. The DXE \r
+ Core will register a handler for the timer interrupt, \r
+ so it can know how much time has passed. This \r
+ information is used to signal timer based events. \r
+ NULL will unregister the handler.\r
+\r
+ @retval EFI_SUCCESS The timer handler was registered.\r
+ @retval EFI_UNSUPPORTED The platform does not support timer interrupts.\r
+ @retval EFI_ALREADY_STARTED NotifyFunction is not NULL, and a handler is already\r
+ registered.\r
+ @retval EFI_INVALID_PARAMETER NotifyFunction is NULL, and a handler was not\r
+ previously registered.\r
+ @retval EFI_DEVICE_ERROR The timer handler could not be registered.\r
+\r
+**/\r
+EFI_STATUS\r
+EFIAPI\r
+TimerDriverRegisterHandler (\r
+ IN EFI_TIMER_ARCH_PROTOCOL *This,\r
+ IN EFI_TIMER_NOTIFY NotifyFunction\r
+ )\r
+{\r
+ //\r
+ // Check for invalid parameters\r
+ //\r
+ if (NotifyFunction == NULL && mTimerNotifyFunction == NULL) {\r
+ return EFI_INVALID_PARAMETER;\r
+ }\r
+ if (NotifyFunction != NULL && mTimerNotifyFunction != NULL) {\r
+ return EFI_ALREADY_STARTED;\r
+ }\r
+\r
+ //\r
+ // Cache the registered notification function\r
+ //\r
+ mTimerNotifyFunction = NotifyFunction;\r
+\r
+ return EFI_SUCCESS;\r
+}\r
+\r
+/**\r
+ This function adjusts the period of timer interrupts to the value specified\r
+ by TimerPeriod. If the timer period is updated, then the selected timer\r
+ period is stored in EFI_TIMER.TimerPeriod, and EFI_SUCCESS is returned. If\r
+ the timer hardware is not programmable, then EFI_UNSUPPORTED is returned.\r
+ If an error occurs while attempting to update the timer period, then the\r
+ timer hardware will be put back in its state prior to this call, and\r
+ EFI_DEVICE_ERROR is returned. If TimerPeriod is 0, then the timer interrupt\r
+ is disabled. This is not the same as disabling the CPU's interrupts.\r
+ Instead, it must either turn off the timer hardware, or it must adjust the\r
+ interrupt controller so that a CPU interrupt is not generated when the timer\r
+ interrupt fires.\r
+\r
+ @param This The EFI_TIMER_ARCH_PROTOCOL instance.\r
+ @param TimerPeriod The rate to program the timer interrupt in 100 nS units.\r
+ If the timer hardware is not programmable, then \r
+ EFI_UNSUPPORTED is returned. If the timer is programmable, \r
+ then the timer period will be rounded up to the nearest \r
+ timer period that is supported by the timer hardware. \r
+ If TimerPeriod is set to 0, then the timer interrupts \r
+ will be disabled.\r
+\r
+ @retval EFI_SUCCESS The timer period was changed.\r
+ @retval EFI_UNSUPPORTED The platform cannot change the period of the timer interrupt.\r
+ @retval EFI_DEVICE_ERROR The timer period could not be changed due to a device error.\r
+\r
+**/\r
+EFI_STATUS\r
+EFIAPI\r
+TimerDriverSetTimerPeriod (\r
+ IN EFI_TIMER_ARCH_PROTOCOL *This,\r
+ IN UINT64 TimerPeriod\r
+ )\r
+{\r
+ UINT64 TimerCount;\r
+\r
+ //\r
+ // Disable HPET timer when adjusting the timer period\r
+ //\r
+ HpetEnable (FALSE);\r
+ \r
+ if (TimerPeriod == 0) {\r
+ //\r
+ // If TimerPeriod is 0, then mask HPET Timer interrupts\r
+ //\r
+ \r
+ if (mTimerConfiguration.Bits.MsiInterruptCapablity != 0) {\r
+ //\r
+ // Disable HPET MSI interrupt generation\r
+ //\r
+ mTimerConfiguration.Bits.MsiInterruptEnable = 0;\r
+ } else {\r
+ //\r
+ // Disable I/O APIC Interrupt\r
+ //\r
+ IoApicEnableInterrupt (mTimerIrq, FALSE);\r
+ }\r
+ \r
+ //\r
+ // Disable HPET timer interrupt \r
+ //\r
+ mTimerConfiguration.Bits.InterruptEnable = 0;\r
+ HpetWrite (HPET_TIMER_CONFIGURATION_OFFSET + mTimerIndex * HPET_TIMER_STRIDE, mTimerConfiguration.Uint64);\r
+ } else {\r
+ //\r
+ // Convert TimerPeriod to femtoseconds and divide by the number if femtoseconds \r
+ // per tick of the HPET counter to determine the number of HPET counter ticks\r
+ // in TimerPeriod 100 ns units.\r
+ // \r
+ TimerCount = DivU64x32 (\r
+ MultU64x32 (TimerPeriod, 100000000),\r
+ mHpetGeneralCapabilities.Bits.CounterClockPeriod\r
+ );\r
+\r
+ //\r
+ // Program the HPET Comparator with the number of ticks till the next interrupt\r
+ //\r
+ HpetWrite (HPET_TIMER_COMPARATOR_OFFSET + mTimerIndex * HPET_TIMER_STRIDE, TimerCount);\r
+\r
+ //\r
+ // Capture the number of ticks since the last HPET Timer interrupt before \r
+ // clearing the main counter. This value will be used in the next HPET\r
+ // timer interrupt handler to compute the total amount of time since the\r
+ // last HPET timer interrupt\r
+ // \r
+ mTimerAccumulator = HpetRead (HPET_MAIN_COUNTER_OFFSET);\r
+ \r
+ //\r
+ // If the number of ticks since the last timer interrupt is greater than the\r
+ // timer period, reduce the number of ticks till the next interrupt to 1, so \r
+ // a timer interrupt will be generated as soon as the HPET counter is enabled.\r
+ // \r
+ if (mTimerAccumulator >= TimerCount) {\r
+ HpetWrite (HPET_MAIN_COUNTER_OFFSET, TimerCount - 1);\r
+ //\r
+ // Adjust the accumulator down by TimerCount ticks because TimerCount\r
+ // ticks will be added to the accumulator on the next interrupt\r
+ //\r
+ mTimerAccumulator -= TimerCount;\r
+ }\r
+ \r
+ //\r
+ // Enable HPET Timer interrupt generation\r
+ //\r
+ if (mTimerConfiguration.Bits.MsiInterruptCapablity != 0) {\r
+ //\r
+ // Enable HPET MSI Interrupt\r
+ //\r
+ mTimerConfiguration.Bits.MsiInterruptEnable = 1;\r
+ } else {\r
+ //\r
+ // Enable timer interrupt through I/O APIC\r
+ //\r
+ IoApicEnableInterrupt (mTimerIrq, TRUE);\r
+ }\r
+\r
+ //\r
+ // Enable HPET Interrupt Generation\r
+ //\r
+ mTimerConfiguration.Bits.InterruptEnable = 1;\r
+ HpetWrite (HPET_TIMER_CONFIGURATION_OFFSET + mTimerIndex * HPET_TIMER_STRIDE, mTimerConfiguration.Uint64);\r
+ }\r
+ \r
+ //\r
+ // Save the new timer period\r
+ //\r
+ mTimerPeriod = TimerPeriod;\r
+\r
+ //\r
+ // Enable the HPET counter once new timer period has been established\r
+ // The HPET counter should run even if the HPET Timer interrupts are\r
+ // disabled. This is used to account for time passed while the interrupt\r
+ // is disabled.\r
+ //\r
+ HpetEnable (TRUE);\r
+ \r
+ return EFI_SUCCESS;\r
+}\r
+\r
+/**\r
+ This function retrieves the period of timer interrupts in 100 ns units,\r
+ returns that value in TimerPeriod, and returns EFI_SUCCESS. If TimerPeriod\r
+ is NULL, then EFI_INVALID_PARAMETER is returned. If a TimerPeriod of 0 is\r
+ returned, then the timer is currently disabled.\r
+\r
+ @param This The EFI_TIMER_ARCH_PROTOCOL instance.\r
+ @param TimerPeriod A pointer to the timer period to retrieve in 100 ns units.\r
+ If 0 is returned, then the timer is currently disabled.\r
+\r
+ @retval EFI_SUCCESS The timer period was returned in TimerPeriod.\r
+ @retval EFI_INVALID_PARAMETER TimerPeriod is NULL.\r
+\r
+**/\r
+EFI_STATUS\r
+EFIAPI\r
+TimerDriverGetTimerPeriod (\r
+ IN EFI_TIMER_ARCH_PROTOCOL *This,\r
+ OUT UINT64 *TimerPeriod\r
+ )\r
+{\r
+ if (TimerPeriod == NULL) {\r
+ return EFI_INVALID_PARAMETER;\r
+ }\r
+\r
+ *TimerPeriod = mTimerPeriod;\r
+\r
+ return EFI_SUCCESS;\r
+}\r
+\r
+/**\r
+ This function generates a soft timer interrupt. If the platform does not support soft\r
+ timer interrupts, then EFI_UNSUPPORTED is returned. Otherwise, EFI_SUCCESS is returned.\r
+ If a handler has been registered through the EFI_TIMER_ARCH_PROTOCOL.RegisterHandler()\r
+ service, then a soft timer interrupt will be generated. If the timer interrupt is\r
+ enabled when this service is called, then the registered handler will be invoked. The\r
+ registered handler should not be able to distinguish a hardware-generated timer\r
+ interrupt from a software-generated timer interrupt.\r
+\r
+ @param This The EFI_TIMER_ARCH_PROTOCOL instance.\r
+\r
+ @retval EFI_SUCCESS The soft timer interrupt was generated.\r
+ @retval EFI_UNSUPPORTEDT The platform does not support the generation of soft \r
+ timer interrupts.\r
+\r
+**/\r
+EFI_STATUS\r
+EFIAPI\r
+TimerDriverGenerateSoftInterrupt (\r
+ IN EFI_TIMER_ARCH_PROTOCOL *This\r
+ )\r
+{\r
+ EFI_TPL Tpl;\r
+ UINT64 TimerPeriod;\r
+\r
+ //\r
+ // Disable interrupts\r
+ // \r
+ Tpl = gBS->RaiseTPL (TPL_HIGH_LEVEL);\r
+\r
+ //\r
+ // Read the current HPET main counter value\r
+ //\r
+ mTimerAccumulator += HpetRead (HPET_MAIN_COUNTER_OFFSET);\r
+\r
+ //\r
+ // Reset HPET main counter to 0\r
+ //\r
+ HpetWrite (HPET_MAIN_COUNTER_OFFSET, 0);\r
+\r
+ //\r
+ // Check to see if there is a registered notification function\r
+ //\r
+ if (mTimerNotifyFunction != NULL) {\r
+ //\r
+ // Compute time since last interrupt in 100 ns units (10 ^ -7) \r
+ //\r
+ TimerPeriod = DivU64x32 (\r
+ MultU64x32 (\r
+ mTimerAccumulator, \r
+ mHpetGeneralCapabilities.Bits.CounterClockPeriod\r
+ ), \r
+ 100000000\r
+ );\r
+ mTimerAccumulator = 0;\r
+ \r
+ //\r
+ // Call registered notification function passing in the time since the last\r
+ // interrupt in 100 ns units.\r
+ // \r
+ mTimerNotifyFunction (TimerPeriod);\r
+ }\r
+\r
+ //\r
+ // Restore interrupts\r
+ // \r
+ gBS->RestoreTPL (Tpl);\r
+ \r
+ return EFI_SUCCESS;\r
+}\r
+\r
+/**\r
+ Initialize the Timer Architectural Protocol driver\r
+\r
+ @param ImageHandle ImageHandle of the loaded driver\r
+ @param SystemTable Pointer to the System Table\r
+\r
+ @retval EFI_SUCCESS Timer Architectural Protocol created\r
+ @retval EFI_OUT_OF_RESOURCES Not enough resources available to initialize driver.\r
+ @retval EFI_DEVICE_ERROR A device error occured attempting to initialize the driver.\r
+\r
+**/\r
+EFI_STATUS\r
+EFIAPI\r
+TimerDriverInitialize (\r
+ IN EFI_HANDLE ImageHandle,\r
+ IN EFI_SYSTEM_TABLE *SystemTable\r
+ )\r
+{\r
+ EFI_STATUS Status;\r
+ UINTN TimerIndex;\r
+ UINTN MsiTimerIndex;\r
+ HPET_TIMER_MSI_ROUTE_REGISTER HpetTimerMsiRoute;\r
+\r
+ DEBUG ((DEBUG_INFO, "Init HPET Timer Driver\n"));\r
+\r
+ //\r
+ // Make sure the Timer Architectural Protocol is not already installed in the system\r
+ //\r
+ ASSERT_PROTOCOL_ALREADY_INSTALLED (NULL, &gEfiTimerArchProtocolGuid);\r
+\r
+ //\r
+ // Find the CPU architectural protocol.\r
+ //\r
+ Status = gBS->LocateProtocol (&gEfiCpuArchProtocolGuid, NULL, (VOID **) &mCpu);\r
+ ASSERT_EFI_ERROR (Status);\r
+\r
+ //\r
+ // Retrieve HPET Capabilities and Configuration Information\r
+ // \r
+ mHpetGeneralCapabilities.Uint64 = HpetRead (HPET_GENERAL_CAPABILITIES_ID_OFFSET);\r
+ mHpetGeneralConfiguration.Uint64 = HpetRead (HPET_GENERAL_CONFIGURATION_OFFSET);\r
+ \r
+ //\r
+ // If Revision is not valid, then ASSERT() and unload the driver because the HPET \r
+ // device is not present.\r
+ // \r
+ ASSERT (mHpetGeneralCapabilities.Uint64 != 0);\r
+ ASSERT (mHpetGeneralCapabilities.Uint64 != 0xFFFFFFFFFFFFFFFFULL);\r
+ if (mHpetGeneralCapabilities.Uint64 == 0 || mHpetGeneralCapabilities.Uint64 == 0xFFFFFFFFFFFFFFFFULL) {\r
+ DEBUG ((DEBUG_ERROR, "HPET device is not present. Unload HPET driver.\n"));\r
+ return EFI_DEVICE_ERROR;\r
+ }\r
+\r
+ //\r
+ // Force the HPET timer to be disabled while setting everything up\r
+ //\r
+ HpetEnable (FALSE);\r
+\r
+ //\r
+ // Dump HPET Configuration Information\r
+ // \r
+ DEBUG_CODE (\r
+ DEBUG ((DEBUG_INFO, "HPET Base Address = %08x\n", PcdGet32 (PcdHpetBaseAddress)));\r
+ DEBUG ((DEBUG_INFO, " HPET_GENERAL_CAPABILITIES_ID = %016lx\n", mHpetGeneralCapabilities));\r
+ DEBUG ((DEBUG_INFO, " HPET_GENERAL_CONFIGURATION = %016lx\n", mHpetGeneralConfiguration.Uint64));\r
+ DEBUG ((DEBUG_INFO, " HPET_GENERAL_INTERRUPT_STATUS = %016lx\n", HpetRead (HPET_GENERAL_INTERRUPT_STATUS_OFFSET)));\r
+ DEBUG ((DEBUG_INFO, " HPET_MAIN_COUNTER = %016lx\n", HpetRead (HPET_MAIN_COUNTER_OFFSET)));\r
+ DEBUG ((DEBUG_INFO, " HPET Main Counter Period = %d (fs)\n", mHpetGeneralCapabilities.Bits.CounterClockPeriod));\r
+ for (TimerIndex = 0; TimerIndex <= mHpetGeneralCapabilities.Bits.NumberOfTimers; TimerIndex++) {\r
+ DEBUG ((DEBUG_INFO, " HPET_TIMER%d_CONFIGURATION = %016lx\n", TimerIndex, HpetRead (HPET_TIMER_CONFIGURATION_OFFSET + TimerIndex * HPET_TIMER_STRIDE)));\r
+ DEBUG ((DEBUG_INFO, " HPET_TIMER%d_COMPARATOR = %016lx\n", TimerIndex, HpetRead (HPET_TIMER_COMPARATOR_OFFSET + TimerIndex * HPET_TIMER_STRIDE)));\r
+ DEBUG ((DEBUG_INFO, " HPET_TIMER%d_MSI_ROUTE = %016lx\n", TimerIndex, HpetRead (HPET_TIMER_MSI_ROUTE_OFFSET + TimerIndex * HPET_TIMER_STRIDE)));\r
+ }\r
+ );\r
+ \r
+ //\r
+ // Determine the interrupt mode to use for the HPET Timer. \r
+ // Look for MSI first, then unused PIC mode interrupt, then I/O APIC mode interrupt\r
+ // \r
+ MsiTimerIndex = HPET_INVALID_TIMER_INDEX;\r
+ mTimerIndex = HPET_INVALID_TIMER_INDEX;\r
+ for (TimerIndex = 0; TimerIndex <= mHpetGeneralCapabilities.Bits.NumberOfTimers; TimerIndex++) {\r
+ //\r
+ // Read the HPET Timer Capabilities and Configuration register\r
+ //\r
+ mTimerConfiguration.Uint64 = HpetRead (HPET_TIMER_CONFIGURATION_OFFSET + TimerIndex * HPET_TIMER_STRIDE);\r
+ \r
+ //\r
+ // Check to see if this HPET Timer supports MSI \r
+ //\r
+ if (mTimerConfiguration.Bits.MsiInterruptCapablity != 0) {\r
+ //\r
+ // Save the index of the first HPET Timer that supports MSI interrupts\r
+ //\r
+ if (MsiTimerIndex == HPET_INVALID_TIMER_INDEX) {\r
+ MsiTimerIndex = TimerIndex;\r
+ }\r
+ }\r
+ \r
+ //\r
+ // Check to see if this HPET Timer supports I/O APIC interrupts\r
+ //\r
+ if (mTimerConfiguration.Bits.InterruptRouteCapability != 0) {\r
+ //\r
+ // Save the index of the first HPET Timer that supports I/O APIC interrupts\r
+ //\r
+ if (mTimerIndex == HPET_INVALID_TIMER_INDEX) {\r
+ mTimerIndex = TimerIndex;\r
+ mTimerIrq = (UINT32)LowBitSet32 (mTimerConfiguration.Bits.InterruptRouteCapability);\r
+ }\r
+ }\r
+ }\r
+\r
+ if (FeaturePcdGet (PcdHpetMsiEnable) && MsiTimerIndex != HPET_INVALID_TIMER_INDEX) {\r
+ //\r
+ // Use MSI interrupt if supported\r
+ //\r
+ mTimerIndex = MsiTimerIndex;\r
+\r
+ //\r
+ // Program MSI Address and MSI Data values in the selected HPET Timer\r
+ //\r
+ HpetTimerMsiRoute.Bits.Address = GetApicMsiAddress ();\r
+ HpetTimerMsiRoute.Bits.Value = (UINT32)GetApicMsiValue (PcdGet8 (PcdHpetLocalApicVector), LOCAL_APIC_DELIVERY_MODE_LOWEST_PRIORITY, FALSE, FALSE);\r
+ HpetWrite (HPET_TIMER_MSI_ROUTE_OFFSET + mTimerIndex * HPET_TIMER_STRIDE, HpetTimerMsiRoute.Uint64);\r
+\r
+ //\r
+ // Read the HPET Timer Capabilities and Configuration register and initialize for MSI mode\r
+ // Clear LevelTriggeredInterrupt to use edge triggered interrupts when in MSI mode\r
+ //\r
+ mTimerConfiguration.Uint64 = HpetRead (HPET_TIMER_CONFIGURATION_OFFSET + mTimerIndex * HPET_TIMER_STRIDE);\r
+ mTimerConfiguration.Bits.LevelTriggeredInterrupt = 0;\r
+ } else {\r
+ //\r
+ // If no HPET timers support MSI or I/O APIC modes, then ASSERT() and unload the driver.\r
+ //\r
+ ASSERT (mTimerIndex != HPET_INVALID_TIMER_INDEX);\r
+ if (mTimerIndex == HPET_INVALID_TIMER_INDEX) {\r
+ DEBUG ((DEBUG_ERROR, "No HPET timers support MSI or I/O APIC mode. Unload HPET driver.\n"));\r
+ return EFI_DEVICE_ERROR;\r
+ }\r
+ \r
+ //\r
+ // Initialize I/O APIC entry for HPET Timer Interrupt\r
+ // Fixed Delivery Mode, Level Triggered, Asserted Low\r
+ //\r
+ IoApicConfigureInterrupt (mTimerIrq, PcdGet8 (PcdHpetLocalApicVector), IO_APIC_DELIVERY_MODE_LOWEST_PRIORITY, TRUE, FALSE);\r
+\r
+ //\r
+ // Read the HPET Timer Capabilities and Configuration register and initialize for I/O APIC mode\r
+ // Clear MsiInterruptCapability to force rest of driver to use I/O APIC mode\r
+ // Set LevelTriggeredInterrupt to use level triggered interrupts when in I/O APIC mode\r
+ // Set InterruptRoute field based in mTimerIrq\r
+ //\r
+ mTimerConfiguration.Uint64 = HpetRead (HPET_TIMER_CONFIGURATION_OFFSET + mTimerIndex * HPET_TIMER_STRIDE);\r
+ mTimerConfiguration.Bits.MsiInterruptCapablity = 0;\r
+ mTimerConfiguration.Bits.LevelTriggeredInterrupt = 1;\r
+ mTimerConfiguration.Bits.InterruptRoute = mTimerIrq;\r
+ }\r
+\r
+ //\r
+ // Configure the selected HPET Timer with settings common to both MSI mode and I/O APIC mode\r
+ // Clear InterruptEnable to keep interrupts disabled until full init is complete \r
+ // Clear PeriodicInterruptEnable to use one-shot mode \r
+ // Configure as a 32-bit counter \r
+ //\r
+ mTimerConfiguration.Bits.InterruptEnable = 0;\r
+ mTimerConfiguration.Bits.PeriodicInterruptEnable = 0;\r
+ mTimerConfiguration.Bits.CounterSizeEnable = 0;\r
+ HpetWrite (HPET_TIMER_CONFIGURATION_OFFSET + mTimerIndex * HPET_TIMER_STRIDE, mTimerConfiguration.Uint64);\r
+\r
+ //\r
+ // Install interrupt handler for selected HPET Timer\r
+ //\r
+ Status = mCpu->RegisterInterruptHandler (mCpu, PcdGet8 (PcdHpetLocalApicVector), TimerInterruptHandler);\r
+ ASSERT_EFI_ERROR (Status);\r
+ if (EFI_ERROR (Status)) {\r
+ DEBUG ((DEBUG_ERROR, "Unable to register HPET interrupt with CPU Arch Protocol. Unload HPET driver.\n"));\r
+ return EFI_DEVICE_ERROR;\r
+ }\r
+\r
+ //\r
+ // Force the HPET Timer to be enabled at its default period\r
+ //\r
+ Status = TimerDriverSetTimerPeriod (&mTimer, PcdGet64 (PcdHpetDefaultTimerPeriod));\r
+ ASSERT_EFI_ERROR (Status);\r
+ if (EFI_ERROR (Status)) {\r
+ DEBUG ((DEBUG_ERROR, "Unable to set HPET default timer rate. Unload HPET driver.\n"));\r
+ return EFI_DEVICE_ERROR;\r
+ }\r
+\r
+ //\r
+ // Show state of enabled HPET timer\r
+ //\r
+ DEBUG_CODE (\r
+ if (mTimerConfiguration.Bits.MsiInterruptCapablity != 0) {\r
+ DEBUG ((DEBUG_INFO, "HPET Interrupt Mode MSI\n"));\r
+ } else {\r
+ DEBUG ((DEBUG_INFO, "HPET Interrupt Mode I/O APIC\n"));\r
+ DEBUG ((DEBUG_INFO, "HPET I/O APIC IRQ = %02x\n", mTimerIrq));\r
+ } \r
+ DEBUG ((DEBUG_INFO, "HPET Interrupt Vector = %02x\n", PcdGet8 (PcdHpetLocalApicVector)));\r
+ DEBUG ((DEBUG_INFO, "HPET_TIMER%d_CONFIGURATION = %016lx\n", mTimerIndex, HpetRead (HPET_TIMER_CONFIGURATION_OFFSET + mTimerIndex * HPET_TIMER_STRIDE)));\r
+ DEBUG ((DEBUG_INFO, "HPET_TIMER%d_COMPARATOR = %016lx\n", mTimerIndex, HpetRead (HPET_TIMER_COMPARATOR_OFFSET + mTimerIndex * HPET_TIMER_STRIDE)));\r
+ DEBUG ((DEBUG_INFO, "HPET_TIMER%d_MSI_ROUTE = %016lx\n", mTimerIndex, HpetRead (HPET_TIMER_MSI_ROUTE_OFFSET + mTimerIndex * HPET_TIMER_STRIDE)));\r
+\r
+ //\r
+ // Wait for a few timer interrupts to fire before continuing\r
+ // \r
+ while (mNumTicks < 10);\r
+ );\r
+ \r
+ //\r
+ // Install the Timer Architectural Protocol onto a new handle\r
+ //\r
+ Status = gBS->InstallMultipleProtocolInterfaces (\r
+ &mTimerHandle,\r
+ &gEfiTimerArchProtocolGuid, &mTimer,\r
+ NULL\r
+ );\r
+ ASSERT_EFI_ERROR (Status);\r
+\r
+ return Status;\r
+}\r