1 | /** @file
|
---|
2 | *
|
---|
3 | * Copyright (c) 2023, Ampere Computing LLC. All rights reserved.<BR>
|
---|
4 | * Copyright (c) 2013-2018, ARM Limited. All rights reserved.
|
---|
5 | *
|
---|
6 | * SPDX-License-Identifier: BSD-2-Clause-Patent
|
---|
7 | *
|
---|
8 | **/
|
---|
9 |
|
---|
10 | #include <PiDxe.h>
|
---|
11 |
|
---|
12 | #include <Library/BaseLib.h>
|
---|
13 | #include <Library/BaseMemoryLib.h>
|
---|
14 | #include <Library/DebugLib.h>
|
---|
15 | #include <Library/IoLib.h>
|
---|
16 | #include <Library/PcdLib.h>
|
---|
17 | #include <Library/UefiBootServicesTableLib.h>
|
---|
18 | #include <Library/UefiRuntimeServicesTableLib.h>
|
---|
19 | #include <Library/UefiLib.h>
|
---|
20 | #include <Library/ArmGenericTimerCounterLib.h>
|
---|
21 |
|
---|
22 | #include <Protocol/HardwareInterrupt2.h>
|
---|
23 | #include <Protocol/WatchdogTimer.h>
|
---|
24 |
|
---|
25 | #include "GenericWatchdog.h"
|
---|
26 |
|
---|
27 | /* The number of 100ns periods (the unit of time passed to these functions)
|
---|
28 | in a second */
|
---|
29 | #define TIME_UNITS_PER_SECOND 10000000
|
---|
30 |
|
---|
31 | /* In cases where the compare register was set manually, information about
|
---|
32 | how long the watchdog was asked to wait cannot be retrieved from hardware.
|
---|
33 | It is therefore stored here. 0 means the timer is not running. */
|
---|
34 | STATIC UINT64 mTimerPeriod = 0;
|
---|
35 |
|
---|
36 | /* disables watchdog interaction after Exit Boot Services */
|
---|
37 | STATIC BOOLEAN mExitedBootServices = FALSE;
|
---|
38 |
|
---|
39 | #define MAX_UINT48 0xFFFFFFFFFFFFULL
|
---|
40 |
|
---|
41 | STATIC EFI_HARDWARE_INTERRUPT2_PROTOCOL *mInterruptProtocol;
|
---|
42 | STATIC EFI_WATCHDOG_TIMER_NOTIFY mWatchdogNotify;
|
---|
43 | STATIC EFI_EVENT mEfiExitBootServicesEvent;
|
---|
44 |
|
---|
45 | /**
|
---|
46 | This function returns the maximum watchdog offset register value.
|
---|
47 |
|
---|
48 | @retval MAX_UINT32 The watchdog offset register holds a 32-bit value.
|
---|
49 | @retval MAX_UINT48 The watchdog offset register holds a 48-bit value.
|
---|
50 | **/
|
---|
51 | STATIC
|
---|
52 | UINT64
|
---|
53 | GetMaxWatchdogOffsetRegisterValue (
|
---|
54 | VOID
|
---|
55 | )
|
---|
56 | {
|
---|
57 | UINT64 MaxWatchdogOffsetValue;
|
---|
58 | UINT32 WatchdogIId;
|
---|
59 | UINT8 WatchdogArchRevision;
|
---|
60 |
|
---|
61 | WatchdogIId = MmioRead32 (GENERIC_WDOG_IID_REG);
|
---|
62 | WatchdogArchRevision = (WatchdogIId >> GENERIC_WDOG_IID_ARCH_REV_SHIFT) & GENERIC_WDOG_IID_ARCH_REV_MASK;
|
---|
63 |
|
---|
64 | if (WatchdogArchRevision == 0) {
|
---|
65 | MaxWatchdogOffsetValue = MAX_UINT32;
|
---|
66 | } else {
|
---|
67 | MaxWatchdogOffsetValue = MAX_UINT48;
|
---|
68 | }
|
---|
69 |
|
---|
70 | return MaxWatchdogOffsetValue;
|
---|
71 | }
|
---|
72 |
|
---|
73 | STATIC
|
---|
74 | VOID
|
---|
75 | WatchdogWriteOffsetRegister (
|
---|
76 | UINT64 Value
|
---|
77 | )
|
---|
78 | {
|
---|
79 | MmioWrite32 (GENERIC_WDOG_OFFSET_REG_LOW, Value & MAX_UINT32);
|
---|
80 | if (GetMaxWatchdogOffsetRegisterValue () == MAX_UINT48) {
|
---|
81 | MmioWrite32 (GENERIC_WDOG_OFFSET_REG_HIGH, (Value >> 32) & MAX_UINT16);
|
---|
82 | }
|
---|
83 | }
|
---|
84 |
|
---|
85 | STATIC
|
---|
86 | VOID
|
---|
87 | WatchdogWriteCompareRegister (
|
---|
88 | UINT64 Value
|
---|
89 | )
|
---|
90 | {
|
---|
91 | MmioWrite32 (GENERIC_WDOG_COMPARE_VALUE_REG_LOW, Value & MAX_UINT32);
|
---|
92 | MmioWrite32 (GENERIC_WDOG_COMPARE_VALUE_REG_HIGH, (Value >> 32) & MAX_UINT32);
|
---|
93 | }
|
---|
94 |
|
---|
95 | STATIC
|
---|
96 | VOID
|
---|
97 | WatchdogEnable (
|
---|
98 | VOID
|
---|
99 | )
|
---|
100 | {
|
---|
101 | MmioWrite32 (GENERIC_WDOG_CONTROL_STATUS_REG, GENERIC_WDOG_ENABLED);
|
---|
102 | }
|
---|
103 |
|
---|
104 | STATIC
|
---|
105 | VOID
|
---|
106 | WatchdogDisable (
|
---|
107 | VOID
|
---|
108 | )
|
---|
109 | {
|
---|
110 | MmioWrite32 (GENERIC_WDOG_CONTROL_STATUS_REG, GENERIC_WDOG_DISABLED);
|
---|
111 | }
|
---|
112 |
|
---|
113 | /** On exiting boot services we must make sure the Watchdog Timer
|
---|
114 | is stopped.
|
---|
115 | **/
|
---|
116 | STATIC
|
---|
117 | VOID
|
---|
118 | EFIAPI
|
---|
119 | WatchdogExitBootServicesEvent (
|
---|
120 | IN EFI_EVENT Event,
|
---|
121 | IN VOID *Context
|
---|
122 | )
|
---|
123 | {
|
---|
124 | WatchdogDisable ();
|
---|
125 | mTimerPeriod = 0;
|
---|
126 | mExitedBootServices = TRUE;
|
---|
127 | }
|
---|
128 |
|
---|
129 | /* This function is called when the watchdog's first signal (WS0) goes high.
|
---|
130 | It uses the ResetSystem Runtime Service to reset the board.
|
---|
131 | */
|
---|
132 | STATIC
|
---|
133 | VOID
|
---|
134 | EFIAPI
|
---|
135 | WatchdogInterruptHandler (
|
---|
136 | IN HARDWARE_INTERRUPT_SOURCE Source,
|
---|
137 | IN EFI_SYSTEM_CONTEXT SystemContext
|
---|
138 | )
|
---|
139 | {
|
---|
140 | STATIC CONST CHAR16 ResetString[] = L"The generic watchdog timer ran out.";
|
---|
141 |
|
---|
142 | WatchdogDisable ();
|
---|
143 |
|
---|
144 | mInterruptProtocol->EndOfInterrupt (mInterruptProtocol, Source);
|
---|
145 |
|
---|
146 | //
|
---|
147 | // The notify function should be called with the elapsed number of ticks
|
---|
148 | // since the watchdog was armed, which should exceed the timer period.
|
---|
149 | // We don't actually know the elapsed number of ticks, so let's return
|
---|
150 | // the timer period plus 1.
|
---|
151 | //
|
---|
152 | if (mWatchdogNotify != NULL) {
|
---|
153 | mWatchdogNotify (mTimerPeriod + 1);
|
---|
154 | }
|
---|
155 |
|
---|
156 | gRT->ResetSystem (
|
---|
157 | EfiResetCold,
|
---|
158 | EFI_TIMEOUT,
|
---|
159 | StrSize (ResetString),
|
---|
160 | (CHAR16 *)ResetString
|
---|
161 | );
|
---|
162 |
|
---|
163 | // If we got here then the reset didn't work
|
---|
164 | ASSERT (FALSE);
|
---|
165 | }
|
---|
166 |
|
---|
167 | /**
|
---|
168 | This function registers the handler NotifyFunction so it is called every time
|
---|
169 | the watchdog timer expires. It also passes the amount of time since the last
|
---|
170 | handler call to the NotifyFunction.
|
---|
171 | If NotifyFunction is not NULL and a handler is not already registered,
|
---|
172 | then the new handler is registered and EFI_SUCCESS is returned.
|
---|
173 | If NotifyFunction is NULL, and a handler is already registered,
|
---|
174 | then that handler is unregistered.
|
---|
175 | If an attempt is made to register a handler when a handler is already
|
---|
176 | registered, then EFI_ALREADY_STARTED is returned.
|
---|
177 | If an attempt is made to unregister a handler when a handler is not
|
---|
178 | registered, then EFI_INVALID_PARAMETER is returned.
|
---|
179 |
|
---|
180 | @param This The EFI_TIMER_ARCH_PROTOCOL instance.
|
---|
181 | @param NotifyFunction The function to call when a timer interrupt fires.
|
---|
182 | This function executes at TPL_HIGH_LEVEL. The DXE
|
---|
183 | Core will register a handler for the timer interrupt,
|
---|
184 | so it can know how much time has passed. This
|
---|
185 | information is used to signal timer based events.
|
---|
186 | NULL will unregister the handler.
|
---|
187 |
|
---|
188 | @retval EFI_UNSUPPORTED The code does not support NotifyFunction.
|
---|
189 |
|
---|
190 | **/
|
---|
191 | STATIC
|
---|
192 | EFI_STATUS
|
---|
193 | EFIAPI
|
---|
194 | WatchdogRegisterHandler (
|
---|
195 | IN EFI_WATCHDOG_TIMER_ARCH_PROTOCOL *This,
|
---|
196 | IN EFI_WATCHDOG_TIMER_NOTIFY NotifyFunction
|
---|
197 | )
|
---|
198 | {
|
---|
199 | if ((mWatchdogNotify == NULL) && (NotifyFunction == NULL)) {
|
---|
200 | return EFI_INVALID_PARAMETER;
|
---|
201 | }
|
---|
202 |
|
---|
203 | if ((mWatchdogNotify != NULL) && (NotifyFunction != NULL)) {
|
---|
204 | return EFI_ALREADY_STARTED;
|
---|
205 | }
|
---|
206 |
|
---|
207 | mWatchdogNotify = NotifyFunction;
|
---|
208 | return EFI_SUCCESS;
|
---|
209 | }
|
---|
210 |
|
---|
211 | /**
|
---|
212 | This function sets the amount of time to wait before firing the watchdog
|
---|
213 | timer to TimerPeriod 100ns units. If TimerPeriod is 0, then the watchdog
|
---|
214 | timer is disabled.
|
---|
215 |
|
---|
216 | @param This The EFI_WATCHDOG_TIMER_ARCH_PROTOCOL instance.
|
---|
217 | @param TimerPeriod The amount of time in 100ns units to wait before
|
---|
218 | the watchdog timer is fired. If TimerPeriod is zero,
|
---|
219 | then the watchdog timer is disabled.
|
---|
220 |
|
---|
221 | @retval EFI_SUCCESS The watchdog timer has been programmed to fire
|
---|
222 | in TimerPeriod 100ns units.
|
---|
223 | @retval EFI_DEVICE_ERROR Boot Services has been exited but TimerPeriod
|
---|
224 | is not zero.
|
---|
225 |
|
---|
226 | **/
|
---|
227 | STATIC
|
---|
228 | EFI_STATUS
|
---|
229 | EFIAPI
|
---|
230 | WatchdogSetTimerPeriod (
|
---|
231 | IN EFI_WATCHDOG_TIMER_ARCH_PROTOCOL *This,
|
---|
232 | IN UINT64 TimerPeriod // In 100ns units
|
---|
233 | )
|
---|
234 | {
|
---|
235 | UINTN SystemCount;
|
---|
236 | UINT64 MaxWatchdogOffsetValue;
|
---|
237 | UINT64 TimerFrequencyHz;
|
---|
238 | UINT64 NumTimerTicks;
|
---|
239 |
|
---|
240 | // If we've exited Boot Services but TimerPeriod isn't zero, this
|
---|
241 | // indicates that the caller is doing something wrong.
|
---|
242 | if (mExitedBootServices && (TimerPeriod != 0)) {
|
---|
243 | mTimerPeriod = 0;
|
---|
244 | WatchdogDisable ();
|
---|
245 | return EFI_DEVICE_ERROR;
|
---|
246 | }
|
---|
247 |
|
---|
248 | // If TimerPeriod is 0 this is a request to stop the watchdog.
|
---|
249 | if (TimerPeriod == 0) {
|
---|
250 | mTimerPeriod = 0;
|
---|
251 | WatchdogDisable ();
|
---|
252 | return EFI_SUCCESS;
|
---|
253 | }
|
---|
254 |
|
---|
255 | // Work out how many timer ticks will equate to TimerPeriod
|
---|
256 | TimerFrequencyHz = ArmGenericTimerGetTimerFreq ();
|
---|
257 | ASSERT (TimerFrequencyHz != 0);
|
---|
258 | mTimerPeriod = TimerPeriod;
|
---|
259 | NumTimerTicks = (TimerFrequencyHz * TimerPeriod) / TIME_UNITS_PER_SECOND;
|
---|
260 |
|
---|
261 | /* If the number of required ticks is greater than the max the watchdog's
|
---|
262 | offset register (WOR) can hold, we need to manually compute and set
|
---|
263 | the compare register (WCV) */
|
---|
264 | MaxWatchdogOffsetValue = GetMaxWatchdogOffsetRegisterValue ();
|
---|
265 | if (NumTimerTicks > MaxWatchdogOffsetValue) {
|
---|
266 | /* We need to enable the watchdog *before* writing to the compare register,
|
---|
267 | because enabling the watchdog causes an "explicit refresh", which
|
---|
268 | clobbers the compare register (WCV). In order to make sure this doesn't
|
---|
269 | trigger an interrupt, set the offset to max. */
|
---|
270 | WatchdogWriteOffsetRegister (MaxWatchdogOffsetValue);
|
---|
271 | WatchdogEnable ();
|
---|
272 | SystemCount = ArmGenericTimerGetSystemCount ();
|
---|
273 | WatchdogWriteCompareRegister (SystemCount + NumTimerTicks);
|
---|
274 | } else {
|
---|
275 | WatchdogWriteOffsetRegister (NumTimerTicks);
|
---|
276 | WatchdogEnable ();
|
---|
277 | }
|
---|
278 |
|
---|
279 | return EFI_SUCCESS;
|
---|
280 | }
|
---|
281 |
|
---|
282 | /**
|
---|
283 | This function retrieves the period of timer interrupts in 100ns units,
|
---|
284 | returns that value in TimerPeriod, and returns EFI_SUCCESS. If TimerPeriod
|
---|
285 | is NULL, then EFI_INVALID_PARAMETER is returned. If a TimerPeriod of 0 is
|
---|
286 | returned, then the timer is currently disabled.
|
---|
287 |
|
---|
288 | @param This The EFI_TIMER_ARCH_PROTOCOL instance.
|
---|
289 | @param TimerPeriod A pointer to the timer period to retrieve in
|
---|
290 | 100ns units. If 0 is returned, then the timer is
|
---|
291 | currently disabled.
|
---|
292 |
|
---|
293 |
|
---|
294 | @retval EFI_SUCCESS The timer period was returned in TimerPeriod.
|
---|
295 | @retval EFI_INVALID_PARAMETER TimerPeriod is NULL.
|
---|
296 |
|
---|
297 | **/
|
---|
298 | STATIC
|
---|
299 | EFI_STATUS
|
---|
300 | EFIAPI
|
---|
301 | WatchdogGetTimerPeriod (
|
---|
302 | IN EFI_WATCHDOG_TIMER_ARCH_PROTOCOL *This,
|
---|
303 | OUT UINT64 *TimerPeriod
|
---|
304 | )
|
---|
305 | {
|
---|
306 | if (TimerPeriod == NULL) {
|
---|
307 | return EFI_INVALID_PARAMETER;
|
---|
308 | }
|
---|
309 |
|
---|
310 | *TimerPeriod = mTimerPeriod;
|
---|
311 |
|
---|
312 | return EFI_SUCCESS;
|
---|
313 | }
|
---|
314 |
|
---|
315 | /**
|
---|
316 | Interface structure for the Watchdog Architectural Protocol.
|
---|
317 |
|
---|
318 | @par Protocol Description:
|
---|
319 | This protocol provides a service to set the amount of time to wait
|
---|
320 | before firing the watchdog timer, and it also provides a service to
|
---|
321 | register a handler that is invoked when the watchdog timer fires.
|
---|
322 |
|
---|
323 | @par When the watchdog timer fires, control will be passed to a handler
|
---|
324 | if one has been registered. If no handler has been registered,
|
---|
325 | or the registered handler returns, then the system will be
|
---|
326 | reset by calling the Runtime Service ResetSystem().
|
---|
327 |
|
---|
328 | @param RegisterHandler
|
---|
329 | Registers a handler that will be called each time the
|
---|
330 | watchdogtimer interrupt fires. TimerPeriod defines the minimum
|
---|
331 | time between timer interrupts, so TimerPeriod will also
|
---|
332 | be the minimum time between calls to the registered
|
---|
333 | handler.
|
---|
334 | NOTE: If the watchdog resets the system in hardware, then
|
---|
335 | this function will not have any chance of executing.
|
---|
336 |
|
---|
337 | @param SetTimerPeriod
|
---|
338 | Sets the period of the timer interrupt in 100ns units.
|
---|
339 | This function is optional, and may return EFI_UNSUPPORTED.
|
---|
340 | If this function is supported, then the timer period will
|
---|
341 | be rounded up to the nearest supported timer period.
|
---|
342 |
|
---|
343 | @param GetTimerPeriod
|
---|
344 | Retrieves the period of the timer interrupt in 100ns units.
|
---|
345 |
|
---|
346 | **/
|
---|
347 | STATIC EFI_WATCHDOG_TIMER_ARCH_PROTOCOL mWatchdogTimer = {
|
---|
348 | WatchdogRegisterHandler,
|
---|
349 | WatchdogSetTimerPeriod,
|
---|
350 | WatchdogGetTimerPeriod
|
---|
351 | };
|
---|
352 |
|
---|
353 | EFI_STATUS
|
---|
354 | EFIAPI
|
---|
355 | GenericWatchdogEntry (
|
---|
356 | IN EFI_HANDLE ImageHandle,
|
---|
357 | IN EFI_SYSTEM_TABLE *SystemTable
|
---|
358 | )
|
---|
359 | {
|
---|
360 | EFI_STATUS Status;
|
---|
361 | EFI_HANDLE Handle;
|
---|
362 |
|
---|
363 | Status = gBS->LocateProtocol (
|
---|
364 | &gHardwareInterrupt2ProtocolGuid,
|
---|
365 | NULL,
|
---|
366 | (VOID **)&mInterruptProtocol
|
---|
367 | );
|
---|
368 | ASSERT_EFI_ERROR (Status);
|
---|
369 |
|
---|
370 | /* Make sure the Watchdog Timer Architectural Protocol has not been installed
|
---|
371 | in the system yet.
|
---|
372 | This will avoid conflicts with the universal watchdog */
|
---|
373 | ASSERT_PROTOCOL_ALREADY_INSTALLED (NULL, &gEfiWatchdogTimerArchProtocolGuid);
|
---|
374 |
|
---|
375 | // Install interrupt handler
|
---|
376 | Status = mInterruptProtocol->RegisterInterruptSource (
|
---|
377 | mInterruptProtocol,
|
---|
378 | FixedPcdGet32 (PcdGenericWatchdogEl2IntrNum),
|
---|
379 | WatchdogInterruptHandler
|
---|
380 | );
|
---|
381 | if (EFI_ERROR (Status)) {
|
---|
382 | return Status;
|
---|
383 | }
|
---|
384 |
|
---|
385 | Status = mInterruptProtocol->SetTriggerType (
|
---|
386 | mInterruptProtocol,
|
---|
387 | FixedPcdGet32 (PcdGenericWatchdogEl2IntrNum),
|
---|
388 | EFI_HARDWARE_INTERRUPT2_TRIGGER_EDGE_RISING
|
---|
389 | );
|
---|
390 | if (EFI_ERROR (Status)) {
|
---|
391 | goto UnregisterHandler;
|
---|
392 | }
|
---|
393 |
|
---|
394 | // Install the Timer Architectural Protocol onto a new handle
|
---|
395 | Handle = NULL;
|
---|
396 | Status = gBS->InstallMultipleProtocolInterfaces (
|
---|
397 | &Handle,
|
---|
398 | &gEfiWatchdogTimerArchProtocolGuid,
|
---|
399 | &mWatchdogTimer,
|
---|
400 | NULL
|
---|
401 | );
|
---|
402 | if (EFI_ERROR (Status)) {
|
---|
403 | goto UnregisterHandler;
|
---|
404 | }
|
---|
405 |
|
---|
406 | // Register for an ExitBootServicesEvent
|
---|
407 | Status = gBS->CreateEvent (
|
---|
408 | EVT_SIGNAL_EXIT_BOOT_SERVICES,
|
---|
409 | TPL_NOTIFY,
|
---|
410 | WatchdogExitBootServicesEvent,
|
---|
411 | NULL,
|
---|
412 | &mEfiExitBootServicesEvent
|
---|
413 | );
|
---|
414 | ASSERT_EFI_ERROR (Status);
|
---|
415 |
|
---|
416 | WatchdogDisable ();
|
---|
417 |
|
---|
418 | return EFI_SUCCESS;
|
---|
419 |
|
---|
420 | UnregisterHandler:
|
---|
421 | // Unregister the handler
|
---|
422 | mInterruptProtocol->RegisterInterruptSource (
|
---|
423 | mInterruptProtocol,
|
---|
424 | FixedPcdGet32 (PcdGenericWatchdogEl2IntrNum),
|
---|
425 | NULL
|
---|
426 | );
|
---|
427 | return Status;
|
---|
428 | }
|
---|