Add a qtest exercising the L2VIC interrupt controller's basic
register access and interrupt enable/disable behavior.

Reviewed-by: Pierrick Bouvier <[email protected]>
Signed-off-by: Brian Cain <[email protected]>
---
 MAINTAINERS              |   1 +
 tests/qtest/l2vic-test.c | 242 +++++++++++++++++++++++++++++++++++++++
 tests/qtest/meson.build  |   2 +-
 3 files changed, 244 insertions(+), 1 deletion(-)
 create mode 100644 tests/qtest/l2vic-test.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 7281a281632..b7012e945e2 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -252,6 +252,7 @@ S: Supported
 F: target/hexagon/
 F: hw/intc/hex-l2vic.c
 F: include/hw/intc/hex-l2vic.h
+F: tests/qtest/l2vic-test.c
 X: target/hexagon/idef-parser/
 X: target/hexagon/gen_idef_parser_funcs.py
 F: linux-user/hexagon/
diff --git a/tests/qtest/l2vic-test.c b/tests/qtest/l2vic-test.c
new file mode 100644
index 00000000000..9887e458a6f
--- /dev/null
+++ b/tests/qtest/l2vic-test.c
@@ -0,0 +1,242 @@
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * QTest testcase for the L2VIC Interrupt Controller
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest-single.h"
+#include "hw/intc/hex-l2vic.h"
+
+/* Base addresses for L2VIC */
+#define L2VIC_BASE 0xfc910000
+#define L2VIC_FAST_BASE 0xfc920000
+
+/* Helper functions */
+static uint32_t l2vic_read32(uint64_t base, uint32_t offset)
+{
+    return readl(base + offset);
+}
+
+static void l2vic_write32(uint64_t base, uint32_t offset, uint32_t value)
+{
+    writel(base + offset, value);
+}
+
+/* Test basic register access */
+static void test_l2vic_register_access(void)
+{
+    uint32_t val;
+
+    /* Test that basic registers can be read/written */
+    l2vic_write32(L2VIC_BASE, L2VIC_INT_ENABLE_SETn, 0x1);
+    val = l2vic_read32(L2VIC_BASE, L2VIC_INT_ENABLEn);
+    /* Should have enabled interrupt 0 */
+    g_assert_cmpuint(val & 0x1, ==, 0x1);
+
+    /* Clear it */
+    l2vic_write32(L2VIC_BASE, L2VIC_INT_ENABLE_CLEARn, 0x1);
+    val = l2vic_read32(L2VIC_BASE, L2VIC_INT_ENABLEn);
+    /* Should be cleared now */
+    g_assert_cmpuint(val & 0x1, ==, 0x0);
+}
+
+/* Test interrupt enable/disable */
+static void test_l2vic_interrupt_enable(void)
+{
+    uint32_t val;
+
+    /* Initially all interrupts should be disabled */
+    val = l2vic_read32(L2VIC_BASE, L2VIC_INT_ENABLEn);
+    g_assert_cmpuint(val, ==, 0);
+
+    /* Enable some interrupts */
+    /* Enable IRQ 0 and 2 */
+    l2vic_write32(L2VIC_BASE, L2VIC_INT_ENABLE_SETn, 0x5);
+    val = l2vic_read32(L2VIC_BASE, L2VIC_INT_ENABLEn);
+    g_assert_cmpuint(val & 0x5, ==, 0x5);
+
+    /* Disable one */
+    /* Disable IRQ 0 */
+    l2vic_write32(L2VIC_BASE, L2VIC_INT_ENABLE_CLEARn, 0x1);
+    val = l2vic_read32(L2VIC_BASE, L2VIC_INT_ENABLEn);
+    g_assert_cmpuint(val & 0x1, ==, 0x0);  /* IRQ 0 should be disabled */
+    g_assert_cmpuint(val & 0x4, ==, 0x4);  /* IRQ 2 should still be enabled */
+}
+
+/* Test register read/write without triggering interrupts */
+static void test_l2vic_basic_functionality(void)
+{
+    l2vic_read32(L2VIC_BASE, L2VIC_INT_ENABLEn);
+    l2vic_read32(L2VIC_BASE, L2VIC_INT_PENDINGn);
+    l2vic_read32(L2VIC_BASE, L2VIC_INT_STATUSn);
+    l2vic_read32(L2VIC_BASE, L2VIC_INT_TYPEn);
+
+    /* Exercise write-only registers */
+    l2vic_write32(L2VIC_BASE, L2VIC_INT_ENABLE_SETn, 0);
+    l2vic_write32(L2VIC_BASE, L2VIC_INT_ENABLE_CLEARn, 0);
+}
+
+/*
+ * Test L2VIC IRQ outputs
+ */
+static void test_l2vic_irq_outputs(void)
+{
+    uint32_t val;
+
+    /* Clear any previous configuration */
+    l2vic_write32(L2VIC_BASE, L2VIC_INT_ENABLE_CLEARn, 0xFFFFFFFF);
+    l2vic_write32(L2VIC_BASE, L2VIC_INT_CLEARn, 0xFFFFFFFF);
+    /* Reset all to level-triggered */
+    l2vic_write32(L2VIC_BASE, L2VIC_INT_TYPEn, 0);
+
+    /* Test Group 0 interrupts (default group: IRQ2) */
+    /* Configure interrupt 0 as edge-triggered (required for soft interrupts) 
*/
+    l2vic_write32(L2VIC_BASE, L2VIC_INT_TYPEn, 0x1);
+    /* Enable interrupt 0 and generate software interrupt */
+    l2vic_write32(L2VIC_BASE, L2VIC_INT_ENABLE_SETn, 0x1);
+    l2vic_write32(L2VIC_BASE, L2VIC_SOFT_INTn, 0x1);
+
+    /* Verify interrupt is active (would assert IRQ2 in real hardware) */
+    /* In L2VIC: pending interrupts become active and clear from pending */
+    val = l2vic_read32(L2VIC_BASE, L2VIC_INT_STATUSn);
+    g_assert_cmpuint(val & 0x1, ==, 0x1);
+
+    /* Clear the interrupt using INT_CLEARn register */
+    l2vic_write32(L2VIC_BASE, L2VIC_INT_CLEARn, 0x1);
+    val = l2vic_read32(L2VIC_BASE, L2VIC_INT_STATUSn);
+    g_assert_cmpuint(val & 0x1, ==, 0x0);
+
+    /* Test Group 1 interrupts: IRQ3 */
+    /* Configure interrupt 1 as edge-triggered and to group 1 */
+    l2vic_write32(L2VIC_BASE, L2VIC_INT_TYPEn, 0x2);
+    /* Set bit 1 for interrupt 1 */
+    l2vic_write32(L2VIC_BASE, L2VIC_INT_GRPn_1, 0x2);
+    l2vic_write32(L2VIC_BASE, L2VIC_INT_ENABLE_SETn, 0x2);
+    l2vic_write32(L2VIC_BASE, L2VIC_SOFT_INTn, 0x2);
+
+    /* Verify interrupt is active in group 1 (would assert IRQ3) */
+    val = l2vic_read32(L2VIC_BASE, L2VIC_INT_STATUSn);
+    g_assert_cmpuint(val & 0x2, ==, 0x2);
+    val = l2vic_read32(L2VIC_BASE, L2VIC_INT_GRPn_1);
+    g_assert_cmpuint(val & 0x2, ==, 0x2);
+
+    /* Clear the interrupt using INT_CLEARn register */
+    l2vic_write32(L2VIC_BASE, L2VIC_INT_CLEARn, 0x2);
+
+    /* Test Group 2 interrupts: IRQ4 */
+    /* Configure interrupt 2 as edge-triggered and to group 2 */
+    l2vic_write32(L2VIC_BASE, L2VIC_INT_TYPEn, 0x4);
+    /* Set bit 2 for interrupt 2 */
+    l2vic_write32(L2VIC_BASE, L2VIC_INT_GRPn_2, 0x4);
+    l2vic_write32(L2VIC_BASE, L2VIC_INT_ENABLE_SETn, 0x4);
+    l2vic_write32(L2VIC_BASE, L2VIC_SOFT_INTn, 0x4);
+
+    /* Verify interrupt is active in group 2 (would assert IRQ4) */
+    val = l2vic_read32(L2VIC_BASE, L2VIC_INT_STATUSn);
+    g_assert_cmpuint(val & 0x4, ==, 0x4);
+    val = l2vic_read32(L2VIC_BASE, L2VIC_INT_GRPn_2);
+    g_assert_cmpuint(val & 0x4, ==, 0x4);
+
+    /* Clear the interrupt using INT_CLEARn register */
+    l2vic_write32(L2VIC_BASE, L2VIC_INT_CLEARn, 0x4);
+
+    /* Test Group 3 interrupts: IRQ5 */
+    /* Configure interrupt 3 as edge-triggered and to group 3 */
+    l2vic_write32(L2VIC_BASE, L2VIC_INT_TYPEn, 0x8);
+    /* Set bit 3 for interrupt 3 */
+    l2vic_write32(L2VIC_BASE, L2VIC_INT_GRPn_3, 0x8);
+    l2vic_write32(L2VIC_BASE, L2VIC_INT_ENABLE_SETn, 0x8);
+    l2vic_write32(L2VIC_BASE, L2VIC_SOFT_INTn, 0x8);
+
+    /* Verify interrupt is active in group 3 (would assert IRQ5) */
+    val = l2vic_read32(L2VIC_BASE, L2VIC_INT_STATUSn);
+    g_assert_cmpuint(val & 0x8, ==, 0x8);
+    val = l2vic_read32(L2VIC_BASE, L2VIC_INT_GRPn_3);
+    g_assert_cmpuint(val & 0x8, ==, 0x8);
+
+    /* Clear the interrupt using INT_CLEARn register */
+    l2vic_write32(L2VIC_BASE, L2VIC_INT_CLEARn, 0x8);
+
+    /* Test multiple pending interrupts (only one can be active at a time) */
+    /* Configure all interrupts 0-3 as edge-triggered */
+    l2vic_write32(L2VIC_BASE, L2VIC_INT_TYPEn, 0xF);
+    /* Enable interrupts 0-3 */
+    l2vic_write32(L2VIC_BASE, L2VIC_INT_ENABLE_SETn, 0xF);
+    /* Generate all 4 interrupts */
+    l2vic_write32(L2VIC_BASE, L2VIC_SOFT_INTn, 0xF);
+
+    /* Verify that highest prio interrupt becomes active */
+    val = l2vic_read32(L2VIC_BASE, L2VIC_INT_STATUSn);
+    /* At least one bit should be set, but not necessarily all */
+    g_assert_cmpuint(val & 0xF, !=, 0x0);
+
+    /* Clear all active interrupts */
+    val = l2vic_read32(L2VIC_BASE, L2VIC_INT_STATUSn);
+    l2vic_write32(L2VIC_BASE, L2VIC_INT_CLEARn, val & 0xF);
+
+    /* After clearing, check if more interrupts become active */
+    val = l2vic_read32(L2VIC_BASE, L2VIC_INT_STATUSn);
+    /* Could be 0 if all processed, or have more if pending */
+
+    /* Test that software interrupts require edge-triggered configuration */
+    /* Configure interrupt 5 as level-triggered (default) */
+    l2vic_write32(L2VIC_BASE, L2VIC_INT_TYPEn, 0x0);
+    /* Enable interrupt 5 */
+    l2vic_write32(L2VIC_BASE, L2VIC_INT_ENABLE_SETn, 0x20);
+    /* Try to generate level interrupt */
+    l2vic_write32(L2VIC_BASE, L2VIC_SOFT_INTn, 0x20);
+
+    /* Should not work - no interrupt should be active */
+    val = l2vic_read32(L2VIC_BASE, L2VIC_INT_STATUSn);
+    /* No interrupt because level-triggered */
+    g_assert_cmpuint(val & 0x20, ==, 0x0);
+
+    /* Now configure as edge-triggered and try again */
+    /* Set edge-triggered */
+    l2vic_write32(L2VIC_BASE, L2VIC_INT_TYPEn, 0x20);
+    /* Generate interrupt */
+    l2vic_write32(L2VIC_BASE, L2VIC_SOFT_INTn, 0x20);
+    val = l2vic_read32(L2VIC_BASE, L2VIC_INT_STATUSn);
+    g_assert_cmpuint(val & 0x20, ==, 0x20);
+
+    /* Clean up - clear all configuration */
+    l2vic_write32(L2VIC_BASE, L2VIC_INT_ENABLE_CLEARn, 0xFFFFFFFF);
+    l2vic_write32(L2VIC_BASE, L2VIC_INT_CLEARn, 0xFFFFFFFF);
+    l2vic_write32(L2VIC_BASE, L2VIC_INT_GRPn_0, 0);
+    l2vic_write32(L2VIC_BASE, L2VIC_INT_GRPn_1, 0);
+    l2vic_write32(L2VIC_BASE, L2VIC_INT_GRPn_2, 0);
+    l2vic_write32(L2VIC_BASE, L2VIC_INT_GRPn_3, 0);
+}
+
+static void test_l2vic_on_machine(gconstpointer data)
+{
+    const char *machine = (const char *)data;
+    g_autofree char *args = g_strdup_printf("-machine %s", machine);
+
+    qtest_start(args);
+
+    /* Run all the L2VIC tests */
+    test_l2vic_register_access();
+    test_l2vic_interrupt_enable();
+    test_l2vic_basic_functionality();
+    test_l2vic_irq_outputs();
+
+    qtest_end();
+}
+
+int main(int argc, char **argv)
+{
+    g_test_init(&argc, &argv, NULL);
+
+    /* Test on virt machine */
+    qtest_add_data_func("/l2vic/virt/all-tests", "virt",
+                        test_l2vic_on_machine);
+
+    /* Test on V66G_1024 machine */
+    qtest_add_data_func("/l2vic/V66G_1024/all-tests", "V66G_1024",
+                        test_l2vic_on_machine);
+
+    return g_test_run();
+}
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index 56ff860e216..a76863c7dd6 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -299,7 +299,7 @@ qtests_riscv64 = ['riscv-csr-test'] + \
    ['iommu-riscv-test'] : []) + \
   (config_all_devices.has_key('CONFIG_K230') ? ['k230-wdt-test'] : [])
 
-qtests_hexagon = ['boot-serial-test']
+qtests_hexagon = ['boot-serial-test', 'l2vic-test']
 
 qos_test_ss = ss.source_set()
 qos_test_ss.add(
-- 
2.34.1

Reply via email to