[mask_rom] Add watchdog driver.

1. Add a driver to initialize and pet the watchdog.
2. Add unit and functional tests.
3. Enable test automation (systemtest and dvsim).

Signed-off-by: Chris Frantz <cfrantz@google.com>
diff --git a/hw/top_earlgrey/dv/verilator_sim_cfg.hjson b/hw/top_earlgrey/dv/verilator_sim_cfg.hjson
index 20c9cbe..5c3d490 100644
--- a/hw/top_earlgrey/dv/verilator_sim_cfg.hjson
+++ b/hw/top_earlgrey/dv/verilator_sim_cfg.hjson
@@ -205,6 +205,10 @@
       name: sw_silicon_creator_lib_driver_alert_functest
       sw_images: ["sw/device/silicon_creator/testing/sw_silicon_creator_lib_driver_alert_functest:1"]
     }
+    {
+      name: sw_silicon_creator_lib_driver_watchdog_functest
+      sw_images: ["sw/device/silicon_creator/testing/sw_silicon_creator_lib_driver_watchdog_functest:1"]
+    }
   ]
 
   // List of regressions.
diff --git a/sw/device/silicon_creator/lib/drivers/meson.build b/sw/device/silicon_creator/lib/drivers/meson.build
index 229a024..a888c6f 100644
--- a/sw/device/silicon_creator/lib/drivers/meson.build
+++ b/sw/device/silicon_creator/lib/drivers/meson.build
@@ -316,6 +316,41 @@
   suite: 'mask_rom',
 )
 
+# Mask ROM watchdog driver
+sw_silicon_creator_lib_driver_watchdog = declare_dependency(
+  link_with: static_library(
+    'sw_silicon_creator_lib_driver_watchdog',
+    sources: [
+      hw_ip_aon_timer_reg_h,
+      hw_ip_pwrmgr_reg_h,
+      'watchdog.c',
+    ],
+    dependencies: [
+      sw_lib_mmio,
+      sw_silicon_creator_lib_base_abs_mmio,
+    ],
+  ),
+)
+
+test('sw_silicon_creator_lib_driver_watchdog_unittest', executable(
+    'sw_silicon_creator_lib_driver_watchdog_unittest',
+    sources: [
+      'watchdog_unittest.cc',
+      hw_ip_aon_timer_reg_h,
+      hw_ip_pwrmgr_reg_h,
+      'watchdog.c',
+    ],
+    dependencies: [
+      sw_vendor_gtest,
+      sw_silicon_creator_lib_base_mock_abs_mmio,
+    ],
+    native: true,
+    c_args: ['-DMOCK_ABS_MMIO'],
+    cpp_args: ['-DMOCK_ABS_MMIO'],
+  ),
+  suite: 'mask_rom',
+)
+
 # Mask ROM otbn driver
 sw_silicon_creator_lib_driver_otbn = declare_dependency(
   link_with: static_library(
@@ -347,3 +382,22 @@
   ),
   suite: 'mask_rom',
 )
