]> git.proxmox.com Git - mirror_edk2.git/blobdiff - ArmPkg/Drivers/TimerDxe/TimerDxe.c
ArmPkg/TimerDxe: Add ISB for timer compare value reload
[mirror_edk2.git] / ArmPkg / Drivers / TimerDxe / TimerDxe.c
index cbc34e8e412123515087f7e3e0080fbb93d84b44..a3202fa056f3026cd1c6ab3781649923d848d62c 100644 (file)
@@ -1,15 +1,15 @@
 /** @file\r
   Timer Architecture Protocol driver of the ARM flavor\r
 \r
 /** @file\r
   Timer Architecture Protocol driver of the ARM flavor\r
 \r
-  Copyright (c) 2011 ARM Ltd. All rights reserved.<BR>\r
-  \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
+  Copyright (c) 2011-2013 ARM Ltd. All rights reserved.<BR>\r
 \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
+  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
 \r
 **/\r
 \r
@@ -24,7 +24,7 @@
 #include <Library/UefiLib.h>\r
 #include <Library/PcdLib.h>\r
 #include <Library/IoLib.h>\r
 #include <Library/UefiLib.h>\r
 #include <Library/PcdLib.h>\r
 #include <Library/IoLib.h>\r
-#include <Library/ArmV7ArchTimerLib.h>\r
+#include <Library/ArmGenericTimerCounterLib.h>\r
 \r
 #include <Protocol/Timer.h>\r
 #include <Protocol/HardwareInterrupt.h>\r
 \r
 #include <Protocol/Timer.h>\r
 #include <Protocol/HardwareInterrupt.h>\r
@@ -35,21 +35,25 @@ EFI_EVENT             EfiExitBootServicesEvent = (EFI_EVENT)NULL;
 \r
 // The current period of the timer interrupt\r
 UINT64 mTimerPeriod = 0;\r
 \r
 // The current period of the timer interrupt\r
 UINT64 mTimerPeriod = 0;\r
