Add a qtest exercising the L2VIC interrupt controller's basic
register access and interrupt enable/disable behavior.
Signed-off-by: Brian Cain <brian.cain@oss.qualcomm.com>
---
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 1240d180b85..8201e487000 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
On 7/14/2026 8:51 AM, Brian Cain wrote: > Add a qtest exercising the L2VIC interrupt controller's basic > register access and interrupt enable/disable behavior. > > Signed-off-by: Brian Cain <brian.cain@oss.qualcomm.com> > --- > 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 > Reviewed-by: Pierrick Bouvier <pierrick.bouvier@oss.qualcomm.com>
© 2016 - 2026 Red Hat, Inc.