]> git.proxmox.com Git - mirror_edk2.git/blobdiff - PcAtChipsetPkg/HpetTimerDxe/HpetTimer.c
Add generic HPET Timer DXE Driver and support libraries
[mirror_edk2.git] / PcAtChipsetPkg / HpetTimerDxe / HpetTimer.c
diff --git a/PcAtChipsetPkg/HpetTimerDxe/HpetTimer.c b/PcAtChipsetPkg/HpetTimerDxe/HpetTimer.c
new file mode 100644 (file)
index 0000000..025d504
--- /dev/null
@@ -0,0 +1,860 @@
+/** @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