Cc: Thang Nguyen <[email protected]>
Cc: Chuong Tran <[email protected]>
Cc: Phong Vo <[email protected]>
Cc: Leif Lindholm <[email protected]>
Cc: Michael D Kinney <[email protected]>
Cc: Ard Biesheuvel <[email protected]>
Cc: Nate DeSimone <[email protected]>
Signed-off-by: Nhi Pham <[email protected]>
Reviewed-by: Leif Lindholm <[email protected]>
---
Silicon/Ampere/AmpereAltraPkg/AmpereAltraPkg.dsc.inc | 6 +-
Platform/Ampere/JadePkg/Jade.fdf | 6 +-
Silicon/Ampere/AmpereAltraPkg/Drivers/FailSafeDxe/FailSafeDxe.inf | 51 +++
Silicon/Ampere/AmpereAltraPkg/Drivers/FailSafeDxe/FailSafe.h | 44 +++
Silicon/Ampere/AmpereAltraPkg/Drivers/FailSafeDxe/Watchdog.h | 29 ++
Silicon/Ampere/AmpereAltraPkg/Drivers/FailSafeDxe/FailSafeDxe.c | 243
+++++++++++++
Silicon/Ampere/AmpereAltraPkg/Drivers/FailSafeDxe/Watchdog.c | 357
++++++++++++++++++++
7 files changed, 734 insertions(+), 2 deletions(-)
diff --git a/Silicon/Ampere/AmpereAltraPkg/AmpereAltraPkg.dsc.inc
b/Silicon/Ampere/AmpereAltraPkg/AmpereAltraPkg.dsc.inc
index 69a6caa56752..bfe66f332c56 100644
--- a/Silicon/Ampere/AmpereAltraPkg/AmpereAltraPkg.dsc.inc
+++ b/Silicon/Ampere/AmpereAltraPkg/AmpereAltraPkg.dsc.inc
@@ -588,7 +588,11 @@ [Components.common]
# Timer
#
ArmPkg/Drivers/TimerDxe/TimerDxe.inf
- MdeModulePkg/Universal/WatchdogTimerDxe/WatchdogTimer.inf
+
+ #
+ # FailSafe and Watchdog Timer
+ #
+ Silicon/Ampere/AmpereAltraPkg/Drivers/FailSafeDxe/FailSafeDxe.inf
#
# ARM GIC Dxe
diff --git a/Platform/Ampere/JadePkg/Jade.fdf b/Platform/Ampere/JadePkg/Jade.fdf
index 6e228d4ecb89..49e38db1bce4 100644
--- a/Platform/Ampere/JadePkg/Jade.fdf
+++ b/Platform/Ampere/JadePkg/Jade.fdf
@@ -185,7 +185,11 @@ [FV.FvMain]
# Timer
#
INF ArmPkg/Drivers/TimerDxe/TimerDxe.inf
- INF MdeModulePkg/Universal/WatchdogTimerDxe/WatchdogTimer.inf
+
+ #
+ # FailSafe and Watchdog Timer
+ #
+ INF Silicon/Ampere/AmpereAltraPkg/Drivers/FailSafeDxe/FailSafeDxe.inf
#
# ARM GIC Dxe
diff --git a/Silicon/Ampere/AmpereAltraPkg/Drivers/FailSafeDxe/FailSafeDxe.inf
b/Silicon/Ampere/AmpereAltraPkg/Drivers/FailSafeDxe/FailSafeDxe.inf
new file mode 100644
index 000000000000..cea69516d0bb
--- /dev/null
+++ b/Silicon/Ampere/AmpereAltraPkg/Drivers/FailSafeDxe/FailSafeDxe.inf
@@ -0,0 +1,51 @@
+## @file
+#
+# Copyright (c) 2020 - 2021, Ampere Computing LLC. All rights reserved.<BR>
+#
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+#
+##
+
+[Defines]
+ INF_VERSION = 0x0001001B
+ BASE_NAME = FailSafeDxe
+ FILE_GUID = 7BC4F970-B1CF-11E6-80F5-76304DEC7EB7
+ MODULE_TYPE = DXE_DRIVER
+ VERSION_STRING = 1.0
+ ENTRY_POINT = FailSafeDxeEntryPoint
+
+[Sources]
+ FailSafe.h
+ FailSafeDxe.c
+ Watchdog.c
+ Watchdog.h
+
+[Packages]
+ ArmPkg/ArmPkg.dec
+ ArmPlatformPkg/ArmPlatformPkg.dec
+ EmbeddedPkg/EmbeddedPkg.dec
+ MdeModulePkg/MdeModulePkg.dec
+ MdePkg/MdePkg.dec
+ Silicon/Ampere/AmpereAltraPkg/AmpereAltraPkg.dec
+ Silicon/Ampere/AmpereSiliconPkg/AmpereSiliconPkg.dec
+
+[LibraryClasses]
+ DebugLib
+ FlashLib
+ IoLib
+ NVParamLib
+ TimerLib
+ UefiBootServicesTableLib
+ UefiDriverEntryPoint
+ UefiLib
+
+[Pcd]
+ gArmTokenSpaceGuid.PcdGenericWatchdogControlBase
+ gArmTokenSpaceGuid.PcdGenericWatchdogEl2IntrNum
+
+[Protocols]
+ gEfiWatchdogTimerArchProtocolGuid ## PRODUCES
+ gHardwareInterrupt2ProtocolGuid ## CONSUMES
+
+[Depex]
+ gHardwareInterrupt2ProtocolGuid
diff --git a/Silicon/Ampere/AmpereAltraPkg/Drivers/FailSafeDxe/FailSafe.h
b/Silicon/Ampere/AmpereAltraPkg/Drivers/FailSafeDxe/FailSafe.h
new file mode 100644
index 000000000000..911b093dce28
--- /dev/null
+++ b/Silicon/Ampere/AmpereAltraPkg/Drivers/FailSafeDxe/FailSafe.h
@@ -0,0 +1,44 @@
+/** @file
+
+ Copyright (c) 2020 - 2021, Ampere Computing LLC. All rights reserved.<BR>
+
+ SPDX-License-Identifier: BSD-2-Clause-Patent
+
+**/
+
+#ifndef FAILSAFE_H_
+#define FAILSAFE_H_
+
+#define FAILSAFE_BOOT_NORMAL 0x00
+#define FAILSAFE_BOOT_LAST_KNOWN_SETTINGS 0x01
+#define FAILSAFE_BOOT_DEFAULT_SETTINGS 0x02
+#define FAILSAFE_BOOT_DDR_DOWNGRADE 0x03
+#define FAILSAFE_BOOT_SUCCESSFUL 0x04
+
+#pragma pack(1)
+typedef struct {
+ UINT8 ImgMajorVer;
+ UINT8 ImgMinorVer;
+ UINT32 NumRetry1;
+ UINT32 NumRetry2;
+ UINT32 MaxRetry;
+ UINT8 Status;
+ //
+ // Byte[3]: Reserved
+ // Byte[2]: Slave MCU Failure Mask
+ // Byte[1]: Reserved
+ // Byte[0]: Master MCU Failure Mask
+ //
+ UINT32 MCUFailsMask;
+ UINT16 CRC16;
+ UINT8 Reserved[3];
+} FAIL_SAFE_CONTEXT;
+#pragma pack()
+
+BOOLEAN
+EFIAPI
+IsFailSafeOff (
+ VOID
+ );
+
+#endif /* FAILSAFE_H_ */
diff --git a/Silicon/Ampere/AmpereAltraPkg/Drivers/FailSafeDxe/Watchdog.h
b/Silicon/Ampere/AmpereAltraPkg/Drivers/FailSafeDxe/Watchdog.h
new file mode 100644
index 000000000000..6c9106fdbea5
--- /dev/null
+++ b/Silicon/Ampere/AmpereAltraPkg/Drivers/FailSafeDxe/Watchdog.h
@@ -0,0 +1,29 @@
+/** @file
+
+ Copyright (c) 2020 - 2021, Ampere Computing LLC. All rights reserved.<BR>
+
+ SPDX-License-Identifier: BSD-2-Clause-Patent
+
+**/
+
+#ifndef GENERIC_WATCHDOG_H_
+#define GENERIC_WATCHDOG_H_
+
+#include <Protocol/WatchdogTimer.h>
+
+/* The number of 100ns periods (the unit of time passed to these functions)
+ in a second */
+#define TIME_UNITS_PER_SECOND 10000000
+
+/**
+ The function to install Watchdog timer protocol to the system
+
+ @retval Return EFI_SUCCESS if install Watchdog timer protocol
successfully.
+ **/
+EFI_STATUS
+EFIAPI
+WatchdogTimerInstallProtocol (
+ EFI_WATCHDOG_TIMER_ARCH_PROTOCOL **WatchdogTimerProtocol
+ );
+
+#endif /* GENERIC_WATCHDOG_H_ */
diff --git a/Silicon/Ampere/AmpereAltraPkg/Drivers/FailSafeDxe/FailSafeDxe.c
b/Silicon/Ampere/AmpereAltraPkg/Drivers/FailSafeDxe/FailSafeDxe.c
new file mode 100644
index 000000000000..487e0d3870ab
--- /dev/null
+++ b/Silicon/Ampere/AmpereAltraPkg/Drivers/FailSafeDxe/FailSafeDxe.c
@@ -0,0 +1,243 @@
+/** @file
+
+ Copyright (c) 2020 - 2021, Ampere Computing LLC. All rights reserved.<BR>
+
+ SPDX-License-Identifier: BSD-2-Clause-Patent
+
+**/
+
+#include <Uefi.h>
+
+#include <Library/BaseLib.h>
+#include <Library/DebugLib.h>
+#include <Library/FlashLib.h>
+#include <Library/NVParamLib.h>
+#include <Library/UefiBootServicesTableLib.h>
+#include <Library/UefiLib.h>
+#include <Library/UefiRuntimeServicesTableLib.h>
+#include <NVParamDef.h>
+
+#include "FailSafe.h"
+#include "Watchdog.h"
+
+STATIC UINTN gWatchdogOSTimeout;
+STATIC BOOLEAN gFailSafeOff;
+STATIC EFI_WATCHDOG_TIMER_ARCH_PROTOCOL *gWatchdogTimer;
+
+STATIC
+INTN
+CheckCrc16 (
+ UINT8 *Pointer,
+ INTN Count
+ )
+{
+ INTN Crc = 0;
+ INTN Index;
+
+ while (--Count >= 0) {
+ Crc = Crc ^ (INTN)*Pointer++ << 8;
+ for (Index = 0; Index < 8; ++Index) {
+ if ((Crc & 0x8000) != 0) {
+ Crc = Crc << 1 ^ 0x1021;
+ } else {
+ Crc = Crc << 1;
+ }
+ }
+ }
+
+ return Crc & 0xFFFF;
+}
+
+BOOLEAN
+FailSafeValidCRC (
+ FAIL_SAFE_CONTEXT *FailSafeBuf
+ )
+{
+ UINT8 Valid;
+ UINT16 Crc;
+ UINT32 Len;
+
+ Len = sizeof (FAIL_SAFE_CONTEXT);
+ Crc = FailSafeBuf->CRC16;
+ FailSafeBuf->CRC16 = 0;
+
+ Valid = (Crc == CheckCrc16 ((UINT8 *)FailSafeBuf, Len));
+ FailSafeBuf->CRC16 = Crc;
+
+ return Valid;
+}
+
+BOOLEAN
+FailSafeFailureStatus (
+ UINT8 Status
+ )
+{
+ if ((Status == FAILSAFE_BOOT_LAST_KNOWN_SETTINGS) ||
+ (Status == FAILSAFE_BOOT_DEFAULT_SETTINGS) ||
+ (Status == FAILSAFE_BOOT_DDR_DOWNGRADE)) {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+EFI_STATUS
+EFIAPI
+FailSafeBootSuccessfully (
+ VOID
+ )
+{
+ EFI_STATUS Status;
+ FAIL_SAFE_CONTEXT FailSafeBuf;
+ UINT32 FailSafeSize;
+ UINT64 FailSafeStartOffset;
+
+ Status = FlashGetFailSafeInfo (&FailSafeStartOffset, &FailSafeSize);
+ if (EFI_ERROR (Status)) {
+ DEBUG ((DEBUG_ERROR, "%a: Failed to get context region information\n",
__FUNCTION__));
+ return EFI_DEVICE_ERROR;
+ }
+
+ Status = FlashReadCommand (FailSafeStartOffset, (UINT8 *)&FailSafeBuf,
sizeof (FAIL_SAFE_CONTEXT));
+ if (EFI_ERROR (Status)) {
+ return Status;
+ }
+
+ //
+ // If failsafe context is valid, and:
+ // - The status indicate non-failure, then don't clear it
+ // - The status indicate a failure, then go and clear it
+ //
+ if (FailSafeValidCRC (&FailSafeBuf)
+ && !FailSafeFailureStatus (FailSafeBuf.Status)) {
+ return EFI_SUCCESS;
+ }
+
+ Status = FlashEraseCommand (FailSafeStartOffset, FailSafeSize);
+ if (EFI_ERROR (Status)) {
+ return Status;
+ }
+
+ return EFI_SUCCESS;
+}
+
+EFI_STATUS
+FailSafeTestBootFailure (
+ VOID
+ )
+{
+ EFI_STATUS Status;
+ UINT32 Value = 0;
+
+ //
+ // Simulate UEFI boot failure due to config wrong NVPARAM for
+ // testing failsafe feature
+ //
+ Status = NVParamGet (NV_SI_UEFI_FAILURE_FAILSAFE, NV_PERM_ALL, &Value);
+ if (!EFI_ERROR (Status) && (Value == 1)) {
+ CpuDeadLoop ();
+ }
+
+ return EFI_SUCCESS;
+}
+
+VOID
+FailSafeTurnOff (
+ VOID
+ )
+{
+ EFI_STATUS Status;
+
+ if (IsFailSafeOff ()) {
+ return;
+ }
+
+ Status = FailSafeBootSuccessfully ();
+ ASSERT_EFI_ERROR (Status);
+
+ gFailSafeOff = TRUE;
+
+ /* Disable Watchdog timer */
+ gWatchdogTimer->SetTimerPeriod (gWatchdogTimer, 0);
+}
+
+BOOLEAN
+EFIAPI
+IsFailSafeOff (
+ VOID
+ )
+{
+ return gFailSafeOff;
+}
+
+/**
+ The function to refresh Watchdog timer in the event before exiting boot
services
+**/
+VOID
+WdtTimerExitBootServiceCallback (
+ IN EFI_EVENT Event,
+ IN VOID *Context
+ )
+{
+
+ /* Enable Watchdog timer for OS booting */
+ if (gWatchdogOSTimeout != 0) {
+ gWatchdogTimer->SetTimerPeriod (
+ gWatchdogTimer,
+ gWatchdogOSTimeout * TIME_UNITS_PER_SECOND
+ );
+ } else {
+ /* Disable Watchdog timer */
+ gWatchdogTimer->SetTimerPeriod (gWatchdogTimer, 0);
+ }
+}
+
+/**
+ Main entry for this driver.
+
+ @param ImageHandle Image handle this driver.
+ @param SystemTable Pointer to SystemTable.
+
+ @retval EFI_SUCCESS This function always complete successfully.
+
+**/
+EFI_STATUS
+EFIAPI
+FailSafeDxeEntryPoint (
+ IN EFI_HANDLE ImageHandle,
+ IN EFI_SYSTEM_TABLE *SystemTable
+ )
+{
+ EFI_EVENT ExitBootServicesEvent;
+ EFI_STATUS Status;
+
+ gFailSafeOff = FALSE;
+
+ FailSafeTestBootFailure ();
+
+ /* We need to setup non secure Watchdog to ensure that the system will
+ * boot to OS successfully.
+ *
+ * The BIOS doesn't handle Watchdog interrupt so we expect WS1 asserted EL3
+ * when Watchdog timeout triggered
+ */
+
+ Status = WatchdogTimerInstallProtocol (&gWatchdogTimer);
+ ASSERT_EFI_ERROR (Status);
+
+ // We should register a callback function before entering to Setup screen
+ // rather than always call it at DXE phase.
+ FailSafeTurnOff ();
+
+ /* Register event before exit boot services */
+ Status = gBS->CreateEvent (
+ EVT_SIGNAL_EXIT_BOOT_SERVICES,
+ TPL_NOTIFY,
+ WdtTimerExitBootServiceCallback,
+ NULL,
+ &ExitBootServicesEvent
+ );
+ ASSERT_EFI_ERROR (Status);
+
+ return Status;
+}
diff --git a/Silicon/Ampere/AmpereAltraPkg/Drivers/FailSafeDxe/Watchdog.c
b/Silicon/Ampere/AmpereAltraPkg/Drivers/FailSafeDxe/Watchdog.c
new file mode 100644
index 000000000000..34329d04206a
--- /dev/null
+++ b/Silicon/Ampere/AmpereAltraPkg/Drivers/FailSafeDxe/Watchdog.c
@@ -0,0 +1,357 @@
+/** @file
+
+ Copyright (c) 2020 - 2021, Ampere Computing LLC. All rights reserved.<BR>
+
+ SPDX-License-Identifier: BSD-2-Clause-Patent
+
+**/
+
+#include <Library/ArmGenericTimerCounterLib.h>
+#include <Library/ArmLib.h>
+#include <Library/BaseLib.h>
+#include <Library/DebugLib.h>
+#include <Library/IoLib.h>
+#include <Library/PcdLib.h>
+#include <Library/UefiBootServicesTableLib.h>
+#include <Library/UefiRuntimeServicesTableLib.h>
+#include <Protocol/HardwareInterrupt2.h>
+
+#include "FailSafe.h"
+#include "Watchdog.h"
+
+/* Watchdog timer controller registers */
+#define WDT_CTRL_BASE_REG FixedPcdGet64
(PcdGenericWatchdogControlBase)
+#define WDT_CTRL_WCS_OFF 0x0
+#define WDT_CTRL_WCS_ENABLE_MASK 0x1
+#define WDT_CTRL_WOR_OFF 0x8
+#define WDT_CTRL_WCV_OFF 0x10
+#define WS0_INTERRUPT_SOURCE FixedPcdGet32
(PcdGenericWatchdogEl2IntrNum)
+
+STATIC UINT64 mNumTimerTicks;
+STATIC EFI_HARDWARE_INTERRUPT2_PROTOCOL *mInterruptProtocol;
+BOOLEAN mInterruptWS0Enabled;
+
+STATIC
+VOID
+WatchdogTimerWriteOffsetRegister (
+ UINT32 Value
+ )
+{
+ MmioWrite32 (WDT_CTRL_BASE_REG + WDT_CTRL_WOR_OFF, Value);
+}
+
+STATIC
+VOID
+WatchdogTimerWriteCompareRegister (
+ UINT64 Value
+ )
+{
+ MmioWrite64 (WDT_CTRL_BASE_REG + WDT_CTRL_WCV_OFF, Value);
+}
+
+STATIC
+EFI_STATUS
+WatchdogTimerEnable (
+ IN BOOLEAN Enable
+ )
+{
+ UINT32 Val = MmioRead32 ((UINTN)(WDT_CTRL_BASE_REG + WDT_CTRL_WCS_OFF));
+
+ if (Enable) {
+ Val |= WDT_CTRL_WCS_ENABLE_MASK;
+ } else {
+ Val &= ~WDT_CTRL_WCS_ENABLE_MASK;
+ }
+ MmioWrite32 ((UINTN)(WDT_CTRL_BASE_REG + WDT_CTRL_WCS_OFF), Val);
+
+ return EFI_SUCCESS;
+}
+
+STATIC
+EFI_STATUS
+WatchdogTimerSetup (
+ VOID
+ )
+{
+ EFI_STATUS Status;
+
+ /* Disable Watchdog timer */
+ WatchdogTimerEnable (FALSE);
+
+ if (!mInterruptWS0Enabled) {
+ Status = mInterruptProtocol->EnableInterruptSource (
+ mInterruptProtocol,
+ WS0_INTERRUPT_SOURCE
+ );
+ ASSERT_EFI_ERROR (Status);
+
+ mInterruptWS0Enabled = TRUE;
+ }
+
+ if (mNumTimerTicks == 0) {
+ return EFI_SUCCESS;
+ }
+
+ /* If the number of required ticks is greater than the max the Watchdog's
+ offset register (WOR) can hold, we need to manually compute and set
+ the compare register (WCV) */
+ if (mNumTimerTicks > MAX_UINT32) {
+ /* We need to enable the Watchdog *before* writing to the compare register,
+ because enabling the Watchdog causes an "explicit refresh", which
+ clobbers the compare register (WCV). In order to make sure this doesn't
+ trigger an interrupt, set the offset to max. */
+ WatchdogTimerWriteOffsetRegister (MAX_UINT32);
+ WatchdogTimerEnable (TRUE);
+ WatchdogTimerWriteCompareRegister (ArmGenericTimerGetSystemCount () +
mNumTimerTicks);
+ } else {
+ WatchdogTimerWriteOffsetRegister ((UINT32)mNumTimerTicks);
+ WatchdogTimerEnable (TRUE);
+ }
+
+ return EFI_SUCCESS;
+}
+
+
+/* This function is called when the Watchdog's first signal (WS0) goes high.
+ It uses the ResetSystem Runtime Service to reset the board.
+*/
+VOID
+EFIAPI
+WatchdogTimerInterruptHandler (
+ IN HARDWARE_INTERRUPT_SOURCE Source,
+ IN EFI_SYSTEM_CONTEXT SystemContext
+ )
+{
+ STATIC CONST CHAR16 ResetString[]= L"The generic Watchdog timer ran out.";
+
+ mInterruptProtocol->EndOfInterrupt (mInterruptProtocol, Source);
+
+ if (!IsFailSafeOff ()) {
+ /* Not handling interrupt as ATF is monitoring it */
+ return;
+ }
+
+ WatchdogTimerEnable (FALSE);
+
+ gRT->ResetSystem (
+ EfiResetCold,
+ EFI_TIMEOUT,
+ StrSize (ResetString),
+ (VOID *)&ResetString
+ );
+
+ /* If we got here then the reset didn't work */
+ ASSERT (FALSE);
+}
+
+/**
+ This function registers the handler NotifyFunction so it is called every time
+ the Watchdog timer expires. It also passes the amount of time since the last
+ handler call to the NotifyFunction.
+ If NotifyFunction is not NULL and a handler is not already registered,
+ then the new handler is registered and EFI_SUCCESS is returned.
+ If NotifyFunction is NULL, and a handler is already registered,
+ then that handler is unregistered.
+ If an attempt is made to register a handler when a handler is already
+ registered, then EFI_ALREADY_STARTED is returned.
+ If an attempt is made to unregister a handler when a handler is not
+ registered, then EFI_INVALID_PARAMETER is returned.
+
+ @param This The EFI_TIMER_ARCH_PROTOCOL instance.
+ @param NotifyFunction The function to call when a timer interrupt fires.
+ This function executes at TPL_HIGH_LEVEL. The DXE
+ Core will register a handler for the timer
interrupt,
+ so it can know how much time has passed. This
+ information is used to signal timer based events.
+ NULL will unregister the handler.
+
+ @retval EFI_UNSUPPORTED The code does not support NotifyFunction.
+
+**/
+EFI_STATUS
+EFIAPI
+WatchdogTimerRegisterHandler (
+ IN CONST EFI_WATCHDOG_TIMER_ARCH_PROTOCOL *This,
+ IN EFI_WATCHDOG_TIMER_NOTIFY NotifyFunction
+ )
+{
+ /* Not support. Watchdog will reset the board */
+ return EFI_UNSUPPORTED;