@@ -76,6 +76,7 @@ TEST_GEN_PROGS_x86_64 += x86_64/get_msr_index_features
TEST_GEN_PROGS_x86_64 += x86_64/exit_on_emulation_failure_test
TEST_GEN_PROGS_x86_64 += x86_64/fix_hypercall_test
TEST_GEN_PROGS_x86_64 += x86_64/hwcr_msr_test
+TEST_GEN_PROGS_x86_64 += x86_64/fred_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_evmcs
@@ -1260,4 +1260,33 @@ void virt_map_level(struct kvm_vm *vm, uint64_t vaddr, uint64_t paddr,
#define PFERR_GUEST_PAGE_MASK BIT_ULL(PFERR_GUEST_PAGE_BIT)
#define PFERR_IMPLICIT_ACCESS BIT_ULL(PFERR_IMPLICIT_ACCESS_BIT)
+/*
+ * FRED related data structures and functions
+ */
+struct fred_stack {
+ u64 r15;
+ u64 r14;
+ u64 r13;
+ u64 r12;
+ u64 bp;
+ u64 bx;
+ u64 r11;
+ u64 r10;
+ u64 r9;
+ u64 r8;
+ u64 ax;
+ u64 cx;
+ u64 dx;
+ u64 si;
+ u64 di;
+ u64 error_code;
+ u64 ip;
+ u64 csx;
+ u64 flags;
+ u64 sp;
+ u64 ssx;
+ u64 event_data;
+ u64 reserved;
+};
+
#endif /* SELFTEST_KVM_PROCESSOR_H */
new file mode 100644
@@ -0,0 +1,262 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * FRED nested exception tests
+ *
+ * Copyright (C) 2023, Intel, Inc.
+ */
+#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 <asm/msr-index.h>
+
+#include "kvm_util.h"
+#include "test_util.h"
+#include "guest_modes.h"
+#include "processor.h"
+
+#define FRED_STKLVL(v,l) (_AT(unsigned long, l) << (2 * (v)))
+#define FRED_CONFIG_ENTRYPOINT(p) _AT(unsigned long, (p))
+
+/* This address is already mapped in guest page table. */
+#define FRED_VALID_RSP 0x8000
+
+/*
+ * The following addresses are not yet mapped in both EPT and guest page
+ * tables at the beginning. As a result, it causes an EPT violation VM
+ * exit with an original guest #PF to access any of them for the first
+ * time.
+ *
+ * Use these addresses as guest FRED RSP0 to generate nested #PFs to test
+ * if event data are properly virtualized.
+ */
+static unsigned long fred_invalid_rsp[4] = {
+ 0x0,
+ 0xf0000000,
+ 0xe0000000,
+ 0xd0000000,
+};
+
+extern char asm_user_wrmsr[];
+extern char asm_user_ud[];
+extern char asm_done_fault[];
+
+extern void asm_test_fault(int test);
+
+/*
+ * user level code for triggering faults.
+ */
+asm(".pushsection .text\n"
+ ".type asm_user_wrmsr, @function\n"
+ ".align 4096\n"
+ "asm_user_wrmsr:\n"
+ /* Trigger a #GP */
+ "wrmsr\n"
+
+ ".fill asm_user_ud - ., 1, 0xcc\n"
+
+ ".type asm_user_ud, @function\n"
+ ".org asm_user_wrmsr + 16\n"
+ "asm_user_ud:\n"
+ /* Trigger a #UD */
+ "ud2\n"
+
+ ".align 4096, 0xcc\n"
+ ".popsection");
+
+/* Send current stack level and #PF address */
+#define GUEST_SYNC_CSL_FA(__stage, __pf_address) \
+ GUEST_SYNC_ARGS(__stage, __pf_address, 0, 0, 0)
+
+void fred_entry_from_user(struct fred_stack *stack)
+{
+ u32 current_stack_level = rdmsr(MSR_IA32_FRED_CONFIG) & 0x3;
+
+ GUEST_SYNC_CSL_FA(current_stack_level, stack->event_data);
+
+ /* Do NOT go back to user level, continue the next test instead */
+ stack->ssx = 0x18;
+ stack->csx = 0x10;
+ stack->ip = (u64)&asm_done_fault;
+}
+
+void fred_entry_from_kernel(struct fred_stack *stack)
+{
+ TEST_FAIL("kernel events not allowed in FRED tests.");
+}
+
+#define PUSH_REGS \
+ "push %rdi\n" \
+ "push %rsi\n" \
+ "push %rdx\n" \
+ "push %rcx\n" \
+ "push %rax\n" \
+ "push %r8\n" \
+ "push %r9\n" \
+ "push %r10\n" \
+ "push %r11\n" \
+ "push %rbx\n" \
+ "push %rbp\n" \
+ "push %r12\n" \
+ "push %r13\n" \
+ "push %r14\n" \
+ "push %r15\n"
+
+#define POP_REGS \
+ "pop %r15\n" \
+ "pop %r14\n" \
+ "pop %r13\n" \
+ "pop %r12\n" \
+ "pop %rbp\n" \
+ "pop %rbx\n" \
+ "pop %r11\n" \
+ "pop %r10\n" \
+ "pop %r9\n" \
+ "pop %r8\n" \
+ "pop %rax\n" \
+ "pop %rcx\n" \
+ "pop %rdx\n" \
+ "pop %rsi\n" \
+ "pop %rdi\n"
+
+/*
+ * FRED entry points.
+ */
+asm(".pushsection .text\n"
+ ".type asm_fred_entrypoint_user, @function\n"
+ ".align 4096\n"
+ "asm_fred_entrypoint_user:\n"
+ "endbr64\n"
+ PUSH_REGS
+ "movq %rsp, %rdi\n"
+ "call fred_entry_from_user\n"
+ POP_REGS
+ /* Do NOT go back to user level, continue the next test instead */
+ ".byte 0xf2,0x0f,0x01,0xca\n" /* ERETS */
+
+ ".fill asm_fred_entrypoint_kernel - ., 1, 0xcc\n"
+
+ ".type asm_fred_entrypoint_kernel, @function\n"
+ ".org asm_fred_entrypoint_user + 256\n"
+ "asm_fred_entrypoint_kernel:\n"
+ "endbr64\n"
+ PUSH_REGS
+ "movq %rsp, %rdi\n"
+ "call fred_entry_from_kernel\n"
+ POP_REGS
+ ".byte 0xf2,0x0f,0x01,0xca\n" /* ERETS */
+ ".align 4096, 0xcc\n"
+ ".popsection");
+
+extern char asm_fred_entrypoint_user[];
+
+/*
+ * Prepare a FRED stack frame for ERETU to run user level code, WRMSR or UD,
+ * which causes a #GP or #UD. However because FRED RSP0 is not yet mapped
+ * in guest page table, the delivery of the #GP or #UD causes a nested #PF,
+ * which is then delivered on FRED RSPx (x is 1, 2 or 3, determinated by MSR
+ * FRED_STKLVL(PF_VECTOR)).
+ *
+ * If FRED RSPx is also not yet mapped in guest page table, a triple fault is
+ * generated.
+ */
+asm(".pushsection .text\n"
+ ".type asm_test_fault, @function\n"
+ ".align 4096\n"
+ "asm_test_fault:\n"
+ "endbr64\n"
+ "push %rbp\n"
+ "mov %rsp, %rbp\n"
+ "and $(~0x3f), %rsp\n"
+ "push $0\n"
+ "push $0\n"
+ "mov $0x2b, %rax\n"
+ "bts $57, %rax\n"
+ "push %rax\n"
+ /* The FRED user level test code does NOT need a stack. */
+ "push $0\n"
+ "pushf\n"
+ "mov $0x33, %rax\n"
+ "push %rax\n"
+ "cmp $0, %edi\n"
+ "jne 1f\n"
+ "lea asm_user_wrmsr(%rip), %rax\n"
+ "jmp 2f\n"
+ "1: lea asm_user_ud(%rip), %rax\n"
+ "2: push %rax\n"
+ "push $0\n"
+ /* ERETU to user level code to generate a fault immediately */
+ ".byte 0xf3,0x0f,0x01,0xca\n"
+ "asm_done_fault:\n"
+ "mov %rbp, %rsp\n"
+ "pop %rbp\n"
+ "ret\n"
+ ".align 4096, 0xcc\n"
+ ".popsection");
+
+static void guest_code(void)
+{
+ wrmsr(MSR_IA32_FRED_CONFIG,
+ FRED_CONFIG_ENTRYPOINT(asm_fred_entrypoint_user));
+
+ wrmsr(MSR_IA32_FRED_RSP1, FRED_VALID_RSP);
+ wrmsr(MSR_IA32_FRED_RSP2, FRED_VALID_RSP);
+ wrmsr(MSR_IA32_FRED_RSP3, FRED_VALID_RSP);
+
+ /* Enable FRED */
+ set_cr4(get_cr4() | X86_CR4_FRED);
+
+ wrmsr(MSR_IA32_FRED_STKLVLS, FRED_STKLVL(PF_VECTOR, 1));
+ wrmsr(MSR_IA32_FRED_RSP0, fred_invalid_rsp[1]);
+ /* 0: wrmsr to generate #GP */
+ asm_test_fault(0);
+
+ wrmsr(MSR_IA32_FRED_STKLVLS, FRED_STKLVL(PF_VECTOR, 2));
+ wrmsr(MSR_IA32_FRED_RSP0, fred_invalid_rsp[2]);
+ /* 1: ud2 to generate #UD */
+ asm_test_fault(1);
+
+ wrmsr(MSR_IA32_FRED_STKLVLS, FRED_STKLVL(PF_VECTOR, 3));
+ wrmsr(MSR_IA32_FRED_RSP0, fred_invalid_rsp[3]);
+ /* 0: wrmsr to generate #GP */
+ asm_test_fault(0);
+
+ GUEST_DONE();
+}
+
+int main(int argc, char *argv[])
+{
+ struct kvm_vcpu *vcpu;
+ struct kvm_vm *vm;
+ struct ucall uc;
+ uint64_t expected_current_stack_level = 1;
+
+ TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_FRED));
+
+ vm = __vm_create_with_vcpus(VM_MODE_PXXV48_4K_USER, 1, 0,
+ guest_code, &vcpu);
+
+ while (true) {
+ uint64_t r;
+
+ vcpu_run(vcpu);
+
+ r = get_ucall(vcpu, &uc);
+
+ if (r == UCALL_DONE)
+ break;
+
+ if (r == UCALL_SYNC) {
+ TEST_ASSERT((uc.args[1] == expected_current_stack_level) &&
+ (uc.args[2] == fred_invalid_rsp[expected_current_stack_level] - 1),
+ "Incorrect stack level %lx and #PF address %lx\n",
+ uc.args[1], uc.args[2]);
+ expected_current_stack_level++;
+ }
+ }
+
+ kvm_vm_free(vm);
+ return 0;
+}