Add tests that validates some edge cases related to the virtual arch-timer: - timers above the max TVAL value. - timers in the past, including TVALs that rollover from 0. - timers across counter roll-overs. - moving counters ahead and behind pending timers. - reprograming timers. - the same timer condition firing multiple times. - masking/unmasking using the timer control mask. Reviewed-by: Reiji Watanabe <reijiw@xxxxxxxxxx> Signed-off-by: Ricardo Koller <ricarkol@xxxxxxxxxx> --- .../kvm/aarch64/arch_timer_edge_cases.c | 555 +++++++++++++++++- 1 file changed, 550 insertions(+), 5 deletions(-) diff --git a/tools/testing/selftests/kvm/aarch64/arch_timer_edge_cases.c b/tools/testing/selftests/kvm/aarch64/arch_timer_edge_cases.c index 48c886bce849..ec791a7d2e8d 100644 --- a/tools/testing/selftests/kvm/aarch64/arch_timer_edge_cases.c +++ b/tools/testing/selftests/kvm/aarch64/arch_timer_edge_cases.c @@ -2,6 +2,16 @@ /* * arch_timer_edge_cases.c - Tests the aarch64 timer IRQ functionality. * + * The test validates some edge cases related to the virtual arch-timer: + * - timers above the max TVAL value. + * - timers in the past, including TVALs that rollover from 0. + * - timers across counter roll-overs. + * - masking a pending timer across counter roll-overs. + * - moving counters ahead and behind pending timers. + * - reprograming timers. + * - the same timer condition firing multiple times. + * - masking/unmasking using the timer control mask. + * * Copyright (c) 2021, Google LLC. */ @@ -46,14 +56,20 @@ /* Number of runs. */ #define NR_TEST_ITERS_DEF 5 +/* Default "long" wait test time in ms. */ +#define LONG_WAIT_TEST_MS 100 + /* Shared with IRQ handler. */ volatile struct test_vcpu_shared_data { int handled; + int spurious; } shared_data; struct test_args { /* Virtual or physical timer and counter tests. */ enum arch_timer timer; + /* Delay used in the test_long_timer_delays test. */ + uint64_t long_wait_ms; /* Number of iterations. */ int iterations; }; @@ -61,6 +77,7 @@ struct test_args { struct test_args test_args = { /* Only testing VIRTUAL timers for now. */ .timer = VIRTUAL, + .long_wait_ms = LONG_WAIT_TEST_MS, .iterations = NR_TEST_ITERS_DEF, }; @@ -68,10 +85,25 @@ static int vtimer_irq, ptimer_irq; typedef enum sync_cmd { SET_REG_KVM_REG_ARM_TIMER_CNT = 100001, + USERSPACE_USLEEP, USERSPACE_SCHED_YIELD, USERSPACE_MIGRATE_SELF, } sync_cmd_t; +typedef void (*sleep_method_t)(uint64_t usec); + +static void sleep_poll(uint64_t usec); +static void sleep_sched_poll(uint64_t usec); +static void sleep_in_userspace(uint64_t usec); +static void sleep_migrate(uint64_t usec); + +sleep_method_t sleep_method[] = { + sleep_poll, + sleep_sched_poll, + sleep_migrate, + sleep_in_userspace, +}; + typedef void (*wfi_method_t)(void); static void wait_for_non_spurious_irq(void); @@ -89,6 +121,9 @@ wfi_method_t wfi_method[] = { #define for_each_wfi_method(i) \ for ((i) = 0; (i) < ARRAY_SIZE(wfi_method); (i)++) +#define for_each_sleep_method(i) \ + for ((i) = 0; (i) < ARRAY_SIZE(sleep_method); (i)++) + typedef enum timer_view { TIMER_CVAL = 1, TIMER_TVAL, @@ -136,6 +171,9 @@ static uint32_t next_pcpu(void) #define TIMER_GET_CNTCT() \ timer_get_cntct(test_args.timer) +#define GUEST_SYNC_CLOCK(__cmd, __val) \ + GUEST_SYNC_ARGS(__cmd, __val, 0, 0, 0) + #define __SET_COUNTER(__ctr, __t) \ GUEST_SYNC_ARGS(SET_REG_KVM_REG_ARM_TIMER_CNT, (__ctr), (__t), 0, 0) @@ -151,6 +189,9 @@ static uint32_t next_pcpu(void) #define USERSPACE_MIGRATE_VCPU() \ USERSPACE_CMD(USERSPACE_MIGRATE_SELF) +#define SLEEP_IN_USERSPACE(__usecs) \ + GUEST_SYNC_ARGS(USERSPACE_USLEEP, (__usecs), 0, 0, 0) + #define IAR_SPURIOUS 1023 static void guest_irq_handler(struct ex_regs *regs) @@ -161,8 +202,10 @@ static void guest_irq_handler(struct ex_regs *regs) GUEST_ASSERT(gic_irq_get_pending(intid)); - if (intid == IAR_SPURIOUS) + if (intid == IAR_SPURIOUS) { + shared_data.spurious++; return; + } ctl = TIMER_GET_CTL(); cnt = TIMER_GET_CNTCT(); @@ -184,6 +227,7 @@ static void guest_irq_handler(struct ex_regs *regs) static void set_cval_irq(uint64_t cval_cycles, uint32_t ctl) { shared_data.handled = 0; + shared_data.spurious = 0; TIMER_SET_CVAL(cval_cycles); TIMER_SET_CTL(ctl); } @@ -191,6 +235,7 @@ static void set_cval_irq(uint64_t cval_cycles, uint32_t ctl) static void set_tval_irq(uint64_t tval_cycles, uint32_t ctl) { shared_data.handled = 0; + shared_data.spurious = 0; TIMER_SET_TVAL(tval_cycles); TIMER_SET_CTL(ctl); } @@ -269,6 +314,60 @@ static void wait_migrate_poll_for_irq(void) poll_for_non_spurious_irq(true, USERSPACE_MIGRATE_SELF); } +/* + * Sleep for usec microseconds by polling in the guest (userspace=0) or in + * userspace (e.g., userspace=1 and userspace_cmd=USERSPACE_SCHEDULE). + */ +static void guest_poll(enum arch_timer timer, uint64_t usec, + bool userspace, sync_cmd_t userspace_cmd) +{ + uint64_t cycles = usec_to_cycles(usec); + uint64_t start = timer_get_cntct(timer); + + /* + * TODO: Take care of roll-overs. Right now, we are fine as we use the + * virtual timer/counter for all of our roll-over tests, and so we can use + * the physical counter for this function. Assert this (temporarily): + */ + GUEST_ASSERT(test_args.timer == VIRTUAL && timer == PHYSICAL); + + while ((timer_get_cntct(timer) - start) < cycles) { + if (userspace) + USERSPACE_CMD(userspace_cmd); + else + cpu_relax(); + } +} + +static void sleep_poll(uint64_t usec) +{ + if (test_args.timer == VIRTUAL) + guest_poll(PHYSICAL, usec, false, -1); + else + GUEST_ASSERT(0); /* Not implemented. */ +} + +static void sleep_sched_poll(uint64_t usec) +{ + if (test_args.timer == VIRTUAL) + guest_poll(PHYSICAL, usec, true, USERSPACE_SCHED_YIELD); + else + GUEST_ASSERT(0); /* Not implemented. */ +} + +static void sleep_migrate(uint64_t usec) +{ + if (test_args.timer == VIRTUAL) + guest_poll(PHYSICAL, usec, true, USERSPACE_MIGRATE_SELF); + else + GUEST_ASSERT(0); /* Not implemented. */ +} + +static void sleep_in_userspace(uint64_t usec) +{ + SLEEP_IN_USERSPACE(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. @@ -315,6 +414,221 @@ static void test_timer_tval(int32_t tval, wfi_method_t wm, bool reset_state, test_timer_xval((uint64_t)tval, TIMER_TVAL, wm, reset_state, reset_cnt); } +static void test_xval_check_no_irq(uint64_t xval, uint64_t usec, + timer_view_t timer_view, + sleep_method_t guest_sleep) +{ + local_irq_disable(); + + set_xval_irq(xval, CTL_ENABLE, timer_view); + guest_sleep(usec); + + local_irq_enable(); + isb(); + + /* Assume success (no IRQ) after waiting usec microseconds */ + ASSERT_IRQS_HANDLED(0); + TIMER_SET_CTL(CTL_IMASK); +} + +static void test_cval_no_irq(uint64_t cval, uint64_t usec, sleep_method_t wm) +{ + test_xval_check_no_irq(cval, usec, TIMER_CVAL, wm); +} + +static void test_tval_no_irq(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((uint64_t)tval, usec, TIMER_TVAL, wm); +} + +/* + * Set the counter to just below the edge (CVAL_MAX) and set a timer that + * crosses it over. + */ +static void test_timers_across_rollovers(void) +{ + uint64_t edge_minus_5ms = CVAL_MAX - msec_to_cycles(5); + int i; + + for_each_wfi_method(i) { + wfi_method_t wm = wfi_method[i]; + + reset_timer_state(edge_minus_5ms); + test_timer_tval(msec_to_cycles(10), wm, false, -1); + + reset_timer_state(edge_minus_5ms); + test_timer_tval(TVAL_MAX, wm, false, -1); + + reset_timer_state(edge_minus_5ms); + test_timer_tval(TVAL_MIN, wm, false, -1); + } +} + +/* Test masking/unmasking a timer using the timer mask (not the IRQ mask). */ +static void test_timer_control_mask_then_unmask(void) +{ + reset_timer_state(DEF_CNT); + set_tval_irq(-1, CTL_ENABLE | CTL_IMASK); + + /* No IRQs because the timer is still masked. */ + ASSERT_IRQS_HANDLED(0); + + /* Unmask the timer, and then get an IRQ. */ + local_irq_disable(); + TIMER_SET_CTL(CTL_ENABLE); + 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(void) +{ + reset_timer_state(DEF_CNT); + + /* Local IRQs are not masked at this point. */ + + set_tval_irq(-1, CTL_ENABLE | CTL_IMASK); + + /* Assume no IRQ after waiting TIMEOUT_NO_IRQ_US microseconds */ + sleep_poll(TIMEOUT_NO_IRQ_US); + + ASSERT_IRQS_HANDLED(0); + TIMER_SET_CTL(CTL_IMASK); +} + +static void test_fire_a_timer_multiple_times(wfi_method_t wm, int num) +{ + int i; + + local_irq_disable(); + reset_timer_state(DEF_CNT); + + set_tval_irq(0, CTL_ENABLE); + + for (i = 1; i <= num; i++) { + wm(); + + /* + * The IRQ handler masked and disabled the timer. + * Enable and unmmask it again. + */ + TIMER_SET_CTL(CTL_ENABLE); + + ASSERT_IRQS_HANDLED(i); + } + + local_irq_enable(); +} + +static void test_timers_fired_multiple_times(void) +{ + int i; + + for_each_wfi_method(i) + test_fire_a_timer_multiple_times(wfi_method[i], 10); +} + +/* + * Set a timer for tval=d_1_ms then reprogram it to tval=d_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(wfi_method_t wm, + int32_t d_1_ms, int32_t d_2_ms) +{ + local_irq_disable(); + reset_timer_state(DEF_CNT); + + /* Program the timer to DEF_CNT + d_1_ms. */ + set_tval_irq(msec_to_cycles(d_1_ms), CTL_ENABLE); + + /* Reprogram the timer to DEF_CNT + d_2_ms. */ + TIMER_SET_TVAL(msec_to_cycles(d_2_ms)); + + wm(); + + /* The IRQ should arrive at DEF_CNT + d_2_ms (or after). */ + GUEST_ASSERT(TIMER_GET_CNTCT() >= DEF_CNT + msec_to_cycles(d_2_ms)); + + local_irq_enable(); + ASSERT_IRQS_HANDLED_1(1, wm); +}; + +/* + * Set a timer for tval=d_1_ms then reprogram it to tval=d_2_ms. Check + * that we get the timer fired in d_2_ms. + */ +static void test_reprogramming_timer_with_timeout(sleep_method_t guest_sleep, + int32_t d_1_ms, int32_t d_2_ms) +{ + local_irq_disable(); + reset_timer_state(DEF_CNT); + + set_tval_irq(msec_to_cycles(d_1_ms), CTL_ENABLE); + + /* Reprogram the timer. */ + TIMER_SET_TVAL(msec_to_cycles(d_2_ms)); + + guest_sleep(msecs_to_usecs(d_2_ms) + TEST_MARGIN_US); + + local_irq_enable(); + isb(); + ASSERT_IRQS_HANDLED(1); +}; + +static void test_reprogram_timers(void) +{ + int i; + + for_each_wfi_method(i) { + test_reprogramming_timer(wfi_method[i], 20, 5); + test_reprogramming_timer(wfi_method[i], 5, 20); + } + + for_each_sleep_method(i) { + test_reprogramming_timer_with_timeout(sleep_method[i], 20, 5); + test_reprogramming_timer_with_timeout(sleep_method[i], 5, 20); + } +} + +/* + * Mask local IRQs, set the counter to MAX-5ms and a timer to fire + * immediately, wait for 10ms to roll-over, and then unmask. The timer should + * not fire as the timer condition is not valid anymore. + */ +static void test_irq_masked_timer_across_rollover(sleep_method_t guest_sleep) +{ + local_irq_disable(); + reset_timer_state(CVAL_MAX - msec_to_cycles(5)); + + set_tval_irq(-1, CTL_ENABLE); + + GUEST_ASSERT(TIMER_GET_CTL() & CTL_ISTATUS); + guest_sleep(msecs_to_usecs(10)); + GUEST_ASSERT((TIMER_GET_CTL() & CTL_ISTATUS) == 0); + + local_irq_enable(); + isb(); + + ASSERT_IRQS_HANDLED(0); +} + +/* + * Set a timer at the edge, and wait with irqs masked for so long that the + * counter rolls over and the "Timer Condition" doesn't apply anymore. + * We should still get an IRQ. + */ +static void test_irq_masked_timers_across_rollovers(void) +{ + int i; + + for_each_sleep_method(i) + test_irq_masked_timer_across_rollover(sleep_method[i]); +} + static void test_basic_functionality(void) { int32_t tval = (int32_t)msec_to_cycles(10); @@ -377,8 +691,7 @@ static void timers_sanity_checks(bool use_sched) /* tval should keep down-counting from 0 to -1. */ TIMER_SET_TVAL(0); - /* We just need 1 cycle to pass. */ - isb(); + sleep_poll(1); GUEST_ASSERT(TIMER_GET_TVAL() < 0); local_irq_enable(); @@ -394,10 +707,229 @@ static void test_timers_sanity_checks(void) timers_sanity_checks(true); } +static void test_set_cnt_after_tval_max(wfi_method_t wm) +{ + local_irq_disable(); + reset_timer_state(DEF_CNT); + + set_cval_irq((uint64_t)TVAL_MAX + msec_to_cycles(5), CTL_ENABLE); + + SET_COUNTER(TVAL_MAX); + wm(); + + ASSERT_IRQS_HANDLED_1(1, wm); + local_irq_enable(); +} + +/* Test timers set for: cval = now + TVAL_MAX + 5ms */ +static void test_timers_above_tval_max(void) +{ + 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 + 5ms" + * would wrap to "cval = now + 5ms", and the timer would fire + * immediately. Test that it doesn't. + */ + for_each_sleep_method(i) { + reset_timer_state(DEF_CNT); + cval = TIMER_GET_CNTCT() + TVAL_MAX + msec_to_cycles(5); + test_cval_no_irq(cval, msecs_to_usecs(5) + TEST_MARGIN_US, + sleep_method[i]); + } + + for_each_wfi_method(i) { + /* Get the IRQ by moving the counter forward. */ + test_set_cnt_after_tval_max(wfi_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(uint64_t cnt_1, uint64_t xval, + uint64_t cnt_2, wfi_method_t wm, timer_view_t tv) +{ + local_irq_disable(); + + SET_COUNTER(cnt_1); + TIMER_SET_CTL(CTL_IMASK); + + set_xval_irq(xval, CTL_ENABLE, tv); + SET_COUNTER(cnt_2); + 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(uint64_t cnt_1, uint64_t xval, + uint64_t cnt_2, sleep_method_t guest_sleep, timer_view_t tv) +{ + local_irq_disable(); + + SET_COUNTER(cnt_1); + TIMER_SET_CTL(CTL_IMASK); + + set_xval_irq(xval, CTL_ENABLE, tv); + SET_COUNTER(cnt_2); + guest_sleep(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(CTL_IMASK); +} + +static void test_set_cnt_after_tval(uint64_t cnt_1, int32_t tval, + uint64_t cnt_2, wfi_method_t wm) +{ + test_set_cnt_after_xval(cnt_1, tval, cnt_2, wm, TIMER_TVAL); +} + +static void test_set_cnt_after_cval(uint64_t cnt_1, uint64_t cval, + uint64_t cnt_2, wfi_method_t wm) +{ + test_set_cnt_after_xval(cnt_1, cval, cnt_2, wm, TIMER_CVAL); +} + +static void test_set_cnt_after_tval_no_irq(uint64_t cnt_1, int32_t tval, + uint64_t cnt_2, sleep_method_t wm) +{ + test_set_cnt_after_xval_no_irq(cnt_1, tval, cnt_2, wm, TIMER_TVAL); +} + +static void test_set_cnt_after_cval_no_irq(uint64_t cnt_1, uint64_t cval, + uint64_t cnt_2, sleep_method_t wm) +{ + test_set_cnt_after_xval_no_irq(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(void) +{ + int i; + int32_t tval; + + for_each_wfi_method(i) { + wfi_method_t wm = wfi_method[i]; + + test_set_cnt_after_cval(0, DEF_CNT, DEF_CNT + 1, wm); + test_set_cnt_after_cval(CVAL_MAX, 1, 2, wm); + + /* Move counter ahead of negative tval. */ + test_set_cnt_after_tval(0, -1, DEF_CNT + 1, wm); + test_set_cnt_after_tval(0, -1, TVAL_MAX, wm); + tval = TVAL_MAX; + test_set_cnt_after_tval(0, tval, (uint64_t)tval + 1, wm); + } + + for_each_sleep_method(i) { + sleep_method_t sm = sleep_method[i]; + + test_set_cnt_after_cval_no_irq(0, DEF_CNT, CVAL_MAX, sm); + test_set_cnt_after_cval_no_irq(0, CVAL_MAX - 1, 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(void) +{ + int i; + + for_each_sleep_method(i) { + sleep_method_t sm = sleep_method[i]; + + test_set_cnt_after_cval_no_irq(DEF_CNT, DEF_CNT - 1, 0, sm); + test_set_cnt_after_tval_no_irq(DEF_CNT, -1, 0, sm); + } +} + +static void test_timers_in_the_past(void) +{ + int32_t tval = -1 * (int32_t)msec_to_cycles(10); + uint64_t cval; + int i; + + for_each_wfi_method(i) { + wfi_method_t wm = wfi_method[i]; + + /* set a timer 10ms the past. */ + cval = DEF_CNT - msec_to_cycles(10); + test_timer_cval(cval, wm, true, DEF_CNT); + test_timer_tval(tval, wm, true, DEF_CNT); + + /* Set a timer to counter=0 (in the past) */ + test_timer_cval(0, wm, true, DEF_CNT); + + /* Set a time for tval=0 (now) */ + test_timer_tval(0, wm, true, DEF_CNT); + + /* Set a timer to as far in the past as possible */ + test_timer_tval(TVAL_MIN, wm, true, DEF_CNT); + } + + /* + * Set the counter to 5ms, and a tval to -10ms. There should be no + * timer as that tval means cval=CVAL_MAX-5ms. + */ + for_each_sleep_method(i) { + sleep_method_t sm = sleep_method[i]; + + SET_COUNTER(msec_to_cycles(5)); + test_tval_no_irq(tval, TIMEOUT_NO_IRQ_US, sm); + } +} + +static void test_long_timer_delays(void) +{ + uint64_t wait_ms = test_args.long_wait_ms; + int32_t tval = (int32_t)msec_to_cycles(wait_ms); + uint64_t cval; + int i; + + for_each_wfi_method(i) { + wfi_method_t wm = wfi_method[i]; + + cval = DEF_CNT + msec_to_cycles(wait_ms); + test_timer_cval(cval, wm, true, DEF_CNT); + test_timer_tval(tval, wm, true, DEF_CNT); + } +} + static void guest_run_iteration(void) { test_basic_functionality(); test_timers_sanity_checks(); + + test_timers_above_tval_max(); + test_timers_in_the_past(); + + test_timers_across_rollovers(); + test_irq_masked_timers_across_rollovers(); + + test_move_counters_ahead_of_timers(); + test_move_counters_behind_timers(); + test_reprogram_timers(); + + test_timers_fired_multiple_times(); + + test_timer_control_mask_then_unmask(); + test_timer_control_masks(); } static void guest_code(void) @@ -420,6 +952,7 @@ static void guest_code(void) guest_run_iteration(); } + test_long_timer_delays(); GUEST_DONE(); } @@ -492,6 +1025,9 @@ static void handle_sync(struct kvm_vm *vm, struct ucall *uc) case SET_REG_KVM_REG_ARM_TIMER_CNT: kvm_set_cntxct(vm, val, timer); break; + case USERSPACE_USLEEP: + usleep(val); + break; case USERSPACE_SCHED_YIELD: sched_yield(); break; @@ -570,13 +1106,15 @@ static struct kvm_vm *test_vm_create(void) static void test_print_help(char *name) { - pr_info("Usage: %s [-h] [-i iterations] [-w] [-p pcpu1,pcpu2]\n", + pr_info("Usage: %s [-h] [-i iterations] [-p pcpu1,pcpu2] [-l long_wait_ms]\n", name); pr_info("\t-i: Number of iterations (default: %u)\n", NR_TEST_ITERS_DEF); pr_info("\t-p: Pair of pcpus for the vcpus to alternate between. " "Defaults to use the current cpu and the one right after " "in the affinity set.\n"); + pr_info("\t-l: Delta (in ms) used for long wait time test (default: %u)\n", + LONG_WAIT_TEST_MS); pr_info("\t-h: Print this help message\n"); } @@ -584,7 +1122,7 @@ static bool parse_args(int argc, char *argv[]) { int opt, ret; - while ((opt = getopt(argc, argv, "hi:p:")) != -1) { + while ((opt = getopt(argc, argv, "hi:p:l:")) != -1) { switch (opt) { case 'i': test_args.iterations = atoi(optarg); @@ -600,6 +1138,13 @@ static bool parse_args(int argc, char *argv[]) goto err; } break; + case 'l': + test_args.long_wait_ms = atoi(optarg); + if (test_args.long_wait_ms <= 0) { + pr_info("Positive value needed for -l\n"); + goto err; + } + break; case 'h': default: goto err; -- 2.35.1.574.g5d30c73bfb-goog