On Thu, 2022-04-14 at 15:20 +0200, Vitaly Kuznetsov wrote: > Introduce a selftest for Hyper-V PV IPI hypercalls > (HvCallSendSyntheticClusterIpi, HvCallSendSyntheticClusterIpiEx). > > The test creates one 'sender' vCPU and two 'receiver' vCPU and then > issues various combinations of send IPI hypercalls in both 'normal' > and 'fast' (with XMM input where necessary) mode. Later, the test > checks whether IPIs were delivered to the expected destination vCPU[s]. > > Signed-off-by: Vitaly Kuznetsov <vkuznets@xxxxxxxxxx> > --- > tools/testing/selftests/kvm/.gitignore | 1 + > tools/testing/selftests/kvm/Makefile | 1 + > .../selftests/kvm/include/x86_64/hyperv.h | 3 + > .../selftests/kvm/x86_64/hyperv_features.c | 5 +- > .../testing/selftests/kvm/x86_64/hyperv_ipi.c | 374 ++++++++++++++++++ > 5 files changed, 381 insertions(+), 3 deletions(-) > create mode 100644 tools/testing/selftests/kvm/x86_64/hyperv_ipi.c > > diff --git a/tools/testing/selftests/kvm/.gitignore b/tools/testing/selftests/kvm/.gitignore > index 56140068b763..5d5fbb161d56 100644 > --- a/tools/testing/selftests/kvm/.gitignore > +++ b/tools/testing/selftests/kvm/.gitignore > @@ -23,6 +23,7 @@ > /x86_64/hyperv_clock > /x86_64/hyperv_cpuid > /x86_64/hyperv_features > +/x86_64/hyperv_ipi > /x86_64/hyperv_svm_test > /x86_64/mmio_warning_test > /x86_64/mmu_role_test > diff --git a/tools/testing/selftests/kvm/Makefile b/tools/testing/selftests/kvm/Makefile > index af582d168621..44889f897fe7 100644 > --- a/tools/testing/selftests/kvm/Makefile > +++ b/tools/testing/selftests/kvm/Makefile > @@ -52,6 +52,7 @@ TEST_GEN_PROGS_x86_64 += x86_64/fix_hypercall_test > TEST_GEN_PROGS_x86_64 += x86_64/hyperv_clock > TEST_GEN_PROGS_x86_64 += x86_64/hyperv_cpuid > TEST_GEN_PROGS_x86_64 += x86_64/hyperv_features > +TEST_GEN_PROGS_x86_64 += x86_64/hyperv_ipi > TEST_GEN_PROGS_x86_64 += x86_64/hyperv_svm_test > TEST_GEN_PROGS_x86_64 += x86_64/kvm_clock_test > TEST_GEN_PROGS_x86_64 += x86_64/kvm_pv_test > diff --git a/tools/testing/selftests/kvm/include/x86_64/hyperv.h b/tools/testing/selftests/kvm/include/x86_64/hyperv.h > index b66910702c0a..f51d6fab8e93 100644 > --- a/tools/testing/selftests/kvm/include/x86_64/hyperv.h > +++ b/tools/testing/selftests/kvm/include/x86_64/hyperv.h > @@ -184,5 +184,8 @@ > > /* hypercall options */ > #define HV_HYPERCALL_FAST_BIT BIT(16) > +#define HV_HYPERCALL_VARHEAD_OFFSET 17 > + > +#define HYPERV_LINUX_OS_ID ((u64)0x8100 << 48) > > #endif /* !SELFTEST_KVM_HYPERV_H */ > diff --git a/tools/testing/selftests/kvm/x86_64/hyperv_features.c b/tools/testing/selftests/kvm/x86_64/hyperv_features.c > index 672915ce73d8..98c020356925 100644 > --- a/tools/testing/selftests/kvm/x86_64/hyperv_features.c > +++ b/tools/testing/selftests/kvm/x86_64/hyperv_features.c > @@ -14,7 +14,6 @@ > #include "hyperv.h" > > #define VCPU_ID 0 > -#define LINUX_OS_ID ((u64)0x8100 << 48) > > extern unsigned char rdmsr_start; > extern unsigned char rdmsr_end; > @@ -127,7 +126,7 @@ static void guest_hcall(vm_vaddr_t pgs_gpa, struct hcall_data *hcall) > int i = 0; > u64 res, input, output; > > - wrmsr(HV_X64_MSR_GUEST_OS_ID, LINUX_OS_ID); > + wrmsr(HV_X64_MSR_GUEST_OS_ID, HYPERV_LINUX_OS_ID); > wrmsr(HV_X64_MSR_HYPERCALL, pgs_gpa); > > while (hcall->control) { > @@ -230,7 +229,7 @@ static void guest_test_msrs_access(void) > */ > msr->idx = HV_X64_MSR_GUEST_OS_ID; > msr->write = 1; > - msr->write_val = LINUX_OS_ID; > + msr->write_val = HYPERV_LINUX_OS_ID; > msr->available = 1; > break; > case 3: Nitpick: I think that the HYPERV_LINUX_OS_ID change should be in a separate patch. > diff --git a/tools/testing/selftests/kvm/x86_64/hyperv_ipi.c b/tools/testing/selftests/kvm/x86_64/hyperv_ipi.c > new file mode 100644 > index 000000000000..075963c32d45 > --- /dev/null > +++ b/tools/testing/selftests/kvm/x86_64/hyperv_ipi.c > @@ -0,0 +1,374 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * Hyper-V HvCallSendSyntheticClusterIpi{,Ex} tests > + * > + * Copyright (C) 2022, Red Hat, Inc. > + * > + */ > + > +#define _GNU_SOURCE /* for program_invocation_short_name */ > +#include <pthread.h> > +#include <inttypes.h> > + > +#include "kvm_util.h" > +#include "hyperv.h" > +#include "processor.h" > +#include "test_util.h" > +#include "vmx.h" > + > +#define SENDER_VCPU_ID 1 > +#define RECEIVER_VCPU_ID_1 2 > +#define RECEIVER_VCPU_ID_2 65 > + > +#define IPI_VECTOR 0xfe > + > +static volatile uint64_t ipis_rcvd[RECEIVER_VCPU_ID_2 + 1]; > + > +struct thread_params { > + struct kvm_vm *vm; > + uint32_t vcpu_id; > +}; > + > +struct hv_vpset { > + u64 format; > + u64 valid_bank_mask; > + u64 bank_contents[2]; > +}; > + > +enum HV_GENERIC_SET_FORMAT { > + HV_GENERIC_SET_SPARSE_4K, > + HV_GENERIC_SET_ALL, > +}; > + > +/* HvCallSendSyntheticClusterIpi hypercall */ > +struct hv_send_ipi { > + u32 vector; > + u32 reserved; > + u64 cpu_mask; > +}; > + > +/* HvCallSendSyntheticClusterIpiEx hypercall */ > +struct hv_send_ipi_ex { > + u32 vector; > + u32 reserved; > + struct hv_vpset vp_set; > +}; > + > +static inline void hv_init(vm_vaddr_t pgs_gpa) > +{ > + wrmsr(HV_X64_MSR_GUEST_OS_ID, HYPERV_LINUX_OS_ID); > + wrmsr(HV_X64_MSR_HYPERCALL, pgs_gpa); > +} > + > +static void receiver_code(void *hcall_page, vm_vaddr_t pgs_gpa) > +{ > + u32 vcpu_id; > + > + x2apic_enable(); > + hv_init(pgs_gpa); > + > + vcpu_id = rdmsr(HV_X64_MSR_VP_INDEX); > + > + /* Signal sender vCPU we're ready */ > + ipis_rcvd[vcpu_id] = (u64)-1; > + > + for (;;) > + asm volatile("sti; hlt; cli"); > +} > + > +static void guest_ipi_handler(struct ex_regs *regs) > +{ > + u32 vcpu_id = rdmsr(HV_X64_MSR_VP_INDEX); > + > + ipis_rcvd[vcpu_id]++; > + wrmsr(HV_X64_MSR_EOI, 1); > +} > + > +static inline u64 hypercall(u64 control, vm_vaddr_t arg1, vm_vaddr_t arg2) > +{ > + u64 hv_status; > + > + asm volatile("mov %3, %%r8\n" > + "vmcall" > + : "=a" (hv_status), > + "+c" (control), "+d" (arg1) > + : "r" (arg2) > + : "cc", "memory", "r8", "r9", "r10", "r11"); > + > + return hv_status; > +} > + > +static inline void nop_loop(void) > +{ > + int i; > + > + for (i = 0; i < 100000000; i++) > + asm volatile("nop"); > +} > + > +static inline void sync_to_xmm(void *data) > +{ > + int i; > + > + for (i = 0; i < 8; i++) > + write_sse_reg(i, (sse128_t *)(data + sizeof(sse128_t) * i)); > +} > + > +static void sender_guest_code(void *hcall_page, vm_vaddr_t pgs_gpa) > +{ > + struct hv_send_ipi *ipi = (struct hv_send_ipi *)hcall_page; > + struct hv_send_ipi_ex *ipi_ex = (struct hv_send_ipi_ex *)hcall_page; > + int stage = 1, ipis_expected[2] = {0}; > + u64 res; > + > + hv_init(pgs_gpa); > + GUEST_SYNC(stage++); > + > + /* Wait for receiver vCPUs to come up */ > + while (!ipis_rcvd[RECEIVER_VCPU_ID_1] || !ipis_rcvd[RECEIVER_VCPU_ID_2]) > + nop_loop(); > + ipis_rcvd[RECEIVER_VCPU_ID_1] = ipis_rcvd[RECEIVER_VCPU_ID_2] = 0; > + > + /* 'Slow' HvCallSendSyntheticClusterIpi to RECEIVER_VCPU_ID_1 */ > + ipi->vector = IPI_VECTOR; > + ipi->cpu_mask = 1 << RECEIVER_VCPU_ID_1; > + res = hypercall(HVCALL_SEND_IPI, pgs_gpa, pgs_gpa + 4096); > + GUEST_ASSERT((res & 0xffff) == 0); > + nop_loop(); > + GUEST_ASSERT(ipis_rcvd[RECEIVER_VCPU_ID_1] == ++ipis_expected[0]); > + GUEST_ASSERT(ipis_rcvd[RECEIVER_VCPU_ID_2] == ipis_expected[1]); > + GUEST_SYNC(stage++); > + /* 'Fast' HvCallSendSyntheticClusterIpi to RECEIVER_VCPU_ID_1 */ > + res = hypercall(HVCALL_SEND_IPI | HV_HYPERCALL_FAST_BIT, > + IPI_VECTOR, 1 << RECEIVER_VCPU_ID_1); > + GUEST_ASSERT((res & 0xffff) == 0); > + nop_loop(); > + GUEST_ASSERT(ipis_rcvd[RECEIVER_VCPU_ID_1] == ++ipis_expected[0]); > + GUEST_ASSERT(ipis_rcvd[RECEIVER_VCPU_ID_2] == ipis_expected[1]); > + GUEST_SYNC(stage++); > + > + /* 'Slow' HvCallSendSyntheticClusterIpiEx to RECEIVER_VCPU_ID_1 */ > + memset(hcall_page, 0, 4096); > + ipi_ex->vector = IPI_VECTOR; > + ipi_ex->vp_set.format = HV_GENERIC_SET_SPARSE_4K; > + ipi_ex->vp_set.valid_bank_mask = 1 << 0; > + ipi_ex->vp_set.bank_contents[0] = BIT(RECEIVER_VCPU_ID_1); > + res = hypercall(HVCALL_SEND_IPI_EX | (1 << HV_HYPERCALL_VARHEAD_OFFSET), > + pgs_gpa, pgs_gpa + 4096); > + GUEST_ASSERT((res & 0xffff) == 0); > + nop_loop(); > + GUEST_ASSERT(ipis_rcvd[RECEIVER_VCPU_ID_1] == ++ipis_expected[0]); > + GUEST_ASSERT(ipis_rcvd[RECEIVER_VCPU_ID_2] == ipis_expected[1]); > + GUEST_SYNC(stage++); > + /* 'XMM Fast' HvCallSendSyntheticClusterIpiEx to RECEIVER_VCPU_ID_1 */ > + sync_to_xmm(&ipi_ex->vp_set.valid_bank_mask); > + res = hypercall(HVCALL_SEND_IPI_EX | HV_HYPERCALL_FAST_BIT | > + (1 << HV_HYPERCALL_VARHEAD_OFFSET), > + IPI_VECTOR, HV_GENERIC_SET_SPARSE_4K); > + GUEST_ASSERT((res & 0xffff) == 0); > + nop_loop(); > + GUEST_ASSERT(ipis_rcvd[RECEIVER_VCPU_ID_1] == ++ipis_expected[0]); > + GUEST_ASSERT(ipis_rcvd[RECEIVER_VCPU_ID_2] == ipis_expected[1]); > + GUEST_SYNC(stage++); > + > + /* 'Slow' HvCallSendSyntheticClusterIpiEx to RECEIVER_VCPU_ID_2 */ > + memset(hcall_page, 0, 4096); > + ipi_ex->vector = IPI_VECTOR; > + ipi_ex->vp_set.format = HV_GENERIC_SET_SPARSE_4K; > + ipi_ex->vp_set.valid_bank_mask = 1 << 1; > + ipi_ex->vp_set.bank_contents[0] = BIT(RECEIVER_VCPU_ID_2 - 64); > + res = hypercall(HVCALL_SEND_IPI_EX | (1 << HV_HYPERCALL_VARHEAD_OFFSET), > + pgs_gpa, pgs_gpa + 4096); > + GUEST_ASSERT((res & 0xffff) == 0); > + nop_loop(); > + GUEST_ASSERT(ipis_rcvd[RECEIVER_VCPU_ID_1] == ipis_expected[0]); > + GUEST_ASSERT(ipis_rcvd[RECEIVER_VCPU_ID_2] == ++ipis_expected[1]); > + GUEST_SYNC(stage++); > + /* 'XMM Fast' HvCallSendSyntheticClusterIpiEx to RECEIVER_VCPU_ID_2 */ > + sync_to_xmm(&ipi_ex->vp_set.valid_bank_mask); > + res = hypercall(HVCALL_SEND_IPI_EX | HV_HYPERCALL_FAST_BIT | > + (1 << HV_HYPERCALL_VARHEAD_OFFSET), > + IPI_VECTOR, HV_GENERIC_SET_SPARSE_4K); > + GUEST_ASSERT((res & 0xffff) == 0); > + nop_loop(); > + GUEST_ASSERT(ipis_rcvd[RECEIVER_VCPU_ID_1] == ipis_expected[0]); > + GUEST_ASSERT(ipis_rcvd[RECEIVER_VCPU_ID_2] == ++ipis_expected[1]); > + GUEST_SYNC(stage++); > + > + /* 'Slow' HvCallSendSyntheticClusterIpiEx to both RECEIVER_VCPU_ID_{1,2} */ > + memset(hcall_page, 0, 4096); > + ipi_ex->vector = IPI_VECTOR; > + ipi_ex->vp_set.format = HV_GENERIC_SET_SPARSE_4K; > + ipi_ex->vp_set.valid_bank_mask = 1 << 1 | 1; > + ipi_ex->vp_set.bank_contents[0] = BIT(RECEIVER_VCPU_ID_1); > + ipi_ex->vp_set.bank_contents[1] = BIT(RECEIVER_VCPU_ID_2 - 64); > + res = hypercall(HVCALL_SEND_IPI_EX | (2 << HV_HYPERCALL_VARHEAD_OFFSET), > + pgs_gpa, pgs_gpa + 4096); > + GUEST_ASSERT((res & 0xffff) == 0); > + nop_loop(); > + GUEST_ASSERT(ipis_rcvd[RECEIVER_VCPU_ID_1] == ++ipis_expected[0]); > + GUEST_ASSERT(ipis_rcvd[RECEIVER_VCPU_ID_2] == ++ipis_expected[1]); > + GUEST_SYNC(stage++); > + /* 'XMM Fast' HvCallSendSyntheticClusterIpiEx to both RECEIVER_VCPU_ID_{1, 2} */ > + sync_to_xmm(&ipi_ex->vp_set.valid_bank_mask); > + res = hypercall(HVCALL_SEND_IPI_EX | HV_HYPERCALL_FAST_BIT | > + (2 << HV_HYPERCALL_VARHEAD_OFFSET), > + IPI_VECTOR, HV_GENERIC_SET_SPARSE_4K); > + GUEST_ASSERT((res & 0xffff) == 0); > + nop_loop(); > + GUEST_ASSERT(ipis_rcvd[RECEIVER_VCPU_ID_1] == ++ipis_expected[0]); > + GUEST_ASSERT(ipis_rcvd[RECEIVER_VCPU_ID_2] == ++ipis_expected[1]); > + GUEST_SYNC(stage++); > + > + /* 'Slow' HvCallSendSyntheticClusterIpiEx to HV_GENERIC_SET_ALL */ > + memset(hcall_page, 0, 4096); > + ipi_ex->vector = IPI_VECTOR; > + ipi_ex->vp_set.format = HV_GENERIC_SET_ALL; > + res = hypercall(HVCALL_SEND_IPI_EX, > + pgs_gpa, pgs_gpa + 4096); > + GUEST_ASSERT((res & 0xffff) == 0); > + nop_loop(); > + GUEST_ASSERT(ipis_rcvd[RECEIVER_VCPU_ID_1] == ++ipis_expected[0]); > + GUEST_ASSERT(ipis_rcvd[RECEIVER_VCPU_ID_2] == ++ipis_expected[1]); > + GUEST_SYNC(stage++); > + /* 'XMM Fast' HvCallSendSyntheticClusterIpiEx to HV_GENERIC_SET_ALL */ > + sync_to_xmm(&ipi_ex->vp_set.valid_bank_mask); > + res = hypercall(HVCALL_SEND_IPI_EX | HV_HYPERCALL_FAST_BIT, > + IPI_VECTOR, HV_GENERIC_SET_ALL); > + GUEST_ASSERT((res & 0xffff) == 0); > + nop_loop(); > + GUEST_ASSERT(ipis_rcvd[RECEIVER_VCPU_ID_1] == ++ipis_expected[0]); > + GUEST_ASSERT(ipis_rcvd[RECEIVER_VCPU_ID_2] == ++ipis_expected[1]); > + GUEST_SYNC(stage++); > + > + GUEST_DONE(); > +} > + > +static void *vcpu_thread(void *arg) > +{ > + struct thread_params *params = (struct thread_params *)arg; > + struct ucall uc; > + int old; > + int r; > + unsigned int exit_reason; > + > + r = pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &old); > + TEST_ASSERT(r == 0, > + "pthread_setcanceltype failed on vcpu_id=%u with errno=%d", > + params->vcpu_id, r); > + > + vcpu_run(params->vm, params->vcpu_id); > + exit_reason = vcpu_state(params->vm, params->vcpu_id)->exit_reason; > + > + TEST_ASSERT(exit_reason == KVM_EXIT_IO, > + "vCPU %u exited with unexpected exit reason %u-%s, expected KVM_EXIT_IO", > + params->vcpu_id, exit_reason, exit_reason_str(exit_reason)); > + > + if (get_ucall(params->vm, params->vcpu_id, &uc) == UCALL_ABORT) { > + TEST_ASSERT(false, > + "vCPU %u exited with error: %s.\n", > + params->vcpu_id, (const char *)uc.args[0]); > + } > + > + return NULL; > +} > + > +static void cancel_join_vcpu_thread(pthread_t thread, uint32_t vcpu_id) > +{ > + void *retval; > + int r; > + > + r = pthread_cancel(thread); > + TEST_ASSERT(r == 0, > + "pthread_cancel on vcpu_id=%d failed with errno=%d", > + vcpu_id, r); > + > + r = pthread_join(thread, &retval); > + TEST_ASSERT(r == 0, > + "pthread_join on vcpu_id=%d failed with errno=%d", > + vcpu_id, r); > + TEST_ASSERT(retval == PTHREAD_CANCELED, > + "expected retval=%p, got %p", PTHREAD_CANCELED, > + retval); > +} > + > +int main(int argc, char *argv[]) > +{ > + int r; > + pthread_t threads[2]; > + struct thread_params params[2]; > + struct kvm_vm *vm; > + struct kvm_run *run; > + vm_vaddr_t hcall_page; > + struct ucall uc; > + int stage = 1; > + > + vm = vm_create_default(SENDER_VCPU_ID, 0, sender_guest_code); > + params[0].vm = vm; > + params[1].vm = vm; > + > + /* Hypercall input/output */ > + hcall_page = vm_vaddr_alloc_pages(vm, 2); > + memset(addr_gva2hva(vm, hcall_page), 0x0, 2 * getpagesize()); > + > + vm_init_descriptor_tables(vm); > + > + vm_vcpu_add_default(vm, RECEIVER_VCPU_ID_1, receiver_code); > + vcpu_init_descriptor_tables(vm, RECEIVER_VCPU_ID_1); > + vcpu_args_set(vm, RECEIVER_VCPU_ID_1, 2, hcall_page, addr_gva2gpa(vm, hcall_page)); > + vcpu_set_msr(vm, RECEIVER_VCPU_ID_1, HV_X64_MSR_VP_INDEX, RECEIVER_VCPU_ID_1); > + vcpu_set_hv_cpuid(vm, RECEIVER_VCPU_ID_1); > + > + vm_vcpu_add_default(vm, RECEIVER_VCPU_ID_2, receiver_code); > + vcpu_init_descriptor_tables(vm, RECEIVER_VCPU_ID_2); > + vcpu_args_set(vm, RECEIVER_VCPU_ID_2, 2, hcall_page, addr_gva2gpa(vm, hcall_page)); > + vcpu_set_msr(vm, RECEIVER_VCPU_ID_2, HV_X64_MSR_VP_INDEX, RECEIVER_VCPU_ID_2); > + vcpu_set_hv_cpuid(vm, RECEIVER_VCPU_ID_2); > + > + vm_install_exception_handler(vm, IPI_VECTOR, guest_ipi_handler); > + > + vcpu_args_set(vm, SENDER_VCPU_ID, 2, hcall_page, addr_gva2gpa(vm, hcall_page)); > + vcpu_set_hv_cpuid(vm, SENDER_VCPU_ID); > + > + params[0].vcpu_id = RECEIVER_VCPU_ID_1; > + r = pthread_create(&threads[0], NULL, vcpu_thread, ¶ms[0]); > + TEST_ASSERT(r == 0, > + "pthread_create halter failed errno=%d", errno); > + > + params[1].vcpu_id = RECEIVER_VCPU_ID_2; > + r = pthread_create(&threads[1], NULL, vcpu_thread, ¶ms[1]); > + TEST_ASSERT(r == 0, > + "pthread_create halter failed errno=%d", errno); > + > + run = vcpu_state(vm, SENDER_VCPU_ID); > + > + while (true) { > + r = _vcpu_run(vm, SENDER_VCPU_ID); > + TEST_ASSERT(!r, "vcpu_run failed: %d\n", r); > + TEST_ASSERT(run->exit_reason == KVM_EXIT_IO, > + "unexpected exit reason: %u (%s)", > + run->exit_reason, exit_reason_str(run->exit_reason)); > + > + switch (get_ucall(vm, SENDER_VCPU_ID, &uc)) { > + case UCALL_SYNC: > + TEST_ASSERT(uc.args[1] == stage, > + "Unexpected stage: %ld (%d expected)\n", > + uc.args[1], stage); > + break; > + case UCALL_ABORT: > + TEST_FAIL("%s at %s:%ld", (const char *)uc.args[0], > + __FILE__, uc.args[1]); > + return 1; > + case UCALL_DONE: > + return 0; > + } > + > + stage++; > + } > + > + cancel_join_vcpu_thread(threads[0], RECEIVER_VCPU_ID_1); > + cancel_join_vcpu_thread(threads[1], RECEIVER_VCPU_ID_2); > + kvm_vm_free(vm); > + > + return 0; > +} Looks overall good to me, but I might have missed something. Reviewed-by: Maxim Levitsky <mlevitsk@xxxxxxxxxx> Best regards, Maxim Levitsky