[RFC PATCH v5 2/8] KVM: SVM: Add support for the SEV-SNP #HV doorbell page NAE event
From: Melody Wang
Date: Sun Nov 08 2026 - 22:19:46 EST
To support Restricted Injection, the SEV-SNP guest must register
a doorbell page for use with #HV.
The #HV doorbell page NAE event allows the guest to register a doorbell
page. 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 | 68 +++++++++++++++++++++++++++++++++
arch/x86/kvm/svm/svm.c | 3 ++
arch/x86/kvm/svm/svm.h | 2 +
4 files changed, 78 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 5705723f1f41..da07fe476f3d 100644
--- a/arch/x86/kvm/svm/sev.c
+++ b/arch/x86/kvm/svm/sev.c
@@ -4346,6 +4346,68 @@ 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 (!sev_snp_guest(vcpu->kvm))
+ 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:
+ if (svm->sev_es.hvdb_gpa == INVALID_PAGE)
+ ghcb_set_sw_exit_info_2(svm->sev_es.ghcb, 0);
+ else
+ 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;
@@ -4497,6 +4559,7 @@ static bool is_snp_only_vmgexit(u64 exit_code)
case SVM_VMGEXIT_GUEST_REQUEST:
case SVM_VMGEXIT_EXT_GUEST_REQUEST:
case SVM_VMGEXIT_PSC:
+ case SVM_VMGEXIT_HVDB_PAGE:
return true;
default:
return false;
@@ -4680,6 +4743,11 @@ int sev_handle_vmgexit(struct kvm_vcpu *vcpu)
return snp_handle_ext_guest_req(svm, control->exit_info_1,
control->exit_info_2);
+ case SVM_VMGEXIT_HVDB_PAGE:
+ if (sev_snp_hv_doorbell_page(svm))
+ svm_vmgexit_bad_input(svm, GHCB_ERR_INVALID_INPUT);
+
+ return 1;
case SVM_VMGEXIT_UNSUPPORTED_EVENT:
/*
* Note, the _guest_ is reporting an unsupported #VC, i.e. this
diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c
index 7d59d301e1e5..da7abf0c39fe 100644
--- a/arch/x86/kvm/svm/svm.c
+++ b/arch/x86/kvm/svm/svm.c
@@ -1291,6 +1291,9 @@ static void __svm_vcpu_reset(struct kvm_vcpu *vcpu)
svm->nmi_masked = false;
svm->awaiting_iret_completion = false;
+
+ if (is_sev_snp_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 e958943b8162..f212f2e68d87 100644
--- a/arch/x86/kvm/svm/svm.h
+++ b/arch/x86/kvm/svm/svm.h
@@ -276,6 +276,8 @@ struct vcpu_sev_es_state {
gpa_t snp_guest_vmsa_gpa;
bool snp_ap_waiting_for_reset;
bool snp_has_guest_vmsa;
+
+ gpa_t hvdb_gpa;
};
struct vcpu_svm {
--
2.43.0