1/** @file
2*
3*  Copyright (c) 2013-2014, ARM Limited. All rights reserved.
4*
5*  This program and the accompanying materials
6*  are licensed and made available under the terms and conditions of the BSD
7*  License which accompanies this distribution.  The full text of the license
8*  may be found at http://opensource.org/licenses/bsd-license.php
9*
10*  THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
11*  WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
12*
13**/
14
15#include <PiDxe.h>
16
17#include <Library/BaseLib.h>
18#include <Library/BaseMemoryLib.h>
19#include <Library/DebugLib.h>
20#include <Library/IoLib.h>
21#include <Library/PcdLib.h>
22#include <Library/UefiBootServicesTableLib.h>
23#include <Library/UefiRuntimeServicesTableLib.h>
24#include <Library/UefiLib.h>
25#include <Library/ArmGenericTimerCounterLib.h>
26
27#include <Protocol/WatchdogTimer.h>
28#include <Protocol/HardwareInterrupt.h>
29
30#include "GenericWatchdog.h"
31
32// The number of 100ns periods (the unit of time passed to these functions)
33// in a second
34#define TIME_UNITS_PER_SECOND 10000000
35
36// Tick frequency of the generic timer that is the basis of the generic watchdog
37UINTN mTimerFrequencyHz = 0;
38
39// In cases where the compare register was set manually, information about
40// how long the watchdog was asked to wait cannot be retrieved from hardware.
41// It is therefore stored here. 0 means the timer is not running.
42UINT64 mNumTimerTicks = 0;
43
44EFI_HARDWARE_INTERRUPT_PROTOCOL *mInterruptProtocol;
45
46EFI_STATUS
47WatchdogWriteOffsetRegister (
48  UINT32  Value
49  )
50{
51  return MmioWrite32 (GENERIC_WDOG_OFFSET_REG, Value);
52}
53
54EFI_STATUS
55WatchdogWriteCompareRegister (
56  UINT64  Value
57  )
58{
59  return MmioWrite64 (GENERIC_WDOG_COMPARE_VALUE_REG, Value);
60}
61
62EFI_STATUS
63WatchdogEnable (
64  VOID
65  )
66{
67  return MmioWrite32 (GENERIC_WDOG_CONTROL_STATUS_REG, GENERIC_WDOG_ENABLED);
68}
69
70EFI_STATUS
71WatchdogDisable (
72  VOID
73  )
74{
75  return MmioWrite32 (GENERIC_WDOG_CONTROL_STATUS_REG, GENERIC_WDOG_DISABLED);
76}
77
78/**
79    On exiting boot services we must make sure the Watchdog Timer
80    is stopped.
81**/
82VOID
83EFIAPI
84WatchdogExitBootServicesEvent (
85  IN EFI_EVENT  Event,
86  IN VOID       *Context
87  )
88{
89  WatchdogDisable ();
90  mNumTimerTicks = 0;
91}
92
93/*
94  This function is called when the watchdog's first signal (WS0) goes high.
95  It uses the ResetSystem Runtime Service to reset the board.
96*/
97VOID
98EFIAPI
99WatchdogInterruptHandler (
100  IN  HARDWARE_INTERRUPT_SOURCE   Source,
101  IN  EFI_SYSTEM_CONTEXT          SystemContext
102  )
103{
104  STATIC CONST CHAR16      ResetString[] = L"The generic watchdog timer ran out.";
105
106  WatchdogDisable ();
107
108  mInterruptProtocol->EndOfInterrupt (mInterruptProtocol, Source);
109
110  gRT->ResetSystem (
111         EfiResetCold,
112         EFI_TIMEOUT,
113         StrSize (ResetString),
114         (VOID *) &ResetString
115         );
116
117  // If we got here then the reset didn't work
118  ASSERT (FALSE);
119}
120
121/**
122  This function registers the handler NotifyFunction so it is called every time
123  the watchdog timer expires.  It also passes the amount of time since the last
124  handler call to the NotifyFunction.
125  If NotifyFunction is not NULL and a handler is not already registered,
126  then the new handler is registered and EFI_SUCCESS is returned.
127  If NotifyFunction is NULL, and a handler is already registered,
128  then that handler is unregistered.
129  If an attempt is made to register a handler when a handler is already registered,
130  then EFI_ALREADY_STARTED is returned.
131  If an attempt is made to unregister a handler when a handler is not registered,
132  then EFI_INVALID_PARAMETER is returned.
133
134  @param  This             The EFI_TIMER_ARCH_PROTOCOL instance.
135  @param  NotifyFunction   The function to call when a timer interrupt fires.
136                           This function executes at TPL_HIGH_LEVEL. The DXE
137                           Core will register a handler for the timer interrupt,
138                           so it can know how much time has passed. This
139                           information is used to signal timer based events.
140                           NULL will unregister the handler.
141
142  @retval EFI_SUCCESS           The watchdog timer handler was registered.
143  @retval EFI_ALREADY_STARTED   NotifyFunction is not NULL, and a handler is already
144                                registered.
145  @retval EFI_INVALID_PARAMETER NotifyFunction is NULL, and a handler was not
146                                previously registered.
147
148**/
149EFI_STATUS
150EFIAPI
151WatchdogRegisterHandler (
152  IN CONST EFI_WATCHDOG_TIMER_ARCH_PROTOCOL   *This,
153  IN EFI_WATCHDOG_TIMER_NOTIFY                NotifyFunction
154  )
155{
156  // ERROR: This function is not supported.
157  // The watchdog will reset the board
158  return EFI_UNSUPPORTED;
159}
160
161/**
162  This function sets the amount of time to wait before firing the watchdog
163  timer to TimerPeriod 100 nS units.  If TimerPeriod is 0, then the watchdog
164  timer is disabled.
165
166  @param  This             The EFI_WATCHDOG_TIMER_ARCH_PROTOCOL instance.
167  @param  TimerPeriod      The amount of time in 100 nS units to wait before the watchdog
168                           timer is fired. If TimerPeriod is zero, then the watchdog
169                           timer is disabled.
170
171  @retval EFI_SUCCESS           The watchdog timer has been programmed to fire in Time
172                                100 nS units.
173  @retval EFI_DEVICE_ERROR      A watchdog timer could not be programmed due to a device
174                                error.
175
176**/
177EFI_STATUS
178EFIAPI
179WatchdogSetTimerPeriod (
180  IN CONST EFI_WATCHDOG_TIMER_ARCH_PROTOCOL   *This,
181  IN UINT64                                   TimerPeriod   // In 100ns units
182  )
183{
184  UINTN       SystemCount;
185  EFI_STATUS  Status;
186
187  // if TimerPerdiod is 0, this is a request to stop the watchdog.
188  if (TimerPeriod == 0) {
189    mNumTimerTicks = 0;
190    return WatchdogDisable ();
191  }
192
193  // Work out how many timer ticks will equate to TimerPeriod
194  mNumTimerTicks = (mTimerFrequencyHz * TimerPeriod) / TIME_UNITS_PER_SECOND;
195
196  //
197  // If the number of required ticks is greater than the max number the
198  // watchdog's offset register (WOR) can hold, we need to manually compute and
199  // set the compare register (WCV)
200  //
201  if (mNumTimerTicks > MAX_UINT32) {
202    //
203    // We need to enable the watchdog *before* writing to the compare register,
204    // because enabling the watchdog causes an "explicit refresh", which
205    // clobbers the compare register (WCV). In order to make sure this doesn't
206    // trigger an interrupt, set the offset to max.
207    //
208    Status = WatchdogWriteOffsetRegister (MAX_UINT32);
209    if (EFI_ERROR (Status)) {
210      return Status;
211    }
212    WatchdogEnable ();
213    SystemCount = ArmGenericTimerGetSystemCount ();
214    Status      = WatchdogWriteCompareRegister (SystemCount + mNumTimerTicks);
215  } else {
216    Status = WatchdogWriteOffsetRegister ((UINT32)mNumTimerTicks);
217    WatchdogEnable ();
218  }
219
220  return Status;
221}
222
223/**
224  This function retrieves the period of timer interrupts in 100 ns units,
225  returns that value in TimerPeriod, and returns EFI_SUCCESS.  If TimerPeriod
226  is NULL, then EFI_INVALID_PARAMETER is returned.  If a TimerPeriod of 0 is
227  returned, then the timer is currently disabled.
228
229  @param  This             The EFI_TIMER_ARCH_PROTOCOL instance.
230  @param  TimerPeriod      A pointer to the timer period to retrieve in 100
231                           ns units. If 0 is returned, then the timer is
232                           currently disabled.
233
234
235  @retval EFI_SUCCESS           The timer period was returned in TimerPeriod.
236  @retval EFI_INVALID_PARAMETER TimerPeriod is NULL.
237
238**/
239EFI_STATUS
240EFIAPI
241WatchdogGetTimerPeriod (
242  IN CONST EFI_WATCHDOG_TIMER_ARCH_PROTOCOL   *This,
243  OUT UINT64                                  *TimerPeriod
244  )
245{
246  if (TimerPeriod == NULL) {
247    return EFI_INVALID_PARAMETER;
248  }
249
250  *TimerPeriod = ((TIME_UNITS_PER_SECOND / mTimerFrequencyHz) * mNumTimerTicks);
251
252  return EFI_SUCCESS;
253}
254
255/**
256  Interface structure for the Watchdog Architectural Protocol.
257
258  @par Protocol Description:
259  This protocol provides a service to set the amount of time to wait
260  before firing the watchdog timer, and it also provides a service to
261  register a handler that is invoked when the watchdog timer fires.
262
263  @par When the watchdog timer fires, control will be passed to a handler
264  if one has been registered.  If no handler has been registered,
265  or the registered handler returns, then the system will be
266  reset by calling the Runtime Service ResetSystem().
267
268  @param RegisterHandler
269  Registers a handler that will be called each time the
270  watchdogtimer interrupt fires.  TimerPeriod defines the minimum
271  time between timer interrupts, so TimerPeriod will also
272  be the minimum time between calls to the registered
273  handler.
274  NOTE: If the watchdog resets the system in hardware, then
275        this function will not have any chance of executing.
276
277  @param SetTimerPeriod
278  Sets the period of the timer interrupt in 100 nS units.
279  This function is optional, and may return EFI_UNSUPPORTED.
280  If this function is supported, then the timer period will
281  be rounded up to the nearest supported timer period.
282
283  @param GetTimerPeriod
284  Retrieves the period of the timer interrupt in 100 nS units.
285
286**/
287EFI_WATCHDOG_TIMER_ARCH_PROTOCOL    gWatchdogTimer = {
288  (EFI_WATCHDOG_TIMER_REGISTER_HANDLER) WatchdogRegisterHandler,
289  (EFI_WATCHDOG_TIMER_SET_TIMER_PERIOD) WatchdogSetTimerPeriod,
290  (EFI_WATCHDOG_TIMER_GET_TIMER_PERIOD) WatchdogGetTimerPeriod
291};
292
293EFI_EVENT                           EfiExitBootServicesEvent = (EFI_EVENT)NULL;
294
295EFI_STATUS
296EFIAPI
297GenericWatchdogEntry (
298  IN EFI_HANDLE         ImageHandle,
299  IN EFI_SYSTEM_TABLE   *SystemTable
300  )
301{
302  EFI_STATUS                      Status;
303  EFI_HANDLE                      Handle;
304
305  //
306  // Make sure the Watchdog Timer Architectural Protocol has not been installed
307  // in the system yet.
308  // This will avoid conflicts with the universal watchdog
309  //
310  ASSERT_PROTOCOL_ALREADY_INSTALLED (NULL, &gEfiWatchdogTimerArchProtocolGuid);
311
312  mTimerFrequencyHz = ArmGenericTimerGetTimerFreq ();
313  ASSERT (mTimerFrequencyHz != 0);
314
315  // Register for an ExitBootServicesEvent
316  Status = gBS->CreateEvent (
317                  EVT_SIGNAL_EXIT_BOOT_SERVICES, TPL_NOTIFY,
318                  WatchdogExitBootServicesEvent, NULL, &EfiExitBootServicesEvent
319                  );
320  if (!EFI_ERROR (Status)) {
321    // Install interrupt handler
322    Status = gBS->LocateProtocol (
323                    &gHardwareInterruptProtocolGuid,
324                    NULL,
325                    (VOID **)&mInterruptProtocol
326                    );
327    if (!EFI_ERROR (Status)) {
328      Status = mInterruptProtocol->RegisterInterruptSource (
329                                    mInterruptProtocol,
330                                    FixedPcdGet32 (PcdGenericWatchdogEl2IntrNum),
331                                    WatchdogInterruptHandler
332                                    );
333      if (!EFI_ERROR (Status)) {
334        // Install the Timer Architectural Protocol onto a new handle
335        Handle = NULL;
336        Status = gBS->InstallMultipleProtocolInterfaces (
337                        &Handle,
338                        &gEfiWatchdogTimerArchProtocolGuid, &gWatchdogTimer,
339                        NULL
340                        );
341      }
342    }
343  }
344
345  if (EFI_ERROR (Status)) {
346    // The watchdog failed to initialize
347    ASSERT (FALSE);
348  }
349
350  mNumTimerTicks = 0;
351  WatchdogDisable ();
352
353  return Status;
354}
355