+// The latest Timer Tick calculated for mTimerPeriod\r
+UINT64 mTimerTicks = 0;\r
+// Number of elapsed period since the last Timer interrupt\r
+UINT64 mElapsedPeriod = 1;\r
 \r
 // Cached copy of the Hardware Interrupt protocol instance\r
 EFI_HARDWARE_INTERRUPT_PROTOCOL *gInterrupt = NULL;\r
 \r
 /**\r
 \r
 // Cached copy of the Hardware Interrupt protocol instance\r
 EFI_HARDWARE_INTERRUPT_PROTOCOL *gInterrupt = NULL;\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
+  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
   is returned.\r
 \r
   @param  This             The EFI_TIMER_ARCH_PROTOCOL instance.\r
@@ -97,22 +101,22 @@ ExitBootServicesEvent (
   IN VOID       *Context\r
   )\r
 {\r
   IN VOID       *Context\r
   )\r
 {\r
-  ArmArchTimerDisableTimer ();\r
+  ArmGenericTimerDisableTimer ();\r
 }\r
 \r
 /**\r
 \r
 }\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
+  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. If\r
 \r
   @param  This             The EFI_TIMER_ARCH_PROTOCOL instance.\r
   @param  TimerPeriod      The rate to program the timer interrupt in 100 nS units. If\r
@@ -135,32 +139,51 @@ TimerDriverSetTimerPeriod (
   IN UINT64                   TimerPeriod\r
   )\r
 {\r
   IN UINT64                   TimerPeriod\r
   )\r
 {\r
+  UINT64      CounterValue;\r
   UINT64      TimerTicks;\r
   UINT64      TimerTicks;\r
-  \r
+  EFI_TPL     OriginalTPL;\r
+\r
   // Always disable the timer\r
   // Always disable the timer\r
-  ArmArchTimerDisableTimer ();\r
+  ArmGenericTimerDisableTimer ();\r
 \r
   if (TimerPeriod != 0) {\r
 \r
   if (TimerPeriod != 0) {\r
-    // Convert TimerPeriod to micro sec units\r
-    TimerTicks = DivU64x32 (TimerPeriod, 10);\r
+    // mTimerTicks = TimerPeriod in 1ms unit x Frequency.10^-3\r
+    //             = TimerPeriod.10^-4 x Frequency.10^-3\r
+    //             = (TimerPeriod x Frequency) x 10^-7\r
+    TimerTicks = MultU64x32 (TimerPeriod, ArmGenericTimerGetTimerFreq ());\r
+    TimerTicks = DivU64x32 (TimerTicks, 10000000U);\r
+\r
+    // Raise TPL to update the mTimerTicks and mTimerPeriod to ensure these values\r
+    // are coherent in the interrupt handler\r
+    OriginalTPL = gBS->RaiseTPL (TPL_HIGH_LEVEL);\r
+\r
+    mTimerTicks    = TimerTicks;\r
+    mTimerPeriod   = TimerPeriod;\r
+    mElapsedPeriod = 1;\r
 \r
 \r
-    TimerTicks = MultU64x32 (TimerTicks, (PcdGet32(PcdArmArchTimerFreqInHz)/1000000));\r
+    gBS->RestoreTPL (OriginalTPL);\r
 \r
 \r
-    ArmArchTimerSetTimerVal((UINTN)TimerTicks);\r
+    // Get value of the current timer\r
+    CounterValue = ArmGenericTimerGetSystemCount ();\r
+    // Set the interrupt in Current Time + mTimerTick\r
+    ArmGenericTimerSetCompareVal (CounterValue + mTimerTicks);\r
 \r
     // Enable the timer\r
 \r
     // Enable the timer\r
-    ArmArchTimerEnableTimer ();\r
+    ArmGenericTimerEnableTimer ();\r
+  } else {\r
+    // Save the new timer period\r
+    mTimerPeriod   = TimerPeriod;\r
+    // Reset the elapsed period\r
+    mElapsedPeriod = 1;\r
   }\r
 \r
   }\r
 \r
-  // Save the new timer period\r
-  mTimerPeriod = TimerPeriod;\r
   return EFI_SUCCESS;\r
 }\r
 \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
+  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
   returned, then the timer is currently disabled.\r
 \r
   @param  This             The EFI_TIMER_ARCH_PROTOCOL instance.\r
@@ -188,12 +211,12 @@ TimerDriverGetTimerPeriod (
 }\r
 \r
 /**\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
+  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
   interrupt from a software-generated timer interrupt.\r
 \r
   @param  This             The EFI_TIMER_ARCH_PROTOCOL instance.\r
@@ -273,6 +296,8 @@ TimerInterruptHandler (
   )\r
 {\r
   EFI_TPL      OriginalTPL;\r
   )\r
 {\r
   EFI_TPL      OriginalTPL;\r
+  UINT64       CurrentValue;\r
+  UINT64       CompareValue;\r
 \r
   //\r
   // DXE core uses this callback for the EFI timer tick. The DXE core uses locks\r
 \r
   //\r
   // DXE core uses this callback for the EFI timer tick. The DXE core uses locks\r
@@ -281,23 +306,41 @@ TimerInterruptHandler (
   //\r
   OriginalTPL = gBS->RaiseTPL (TPL_HIGH_LEVEL);\r
 \r
   //\r
   OriginalTPL = gBS->RaiseTPL (TPL_HIGH_LEVEL);\r
 \r
-  // Check if the timer interrupt is active\r
-  if ((ArmArchTimerGetTimerCtrlReg () ) & ARM_ARCH_TIMER_ISTATUS) {\r
+  // Signal end of interrupt early to help avoid losing subsequent ticks\r
+  // from long duration handlers\r
+  gInterrupt->EndOfInterrupt (gInterrupt, Source);\r
 \r
 \r
-    // Signal end of interrupt early to help avoid losing subsequent ticks from long duration handlers\r
-    gInterrupt->EndOfInterrupt (gInterrupt, Source);\r
+  // Check if the timer interrupt is active\r
+  if ((ArmGenericTimerGetTimerCtrlReg () ) & ARM_ARCH_TIMER_ISTATUS) {\r
 \r
     if (mTimerNotifyFunction) {\r
 \r
     if (mTimerNotifyFunction) {\r
-      mTimerNotifyFunction (mTimerPeriod);\r
+      mTimerNotifyFunction (mTimerPeriod * mElapsedPeriod);\r
     }\r
 \r
     }\r
 \r
+    //\r
     // Reload the Timer\r
     // Reload the Timer\r
-    TimerDriverSetTimerPeriod (&gTimer, FixedPcdGet32(PcdTimerPeriod));\r
+    //\r
+\r
+    // Get current counter value\r
+    CurrentValue = ArmGenericTimerGetSystemCount ();\r
+    // Get the counter value to compare with\r
+    CompareValue = ArmGenericTimerGetCompareVal ();\r
+\r
+    // This loop is needed in case we missed interrupts (eg: case when the interrupt handling\r
+    // has taken longer than mTickPeriod).\r
+    // Note: Physical Counter is counting up\r
+    mElapsedPeriod = 0;\r
+    do {\r
+      CompareValue += mTimerTicks;\r
+      mElapsedPeriod++;\r
+    } while (CompareValue < CurrentValue);\r
+\r
+    // Set next compare value\r
+    ArmGenericTimerSetCompareVal (CompareValue);\r
+    ArmGenericTimerEnableTimer ();\r
+    ArmInstructionSynchronizationBarrier ();\r
   }\r
 \r
   }\r
 \r
-  // Enable timer interrupts\r
-  gInterrupt->EnableInterruptSource (gInterrupt, Source);\r
-\r
   gBS->RestoreTPL (OriginalTPL);\r
 }\r
 \r
   gBS->RestoreTPL (OriginalTPL);\r
 }\r
 \r
@@ -324,7 +367,8 @@ TimerInitialize (
 {\r
   EFI_HANDLE  Handle = NULL;\r
   EFI_STATUS  Status;\r
 {\r
   EFI_HANDLE  Handle = NULL;\r
   EFI_STATUS  Status;\r
-  UINTN TimerCtrlReg;\r
+  UINTN       TimerCtrlReg;\r
+  UINT32      TimerHypIntrNum;\r
 \r
   if (ArmIsArchTimerImplemented () == 0) {\r
     DEBUG ((EFI_D_ERROR, "ARM Architectural Timer is not available in the CPU, hence cann't use this Driver \n"));\r
 \r
   if (ArmIsArchTimerImplemented () == 0) {\r
     DEBUG ((EFI_D_ERROR, "ARM Architectural Timer is not available in the CPU, hence cann't use this Driver \n"));\r
@@ -336,6 +380,10 @@ TimerInitialize (
   ASSERT_EFI_ERROR (Status);\r
 \r
   // Disable the timer\r
   ASSERT_EFI_ERROR (Status);\r
 \r
   // Disable the timer\r
+  TimerCtrlReg = ArmGenericTimerGetTimerCtrlReg ();\r
+  TimerCtrlReg |= ARM_ARCH_TIMER_IMASK;\r
+  TimerCtrlReg &= ~ARM_ARCH_TIMER_ENABLE;\r
+  ArmGenericTimerSetTimerCtrlReg (TimerCtrlReg);\r
   Status = TimerDriverSetTimerPeriod (&gTimer, 0);\r
   ASSERT_EFI_ERROR (Status);\r
 \r
   Status = TimerDriverSetTimerPeriod (&gTimer, 0);\r
   ASSERT_EFI_ERROR (Status);\r
 \r
@@ -343,17 +391,25 @@ TimerInitialize (
   // Note: Because it is not possible to determine the security state of the\r
   // CPU dynamically, we just install interrupt handler for both sec and non-sec\r
   // timer PPI\r
   // Note: Because it is not possible to determine the security state of the\r
   // CPU dynamically, we just install interrupt handler for both sec and non-sec\r
   // timer PPI\r
+  Status = gInterrupt->RegisterInterruptSource (gInterrupt, PcdGet32 (PcdArmArchTimerVirtIntrNum), TimerInterruptHandler);\r
+  ASSERT_EFI_ERROR (Status);\r
+\r
+  //\r
+  // The hypervisor timer interrupt may be omitted by implementations that\r
+  // execute under virtualization.\r
+  //\r
+  TimerHypIntrNum = PcdGet32 (PcdArmArchTimerHypIntrNum);\r
+  if (TimerHypIntrNum != 0) {\r
+    Status = gInterrupt->RegisterInterruptSource (gInterrupt, TimerHypIntrNum, TimerInterruptHandler);\r
+    ASSERT_EFI_ERROR (Status);\r
+  }\r
+\r
   Status = gInterrupt->RegisterInterruptSource (gInterrupt, PcdGet32 (PcdArmArchTimerSecIntrNum), TimerInterruptHandler);\r
   ASSERT_EFI_ERROR (Status);\r
 \r
   Status = gInterrupt->RegisterInterruptSource (gInterrupt, PcdGet32 (PcdArmArchTimerIntrNum), TimerInterruptHandler);\r
   ASSERT_EFI_ERROR (Status);\r
 \r
   Status = gInterrupt->RegisterInterruptSource (gInterrupt, PcdGet32 (PcdArmArchTimerSecIntrNum), TimerInterruptHandler);\r
   ASSERT_EFI_ERROR (Status);\r
 \r
   Status = gInterrupt->RegisterInterruptSource (gInterrupt, PcdGet32 (PcdArmArchTimerIntrNum), TimerInterruptHandler);\r
   ASSERT_EFI_ERROR (Status);\r
 \r
-  // Unmask timer interrupts\r
-  TimerCtrlReg = ArmArchTimerGetTimerCtrlReg ();\r
-  TimerCtrlReg &= ~ARM_ARCH_TIMER_IMASK;\r
-  ArmArchTimerSetTimerCtrlReg (TimerCtrlReg);\r
-\r
   // Set up default timer\r
   Status = TimerDriverSetTimerPeriod (&gTimer, FixedPcdGet32(PcdTimerPeriod)); // TIMER_DEFAULT_PERIOD\r
   ASSERT_EFI_ERROR (Status);\r
   // Set up default timer\r
   Status = TimerDriverSetTimerPeriod (&gTimer, FixedPcdGet32(PcdTimerPeriod)); // TIMER_DEFAULT_PERIOD\r
   ASSERT_EFI_ERROR (Status);\r
@@ -366,11 +422,9 @@ TimerInitialize (
                   );\r
   ASSERT_EFI_ERROR(Status);\r
 \r
                   );\r
   ASSERT_EFI_ERROR(Status);\r
 \r
-  // enable Secure timer interrupts\r
-  Status = gInterrupt->EnableInterruptSource (gInterrupt, PcdGet32 (PcdArmArchTimerSecIntrNum));\r
-\r
-  // enable NonSecure timer interrupts\r
-  Status = gInterrupt->EnableInterruptSource (gInterrupt, PcdGet32 (PcdArmArchTimerIntrNum));\r
+  // Everything is ready, unmask and enable timer interrupts\r
+  TimerCtrlReg = ARM_ARCH_TIMER_ENABLE;\r
+  ArmGenericTimerSetTimerCtrlReg (TimerCtrlReg);\r
 \r
   // Register for an ExitBootServicesEvent\r
   Status = gBS->CreateEvent (EVT_SIGNAL_EXIT_BOOT_SERVICES, TPL_NOTIFY, ExitBootServicesEvent, NULL, &EfiExitBootServicesEvent);\r
 \r
   // Register for an ExitBootServicesEvent\r
   Status = gBS->CreateEvent (EVT_SIGNAL_EXIT_BOOT_SERVICES, TPL_NOTIFY, ExitBootServicesEvent, NULL, &EfiExitBootServicesEvent);\r