// SPDX-License-Identifier: GPL-2.0-only /* * arch_timer_edge_cases.c - Tests the aarch64 timer IRQ functionality. * * The test validates some edge cases related to the arch-timer: * - timers above the max TVAL value. * - timers in the past * - moving counters ahead and behind pending timers. * - reprograming timers. * - timers fired multiple times. * - masking/unmasking using the timer control mask. * * Copyright (c) 2021, Google LLC. */ #define _GNU_SOURCE #include #include #include "arch_timer.h" #include "gic.h" #include "vgic.h" static const uint64_t CVAL_MAX = ~0ULL; /* tval is a signed 32-bit int. */ static const int32_t TVAL_MAX = INT32_MAX; static const int32_t TVAL_MIN = INT32_MIN; /* After how much time we say there is no IRQ. */ static const uint32_t TIMEOUT_NO_IRQ_US = 50000; /* A nice counter value to use as the starting one for most tests. */ static const uint64_t DEF_CNT = (CVAL_MAX / 2); /* Number of runs. */ static const uint32_t NR_TEST_ITERS_DEF = 5; /* Default wait test time in ms. */ static const uint32_t WAIT_TEST_MS = 10; /* Default "long" wait test time in ms. */ static const uint32_t LONG_WAIT_TEST_MS = 100; /* Shared with IRQ handler. */ struct test_vcpu_shared_data { atomic_t handled; atomic_t spurious; } shared_data; struct test_args { /* Virtual or physical timer and counter tests. */ enum arch_timer timer; /* Delay used for most timer tests. */ uint64_t wait_ms; /* Delay used in the test_long_timer_delays test. */ uint64_t long_wait_ms; /* Number of iterations. */ int iterations; /* Whether to test the physical timer. */ bool test_physical; /* Whether to test the virtual timer. */ bool test_virtual; }; struct test_args test_args = { .wait_ms = WAIT_TEST_MS, .long_wait_ms = LONG_WAIT_TEST_MS, .iterations = NR_TEST_ITERS_DEF, .test_physical = true, .test_virtual = true, }; static int vtimer_irq, ptimer_irq; enum sync_cmd { SET_COUNTER_VALUE, USERSPACE_USLEEP, USERSPACE_SCHED_YIELD, USERSPACE_MIGRATE_SELF, NO_USERSPACE_CMD, }; typedef void (*sleep_method_t)(enum arch_timer timer, uint64_t usec); static void sleep_poll(enum arch_timer timer, uint64_t usec); static void sleep_sched_poll(enum arch_timer timer, uint64_t usec); static void sleep_in_userspace(enum arch_timer timer, uint64_t usec); static void sleep_migrate(enum arch_timer timer, uint64_t usec); sleep_method_t sleep_method[] = { sleep_poll, sleep_sched_poll, sleep_migrate, sleep_in_userspace, }; typedef void (*irq_wait_method_t)(void); static void wait_for_non_spurious_irq(void); static void wait_poll_for_irq(void); static void wait_sched_poll_for_irq(void); static void wait_migrate_poll_for_irq(void); irq_wait_method_t irq_wait_method[] = { wait_for_non_spurious_irq, wait_poll_for_irq, wait_sched_poll_for_irq, wait_migrate_poll_for_irq, }; enum timer_view { TIMER_CVAL, TIMER_TVAL, }; static void assert_irqs_handled(uint32_t n) { int h = atomic_read(&shared_data.handled); __GUEST_ASSERT(h == n, "Handled %d IRQS but expected %d", h, n); } static void userspace_cmd(uint64_t cmd) { GUEST_SYNC_ARGS(cmd, 0, 0, 0, 0); } static void userspace_migrate_vcpu(void) { userspace_cmd(USERSPACE_MIGRATE_SELF); } static void userspace_sleep(uint64_t usecs) { GUEST_SYNC_ARGS(USERSPACE_USLEEP, usecs, 0, 0, 0); } static void set_counter(enum arch_timer timer, uint64_t counter) { GUEST_SYNC_ARGS(SET_COUNTER_VALUE, counter, timer, 0, 0); } static void guest_irq_handler(struct ex_regs *regs) { unsigned int intid = gic_get_and_ack_irq(); enum arch_timer timer; uint64_t cnt, cval; uint32_t ctl; bool timer_condition, istatus; if (intid == IAR_SPURIOUS) { atomic_inc(&shared_data.spurious); goto out; } if (intid == ptimer_irq) timer = PHYSICAL; else if (intid == vtimer_irq) timer = VIRTUAL; else goto out; ctl = timer_get_ctl(timer); cval = timer_get_cval(timer); cnt = timer_get_cntct(timer); timer_condition = cnt >= cval; istatus = (ctl & CTL_ISTATUS) && (ctl & CTL_ENABLE); GUEST_ASSERT_EQ(timer_condition, istatus); /* Disable and mask the timer. */ timer_set_ctl(timer, CTL_IMASK); atomic_inc(&shared_data.handled); out: gic_set_eoi(intid); } static void set_cval_irq(enum arch_timer timer, uint64_t cval_cycles, uint32_t ctl) { atomic_set(&shared_data.handled, 0); atomic_set(&shared_data.spurious, 0); timer_set_cval(timer, cval_cycles); timer_set_ctl(timer, ctl); } static void set_tval_irq(enum arch_timer timer, uint64_t tval_cycles, uint32_t ctl) { atomic_set(&shared_data.handled, 0); atomic_set(&shared_data.spurious, 0); timer_set_ctl(timer, ctl); timer_set_tval(timer, tval_cycles); } static void set_xval_irq(enum arch_timer timer, uint64_t xval, uint32_t ctl, enum timer_view tv) { switch (tv) { case TIMER_CVAL: set_cval_irq(timer, xval, ctl); break; case TIMER_TVAL: set_tval_irq(timer, xval, ctl); break; default: GUEST_FAIL("Could not get timer %d", timer); } } /* * Note that this can theoretically hang forever, so we rely on having * a timeout mechanism in the "runner", like: * tools/testing/selftests/kselftest/runner.sh. */ static void wait_for_non_spurious_irq(void) { int h; local_irq_disable(); for (h = atomic_read(&shared_data.handled); h == atomic_read(&shared_data.handled);) { wfi(); local_irq_enable(); isb(); /* handle IRQ */ local_irq_disable(); } } /* * Wait for an non-spurious IRQ by polling in the guest or in * userspace (e.g. userspace_cmd=USERSPACE_SCHED_YIELD). * * Note that this can theoretically hang forever, so we rely on having * a timeout mechanism in the "runner", like: * tools/testing/selftests/kselftest/runner.sh. */ static void poll_for_non_spurious_irq(enum sync_cmd usp_cmd) { int h; local_irq_disable(); h = atomic_read(&shared_data.handled); local_irq_enable(); while (h == atomic_read(&shared_data.handled)) { if (usp_cmd == NO_USERSPACE_CMD) cpu_relax(); else userspace_cmd(usp_cmd); } local_irq_disable(); } static void wait_poll_for_irq(void) { poll_for_non_spurious_irq(NO_USERSPACE_CMD); } static void wait_sched_poll_for_irq(void) { poll_for_non_spurious_irq(USERSPACE_SCHED_YIELD); } static void wait_migrate_poll_for_irq(void) { poll_for_non_spurious_irq(USERSPACE_MIGRATE_SELF); } /* * Sleep for usec microseconds by polling in the guest or in * userspace (e.g. userspace_cmd=USERSPACE_SCHEDULE). */ static void guest_poll(enum arch_timer test_timer, uint64_t usec, enum sync_cmd usp_cmd) { uint64_t cycles = usec_to_cycles(usec); /* Whichever timer we are testing with, sleep with the other. */ enum arch_timer sleep_timer = 1 - test_timer; uint64_t start = timer_get_cntct(sleep_timer); while ((timer_get_cntct(sleep_timer) - start) < cycles) { if (usp_cmd == NO_USERSPACE_CMD) cpu_relax(); else userspace_cmd(usp_cmd); } } static void sleep_poll(enum arch_timer timer, uint64_t usec) { guest_poll(timer, usec, NO_USERSPACE_CMD); } static void sleep_sched_poll(enum arch_timer timer, uint64_t usec) { guest_poll(timer, usec, USERSPACE_SCHED_YIELD); } static void sleep_migrate(enum arch_timer timer, uint64_t usec) { guest_poll(timer, usec, USERSPACE_MIGRATE_SELF); } static void sleep_in_userspace(enum arch_timer timer, uint64_t usec) { userspace_sleep(usec); } /* * Reset the timer state to some nice values like the counter not being close * to the edge, and the control register masked and disabled. */ static void reset_timer_state(enum arch_timer timer, uint64_t cnt) { set_counter(timer, cnt); timer_set_ctl(timer, CTL_IMASK); } static void test_timer_xval(enum arch_timer timer, uint64_t xval, enum timer_view tv, irq_wait_method_t wm, bool reset_state, uint64_t reset_cnt) { local_irq_disable(); if (reset_state) reset_timer_state(timer, reset_cnt); set_xval_irq(timer, xval, CTL_ENABLE, tv); /* This method re-enables IRQs to handle the one we're looking for. */ wm(); assert_irqs_handled(1); local_irq_enable(); } /* * The test_timer_* functions will program the timer, wait for it, and assert * the firing of the correct IRQ. * * These functions don't have a timeout and return as soon as they receive an * IRQ. They can hang (forever), so we rely on having a timeout mechanism in * the "runner", like: tools/testing/selftests/kselftest/runner.sh. */ static void test_timer_cval(enum arch_timer timer, uint64_t cval, irq_wait_method_t wm, bool reset_state, uint64_t reset_cnt) { test_timer_xval(timer, cval, TIMER_CVAL, wm, reset_state, reset_cnt); } static void test_timer_tval(enum arch_timer timer, int32_t tval, irq_wait_method_t wm, bool reset_state, uint64_t reset_cnt) { test_timer_xval(timer, (uint64_t) tval, TIMER_TVAL, wm, reset_state, reset_cnt); } static void test_xval_check_no_irq(enum arch_timer timer, uint64_t xval, uint64_t usec, enum timer_view timer_view, sleep_method_t guest_sleep) { local_irq_disable(); set_xval_irq(timer, xval, CTL_ENABLE | CTL_IMASK, timer_view); guest_sleep(timer, usec); local_irq_enable(); isb(); /* Assume success (no IRQ) after waiting usec microseconds */ assert_irqs_handled(0); } static void test_cval_no_irq(enum arch_timer timer, uint64_t cval, uint64_t usec, sleep_method_t wm) { test_xval_check_no_irq(timer, cval, usec, TIMER_CVAL, wm); } static void test_tval_no_irq(enum arch_timer timer, int32_t tval, uint64_t usec, sleep_method_t wm) { /* tval will be cast to an int32_t in test_xval_check_no_irq */ test_xval_check_no_irq(timer, (uint64_t) tval, usec, TIMER_TVAL, wm); } /* Test masking/unmasking a timer using the timer mask (not the IRQ mask). */ static void test_timer_control_mask_then_unmask(enum arch_timer timer) { reset_timer_state(timer, DEF_CNT); set_tval_irq(timer, -1, CTL_ENABLE | CTL_IMASK); /* Unmask the timer, and then get an IRQ. */ local_irq_disable(); timer_set_ctl(timer, CTL_ENABLE); /* This method re-enables IRQs to handle the one we're looking for. */ wait_for_non_spurious_irq(); assert_irqs_handled(1); local_irq_enable(); } /* Check that timer control masks actually mask a timer being fired. */ static void test_timer_control_masks(enum arch_timer timer) { reset_timer_state(timer, DEF_CNT); /* Local IRQs are not masked at this point. */ set_tval_irq(timer, -1, CTL_ENABLE | CTL_IMASK); /* Assume no IRQ after waiting TIMEOUT_NO_IRQ_US microseconds */ sleep_poll(timer, TIMEOUT_NO_IRQ_US); assert_irqs_handled(0); timer_set_ctl(timer, CTL_IMASK); } static void test_fire_a_timer_multiple_times(enum arch_timer timer, irq_wait_method_t wm, int num) { int i; local_irq_disable(); reset_timer_state(timer, DEF_CNT); set_tval_irq(timer, 0, CTL_ENABLE); for (i = 1; i <= num; i++) { /* This method re-enables IRQs to handle the one we're looking for. */ wm(); /* The IRQ handler masked and disabled the timer. * Enable and unmmask it again. */ timer_set_ctl(timer, CTL_ENABLE); assert_irqs_handled(i); } local_irq_enable(); } static void test_timers_fired_multiple_times(enum arch_timer timer) { int i; for (i = 0; i < ARRAY_SIZE(irq_wait_method); i++) test_fire_a_timer_multiple_times(timer, irq_wait_method[i], 10); } /* * Set a timer for tval=delta_1_ms then reprogram it to * tval=delta_2_ms. Check that we get the timer fired. There is no * timeout for the wait: we use the wfi instruction. */ static void test_reprogramming_timer(enum arch_timer timer, irq_wait_method_t wm, int32_t delta_1_ms, int32_t delta_2_ms) { local_irq_disable(); reset_timer_state(timer, DEF_CNT); /* Program the timer to DEF_CNT + delta_1_ms. */ set_tval_irq(timer, msec_to_cycles(delta_1_ms), CTL_ENABLE); /* Reprogram the timer to DEF_CNT + delta_2_ms. */ timer_set_tval(timer, msec_to_cycles(delta_2_ms)); /* This method re-enables IRQs to handle the one we're looking for. */ wm(); /* The IRQ should arrive at DEF_CNT + delta_2_ms (or after). */ GUEST_ASSERT(timer_get_cntct(timer) >= DEF_CNT + msec_to_cycles(delta_2_ms)); local_irq_enable(); assert_irqs_handled(1); }; static void test_reprogram_timers(enum arch_timer timer) { int i; uint64_t base_wait = test_args.wait_ms; for (i = 0; i < ARRAY_SIZE(irq_wait_method); i++) { /* * Ensure reprogramming works whether going from a * longer time to a shorter or vice versa. */ test_reprogramming_timer(timer, irq_wait_method[i], 2 * base_wait, base_wait); test_reprogramming_timer(timer, irq_wait_method[i], base_wait, 2 * base_wait); } } static void test_basic_functionality(enum arch_timer timer) { int32_t tval = (int32_t) msec_to_cycles(test_args.wait_ms); uint64_t cval = DEF_CNT + msec_to_cycles(test_args.wait_ms); int i; for (i = 0; i < ARRAY_SIZE(irq_wait_method); i++) { irq_wait_method_t wm = irq_wait_method[i]; test_timer_cval(timer, cval, wm, true, DEF_CNT); test_timer_tval(timer, tval, wm, true, DEF_CNT); } } /* * This test checks basic timer behavior without actually firing timers, things * like: the relationship between cval and tval, tval down-counting. */ static void timers_sanity_checks(enum arch_timer timer, bool use_sched) { reset_timer_state(timer, DEF_CNT); local_irq_disable(); /* cval in the past */ timer_set_cval(timer, timer_get_cntct(timer) - msec_to_cycles(test_args.wait_ms)); if (use_sched) userspace_migrate_vcpu(); GUEST_ASSERT(timer_get_tval(timer) < 0); /* tval in the past */ timer_set_tval(timer, -1); if (use_sched) userspace_migrate_vcpu(); GUEST_ASSERT(timer_get_cval(timer) < timer_get_cntct(timer)); /* tval larger than TVAL_MAX. This requires programming with * timer_set_cval instead so the value is expressible */ timer_set_cval(timer, timer_get_cntct(timer) + TVAL_MAX + msec_to_cycles(test_args.wait_ms)); if (use_sched) userspace_migrate_vcpu(); GUEST_ASSERT(timer_get_tval(timer) <= 0); /* * tval larger than 2 * TVAL_MAX. * Twice the TVAL_MAX completely loops around the TVAL. */ timer_set_cval(timer, timer_get_cntct(timer) + 2ULL * TVAL_MAX + msec_to_cycles(test_args.wait_ms)); if (use_sched) userspace_migrate_vcpu(); GUEST_ASSERT(timer_get_tval(timer) <= msec_to_cycles(test_args.wait_ms)); /* negative tval that rollovers from 0. */ set_counter(timer, msec_to_cycles(1)); timer_set_tval(timer, -1 * msec_to_cycles(test_args.wait_ms)); if (use_sched) userspace_migrate_vcpu(); GUEST_ASSERT(timer_get_cval(timer) >= (CVAL_MAX - msec_to_cycles(test_args.wait_ms))); /* tval should keep down-counting from 0 to -1. */ timer_set_tval(timer, 0); sleep_poll(timer, 1); GUEST_ASSERT(timer_get_tval(timer) < 0); local_irq_enable(); /* Mask and disable any pending timer. */ timer_set_ctl(timer, CTL_IMASK); } static void test_timers_sanity_checks(enum arch_timer timer) { timers_sanity_checks(timer, false); /* Check how KVM saves/restores these edge-case values. */ timers_sanity_checks(timer, true); } static void test_set_cnt_after_tval_max(enum arch_timer timer, irq_wait_method_t wm) { local_irq_disable(); reset_timer_state(timer, DEF_CNT); set_cval_irq(timer, (uint64_t) TVAL_MAX + msec_to_cycles(test_args.wait_ms) / 2, CTL_ENABLE); set_counter(timer, TVAL_MAX); /* This method re-enables IRQs to handle the one we're looking for. */ wm(); assert_irqs_handled(1); local_irq_enable(); } /* Test timers set for: cval = now + TVAL_MAX + wait_ms / 2 */ static void test_timers_above_tval_max(enum arch_timer timer) { uint64_t cval; int i; /* * Test that the system is not implementing cval in terms of * tval. If that was the case, setting a cval to "cval = now * + TVAL_MAX + wait_ms" would wrap to "cval = now + * wait_ms", and the timer would fire immediately. Test that it * doesn't. */ for (i = 0; i < ARRAY_SIZE(sleep_method); i++) { reset_timer_state(timer, DEF_CNT); cval = timer_get_cntct(timer) + TVAL_MAX + msec_to_cycles(test_args.wait_ms); test_cval_no_irq(timer, cval, msecs_to_usecs(test_args.wait_ms) + TIMEOUT_NO_IRQ_US, sleep_method[i]); } for (i = 0; i < ARRAY_SIZE(irq_wait_method); i++) { /* Get the IRQ by moving the counter forward. */ test_set_cnt_after_tval_max(timer, irq_wait_method[i]); } } /* * Template function to be used by the test_move_counter_ahead_* tests. It * sets the counter to cnt_1, the [c|t]val, the counter to cnt_2, and * then waits for an IRQ. */ static void test_set_cnt_after_xval(enum arch_timer timer, uint64_t cnt_1, uint64_t xval, uint64_t cnt_2, irq_wait_method_t wm, enum timer_view tv) { local_irq_disable(); set_counter(timer, cnt_1); timer_set_ctl(timer, CTL_IMASK); set_xval_irq(timer, xval, CTL_ENABLE, tv); set_counter(timer, cnt_2); /* This method re-enables IRQs to handle the one we're looking for. */ wm(); assert_irqs_handled(1); local_irq_enable(); } /* * Template function to be used by the test_move_counter_ahead_* tests. It * sets the counter to cnt_1, the [c|t]val, the counter to cnt_2, and * then waits for an IRQ. */ static void test_set_cnt_after_xval_no_irq(enum arch_timer timer, uint64_t cnt_1, uint64_t xval, uint64_t cnt_2, sleep_method_t guest_sleep, enum timer_view tv) { local_irq_disable(); set_counter(timer, cnt_1); timer_set_ctl(timer, CTL_IMASK); set_xval_irq(timer, xval, CTL_ENABLE, tv); set_counter(timer, cnt_2); guest_sleep(timer, TIMEOUT_NO_IRQ_US); local_irq_enable(); isb(); /* Assume no IRQ after waiting TIMEOUT_NO_IRQ_US microseconds */ assert_irqs_handled(0); timer_set_ctl(timer, CTL_IMASK); } static void test_set_cnt_after_tval(enum arch_timer timer, uint64_t cnt_1, int32_t tval, uint64_t cnt_2, irq_wait_method_t wm) { test_set_cnt_after_xval(timer, cnt_1, tval, cnt_2, wm, TIMER_TVAL); } static void test_set_cnt_after_cval(enum arch_timer timer, uint64_t cnt_1, uint64_t cval, uint64_t cnt_2, irq_wait_method_t wm) { test_set_cnt_after_xval(timer, cnt_1, cval, cnt_2, wm, TIMER_CVAL); } static void test_set_cnt_after_tval_no_irq(enum arch_timer timer, uint64_t cnt_1, int32_t tval, uint64_t cnt_2, sleep_method_t wm) { test_set_cnt_after_xval_no_irq(timer, cnt_1, tval, cnt_2, wm, TIMER_TVAL); } static void test_set_cnt_after_cval_no_irq(enum arch_timer timer, uint64_t cnt_1, uint64_t cval, uint64_t cnt_2, sleep_method_t wm) { test_set_cnt_after_xval_no_irq(timer, cnt_1, cval, cnt_2, wm, TIMER_CVAL); } /* Set a timer and then move the counter ahead of it. */ static void test_move_counters_ahead_of_timers(enum arch_timer timer) { int i; int32_t tval; for (i = 0; i < ARRAY_SIZE(irq_wait_method); i++) { irq_wait_method_t wm = irq_wait_method[i]; test_set_cnt_after_cval(timer, 0, DEF_CNT, DEF_CNT + 1, wm); test_set_cnt_after_cval(timer, CVAL_MAX, 1, 2, wm); /* Move counter ahead of negative tval. */ test_set_cnt_after_tval(timer, 0, -1, DEF_CNT + 1, wm); test_set_cnt_after_tval(timer, 0, -1, TVAL_MAX, wm); tval = TVAL_MAX; test_set_cnt_after_tval(timer, 0, tval, (uint64_t) tval + 1, wm); } for (i = 0; i < ARRAY_SIZE(sleep_method); i++) { sleep_method_t sm = sleep_method[i]; test_set_cnt_after_cval_no_irq(timer, 0, DEF_CNT, CVAL_MAX, sm); } } /* * Program a timer, mask it, and then change the tval or counter to cancel it. * Unmask it and check that nothing fires. */ static void test_move_counters_behind_timers(enum arch_timer timer) { int i; for (i = 0; i < ARRAY_SIZE(sleep_method); i++) { sleep_method_t sm = sleep_method[i]; test_set_cnt_after_cval_no_irq(timer, DEF_CNT, DEF_CNT - 1, 0, sm); test_set_cnt_after_tval_no_irq(timer, DEF_CNT, -1, 0, sm); } } static void test_timers_in_the_past(enum arch_timer timer) { int32_t tval = -1 * (int32_t) msec_to_cycles(test_args.wait_ms); uint64_t cval; int i; for (i = 0; i < ARRAY_SIZE(irq_wait_method); i++) { irq_wait_method_t wm = irq_wait_method[i]; /* set a timer wait_ms the past. */ cval = DEF_CNT - msec_to_cycles(test_args.wait_ms); test_timer_cval(timer, cval, wm, true, DEF_CNT); test_timer_tval(timer, tval, wm, true, DEF_CNT); /* Set a timer to counter=0 (in the past) */ test_timer_cval(timer, 0, wm, true, DEF_CNT); /* Set a time for tval=0 (now) */ test_timer_tval(timer, 0, wm, true, DEF_CNT); /* Set a timer to as far in the past as possible */ test_timer_tval(timer, TVAL_MIN, wm, true, DEF_CNT); } /* * Set the counter to wait_ms, and a tval to -wait_ms. There should be no * IRQ as that tval means cval=CVAL_MAX-wait_ms. */ for (i = 0; i < ARRAY_SIZE(sleep_method); i++) { sleep_method_t sm = sleep_method[i]; set_counter(timer, msec_to_cycles(test_args.wait_ms)); test_tval_no_irq(timer, tval, TIMEOUT_NO_IRQ_US, sm); } } static void test_long_timer_delays(enum arch_timer timer) { int32_t tval = (int32_t) msec_to_cycles(test_args.long_wait_ms); uint64_t cval = DEF_CNT + msec_to_cycles(test_args.long_wait_ms); int i; for (i = 0; i < ARRAY_SIZE(irq_wait_method); i++) { irq_wait_method_t wm = irq_wait_method[i]; test_timer_cval(timer, cval, wm, true, DEF_CNT); test_timer_tval(timer, tval, wm, true, DEF_CNT); } } static void guest_run_iteration(enum arch_timer timer) { test_basic_functionality(timer); test_timers_sanity_checks(timer); test_timers_above_tval_max(timer); test_timers_in_the_past(timer); test_move_counters_ahead_of_timers(timer); test_move_counters_behind_timers(timer); test_reprogram_timers(timer); test_timers_fired_multiple_times(timer); test_timer_control_mask_then_unmask(timer); test_timer_control_masks(timer); } static void guest_code(enum arch_timer timer) { int i; local_irq_disable(); gic_init(GIC_V3, 1); timer_set_ctl(VIRTUAL, CTL_IMASK); timer_set_ctl(PHYSICAL, CTL_IMASK); gic_irq_enable(vtimer_irq); gic_irq_enable(ptimer_irq); local_irq_enable(); for (i = 0; i < test_args.iterations; i++) { GUEST_SYNC(i); guest_run_iteration(timer); } test_long_timer_delays(timer); GUEST_DONE(); } static uint32_t next_pcpu(void) { uint32_t max = get_nprocs(); uint32_t cur = sched_getcpu(); uint32_t next = cur; cpu_set_t cpuset; TEST_ASSERT(max > 1, "Need at least two physical cpus"); sched_getaffinity(0, sizeof(cpuset), &cpuset); do { next = (next + 1) % CPU_SETSIZE; } while (!CPU_ISSET(next, &cpuset)); return next; } static void migrate_self(uint32_t new_pcpu) { int ret; cpu_set_t cpuset; pthread_t thread; thread = pthread_self(); CPU_ZERO(&cpuset); CPU_SET(new_pcpu, &cpuset); pr_debug("Migrating from %u to %u\n", sched_getcpu(), new_pcpu); ret = pthread_setaffinity_np(thread, sizeof(cpuset), &cpuset); TEST_ASSERT(ret == 0, "Failed to migrate to pCPU: %u; ret: %d\n", new_pcpu, ret); } static void kvm_set_cntxct(struct kvm_vcpu *vcpu, uint64_t cnt, enum arch_timer timer) { if (timer == PHYSICAL) vcpu_set_reg(vcpu, KVM_REG_ARM_PTIMER_CNT, cnt); else vcpu_set_reg(vcpu, KVM_REG_ARM_TIMER_CNT, cnt); } static void handle_sync(struct kvm_vcpu *vcpu, struct ucall *uc) { enum sync_cmd cmd = uc->args[1]; uint64_t val = uc->args[2]; enum arch_timer timer = uc->args[3]; switch (cmd) { case SET_COUNTER_VALUE: kvm_set_cntxct(vcpu, val, timer); break; case USERSPACE_USLEEP: usleep(val); break; case USERSPACE_SCHED_YIELD: sched_yield(); break; case USERSPACE_MIGRATE_SELF: migrate_self(next_pcpu()); break; default: break; } } static void test_run(struct kvm_vm *vm, struct kvm_vcpu *vcpu) { struct ucall uc; /* Start on CPU 0 */ migrate_self(0); while (true) { vcpu_run(vcpu); switch (get_ucall(vcpu, &uc)) { case UCALL_SYNC: handle_sync(vcpu, &uc); break; case UCALL_DONE: goto out; case UCALL_ABORT: REPORT_GUEST_ASSERT(uc); goto out; default: TEST_FAIL("Unexpected guest exit\n"); } } out: return; } static void test_init_timer_irq(struct kvm_vm *vm, struct kvm_vcpu *vcpu) { vcpu_device_attr_get(vcpu, KVM_ARM_VCPU_TIMER_CTRL, KVM_ARM_VCPU_TIMER_IRQ_PTIMER, &ptimer_irq); vcpu_device_attr_get(vcpu, KVM_ARM_VCPU_TIMER_CTRL, KVM_ARM_VCPU_TIMER_IRQ_VTIMER, &vtimer_irq); sync_global_to_guest(vm, ptimer_irq); sync_global_to_guest(vm, vtimer_irq); pr_debug("ptimer_irq: %d; vtimer_irq: %d\n", ptimer_irq, vtimer_irq); } static void test_vm_create(struct kvm_vm **vm, struct kvm_vcpu **vcpu, enum arch_timer timer) { *vm = vm_create_with_one_vcpu(vcpu, guest_code); TEST_ASSERT(*vm, "Failed to create the test VM\n"); vm_init_descriptor_tables(*vm); vm_install_exception_handler(*vm, VECTOR_IRQ_CURRENT, guest_irq_handler); vcpu_init_descriptor_tables(*vcpu); vcpu_args_set(*vcpu, 1, timer); test_init_timer_irq(*vm, *vcpu); vgic_v3_setup(*vm, 1, 64); sync_global_to_guest(*vm, test_args); } static void test_print_help(char *name) { pr_info("Usage: %s [-h] [-b] [-i iterations] [-l long_wait_ms] [-p] [-v]\n" , name); pr_info("\t-i: Number of iterations (default: %u)\n", NR_TEST_ITERS_DEF); pr_info("\t-b: Test both physical and virtual timers (default: true)\n"); pr_info("\t-l: Delta (in ms) used for long wait time test (default: %u)\n", LONG_WAIT_TEST_MS); pr_info("\t-l: Delta (in ms) used for wait times (default: %u)\n", WAIT_TEST_MS); pr_info("\t-p: Test physical timer (default: true)\n"); pr_info("\t-v: Test virtual timer (default: true)\n"); pr_info("\t-h: Print this help message\n"); } static bool parse_args(int argc, char *argv[]) { int opt; while ((opt = getopt(argc, argv, "bhi:l:pvw:")) != -1) { switch (opt) { case 'b': test_args.test_physical = true; test_args.test_virtual = true; break; case 'i': test_args.iterations = atoi_positive("Number of iterations", optarg); break; case 'l': test_args.long_wait_ms = atoi_positive("Long wait time", optarg); break; case 'p': test_args.test_physical = true; test_args.test_virtual = false; break; case 'v': test_args.test_virtual = true; test_args.test_physical = false; break; case 'w': test_args.wait_ms = atoi_positive("Wait time", optarg); break; case 'h': default: goto err; } } return true; err: test_print_help(argv[0]); return false; } int main(int argc, char *argv[]) { struct kvm_vcpu *vcpu; struct kvm_vm *vm; /* Tell stdout not to buffer its content */ setbuf(stdout, NULL); if (!parse_args(argc, argv)) exit(KSFT_SKIP); if (test_args.test_virtual) { test_vm_create(&vm, &vcpu, VIRTUAL); test_run(vm, vcpu); kvm_vm_free(vm); } if (test_args.test_physical) { test_vm_create(&vm, &vcpu, PHYSICAL); test_run(vm, vcpu); kvm_vm_free(vm); } return 0; }