[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"],
},
]