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(