// SPDX-License-Identifier: GPL-2.0-only #include "test_util.h" #include "kvm_util.h" #include "processor.h" #include "vmx.h" #include "svm_util.h" /* * Use the kernel's posted interrupt vectors to minimize the risk of crashing * the host if KVM is buggy. Note, the vectors aren't set in stone, ideally * these will be kept up-to-date if the kernel vectors change, but it's "fine" * if they are stale. */ #define POSTED_INTR_VECTOR 0xf2 #define POSTED_INTR_WAKEUP_VECTOR 0xf1 #define POSTED_INTR_NESTED_VECTOR 0xf0 static bool inhibit_apicv; static volatile unsigned int nr_irqs; static void guest_irq_handler(struct ex_regs *regs) { nr_irqs++; x2apic_write_reg(APIC_EOI, 0); } static void l2_guest_code(void) { if (inhibit_apicv) wrmsr(MSR_IA32_APICBASE, rdmsr(MSR_IA32_APICBASE) & GENMASK_ULL(11, 0)); for (;;) { x2apic_write_reg(APIC_TASKPRI, 0xf0); GUEST_ASSERT_EQ(x2apic_read_reg(APIC_TASKPRI), 0xf0); asm volatile("cpuid" ::: "eax", "ebx", "ecx", "edx"); } } static void l1_svm_code(struct svm_test_data *svm) { struct vmcb_control_area *ctrl = &svm->vmcb->control; generic_svm_setup(svm, l2_guest_code); ctrl->intercept |= BIT_ULL(INTERCEPT_CPUID) | BIT_ULL(INTERCEPT_MSR_PROT); run_guest(svm->vmcb, svm->vmcb_gpa); GUEST_ASSERT_EQ(ctrl->exit_code, SVM_EXIT_CPUID); stgi(); x2apic_write_reg(APIC_TASKPRI, 0); } static void l1_vmx_code(struct vmx_pages *vmx, struct hyperv_test_pages *hv_pages) { u64 control; if (hv_pages) { wrmsr(HV_X64_MSR_GUEST_OS_ID, HYPERV_LINUX_OS_ID); enable_vp_assist(hv_pages->vp_assist_gpa, hv_pages->vp_assist); evmcs_enable(); } GUEST_ASSERT_EQ(prepare_for_vmx_operation(vmx), true); if (hv_pages) { GUEST_ASSERT(load_evmcs(hv_pages)); current_evmcs->hv_enlightenments_control.msr_bitmap = 1; } else { GUEST_ASSERT(load_vmcs(vmx)); } prepare_vmcs(vmx, NULL); GUEST_ASSERT_EQ(vmwrite(GUEST_RIP, (unsigned long)l2_guest_code), 0); control = vmreadz(PIN_BASED_VM_EXEC_CONTROL); control |= PIN_BASED_EXT_INTR_MASK; vmwrite(PIN_BASED_VM_EXEC_CONTROL, control); control = vmreadz(CPU_BASED_VM_EXEC_CONTROL); control |= CPU_BASED_USE_MSR_BITMAPS | CPU_BASED_TPR_SHADOW; GUEST_ASSERT_EQ(vmwrite(CPU_BASED_VM_EXEC_CONTROL, control), 0); control = vmreadz(SECONDARY_VM_EXEC_CONTROL); control |= SECONDARY_EXEC_VIRTUALIZE_X2APIC_MODE | SECONDARY_EXEC_APIC_REGISTER_VIRT | SECONDARY_EXEC_VIRTUAL_INTR_DELIVERY; control &= (rdmsr(MSR_IA32_VMX_PROCBASED_CTLS2) >> 32); GUEST_ASSERT_EQ(vmwrite(SECONDARY_VM_EXEC_CONTROL, control), 0); GUEST_ASSERT(!vmlaunch()); GUEST_ASSERT_EQ(vmreadz(VM_EXIT_REASON), EXIT_REASON_CPUID); GUEST_ASSERT_EQ(vmwrite(GUEST_RIP, vmreadz(GUEST_RIP) + vmreadz(VM_EXIT_INSTRUCTION_LEN)), 0); } static void l1_vmx_code_part2(void) { u64 control; control = vmreadz(CPU_BASED_VM_EXEC_CONTROL); control &= ~CPU_BASED_TPR_SHADOW; GUEST_ASSERT_EQ(vmwrite(CPU_BASED_VM_EXEC_CONTROL, control), 0); control = vmreadz(SECONDARY_VM_EXEC_CONTROL); control &= ~(SECONDARY_EXEC_VIRTUALIZE_X2APIC_MODE | SECONDARY_EXEC_APIC_REGISTER_VIRT | SECONDARY_EXEC_VIRTUAL_INTR_DELIVERY); GUEST_ASSERT_EQ(vmwrite(SECONDARY_VM_EXEC_CONTROL, control), 0); GUEST_ASSERT(!vmresume()); GUEST_ASSERT_EQ(vmreadz(VM_EXIT_REASON), EXIT_REASON_CPUID); GUEST_ASSERT_EQ(vmwrite(GUEST_RIP, vmreadz(GUEST_RIP) + vmreadz(VM_EXIT_INSTRUCTION_LEN)), 0); } static void l1_test_x2apic_intercepts(void) { GUEST_ASSERT_EQ(nr_irqs, 0); sti_nop(); GUEST_ASSERT_EQ(x2apic_read_reg(APIC_TASKPRI), 0xf0); x2apic_write_reg(APIC_ICR, APIC_DEST_SELF | APIC_INT_ASSERT | POSTED_INTR_VECTOR); GUEST_ASSERT_EQ(nr_irqs, 0); x2apic_write_reg(APIC_TASKPRI, 0xff); GUEST_ASSERT_EQ(x2apic_read_reg(APIC_TASKPRI), 0xff); x2apic_write_reg(APIC_ICR, APIC_DEST_SELF | APIC_INT_ASSERT | POSTED_INTR_WAKEUP_VECTOR); GUEST_ASSERT_EQ(nr_irqs, 0); x2apic_write_reg(APIC_TASKPRI, 0); GUEST_ASSERT_EQ(x2apic_read_reg(APIC_TASKPRI), 0); x2apic_write_reg(APIC_ICR, APIC_DEST_SELF | APIC_INT_ASSERT | POSTED_INTR_NESTED_VECTOR); GUEST_ASSERT_EQ(nr_irqs, 3); nr_irqs = 0; } static void l1_guest_code(void *test_data, void *hv_pages) { x2apic_enable(); if (this_cpu_has(X86_FEATURE_SVM)) l1_svm_code(test_data); else l1_vmx_code(test_data, hv_pages); GUEST_ASSERT_EQ(x2apic_read_reg(APIC_TASKPRI), 0); x2apic_write_reg(APIC_TASKPRI, 0xf0); l1_test_x2apic_intercepts(); if (this_cpu_has(X86_FEATURE_VMX)) { l1_vmx_code_part2(); l1_test_x2apic_intercepts(); } GUEST_DONE(); } static void test_x2apic_intercepts(bool with_inhibit_apicv, bool use_evmcs) { gva_t nested_test_data_gva, hv_pages_gva = 0; struct kvm_vcpu *vcpu; struct kvm_vm *vm; struct ucall uc; inhibit_apicv = with_inhibit_apicv; vm = vm_create_with_one_vcpu(&vcpu, l1_guest_code); vm_install_exception_handler(vm, POSTED_INTR_VECTOR, guest_irq_handler); vm_install_exception_handler(vm, POSTED_INTR_WAKEUP_VECTOR, guest_irq_handler); vm_install_exception_handler(vm, POSTED_INTR_NESTED_VECTOR, guest_irq_handler); sync_global_to_guest(vm, inhibit_apicv); if (kvm_cpu_has(X86_FEATURE_SVM)) vcpu_alloc_svm(vm, &nested_test_data_gva); else vcpu_alloc_vmx(vm, &nested_test_data_gva); if (use_evmcs) { vcpu_set_hv_cpuid(vcpu); vcpu_enable_evmcs(vcpu); vcpu_alloc_hyperv_test_pages(vm, &hv_pages_gva); } vcpu_args_set(vcpu, 2, nested_test_data_gva, hv_pages_gva); vcpu_run(vcpu); TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO); switch (get_ucall(vcpu, &uc)) { case UCALL_DONE: break; case UCALL_ABORT: REPORT_GUEST_ASSERT(uc); break; default: TEST_FAIL("Expected DONE, got unexpected ucall %lu", uc.cmd); } kvm_vm_free(vm); } int main(int argc, char *argv[]) { TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_SVM) || kvm_cpu_has(X86_FEATURE_VMX)); test_x2apic_intercepts(true, false); test_x2apic_intercepts(false, false); if (kvm_has_cap(KVM_CAP_HYPERV_ENLIGHTENED_VMCS)) { test_x2apic_intercepts(true, true); test_x2apic_intercepts(false, true); } }