+
+sw_silicon_creator_lib_driver_watchdog_functest = declare_dependency(
+  link_with: static_library(
+    'sw_silicon_creator_lib_driver_watchdog_functest',
+    sources: [
+      hw_ip_rstmgr_reg_h,
+      'watchdog_functest.c'
+    ],
+    dependencies: [
+      sw_silicon_creator_lib_driver_rstmgr,
+      sw_silicon_creator_lib_driver_watchdog,
+    ],
+  ),
+)
+mask_rom_tests += {
+  'sw_silicon_creator_lib_driver_watchdog_functest': {
+    'library': sw_silicon_creator_lib_driver_watchdog_functest,
+  }
+}
diff --git a/sw/device/silicon_creator/lib/drivers/watchdog.c b/sw/device/silicon_creator/lib/drivers/watchdog.c
new file mode 100644
index 0000000..d7f0438
--- /dev/null
+++ b/sw/device/silicon_creator/lib/drivers/watchdog.c
@@ -0,0 +1,52 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#include "sw/device/silicon_creator/lib/drivers/watchdog.h"
+
+#include "sw/device/silicon_creator/lib/base/abs_mmio.h"
+
+#include "aon_timer_regs.h"
+#include "hw/top_earlgrey/sw/autogen/top_earlgrey.h"
+#include "pwrmgr_regs.h"
+
+enum {
+  kBase = TOP_EARLGREY_AON_TIMER_AON_BASE_ADDR,
+  kPwrMgrBase = TOP_EARLGREY_PWRMGR_AON_BASE_ADDR,
+  // The AON domain is always clocked at 200 Hz.
+  // TODO(lowRISC/opentitan#7385): update this after the AON frequency is
+  // formally defined in the project.
+  kAonTimerRate = 200000,
+};
+
+void watchdog_init(uint32_t timeout_ms) {
+  // Tell pwrmgr we want watchdog reset events to reset the chip.
+  abs_mmio_write32(
+      kPwrMgrBase + PWRMGR_RESET_EN_REG_OFFSET,
+      bitfield_bit32_write(
+          0, kTopEarlgreyPowerManagerResetRequestsAonTimerAonAonTimerRstReq,
+          true));
+  abs_mmio_write32(kPwrMgrBase + PWRMGR_CFG_CDC_SYNC_REG_OFFSET, 1);
+
+  // Disable the watchdog before reconfiguring it.
+  abs_mmio_write32(kBase + AON_TIMER_WDOG_CTRL_REG_OFFSET, 0);
+  abs_mmio_write32(kBase + AON_TIMER_WKUP_CTRL_REG_OFFSET, 0);
+  // Configure the watchdog to bite at the requested timeout.
+  abs_mmio_write32(kBase + AON_TIMER_WKUP_COUNT_REG_OFFSET, 0);
+  abs_mmio_write32(kBase + AON_TIMER_WDOG_COUNT_REG_OFFSET, 0);
+  abs_mmio_write32(kBase + AON_TIMER_WKUP_THOLD_REG_OFFSET, UINT32_MAX);
+  abs_mmio_write32(kBase + AON_TIMER_WDOG_BARK_THOLD_REG_OFFSET, UINT32_MAX);
+  abs_mmio_write32(kBase + AON_TIMER_WDOG_BITE_THOLD_REG_OFFSET,
+                   timeout_ms * (kAonTimerRate / 1000));
+  if (timeout_ms) {
+    abs_mmio_write32(kBase + AON_TIMER_WDOG_CTRL_REG_OFFSET, 1);
+  }
+}
+
+void watchdog_pet(void) {
+  abs_mmio_write32(kBase + AON_TIMER_WDOG_COUNT_REG_OFFSET, 0);
+}
+
+uint32_t watchdog_get(void) {
+  return abs_mmio_read32(kBase + AON_TIMER_WDOG_COUNT_REG_OFFSET);
+}
diff --git a/sw/device/silicon_creator/lib/drivers/watchdog.h b/sw/device/silicon_creator/lib/drivers/watchdog.h
new file mode 100644
index 0000000..bc34f7a
--- /dev/null
+++ b/sw/device/silicon_creator/lib/drivers/watchdog.h
@@ -0,0 +1,35 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#ifndef OPENTITAN_SW_DEVICE_SILICON_CREATOR_LIB_DRIVERS_WATCHDOG_H_
+#define OPENTITAN_SW_DEVICE_SILICON_CREATOR_LIB_DRIVERS_WATCHDOG_H_
+#include <stdint.h>
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Initialize the watchdog timer.
+ *
+ * @param timeout_ms The timeout, in milliseconds, after which the watchdog
+ *                   will trigger a reset. A value of zero disables the watchdog
+ *                   timer.
+ */
+void watchdog_init(uint32_t timeout_ms);
+
+/**
+ * Pet the watchdog, thus preventing a watchdog initiated reset.
+ */
+void watchdog_pet(void);
+
+/**
+ * Get the current watchdog counter value.
+ * @return current watchdog value.
+ */
+uint32_t watchdog_get(void);
+
+#ifdef __cplusplus
+}
+#endif
+#endif  // OPENTITAN_SW_DEVICE_SILICON_CREATOR_LIB_DRIVERS_WATCHDOG_H_
diff --git a/sw/device/silicon_creator/lib/drivers/watchdog_functest.c b/sw/device/silicon_creator/lib/drivers/watchdog_functest.c
new file mode 100644
index 0000000..40f809b
--- /dev/null
+++ b/sw/device/silicon_creator/lib/drivers/watchdog_functest.c
@@ -0,0 +1,99 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "sw/device/lib/arch/device.h"
+#include "sw/device/lib/base/memory.h"
+#include "sw/device/lib/base/mmio.h"
+#include "sw/device/lib/runtime/hart.h"
+#include "sw/device/lib/runtime/log.h"
+#include "sw/device/lib/runtime/print.h"
+#include "sw/device/silicon_creator/lib/base/abs_mmio.h"
+#include "sw/device/silicon_creator/lib/drivers/rstmgr.h"
+#include "sw/device/silicon_creator/lib/drivers/watchdog.h"
+#include "sw/device/silicon_creator/lib/error.h"
+#include "sw/device/silicon_creator/lib/test_main.h"
+
+#include "hw/top_earlgrey/sw/autogen/top_earlgrey.h"
+#include "rstmgr_regs.h"
+
+// The reset reason value is really a bitfield. The power-on-reset indicator
+// is defined by rstmgr_regs.h.
+#define RESET_REASON_POR (1 << RSTMGR_RESET_INFO_POR_BIT)
+// FIXME: I don't know where the HW_REQ field of the reset reason register
+// is defined.  I observe a value of 2 for a watchdog reset.
+#define RESET_REASON_WATCHDOG \
+  ((2 << RSTMGR_RESET_INFO_HW_REQ_OFFSET) | RESET_REASON_POR)
+
+void *const kRetentionRamBase = (void *)TOP_EARLGREY_RAM_RET_AON_BASE_ADDR;
+const size_t kRetentionRamSize = TOP_EARLGREY_RAM_RET_AON_SIZE_BYTES;
+
+// Tests that we can pet the watchdog and avoid a reset.
+rom_error_t watchdog_pet_test(void) {
+  watchdog_init(500);
+  for (size_t i = 0; i < 10; ++i) {
+    LOG_INFO("watchdog = %x", watchdog_get());
+    watchdog_pet();
+    usleep(5000);
+  }
+  watchdog_init(0);
+  return kErrorOk;
+}
+
+// Tests that if we neglect the dog, it will bite and reset the chip.
+rom_error_t watchdog_bite_test(void) {
+  watchdog_init(1);
+  usleep(11000);
+  watchdog_init(0);
+  return kErrorUnknown;
+}
+
+const test_config_t kTestConfig;
+
+// The test phases are tracked in retention RAM so that we ensure the reset
+// happened in the correct phase of the test.
+typedef enum TestPhase {
+  kTestPhaseInit = 0,
+  kTestPhasePet,
+  kTestPhaseBite,
+  kTestPhaseDone,
+} test_phase_t;
+
+bool test_main(void) {
+  rom_error_t result = kErrorOk;
+  uint32_t reason = rstmgr_reason_get();
+  rstmgr_alert_info_enable();
+  LOG_INFO("reset_info = %08x", reason);
+
+  volatile test_phase_t *phase = (volatile test_phase_t *)kRetentionRamBase;
+  if (reason == RESET_REASON_POR) {
+    // Power-on: zero out the retention RAM.
+    memset(kRetentionRamBase, 0, kRetentionRamSize);
+
+    *phase = kTestPhasePet;
+    EXECUTE_TEST(result, watchdog_pet_test);
+
+    *phase = kTestPhaseBite;
+    EXECUTE_TEST(result, watchdog_bite_test);
+    // We should never get here - the escalate test should cause a reset
+    // and we should see a reset reason of 0x11.
+    *phase = kTestPhaseDone;
+    LOG_ERROR("Test failure: should have reset before this line.");
+  } else if (reason == RESET_REASON_WATCHDOG) {
+    LOG_INFO("Detected reset after escalation test");
+    if (*phase != kTestPhaseBite) {
+      LOG_ERROR("Test failure: expected phase %d but got phase %d",
+                kTestPhaseBite, *phase);
+      result = kErrorUnknown;
+    } else {
+      result = kErrorOk;
+    }
+  } else {
+    LOG_ERROR("Unknown reset reason");
+    result = kErrorUnknown;
+  }
+  return result == kErrorOk;
+}
diff --git a/sw/device/silicon_creator/lib/drivers/watchdog_unittest.cc b/sw/device/silicon_creator/lib/drivers/watchdog_unittest.cc
new file mode 100644
index 0000000..300e78d
--- /dev/null
+++ b/sw/device/silicon_creator/lib/drivers/watchdog_unittest.cc
@@ -0,0 +1,91 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#include "sw/device/silicon_creator/lib/drivers/watchdog.h"
+
+#include "gtest/gtest.h"
+#include "sw/device/lib/base/mmio.h"
+#include "sw/device/silicon_creator/lib/base/mock_abs_mmio.h"
+
+#include "aon_timer_regs.h"
+#include "hw/top_earlgrey/sw/autogen/top_earlgrey.h"
+#include "pwrmgr_regs.h"
+
+namespace watchdog_unittest {
+namespace {
+
+constexpr uint32_t kAonTimerRate = 200000;
+
+class WatchdogTest : public mask_rom_test::MaskRomTest {
+ protected:
+  uint32_t pwrmgr_ = TOP_EARLGREY_PWRMGR_AON_BASE_ADDR;
+  uint32_t wdog_ = TOP_EARLGREY_AON_TIMER_AON_BASE_ADDR;
+  mask_rom_test::MockAbsMmio mmio_;
+};
+
+TEST_F(WatchdogTest, InitializeEnable) {
+  constexpr uint32_t kTimeoutMs = 100;
+  constexpr uint32_t kBiteThreshold = kTimeoutMs * (kAonTimerRate / 1000);
+
+  EXPECT_ABS_WRITE32(mmio_, pwrmgr_ + PWRMGR_RESET_EN_REG_OFFSET,
+                     {
+                         {PWRMGR_RESET_EN_EN_1_BIT, true},
+                     });
+  EXPECT_ABS_WRITE32(mmio_, pwrmgr_ + PWRMGR_CFG_CDC_SYNC_REG_OFFSET, 1);
+
+  EXPECT_ABS_WRITE32(mmio_, wdog_ + AON_TIMER_WDOG_CTRL_REG_OFFSET, 0);
+  EXPECT_ABS_WRITE32(mmio_, wdog_ + AON_TIMER_WKUP_CTRL_REG_OFFSET, 0);
+  EXPECT_ABS_WRITE32(mmio_, wdog_ + AON_TIMER_WKUP_COUNT_REG_OFFSET, 0);
+  EXPECT_ABS_WRITE32(mmio_, wdog_ + AON_TIMER_WDOG_COUNT_REG_OFFSET, 0);
+  EXPECT_ABS_WRITE32(mmio_, wdog_ + AON_TIMER_WKUP_THOLD_REG_OFFSET,
+                     0xFFFFFFFF);
+  EXPECT_ABS_WRITE32(mmio_, wdog_ + AON_TIMER_WDOG_BARK_THOLD_REG_OFFSET,
+                     0xFFFFFFFF);
+  EXPECT_ABS_WRITE32(mmio_, wdog_ + AON_TIMER_WDOG_BITE_THOLD_REG_OFFSET,
+                     kBiteThreshold);
+  EXPECT_ABS_WRITE32(mmio_, wdog_ + AON_TIMER_WDOG_CTRL_REG_OFFSET, 1);
+
+  watchdog_init(kTimeoutMs);
+}
+
+TEST_F(WatchdogTest, InitializeDisable) {
+  // Using a timeout of zero as the argument to the initialization function
+  // disables the watchdog.
+  constexpr uint32_t kTimeoutMs = 0;
+  constexpr uint32_t kBiteThreshold = kTimeoutMs * (kAonTimerRate / 1000);
+
+  EXPECT_ABS_WRITE32(mmio_, pwrmgr_ + PWRMGR_RESET_EN_REG_OFFSET,
+                     {
+                         {PWRMGR_RESET_EN_EN_1_BIT, true},
+                     });
+  EXPECT_ABS_WRITE32(mmio_, pwrmgr_ + PWRMGR_CFG_CDC_SYNC_REG_OFFSET, 1);
+
+  EXPECT_ABS_WRITE32(mmio_, wdog_ + AON_TIMER_WDOG_CTRL_REG_OFFSET, 0);
+  EXPECT_ABS_WRITE32(mmio_, wdog_ + AON_TIMER_WKUP_CTRL_REG_OFFSET, 0);
+  EXPECT_ABS_WRITE32(mmio_, wdog_ + AON_TIMER_WKUP_COUNT_REG_OFFSET, 0);
+  EXPECT_ABS_WRITE32(mmio_, wdog_ + AON_TIMER_WDOG_COUNT_REG_OFFSET, 0);
+  EXPECT_ABS_WRITE32(mmio_, wdog_ + AON_TIMER_WKUP_THOLD_REG_OFFSET,
+                     0xFFFFFFFF);
+  EXPECT_ABS_WRITE32(mmio_, wdog_ + AON_TIMER_WDOG_BARK_THOLD_REG_OFFSET,
+                     0xFFFFFFFF);
+  EXPECT_ABS_WRITE32(mmio_, wdog_ + AON_TIMER_WDOG_BITE_THOLD_REG_OFFSET,
+                     kBiteThreshold);
+  // Initialization is identical in the disabled case except the last
+  // write to AON_TIMER_WKUP_CTRL_REG is omitted.
+
+  watchdog_init(kTimeoutMs);
+}
+
+TEST_F(WatchdogTest, Pet) {
+  EXPECT_ABS_WRITE32(mmio_, wdog_ + AON_TIMER_WDOG_COUNT_REG_OFFSET, 0);
+  watchdog_pet();
+}
+
+TEST_F(WatchdogTest, Get) {
+  EXPECT_ABS_READ32(mmio_, wdog_ + AON_TIMER_WDOG_COUNT_REG_OFFSET, 12345);
+  EXPECT_EQ(watchdog_get(), 12345);
+}
+
+}  // namespace
+}  // namespace watchdog_unittest
diff --git a/test/systemtest/config.py b/test/systemtest/config.py
index c550bee..cf06729 100644
--- a/test/systemtest/config.py
+++ b/test/systemtest/config.py
@@ -145,5 +145,14 @@
         "test_dir": "sw/device/silicon_creator/testing",
         # Not running on sim_verilator because this test takes a long time to complete.
         "targets": ["fpga_cw310", "fpga_nexysvideo"],
+
+    },
+    {
+        "name": "sw_silicon_creator_lib_driver_watchdog_functest",
+        "test_dir": "sw/device/silicon_creator/testing",
+        # TODO(lowRISC/opentitan#6965) This test resets the chip and appears to
+        # cause a test failure on FPGA boards.  Restrict this test to
+        # verilator for now.
+        "targets": ["sim_verilator"],
     },
 ]