[PATCH v5 13/18] tests/qtest: add L2VIC qtest
Brian Cain <[email protected]> Wed, 5 Aug 2026 21:27:18 -0700
| Newsgroups | gmane.comp.emulators.qemu |
|---|---|
| Message-ID | <[email protected]> |
Add a qtest exercising L2VIC register access and interrupt enable/disable. Reviewed-by: Pierrick Bouvier <[email protected]> Signed-off-by: Brian Cain <[email protected]> --- MAINTAINERS | 1 + tests/qtest/l2vic-test.c | 249 +++++++++++++++++++++++++++++++++++++++ tests/qtest/meson.build | 2 +- 3 files changed, 251 insertions(+), 1 deletion(-) create mode 100644 tests/qtest/l2vic-test.c diff --git a/MAINTAINERS b/MAINTAINERS index 56b12a1af04..678b7bacd79 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..adb4dc7281e --- /dev/null +++ b/tests/qtest/l2vic-test.c @@ -0,0 +1,249 @@ +/* + * 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/hexagon/hexagon.h" + +#include "hw/hexagon/machine_cfg_v66g_1024.h.inc" +#include "hw/hexagon/machine_cfg_v68n_1024.h.inc" + +/* L2VIC register offsets exercised by this test */ +#define L2VIC_INT_ENABLEn 0x100 /* Read/Write */ +#define L2VIC_INT_ENABLE_CLEARn 0x180 /* Write */ +#define L2VIC_INT_ENABLE_SETn 0x200 /* Write */ +#define L2VIC_INT_TYPEn 0x280 /* Read/Write */ +#define L2VIC_INT_STATUSn 0x380 /* Read */ +#define L2VIC_INT_CLEARn 0x400 /* Write */ +#define L2VIC_SOFT_INTn 0x480 /* Write */ +#define L2VIC_INT_PENDINGn 0x500 /* Read */ +#define L2VIC_INT_GRPn_0 0x600 /* Read/Write */ +#define L2VIC_INT_GRPn_1 0x680 /* Read/Write */ +#define L2VIC_INT_GRPn_2 0x700 /* Read/Write */ +#define L2VIC_INT_GRPn_3 0x780 /* Read/Write */ + +/* + * VID group readback: records which irq last fired through each VID + * group. Outputs themselves are momentary pulses (see l2vic_update()), + * so these registers -- not the qtest IRQ level snapshot -- are how the + * test observes VID steering. + */ +#define L2VIC_VID_GRP_0 0x0 +#define L2VIC_VID_GRP_1 0x4 +#define L2VIC_VID_GRP_2 0x8 +#define L2VIC_VID_GRP_3 0xC + +typedef struct { + const char *machine; + const struct hexagon_machine_config *cfg; +} L2VICMachineCfg; + +static const L2VICMachineCfg l2vic_machines[] = { + { "virt", &v68n_1024 }, + { "V66G_1024", &v66g_1024 }, +}; + +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); +} + +static void test_l2vic_register_access(uint64_t base) +{ + uint32_t val; + + l2vic_write32(base, L2VIC_INT_ENABLE_SETn, 0x1); + val = l2vic_read32(base, L2VIC_INT_ENABLEn); + g_assert_cmpuint(val & 0x1, ==, 0x1); + + l2vic_write32(base, L2VIC_INT_ENABLE_CLEARn, 0x1); + val = l2vic_read32(base, L2VIC_INT_ENABLEn); + g_assert_cmpuint(val & 0x1, ==, 0x0); +} + +static void test_l2vic_interrupt_enable(uint64_t base) +{ + uint32_t val; + + val = l2vic_read32(base, L2VIC_INT_ENABLEn); + g_assert_cmpuint(val, ==, 0); + + /* Enable IRQ 0 and 2 */ + l2vic_write32(base, L2VIC_INT_ENABLE_SETn, 0x5); + val = l2vic_read32(base, L2VIC_INT_ENABLEn); + g_assert_cmpuint(val & 0x5, ==, 0x5); + + /* Disable IRQ 0, leaving IRQ 2 enabled */ + l2vic_write32(base, L2VIC_INT_ENABLE_CLEARn, 0x1); + val = l2vic_read32(base, L2VIC_INT_ENABLEn); + g_assert_cmpuint(val & 0x1, ==, 0x0); + g_assert_cmpuint(val & 0x4, ==, 0x4); +} + +static void test_l2vic_basic_functionality(uint64_t base) +{ + l2vic_read32(base, L2VIC_INT_ENABLEn); + l2vic_read32(base, L2VIC_INT_PENDINGn); + l2vic_read32(base, L2VIC_INT_STATUSn); + l2vic_read32(base, L2VIC_INT_TYPEn); + + l2vic_write32(base, L2VIC_INT_ENABLE_SETn, 0); + l2vic_write32(base, L2VIC_INT_ENABLE_CLEARn, 0); +} + +/* + * IRQs 0-7 pack their group-enable/VID-select nibbles into + * L2VIC_INT_GRPn_0 (int_group_n[0]), 4 bits per irq: bit 3 enables + * VID steering, bits 0-2 select the VID group (0-3), which pulses + * output line vid+2. + */ +static void l2vic_set_vid_group(uint64_t base, int irq, int vid) +{ + uint32_t val = l2vic_read32(base, L2VIC_INT_GRPn_0); + uint32_t nibble = 0x8 | (vid & 0x7); + + val &= ~(0xFu << (irq * 4)); + val |= nibble << (irq * 4); + l2vic_write32(base, L2VIC_INT_GRPn_0, val); +} + +static void test_l2vic_irq_outputs(uint64_t base) +{ + uint32_t val; + + l2vic_write32(base, L2VIC_INT_ENABLE_CLEARn, 0xFFFFFFFF); + l2vic_write32(base, L2VIC_INT_CLEARn, 0xFFFFFFFF); + l2vic_write32(base, L2VIC_INT_TYPEn, 0); + l2vic_write32(base, L2VIC_INT_GRPn_0, 0); + + /* Group 0 / IRQ2: soft interrupts require edge-triggered config */ + l2vic_write32(base, L2VIC_INT_TYPEn, 0x1); + l2vic_write32(base, L2VIC_INT_ENABLE_SETn, 0x1); + l2vic_write32(base, L2VIC_SOFT_INTn, 0x1); + + val = l2vic_read32(base, L2VIC_INT_STATUSn); + g_assert_cmpuint(val & 0x1, ==, 0x1); + /* Default VID group (0) records the delivering irq, output line 2 */ + g_assert_cmpuint(l2vic_read32(base, L2VIC_VID_GRP_0), ==, 0); + + l2vic_write32(base, L2VIC_INT_CLEARn, 0x1); + val = l2vic_read32(base, L2VIC_INT_STATUSn); + g_assert_cmpuint(val & 0x1, ==, 0x0); + + /* IRQ1 steered to VID group 1 -> output line 3 */ + l2vic_write32(base, L2VIC_INT_TYPEn, 0x2); + l2vic_set_vid_group(base, 1, 1); + l2vic_write32(base, L2VIC_INT_ENABLE_SETn, 0x2); + l2vic_write32(base, L2VIC_SOFT_INTn, 0x2); + + val = l2vic_read32(base, L2VIC_INT_STATUSn); + g_assert_cmpuint(val & 0x2, ==, 0x2); + g_assert_cmpuint(l2vic_read32(base, L2VIC_VID_GRP_1), ==, 1); + + l2vic_write32(base, L2VIC_INT_CLEARn, 0x2); + + /* IRQ4 steered to VID group 2 -> output line 4 */ + l2vic_write32(base, L2VIC_INT_TYPEn, 0x10); + l2vic_set_vid_group(base, 4, 2); + l2vic_write32(base, L2VIC_INT_ENABLE_SETn, 0x10); + l2vic_write32(base, L2VIC_SOFT_INTn, 0x10); + + val = l2vic_read32(base, L2VIC_INT_STATUSn); + g_assert_cmpuint(val & 0x10, ==, 0x10); + g_assert_cmpuint(l2vic_read32(base, L2VIC_VID_GRP_2), ==, 4); + + l2vic_write32(base, L2VIC_INT_CLEARn, 0x10); + + /* IRQ5 steered to VID group 3 -> output line 5 */ + l2vic_write32(base, L2VIC_INT_TYPEn, 0x20); + l2vic_set_vid_group(base, 5, 3); + l2vic_write32(base, L2VIC_INT_ENABLE_SETn, 0x20); + l2vic_write32(base, L2VIC_SOFT_INTn, 0x20); + + val = l2vic_read32(base, L2VIC_INT_STATUSn); + g_assert_cmpuint(val & 0x20, ==, 0x20); + g_assert_cmpuint(l2vic_read32(base, L2VIC_VID_GRP_3), ==, 5); + + l2vic_write32(base, L2VIC_INT_CLEARn, 0x20); + + /* Restore defaults; the block below reuses IRQ 3-5 without VID steering */ + l2vic_write32(base, L2VIC_INT_GRPn_0, 0); + l2vic_write32(base, L2VIC_INT_TYPEn, 0); + + /* Multiple pending: at most one active at a time */ + l2vic_write32(base, L2VIC_INT_TYPEn, 0xF); + l2vic_write32(base, L2VIC_INT_ENABLE_SETn, 0xF); + l2vic_write32(base, L2VIC_SOFT_INTn, 0xF); + + val = l2vic_read32(base, L2VIC_INT_STATUSn); + g_assert_cmpuint(val & 0xF, !=, 0x0); + + /* + * Only one irq becomes active per delivery; each clear unblocks the + * next pending one, so drain until all four have been delivered. + */ + while ((val = l2vic_read32(base, L2VIC_INT_STATUSn)) & 0xF) { + l2vic_write32(base, L2VIC_INT_CLEARn, val & 0xF); + } + + /* Level-triggered sources ignore soft interrupts */ + l2vic_write32(base, L2VIC_INT_TYPEn, 0x0); + l2vic_write32(base, L2VIC_INT_ENABLE_SETn, 0x20); + l2vic_write32(base, L2VIC_SOFT_INTn, 0x20); + + val = l2vic_read32(base, L2VIC_INT_STATUSn); + g_assert_cmpuint(val & 0x20, ==, 0x0); + + /* Same source, now edge-triggered, does fire */ + l2vic_write32(base, L2VIC_INT_TYPEn, 0x20); + l2vic_write32(base, L2VIC_SOFT_INTn, 0x20); + val = l2vic_read32(base, L2VIC_INT_STATUSn); + g_assert_cmpuint(val & 0x20, ==, 0x20); + + l2vic_write32(base, L2VIC_INT_ENABLE_CLEARn, 0xFFFFFFFF); + l2vic_write32(base, L2VIC_INT_CLEARn, 0xFFFFFFFF); + l2vic_write32(base, L2VIC_INT_GRPn_0, 0); + l2vic_write32(base, L2VIC_INT_GRPn_1, 0); + l2vic_write32(base, L2VIC_INT_GRPn_2, 0); + l2vic_write32(base, L2VIC_INT_GRPn_3, 0); +} + +static void test_l2vic_on_machine(gconstpointer data) +{ + const L2VICMachineCfg *mc = data; + g_autofree char *args = g_strdup_printf("-machine %s", mc->machine); + uint64_t base = mc->cfg->l2vic_base; + + qtest_start(args); + + test_l2vic_register_access(base); + test_l2vic_interrupt_enable(base); + test_l2vic_basic_functionality(base); + test_l2vic_irq_outputs(base); + + qtest_end(); +} + +int main(int argc, char **argv) +{ + size_t i; + + g_test_init(&argc, &argv, NULL); + + for (i = 0; i < ARRAY_SIZE(l2vic_machines); i++) { + g_autofree char *path = g_strdup_printf("/l2vic/%s/all-tests", + l2vic_machines[i].machine); + qtest_add_data_func(path, &l2vic_machines[i], 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