[PATCH v2 10/10] KVM: selftests: x86: Add test for KVM_MAP_MEMORY

From: isaku . yamahata
Date: Wed Apr 10 2024 - 18:11:13 EST


From: Isaku Yamahata <isaku.yamahata@xxxxxxxxx>

Add a test case to exercise KVM_MAP_MEMORY and run the guest to access the
pre-populated area. It tests KVM_MAP_MEMORY ioctl for KVM_X86_DEFAULT_VM
and KVM_X86_SW_PROTECTED_VM.

Signed-off-by: Isaku Yamahata <isaku.yamahata@xxxxxxxxx>
---
v2:
- Catch up for uAPI change.
- Added smm mode test case.
- Added guest mode test case.
---
tools/include/uapi/linux/kvm.h | 8 +
tools/testing/selftests/kvm/Makefile | 1 +
.../selftests/kvm/x86_64/map_memory_test.c | 479 ++++++++++++++++++
3 files changed, 488 insertions(+)
create mode 100644 tools/testing/selftests/kvm/x86_64/map_memory_test.c

diff --git a/tools/include/uapi/linux/kvm.h b/tools/include/uapi/linux/kvm.h
index c3308536482b..c742c403256a 100644
--- a/tools/include/uapi/linux/kvm.h
+++ b/tools/include/uapi/linux/kvm.h
@@ -2227,4 +2227,12 @@ struct kvm_create_guest_memfd {
__u64 reserved[6];
};

+#define KVM_MAP_MEMORY _IOWR(KVMIO, 0xd5, struct kvm_memory_mapping)
+
+struct kvm_memory_mapping {
+ __u64 base_address;
+ __u64 size;
+ __u64 flags;
+};
+
#endif /* __LINUX_KVM_H */
diff --git a/tools/testing/selftests/kvm/Makefile b/tools/testing/selftests/kvm/Makefile
index 871e2de3eb05..2b097b6ec267 100644
--- a/tools/testing/selftests/kvm/Makefile
+++ b/tools/testing/selftests/kvm/Makefile
@@ -144,6 +144,7 @@ TEST_GEN_PROGS_x86_64 += set_memory_region_test
TEST_GEN_PROGS_x86_64 += steal_time
TEST_GEN_PROGS_x86_64 += kvm_binary_stats_test
TEST_GEN_PROGS_x86_64 += system_counter_offset_test
+TEST_GEN_PROGS_x86_64 += x86_64/map_memory_test

