--- /dev/null
+/** @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