Add a selftest for nested SVM DecodeAssists virtualization.

Run L2 with a non-present NPT mapping to trigger a nested page fault, then
verify that L1 sees non-zero DecodeAssist state in VMCB12 and that the
reported instruction bytes match the instruction at L2's RIP.  This covers
the path that copies hardware-provided decode information from VMCB02 to
VMCB12.

Re-enter L2 at a VMMCALL after the #NPF and verify that any reported bytes
match the new RIP.  KVM reuses VMCB02 across nested runs, so the second
exit checks that DecodeAssist state from the earlier #NPF is not leaked
into an unrelated nested VM-Exit.

Also verify that KVM exposes DecodeAssists in L1's SVM CPUID leaf when the
feature is supported by the host.

Tested-by: Yongwei Xu <[email protected]>
Signed-off-by: Tina Zhang <[email protected]>
---
 tools/testing/selftests/kvm/Makefile.kvm      |  1 +
 .../selftests/kvm/include/x86/processor.h     |  1 +
 .../kvm/x86/svm_nested_decode_assists_test.c  | 99 +++++++++++++++++++
 3 files changed, 101 insertions(+)
 create mode 100644 
tools/testing/selftests/kvm/x86/svm_nested_decode_assists_test.c

diff --git a/tools/testing/selftests/kvm/Makefile.kvm 
b/tools/testing/selftests/kvm/Makefile.kvm
index 9118a5a51b89..23bf51074392 100644
--- a/tools/testing/selftests/kvm/Makefile.kvm
+++ b/tools/testing/selftests/kvm/Makefile.kvm
@@ -114,6 +114,7 @@ TEST_GEN_PROGS_x86 += x86/vmx_preemption_timer_test
 TEST_GEN_PROGS_x86 += x86/svm_vmcall_test
 TEST_GEN_PROGS_x86 += x86/svm_int_ctl_test
 TEST_GEN_PROGS_x86 += x86/svm_nested_clear_efer_svme
+TEST_GEN_PROGS_x86 += x86/svm_nested_decode_assists_test
 TEST_GEN_PROGS_x86 += x86/svm_nested_shutdown_test
 TEST_GEN_PROGS_x86 += x86/svm_nested_soft_inject_test
 TEST_GEN_PROGS_x86 += x86/svm_nested_vmcb12_gpa
diff --git a/tools/testing/selftests/kvm/include/x86/processor.h 
b/tools/testing/selftests/kvm/include/x86/processor.h
index 77f576ee7789..ee4520ff2f89 100644
--- a/tools/testing/selftests/kvm/include/x86/processor.h
+++ b/tools/testing/selftests/kvm/include/x86/processor.h
@@ -201,6 +201,7 @@ struct kvm_x86_cpu_feature {
 #define        X86_FEATURE_LBRV                KVM_X86_CPU_FEATURE(0x8000000A, 
0, EDX, 1)
 #define        X86_FEATURE_NRIPS               KVM_X86_CPU_FEATURE(0x8000000A, 
0, EDX, 3)
 #define X86_FEATURE_TSCRATEMSR          KVM_X86_CPU_FEATURE(0x8000000A, 0, 
EDX, 4)
+#define X86_FEATURE_DECODEASSISTS       KVM_X86_CPU_FEATURE(0x8000000A, 0, 
EDX, 7)
 #define X86_FEATURE_PAUSEFILTER         KVM_X86_CPU_FEATURE(0x8000000A, 0, 
EDX, 10)
 #define X86_FEATURE_PFTHRESHOLD         KVM_X86_CPU_FEATURE(0x8000000A, 0, 
EDX, 12)
 #define        X86_FEATURE_V_VMSAVE_VMLOAD     KVM_X86_CPU_FEATURE(0x8000000A, 
