From: Igor Mammedov <[email protected]>

Signed-off-by: Igor Mammedov <[email protected]>
Message-id: [email protected]
[PMM: Added new file to same MAINTAINERS section as the
 gwdt implementation]

Reviewed-by: Peter Maydell <[email protected]>
Signed-off-by: Peter Maydell <[email protected]>
---
 MAINTAINERS                  |   1 +
 tests/qtest/sbsa-gwdt-test.c | 227 +++++++++++++++++++++++++++++++++++
 tests/qtest/meson.build      |   1 +
 3 files changed, 229 insertions(+)
 create mode 100644 tests/qtest/sbsa-gwdt-test.c

diff --git a/MAINTAINERS b/MAINTAINERS
index d9209f043f0..c61980ba13d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1075,6 +1075,7 @@ F: hw/arm/sbsa-ref.c
 F: hw/misc/sbsa_ec.c
 F: hw/watchdog/sbsa_gwdt.c
 F: include/hw/watchdog/sbsa_gwdt.h
+F: tests/qtest/sbsa-gwdt-test.c
 F: docs/system/arm/sbsa.rst
 F: tests/functional/aarch64/test_*sbsaref*.py
 
diff --git a/tests/qtest/sbsa-gwdt-test.c b/tests/qtest/sbsa-gwdt-test.c
new file mode 100644
index 00000000000..ae166778f74
--- /dev/null
+++ b/tests/qtest/sbsa-gwdt-test.c
@@ -0,0 +1,227 @@
+/*
+ * SBSA Generic Watchdog Timer (GWDT) testcase
+ *
+ * Copyright (C) 2026 Red Hat
+ *
+ * Authors:
+ *  Igor Mammedov <[email protected]>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/bitops.h"
+#include "qemu/timer.h"
+#include "libqtest-single.h"
+#include "qobject/qdict.h"
+
+#define GWDT_REFRESH_BASE   0x0f000000
+#define GWDT_CONTROL_BASE   0x0f001000
+#define GWDT_WRR            0x000
+#define GWDT_WCS            0x000
+#define GWDT_WORL           0x008
+#define GWDT_WORH           0x00C
+#define GWDT_WCVL           0x010
+#define GWDT_WCVH           0x014
+#define GWDT_W_IIDR         0xFCC
+
+#define GWDT_WCS_EN         BIT(0)
+#define GWDT_WCS_WS0        BIT(1)
+#define GWDT_WCS_WS1        BIT(2)
+
+#define GWDT_ID_VALUE       0x1043B
+
+static uint32_t ctrl_read(uint32_t offset)
+{
+    return readl(GWDT_CONTROL_BASE + offset);
+}
+
+static void ctrl_write(uint32_t offset, uint32_t value)
+{
+    writel(GWDT_CONTROL_BASE + offset, value);
+}
+
+static void refresh_write(uint32_t offset, uint32_t value)
+{
+    writel(GWDT_REFRESH_BASE + offset, value);
+}
+
+static void reset(void)
+{
+    qtest_system_reset(global_qtest);
+}
+
+/* Convert watchdog ticks to nanoseconds */
+static int64_t gwdt_ticks(uint32_t ticks)
+{
+    QDict *resp = qmp("{ 'execute': 'qom-get', 'arguments':"
+                      "{ 'path': '/machine/peripheral/gwdt',"
+                      "'property': 'clock-frequency' }}");
+    uint64_t freq = qdict_get_uint(resp, "return");
+    qobject_unref(resp);
+    return muldiv64(ticks, NANOSECONDS_PER_SECOND, freq);
+}
+
+static void test_identification(void)
+{
+    g_assert_cmphex(ctrl_read(GWDT_W_IIDR), ==, GWDT_ID_VALUE);
+    g_assert_cmphex(readl(GWDT_REFRESH_BASE + GWDT_W_IIDR), ==, GWDT_ID_VALUE);
+}
+
+static void test_enable_disable(void)
+{
+    uint32_t wcs;
+
+    reset();
+
+    ctrl_write(GWDT_WORL, 0);
+    ctrl_write(GWDT_WORH, 0xF0000000);
+
+    ctrl_write(GWDT_WCS, GWDT_WCS_EN);
+    wcs = ctrl_read(GWDT_WCS);
+    g_assert_cmphex(wcs & GWDT_WCS_EN, ==, GWDT_WCS_EN);
+
+    ctrl_write(GWDT_WCS, 0);
+    wcs = ctrl_read(GWDT_WCS);
+    g_assert_cmphex(wcs & GWDT_WCS_EN, ==, 0);
+
+    /* make sure no timer is armed after disable */
+    clock_step_next();
+    wcs = ctrl_read(GWDT_WCS);
+    g_assert_cmphex(wcs & GWDT_WCS_WS0, ==, 0);
+}
+
+static void test_refresh(void)
+{
+    uint32_t wcs;
+    const uint32_t wor = 100000;
+    const int64_t timeout = gwdt_ticks(wor);
+
+    reset();
+
+    ctrl_write(GWDT_WORL, wor);
+    ctrl_write(GWDT_WORH, 0);
+    ctrl_write(GWDT_WCS, GWDT_WCS_EN);
+
+    /* advance to before the deadline */
+    clock_step(timeout - 1);
+    wcs = ctrl_read(GWDT_WCS);
+    g_assert_cmphex(wcs & GWDT_WCS_WS0, ==, 0);
+
+    /* extend deadline and advance now to deadline - 1 */
+    refresh_write(GWDT_WRR, 0);
+    clock_step(timeout - 1);
+    wcs = ctrl_read(GWDT_WCS);
+    g_assert_cmphex(wcs & GWDT_WCS_WS0, ==, 0);
+
+    /* make sure that gwdt triggers on refresh deadline */
+    clock_step(1);
+    wcs = ctrl_read(GWDT_WCS);
+    g_assert_cmphex(wcs & GWDT_WCS_WS0, ==, GWDT_WCS_WS0);
+}
+
+static void test_wcv_load(void)
+{
+    uint32_t wcs;
+    const uint32_t wor = 10000;
+    const int64_t timeout = gwdt_ticks(wor);
+    int64_t now, deadline;
+
+    reset();
+
+    ctrl_write(GWDT_WORL, wor);
+    ctrl_write(GWDT_WORH, 0);
+    ctrl_write(GWDT_WCS, GWDT_WCS_EN);
+
+    /* explicit WCV refresh */
+    now = clock_step(1);
+    deadline = now + 2 * timeout;
+    ctrl_write(GWDT_WCVH, deadline >> 32);
+    ctrl_write(GWDT_WCVL, deadline);
+    wcs = ctrl_read(GWDT_WCS);
+    g_assert_cmphex(wcs & GWDT_WCS_WS0, ==, 0);
+
+    /* initial WOR deadline, no WS0 yet as WCV load pushed it further */
+    clock_step(timeout);
+    wcs = ctrl_read(GWDT_WCS);
+    g_assert_cmphex(wcs & GWDT_WCS_WS0, ==, 0);
+
+    /* right before WCV deadline, no WS0 */
+    clock_step(timeout - 1);
+    wcs = ctrl_read(GWDT_WCS);
+    g_assert_cmphex(wcs & GWDT_WCS_WS0, ==, 0);
+
+    /* advance to WCV deadline, WS0 is triggered */
+    clock_step(1);
+    wcs = ctrl_read(GWDT_WCS);
+    g_assert_cmphex(wcs & GWDT_WCS_WS0, ==, GWDT_WCS_WS0);
+}
+
+static void test_ws0_timeout(void)
+{
+    uint32_t wcs;
+
+    reset();
+
+    ctrl_write(GWDT_WORL, 10000);
+    ctrl_write(GWDT_WORH, 0);
+
+    ctrl_write(GWDT_WCS, GWDT_WCS_EN);
+    wcs = ctrl_read(GWDT_WCS);
+    g_assert_cmphex(wcs & GWDT_WCS_WS0, ==, 0);
+
+    /* advance clock to WS0 deadline */
+    clock_step_next();
+    wcs = ctrl_read(GWDT_WCS);
+    g_assert_cmphex(wcs & GWDT_WCS_WS0, ==, GWDT_WCS_WS0);
+}
+
+static void test_ws1_timeout(void)
+{
+    uint32_t wcs;
+
+    reset();
+
+    ctrl_write(GWDT_WORL, 10000);
+    ctrl_write(GWDT_WORH, 0);
+    ctrl_write(GWDT_WCS, GWDT_WCS_EN);
+
+    /* WS0 deadline test */
+    clock_step_next();
+    wcs = ctrl_read(GWDT_WCS);
+    g_assert_cmphex(wcs & GWDT_WCS_WS0, ==, GWDT_WCS_WS0);
+    g_assert_cmphex(wcs & GWDT_WCS_WS1, ==, 0);
+
+    /* WS1 deadline test */
+    clock_step_next();
+    wcs = ctrl_read(GWDT_WCS);
+    g_assert_cmphex(wcs & GWDT_WCS_WS0, ==, GWDT_WCS_WS0);
+    g_assert_cmphex(wcs & GWDT_WCS_WS1, ==, GWDT_WCS_WS1);
+}
+
+int main(int argc, char **argv)
+{
+    int ret;
+    const char *arch = qtest_get_arch();
+
+    g_test_init(&argc, &argv, NULL);
+
+    if (strcmp(arch, "aarch64") || !qtest_has_machine("virt")) {
+        g_test_skip("'aarch64/virt' machine is not available");
+        return 0;
+    }
+
+    qtest_add_func("/sbsa-gwdt/identification", test_identification);
+    qtest_add_func("/sbsa-gwdt/enable-disable", test_enable_disable);
+    qtest_add_func("/sbsa-gwdt/refresh", test_refresh);
+    qtest_add_func("/sbsa-gwdt/wcv-load", test_wcv_load);
+    qtest_add_func("/sbsa-gwdt/ws0-timeout", test_ws0_timeout);
+    qtest_add_func("/sbsa-gwdt/ws1-timeout", test_ws1_timeout);
+
+    qtest_start("-M virt -cpu max -device sbsa-gwdt,id=gwdt "
+                "-watchdog-action none");
+    ret = g_test_run();
+    qtest_end();
+
+    return ret;
+}
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index 2d01837ff29..46685d6d46f 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -292,6 +292,7 @@ qtests_aarch64 = \
   (config_all_devices.has_key('CONFIG_IOMMU_TESTDEV') and
    config_all_devices.has_key('CONFIG_ARM_VIRT') ?
    ['iommu-smmuv3-test'] : []) + \
+  (config_all_devices.has_key('CONFIG_WDT_SBSA') ? ['sbsa-gwdt-test'] : []) + \
   qtests_cxl +                                                                 
                 \
   ['arm-cpu-features',
    'numa-test',
-- 
2.43.0


Reply via email to