From: David Woodhouse <[email protected]>

Drive an unimplemented hypercall code through the three interesting
shapes: forwarding disabled (the guest sees
HV_STATUS_INVALID_HYPERCALL_CODE from KVM itself), a slow-form call
(the exit carries the control value and GPAs, and the userspace-chosen
result reaches the guest), and a fast-form call (RDX and R8 in the
exit parameters, XMM0 visible through KVM_GET_FPU).

The tools copy of linux/kvm.h picks up the KVM_CAP_HYPERV_FORWARD_HCALL
capability number for the test.

Signed-off-by: David Woodhouse <[email protected]>
Assisted-by: LLM
---
 tools/include/uapi/linux/kvm.h                |   1 +
 tools/testing/selftests/kvm/Makefile.kvm      |   1 +
 .../selftests/kvm/x86/hyperv_forward_hcall.c  | 144 ++++++++++++++++++
 3 files changed, 146 insertions(+)
 create mode 100644 tools/testing/selftests/kvm/x86/hyperv_forward_hcall.c

diff --git a/tools/include/uapi/linux/kvm.h b/tools/include/uapi/linux/kvm.h
index dcd12f58186e..586c0d6c168c 100644
--- a/tools/include/uapi/linux/kvm.h
+++ b/tools/include/uapi/linux/kvm.h
@@ -1015,6 +1015,7 @@ struct kvm_enable_cap {
 #define KVM_CAP_S390_KEYOP 247
 #define KVM_CAP_S390_VSIE_ESAMODE 248
 #define KVM_CAP_PLANES 249
+#define KVM_CAP_HYPERV_FORWARD_HCALL 250
 
 struct kvm_irq_routing_irqchip {
        __u32 irqchip;
diff --git a/tools/testing/selftests/kvm/Makefile.kvm 
b/tools/testing/selftests/kvm/Makefile.kvm
index 5883359c1a5d..14e385750ed1 100644
--- a/tools/testing/selftests/kvm/Makefile.kvm
+++ b/tools/testing/selftests/kvm/Makefile.kvm
@@ -85,6 +85,7 @@ TEST_GEN_PROGS_x86 += x86/hyperv_cpuid
 TEST_GEN_PROGS_x86 += x86/hyperv_evmcs
 TEST_GEN_PROGS_x86 += x86/hyperv_extended_hypercalls
 TEST_GEN_PROGS_x86 += x86/hyperv_features
+TEST_GEN_PROGS_x86 += x86/hyperv_forward_hcall
 TEST_GEN_PROGS_x86 += x86/hyperv_ipi
 TEST_GEN_PROGS_x86 += x86/hyperv_svm_test
 TEST_GEN_PROGS_x86 += x86/hyperv_tlb_flush
diff --git a/tools/testing/selftests/kvm/x86/hyperv_forward_hcall.c 
b/tools/testing/selftests/kvm/x86/hyperv_forward_hcall.c
new file mode 100644
index 000000000000..47f786ca3e4b
--- /dev/null
+++ b/tools/testing/selftests/kvm/x86/hyperv_forward_hcall.c
@@ -0,0 +1,144 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Test KVM_CAP_HYPERV_FORWARD_HCALL: Hyper-V hypercalls that KVM does
+ * not implement exit to userspace instead of failing with
+ * HV_STATUS_INVALID_HYPERCALL_CODE, in both slow and fast form, and
+ * userspace supplies the result the guest observes.
+ */
+#include "test_util.h"
+#include "kvm_util.h"
+#include "processor.h"
+#include "hyperv.h"
+
+/* A hypercall code KVM does not implement: HvCallGetVpRegisters. */
+#define HVCALL_TEST_CODE       0x50
+
+#define FAST_PARAM0            0x1122334455667788ULL
+#define FAST_PARAM1            0x99aabbccddeeff00ULL
+#define XMM_MARKER             0x5157ca11c0ffee00ULL
+#define USERSPACE_STATUS       0x55AA
+
+static void guest_code(gpa_t pgs_gpa)
+{
+       __sse128_u marker;
+       u64 status;
+       u8 vector;
+
+       marker.as_u64[0] = XMM_MARKER;
+       marker.as_u64[1] = XMM_MARKER + 1;
+
+       wrmsr(HV_X64_MSR_GUEST_OS_ID, HYPERV_LINUX_OS_ID);
+       wrmsr(HV_X64_MSR_HYPERCALL, pgs_gpa);
+
+       /* Forwarding disabled: KVM itself fails the call. */
+       vector = __hyperv_hypercall(HVCALL_TEST_CODE, pgs_gpa,
+                                   pgs_gpa + PAGE_SIZE, &status);
+       GUEST_ASSERT(!vector);
+       GUEST_ASSERT_EQ(status & 0xffff, HV_STATUS_INVALID_HYPERCALL_CODE);
+       GUEST_SYNC(1);
+
+       /* Forwarding enabled: userspace supplies the status. */
+       vector = __hyperv_hypercall(HVCALL_TEST_CODE, pgs_gpa,
+                                   pgs_gpa + PAGE_SIZE, &status);
+       GUEST_ASSERT(!vector);
+       GUEST_ASSERT_EQ(status & 0xffff, 0);
+       GUEST_SYNC(2);
+
+       /* Fast form: register parameters reach userspace. */
+       write_sse_reg(0, &marker.vec);
+       vector = __hyperv_hypercall(HVCALL_TEST_CODE | HV_HYPERCALL_FAST_BIT,
+                                   FAST_PARAM0, FAST_PARAM1, &status);
+       GUEST_ASSERT(!vector);
+       GUEST_ASSERT_EQ(status & 0xffff, USERSPACE_STATUS);
+
+       GUEST_DONE();
+}
+
+static void handle_forwarded_hcall(struct kvm_vcpu *vcpu, u64 expect_control,
+                                  u64 expect_in, u64 expect_out, u64 result)
+{
+       struct kvm_run *run = vcpu->run;
+
+       TEST_ASSERT_EQ(run->exit_reason, KVM_EXIT_HYPERV);
+       TEST_ASSERT_EQ(run->hyperv.type, KVM_EXIT_HYPERV_HCALL);
+       TEST_ASSERT_EQ(run->hyperv.u.hcall.input, expect_control);
+       TEST_ASSERT_EQ(run->hyperv.u.hcall.params[0], expect_in);
+       TEST_ASSERT_EQ(run->hyperv.u.hcall.params[1], expect_out);
+
+       run->hyperv.u.hcall.result = result;
+}
+
+int main(int argc, char *argv[])
+{
+       struct kvm_vcpu *vcpu;
+       struct kvm_vm *vm;
+       gva_t hcall_page;
+       gpa_t hcall_gpa;
+       struct kvm_fpu fpu;
+       struct ucall uc;
+       int stage;
+
+       TEST_REQUIRE(kvm_has_cap(KVM_CAP_HYPERV_FORWARD_HCALL));
+
+       vm = vm_create_with_one_vcpu(&vcpu, guest_code);
+       vcpu_set_hv_cpuid(vcpu);
+
+       /* Hypercall page plus an input and an output page. */
+       hcall_page = vm_alloc_pages(vm, 3);
+       memset(addr_gva2hva(vm, hcall_page), 0x0, 3 * getpagesize());
+       hcall_gpa = addr_gva2gpa(vm, hcall_page);
+
+       vcpu_args_set(vcpu, 1, hcall_gpa);
+
+       for (stage = 1;; stage++) {
+               vcpu_run(vcpu);
+
+               if (vcpu->run->exit_reason == KVM_EXIT_HYPERV) {
+                       switch (stage) {
+                       case 2:
+                               handle_forwarded_hcall(vcpu, HVCALL_TEST_CODE,
+                                                      hcall_gpa,
+                                                      hcall_gpa + 
getpagesize(),
+                                                      0);
+                               break;
+                       case 3:
+                               handle_forwarded_hcall(vcpu,
+                                                      HVCALL_TEST_CODE | 
HV_HYPERCALL_FAST_BIT,
+                                                      FAST_PARAM0, FAST_PARAM1,
+                                                      USERSPACE_STATUS);
+                               /*
+                                * Fast-call register state is reachable with
+                                * the ordinary vCPU state ioctls.
+                                */
+                               vcpu_fpu_get(vcpu, &fpu);
+                               TEST_ASSERT_EQ(*(u64 *)&fpu.xmm[0][0], 
XMM_MARKER);
+                               TEST_ASSERT_EQ(*(u64 *)&fpu.xmm[0][8], 
XMM_MARKER + 1);
+                               break;
+                       default:
+                               TEST_FAIL("Unexpected hypercall exit in stage 
%d", stage);
+                       }
+                       stage--;        /* same guest stage continues */
+                       continue;
+               }
+
+               TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO);
+
+               switch (get_ucall(vcpu, &uc)) {
+               case UCALL_SYNC:
+                       TEST_ASSERT_EQ(uc.args[1], stage);
+                       if (stage == 1)
+                               vm_enable_cap(vm, KVM_CAP_HYPERV_FORWARD_HCALL, 
1);
+                       break;
+               case UCALL_ABORT:
+                       REPORT_GUEST_ASSERT(uc);
+               case UCALL_DONE:
+                       goto done;
+               default:
+                       TEST_FAIL("Unhandled ucall: %ld", uc.cmd);
+               }
+       }
+
+done:
+       kvm_vm_free(vm);
+       return 0;
+}
-- 
2.50.1


Reply via email to