|
| 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