0, EDX, 15)
diff --git a/tools/testing/selftests/kvm/x86/svm_nested_decode_assists_test.c 
b/tools/testing/selftests/kvm/x86/svm_nested_decode_assists_test.c
new file mode 100644
index 000000000000..6b5d38d9d36c
--- /dev/null
+++ b/tools/testing/selftests/kvm/x86/svm_nested_decode_assists_test.c
@@ -0,0 +1,99 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#include "test_util.h"
+#include "kvm_util.h"
+#include "processor.h"
+#include "svm_util.h"
+
+#define L2_GUEST_STACK_SIZE 64
+
+static uint64_t npf_target __aligned(PAGE_SIZE);
+
+static void l2_guest_code(void)
+{
+       asm volatile("mov (%0), %%rax" : : "r"(&npf_target) : "rax", "memory");
+       GUEST_FAIL("L2 access did not cause a nested page fault");
+}
+
+static void l2_vmmcall_code(void)
+{
+       asm volatile("vmmcall");
+       GUEST_FAIL("L2 did not exit on VMMCALL");
+}
+
+/*
+ * Whenever hardware reports DecodeAssist information (insn_len != 0), the 
bytes
+ * KVM reflects into VMCB12 must be the instruction at the current RIP, never
+ * stale bytes carried over from a previous L2 exit.
+ */
+static void assert_decode_assists_sane(struct vmcb *vmcb)
+{
+       GUEST_ASSERT(vmcb->control.insn_len <=
+                    sizeof(vmcb->control.insn_bytes));
+       if (vmcb->control.insn_len)
+               GUEST_ASSERT(!memcmp(vmcb->control.insn_bytes,
+                                    (void *)vmcb->save.rip,
+                                    vmcb->control.insn_len));
+}
+
+static void l1_guest_code(struct svm_test_data *svm)
+{
+       unsigned long l2_guest_stack[L2_GUEST_STACK_SIZE];
+       struct vmcb *vmcb = svm->vmcb;
+
+       GUEST_ASSERT(this_cpu_has(X86_FEATURE_DECODEASSISTS));
+
+       generic_svm_setup(svm, l2_guest_code,
+                         &l2_guest_stack[L2_GUEST_STACK_SIZE]);
+
+       /* A real nested #NPF must reflect valid decode information to L1. */
+       run_guest(vmcb, svm->vmcb_gpa);
+
+       GUEST_ASSERT_EQ(vmcb->control.exit_code, SVM_EXIT_NPF);
+       GUEST_ASSERT(vmcb->control.insn_len);
+       assert_decode_assists_sane(vmcb);
+
+       /*
+        * Redirect L2 past the still-faulting access to a VMMCALL.  KVM reuses
+        * its internal VMCB02, which now holds the #NPF's decode bytes, so this
+        * exercises the path that must clear DecodeAssist state and ensures the
+        * prior exit's bytes are not leaked into a subsequent exit.
+        */
+       vmcb->save.rip = (u64)l2_vmmcall_code;
+       run_guest(vmcb, svm->vmcb_gpa);
+
+       GUEST_ASSERT_EQ(vmcb->control.exit_code, SVM_EXIT_VMMCALL);
+       assert_decode_assists_sane(vmcb);
+
+       GUEST_DONE();
+}
+
+int main(int argc, char *argv[])
+{
+       gva_t svm_gva, npf_gva;
+       gpa_t npf_gpa;
+       struct kvm_vcpu *vcpu;
+       struct kvm_vm *vm;
+       u64 *pte;
+
+       TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_SVM));
+       TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_NPT));
+       TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_DECODEASSISTS));
+
+       vm = vm_create_with_one_vcpu(&vcpu, l1_guest_code);
+       vm_enable_npt(vm);
+       vcpu_alloc_svm(vm, &svm_gva);
+       npf_gva = (gva_t)&npf_target;
+       npf_gpa = addr_gva2gpa(vm, npf_gva);
+
+       tdp_identity_map_default_memslots(vm);
+       pte = tdp_get_pte(vm, npf_gpa);
+       *pte &= ~PTE_PRESENT_MASK(&vm->stage2_mmu);
+
+       vcpu_args_set(vcpu, 1, svm_gva);
+       vcpu_run(vcpu);
+       TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO);
+       TEST_ASSERT_EQ(get_ucall(vcpu, NULL), UCALL_DONE);
+
+       kvm_vm_free(vm);
+       return 0;
+}
-- 
2.43.0


Reply via email to