# Compiled outputs used by test targets
TEST_GEN_PROGS_EXTENDED_x86_64 += x86_64/nx_huge_pages_test
diff --git a/tools/testing/selftests/kvm/x86_64/map_memory_test.c b/tools/testing/selftests/kvm/x86_64/map_memory_test.c
new file mode 100644
index 000000000000..d5728439542e
--- /dev/null
+++ b/tools/testing/selftests/kvm/x86_64/map_memory_test.c
@@ -0,0 +1,479 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2024, Intel, Inc
+ *
+ * Author:
+ * Isaku Yamahata <isaku.yamahata at gmail.com>
+ */
+#include <linux/sizes.h>
+
+#include <test_util.h>
+#include <kvm_util.h>
+#include <processor.h>
+
+/* Arbitrarily chosen value. Pick 3G */
+#define TEST_GVA 0xc0000000
+#define TEST_GPA TEST_GVA
+#define TEST_SIZE (SZ_2M + PAGE_SIZE)
+#define TEST_NPAGES (TEST_SIZE / PAGE_SIZE)
+#define TEST_SLOT 10
+
+/* Nested: VMXON and VMCS12 for VMX or VMCB for XVM */
+/* Arbitrarily chosen value. Pick 128MB after TEST_GVA. */
+#define NESTED_GVA (TEST_GVA + 128 * 1024 * 1024)
+#define NESTED_GPA (TEST_GPA + 128 * 1024 * 1024)
+#define NESTED_NPAGES 2
+#define NESTED_SIZE (NESTED_NPAGES * PAGE_SIZE)
+#define NESTED_SLOT 11
+
+static void guest_code(uint64_t base_gpa)
+{
+ volatile uint64_t val __used;
+ int i;
+
+ for (i = 0; i < TEST_NPAGES; i++) {
+ uint64_t *src = (uint64_t *)(base_gpa + i * PAGE_SIZE);
+
+ val = *src;
+ }
+
+ GUEST_DONE();
+}
+
+static void map_memory(struct kvm_vcpu *vcpu, u64 base_address, u64 size,
+ bool should_success)
+{
+ struct kvm_memory_mapping mapping = {
+ .base_address = base_address,
+ .size = size,
+ .flags = 0,
+ };
+ int ret;
+
+ do {
+ ret = __vcpu_ioctl(vcpu, KVM_MAP_MEMORY, &mapping);
+ } while (ret && (errno == EAGAIN || errno == EINTR));
+
+ if (should_success)
+ __TEST_ASSERT_VM_VCPU_IOCTL(!ret, "KVM_MAP_MEMORY", ret, vcpu->vm);
+ else
+ /* No memory slot causes RET_PF_EMULATE. it results in -EINVAL. */
+ __TEST_ASSERT_VM_VCPU_IOCTL(ret && errno == EINVAL,
+ "KVM_MAP_MEMORY", ret, vcpu->vm);
+}
+
+static void set_smm(struct kvm_vcpu *vcpu, bool enter_or_leave)
+{
+ struct kvm_vcpu_events events;
+
+ vcpu_events_get(vcpu, &events);
+
+ events.smi.smm = !!enter_or_leave;
+ events.smi.pending = 0;
+ events.flags |= KVM_VCPUEVENT_VALID_SMM;
+
+ vcpu_events_set(vcpu, &events);
+}
+
+/* Copied from arch/x86/kvm/vmx/vmcs12.h */
+#define VMCS12_REVISION 0x11e57ed0
+
+struct vmcs_hdr {
+ u32 revision_id:31;
+ u32 shadow_vmcs:1;
+};
+
+typedef u64 natural_width;
+
+struct __packed vmcs12 {
+ struct vmcs_hdr hdr;
+ u32 abort;
+
+ u32 launch_state;
+ u32 padding[7];
+
+ u64 io_bitmap_a;
+ u64 io_bitmap_b;
+ u64 msr_bitmap;
+ u64 vm_exit_msr_store_addr;
+ u64 vm_exit_msr_load_addr;
+ u64 vm_entry_msr_load_addr;
+ u64 tsc_offset;
+ u64 virtual_apic_page_addr;
+ u64 apic_access_addr;
+ u64 posted_intr_desc_addr;
+ u64 ept_pointer;
+ u64 eoi_exit_bitmap0;
+ u64 eoi_exit_bitmap1;
+ u64 eoi_exit_bitmap2;
+ u64 eoi_exit_bitmap3;
+ u64 xss_exit_bitmap;
+ u64 guest_physical_address;
+ u64 vmcs_link_pointer;
+ u64 guest_ia32_debugctl;
+ u64 guest_ia32_pat;
+ u64 guest_ia32_efer;
+ u64 guest_ia32_perf_global_ctrl;
+ u64 guest_pdptr0;
+ u64 guest_pdptr1;
+ u64 guest_pdptr2;
+ u64 guest_pdptr3;
+ u64 guest_bndcfgs;
+ u64 host_ia32_pat;
+ u64 host_ia32_efer;
+ u64 host_ia32_perf_global_ctrl;
+ u64 vmread_bitmap;
+ u64 vmwrite_bitmap;
+ u64 vm_function_control;
+ u64 eptp_list_address;
+ u64 pml_address;
+ u64 encls_exiting_bitmap;
+ u64 tsc_multiplier;
+ u64 padding64[1];
+
+ natural_width cr0_guest_host_mask;
+ natural_width cr4_guest_host_mask;
+ natural_width cr0_read_shadow;
+ natural_width cr4_read_shadow;
+ natural_width dead_space[4];
+ natural_width exit_qualification;
+ natural_width guest_linear_address;
+ natural_width guest_cr0;
+ natural_width guest_cr3;
+ natural_width guest_cr4;
+ natural_width guest_es_base;
+ natural_width guest_cs_base;
+ natural_width guest_ss_base;
+ natural_width guest_ds_base;
+ natural_width guest_fs_base;
+ natural_width guest_gs_base;
+ natural_width guest_ldtr_base;
+ natural_width guest_tr_base;
+ natural_width guest_gdtr_base;
+ natural_width guest_idtr_base;
+ natural_width guest_dr7;
+ natural_width guest_rsp;
+ natural_width guest_rip;
+ natural_width guest_rflags;
+ natural_width guest_pending_dbg_exceptions;
+ natural_width guest_sysenter_esp;
+ natural_width guest_sysenter_eip;
+ natural_width host_cr0;
+ natural_width host_cr3;
+ natural_width host_cr4;
+ natural_width host_fs_base;
+ natural_width host_gs_base;
+ natural_width host_tr_base;
+ natural_width host_gdtr_base;
+ natural_width host_idtr_base;
+ natural_width host_ia32_sysenter_esp;
+ natural_width host_ia32_sysenter_eip;
+ natural_width host_rsp;
+ natural_width host_rip;
+ natural_width paddingl[8];
+
+ u32 pin_based_vm_exec_control;
+ u32 cpu_based_vm_exec_control;
+ u32 exception_bitmap;
+ u32 page_fault_error_code_mask;
+ u32 page_fault_error_code_match;
+ u32 cr3_target_count;
+ u32 vm_exit_controls;
+ u32 vm_exit_msr_store_count;
+ u32 vm_exit_msr_load_count;
+ u32 vm_entry_controls;
+ u32 vm_entry_msr_load_count;
+ u32 vm_entry_intr_info_field;
+ u32 vm_entry_exception_error_code;
+ u32 vm_entry_instruction_len;
+ u32 tpr_threshold;
+ u32 secondary_vm_exec_control;
+ u32 vm_instruction_error;
+ u32 vm_exit_reason;
+ u32 vm_exit_intr_info;
+ u32 vm_exit_intr_error_code;
+ u32 idt_vectoring_info_field;
+ u32 idt_vectoring_error_code;
+ u32 vm_exit_instruction_len;
+ u32 vmx_instruction_info;
+ u32 guest_es_limit;
+ u32 guest_cs_limit;
+ u32 guest_ss_limit;
+ u32 guest_ds_limit;
+ u32 guest_fs_limit;
+ u32 guest_gs_limit;
+ u32 guest_ldtr_limit;
+ u32 guest_tr_limit;
+ u32 guest_gdtr_limit;
+ u32 guest_idtr_limit;
+ u32 guest_es_ar_bytes;
+ u32 guest_cs_ar_bytes;
+ u32 guest_ss_ar_bytes;
+ u32 guest_ds_ar_bytes;
+ u32 guest_fs_ar_bytes;
+ u32 guest_gs_ar_bytes;
+ u32 guest_ldtr_ar_bytes;
+ u32 guest_tr_ar_bytes;
+ u32 guest_interruptibility_info;
+ u32 guest_activity_state;
+ u32 guest_sysenter_cs;
+ u32 host_ia32_sysenter_cs;
+ u32 vmx_preemption_timer_value;
+ u32 padding32[7];
+
+ u16 virtual_processor_id;
+ u16 posted_intr_nv;
+ u16 guest_es_selector;
+ u16 guest_cs_selector;
+ u16 guest_ss_selector;
+ u16 guest_ds_selector;
+ u16 guest_fs_selector;
+ u16 guest_gs_selector;
+ u16 guest_ldtr_selector;
+ u16 guest_tr_selector;
+ u16 guest_intr_status;
+ u16 host_es_selector;
+ u16 host_cs_selector;
+ u16 host_ss_selector;
+ u16 host_ds_selector;
+ u16 host_fs_selector;
+ u16 host_gs_selector;
+ u16 host_tr_selector;
+ u16 guest_pml_index;
+};
+
+/* Fill values to make KVM vmx_set_nested_state() pass. */
+void vmx_vmcs12_init(struct vmcs12 *vmcs12)
+{
+ *(__u32*)(vmcs12) = VMCS12_REVISION;
+
+ vmcs12->vmcs_link_pointer = -1;
+
+#define PIN_BASED_ALWAYSON_WITHOUT_TRUE_MSR 0x00000016
+ vmcs12->pin_based_vm_exec_control = PIN_BASED_ALWAYSON_WITHOUT_TRUE_MSR;
+
+#define CPU_BASED_ALWAYSON_WITHOUT_TRUE_MSR 0x0401e172
+ vmcs12->cpu_based_vm_exec_control = CPU_BASED_ALWAYSON_WITHOUT_TRUE_MSR;
+
+ vmcs12->secondary_vm_exec_control = 0;
+
+#define VM_EXIT_ALWAYSON_WITHOUT_TRUE_MSR 0x00036dff
+ vmcs12->vm_exit_controls = VM_EXIT_ALWAYSON_WITHOUT_TRUE_MSR;
+
+#define VM_ENTRY_ALWAYSON_WITHOUT_TRUE_MSR 0x000011ff
+ vmcs12->vm_entry_controls = VM_ENTRY_ALWAYSON_WITHOUT_TRUE_MSR;
+
+#define VMXON_CR0_ALWAYSON (X86_CR0_PE | X86_CR0_PG | X86_CR0_NE)
+#define VMXON_CR4_ALWAYSON X86_CR4_VMXE
+
+ /* host */
+ vmcs12->host_cr0 = VMXON_CR0_ALWAYSON;
+ vmcs12->host_cr3 = TEST_GPA;
+ vmcs12->host_cr4 = VMXON_CR4_ALWAYSON;;
+
+ /* Non-zero to make KVM vmx check pass */
+ vmcs12->host_cs_selector = 8;
+ vmcs12->host_ss_selector = 8;
+ vmcs12->host_ds_selector = 8;
+ vmcs12->host_es_selector = 8;
+ vmcs12->host_fs_selector = 8;
+ vmcs12->host_gs_selector = 8;
+ vmcs12->host_tr_selector = 8;
+
+ /* guest */
+ vmcs12->guest_cr0 = VMXON_CR0_ALWAYSON;
+ vmcs12->guest_cr4 = VMXON_CR4_ALWAYSON;
+
+ vmcs12->guest_cs_selector = 0xf000;
+ vmcs12->guest_cs_base = 0xffff0000UL;
+ vmcs12->guest_cs_limit = 0xffff;
+ vmcs12->guest_cs_ar_bytes = 0x93 | 0x08;
+
+ vmcs12->guest_ds_selector = 0;
+ vmcs12->guest_ds_base = 0;
+ vmcs12->guest_ds_limit = 0xffff;
+ vmcs12->guest_ds_ar_bytes = 0x93;
+
+ vmcs12->guest_es_selector = 0;
+ vmcs12->guest_es_base = 0;
+ vmcs12->guest_es_limit = 0xffff;
+ vmcs12->guest_es_ar_bytes = 0x93;
+
+ vmcs12->guest_fs_selector = 0;
+ vmcs12->guest_fs_base = 0;
+ vmcs12->guest_fs_limit = 0xffff;
+ vmcs12->guest_fs_ar_bytes = 0x93;
+
+ vmcs12->guest_gs_selector = 0;
+ vmcs12->guest_gs_base = 0;
+ vmcs12->guest_gs_limit = 0xffff;
+ vmcs12->guest_gs_ar_bytes = 0x93;
+
+ vmcs12->guest_ss_selector = 0;
+ vmcs12->guest_ss_base = 0;
+ vmcs12->guest_ss_limit = 0xffff;
+ vmcs12->guest_ss_ar_bytes = 0x93;
+
+ vmcs12->guest_ldtr_selector = 0;
+ vmcs12->guest_ldtr_base = 0;
+ vmcs12->guest_ldtr_limit = 0xfff;
+ vmcs12->guest_ldtr_ar_bytes = 0x008b;
+
+ vmcs12->guest_gdtr_base = 0;
+ vmcs12->guest_gdtr_limit = 0xffff;
+
+ vmcs12->guest_idtr_base = 0;
+ vmcs12->guest_idtr_limit = 0xffff;
+
+ /* ACTIVE = 0 */
+ vmcs12->guest_activity_state = 0;
+
+ vmcs12->guest_interruptibility_info = 0;
+ vmcs12->guest_pending_dbg_exceptions = 0;
+
+ vmcs12->vm_entry_intr_info_field = 0;
+}
+
+void vmx_state_set(struct kvm_vcpu *vcpu, struct kvm_nested_state *state,
+ __u16 flags, __u64 vmxon_pa, __u64 vmcs12_pa)
+{
+ struct vmcs12 *vmcs12 = (struct vmcs12 *)state->data.vmx->vmcs12;
+
+ memset(state, 0, sizeof(*state) + KVM_STATE_NESTED_VMX_VMCS_SIZE);
+
+ state->flags = flags;
+ state->format = KVM_STATE_NESTED_FORMAT_VMX;
+ state->size = KVM_STATE_NESTED_VMX_VMCS_SIZE;
+
+ state->hdr.vmx.vmxon_pa = vmxon_pa;
+ state->hdr.vmx.vmcs12_pa = vmcs12_pa;
+ state->hdr.vmx.smm.flags = 0;
+ state->hdr.vmx.pad = 0;
+ state->hdr.vmx.flags = 0;
+ state->hdr.vmx.preemption_timer_deadline = 0;
+
+ vmx_vmcs12_init(vmcs12);
+
+ vcpu_nested_state_set(vcpu, state);
+}
+
+static void __test_map_memory(unsigned long vm_type, bool private,
+ bool smm, bool nested)
+{
+ struct kvm_nested_state *state = NULL;
+ const struct vm_shape shape = {
+ .mode = VM_MODE_DEFAULT,
+ .type = vm_type,
+ };
+ struct kvm_sregs sregs;
+ struct kvm_vcpu *vcpu;
+ struct kvm_regs regs;
+ struct kvm_run *run;
+ struct kvm_vm *vm;
+ struct ucall uc;
+
+ vm = vm_create_shape_with_one_vcpu(shape, &vcpu, guest_code);
+ vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS,
+ TEST_GPA, TEST_SLOT, TEST_NPAGES,
+ private ? KVM_MEM_GUEST_MEMFD : 0);
+ virt_map(vm, TEST_GVA, TEST_GPA, TEST_NPAGES);
+
+ if (private)
+ vm_mem_set_private(vm, TEST_GPA, TEST_SIZE);
+ if (nested) {
+ size_t size = sizeof(*state);
+
+ if (kvm_cpu_has(X86_FEATURE_VMX)) {
+ size += KVM_STATE_NESTED_VMX_VMCS_SIZE;
+ vcpu_set_cpuid_feature(vcpu, X86_FEATURE_VMX);
+ }
+
+ state = malloc(size);
+ vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, NESTED_GPA,
+ NESTED_SLOT, NESTED_NPAGES, 0);
+ virt_map(vm, NESTED_GVA, NESTED_GPA, NESTED_NPAGES);
+ }
+
+ if (smm)
+ set_smm(vcpu, true);
+ if (nested) {
+ vcpu_regs_get(vcpu, &regs);
+ vcpu_sregs_get(vcpu, &sregs);
+ vmx_state_set(vcpu, state, KVM_STATE_NESTED_RUN_PENDING |
+ KVM_STATE_NESTED_GUEST_MODE,
+ NESTED_GPA, NESTED_GPA + PAGE_SIZE);
+ }
+ map_memory(vcpu, TEST_GPA, SZ_2M, true);
+ map_memory(vcpu, TEST_GPA + SZ_2M, PAGE_SIZE, true);
+ map_memory(vcpu, TEST_GPA + TEST_SIZE, PAGE_SIZE, false);
+ if (nested) {
+ vmx_state_set(vcpu, state, 0, -1, -1);
+ free(state);
+ vcpu_sregs_set(vcpu, &sregs);
+ vcpu_regs_set(vcpu, &regs);
+ }
+ if (smm)
+ set_smm(vcpu, false);
+
+ vcpu_args_set(vcpu, 1, TEST_GVA);
+ vcpu_run(vcpu);
+
+ run = vcpu->run;
+ TEST_ASSERT(run->exit_reason == KVM_EXIT_IO,
+ "Wanted KVM_EXIT_IO, got exit reason: %u (%s)",
+ run->exit_reason, exit_reason_str(run->exit_reason));
+
+ switch (get_ucall(vcpu, &uc)) {
+ case UCALL_ABORT:
+ REPORT_GUEST_ASSERT(uc);
+ break;
+ case UCALL_DONE:
+ break;
+ default:
+ TEST_FAIL("Unknown ucall 0x%lx.", uc.cmd);
+ break;
+ }
+
+ kvm_vm_free(vm);
+}
+
+static void test_map_memory(unsigned long vm_type, bool private)
+{
+ if (!(kvm_check_cap(KVM_CAP_VM_TYPES) & BIT(vm_type))) {
+ pr_info("Skipping tests for vm_type 0x%lx\n", vm_type);
+ return;
+ }
+
+ __test_map_memory(vm_type, private, false, false);
+
+ if (kvm_has_cap(KVM_CAP_VCPU_EVENTS) && kvm_has_cap(KVM_CAP_X86_SMM))
+ __test_map_memory(vm_type, private, true, false);
+ else
+ pr_info("skipping test for vm_type 0x%lx with smm\n", vm_type);
+
+ if (!kvm_has_cap(KVM_CAP_NESTED_STATE)) {
+ pr_info("Skipping test for vm_type 0x%lx with nesting\n", vm_type);
+ return;
+ }
+
+ if (kvm_cpu_has(X86_FEATURE_SVM)) {
+ pr_info("Implement nested SVM case\n");
+ return;
+ }
+ if (!kvm_cpu_has(X86_FEATURE_VMX)) {
+ pr_info("Skipping test for vm_type 0x%lx with nested VMX\n",
+ vm_type);
+ return;
+ }
+ __test_map_memory(vm_type, private, false, true);
+}
+
+int main(int argc, char *argv[])
+{
+ TEST_REQUIRE(kvm_check_cap(KVM_CAP_MAP_MEMORY));
+
+ test_map_memory(KVM_X86_DEFAULT_VM, false);
+ test_map_memory(KVM_X86_SW_PROTECTED_VM, false);
+ test_map_memory(KVM_X86_SW_PROTECTED_VM, true);
+ return 0;
+}
--
2.43.2