This GHCB protocol event is used by the guest to notify KVM of the GPA of the APIC backing page being used by a vCPU. The APIC ID parameter is used to identify the vCPU to which the backing page action is related is assigned. An APIC ID value of 0xffff_ffff_ffff_ffff means that the backing page action is for the vCPU performing the call. Secure AVIC requires the guest vCPU APIC backing page entry to be always present in the guest’s Nested Paginge Table (NPT) while the vCPU is running because some AVIC hardware acceleration sequences may not be restartable when Secure AVIC is enabled. If an access to the guest's APIC backing page by Secure AVIC hardware results in a nested page fault, the BUSY bit in the VMSA is set and subsequent VMRUN fails with a VMEXIT_BUSY error code. VMEXIT_BUSY is unrecoverable in this instance and the vCPU cannot be resumed post this event. Two actions are available to the guest to notify KVM of these pages. • SVM_VMGEXIT_SAVIC_REGISTER_BACKING_PAGE (0) A Secure AVIC guest should use this action to inform KVM of the page-aligned GPA that will be used as the Secure AVIC backing page for the specified vCPU. To ensure that the backing page NPT entry is present while vCPU is running, KVM does a PSMASH for the GPA if the corresponding NPT entry is of size 2M. Without PSMASH, it is possible for other allocations to be part of the same 2M page as the APIC backing page and any modifications (page state change from private to shared) to any one of those allocations would result in splitting the 2M page to 4K pages. This would result in zapping the 2M PTE while APIC backing page is potentially being accessed by Secure AVIC hardware. Setting a Secure AVIC backing page GPA automatically clears any currently set Secure AVIC backing page GPA. • SVM_VMGEXIT_SAVIC_UNREGISTER_BACKING_PAGE (1) A guest may use this action to inform KVM that the previously set GPA is no longer being used as the Secure AVIC backing page for the specified vCPU. This removes the requirement on KVM to ensure that the specified GPA is always present in the NPT of the guest while the specified vCPU is running. KVM returns the GPA that was currently SET or 0 if there was no previously set GPA. Co-developed-by: Kishon Vijay Abraham I <kvijayab@xxxxxxx> Signed-off-by: Kishon Vijay Abraham I <kvijayab@xxxxxxx> Signed-off-by: Neeraj Upadhyay <Neeraj.Upadhyay@xxxxxxx> --- Initial GHCB draft spec for the new GHCB event for Secure AVIC is at: https://lore.kernel.org/linux-coco/3453675d-ca29-4715-9c17-10b56b3af17e@xxxxxxx/T/#u The GHCB event has been updated to pass the action param for GPA register/unregister. The new GHCB spec will be published soon. I will share the link to the updated spec once it is available publically. arch/x86/include/uapi/asm/svm.h | 3 ++ arch/x86/kvm/svm/sev.c | 58 +++++++++++++++++++++++++++++++++ arch/x86/kvm/svm/svm.h | 1 + 3 files changed, 62 insertions(+) diff --git a/arch/x86/include/uapi/asm/svm.h b/arch/x86/include/uapi/asm/svm.h index ec1321248dac..0a1e8687f464 100644 --- a/arch/x86/include/uapi/asm/svm.h +++ b/arch/x86/include/uapi/asm/svm.h @@ -117,6 +117,9 @@ #define SVM_VMGEXIT_AP_CREATE 1 #define SVM_VMGEXIT_AP_DESTROY 2 #define SVM_VMGEXIT_SNP_RUN_VMPL 0x80000018 +#define SVM_VMGEXIT_SECURE_AVIC 0x8000001a +#define SVM_VMGEXIT_SAVIC_REGISTER_BACKING_PAGE 0 +#define SVM_VMGEXIT_SAVIC_UNREGISTER_BACKING_PAGE 1 #define SVM_VMGEXIT_HV_FEATURES 0x8000fffd #define SVM_VMGEXIT_TERM_REQUEST 0x8000fffe #define SVM_VMGEXIT_TERM_REASON(reason_set, reason_code) \ diff --git a/arch/x86/kvm/svm/sev.c b/arch/x86/kvm/svm/sev.c index 40314c4086c2..77c1ecebf677 100644 --- a/arch/x86/kvm/svm/sev.c +++ b/arch/x86/kvm/svm/sev.c @@ -3400,6 +3400,14 @@ static int sev_es_validate_vmgexit(struct vcpu_svm *svm) !kvm_ghcb_rcx_is_valid(svm)) goto vmgexit_err; break; + case SVM_VMGEXIT_SECURE_AVIC: + if (!sev_savic_active(vcpu->kvm) || + !kvm_ghcb_rax_is_valid(svm)) + goto vmgexit_err; + if (svm->vmcb->control.exit_info_1 == SVM_VMGEXIT_SAVIC_REGISTER_BACKING_PAGE) + if (!kvm_ghcb_rbx_is_valid(svm)) + goto vmgexit_err; + break; case SVM_VMGEXIT_MMIO_READ: case SVM_VMGEXIT_MMIO_WRITE: if (!kvm_ghcb_sw_scratch_is_valid(svm)) @@ -4511,6 +4519,53 @@ static bool savic_handle_msr_exit(struct kvm_vcpu *vcpu) return false; } +static int sev_handle_savic_vmgexit(struct vcpu_svm *svm) +{ + struct kvm_vcpu *vcpu = NULL; + u64 apic_id; + + apic_id = kvm_rax_read(&svm->vcpu); + + if (apic_id == -1ULL) { + vcpu = &svm->vcpu; + } else { + vcpu = kvm_get_vcpu_by_id(vcpu->kvm, apic_id); + if (!vcpu) + goto savic_request_invalid; + } + + switch (svm->vmcb->control.exit_info_1) { + case SVM_VMGEXIT_SAVIC_REGISTER_BACKING_PAGE: + gpa_t gpa; + + gpa = kvm_rbx_read(&svm->vcpu); + if (!PAGE_ALIGNED(gpa)) + goto savic_request_invalid; + + /* + * sev_handle_rmp_fault() invocation would result in PSMASH if + * NPTE size is 2M. + */ + sev_handle_rmp_fault(vcpu, gpa, 0); + to_svm(vcpu)->sev_savic_gpa = gpa; + break; + case SVM_VMGEXIT_SAVIC_UNREGISTER_BACKING_PAGE: + kvm_rbx_write(&svm->vcpu, to_svm(vcpu)->sev_savic_gpa); + to_svm(vcpu)->sev_savic_gpa = 0; + break; + default: + goto savic_request_invalid; + } + + return 1; + +savic_request_invalid: + ghcb_set_sw_exit_info_1(svm->sev_es.ghcb, 2); + ghcb_set_sw_exit_info_2(svm->sev_es.ghcb, GHCB_ERR_INVALID_INPUT); + + return 1; +} + int sev_handle_vmgexit(struct kvm_vcpu *vcpu) { struct vcpu_svm *svm = to_svm(vcpu); @@ -4653,6 +4708,9 @@ int sev_handle_vmgexit(struct kvm_vcpu *vcpu) control->exit_info_1, control->exit_info_2); ret = -EINVAL; break; + case SVM_VMGEXIT_SECURE_AVIC: + ret = sev_handle_savic_vmgexit(svm); + break; case SVM_EXIT_MSR: if (sev_savic_active(vcpu->kvm) && savic_handle_msr_exit(vcpu)) return 1; diff --git a/arch/x86/kvm/svm/svm.h b/arch/x86/kvm/svm/svm.h index 62e3581b7d31..be87b9a0284f 100644 --- a/arch/x86/kvm/svm/svm.h +++ b/arch/x86/kvm/svm/svm.h @@ -329,6 +329,7 @@ struct vcpu_svm { bool guest_gif; bool sev_savic_has_pending_ipi; + gpa_t sev_savic_gpa; }; struct svm_cpu_data { -- 2.34.1