Skip to content

Commit 4959d86

Browse files
rananta468Marc Zyngier
authored andcommitted
KVM: arm64: selftests: Add arch_timer test
Add a KVM selftest to validate the arch_timer functionality. Primarily, the test sets up periodic timer interrupts and validates the basic architectural expectations upon its receipt. The test provides command-line options to configure the period of the timer, number of iterations, and number of vCPUs. Signed-off-by: Raghavendra Rao Ananta <[email protected]> Reviewed-by: Andrew Jones <[email protected]> Signed-off-by: Marc Zyngier <[email protected]> Link: https://lore.kernel.org/r/[email protected]
1 parent 250b8d6 commit 4959d86

File tree

3 files changed

+368
-0
lines changed

3 files changed

+368
-0
lines changed

tools/testing/selftests/kvm/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# SPDX-License-Identifier: GPL-2.0-only
2+
/aarch64/arch_timer
23
/aarch64/debug-exceptions
34
/aarch64/get-reg-list
45
/aarch64/psci_cpu_on_test

tools/testing/selftests/kvm/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ TEST_GEN_PROGS_x86_64 += set_memory_region_test
8686
TEST_GEN_PROGS_x86_64 += steal_time
8787
TEST_GEN_PROGS_x86_64 += kvm_binary_stats_test
8888

