Tests will initialize the ucall implementation in 3 main ways, depending on the test/architecture: 1) by relying on a default ucall implementation being available without the need to do any additional setup 2) by calling ucall_init() to initialize the default ucall implementation 3) by using ucall_init_ops() to initialize a specific ucall implementation and in each of these cases it may use the ucall implementation to execute the standard ucall()/get_ucall() interfaces, or the new ucall_shared()/get_ucall_shared() interfaces. Implement a basic self-test to exercise ucall under all the scenarios that are applicable for a particular architecture. Signed-off-by: Michael Roth <michael.roth@xxxxxxx> --- tools/testing/selftests/kvm/.gitignore | 1 + tools/testing/selftests/kvm/Makefile | 3 + tools/testing/selftests/kvm/ucall_test.c | 182 +++++++++++++++++++++++ 3 files changed, 186 insertions(+) create mode 100644 tools/testing/selftests/kvm/ucall_test.c diff --git a/tools/testing/selftests/kvm/.gitignore b/tools/testing/selftests/kvm/.gitignore index 3763105029fb..4a801cba9c62 100644 --- a/tools/testing/selftests/kvm/.gitignore +++ b/tools/testing/selftests/kvm/.gitignore @@ -57,3 +57,4 @@ /steal_time /kvm_binary_stats_test /system_counter_offset_test +/ucall_test diff --git a/tools/testing/selftests/kvm/Makefile b/tools/testing/selftests/kvm/Makefile index 06a02b6fa907..412de8093e6c 100644 --- a/tools/testing/selftests/kvm/Makefile +++ b/tools/testing/selftests/kvm/Makefile @@ -88,6 +88,7 @@ TEST_GEN_PROGS_x86_64 += set_memory_region_test TEST_GEN_PROGS_x86_64 += steal_time TEST_GEN_PROGS_x86_64 += kvm_binary_stats_test TEST_GEN_PROGS_x86_64 += system_counter_offset_test +TEST_GEN_PROGS_x86_64 += ucall_test TEST_GEN_PROGS_aarch64 += aarch64/arch_timer TEST_GEN_PROGS_aarch64 += aarch64/debug-exceptions @@ -105,6 +106,7 @@ TEST_GEN_PROGS_aarch64 += rseq_test TEST_GEN_PROGS_aarch64 += set_memory_region_test TEST_GEN_PROGS_aarch64 += steal_time TEST_GEN_PROGS_aarch64 += kvm_binary_stats_test +TEST_GEN_PROGS_aarch64 += ucall_test TEST_GEN_PROGS_s390x = s390x/memop TEST_GEN_PROGS_s390x += s390x/resets @@ -116,6 +118,7 @@ TEST_GEN_PROGS_s390x += kvm_page_table_test TEST_GEN_PROGS_s390x += rseq_test TEST_GEN_PROGS_s390x += set_memory_region_test TEST_GEN_PROGS_s390x += kvm_binary_stats_test +TEST_GEN_PROGS_s390x += ucall_test TEST_GEN_PROGS += $(TEST_GEN_PROGS_$(UNAME_M)) LIBKVM += $(LIBKVM_$(UNAME_M)) diff --git a/tools/testing/selftests/kvm/ucall_test.c b/tools/testing/selftests/kvm/ucall_test.c new file mode 100644 index 000000000000..f0e6e4e79786 --- /dev/null +++ b/tools/testing/selftests/kvm/ucall_test.c @@ -0,0 +1,182 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ucall interface/implementation tests. + * + * Copyright (C) 2021 Advanced Micro Devices + */ +#define _GNU_SOURCE /* for program_invocation_short_name */ +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> + +#include "test_util.h" + +#include "kvm_util.h" +#include "processor.h" + +#define VCPU_ID 2 +#define TOTAL_PAGES 512 + +enum uc_test_type { + UC_TEST_WITHOUT_UCALL_INIT, + UC_TEST_WITH_UCALL_INIT, + UC_TEST_WITH_UCALL_INIT_OPS, + UC_TEST_WITH_UCALL_INIT_OPS_SHARED, + UC_TEST_MAX, +}; + +struct uc_test_config { + enum uc_test_type type; + const struct ucall_ops *ops; +}; + +static void test_ucall(void) +{ + GUEST_SYNC(1); + GUEST_SYNC(2); + GUEST_DONE(); + GUEST_ASSERT(false); +} + +static void check_ucall(struct kvm_vm *vm) +{ + struct ucall uc_tmp; + + vcpu_run(vm, VCPU_ID); + TEST_ASSERT(get_ucall(vm, VCPU_ID, &uc_tmp) == UCALL_SYNC, "sync failed"); + + vcpu_run(vm, VCPU_ID); + TEST_ASSERT(get_ucall(vm, VCPU_ID, &uc_tmp) == UCALL_SYNC, "sync failed"); + + vcpu_run(vm, VCPU_ID); + TEST_ASSERT(get_ucall(vm, VCPU_ID, &uc_tmp) == UCALL_DONE, "done failed"); + + vcpu_run(vm, VCPU_ID); + TEST_ASSERT(get_ucall(vm, VCPU_ID, &uc_tmp) == UCALL_ABORT, "abort failed"); +} + +static void test_ucall_shared(struct ucall *uc) +{ + GUEST_SHARED_SYNC(uc, 1); + GUEST_SHARED_SYNC(uc, 2); + GUEST_SHARED_DONE(uc); + GUEST_SHARED_ASSERT(uc, false); +} + +static void check_ucall_shared(struct kvm_vm *vm, struct ucall *uc) +{ + vcpu_run(vm, VCPU_ID); + CHECK_SHARED_SYNC(vm, VCPU_ID, uc, 1); + + vcpu_run(vm, VCPU_ID); + CHECK_SHARED_SYNC(vm, VCPU_ID, uc, 2); + + vcpu_run(vm, VCPU_ID); + CHECK_SHARED_DONE(vm, VCPU_ID, uc); + + vcpu_run(vm, VCPU_ID); + CHECK_SHARED_ABORT(vm, VCPU_ID, uc); +} + +static void __attribute__((__flatten__)) +guest_code(struct ucall *uc) +{ + if (uc) + test_ucall_shared(uc); + else + test_ucall(); +} + +static struct kvm_vm *setup_vm(void) +{ + struct kvm_vm *vm; + + vm = vm_create(VM_MODE_DEFAULT, 0, O_RDWR); + vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, 0, 0, TOTAL_PAGES, 0); + + /* Set up VCPU and initial guest kernel. */ + vm_vcpu_add_default(vm, VCPU_ID, guest_code); + kvm_vm_elf_load(vm, program_invocation_name); + + return vm; +} + +static void setup_vm_args(struct kvm_vm *vm, vm_vaddr_t uc_gva) +{ + vcpu_args_set(vm, VCPU_ID, 1, uc_gva); +} + +static void run_ucall_test(const struct uc_test_config *config) +{ + struct kvm_vm *vm = setup_vm(); + const struct ucall_ops *ops = config->ops; + bool is_default_ops = (!ops || ops == &ucall_ops_default); + bool shared = (config->type == UC_TEST_WITH_UCALL_INIT_OPS_SHARED); + + pr_info("Testing ucall%s ops for: %s%s\n", + shared ? "_shared" : "", + ops ? ops->name : "unspecified", + is_default_ops ? " (via default)" : ""); + + if (config->type == UC_TEST_WITH_UCALL_INIT) + ucall_init(vm, NULL); + else if (config->type == UC_TEST_WITH_UCALL_INIT_OPS || + config->type == UC_TEST_WITH_UCALL_INIT_OPS_SHARED) + ucall_init_ops(vm, NULL, config->ops); + + if (shared) { + struct ucall *uc; + vm_vaddr_t uc_gva; + + /* Set up ucall buffer. */ + uc_gva = ucall_shared_alloc(vm, 1); + uc = addr_gva2hva(vm, uc_gva); + + setup_vm_args(vm, uc_gva); + check_ucall_shared(vm, uc); + } else { + setup_vm_args(vm, 0); + check_ucall(vm); + } + + if (config->type == UC_TEST_WITH_UCALL_INIT) + ucall_uninit(vm); + else if (config->type == UC_TEST_WITH_UCALL_INIT_OPS || + config->type == UC_TEST_WITH_UCALL_INIT_OPS_SHARED) + ucall_uninit_ops(vm); + + kvm_vm_free(vm); +} + +static const struct uc_test_config test_configs[] = { +#if defined(__x86_64__) + { UC_TEST_WITHOUT_UCALL_INIT, NULL }, + { UC_TEST_WITH_UCALL_INIT, NULL }, + { UC_TEST_WITH_UCALL_INIT_OPS, &ucall_ops_default }, + { UC_TEST_WITH_UCALL_INIT_OPS, &ucall_ops_pio }, + { UC_TEST_WITH_UCALL_INIT_OPS_SHARED, &ucall_ops_pio }, + { UC_TEST_WITH_UCALL_INIT_OPS_SHARED, &ucall_ops_halt }, +#elif defined(__aarch64__) + { UC_TEST_WITH_UCALL_INIT, NULL }, + { UC_TEST_WITH_UCALL_INIT_OPS, &ucall_ops_default }, + { UC_TEST_WITH_UCALL_INIT_OPS, &ucall_ops_mmio }, +#elif defined(__s390x__) + { UC_TEST_WITHOUT_UCALL_INIT, NULL }, + { UC_TEST_WITH_UCALL_INIT, NULL }, + { UC_TEST_WITH_UCALL_INIT_OPS, &ucall_ops_default }, + { UC_TEST_WITH_UCALL_INIT_OPS, &ucall_ops_diag501 }, +#endif + { UC_TEST_MAX, NULL }, +}; + +int main(int argc, char *argv[]) +{ + int i; + + for (i = 0; test_configs[i].type != UC_TEST_MAX; i++) + run_ucall_test(&test_configs[i]); + + return 0; +} -- 2.25.1