@samitouri / QOSamiQemu / commits / 8ff5b8d403

tests/qtest: add L2VIC qtest

Add a qtest exercising L2VIC register access and interrupt enable/disable. Reviewed-by: Pierrick Bouvier <pierrick.bouvier@oss.qualcomm.com> Link: https://lore.kernel.org/qemu-devel/20260806042723.3785369-14-brian.cain@oss.qualcomm.com Signed-off-by: Brian Cain <brian.cain@oss.qualcomm.com>

Brian Cain committed Aug 5, 2026 at 21:27 UTC 8ff5b8d403483f76e36ffb30087aacaf1a41fe07
3 files changed +251 -1
MAINTAINERS
+1
@@ -252,6 +252,7 @@ S: Supported
252 F: target/hexagon/
253 F: hw/intc/hex-l2vic.c
254 F: include/hw/intc/hex-l2vic.h
255 +F: tests/qtest/l2vic-test.c
256 X: target/hexagon/idef-parser/
257 X: target/hexagon/gen_idef_parser_funcs.py
258 F: linux-user/hexagon/
tests/qtest/l2vic-test.c new
+249
@@ -0,0 +1,249 @@
1 +/*
2 + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
3 + * SPDX-License-Identifier: GPL-2.0-or-later
4 + *
5 + * QTest testcase for the L2VIC Interrupt Controller
6 + */
7 +
8 +#include "qemu/osdep.h"
9 +#include "libqtest-single.h"
10 +#include "hw/hexagon/hexagon.h"
11 +
12 +#include "hw/hexagon/machine_cfg_v66g_1024.h.inc"
13 +#include "hw/hexagon/machine_cfg_v68n_1024.h.inc"
14 +
15 +/* L2VIC register offsets exercised by this test */
16 +#define L2VIC_INT_ENABLEn 0x100 /* Read/Write */
17 +#define L2VIC_INT_ENABLE_CLEARn 0x180 /* Write */
18 +#define L2VIC_INT_ENABLE_SETn 0x200 /* Write */
19 +#define L2VIC_INT_TYPEn 0x280 /* Read/Write */
20 +#define L2VIC_INT_STATUSn 0x380 /* Read */
21 +#define L2VIC_INT_CLEARn 0x400 /* Write */
22 +#define L2VIC_SOFT_INTn 0x480 /* Write */
23 +#define L2VIC_INT_PENDINGn 0x500 /* Read */
24 +#define L2VIC_INT_GRPn_0 0x600 /* Read/Write */
25 +#define L2VIC_INT_GRPn_1 0x680 /* Read/Write */
26 +#define L2VIC_INT_GRPn_2 0x700 /* Read/Write */
27 +#define L2VIC_INT_GRPn_3 0x780 /* Read/Write */
28 +
29 +/*
30 + * VID group readback: records which irq last fired through each VID
31 + * group. Outputs themselves are momentary pulses (see l2vic_update()),
32 + * so these registers -- not the qtest IRQ level snapshot -- are how the
33 + * test observes VID steering.
34 + */
35 +#define L2VIC_VID_GRP_0 0x0
36 +#define L2VIC_VID_GRP_1 0x4
37 +#define L2VIC_VID_GRP_2 0x8
38 +#define L2VIC_VID_GRP_3 0xC
39 +
40 +typedef struct {
41 + const char *machine;
42 + const struct hexagon_machine_config *cfg;
43 +} L2VICMachineCfg;
44 +
45 +static const L2VICMachineCfg l2vic_machines[] = {
46 + { "virt", &v68n_1024 },
47 + { "V66G_1024", &v66g_1024 },
48 +};
49 +
50 +static uint32_t l2vic_read32(uint64_t base, uint32_t offset)
51 +{
52 + return readl(base + offset);
53 +}
54 +
55 +static void l2vic_write32(uint64_t base, uint32_t offset, uint32_t value)
56 +{
57 + writel(base + offset, value);
58 +}
59 +
60 +static void test_l2vic_register_access(uint64_t base)
61 +{
62 + uint32_t val;
63 +
64 + l2vic_write32(base, L2VIC_INT_ENABLE_SETn, 0x1);
65 + val = l2vic_read32(base, L2VIC_INT_ENABLEn);
66 + g_assert_cmpuint(val & 0x1, ==, 0x1);
67 +
68 + l2vic_write32(base, L2VIC_INT_ENABLE_CLEARn, 0x1);
69 + val = l2vic_read32(base, L2VIC_INT_ENABLEn);
70 + g_assert_cmpuint(val & 0x1, ==, 0x0);
71 +}
72 +
73 +static void test_l2vic_interrupt_enable(uint64_t base)
74 +{
75 + uint32_t val;
76 +
77 + val = l2vic_read32(base, L2VIC_INT_ENABLEn);
78 + g_assert_cmpuint(val, ==, 0);
79 +
80 + /* Enable IRQ 0 and 2 */
81 + l2vic_write32(base, L2VIC_INT_ENABLE_SETn, 0x5);
82 + val = l2vic_read32(base, L2VIC_INT_ENABLEn);
83 + g_assert_cmpuint(val & 0x5, ==, 0x5);
84 +
85 + /* Disable IRQ 0, leaving IRQ 2 enabled */
86 + l2vic_write32(base, L2VIC_INT_ENABLE_CLEARn, 0x1);
87 + val = l2vic_read32(base, L2VIC_INT_ENABLEn);
88 + g_assert_cmpuint(val & 0x1, ==, 0x0);
89 + g_assert_cmpuint(val & 0x4, ==, 0x4);
90 +}
91 +
92 +static void test_l2vic_basic_functionality(uint64_t base)
93 +{
94 + l2vic_read32(base, L2VIC_INT_ENABLEn);
95 + l2vic_read32(base, L2VIC_INT_PENDINGn);
96 + l2vic_read32(base, L2VIC_INT_STATUSn);
97 + l2vic_read32(base, L2VIC_INT_TYPEn);
98 +
99 + l2vic_write32(base, L2VIC_INT_ENABLE_SETn, 0);
100 + l2vic_write32(base, L2VIC_INT_ENABLE_CLEARn, 0);
101 +}
102 +
103 +/*
104 + * IRQs 0-7 pack their group-enable/VID-select nibbles into
105 + * L2VIC_INT_GRPn_0 (int_group_n[0]), 4 bits per irq: bit 3 enables
106 + * VID steering, bits 0-2 select the VID group (0-3), which pulses
107 + * output line vid+2.
108 + */
109 +static void l2vic_set_vid_group(uint64_t base, int irq, int vid)
110 +{
111 + uint32_t val = l2vic_read32(base, L2VIC_INT_GRPn_0);
112 + uint32_t nibble = 0x8 | (vid & 0x7);
113 +
114 + val &= ~(0xFu << (irq * 4));
115 + val |= nibble << (irq * 4);
116 + l2vic_write32(base, L2VIC_INT_GRPn_0, val);
117 +}
118 +
119 +static void test_l2vic_irq_outputs(uint64_t base)
120 +{
121 + uint32_t val;
122 +
123 + l2vic_write32(base, L2VIC_INT_ENABLE_CLEARn, 0xFFFFFFFF);
124 + l2vic_write32(base, L2VIC_INT_CLEARn, 0xFFFFFFFF);
125 + l2vic_write32(base, L2VIC_INT_TYPEn, 0);
126 + l2vic_write32(base, L2VIC_INT_GRPn_0, 0);
127 +
128 + /* Group 0 / IRQ2: soft interrupts require edge-triggered config */
129 + l2vic_write32(base, L2VIC_INT_TYPEn, 0x1);
130 + l2vic_write32(base, L2VIC_INT_ENABLE_SETn, 0x1);
131 + l2vic_write32(base, L2VIC_SOFT_INTn, 0x1);
132 +
133 + val = l2vic_read32(base, L2VIC_INT_STATUSn);
134 + g_assert_cmpuint(val & 0x1, ==, 0x1);
135 + /* Default VID group (0) records the delivering irq, output line 2 */
136 + g_assert_cmpuint(l2vic_read32(base, L2VIC_VID_GRP_0), ==, 0);
137 +
138 + l2vic_write32(base, L2VIC_INT_CLEARn, 0x1);
139 + val = l2vic_read32(base, L2VIC_INT_STATUSn);
140 + g_assert_cmpuint(val & 0x1, ==, 0x0);
141 +
142 + /* IRQ1 steered to VID group 1 -> output line 3 */
143 + l2vic_write32(base, L2VIC_INT_TYPEn, 0x2);
144 + l2vic_set_vid_group(base, 1, 1);
145 + l2vic_write32(base, L2VIC_INT_ENABLE_SETn, 0x2);
146 + l2vic_write32(base, L2VIC_SOFT_INTn, 0x2);
147 +
148 + val = l2vic_read32(base, L2VIC_INT_STATUSn);
149 + g_assert_cmpuint(val & 0x2, ==, 0x2);
150 + g_assert_cmpuint(l2vic_read32(base, L2VIC_VID_GRP_1), ==, 1);
151 +
152 + l2vic_write32(base, L2VIC_INT_CLEARn, 0x2);
153 +
154 + /* IRQ4 steered to VID group 2 -> output line 4 */
155 + l2vic_write32(base, L2VIC_INT_TYPEn, 0x10);
156 + l2vic_set_vid_group(base, 4, 2);
157 + l2vic_write32(base, L2VIC_INT_ENABLE_SETn, 0x10);
158 + l2vic_write32(base, L2VIC_SOFT_INTn, 0x10);
159 +
160 + val = l2vic_read32(base, L2VIC_INT_STATUSn);
161 + g_assert_cmpuint(val & 0x10, ==, 0x10);
162 + g_assert_cmpuint(l2vic_read32(base, L2VIC_VID_GRP_2), ==, 4);
163 +
164 + l2vic_write32(base, L2VIC_INT_CLEARn, 0x10);
165 +
166 + /* IRQ5 steered to VID group 3 -> output line 5 */
167 + l2vic_write32(base, L2VIC_INT_TYPEn, 0x20);
168 + l2vic_set_vid_group(base, 5, 3);
169 + l2vic_write32(base, L2VIC_INT_ENABLE_SETn, 0x20);
170 + l2vic_write32(base, L2VIC_SOFT_INTn, 0x20);
171 +
172 + val = l2vic_read32(base, L2VIC_INT_STATUSn);
173 + g_assert_cmpuint(val & 0x20, ==, 0x20);
174 + g_assert_cmpuint(l2vic_read32(base, L2VIC_VID_GRP_3), ==, 5);
175 +
176 + l2vic_write32(base, L2VIC_INT_CLEARn, 0x20);
177 +
178 + /* Restore defaults; the block below reuses IRQ 3-5 without VID steering */
179 + l2vic_write32(base, L2VIC_INT_GRPn_0, 0);
180 + l2vic_write32(base, L2VIC_INT_TYPEn, 0);
181 +
182 + /* Multiple pending: at most one active at a time */
183 + l2vic_write32(base, L2VIC_INT_TYPEn, 0xF);
184 + l2vic_write32(base, L2VIC_INT_ENABLE_SETn, 0xF);
185 + l2vic_write32(base, L2VIC_SOFT_INTn, 0xF);
186 +
187 + val = l2vic_read32(base, L2VIC_INT_STATUSn);
188 + g_assert_cmpuint(val & 0xF, !=, 0x0);
189 +
190 + /*
191 + * Only one irq becomes active per delivery; each clear unblocks the
192 + * next pending one, so drain until all four have been delivered.
193 + */
194 + while ((val = l2vic_read32(base, L2VIC_INT_STATUSn)) & 0xF) {
195 + l2vic_write32(base, L2VIC_INT_CLEARn, val & 0xF);
196 + }
197 +
198 + /* Level-triggered sources ignore soft interrupts */
199 + l2vic_write32(base, L2VIC_INT_TYPEn, 0x0);
200 + l2vic_write32(base, L2VIC_INT_ENABLE_SETn, 0x20);
201 + l2vic_write32(base, L2VIC_SOFT_INTn, 0x20);
202 +
203 + val = l2vic_read32(base, L2VIC_INT_STATUSn);
204 + g_assert_cmpuint(val & 0x20, ==, 0x0);
205 +
206 + /* Same source, now edge-triggered, does fire */
207 + l2vic_write32(base, L2VIC_INT_TYPEn, 0x20);
208 + l2vic_write32(base, L2VIC_SOFT_INTn, 0x20);
209 + val = l2vic_read32(base, L2VIC_INT_STATUSn);
210 + g_assert_cmpuint(val & 0x20, ==, 0x20);
211 +
212 + l2vic_write32(base, L2VIC_INT_ENABLE_CLEARn, 0xFFFFFFFF);
213 + l2vic_write32(base, L2VIC_INT_CLEARn, 0xFFFFFFFF);
214 + l2vic_write32(base, L2VIC_INT_GRPn_0, 0);
215 + l2vic_write32(base, L2VIC_INT_GRPn_1, 0);
216 + l2vic_write32(base, L2VIC_INT_GRPn_2, 0);
217 + l2vic_write32(base, L2VIC_INT_GRPn_3, 0);
218 +}
219 +
220 +static void test_l2vic_on_machine(gconstpointer data)
221 +{
222 + const L2VICMachineCfg *mc = data;
223 + g_autofree char *args = g_strdup_printf("-machine %s", mc->machine);
224 + uint64_t base = mc->cfg->l2vic_base;
225 +
226 + qtest_start(args);
227 +
228 + test_l2vic_register_access(base);
229 + test_l2vic_interrupt_enable(base);
230 + test_l2vic_basic_functionality(base);
231 + test_l2vic_irq_outputs(base);
232 +
233 + qtest_end();
234 +}
235 +
236 +int main(int argc, char **argv)
237 +{
238 + size_t i;
239 +
240 + g_test_init(&argc, &argv, NULL);
241 +
242 + for (i = 0; i < ARRAY_SIZE(l2vic_machines); i++) {
243 + g_autofree char *path = g_strdup_printf("/l2vic/%s/all-tests",
244 + l2vic_machines[i].machine);
245 + qtest_add_data_func(path, &l2vic_machines[i], test_l2vic_on_machine);
246 + }
247 +
248 + return g_test_run();
249 +}
tests/qtest/meson.build
+1 -1
@@ -299,7 +299,7 @@ qtests_riscv64 = ['riscv-csr-test'] + \
299 ['iommu-riscv-test'] : []) + \
300 (config_all_devices.has_key('CONFIG_K230') ? ['k230-wdt-test'] : [])
301
302 -qtests_hexagon = ['boot-serial-test']
302 +qtests_hexagon = ['boot-serial-test', 'l2vic-test']
303
304 qos_test_ss = ss.source_set()
305 qos_test_ss.add(