aboutsummaryrefslogtreecommitdiff
path: root/tests/qtest/npcm7xx_watchdog_timer-test.c
diff options
context:
space:
mode:
Diffstat (limited to 'tests/qtest/npcm7xx_watchdog_timer-test.c')
-rw-r--r--tests/qtest/npcm7xx_watchdog_timer-test.c319
1 files changed, 319 insertions, 0 deletions
diff --git a/tests/qtest/npcm7xx_watchdog_timer-test.c b/tests/qtest/npcm7xx_watchdog_timer-test.c
new file mode 100644
index 0000000000..54d5d6d8f2
--- /dev/null
+++ b/tests/qtest/npcm7xx_watchdog_timer-test.c
@@ -0,0 +1,319 @@
+/*
+ * QTests for Nuvoton NPCM7xx Timer Watchdog Modules.
+ *
+ * Copyright 2020 Google LLC
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/timer.h"
+
+#include "libqos/libqtest.h"
+#include "qapi/qmp/qdict.h"
+
+#define WTCR_OFFSET 0x1c
+#define REF_HZ (25000000)
+
+/* WTCR bit fields */
+#define WTCLK(rv) ((rv) << 10)
+#define WTE BIT(7)
+#define WTIE BIT(6)
+#define WTIS(rv) ((rv) << 4)
+#define WTIF BIT(3)
+#define WTRF BIT(2)
+#define WTRE BIT(1)
+#define WTR BIT(0)
+
+typedef struct Watchdog {
+ int irq;
+ uint64_t base_addr;
+} Watchdog;
+
+static const Watchdog watchdog_list[] = {
+ {
+ .irq = 47,
+ .base_addr = 0xf0008000
+ },
+ {
+ .irq = 48,
+ .base_addr = 0xf0009000
+ },
+ {
+ .irq = 49,
+ .base_addr = 0xf000a000
+ }
+};
+
+static int watchdog_index(const Watchdog *wd)
+{
+ ptrdiff_t diff = wd - watchdog_list;
+
+ g_assert(diff >= 0 && diff < ARRAY_SIZE(watchdog_list));
+
+ return diff;
+}
+
+static uint32_t watchdog_read_wtcr(QTestState *qts, const Watchdog *wd)
+{
+ return qtest_readl(qts, wd->base_addr + WTCR_OFFSET);
+}
+
+static void watchdog_write_wtcr(QTestState *qts, const Watchdog *wd,
+ uint32_t value)
+{
+ qtest_writel(qts, wd->base_addr + WTCR_OFFSET, value);
+}
+
+static uint32_t watchdog_prescaler(QTestState *qts, const Watchdog *wd)
+{
+ switch (extract32(watchdog_read_wtcr(qts, wd), 10, 2)) {
+ case 0:
+ return 1;
+ case 1:
+ return 256;
+ case 2:
+ return 2048;
+ case 3:
+ return 65536;
+ default:
+ g_assert_not_reached();
+ }
+}
+
+static QDict *get_watchdog_action(QTestState *qts)
+{
+ QDict *ev = qtest_qmp_eventwait_ref(qts, "WATCHDOG");
+ QDict *data;
+
+ data = qdict_get_qdict(ev, "data");
+ qobject_ref(data);
+ qobject_unref(ev);
+ return data;
+}
+
+#define RESET_CYCLES 1024
+static uint32_t watchdog_interrupt_cycles(QTestState *qts, const Watchdog *wd)
+{
+ uint32_t wtis = extract32(watchdog_read_wtcr(qts, wd), 4, 2);
+ return 1 << (14 + 2 * wtis);
+}
+
+static int64_t watchdog_calculate_steps(uint32_t count, uint32_t prescale)
+{
+ return (NANOSECONDS_PER_SECOND / REF_HZ) * count * prescale;
+}
+
+static int64_t watchdog_interrupt_steps(QTestState *qts, const Watchdog *wd)
+{
+ return watchdog_calculate_steps(watchdog_interrupt_cycles(qts, wd),
+ watchdog_prescaler(qts, wd));
+}
+
+/* Check wtcr can be reset to default value */
+static void test_init(gconstpointer watchdog)
+{
+ const Watchdog *wd = watchdog;
+ QTestState *qts = qtest_init("-machine quanta-gsj");
+
+ qtest_irq_intercept_in(qts, "/machine/soc/a9mpcore/gic");
+
+ watchdog_write_wtcr(qts, wd, WTCLK(1) | WTRF | WTIF | WTR);
+ g_assert_cmphex(watchdog_read_wtcr(qts, wd), ==, WTCLK(1));
+
+ qtest_quit(qts);
+}
+
+/* Check a watchdog can generate interrupt and reset actions */
+static void test_reset_action(gconstpointer watchdog)
+{
+ const Watchdog *wd = watchdog;
+ QTestState *qts = qtest_init("-machine quanta-gsj");
+ QDict *ad;
+
+ qtest_irq_intercept_in(qts, "/machine/soc/a9mpcore/gic");
+
+ watchdog_write_wtcr(qts, wd,
+ WTCLK(0) | WTE | WTRF | WTRE | WTIF | WTIE | WTR);
+ g_assert_cmphex(watchdog_read_wtcr(qts, wd), ==,
+ WTCLK(0) | WTE | WTRE | WTIE);
+
+ /* Check a watchdog can generate an interrupt */
+ qtest_clock_step(qts, watchdog_interrupt_steps(qts, wd));
+ g_assert_cmphex(watchdog_read_wtcr(qts, wd), ==,
+ WTCLK(0) | WTE | WTIF | WTIE | WTRE);
+ g_assert_true(qtest_get_irq(qts, wd->irq));
+
+ /* Check a watchdog can generate a reset signal */
+ qtest_clock_step(qts, watchdog_calculate_steps(RESET_CYCLES,
+ watchdog_prescaler(qts, wd)));
+ ad = get_watchdog_action(qts);
+ /* The signal is a reset signal */
+ g_assert_false(strcmp(qdict_get_str(ad, "action"), "reset"));
+ qobject_unref(ad);
+ qtest_qmp_eventwait(qts, "RESET");
+ /*
+ * Make sure WTCR is reset to default except for WTRF bit which shouldn't
+ * be reset.
+ */
+ g_assert_cmphex(watchdog_read_wtcr(qts, wd), ==, WTCLK(1) | WTRF);
+ qtest_quit(qts);
+}
+
+/* Check a watchdog works with all possible WTCLK prescalers and WTIS cycles */
+static void test_prescaler(gconstpointer watchdog)
+{
+ const Watchdog *wd = watchdog;
+
+ for (int wtclk = 0; wtclk < 4; ++wtclk) {
+ for (int wtis = 0; wtis < 4; ++wtis) {
+ QTestState *qts = qtest_init("-machine quanta-gsj");
+
+ qtest_irq_intercept_in(qts, "/machine/soc/a9mpcore/gic");
+ watchdog_write_wtcr(qts, wd,
+ WTCLK(wtclk) | WTE | WTIF | WTIS(wtis) | WTIE | WTR);
+ /*
+ * The interrupt doesn't fire until watchdog_interrupt_steps()
+ * cycles passed
+ */
+ qtest_clock_step(qts, watchdog_interrupt_steps(qts, wd) - 1);
+ g_assert_false(watchdog_read_wtcr(qts, wd) & WTIF);
+ g_assert_false(qtest_get_irq(qts, wd->irq));
+ qtest_clock_step(qts, 1);
+ g_assert_true(watchdog_read_wtcr(qts, wd) & WTIF);
+ g_assert_true(qtest_get_irq(qts, wd->irq));
+
+ qtest_quit(qts);
+ }
+ }
+}
+
+/*
+ * Check a watchdog doesn't fire if corresponding flags (WTIE and WTRE) are not
+ * set.
+ */
+static void test_enabling_flags(gconstpointer watchdog)
+{
+ const Watchdog *wd = watchdog;
+ QTestState *qts;
+
+ /* Neither WTIE or WTRE is set, no interrupt or reset should happen */
+ qts = qtest_init("-machine quanta-gsj");
+ qtest_irq_intercept_in(qts, "/machine/soc/a9mpcore/gic");
+ watchdog_write_wtcr(qts, wd, WTCLK(0) | WTE | WTIF | WTRF | WTR);
+ qtest_clock_step(qts, watchdog_interrupt_steps(qts, wd));
+ g_assert_true(watchdog_read_wtcr(qts, wd) & WTIF);
+ g_assert_false(qtest_get_irq(qts, wd->irq));
+ qtest_clock_step(qts, watchdog_calculate_steps(RESET_CYCLES,
+ watchdog_prescaler(qts, wd)));
+ g_assert_true(watchdog_read_wtcr(qts, wd) & WTIF);
+ g_assert_false(watchdog_read_wtcr(qts, wd) & WTRF);
+ qtest_quit(qts);
+
+ /* Only WTIE is set, interrupt is triggered but reset should not happen */
+ qts = qtest_init("-machine quanta-gsj");
+ qtest_irq_intercept_in(qts, "/machine/soc/a9mpcore/gic");
+ watchdog_write_wtcr(qts, wd, WTCLK(0) | WTE | WTIF | WTIE | WTRF | WTR);
+ qtest_clock_step(qts, watchdog_interrupt_steps(qts, wd));
+ g_assert_true(watchdog_read_wtcr(qts, wd) & WTIF);
+ g_assert_true(qtest_get_irq(qts, wd->irq));
+ qtest_clock_step(qts, watchdog_calculate_steps(RESET_CYCLES,
+ watchdog_prescaler(qts, wd)));
+ g_assert_true(watchdog_read_wtcr(qts, wd) & WTIF);
+ g_assert_false(watchdog_read_wtcr(qts, wd) & WTRF);
+ qtest_quit(qts);
+
+ /* Only WTRE is set, interrupt is triggered but reset should not happen */
+ qts = qtest_init("-machine quanta-gsj");
+ qtest_irq_intercept_in(qts, "/machine/soc/a9mpcore/gic");
+ watchdog_write_wtcr(qts, wd, WTCLK(0) | WTE | WTIF | WTRE | WTRF | WTR);
+ qtest_clock_step(qts, watchdog_interrupt_steps(qts, wd));
+ g_assert_true(watchdog_read_wtcr(qts, wd) & WTIF);
+ g_assert_false(qtest_get_irq(qts, wd->irq));
+ qtest_clock_step(qts, watchdog_calculate_steps(RESET_CYCLES,
+ watchdog_prescaler(qts, wd)));
+ g_assert_false(strcmp(qdict_get_str(get_watchdog_action(qts), "action"),
+ "reset"));
+ qtest_qmp_eventwait(qts, "RESET");
+ qtest_quit(qts);
+
+ /*
+ * The case when both flags are set is already tested in
+ * test_reset_action().
+ */
+}
+
+/* Check a watchdog can pause and resume by setting WTE bits */
+static void test_pause(gconstpointer watchdog)
+{
+ const Watchdog *wd = watchdog;
+ QTestState *qts;
+ int64_t remaining_steps, steps;
+
+ qts = qtest_init("-machine quanta-gsj");
+ qtest_irq_intercept_in(qts, "/machine/soc/a9mpcore/gic");
+ watchdog_write_wtcr(qts, wd, WTCLK(0) | WTE | WTIF | WTIE | WTRF | WTR);
+ remaining_steps = watchdog_interrupt_steps(qts, wd);
+ g_assert_cmphex(watchdog_read_wtcr(qts, wd), ==, WTCLK(0) | WTE | WTIE);
+
+ /* Run for half of the execution period. */
+ steps = remaining_steps / 2;
+ remaining_steps -= steps;
+ qtest_clock_step(qts, steps);
+
+ /* Pause the watchdog */
+ watchdog_write_wtcr(qts, wd, WTCLK(0) | WTIE);
+ g_assert_cmphex(watchdog_read_wtcr(qts, wd), ==, WTCLK(0) | WTIE);
+
+ /* Run for a long period of time, the watchdog shouldn't fire */
+ qtest_clock_step(qts, steps << 4);
+ g_assert_cmphex(watchdog_read_wtcr(qts, wd), ==, WTCLK(0) | WTIE);
+ g_assert_false(qtest_get_irq(qts, wd->irq));
+
+ /* Resume the watchdog */
+ watchdog_write_wtcr(qts, wd, WTCLK(0) | WTE | WTIE);
+ g_assert_cmphex(watchdog_read_wtcr(qts, wd), ==, WTCLK(0) | WTE | WTIE);
+
+ /* Run for the reset of the execution period, the watchdog should fire */
+ qtest_clock_step(qts, remaining_steps);
+ g_assert_cmphex(watchdog_read_wtcr(qts, wd), ==,
+ WTCLK(0) | WTE | WTIF | WTIE);
+ g_assert_true(qtest_get_irq(qts, wd->irq));
+
+ qtest_quit(qts);
+}
+
+static void watchdog_add_test(const char *name, const Watchdog* wd,
+ GTestDataFunc fn)
+{
+ g_autofree char *full_name = g_strdup_printf(
+ "npcm7xx_watchdog_timer[%d]/%s", watchdog_index(wd), name);
+ qtest_add_data_func(full_name, wd, fn);
+}
+#define add_test(name, td) watchdog_add_test(#name, td, test_##name)
+
+int main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+ g_test_set_nonfatal_assertions();
+
+ for (int i = 0; i < ARRAY_SIZE(watchdog_list); ++i) {
+ const Watchdog *wd = &watchdog_list[i];
+
+ add_test(init, wd);
+ add_test(reset_action, wd);
+ add_test(prescaler, wd);
+ add_test(enabling_flags, wd);
+ add_test(pause, wd);
+ }
+
+ return g_test_run();
+}