VirtualBox

source: vbox/trunk/src/VBox/Devices/EFI/FirmwareNew/ArmPkg/Drivers/GenericWatchdogDxe/GenericWatchdogDxe.c@ 105670

Last change on this file since 105670 was 105670, checked in by vboxsync, 4 months ago

Devices/EFI/FirmwareNew: Merge edk2-stable-202405 and make it build on aarch64, bugref:4643

  • Property svn:eol-style set to native
File size: 12.8 KB
Line 
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. */
34STATIC UINT64 mTimerPeriod = 0;
35
36/* disables watchdog interaction after Exit Boot Services */
37STATIC BOOLEAN mExitedBootServices = FALSE;
38
39#define MAX_UINT48 0xFFFFFFFFFFFFULL
40
41STATIC EFI_HARDWARE_INTERRUPT2_PROTOCOL *mInterruptProtocol;
42STATIC EFI_WATCHDOG_TIMER_NOTIFY mWatchdogNotify;
43STATIC 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**/
51STATIC
52UINT64
53GetMaxWatchdogOffsetRegisterValue (
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
73STATIC
74VOID
75WatchdogWriteOffsetRegister (
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
85STATIC
86VOID
87WatchdogWriteCompareRegister (
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
95STATIC
96VOID
97WatchdogEnable (
98 VOID
99 )
100{
101 MmioWrite32 (GENERIC_WDOG_CONTROL_STATUS_REG, GENERIC_WDOG_ENABLED);
102}
103
104STATIC
105VOID
106WatchdogDisable (
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**/
116STATIC
117VOID
118EFIAPI
119WatchdogExitBootServicesEvent (
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*/
132STATIC
133VOID
134EFIAPI
135WatchdogInterruptHandler (
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**/
191STATIC
192EFI_STATUS
193EFIAPI
194WatchdogRegisterHandler (
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**/
227STATIC
228EFI_STATUS
229EFIAPI
230WatchdogSetTimerPeriod (
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**/
298STATIC
299EFI_STATUS
300EFIAPI
301WatchdogGetTimerPeriod (
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**/
347STATIC EFI_WATCHDOG_TIMER_ARCH_PROTOCOL mWatchdogTimer = {
348 WatchdogRegisterHandler,
349 WatchdogSetTimerPeriod,
350 WatchdogGetTimerPeriod
351};
352
353EFI_STATUS
354EFIAPI
355GenericWatchdogEntry (
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
420UnregisterHandler:
421 // Unregister the handler
422 mInterruptProtocol->RegisterInterruptSource (
423 mInterruptProtocol,
424 FixedPcdGet32 (PcdGenericWatchdogEl2IntrNum),
425 NULL
426 );
427 return Status;
428}
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette