Test that the KVM_{GET,SET}_SYSTEM_COUNTER_STATE ioctls correctly configure the guest's view of the virtual counter-timer (CNTVCT_EL0). Reviewed-by: Jing Zhang <jingzhangos@xxxxxxxxxx> Reviewed-by: Jim Mattson <jmattson@xxxxxxxxxx> Reviewed-by: David Matlack <dmatlack@xxxxxxxxxx> Reviewed-by: Peter Shier <pshier@xxxxxxxxxx> Reviewed-by: Ricardo Koller <ricarkol@xxxxxxxxxx> Signed-off-by: Oliver Upton <oupton@xxxxxxxxxx> --- tools/testing/selftests/kvm/.gitignore | 1 + tools/testing/selftests/kvm/Makefile | 1 + .../selftests/kvm/include/aarch64/processor.h | 24 +++ .../selftests/kvm/system_counter_state_test.c | 199 ++++++++++++++++++ 4 files changed, 225 insertions(+) create mode 100644 tools/testing/selftests/kvm/system_counter_state_test.c diff --git a/tools/testing/selftests/kvm/.gitignore b/tools/testing/selftests/kvm/.gitignore index bd83158e0e0b..1a5782d8a0d4 100644 --- a/tools/testing/selftests/kvm/.gitignore +++ b/tools/testing/selftests/kvm/.gitignore @@ -34,6 +34,7 @@ /x86_64/xen_vmcall_test /x86_64/xss_msr_test /x86_64/vmx_pmu_msrs_test +/system_counter_state_test /demand_paging_test /dirty_log_test /dirty_log_perf_test diff --git a/tools/testing/selftests/kvm/Makefile b/tools/testing/selftests/kvm/Makefile index e439d027939d..b14f16dc954a 100644 --- a/tools/testing/selftests/kvm/Makefile +++ b/tools/testing/selftests/kvm/Makefile @@ -80,6 +80,7 @@ TEST_GEN_PROGS_x86_64 += steal_time TEST_GEN_PROGS_aarch64 += aarch64/get-reg-list TEST_GEN_PROGS_aarch64 += aarch64/get-reg-list-sve TEST_GEN_PROGS_aarch64 += aarch64/vgic_init +TEST_GEN_PROGS_aarch64 += system_counter_state_test TEST_GEN_PROGS_aarch64 += demand_paging_test TEST_GEN_PROGS_aarch64 += dirty_log_test TEST_GEN_PROGS_aarch64 += dirty_log_perf_test diff --git a/tools/testing/selftests/kvm/include/aarch64/processor.h b/tools/testing/selftests/kvm/include/aarch64/processor.h index b7fa0c8551db..48c964ce62ff 100644 --- a/tools/testing/selftests/kvm/include/aarch64/processor.h +++ b/tools/testing/selftests/kvm/include/aarch64/processor.h @@ -52,6 +52,30 @@ static inline void set_reg(struct kvm_vm *vm, uint32_t vcpuid, uint64_t id, uint vcpu_ioctl(vm, vcpuid, KVM_SET_ONE_REG, ®); } +static inline uint64_t read_cntpct_ordered(void) +{ + uint64_t r; + + asm volatile("isb\n\t" + "mrs %0, cntpct_el0\n\t" + "isb\n\t" + : "=r"(r)); + + return r; +} + +static inline uint64_t read_cntvct_ordered(void) +{ + uint64_t r; + + asm volatile("isb\n\t" + "mrs %0, cntvct_el0\n\t" + "isb\n\t" + : "=r"(r)); + + return r; +} + void aarch64_vcpu_setup(struct kvm_vm *vm, int vcpuid, struct kvm_vcpu_init *init); void aarch64_vcpu_add_default(struct kvm_vm *vm, uint32_t vcpuid, struct kvm_vcpu_init *init, void *guest_code); diff --git a/tools/testing/selftests/kvm/system_counter_state_test.c b/tools/testing/selftests/kvm/system_counter_state_test.c new file mode 100644 index 000000000000..059971f6cb87 --- /dev/null +++ b/tools/testing/selftests/kvm/system_counter_state_test.c @@ -0,0 +1,199 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * system_counter_state_test.c -- suite of tests for correctness of + * KVM_{GET,SET}_SYSTEM_COUNTER_STATE ioctls. + * + * Copyright (c) 2021, Google LLC. + */ +#define _GNU_SOURCE +#include <asm/kvm.h> +#include <linux/kvm.h> +#include <stdint.h> + +#include "kvm_util.h" +#include "processor.h" +#include "test_util.h" + +#define VCPU_ID 0 + +#ifdef __aarch64__ + +enum counter { + VIRTUAL, + PHYSICAL, +}; + +static char *counter_name(enum counter counter) +{ + switch (counter) { + case VIRTUAL: + return "virtual"; + case PHYSICAL: + return "physical"; + default: + TEST_ASSERT(false, "unrecognized counter: %d", counter); + } + + /* never reached */ + return NULL; +} + +struct system_counter_state_test { + enum counter counter; + struct kvm_system_counter_state state; +}; + +static struct system_counter_state_test test_cases[] = { + { + .counter = VIRTUAL, + .state = { + .cntvoff = 0 + } + }, + { + .counter = VIRTUAL, + .state = { + .cntvoff = 1000000 + } + }, + { + .counter = VIRTUAL, + .state = { + .cntvoff = -1 + } + }, +}; + +static void pr_test(struct system_counter_state_test *test) +{ + pr_info("counter: %s, cntvoff: %lld\n", counter_name(test->counter), test->state.cntvoff); +} + +static struct kvm_system_counter_state * +get_system_counter_state(struct system_counter_state_test *test) +{ + return &test->state; +} + +/* + * Reads the guest counter-timer under test. + */ +static uint64_t guest_read_counter(struct system_counter_state_test *test) +{ + switch (test->counter) { + case PHYSICAL: + return read_cntpct_ordered(); + case VIRTUAL: + return read_cntvct_ordered(); + default: + GUEST_ASSERT(0); + } + + /* never reached */ + return -1; +} + +/* + * Reads the host physical counter-timer and transforms it into a guest value + * according to the kvm_system_counter_state structure. + */ +static uint64_t host_read_guest_counter(struct system_counter_state_test *test) +{ + uint64_t r; + + r = read_cntvct_ordered(); + switch (test->counter) { + case VIRTUAL: + r -= test->state.cntvoff; + break; + default: + TEST_ASSERT(false, "unrecognized counter: %d", test->counter); + } + + return r; +} + +#else +#error test not implemented for architecture being built! +#endif + +static void guest_main(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(test_cases); i++) { + struct system_counter_state_test *test = &test_cases[i]; + + GUEST_SYNC(guest_read_counter(test)); + } + + GUEST_DONE(); +} + +static bool enter_guest(struct kvm_vm *vm, uint64_t *guest_counter) +{ + struct ucall uc; + + vcpu_ioctl(vm, VCPU_ID, KVM_RUN, NULL); + + switch (get_ucall(vm, VCPU_ID, &uc)) { + case UCALL_DONE: + return true; + case UCALL_SYNC: + if (guest_counter) + *guest_counter = uc.args[1]; + break; + case UCALL_ABORT: + TEST_ASSERT(false, "%s at %s:%ld", (const char *)uc.args[0], + __FILE__, uc.args[1]); + break; + default: + TEST_ASSERT(false, "unexpected exit: %s", + exit_reason_str(vcpu_state(vm, VCPU_ID)->exit_reason)); + break; + } + + /* more work to do in the guest */ + return false; +} + +static void run_tests(struct kvm_vm *vm) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(test_cases); i++) { + struct system_counter_state_test *test = &test_cases[i]; + uint64_t start, end, obs; + + pr_info("test %d: ", i); + pr_test(test); + + vcpu_ioctl(vm, VCPU_ID, KVM_SET_SYSTEM_COUNTER_STATE, + get_system_counter_state(test)); + + start = host_read_guest_counter(test); + TEST_ASSERT(!enter_guest(vm, &obs), "guest completed unexpectedly"); + end = host_read_guest_counter(test); + + TEST_ASSERT(start < obs && obs < end, + "guest counter value (%ld) outside expected bounds: (%ld, %ld)", + obs, start, end); + } + + TEST_ASSERT(enter_guest(vm, NULL), "guest didn't run to completion"); +} + +int main(void) +{ + struct kvm_vm *vm; + + if (!kvm_check_cap(KVM_CAP_SYSTEM_COUNTER_STATE)) { + print_skip("KVM_CAP_SYSTEM_COUNTER_STATE not supported"); + exit(KSFT_SKIP); + } + + vm = vm_create_default(0, 0, guest_main); + ucall_init(vm, NULL); + run_tests(vm); + kvm_vm_free(vm); +} -- 2.32.0.rc1.229.g3e70b5a671-goog