[PATCH v4 2/7] KVM: SVM: Add support for the SEV-SNP #HV doorbell page NAE event

From: Melody Wang

Date: Fri Apr 24 2026 - 13:43:37 EST


To support Restricted Injection, the SEV-SNP guest must register a doorbell
page for use with #HV. This is done using the #HV doorbell page NAE event.

This event consists of four actions: GET_PREFERRED, SET, QUERY, CLEAR.
Implement it per the GHCB specification.

Co-developed-by: Thomas Lendacky <thomas.lendacky@xxxxxxx>
Signed-off-by: Thomas Lendacky <thomas.lendacky@xxxxxxx>
Signed-off-by: Melody Wang <huibo.wang@xxxxxxx>
---
arch/x86/include/uapi/asm/svm.h | 5 +++
arch/x86/kvm/svm/sev.c | 71 +++++++++++++++++++++++++++++++++
arch/x86/kvm/svm/svm.c | 3 ++
arch/x86/kvm/svm/svm.h | 2 +
4 files changed, 81 insertions(+)

diff --git a/arch/x86/include/uapi/asm/svm.h b/arch/x86/include/uapi/asm/svm.h
index 010a45c9f614..d84a13ac4627 100644
--- a/arch/x86/include/uapi/asm/svm.h
+++ b/arch/x86/include/uapi/asm/svm.h
@@ -117,6 +117,11 @@
#define SVM_VMGEXIT_AP_CREATE_ON_INIT 0
#define SVM_VMGEXIT_AP_CREATE 1
#define SVM_VMGEXIT_AP_DESTROY 2
+#define SVM_VMGEXIT_HVDB_PAGE 0x80000014ull
+#define SVM_VMGEXIT_HVDB_GET_PREFERRED 0
+#define SVM_VMGEXIT_HVDB_SET 1
+#define SVM_VMGEXIT_HVDB_QUERY 2
+#define SVM_VMGEXIT_HVDB_CLEAR 3
#define SVM_VMGEXIT_SNP_RUN_VMPL 0x80000018ull
#define SVM_VMGEXIT_SAVIC 0x8000001aull
#define SVM_VMGEXIT_SAVIC_REGISTER_GPA 0
diff --git a/arch/x86/kvm/svm/sev.c b/arch/x86/kvm/svm/sev.c
index c2126b3c3072..f09c4236fb5e 100644
--- a/arch/x86/kvm/svm/sev.c
+++ b/arch/x86/kvm/svm/sev.c
@@ -3553,6 +3553,10 @@ static int sev_es_validate_vmgexit(struct vcpu_svm *svm)
control->exit_info_1 == control->exit_info_2)
goto vmgexit_err;
break;
+ case SVM_VMGEXIT_HVDB_PAGE:
+ if (!is_sev_snp_guest(vcpu))
+ goto vmgexit_err;
+ break;
default:
reason = GHCB_ERR_INVALID_EVENT;
goto vmgexit_err;
@@ -4299,6 +4303,65 @@ static int snp_handle_ext_guest_req(struct vcpu_svm *svm, gpa_t req_gpa, gpa_t r
return 1; /* resume guest */
}

+static int sev_snp_hv_doorbell_page(struct vcpu_svm *svm)
+{
+ struct kvm_vcpu *vcpu = &svm->vcpu;
+ struct kvm_host_map hvdb_map;
+ gpa_t hvdb_gpa;
+ u64 request;
+
+ if (!is_sev_snp_guest(vcpu))
+ return -EINVAL;
+
+ request = svm->vmcb->control.exit_info_1;
+ hvdb_gpa = svm->vmcb->control.exit_info_2;
+
+ switch (request) {
+ case SVM_VMGEXIT_HVDB_GET_PREFERRED:
+ ghcb_set_sw_exit_info_2(svm->sev_es.ghcb, ~0ULL);
+ break;
+ case SVM_VMGEXIT_HVDB_SET:
+ svm->sev_es.hvdb_gpa = INVALID_PAGE;
+
+ if (!PAGE_ALIGNED(hvdb_gpa)) {
+ vcpu_unimpl(vcpu, "vmgexit: unaligned #HV doorbell page address [%#llx] from guest\n",
+ hvdb_gpa);
+ return -EINVAL;
+ }
+
+ if (!page_address_valid(vcpu, hvdb_gpa)) {
+ vcpu_unimpl(vcpu, "vmgexit: invalid #HV doorbell page address [%#llx] from guest\n",
+ hvdb_gpa);
+ return -EINVAL;
+ }
+
+ /* Map and unmap the GPA just to be sure the GPA is valid */
+ if (kvm_vcpu_map(vcpu, gpa_to_gfn(hvdb_gpa), &hvdb_map)) {
+ vcpu_unimpl(vcpu, "vmgexit: error mapping #HV doorbell page [%#llx] from guest\n",
+ hvdb_gpa);
+ return -EINVAL;
+ }
+ kvm_vcpu_unmap(vcpu, &hvdb_map);
+
+ svm->sev_es.hvdb_gpa = hvdb_gpa;
+ fallthrough;
+ case SVM_VMGEXIT_HVDB_QUERY:
+ ghcb_set_sw_exit_info_2(svm->sev_es.ghcb, svm->sev_es.hvdb_gpa);
+ break;
+ case SVM_VMGEXIT_HVDB_CLEAR:
+ svm->sev_es.hvdb_gpa = INVALID_PAGE;
+ break;
+ default:
+ svm->sev_es.hvdb_gpa = INVALID_PAGE;
+
+ vcpu_unimpl(vcpu, "vmgexit: invalid #HV doorbell page request [%#llx] from guest\n",
+ request);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
static int sev_handle_vmgexit_msr_protocol(struct vcpu_svm *svm)
{
struct vmcb_control_area *control = &svm->vmcb->control;
@@ -4566,6 +4629,14 @@ int sev_handle_vmgexit(struct kvm_vcpu *vcpu)
case SVM_VMGEXIT_EXT_GUEST_REQUEST:
ret = snp_handle_ext_guest_req(svm, control->exit_info_1, control->exit_info_2);
break;
+ case SVM_VMGEXIT_HVDB_PAGE:
+ if (sev_snp_hv_doorbell_page(svm)) {
+ 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);
+ }
+
+ ret = 1;
+ break;
case SVM_VMGEXIT_UNSUPPORTED_EVENT:
vcpu_unimpl(vcpu,
"vmgexit: unsupported event - exit_info_1=%#llx, exit_info_2=%#llx\n",
diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c
index e7fdd7a9c280..826d60527297 100644
--- a/arch/x86/kvm/svm/svm.c
+++ b/arch/x86/kvm/svm/svm.c
@@ -1266,6 +1266,9 @@ static void __svm_vcpu_reset(struct kvm_vcpu *vcpu)

svm->nmi_masked = false;
svm->awaiting_iret_completion = false;
+
+ if (is_sev_es_guest(vcpu))
+ svm->sev_es.hvdb_gpa = INVALID_PAGE;
}

static void svm_vcpu_reset(struct kvm_vcpu *vcpu, bool init_event)
diff --git a/arch/x86/kvm/svm/svm.h b/arch/x86/kvm/svm/svm.h
index a10668d17a16..4ab58307bf75 100644
--- a/arch/x86/kvm/svm/svm.h
+++ b/arch/x86/kvm/svm/svm.h
@@ -267,6 +267,8 @@ struct vcpu_sev_es_state {
gpa_t snp_vmsa_gpa;
bool snp_ap_waiting_for_reset;
bool snp_has_guest_vmsa;
+
+ gpa_t hvdb_gpa;
};

struct vcpu_svm {
--
2.43.0