]> git.proxmox.com Git - mirror_edk2.git/blobdiff - ArmPkg/Drivers/GenericWatchdogDxe/GenericWatchdogDxe.c
ArmPlatformPkg/ArmJunoPkg: Move the watchdog generic driver to ArmPkg/Drivers
[mirror_edk2.git] / ArmPkg / Drivers / GenericWatchdogDxe / GenericWatchdogDxe.c
diff --git a/ArmPkg/Drivers/GenericWatchdogDxe/GenericWatchdogDxe.c b/ArmPkg/Drivers/GenericWatchdogDxe/GenericWatchdogDxe.c
new file mode 100644 (file)
index 0000000..ba09227
--- /dev/null
@@ -0,0 +1,354 @@
+/** @file\r
+*\r
+*  Copyright (c) 2013-2014, ARM Limited. All rights reserved.\r
+*\r
+*  This program and the accompanying materials\r
+*  are licensed and made available under the terms and conditions of the BSD\r
+*  License which accompanies this distribution.  The full text of the license\r
+*  may be found at 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 <Library/BaseLib.h>\r
+#include <Library/BaseMemoryLib.h>\r
+#include <Library/DebugLib.h>\r
+#include <Library/IoLib.h>\r
+#include <Library/PcdLib.h>\r
+#include <Library/UefiBootServicesTableLib.h>\r
+#include <Library/UefiRuntimeServicesTableLib.h>\r
+#include <Library/UefiLib.h>\r
+#include <Library/ArmGenericTimerCounterLib.h>\r
+\r
+#include <Protocol/WatchdogTimer.h>\r
+#include <Protocol/HardwareInterrupt.h>\r
+\r
+#include "GenericWatchdog.h"\r
+\r
+// The number of 100ns periods (the unit of time passed to these functions)\r
+// in a second\r
+#define TIME_UNITS_PER_SECOND 10000000\r
+\r
+// Tick frequency of the generic timer that is the basis of the generic watchdog\r
+UINTN mTimerFrequencyHz = 0;\r
+\r
+// In cases where the compare register was set manually, information about\r
+// how long the watchdog was asked to wait cannot be retrieved from hardware.\r
+// It is therefore stored here. 0 means the timer is not running.\r
+UINT64 mNumTimerTicks = 0;\r
+\r
+EFI_HARDWARE_INTERRUPT_PROTOCOL *mInterruptProtocol;\r
+\r
+EFI_STATUS\r
+WatchdogWriteOffsetRegister (\r
+  UINT32  Value\r
+  )\r
+{\r
+  return MmioWrite32 (GENERIC_WDOG_OFFSET_REG, Value);\r
+}\r
+\r
+EFI_STATUS\r
+WatchdogWriteCompareRegister (\r
+  UINT64  Value\r
+  )\r
+{\r
+  return MmioWrite64 (GENERIC_WDOG_COMPARE_VALUE_REG, Value);\r
+}\r
+\r
+EFI_STATUS\r
+WatchdogEnable (\r
+  VOID\r
+  )\r
+{\r
+  return MmioWrite32 (GENERIC_WDOG_CONTROL_STATUS_REG, GENERIC_WDOG_ENABLED);\r
+}\r
+\r
+EFI_STATUS\r
+WatchdogDisable (\r
+  VOID\r
+  )\r
+{\r
+  return MmioWrite32 (GENERIC_WDOG_CONTROL_STATUS_REG, GENERIC_WDOG_DISABLED);\r
+}\r
+\r
+/**\r
+    On exiting boot services we must make sure the Watchdog Timer\r
+    is stopped.\r
+**/\r
+VOID\r
+EFIAPI\r
+WatchdogExitBootServicesEvent (\r
+  IN EFI_EVENT  Event,\r
+  IN VOID       *Context\r
+  )\r
+{\r
+  WatchdogDisable ();\r
+  mNumTimerTicks = 0;\r
+}\r
+\r
+/*\r
+  This function is called when the watchdog's first signal (WS0) goes high.\r
+  It uses the ResetSystem Runtime Service to reset the board.\r
+*/\r
+VOID\r
+EFIAPI\r
+WatchdogInterruptHandler (\r
+  IN  HARDWARE_INTERRUPT_SOURCE   Source,\r
+  IN  EFI_SYSTEM_CONTEXT          SystemContext\r
+  )\r
+{\r
+  STATIC CONST CHAR16      ResetString[] = L"The generic watchdog timer ran out.";\r
+\r
+  WatchdogDisable ();\r
+\r
+  mInterruptProtocol->EndOfInterrupt (mInterruptProtocol, Source);\r
+\r
+  gRT->ResetSystem (\r
+         EfiResetCold,\r
+         EFI_TIMEOUT,\r
+         StrSize (ResetString),\r
+         &ResetString\r
+         );\r
+\r
+  // If we got here then the reset didn't work\r
+  ASSERT (FALSE);\r
+}\r
+\r
+/**\r
+  This function registers the handler NotifyFunction so it is called every time\r
+  the watchdog timer expires.  It also passes the amount of time since the last\r
+  handler call to the NotifyFunction.\r
+  If NotifyFunction is not NULL and a handler is not already registered,\r
+  then the new handler is registered and EFI_SUCCESS is returned.\r
+  If NotifyFunction is NULL, and a handler is already registered,\r
+  then that handler is unregistered.\r
+  If an attempt is made to register a handler when a handler is already registered,\r
+  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.\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 watchdog timer handler was registered.\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
+\r
+**/\r
+EFI_STATUS\r
+EFIAPI\r
+WatchdogRegisterHandler (\r
+  IN CONST EFI_WATCHDOG_TIMER_ARCH_PROTOCOL   *This,\r
+  IN EFI_WATCHDOG_TIMER_NOTIFY                NotifyFunction\r
+  )\r
+{\r
+  // ERROR: This function is not supported.\r
+  // The watchdog will reset the board\r
+  return EFI_UNSUPPORTED;\r
+}\r
+\r
+/**\r
+  This function sets the amount of time to wait before firing the watchdog\r
+  timer to TimerPeriod 100 nS units.  If TimerPeriod is 0, then the watchdog\r
+  timer is disabled.\r
+\r
+  @param  This             The EFI_WATCHDOG_TIMER_ARCH_PROTOCOL instance.\r
+  @param  TimerPeriod      The amount of time in 100 nS units to wait before the watchdog\r
+                           timer is fired. If TimerPeriod is zero, then the watchdog\r
+                           timer is disabled.\r
+\r
+  @retval EFI_SUCCESS           The watchdog timer has been programmed to fire in Time\r
+                                100 nS units.\r
+  @retval EFI_DEVICE_ERROR      A watchdog timer could not be programmed due to a device\r
+                                error.\r
+\r
+**/\r
+EFI_STATUS\r
+EFIAPI\r
+WatchdogSetTimerPeriod (\r
+  IN CONST EFI_WATCHDOG_TIMER_ARCH_PROTOCOL   *This,\r
+  IN UINT64                                   TimerPeriod   // In 100ns units\r
+  )\r
+{\r
+  UINTN       TimerVal;\r
+  EFI_STATUS  Status;\r
+\r
+  // if TimerPerdiod is 0, this is a request to stop the watchdog.\r
+  if (TimerPeriod == 0) {\r
+    mNumTimerTicks = 0;\r
+    return WatchdogDisable ();\r
+  }\r
+\r
+  // Work out how many timer ticks will equate to TimerPeriod\r
+  mNumTimerTicks = (mTimerFrequencyHz * TimerPeriod) / TIME_UNITS_PER_SECOND;\r
+\r
+  //\r
+  // If the number of required ticks is greater than the max number the\r
+  // watchdog's offset register (WOR) can hold, we need to manually compute and\r
+  // set the compare register (WCV)\r
+  //\r
+  if (mNumTimerTicks > MAX_UINT32) {\r
+    //\r
+    // We need to enable the watchdog *before* writing to the compare register,\r
+    // because enabling the watchdog causes an "explicit refresh", which\r
+    // clobbers the compare register (WCV). In order to make sure this doesn't\r
+    // trigger an interrupt, set the offset to max.\r
+    //\r
+    Status = WatchdogWriteOffsetRegister (MAX_UINT32);\r
+    if (EFI_ERROR (Status)) {\r
+      return Status;\r
+    }\r
+    WatchdogEnable ();\r
+    TimerVal = ArmGenericTimerGetTimerVal ();\r
+    Status = WatchdogWriteCompareRegister (TimerVal + mNumTimerTicks);\r
+  } else {\r
+    Status = WatchdogWriteOffsetRegister ((UINT32)mNumTimerTicks);\r
+    WatchdogEnable ();\r
+  }\r
+\r
+  return Status;\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\r
+                           ns units. If 0 is returned, then the timer is\r
+                           currently disabled.\r
+\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
+WatchdogGetTimerPeriod (\r
+  IN CONST EFI_WATCHDOG_TIMER_ARCH_PROTOCOL   *This,\r
+  OUT UINT64                                  *TimerPeriod\r
+  )\r
+{\r
+  if (TimerPeriod == NULL) {\r
+    return EFI_INVALID_PARAMETER;\r
+  }\r
+\r
+  *TimerPeriod = ((TIME_UNITS_PER_SECOND / mTimerFrequencyHz) * mNumTimerTicks);\r
+\r
+  return EFI_SUCCESS;\r
+}\r
+\r
+/**\r
+  Interface structure for the Watchdog Architectural Protocol.\r
+\r
+  @par Protocol Description:\r
+  This protocol provides a service to set the amount of time to wait\r
+  before firing the watchdog timer, and it also provides a service to\r
+  register a handler that is invoked when the watchdog timer fires.\r
+\r
+  @par When the watchdog timer fires, control will be passed to a handler\r
+  if one has been registered.  If no handler has been registered,\r
+  or the registered handler returns, then the system will be\r
+  reset by calling the Runtime Service ResetSystem().\r
+\r
+  @param RegisterHandler\r
+  Registers a handler that will be called each time the\r
+  watchdogtimer interrupt fires.  TimerPeriod defines the minimum\r
+  time between timer interrupts, so TimerPeriod will also\r
+  be the minimum time between calls to the registered\r
+  handler.\r
+  NOTE: If the watchdog resets the system in hardware, then\r
+        this function will not have any chance of executing.\r
+\r
+  @param SetTimerPeriod\r
+  Sets the period of the timer interrupt in 100 nS units.\r
+  This function is optional, and may return EFI_UNSUPPORTED.\r
+  If this function is supported, then the timer period will\r
+  be rounded up to the nearest supported timer period.\r
+\r
+  @param GetTimerPeriod\r
+  Retrieves the period of the timer interrupt in 100 nS units.\r
+\r
+**/\r
+EFI_WATCHDOG_TIMER_ARCH_PROTOCOL    gWatchdogTimer = {\r
+  (EFI_WATCHDOG_TIMER_REGISTER_HANDLER) WatchdogRegisterHandler,\r
+  (EFI_WATCHDOG_TIMER_SET_TIMER_PERIOD) WatchdogSetTimerPeriod,\r
+  (EFI_WATCHDOG_TIMER_GET_TIMER_PERIOD) WatchdogGetTimerPeriod\r
+};\r
+\r
+EFI_EVENT                           EfiExitBootServicesEvent = (EFI_EVENT)NULL;\r
+\r
+EFI_STATUS\r
+EFIAPI\r
+GenericWatchdogEntry (\r
+  IN EFI_HANDLE         ImageHandle,\r
+  IN EFI_SYSTEM_TABLE   *SystemTable\r
+  )\r
+{\r
+  EFI_STATUS                      Status;\r
+  EFI_HANDLE                      Handle;\r
+\r
+  //\r
+  // Make sure the Watchdog Timer Architectural Protocol has not been installed\r
+  // in the system yet.\r
+  // This will avoid conflicts with the universal watchdog\r
+  //\r
+  ASSERT_PROTOCOL_ALREADY_INSTALLED (NULL, &gEfiWatchdogTimerArchProtocolGuid);\r
+\r
+  mTimerFrequencyHz = ArmGenericTimerGetTimerFreq ();\r
+  ASSERT (mTimerFrequencyHz != 0);\r
+\r
+  // Register for an ExitBootServicesEvent\r
+  Status = gBS->CreateEvent (\r
+                  EVT_SIGNAL_EXIT_BOOT_SERVICES, TPL_NOTIFY,\r
+                  WatchdogExitBootServicesEvent, NULL, &EfiExitBootServicesEvent\r
+                  );\r
+  if (!EFI_ERROR (Status)) {\r
+    // Install interrupt handler\r
+    Status = gBS->LocateProtocol (\r
+                    &gHardwareInterruptProtocolGuid,\r
+                    NULL,\r
+                    (VOID **)&mInterruptProtocol\r
+                    );\r
+    if (!EFI_ERROR (Status)) {\r
+      Status = mInterruptProtocol->RegisterInterruptSource (\r
+                                    mInterruptProtocol,\r
+                                    FixedPcdGet32 (PcdGenericWatchdogEl2IntrNum),\r
+                                    WatchdogInterruptHandler\r
+                                    );\r
+      if (!EFI_ERROR (Status)) {\r
+        // Install the Timer Architectural Protocol onto a new handle\r
+        Handle = NULL;\r
+        Status = gBS->InstallMultipleProtocolInterfaces (\r
+                        &Handle,\r
+                        &gEfiWatchdogTimerArchProtocolGuid, &gWatchdogTimer,\r
+                        NULL\r
+                        );\r
+      }\r
+    }\r
+  }\r
+\r
+  if (EFI_ERROR (Status)) {\r
+    // The watchdog failed to initialize\r
+    ASSERT (FALSE);\r
+  }\r
+\r
+  mNumTimerTicks = 0;\r
+  WatchdogDisable ();\r
+\r
+  return Status;\r
+}\r