89+
TEST_GEN_PROGS_aarch64 += aarch64/arch_timer
8990
TEST_GEN_PROGS_aarch64 += aarch64/debug-exceptions
9091
TEST_GEN_PROGS_aarch64 += aarch64/get-reg-list
9192
TEST_GEN_PROGS_aarch64 += aarch64/psci_cpu_on_test
Lines changed: 366 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,366 @@
1+
// SPDX-License-Identifier: GPL-2.0-only
2+
/*
3+
* arch_timer.c - Tests the aarch64 timer IRQ functionality
4+
*
5+
* The test validates both the virtual and physical timer IRQs using
6+
* CVAL and TVAL registers. This consitutes the four stages in the test.
7+
* The guest's main thread configures the timer interrupt for a stage
8+
* and waits for it to fire, with a timeout equal to the timer period.
9+
* It asserts that the timeout doesn't exceed the timer period.
10+
*
11+
* On the other hand, upon receipt of an interrupt, the guest's interrupt
12+
* handler validates the interrupt by checking if the architectural state
13+
* is in compliance with the specifications.
14+
*
15+
* The test provides command-line options to configure the timer's
16+
* period (-p), number of vCPUs (-n), and iterations per stage (-i).
17+
*
18+
* Copyright (c) 2021, Google LLC.
19+
*/
20+
21+
#define _GNU_SOURCE
22+
23+
#include <stdlib.h>
24+
#include <pthread.h>
25+
#include <linux/kvm.h>
26+
#include <linux/sizes.h>
27+
28+
#include "kvm_util.h"
29+
#include "processor.h"
30+
#include "delay.h"
31+
#include "arch_timer.h"
32+
#include "gic.h"
33+
#include "vgic.h"
34+
35+
#define NR_VCPUS_DEF 4
36+
#define NR_TEST_ITERS_DEF 5
37+
#define TIMER_TEST_PERIOD_MS_DEF 10
38+
#define TIMER_TEST_ERR_MARGIN_US 100
39+
40+
struct test_args {
41+
int nr_vcpus;
42+
int nr_iter;
43+
int timer_period_ms;
44+
};
45+
46+
static struct test_args test_args = {
47+
.nr_vcpus = NR_VCPUS_DEF,
48+
.nr_iter = NR_TEST_ITERS_DEF,
49+
.timer_period_ms = TIMER_TEST_PERIOD_MS_DEF,
50+
};
51+
52+
#define msecs_to_usecs(msec) ((msec) * 1000LL)
53+
54+
#define GICD_BASE_GPA 0x8000000ULL
55+
#define GICR_BASE_GPA 0x80A0000ULL
56+
57+
enum guest_stage {
58+
GUEST_STAGE_VTIMER_CVAL = 1,
59+
GUEST_STAGE_VTIMER_TVAL,
60+
GUEST_STAGE_PTIMER_CVAL,
61+
GUEST_STAGE_PTIMER_TVAL,
62+
GUEST_STAGE_MAX,
63+
};
64+
65+
/* Shared variables between host and guest */
66+
struct test_vcpu_shared_data {
67+
int nr_iter;
68+
enum guest_stage guest_stage;
69+
uint64_t xcnt;
70+
};
71+
72+
struct test_vcpu {
73+
uint32_t vcpuid;
74+
pthread_t pt_vcpu_run;
75+
struct kvm_vm *vm;
76+
};
77+
78+
static struct test_vcpu test_vcpu[KVM_MAX_VCPUS];
79+
static struct test_vcpu_shared_data vcpu_shared_data[KVM_MAX_VCPUS];
80+
81+
static int vtimer_irq, ptimer_irq;
82+
83+
static void
84+
guest_configure_timer_action(struct test_vcpu_shared_data *shared_data)
85+
{
86+
switch (shared_data->guest_stage) {
87+
case GUEST_STAGE_VTIMER_CVAL:
88+
timer_set_next_cval_ms(VIRTUAL, test_args.timer_period_ms);
89+
shared_data->xcnt = timer_get_cntct(VIRTUAL);
90+
timer_set_ctl(VIRTUAL, CTL_ENABLE);
91+
break;
92+
case GUEST_STAGE_VTIMER_TVAL:
93+
timer_set_next_tval_ms(VIRTUAL, test_args.timer_period_ms);
94+
shared_data->xcnt = timer_get_cntct(VIRTUAL);
95+
timer_set_ctl(VIRTUAL, CTL_ENABLE);
96+
break;
97+
case GUEST_STAGE_PTIMER_CVAL:
98+
timer_set_next_cval_ms(PHYSICAL, test_args.timer_period_ms);
99+
shared_data->xcnt = timer_get_cntct(PHYSICAL);
100+
timer_set_ctl(PHYSICAL, CTL_ENABLE);
101+
break;
102+
case GUEST_STAGE_PTIMER_TVAL:
103+
timer_set_next_tval_ms(PHYSICAL, test_args.timer_period_ms);
104+
shared_data->xcnt = timer_get_cntct(PHYSICAL);
105+
timer_set_ctl(PHYSICAL, CTL_ENABLE);
106+
break;
107+
default:
108+
GUEST_ASSERT(0);
109+
}
110+
}
111+
112+
static void guest_validate_irq(unsigned int intid,
113+
struct test_vcpu_shared_data *shared_data)
114+
{
115+
enum guest_stage stage = shared_data->guest_stage;
116+
uint64_t xcnt = 0, xcnt_diff_us, cval = 0;
117+
unsigned long xctl = 0;
118+
unsigned int timer_irq = 0;
119+
120+
if (stage == GUEST_STAGE_VTIMER_CVAL ||
121+
stage == GUEST_STAGE_VTIMER_TVAL) {
122+
xctl = timer_get_ctl(VIRTUAL);
123+
timer_set_ctl(VIRTUAL, CTL_IMASK);
124+
xcnt = timer_get_cntct(VIRTUAL);
125+
cval = timer_get_cval(VIRTUAL);
126+
timer_irq = vtimer_irq;
127+
} else if (stage == GUEST_STAGE_PTIMER_CVAL ||
128+
stage == GUEST_STAGE_PTIMER_TVAL) {
129+
xctl = timer_get_ctl(PHYSICAL);
130+
timer_set_ctl(PHYSICAL, CTL_IMASK);
131+
xcnt = timer_get_cntct(PHYSICAL);
132+
cval = timer_get_cval(PHYSICAL);
133+
timer_irq = ptimer_irq;
134+
} else {
135+
GUEST_ASSERT(0);
136+
}
137+
138+
xcnt_diff_us = cycles_to_usec(xcnt - shared_data->xcnt);
139+
140+
/* Make sure we are dealing with the correct timer IRQ */
141+
GUEST_ASSERT_2(intid == timer_irq, intid, timer_irq);
142+
143+
/* Basic 'timer condition met' check */
144+
GUEST_ASSERT_3(xcnt >= cval, xcnt, cval, xcnt_diff_us);
145+
GUEST_ASSERT_1(xctl & CTL_ISTATUS, xctl);
146+
}
147+
148+
static void guest_irq_handler(struct ex_regs *regs)
149+
{
150+
unsigned int intid = gic_get_and_ack_irq();
151+
uint32_t cpu = guest_get_vcpuid();
152+
struct test_vcpu_shared_data *shared_data = &vcpu_shared_data[cpu];
153+
154+
guest_validate_irq(intid, shared_data);
155+
156+
WRITE_ONCE(shared_data->nr_iter, shared_data->nr_iter + 1);
157+
158+
gic_set_eoi(intid);
159+
}
160+
161+
static void guest_run_stage(struct test_vcpu_shared_data *shared_data,
162+
enum guest_stage stage)
163+
{
164+
uint32_t irq_iter, config_iter;
165+
166+
shared_data->guest_stage = stage;
167+
shared_data->nr_iter = 0;
168+
169+
for (config_iter = 0; config_iter < test_args.nr_iter; config_iter++) {
170+
/* Setup the next interrupt */
171+
guest_configure_timer_action(shared_data);
172+
173+
/* Setup a timeout for the interrupt to arrive */
174+
udelay(msecs_to_usecs(test_args.timer_period_ms) +
175+
TIMER_TEST_ERR_MARGIN_US);
176+
177+
irq_iter = READ_ONCE(shared_data->nr_iter);
178+
GUEST_ASSERT_2(config_iter + 1 == irq_iter,
179+
config_iter + 1, irq_iter);
180+
}
181+
}
182+
183+
static void guest_code(void)
184+
{
185+
uint32_t cpu = guest_get_vcpuid();
186+
struct test_vcpu_shared_data *shared_data = &vcpu_shared_data[cpu];
187+
188+
local_irq_disable();
189+
190+
gic_init(GIC_V3, test_args.nr_vcpus,
191+
(void *)GICD_BASE_GPA, (void *)GICR_BASE_GPA);
192+
193+
timer_set_ctl(VIRTUAL, CTL_IMASK);
194+
timer_set_ctl(PHYSICAL, CTL_IMASK);
195+
196+
gic_irq_enable(vtimer_irq);
197+
gic_irq_enable(ptimer_irq);
198+
local_irq_enable();
199+
200+
guest_run_stage(shared_data, GUEST_STAGE_VTIMER_CVAL);
201+
guest_run_stage(shared_data, GUEST_STAGE_VTIMER_TVAL);
202+
guest_run_stage(shared_data, GUEST_STAGE_PTIMER_CVAL);
203+
guest_run_stage(shared_data, GUEST_STAGE_PTIMER_TVAL);
204+
205+
GUEST_DONE();
206+
}
207+
208+
static void *test_vcpu_run(void *arg)
209+
{
210+
struct ucall uc;
211+
struct test_vcpu *vcpu = arg;
212+
struct kvm_vm *vm = vcpu->vm;
213+
uint32_t vcpuid = vcpu->vcpuid;
214+
struct test_vcpu_shared_data *shared_data = &vcpu_shared_data[vcpuid];
215+
216+
vcpu_run(vm, vcpuid);
217+
218+
switch (get_ucall(vm, vcpuid, &uc)) {
219+
case UCALL_SYNC:
220+
case UCALL_DONE:
221+
break;
222+
case UCALL_ABORT:
223+
sync_global_from_guest(vm, *shared_data);
224+
TEST_FAIL("%s at %s:%ld\n\tvalues: %lu, %lu; %lu, vcpu: %u; stage: %u; iter: %u",
225+
(const char *)uc.args[0], __FILE__, uc.args[1],
226+
uc.args[2], uc.args[3], uc.args[4], vcpuid,
227+
shared_data->guest_stage, shared_data->nr_iter);
228+
break;
229+
default:
230+
TEST_FAIL("Unexpected guest exit\n");
231+
}
232+
233+
return NULL;
234+
}
235+
236+
static void test_run(struct kvm_vm *vm)
237+
{
238+
int i, ret;
239+
240+
for (i = 0; i < test_args.nr_vcpus; i++) {
241+
ret = pthread_create(&test_vcpu[i].pt_vcpu_run, NULL,
242+
test_vcpu_run, &test_vcpu[i]);
243+
TEST_ASSERT(!ret, "Failed to create vCPU-%d pthread\n", i);
244+
}
245+
246+
for (i = 0; i < test_args.nr_vcpus; i++)
247+
pthread_join(test_vcpu[i].pt_vcpu_run, NULL);
248+
}
249+
250+
static void test_init_timer_irq(struct kvm_vm *vm)
251+
{
252+
/* Timer initid should be same for all the vCPUs, so query only vCPU-0 */
253+
int vcpu0_fd = vcpu_get_fd(vm, 0);
254+
255+
kvm_device_access(vcpu0_fd, KVM_ARM_VCPU_TIMER_CTRL,
256+
KVM_ARM_VCPU_TIMER_IRQ_PTIMER, &ptimer_irq, false);
257+
kvm_device_access(vcpu0_fd, KVM_ARM_VCPU_TIMER_CTRL,
258+
KVM_ARM_VCPU_TIMER_IRQ_VTIMER, &vtimer_irq, false);
259+
260+
sync_global_to_guest(vm, ptimer_irq);
261+
sync_global_to_guest(vm, vtimer_irq);
262+
263+
pr_debug("ptimer_irq: %d; vtimer_irq: %d\n", ptimer_irq, vtimer_irq);
264+
}
265+
266+
static struct kvm_vm *test_vm_create(void)
267+
{
268+
struct kvm_vm *vm;
269+
unsigned int i;
270+
int nr_vcpus = test_args.nr_vcpus;
271+
272+
vm = vm_create_default_with_vcpus(nr_vcpus, 0, 0, guest_code, NULL);
273+
274+
vm_init_descriptor_tables(vm);
275+
vm_install_exception_handler(vm, VECTOR_IRQ_CURRENT, guest_irq_handler);
276+
277+
for (i = 0; i < nr_vcpus; i++) {
278+
vcpu_init_descriptor_tables(vm, i);
279+
280+
test_vcpu[i].vcpuid = i;
281+
test_vcpu[i].vm = vm;
282+
}
283+
284+
ucall_init(vm, NULL);
285+
test_init_timer_irq(vm);
286+
vgic_v3_setup(vm, nr_vcpus, GICD_BASE_GPA, GICR_BASE_GPA);
287+
288+
/* Make all the test's cmdline args visible to the guest */
289+
sync_global_to_guest(vm, test_args);
290+
291+
return vm;
292+
}
293+
294+
static void test_print_help(char *name)
295+
{
296+
pr_info("Usage: %s [-h] [-n nr_vcpus] [-i iterations] [-p timer_period_ms]\n",
297+
name);
298+
pr_info("\t-n: Number of vCPUs to configure (default: %u; max: %u)\n",
299+
NR_VCPUS_DEF, KVM_MAX_VCPUS);
300+
pr_info("\t-i: Number of iterations per stage (default: %u)\n",
301+
NR_TEST_ITERS_DEF);
302+
pr_info("\t-p: Periodicity (in ms) of the guest timer (default: %u)\n",
303+
TIMER_TEST_PERIOD_MS_DEF);
304+
pr_info("\t-h: print this help screen\n");
305+
}
306+
307+
static bool parse_args(int argc, char *argv[])
308+
{
309+
int opt;
310+
311+
while ((opt = getopt(argc, argv, "hn:i:p:")) != -1) {
312+
switch (opt) {
313+
case 'n':
314+
test_args.nr_vcpus = atoi(optarg);
315+
if (test_args.nr_vcpus <= 0) {
316+
pr_info("Positive value needed for -n\n");
317+
goto err;
318+
} else if (test_args.nr_vcpus > KVM_MAX_VCPUS) {
319+
pr_info("Max allowed vCPUs: %u\n",
320+
KVM_MAX_VCPUS);
321+
goto err;
322+
}
323+
break;
324+
case 'i':
325+
test_args.nr_iter = atoi(optarg);
326+
if (test_args.nr_iter <= 0) {
327+
pr_info("Positive value needed for -i\n");
328+
goto err;
329+
}
330+
break;
331+
case 'p':
332+
test_args.timer_period_ms = atoi(optarg);
333+
if (test_args.timer_period_ms <= 0) {
334+
pr_info("Positive value needed for -p\n");
335+
goto err;
336+
}
337+
break;
338+
case 'h':
339+
default:
340+
goto err;
341+
}
342+
}
343+
344+
return true;
345+
346+
err:
347+
test_print_help(argv[0]);
348+
return false;
349+
}
350+
351+
int main(int argc, char *argv[])
352+
{
353+
struct kvm_vm *vm;
354+
355+
/* Tell stdout not to buffer its content */
356+
setbuf(stdout, NULL);
357+
358+
if (!parse_args(argc, argv))
359+
exit(KSFT_SKIP);
360+
361+
vm = test_vm_create();
362+
test_run(vm);
363+
kvm_vm_free(vm);
364+
365+
return 0;
366+
}

0 commit comments

Comments
 (0)