Add a nested SVM selftest for DecodeAssists.  Verify that KVM exposes
the feature to L1 and provides architectural exit state for MOV CR/DR,
CLTS, LMSW, SMSW, INTn, INVLPG, and INVLPGA intercepts.

For data #NPF and intercepted #PF exits, cover instruction bytes from
hardware, the emulator fetch cache, and on-demand fetching from L2 RIP.
Verify that instruction-fetch #PF reports no bytes, that fetching stops
at an unreadable page or non-canonical address, and that 32-bit linear
addresses wrap correctly.

Add regressions for a synthesized #NPF that follows a hardware #NPF in
the same emulated instruction, and for a userspace-injected #PF while an
emulated MMIO read is awaiting completion.

The synthesized OUTSB #NPF and userspace-injected #PF paths run by
default.  The remaining synthesized paths are covered when
kvm.force_emulation_prefix=1 is enabled.

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  | 791 ++++++++++++++++++
 3 files changed, 793 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 88c6c8046dde..e31e3a2412ae 100644
--- a/tools/testing/selftests/kvm/Makefile.kvm
+++ b/tools/testing/selftests/kvm/Makefile.kvm
@@ -116,6 +116,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 b161174ece45..42e009af525e 100644
--- a/tools/testing/selftests/kvm/include/x86/processor.h
+++ b/tools/testing/selftests/kvm/include/x86/processor.h
@@ -219,6 +219,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..13511ca8473b
--- /dev/null
+++ b/tools/testing/selftests/kvm/x86/svm_nested_decode_assists_test.c
@@ -0,0 +1,791 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Test KVM's virtualization of SVM DecodeAssists for nested guests.
+ */
+
+#include "test_util.h"
+#include "kvm_util.h"
+#include "processor.h"
+#include "svm_util.h"
+
+#define TEST_INT_VECTOR 0x81
+
+/* Any canonical virtual address that is never mapped by the selftest VM. */
+#define PF_TEST_GVA BIT_ULL(40)
+#define PF_FETCH_TEST_GVA BIT_ULL(41)
+
+#define OUTSB_OPCODE 0x6e
+#define BOUNDARY_OUTSB_CODE_SIZE 15
+#define LINEAR_WRAP_CODE_GVA (BIT_ULL(32) - PAGE_SIZE)
+#define LINEAR_WRAP_OUTSB_OFFSET (PAGE_SIZE - 8)
+#define LINEAR_WRAP_SETUP_SIZE 9
+#define LINEAR_WRAP_SETUP_OFFSET \
+       (LINEAR_WRAP_OUTSB_OFFSET - LINEAR_WRAP_SETUP_SIZE)
+#define LINEAR_WRAP_CS_BASE PAGE_SIZE
+#define TEST_IOPM_SIZE (3 * PAGE_SIZE)
+
+static u8 npf_target[PAGE_SIZE] __aligned(PAGE_SIZE);
+static u8 mmio_source __aligned(PAGE_SIZE);
+static u8 boundary_outsb_code[2 * PAGE_SIZE] __aligned(PAGE_SIZE);
+static const u8 linear_wrap_insn_bytes[15] = {
+       OUTSB_OPCODE, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
+       0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee,
+};
+
+static void l2_read_code(void)
+{
+       asm volatile("mov (%0), %%rax" : : "r"(&npf_target) : "rax", "memory");
+       GUEST_FAIL("L2 read did not cause a nested page fault");
+}
+
+static void l2_outsb_code(void)
+{
+       asm volatile("mov %0, %%rsi\n\t"
+                    "mov $0x80, %%dx\n\t"
+                    "outsb"
+                    : : "r"(&npf_target) : "rsi", "rdx", "memory");
+       GUEST_FAIL("L2 OUTSB did not cause a nested page fault");
+}
+
+static void l2_movsb_code(void)
+{
+       asm volatile("mov %0, %%rsi\n\t"
+                    "mov %1, %%rdi\n\t"
+                    "movsb"
+                    : : "r"(&mmio_source), "r"(&npf_target)
+                    : "rsi", "rdi", "memory");
+       GUEST_FAIL("L2 MOVSB did not cause a nested page fault");
+}
+
+static void l2_pf_code(void)
+{
+       asm volatile("mov (%0), %%rax"
+                    : : "r"(PF_TEST_GVA) : "rax", "memory");
+       GUEST_FAIL("L2 access to an unmapped VA did not #PF");
+}
+
+static void l2_fep_pf_code(void)
+{
+       asm volatile(KVM_FEP "mov (%0), %%rax"
+                    : : "r"(PF_TEST_GVA) : "rax", "memory");
+       GUEST_FAIL("L2 forced-emulated access to an unmapped VA did not #PF");
+}
+
+static void l2_stale_emulator_pf_code(void)
+{
+       asm volatile("movb (%0), %%al\n\t"
+                    "nop"
+                    : : "r"(&mmio_source) : "rax", "memory");
+       GUEST_FAIL("Userspace-injected #PF was not intercepted by L1");
+}
+
+static void l2_mov_from_cr4_code(void)
+{
+       asm volatile("mov %%cr4, %%r10" : : : "r10");
+       GUEST_FAIL("L2 MOV-from-CR4 was not intercepted");
+}
+
+static void l2_fep_mov_from_cr4_code(void)
+{
+       asm volatile(KVM_FEP "mov %%cr4, %%r10" : : : "r10");
+       GUEST_FAIL("L2 forced-emulated MOV-from-CR4 was not intercepted");
+}
+
+static void l2_mov_to_cr4_code(void)
+{
+       asm volatile("mov %%cr4, %%rax\n\t"
+                    "mov %%rax, %%cr4" : : : "rax");
+       GUEST_FAIL("L2 MOV-to-CR4 was not intercepted");
+}
+
+static void l2_fep_mov_to_cr4_code(void)
+{
+       asm volatile("mov %%cr4, %%rax\n\t"
+                    KVM_FEP "mov %%rax, %%cr4" : : : "rax");
+       GUEST_FAIL("L2 forced-emulated MOV-to-CR4 was not intercepted");
+}
+
+static void l2_mov_to_dr7_code(void)
+{
+       asm volatile("mov %%dr7, %%rax\n\t"
+                    "mov %%rax, %%rbx\n\t"
+                    "mov %%rbx, %%dr7" : : : "rax", "rbx");
+       GUEST_FAIL("L2 MOV-to-DR7 was not intercepted");
+}
+
+static void l2_fep_mov_to_dr7_code(void)
+{
+       asm volatile("mov %%dr7, %%rax\n\t"
+                    "mov %%rax, %%rbx\n\t"
+                    KVM_FEP "mov %%rbx, %%dr7" : : : "rax", "rbx");
+       GUEST_FAIL("L2 forced-emulated MOV-to-DR7 was not intercepted");
+}
+
+static void l2_mov_from_dr7_code(void)
+{
+       asm volatile("mov %%dr7, %%r10" : : : "r10");
+       GUEST_FAIL("L2 MOV-from-DR7 was not intercepted");
+}
+
+static void l2_fep_mov_from_dr7_code(void)
+{
+       asm volatile(KVM_FEP "mov %%dr7, %%r10" : : : "r10");
+       GUEST_FAIL("L2 forced-emulated MOV-from-DR7 was not intercepted");
+}
+
+static void l2_clts_code(void)
+{
+       asm volatile("clts" : : : "memory");
+       GUEST_FAIL("L2 CLTS was not intercepted");
+}
+
+static void l2_fep_clts_code(void)
+{
+       asm volatile(KVM_FEP "clts" : : : "memory");
+       GUEST_FAIL("L2 forced-emulated CLTS was not intercepted");
+}
+
+static void l2_lmsw_code(void)
+{
+       asm volatile("smsw %%ax\n\t"
+                    "lmsw %%ax" : : : "rax", "memory");
+       GUEST_FAIL("L2 LMSW was not intercepted");
+}
+
+static void l2_fep_lmsw_code(void)
+{
+       asm volatile("smsw %%ax\n\t"
+                    KVM_FEP "lmsw %%ax" : : : "rax", "memory");
+       GUEST_FAIL("L2 forced-emulated LMSW was not intercepted");
+}
+
+static void l2_smsw_code(void)
+{
+       asm volatile("smsw %%ax" : : : "rax", "memory");
+       GUEST_FAIL("L2 SMSW was not intercepted");
+}
+
+static void l2_fep_smsw_code(void)
+{
+       asm volatile(KVM_FEP "smsw %%ax" : : : "rax", "memory");
+       GUEST_FAIL("L2 forced-emulated SMSW was not intercepted");
+}
+
+static void l2_int_code(void)
+{
+       asm volatile("int %0" : : "i"(TEST_INT_VECTOR));
+       GUEST_FAIL("L2 INTn was not intercepted");
+}
+
+static void l2_fep_int_code(void)
+{
+       asm volatile(KVM_FEP "int %0" : : "i"(TEST_INT_VECTOR));
+       GUEST_FAIL("L2 forced-emulated INTn was not intercepted");
+}
+
+static void l2_invlpg_code(void)
+{
+       asm volatile("invlpg (%0)" : : "r"(&npf_target) : "memory");
+       GUEST_FAIL("L2 INVLPG was not intercepted");
+}
+
+static void l2_fep_invlpg_code(void)
+{
+       asm volatile(KVM_FEP "invlpg (%0)" : : "r"(&npf_target) : "memory");
+       GUEST_FAIL("L2 forced-emulated INVLPG was not intercepted");
+}
+
+static void l2_invlpga_code(void)
+{
+       asm volatile("invlpga" : : "a"(&npf_target), "c"(0) : "memory");
+       GUEST_FAIL("L2 INVLPGA was not intercepted");
+}
+
+static void l2_fep_invlpga_code(void)
+{
+       asm volatile(KVM_FEP "invlpga"
+                    : : "a"(&npf_target), "c"(0) : "memory");
+       GUEST_FAIL("L2 forced-emulated INVLPGA was not intercepted");
+}
+
+struct instruction_intercept_test {
+       const char *name;
+       void (*code)(void);
+       void (*fep_code)(void);
+       u64 intercept;
+       u32 intercept_cr;
+       u32 intercept_dr;
+       u64 exit_code;
+       u64 exit_info_1;
+       u64 exit_info_1_mask;
+       bool check_rax;
+       u64 rax;
+};
+
+static const struct instruction_intercept_test instruction_intercept_tests[] = 
{
+       {
+               .name = "MOV-to-CR4",
+               .code = l2_mov_to_cr4_code,
+               .fep_code = l2_fep_mov_to_cr4_code,
+               .intercept_cr = BIT(INTERCEPT_CR4_WRITE),
+               .exit_code = SVM_EXIT_WRITE_CR4,
+               .exit_info_1 = BIT_ULL(63),
+               .exit_info_1_mask = ~0ULL,
+       }, {
+               .name = "MOV-from-CR4",
+               .code = l2_mov_from_cr4_code,
+               .fep_code = l2_fep_mov_from_cr4_code,
+               .intercept_cr = BIT(INTERCEPT_CR4_READ),
+               .exit_code = SVM_EXIT_READ_CR4,
+               .exit_info_1 = BIT_ULL(63) | 10,
+               .exit_info_1_mask = ~0ULL,
+       }, {
+               .name = "MOV-to-DR7",
+               .code = l2_mov_to_dr7_code,
+               .fep_code = l2_fep_mov_to_dr7_code,
+               .intercept_dr = BIT(INTERCEPT_DR7_WRITE),
+               .exit_code = SVM_EXIT_WRITE_DR7,
+               .exit_info_1 = 3,
+               .exit_info_1_mask = ~0ULL,
+       }, {
+               .name = "MOV-from-DR7",
+               .code = l2_mov_from_dr7_code,
+               .fep_code = l2_fep_mov_from_dr7_code,
+               .intercept_dr = BIT(INTERCEPT_DR7_READ),
+               .exit_code = SVM_EXIT_READ_DR7,
+               .exit_info_1 = 10,
+               .exit_info_1_mask = ~0ULL,
+       }, {
+               .name = "CLTS",
+               .code = l2_clts_code,
+               .fep_code = l2_fep_clts_code,
+               .intercept_cr = BIT(INTERCEPT_CR0_WRITE),
+               .exit_code = SVM_EXIT_WRITE_CR0,
+               .exit_info_1_mask = BIT_ULL(63),
+       }, {
+               .name = "LMSW",
+               .code = l2_lmsw_code,
+               .fep_code = l2_fep_lmsw_code,
+               .intercept_cr = BIT(INTERCEPT_CR0_WRITE),
+               .exit_code = SVM_EXIT_WRITE_CR0,
+               .exit_info_1_mask = BIT_ULL(63),
+       }, {
+               .name = "SMSW",
+               .code = l2_smsw_code,
+               .fep_code = l2_fep_smsw_code,
+               .intercept_cr = BIT(INTERCEPT_CR0_READ),
+               .exit_code = SVM_EXIT_READ_CR0,
+               .exit_info_1_mask = BIT_ULL(63),
+       }, {
+               .name = "INTn",
+               .code = l2_int_code,
+               .fep_code = l2_fep_int_code,
+               .intercept = BIT_ULL(INTERCEPT_INTn),
+               .exit_code = SVM_EXIT_SWINT,
+               .exit_info_1 = TEST_INT_VECTOR,
+               .exit_info_1_mask = ~0ULL,
+       }, {
+               .name = "INVLPG",
+               .code = l2_invlpg_code,
+               .fep_code = l2_fep_invlpg_code,
+               .intercept = BIT_ULL(INTERCEPT_INVLPG),
+               .exit_code = SVM_EXIT_INVLPG,
+               .exit_info_1 = (u64)&npf_target,
+               .exit_info_1_mask = ~0ULL,
+       }, {
+               .name = "INVLPGA",
+               .code = l2_invlpga_code,
+               .fep_code = l2_fep_invlpga_code,
+               .intercept = BIT_ULL(INTERCEPT_INVLPGA),
+               .exit_code = SVM_EXIT_INVLPGA,
+               .exit_info_1_mask = ~0ULL,
+               .check_rax = true,
+               .rax = (u64)&npf_target,
+       },
+};
+
+static void assert_decode_assist_insn_bytes(struct vmcb *vmcb)
+{
+       GUEST_ASSERT(vmcb->control.insn_len);
+       GUEST_ASSERT(vmcb->control.insn_len <=
+                    sizeof(vmcb->control.insn_bytes));
+       GUEST_ASSERT(!memcmp(vmcb->control.insn_bytes,
+                            (void *)vmcb->save.rip,
+                            vmcb->control.insn_len));
+}
+
+static void assert_full_decode_assist_insn_bytes(struct vmcb *vmcb)
+{
+       GUEST_ASSERT_EQ(vmcb->control.insn_len,
+                       sizeof(vmcb->control.insn_bytes));
+       assert_decode_assist_insn_bytes(vmcb);
+}
+
+static void prepare_l2_for_vmrun(struct svm_test_data *svm, gva_t rip)
+{
+       struct vmcb *vmcb = svm->vmcb;
+
+       vmcb->save.rip = rip;
+       vmcb->save.rsp = (u64)svm->stack;
+}
+
+static void run_intercept_test(struct svm_test_data *svm,
+                              const struct instruction_intercept_test *test,
+                              bool synthesized)
+{
+       struct vmcb *vmcb = svm->vmcb;
+       struct vmcb_control_area *control = &vmcb->control;
+       const char *source = synthesized ? "synthesized" : "hardware";
+       u64 expected_exit_info_1 = test->exit_info_1 & test->exit_info_1_mask;
+
+       control->intercept |= test->intercept;
+       control->intercept_cr |= test->intercept_cr;
+       control->intercept_dr |= test->intercept_dr;
+
+       if (synthesized) {
+               control->exit_info_1 = ~0ULL;
+               control->exit_info_2 = ~0ULL;
+               prepare_l2_for_vmrun(svm, (u64)test->fep_code);
+       } else {
+               prepare_l2_for_vmrun(svm, (u64)test->code);
+       }
+
+       run_guest(vmcb, svm->vmcb_gpa);
+
+       __GUEST_ASSERT(control->exit_code == test->exit_code,
+                      "%s (%s): expected exit code %#lx, got %#lx",
+                      test->name, source, (unsigned long)test->exit_code,
+                      (unsigned long)control->exit_code);
+       __GUEST_ASSERT((control->exit_info_1 & test->exit_info_1_mask) ==
+                      expected_exit_info_1,
+                      "%s (%s): expected EXITINFO1 %#lx with mask %#lx, got 
%#lx",
+                      test->name, source, (unsigned long)expected_exit_info_1,
+                      (unsigned long)test->exit_info_1_mask,
+                      (unsigned long)control->exit_info_1);
+       __GUEST_ASSERT(!control->insn_len,
+                      "%s (%s): expected no instruction bytes, got %u",
+                      test->name, source, control->insn_len);
+
+       if (test->check_rax)
+               __GUEST_ASSERT(vmcb->save.rax == test->rax,
+                              "%s (%s): expected rAX %#lx, got %#lx",
+                              test->name, source, (unsigned long)test->rax,
+                              (unsigned long)vmcb->save.rax);
+
+       if (synthesized)
+               __GUEST_ASSERT(!control->exit_info_2,
+                              "%s (%s): expected EXITINFO2 to be clear, got 
%#lx",
+                              test->name, source,
+                              (unsigned long)control->exit_info_2);
+
+       control->intercept &= ~test->intercept;
+       control->intercept_cr &= ~test->intercept_cr;
+       control->intercept_dr &= ~test->intercept_dr;
+}
+
+static void test_instruction_intercepts(struct svm_test_data *svm)
+{
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(instruction_intercept_tests); i++) {
+               run_intercept_test(svm, &instruction_intercept_tests[i], false);
+
+               if (is_forced_emulation_enabled)
+                       run_intercept_test(svm, &instruction_intercept_tests[i],
+                                          true);
+       }
+}
+
+static void test_hardware_npf(struct svm_test_data *svm, gpa_t npf_gpa)
+{
+       struct vmcb *vmcb = svm->vmcb;
+
+       prepare_l2_for_vmrun(svm, (u64)l2_read_code);
+       run_guest(vmcb, svm->vmcb_gpa);
+       GUEST_ASSERT_EQ(vmcb->control.exit_code, SVM_EXIT_NPF);
+       GUEST_ASSERT_EQ(vmcb->control.exit_info_2, npf_gpa);
+       assert_decode_assist_insn_bytes(vmcb);
+}
+
+/*
+ * The IOIO intercept causes L0 to emulate OUTSB before accessing its source
+ * operand.  The emulated read then faults on L1's NPT, resulting in a
+ * KVM-synthesized #NPF.
+ */
+static void test_synthesized_npf(struct svm_test_data *svm, gpa_t npf_gpa)
+{
+       struct vmcb *vmcb = svm->vmcb;
+
+       prepare_l2_for_vmrun(svm, (u64)l2_outsb_code);
+       run_guest(vmcb, svm->vmcb_gpa);
+       GUEST_ASSERT_EQ(vmcb->control.exit_code, SVM_EXIT_NPF);
+       GUEST_ASSERT_EQ(vmcb->control.exit_info_2, npf_gpa);
+       assert_full_decode_assist_insn_bytes(vmcb);
+}
+
+/*
+ * MOVSB first reads from MMIO, causing a hardware #NPF that L0 emulates.
+ * After userspace completes the read, the emulated destination write faults
+ * on L1's NPT.  The new #NPF must not reuse the original hardware exit's GPA.
+ */
+static void test_synthesized_npf_after_hardware_npf(struct svm_test_data *svm,
+                                                   gpa_t npf_gpa)
+{
+       struct vmcb *vmcb = svm->vmcb;
+
+       prepare_l2_for_vmrun(svm, (u64)l2_movsb_code);
+       run_guest(vmcb, svm->vmcb_gpa);
+       GUEST_ASSERT_EQ(vmcb->control.exit_code, SVM_EXIT_NPF);
+       GUEST_ASSERT_EQ(vmcb->control.exit_info_2, npf_gpa);
+       assert_full_decode_assist_insn_bytes(vmcb);
+}
+
+/*
+ * OUTSB is the final byte of a mapped code page, and the following page is
+ * not present in L2's page tables.  DecodeAssist byte fetching must stop at
+ * the page boundary and report only the OUTSB opcode.
+ */
+static void test_synthesized_npf_truncated(struct svm_test_data *svm,
+                                          gpa_t npf_gpa)
+{
+       struct vmcb *vmcb = svm->vmcb;
+
+       prepare_l2_for_vmrun(svm,
+                            (u64)&boundary_outsb_code[PAGE_SIZE -
+                                                      
BOUNDARY_OUTSB_CODE_SIZE]);
+       run_guest(vmcb, svm->vmcb_gpa);
+       GUEST_ASSERT_EQ(vmcb->control.exit_code, SVM_EXIT_NPF);
+       GUEST_ASSERT_EQ(vmcb->control.exit_info_2, npf_gpa);
+       GUEST_ASSERT_EQ(vmcb->save.rip,
+                       (u64)&boundary_outsb_code[PAGE_SIZE - 1]);
+       GUEST_ASSERT_EQ(vmcb->control.insn_len, 1);
+       GUEST_ASSERT_EQ(vmcb->control.insn_bytes[0], OUTSB_OPCODE);
+}
+
+/*
+ * OUTSB is the final byte in the lower canonical address range.  The first
+ * byte after OUTSB is non-canonical, and must terminate DecodeAssist fetching
+ * even though the corresponding high canonical address is mapped.
+ */
+static void test_synthesized_npf_canonical_boundary(struct svm_test_data *svm,
+                                                   gpa_t npf_gpa,
+                                                   gva_t code_gva)
+{
+       struct vmcb *vmcb = svm->vmcb;
+
+       prepare_l2_for_vmrun(svm, code_gva + PAGE_SIZE -
+                                 BOUNDARY_OUTSB_CODE_SIZE);
+       run_guest(vmcb, svm->vmcb_gpa);
+       GUEST_ASSERT_EQ(vmcb->control.exit_code, SVM_EXIT_NPF);
+       GUEST_ASSERT_EQ(vmcb->control.exit_info_2, npf_gpa);
+       GUEST_ASSERT_EQ(vmcb->save.rip, code_gva + PAGE_SIZE - 1);
+       GUEST_ASSERT_EQ(vmcb->control.insn_len, 1);
+       GUEST_ASSERT_EQ(vmcb->control.insn_bytes[0], OUTSB_OPCODE);
+}
+
+/*
+ * A nonzero CS.base makes the DecodeAssist byte window cross the 32-bit
+ * linear-address boundary without crossing the CS limit.
+ */
+static void test_synthesized_npf_linear_wrap(struct svm_test_data *svm,
+                                            gpa_t npf_gpa)
+{
+       struct vmcb *vmcb = svm->vmcb;
+       u16 cs_attrib = vmcb->save.cs.attrib;
+       u64 cs_base = vmcb->save.cs.base;
+       u32 cs_limit = vmcb->save.cs.limit;
+       u32 outsb_eip = LINEAR_WRAP_CODE_GVA + LINEAR_WRAP_OUTSB_OFFSET -
+                       LINEAR_WRAP_CS_BASE;
+       u32 setup_eip = LINEAR_WRAP_CODE_GVA + LINEAR_WRAP_SETUP_OFFSET -
+                       LINEAR_WRAP_CS_BASE;
+
+       vmcb->save.cs.attrib &= ~SVM_SELECTOR_L_MASK;
+       vmcb->save.cs.attrib |= SVM_SELECTOR_DB_MASK;
+       vmcb->save.cs.base = LINEAR_WRAP_CS_BASE;
+       vmcb->save.cs.limit = UINT32_MAX;
+       prepare_l2_for_vmrun(svm, setup_eip);
+       run_guest(vmcb, svm->vmcb_gpa);
+       GUEST_ASSERT_EQ(vmcb->control.exit_code, SVM_EXIT_NPF);
+       GUEST_ASSERT_EQ(vmcb->control.exit_info_2, npf_gpa);
+       GUEST_ASSERT_EQ(vmcb->save.rip, outsb_eip);
+       GUEST_ASSERT_EQ(vmcb->control.insn_len,
+                       sizeof(linear_wrap_insn_bytes));
+       GUEST_ASSERT(!memcmp(vmcb->control.insn_bytes,
+                            linear_wrap_insn_bytes,
+                            sizeof(linear_wrap_insn_bytes)));
+       vmcb->save.cs.attrib = cs_attrib;
+       vmcb->save.cs.base = cs_base;
+       vmcb->save.cs.limit = cs_limit;
+}
+
+static void test_hardware_intercepted_pf(struct svm_test_data *svm)
+{
+       struct vmcb *vmcb = svm->vmcb;
+
+       prepare_l2_for_vmrun(svm, (u64)l2_pf_code);
+       run_guest(vmcb, svm->vmcb_gpa);
+       GUEST_ASSERT_EQ(vmcb->control.exit_code, SVM_EXIT_EXCP_BASE + 
PF_VECTOR);
+       GUEST_ASSERT_EQ(vmcb->control.exit_info_2, PF_TEST_GVA);
+       GUEST_ASSERT(!(vmcb->control.exit_info_1 & PFERR_PRESENT_MASK));
+       GUEST_ASSERT(!(vmcb->control.exit_info_1 & PFERR_FETCH_MASK));
+       assert_decode_assist_insn_bytes(vmcb);
+}
+
+static void test_hardware_intercepted_fetch_pf(struct svm_test_data *svm)
+{
+       struct vmcb *vmcb = svm->vmcb;
+
+       prepare_l2_for_vmrun(svm, PF_FETCH_TEST_GVA);
+       run_guest(vmcb, svm->vmcb_gpa);
+       GUEST_ASSERT_EQ(vmcb->control.exit_code, SVM_EXIT_EXCP_BASE + 
PF_VECTOR);
+       GUEST_ASSERT_EQ(vmcb->control.exit_info_2, PF_FETCH_TEST_GVA);
+       GUEST_ASSERT(!(vmcb->control.exit_info_1 & PFERR_PRESENT_MASK));
+       GUEST_ASSERT(vmcb->control.exit_info_1 & PFERR_FETCH_MASK);
+       GUEST_ASSERT_EQ(vmcb->control.insn_len, 0);
+}
+
+static void test_synthesized_pf(struct svm_test_data *svm)
+{
+       struct vmcb *vmcb = svm->vmcb;
+
+       if (!is_forced_emulation_enabled)
+               return;
+
+       prepare_l2_for_vmrun(svm, (u64)l2_fep_pf_code);
+       run_guest(vmcb, svm->vmcb_gpa);
+       GUEST_ASSERT_EQ(vmcb->control.exit_code, SVM_EXIT_EXCP_BASE + 
PF_VECTOR);
+       GUEST_ASSERT_EQ(vmcb->control.exit_info_2, PF_TEST_GVA);
+       GUEST_ASSERT(!(vmcb->control.exit_info_1 & PFERR_PRESENT_MASK));
+       GUEST_ASSERT(!(vmcb->control.exit_info_1 & PFERR_FETCH_MASK));
+       assert_full_decode_assist_insn_bytes(vmcb);
+}
+
+/*
+ * Inject #PF while an emulated MMIO read is awaiting completion.  KVM
+ * completes the instruction before constructing the nested VM-Exit, so the
+ * old emulator fetch cache must not be reported for the following instruction.
+ */
+static void test_userspace_injected_pf_during_emulation(struct svm_test_data 
*svm)
+{
+       struct vmcb *vmcb = svm->vmcb;
+
+       prepare_l2_for_vmrun(svm, (u64)l2_stale_emulator_pf_code);
+       run_guest(vmcb, svm->vmcb_gpa);
+       GUEST_ASSERT_EQ(vmcb->control.exit_code, SVM_EXIT_EXCP_BASE + 
PF_VECTOR);
+       GUEST_ASSERT_EQ(vmcb->control.exit_info_2, PF_TEST_GVA);
+       GUEST_ASSERT(!(vmcb->control.exit_info_1 & PFERR_FETCH_MASK));
+       assert_full_decode_assist_insn_bytes(vmcb);
+}
+
+static void l1_guest_code(struct svm_test_data *svm, gpa_t npf_gpa,
+                         gpa_t iopm_gpa, gva_t canonical_code_gva)
+{
+       struct vmcb *vmcb = svm->vmcb;
+
+       GUEST_ASSERT(this_cpu_has(X86_FEATURE_DECODEASSISTS));
+
+       generic_svm_setup(svm, l2_read_code);
+       vmcb->control.iopm_base_pa = iopm_gpa;
+
+       vmcb->control.intercept |= BIT_ULL(INTERCEPT_IOIO_PROT);
+       vmcb->control.intercept_exceptions |= 1U << PF_VECTOR;
+
+       test_hardware_npf(svm, npf_gpa);
+       test_synthesized_npf(svm, npf_gpa);
+       test_synthesized_npf_after_hardware_npf(svm, npf_gpa);
+       test_synthesized_npf_truncated(svm, npf_gpa);
+       test_synthesized_npf_canonical_boundary(svm, npf_gpa,
+                                               canonical_code_gva);
+       test_synthesized_npf_linear_wrap(svm, npf_gpa);
+       test_hardware_intercepted_pf(svm);
+       test_hardware_intercepted_fetch_pf(svm);
+       test_synthesized_pf(svm);
+       test_userspace_injected_pf_during_emulation(svm);
+       test_instruction_intercepts(svm);
+
+       GUEST_DONE();
+}
+
+static void build_boundary_outsb_code(u8 *code)
+{
+       u64 source = (u64)&npf_target;
+
+       /* movabs $npf_target, %rsi */
+       code[0] = 0x48;
+       code[1] = 0xbe;
+       memcpy(&code[2], &source, sizeof(source));
+
+       /* mov $0x80, %dx; outsb */
+       code[10] = 0x66;
+       code[11] = 0xba;
+       code[12] = 0x80;
+       code[13] = 0x00;
+       code[14] = OUTSB_OPCODE;
+}
+
+static void prepare_boundary_outsb_code(struct kvm_vm *vm)
+{
+       gva_t code_gva = (gva_t)&boundary_outsb_code[PAGE_SIZE -
+                                                     BOUNDARY_OUTSB_CODE_SIZE];
+
+       build_boundary_outsb_code(addr_gva2hva(vm, code_gva));
+}
+
+static gva_t prepare_canonical_boundary_outsb_code(struct kvm_vm *vm)
+{
+       gva_t backing_gva = vm_alloc_pages(vm, 2);
+       u8 *code_page = addr_gva2hva(vm, backing_gva);
+       u8 *alias_page = addr_gva2hva(vm, backing_gva + PAGE_SIZE);
+       gpa_t code_gpa = addr_gva2gpa(vm, backing_gva);
+       gpa_t alias_gpa = addr_gva2gpa(vm, backing_gva + PAGE_SIZE);
+       gva_t code_gva = BIT_ULL(vm->va_bits - 1) - PAGE_SIZE;
+       gva_t alias_gva = ~(BIT_ULL(vm->va_bits - 1) - 1);
+       u8 *code = &code_page[PAGE_SIZE - BOUNDARY_OUTSB_CODE_SIZE];
+
+       build_boundary_outsb_code(code);
+       memset(alias_page, 0xcc, PAGE_SIZE);
+       virt_map(vm, code_gva, code_gpa, 1);
+       virt_map(vm, alias_gva, alias_gpa, 1);
+
+       return code_gva;
+}
+
+static void prepare_linear_wrap_outsb_code(struct kvm_vm *vm)
+{
+       gva_t code_gva = vm_alloc_pages(vm, 2);
+       u8 *high_page = addr_gva2hva(vm, code_gva);
+       u8 *low_page = addr_gva2hva(vm, code_gva + PAGE_SIZE);
+       gpa_t high_gpa = addr_gva2gpa(vm, code_gva);
+       gpa_t low_gpa = addr_gva2gpa(vm, code_gva + PAGE_SIZE);
+       u32 source = (u32)(u64)&npf_target;
+       u8 *setup = &high_page[LINEAR_WRAP_SETUP_OFFSET];
+
+       TEST_ASSERT((u64)&npf_target <= UINT32_MAX,
+                   "npf_target must be addressable from compatibility mode");
+
+       /* mov $npf_target, %esi; mov $0x80, %dx */
+       setup[0] = 0xbe;
+       memcpy(&setup[1], &source, sizeof(source));
+       setup[5] = 0x66;
+       setup[6] = 0xba;
+       setup[7] = 0x80;
+       setup[8] = 0x00;
+
+       memcpy(&high_page[LINEAR_WRAP_OUTSB_OFFSET],
+              linear_wrap_insn_bytes,
+              PAGE_SIZE - LINEAR_WRAP_OUTSB_OFFSET);
+       memcpy(low_page,
+              &linear_wrap_insn_bytes[PAGE_SIZE - LINEAR_WRAP_OUTSB_OFFSET],
+              sizeof(linear_wrap_insn_bytes) -
+              (PAGE_SIZE - LINEAR_WRAP_OUTSB_OFFSET));
+
+       virt_map(vm, LINEAR_WRAP_CODE_GVA, high_gpa, 1);
+       virt_map(vm, 0, low_gpa, 1);
+}
+
+static void queue_userspace_pf(struct kvm_vcpu *vcpu)
+{
+       struct kvm_vcpu_events events;
+
+       vcpu_events_get(vcpu, &events);
+       TEST_ASSERT(!events.exception.pending && !events.exception.injected,
+                   "Unexpected exception queued before userspace #PF 
injection");
+       TEST_ASSERT(events.flags & KVM_VCPUEVENT_VALID_PAYLOAD,
+                   "KVM_CAP_EXCEPTION_PAYLOAD was not enabled");
+
+       events.flags |= KVM_VCPUEVENT_VALID_PAYLOAD;
+       events.exception.injected = false;
+       events.exception.pending = true;
+       events.exception.nr = PF_VECTOR;
+       events.exception.has_error_code = true;
+       events.exception.error_code = 0;
+       events.exception_has_payload = true;
+       events.exception_payload = PF_TEST_GVA;
+       vcpu_events_set(vcpu, &events);
+}
+
+static void complete_mmio_read(struct kvm_vcpu *vcpu, gpa_t expected_gpa,
+                              u8 value)
+{
+       TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_MMIO);
+       TEST_ASSERT(!vcpu->run->mmio.is_write,
+                   "Expected an MMIO read, got a write");
+       TEST_ASSERT_EQ(vcpu->run->mmio.phys_addr, expected_gpa);
+       TEST_ASSERT_EQ(vcpu->run->mmio.len, 1);
+       vcpu->run->mmio.data[0] = value;
+}
+
+static void assert_ucall_done(struct kvm_vcpu *vcpu)
+{
+       struct ucall uc;
+       u64 actual;
+
+       TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO);
+       actual = get_ucall(vcpu, &uc);
+       if (actual == UCALL_ABORT)
+               REPORT_GUEST_ASSERT(uc);
+
+       TEST_ASSERT_EQ(actual, UCALL_DONE);
+}
+
+int main(int argc, char *argv[])
+{
+       gva_t svm_gva, npf_gva, boundary_page_gva, iopm_gva;
+       gva_t canonical_code_gva;
+       gpa_t npf_gpa, mmio_source_gpa, mmio_gpa, iopm_gpa;
+       struct userspace_mem_region *region;
+       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(this_cpu_has(X86_FEATURE_DECODEASSISTS));
+       TEST_ASSERT(kvm_cpu_has(X86_FEATURE_DECODEASSISTS),
+                   "KVM failed to expose DecodeAssists");
+       TEST_REQUIRE(kvm_has_cap(KVM_CAP_EXCEPTION_PAYLOAD));
+
+       vm = vm_create_with_one_vcpu(&vcpu, l1_guest_code);
+       vm_enable_cap(vm, KVM_CAP_EXCEPTION_PAYLOAD, 1);
+       prepare_boundary_outsb_code(vm);
+       canonical_code_gva = prepare_canonical_boundary_outsb_code(vm);
+       prepare_linear_wrap_outsb_code(vm);
+       vm_enable_npt(vm);
+       vcpu_alloc_svm(vm, &svm_gva);
+       iopm_gva = vm_alloc_pages(vm, TEST_IOPM_SIZE / PAGE_SIZE);
+       iopm_gpa = addr_gva2gpa(vm, iopm_gva);
+       memset(addr_gva2hva(vm, iopm_gva), 0, TEST_IOPM_SIZE);
+       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);
+       region = memslot2region(vm, 0);
+       mmio_gpa = region->region.guest_phys_addr +
+                  region->region.memory_size + PAGE_SIZE;
+       mmio_source_gpa = addr_gva2gpa(vm, (gva_t)&mmio_source);
+       pte = tdp_get_pte(vm, mmio_source_gpa);
+       *pte = (*pte & ~PHYSICAL_PAGE_MASK) | mmio_gpa;
+
+       boundary_page_gva = (gva_t)&boundary_outsb_code[PAGE_SIZE];
+       pte = vm_get_pte(vm, boundary_page_gva);
+       *pte &= ~PTE_PRESENT_MASK(&vm->mmu);
+
+       vcpu_args_set(vcpu, 4, svm_gva, npf_gpa, iopm_gpa,
+                     canonical_code_gva);
+
+       /* Complete the MMIO source read in the MOVSB #NPF regression test. */
+       vcpu_run(vcpu);
+       complete_mmio_read(vcpu, mmio_gpa, 0xa5);
+
+       /* Leave the second MMIO read pending while injecting #PF. */
+       vcpu_run(vcpu);
+       complete_mmio_read(vcpu, mmio_gpa, 0x5a);
+       queue_userspace_pf(vcpu);
+       vcpu_run(vcpu);
+       assert_ucall_done(vcpu);
+
+       kvm_vm_free(vm);
+       return 0;
+}
-- 
2.43.7


Reply via email to