tests/qtest: add qct-qtimer qtest
Add a qtest exercising the QCT QTimer's register access, view-region frame addressing, and one-shot timer firing behavior. Reviewed-by: Pierrick Bouvier <pierrick.bouvier@oss.qualcomm.com> Link: https://lore.kernel.org/qemu-devel/20260806042723.3785369-18-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
71d618d3aa64bd7a33d8fc14fff6f3051ad633c2
3 files changed
+387
-1
MAINTAINERS
+1
@@ -255,6 +255,7 @@ F: include/hw/intc/hex-l2vic.h
255
F: tests/qtest/l2vic-test.c
256
F: hw/timer/qct-qtimer.c
257
F: include/hw/timer/qct-qtimer.h
258
+F: tests/qtest/qct-qtimer-test.c
259
X: target/hexagon/idef-parser/
260
X: target/hexagon/gen_idef_parser_funcs.py
261
F: linux-user/hexagon/
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', 'l2vic-test']
302
+qtests_hexagon = ['boot-serial-test', 'l2vic-test', 'qct-qtimer-test']
303
304
qos_test_ss = ss.source_set()
305
qos_test_ss.add(
tests/qtest/qct-qtimer-test.c
new
+385
@@ -0,0 +1,385 @@
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 QCT QTimer
6
+ */
7
+
8
+#include "qemu/osdep.h"
9
+#include "libqtest-single.h"
10
+#include "hw/hexagon/hexagon.h"
11
+#include "qemu/bitops.h"
12
+
13
+#include "hw/hexagon/machine_cfg_v68n_1024.h.inc"
14
+#include "hw/hexagon/machine_cfg_v66g_1024.h.inc"
15
+
16
+#define QTIMER_DEFAULT_FREQ_HZ 19200000ULL
17
+
18
+#define QCT_QTIMER_CNTPCT_LO (0x000)
19
+#define QCT_QTIMER_CNTPCT_HI (0x004)
20
+#define QCT_QTIMER_CNT_FREQ (0x010)
21
+#define QCT_QTIMER_CNTP_CVAL_LO (0x020)
22
+#define QCT_QTIMER_CNTP_TVAL (0x028)
23
+#define QCT_QTIMER_CNTP_CTL (0x02c)
24
+#define QCT_QTIMER_CNTP_CTL_ENABLE (1 << 0)
25
+
26
+#define QTIMER_FRAME_STRIDE 0x1000
27
+/* Frames instantiated by hex-subsys, and the L2VIC input frame 0 drives. */
28
+#define QTIMER_NR_FRAMES 3
29
+#define QTIMER_L2VIC_IRQ_BASE 2
30
+
31
+#define QCT_QTIMER_AC_CNTFRQ (0x000)
32
+#define QCT_QTIMER_AC_CNTSR (0x004)
33
+#define QCT_QTIMER_AC_CNTTID_0 (0x08)
34
+#define QCT_QTIMER_AC_CNTACR_START (0x40)
35
+#define QCT_QTIMER_AC_CNTACR_ALL (0x3f)
36
+#define QCT_QTIMER_AC_CNTACR_RWPT (1 << 5) /* R/W of CNTP_* regs */
37
+#define QCT_QTIMER_AC_CNTACR_RFRQ (1 << 2) /* R/W of CNTFRQ register */
38
+#define QCT_QTIMER_AC_CNTACR_RPCT (1 << 0) /* R/W of CNTPCT register */
39
+
40
+static uint64_t qtimer_view_base;
41
+static uint64_t qtimer_ac_base;
42
+
43
+#define TIMER_TEST_OFFSET 1000
44
+/* TIMER_TEST_OFFSET ticks expressed in nanoseconds of QEMU_CLOCK_VIRTUAL */
45
+#define TIMER_TEST_NS \
46
+ ((TIMER_TEST_OFFSET * 1000000000ULL) / QTIMER_DEFAULT_FREQ_HZ)
47
+
48
+static uint32_t qtimer_read32(uint64_t base, uint32_t offset)
49
+{
50
+ return readl(base + offset);
51
+}
52
+
53
+static void qtimer_write32(uint64_t base, uint32_t offset, uint32_t value)
54
+{
55
+ writel(base + offset, value);
56
+}
57
+
58
+static uint64_t qtimer_read64(uint64_t base, uint32_t offset)
59
+{
60
+ uint32_t lo = qtimer_read32(base, offset);
61
+ uint32_t hi = qtimer_read32(base, offset + 4);
62
+
63
+ return ((uint64_t)hi << 32) | lo;
64
+}
65
+
66
+static void qtimer_write64(uint64_t base, uint32_t offset, uint64_t value)
67
+{
68
+ qtimer_write32(base, offset, extract64(value, 0, 32));
69
+ qtimer_write32(base, offset + 4, extract64(value, 32, 32));
70
+}
71
+
72
+static void test_qtimer_basic_access(void)
73
+{
74
+ uint32_t val;
75
+
76
+ val = qtimer_read32(qtimer_view_base, QCT_QTIMER_CNT_FREQ);
77
+ g_assert_cmpuint(val, ==, QTIMER_DEFAULT_FREQ_HZ);
78
+}
79
+
80
+static void test_qtimer_multiple_frames(void)
81
+{
82
+ uint32_t val;
83
+ uint64_t frame0_base = qtimer_view_base;
84
+ uint64_t frame1_base = qtimer_view_base + 0x1000;
85
+
86
+ val = qtimer_read32(frame0_base, QCT_QTIMER_CNT_FREQ);
87
+ g_assert_cmpuint(val, ==, QTIMER_DEFAULT_FREQ_HZ);
88
+
89
+ val = qtimer_read32(frame1_base, QCT_QTIMER_CNT_FREQ);
90
+ g_assert_cmpuint(val, ==, QTIMER_DEFAULT_FREQ_HZ);
91
+}
92
+
93
+static void test_qtimer_register_reads(void)
94
+{
95
+ qtimer_read32(qtimer_view_base, QCT_QTIMER_CNT_FREQ);
96
+ qtimer_read64(qtimer_view_base, QCT_QTIMER_CNTPCT_LO);
97
+ qtimer_read64(qtimer_view_base, QCT_QTIMER_CNTP_CVAL_LO);
98
+ qtimer_read32(qtimer_view_base, QCT_QTIMER_CNTP_CTL);
99
+ qtimer_read32(qtimer_view_base, QCT_QTIMER_CNTP_TVAL);
100
+}
101
+
102
+static void test_qtimer_control_registers(void)
103
+{
104
+ uint32_t ctl_val;
105
+ uint64_t cval_before, cval_after;
106
+
107
+ cval_before = qtimer_read64(qtimer_view_base, QCT_QTIMER_CNTPCT_LO);
108
+
109
+ qtimer_write32(qtimer_view_base, QCT_QTIMER_CNTP_TVAL, 1000);
110
+
111
+ qtimer_write32(qtimer_view_base, QCT_QTIMER_CNTP_CTL, 1);
112
+ ctl_val = qtimer_read32(qtimer_view_base, QCT_QTIMER_CNTP_CTL);
113
+ g_assert_cmpuint(ctl_val & 1, ==, 1);
114
+
115
+ /* CVAL should be greater than before since we set TVAL */
116
+ cval_after = qtimer_read64(qtimer_view_base, QCT_QTIMER_CNTP_CVAL_LO);
117
+ g_assert_cmpuint(cval_after, >, cval_before);
118
+
119
+ qtimer_write32(qtimer_view_base, QCT_QTIMER_CNTP_CTL, 0);
120
+ ctl_val = qtimer_read32(qtimer_view_base, QCT_QTIMER_CNTP_CTL);
121
+ g_assert_cmpuint(ctl_val & 1, ==, 0);
122
+}
123
+
124
+static void test_qtimer_cval_access(void)
125
+{
126
+ uint64_t current_time, test_cval, read_cval;
127
+
128
+ current_time = qtimer_read64(qtimer_view_base, QCT_QTIMER_CNTPCT_LO);
129
+ test_cval = current_time + 10000;
130
+
131
+ qtimer_write64(qtimer_view_base, QCT_QTIMER_CNTP_CVAL_LO, test_cval);
132
+ read_cval = qtimer_read64(qtimer_view_base, QCT_QTIMER_CNTP_CVAL_LO);
133
+ g_assert_cmpuint(read_cval, ==, test_cval);
134
+}
135
+
136
+static void test_qtimer_counter_progression(void)
137
+{
138
+ uint32_t freq;
139
+ uint64_t count1, count2;
140
+
141
+ /*
142
+ * In qtest mode the virtual clock does not advance on its own, so
143
+ * reading the counter twice must give the same value.
144
+ */
145
+ count1 = qtimer_read64(qtimer_view_base, QCT_QTIMER_CNTPCT_LO);
146
+ count2 = qtimer_read64(qtimer_view_base, QCT_QTIMER_CNTPCT_LO);
147
+ g_assert_cmpuint(count2, ==, count1);
148
+
149
+ freq = qtimer_read32(qtimer_view_base, QCT_QTIMER_CNT_FREQ);
150
+ g_assert_cmpuint(freq, ==, QTIMER_DEFAULT_FREQ_HZ);
151
+}
152
+
153
+static void test_qtimer_timer_behavior(void)
154
+{
155
+ uint64_t current_count, target_count, read_cval, new_count;
156
+ uint64_t ctl_val, count_after_disable;
157
+
158
+ current_count = qtimer_read64(qtimer_view_base, QCT_QTIMER_CNTPCT_LO);
159
+
160
+ target_count = current_count + TIMER_TEST_OFFSET;
161
+ qtimer_write64(qtimer_view_base, QCT_QTIMER_CNTP_CVAL_LO, target_count);
162
+
163
+ read_cval = qtimer_read64(qtimer_view_base, QCT_QTIMER_CNTP_CVAL_LO);
164
+ g_assert_cmpuint(read_cval, ==, target_count);
165
+
166
+ qtimer_write32(qtimer_view_base, QCT_QTIMER_CNTP_CTL, 1);
167
+
168
+ ctl_val = qtimer_read64(qtimer_view_base, QCT_QTIMER_CNTP_CTL);
169
+ /* EN set, IMASK clear, ISTAT not yet pending */
170
+ g_assert_cmpuint(ctl_val, ==, 0x1);
171
+
172
+ /* Step forward but not past the target */
173
+ qtest_clock_step(global_qtest, TIMER_TEST_NS / 2);
174
+ new_count = qtimer_read64(qtimer_view_base, QCT_QTIMER_CNTPCT_LO);
175
+ g_assert_cmpuint(new_count, >=, current_count);
176
+
177
+ /* Step past the target */
178
+ qtest_clock_step(global_qtest, TIMER_TEST_NS);
179
+ new_count = qtimer_read64(qtimer_view_base, QCT_QTIMER_CNTPCT_LO);
180
+ g_assert_cmpuint(new_count, >=, target_count);
181
+
182
+ qtimer_write32(qtimer_view_base, QCT_QTIMER_CNTP_CTL, 0);
183
+
184
+ ctl_val = qtimer_read64(qtimer_view_base, QCT_QTIMER_CNTP_CTL);
185
+ /* EN cleared, ISTAT set since new_count >= target_count */
186
+ g_assert_cmpuint(ctl_val, ==, 0x4);
187
+
188
+ /*
189
+ * CNTPCT runs independently of CNTP_CTL.EN: only the compare/IRQ
190
+ * logic is gated by EN, so the counter must keep advancing.
191
+ */
192
+ qtest_clock_step(global_qtest, TIMER_TEST_NS / 2);
193
+ count_after_disable = qtimer_read64(qtimer_view_base,
194
+ QCT_QTIMER_CNTPCT_LO);
195
+ g_assert_cmpuint(count_after_disable, >, new_count);
196
+
197
+ /* ISTAT remains set while CNTPCT >= CVAL, even with EN=0 */
198
+ ctl_val = qtimer_read64(qtimer_view_base, QCT_QTIMER_CNTP_CTL);
199
+ g_assert_cmpuint(ctl_val, ==, 0x4);
200
+}
201
+
202
+/* Test the access-control region: CNTFRQ, CNTSR, CNTTID_0, CNTACR frame 0 */
203
+static void test_qtimer_ac_region(void)
204
+{
205
+ uint32_t freq, sr, tid0, acr0;
206
+
207
+ freq = qtimer_read32(qtimer_ac_base, QCT_QTIMER_AC_CNTFRQ);
208
+ g_assert_cmpuint(freq, ==, QTIMER_DEFAULT_FREQ_HZ);
209
+
210
+ /* A write of 0 to CNTFRQ must be ignored (freq-hz must stay nonzero). */
211
+ qtimer_write32(qtimer_ac_base, QCT_QTIMER_AC_CNTFRQ, 0);
212
+ freq = qtimer_read32(qtimer_ac_base, QCT_QTIMER_AC_CNTFRQ);
213
+ g_assert_cmpuint(freq, ==, QTIMER_DEFAULT_FREQ_HZ);
214
+
215
+ qtimer_write32(qtimer_ac_base, QCT_QTIMER_AC_CNTSR, 0x3);
216
+ sr = qtimer_read32(qtimer_ac_base, QCT_QTIMER_AC_CNTSR);
217
+ g_assert_cmpuint(sr, ==, 0x3);
218
+
219
+ tid0 = qtimer_read32(qtimer_ac_base, QCT_QTIMER_AC_CNTTID_0);
220
+ g_assert_cmpuint(tid0, ==, 0x111);
221
+
222
+ /* CNTACR for frame 0 defaults to full read/write permissions. */
223
+ acr0 = qtimer_read32(qtimer_ac_base, QCT_QTIMER_AC_CNTACR_START);
224
+ g_assert_cmpuint(acr0, !=, 0);
225
+
226
+ qtimer_write32(qtimer_ac_base, QCT_QTIMER_AC_CNTACR_START, 0);
227
+ acr0 = qtimer_read32(qtimer_ac_base, QCT_QTIMER_AC_CNTACR_START);
228
+ g_assert_cmpuint(acr0, ==, 0);
229
+
230
+ /* Restore full permissions so later view-region tests keep working. */
231
+ qtimer_write32(qtimer_ac_base, QCT_QTIMER_AC_CNTACR_START,
232
+ QCT_QTIMER_AC_CNTACR_ALL);
233
+}
234
+
235
+static void test_qtimer_access_denied(void)
236
+{
237
+ uint32_t acr_all = QCT_QTIMER_AC_CNTACR_ALL;
238
+ uint64_t cval, saved_cval;
239
+ uint32_t val;
240
+
241
+ /* Park CVAL at a known nonzero value while access is still permitted. */
242
+ saved_cval = qtimer_read64(qtimer_view_base, QCT_QTIMER_CNTPCT_LO) + 10000;
243
+ qtimer_write64(qtimer_view_base, QCT_QTIMER_CNTP_CVAL_LO, saved_cval);
244
+ g_assert_cmpuint(qtimer_read64(qtimer_view_base, QCT_QTIMER_CNTP_CVAL_LO),
245
+ ==, saved_cval);
246
+
247
+ /* Clearing RFRQ denies CNTFRQ reads. */
248
+ qtimer_write32(qtimer_ac_base, QCT_QTIMER_AC_CNTACR_START,
249
+ acr_all & ~QCT_QTIMER_AC_CNTACR_RFRQ);
250
+ val = qtimer_read32(qtimer_view_base, QCT_QTIMER_CNT_FREQ);
251
+ g_assert_cmpuint(val, ==, 0);
252
+
253
+ /* Clearing RPCT denies CNTPCT reads, both halves. */
254
+ qtimer_write32(qtimer_ac_base, QCT_QTIMER_AC_CNTACR_START,
255
+ acr_all & ~QCT_QTIMER_AC_CNTACR_RPCT);
256
+ val = qtimer_read32(qtimer_view_base, QCT_QTIMER_CNTPCT_LO);
257
+ g_assert_cmpuint(val, ==, 0);
258
+ val = qtimer_read32(qtimer_view_base, QCT_QTIMER_CNTPCT_HI);
259
+ g_assert_cmpuint(val, ==, 0);
260
+
261
+ /*
262
+ * Clearing RWPT denies the whole CNTP_* set: CVAL/TVAL/CTL reads all
263
+ * read back 0 even though CVAL holds saved_cval.
264
+ */
265
+ qtimer_write32(qtimer_ac_base, QCT_QTIMER_AC_CNTACR_START,
266
+ acr_all & ~QCT_QTIMER_AC_CNTACR_RWPT);
267
+ cval = qtimer_read64(qtimer_view_base, QCT_QTIMER_CNTP_CVAL_LO);
268
+ g_assert_cmpuint(cval, ==, 0);
269
+ val = qtimer_read32(qtimer_view_base, QCT_QTIMER_CNTP_TVAL);
270
+ g_assert_cmpuint(val, ==, 0);
271
+ val = qtimer_read32(qtimer_view_base, QCT_QTIMER_CNTP_CTL);
272
+ g_assert_cmpuint(val, ==, 0);
273
+
274
+ /* Denied writes must be dropped rather than applied. */
275
+ qtimer_write64(qtimer_view_base, QCT_QTIMER_CNTP_CVAL_LO, 0x1234);
276
+ qtimer_write32(qtimer_view_base, QCT_QTIMER_CNTP_TVAL, 0x5678);
277
+ qtimer_write32(qtimer_view_base, QCT_QTIMER_CNTP_CTL, 1);
278
+
279
+ /* Restoring RWPT reveals that CVAL still holds the pre-denial value. */
280
+ qtimer_write32(qtimer_ac_base, QCT_QTIMER_AC_CNTACR_START, acr_all);
281
+ cval = qtimer_read64(qtimer_view_base, QCT_QTIMER_CNTP_CVAL_LO);
282
+ g_assert_cmpuint(cval, ==, saved_cval);
283
+ val = qtimer_read32(qtimer_view_base, QCT_QTIMER_CNTP_CTL);
284
+ g_assert_cmpuint(val & 1, ==, 0);
285
+
286
+ /* CNTFRQ and CNTPCT work again once permissions are back. */
287
+ val = qtimer_read32(qtimer_view_base, QCT_QTIMER_CNT_FREQ);
288
+ g_assert_cmpuint(val, ==, QTIMER_DEFAULT_FREQ_HZ);
289
+
290
+ /*
291
+ * An unimplemented offset in the view region is also an access error,
292
+ * and reads back as 0.
293
+ */
294
+ val = qtimer_read32(qtimer_view_base, 0x100);
295
+ g_assert_cmpuint(val, ==, 0);
296
+
297
+ /* Leave the frame with full permissions for any later test. */
298
+ qtimer_write32(qtimer_ac_base, QCT_QTIMER_AC_CNTACR_START, acr_all);
299
+}
300
+
301
+static void test_qtimer_frame_irq_routing(void)
302
+{
303
+ unsigned int frame, other;
304
+ uint64_t frame_base;
305
+
306
+ qtest_irq_intercept_in(global_qtest, "/machine/l2vic");
307
+
308
+ for (frame = 0; frame < QTIMER_NR_FRAMES; frame++) {
309
+ frame_base = qtimer_view_base + frame * QTIMER_FRAME_STRIDE;
310
+
311
+ qtimer_write32(frame_base, QCT_QTIMER_CNTP_TVAL, TIMER_TEST_OFFSET);
312
+ qtimer_write32(frame_base, QCT_QTIMER_CNTP_CTL,
313
+ QCT_QTIMER_CNTP_CTL_ENABLE);
314
+ g_assert_false(qtest_get_irq(global_qtest,
315
+ QTIMER_L2VIC_IRQ_BASE + frame));
316
+
317
+ /* Step past the deadline so the frame raises its interrupt. */
318
+ qtest_clock_step(global_qtest, TIMER_TEST_NS * 2);
319
+ g_assert_true(qtest_get_irq(global_qtest,
320
+ QTIMER_L2VIC_IRQ_BASE + frame));
321
+
322
+ /* No other frame's line may be disturbed. */
323
+ for (other = 0; other < QTIMER_NR_FRAMES; other++) {
324
+ if (other != frame) {
325
+ g_assert_false(qtest_get_irq(global_qtest,
326
+ QTIMER_L2VIC_IRQ_BASE + other));
327
+ }
328
+ }
329
+
330
+ /* Clearing EN drops the interrupt, leaving a clean slate. */
331
+ qtimer_write32(frame_base, QCT_QTIMER_CNTP_CTL, 0);
332
+ g_assert_false(qtest_get_irq(global_qtest,
333
+ QTIMER_L2VIC_IRQ_BASE + frame));
334
+ }
335
+}
336
+
337
+typedef struct {
338
+ const char *machine;
339
+ const struct hexagon_machine_config *cfg;
340
+} QtimerMachineCfg;
341
+
342
+static const QtimerMachineCfg qtimer_machines[] = {
343
+ { "virt", &v68n_1024 },
344
+ { "V66G_1024", &v66g_1024 },
345
+};
346
+
347
+static void test_qtimer_on_machine(gconstpointer data)
348
+{
349
+ const QtimerMachineCfg *mc = data;
350
+ g_autofree char *args = g_strdup_printf(
351
+ "-machine %s -global qct-qtimer.freq-scale=1", mc->machine);
352
+
353
+ qtimer_view_base = mc->cfg->qtmr_region;
354
+ qtimer_ac_base = mc->cfg->csr_base;
355
+
356
+ qtest_start(args);
357
+
358
+ test_qtimer_basic_access();
359
+ test_qtimer_multiple_frames();
360
+ test_qtimer_register_reads();
361
+ test_qtimer_control_registers();
362
+ test_qtimer_cval_access();
363
+ test_qtimer_counter_progression();
364
+ test_qtimer_timer_behavior();
365
+ test_qtimer_ac_region();
366
+ test_qtimer_access_denied();
367
+ test_qtimer_frame_irq_routing();
368
+
369
+ qtest_end();
370
+}
371
+
372
+int main(int argc, char **argv)
373
+{
374
+ size_t i;
375
+
376
+ g_test_init(&argc, &argv, NULL);
377
+
378
+ for (i = 0; i < ARRAY_SIZE(qtimer_machines); i++) {
379
+ g_autofree char *path = g_strdup_printf("/qct-qtimer/%s/all-tests",
380
+ qtimer_machines[i].machine);
381
+ qtest_add_data_func(path, &qtimer_machines[i], test_qtimer_on_machine);
382
+ }
383
+
384
+ return g_test_run();
385
+}