mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [RFC PATCH  0/9] Alternate Injection: Secure Interrupt Delivery for SEV-SNP Guests
@ 2026-11-10  8:28 Melody Wang
  2026-11-10  8:28 ` [RFC PATCH 1/9] x86/sev: Define extended #HV doorbell page structure Melody Wang
                   ` (8 more replies)
  0 siblings, 9 replies; 10+ messages in thread
From: Melody Wang @ 2026-11-10  8:28 UTC (permalink / raw)
  To: Sean Christopherson, Paolo Bonzini
  Cc: LKML, kvm, Joerg Roedel, Tom Lendacky, x86, Melody Wang

This patchset introduces host-side KVM support for AMD SEV-SNP Alternate
Injection, providing secure interrupt delivery for confidential guests against
malicious or untrusted hypervisor injection attacks.

Background & Motivation
-----------------------
In standard SEV-SNP deployments, retaining hypervisor-controlled interrupt
injection leaves confidential VMs vulnerable to unexpected or malformed
interrupt sequences. Alternate Injection mitigates this by shifting interrupt
injection control into the guest security realm. It builds on top of
Restricted Injection, leveraging the established secure communication channel
to interact with a Secure VM Service Module (SVSM) that handles APIC
emulation.

Series Status & Dependencies
----------------------------
- This is an RFC patchset intended for early architectural feedback.
- Rebased on commit 6efc8ea4653f8bfd4d59ea5fa9589d028e037909
  (joro/kvm-planes-v7.1).
- Tested against:
  * Latest SVSM build
  * SEV-SNP guest kernel (rebased to latest, with Alternate Injection)
  * EDK2 (rebased to latest, with Alternate Injection support)
  * QEMU commit 8db5273ca97f26043f64d1390f906e434c675db2
    (HEAD -> qemu-planes-linux-v7.1, joerg/qemu-planes-linux-v7.1)

Next Steps & Discussion
-----------------------
An alternative design approach has also been explored and will be presented
at KVM Forum 2026 in the talk:
"COCONUT-SVSM, KVM Planes, and Secure Interrupt Injection - Upstream Alignment" [1].

Welcome review and comments on this RFC series.

Plan to refine and update the patches further following feedback gathered at
KVM Forum.

[1] https://pretalx.com/kvm-forum-2026/talk/ZB3GWA/

Thanks,
Melody

Melody Wang (9):
  x86/sev: Define extended #HV doorbell page structure
  KVM: SVM: Add support for Alternate Injection for the  SEV-SNP #HV
    doorbell page NAE event
  KVM: SVM: Add Alternate Injection support
  KVM: SVM: Add Alternate Injection path
  KVM: SVM: Bypass lapic for VMPL1-3 guest with Alternate Injection
    enabled
  KVM: SVM: Inject #HV into VMPL0 with Alternate Injection enabled
  KVM: SVM: Notify the SVSM an intercept happened during interrupt
    delivery
  KVM: SVM: Clear AVIC Enable when Alternate Injection is enabled
  KVM: SVM: Enable Alternate Injection for an SEV-SNP guest

 arch/x86/include/asm/cpufeatures.h |   1 +
 arch/x86/include/asm/kvm-x86-ops.h |   2 +
 arch/x86/include/asm/kvm_host.h    |   2 +
 arch/x86/include/asm/sev-common.h  |   1 +
 arch/x86/include/asm/svm.h         |  60 ++++-
 arch/x86/include/uapi/asm/svm.h    |   2 +
 arch/x86/kvm/irq.c                 |   4 +
 arch/x86/kvm/lapic.c               |  10 +-
 arch/x86/kvm/lapic.h               |   1 +
 arch/x86/kvm/svm/sev.c             | 371 ++++++++++++++++++++++++++++-
 arch/x86/kvm/svm/svm.c             |  66 ++++-
 arch/x86/kvm/svm/svm.h             |  30 +++
 12 files changed, 531 insertions(+), 19 deletions(-)

-- 
2.43.0


^ permalink raw reply	[flat|nested] 10+ messages in thread

* [RFC PATCH  1/9] x86/sev: Define extended #HV doorbell page structure
  2026-11-10  8:28 [RFC PATCH 0/9] Alternate Injection: Secure Interrupt Delivery for SEV-SNP Guests Melody Wang
@ 2026-11-10  8:28 ` Melody Wang
  2026-11-10  8:28 ` [RFC PATCH 2/9] KVM: SVM: Add support for Alternate Injection for the SEV-SNP #HV doorbell page NAE event Melody Wang
                   ` (7 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Melody Wang @ 2026-11-10  8:28 UTC (permalink / raw)
  To: Sean Christopherson, Paolo Bonzini
  Cc: LKML, kvm, Joerg Roedel, Tom Lendacky, x86, Melody Wang

Alternate Injection extends the #HV doorbell page with additional
information about interrupts or events related to interrupt processing
for VMPL1-3.

Define the extended #HV doorbell page according to the spec.

Signed-off-by: Melody Wang <huibo.wang@amd.com>
---
 arch/x86/include/asm/svm.h | 60 +++++++++++++++++++++++++++++++++++---
 1 file changed, 56 insertions(+), 4 deletions(-)

diff --git a/arch/x86/include/asm/svm.h b/arch/x86/include/asm/svm.h
index 32a35ee10bce..c658eba189dc 100644
--- a/arch/x86/include/asm/svm.h
+++ b/arch/x86/include/asm/svm.h
@@ -259,8 +259,43 @@ struct __attribute__ ((__packed__)) vmcb_control_area {
  * is shared between the guest and hypervisor to communicate exception and
  * interrupt events.
  */
+struct ext_irq_desc {
+	/* first 16-bit word */
+	union {
+		struct {
+			u16 si_vector:		8,
+			    nmi:		1,
+			    mce:		1,
+			    trigger_mode:	1,
+			    reserved6:		3,
+			    multi_interrupts:	1,
+			    reserved7:		1;
+		};
+
+		u16 ext_word0;
+	};
+
+	/* Second 16-bit word */
+	union {
+		struct {
+			u16 reserved8:		15,
+			    interrupt_vector:	1;
+		};
+
+		u16 ext_word1;
+	};
+
+	/*
+	 * Set the corresponding vmpl?_irq bit in the bitmap after setting
+	 * multiple bits here.
+	 */
+	u16 interrupt_bit_vector[14];
+
+	u8 reserved9[32];
+} __packed;
+
 struct hvdb_events {
-	/* First 64 bytes of HV doorbell page defined in GHCB specification */
+	/* first 16-bit word PendingEvent */
 	union {
 		struct {
 			/* Non-maskable event indicators */
@@ -274,10 +309,27 @@ struct hvdb_events {
 		u16 pending_events;
 	};
 
-	u8 no_eoi_required;
+	/* Second 16-bit word InjectionInfo */
+	union {
+		struct {
+			u16 no_eoi_required:	1,
+			    reserved3:		7,
+			    vmpl1_irq:		1,
+			    vmpl2_irq:		1,
+			    vmpl3_irq:		1,
+			    reserved4:		5;
+		};
+
+		u16 second_word;
+	};
+
+	/* IRQ info associated with the SVSM itself */
+	u8 svsm_irq_info[64 - 4];
 
-	u8 reserved3[61];
-};
+	/* VMPL[1-3] extended IRQ descriptions */
+	struct ext_irq_desc irqs[3];
+
+} __packed;
 
 struct hvdb {
 	struct hvdb_events events;
-- 
2.43.0


^ permalink raw reply	[flat|nested] 10+ messages in thread

* [RFC PATCH  2/9] KVM: SVM: Add support for Alternate Injection for the  SEV-SNP #HV doorbell page NAE event
  2026-11-10  8:28 [RFC PATCH 0/9] Alternate Injection: Secure Interrupt Delivery for SEV-SNP Guests Melody Wang
  2026-11-10  8:28 ` [RFC PATCH 1/9] x86/sev: Define extended #HV doorbell page structure Melody Wang
@ 2026-11-10  8:28 ` Melody Wang
  2026-11-10  8:28 ` [RFC PATCH 3/9] KVM: SVM: Add Alternate Injection support Melody Wang
                   ` (6 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Melody Wang @ 2026-11-10  8:28 UTC (permalink / raw)
  To: Sean Christopherson, Paolo Bonzini
  Cc: LKML, kvm, Joerg Roedel, Tom Lendacky, x86, Melody Wang

To support Alternate Injection, there are three new NAE
events added in the spec:

1. "configure Injection Notification Vector": configures the interrupt
   vector that will be used by the hypervisor whenever it needs to
   notify the SVSM that the interrupt processing for VMPL1-3 is
   required

2. "Specific EOI" - requests the hypervisor to perform an EOI cycle on
   level-sensitive interrupt, and

3. "Disable Alternate Injection" - requests the hypervisor to disable
   Alternate Injection for a specific VMPL.

Implement the first two.

Signed-off-by: Melody Wang <huibo.wang@amd.com>
---
 arch/x86/include/uapi/asm/svm.h |  2 ++
 arch/x86/kvm/lapic.c            |  3 ++-
 arch/x86/kvm/lapic.h            |  1 +
 arch/x86/kvm/svm/sev.c          | 40 +++++++++++++++++++++++++++++++--
 arch/x86/kvm/svm/svm.h          |  1 +
 5 files changed, 44 insertions(+), 3 deletions(-)

diff --git a/arch/x86/include/uapi/asm/svm.h b/arch/x86/include/uapi/asm/svm.h
index 60b7a52f6f7e..786f50f33457 100644
--- a/arch/x86/include/uapi/asm/svm.h
+++ b/arch/x86/include/uapi/asm/svm.h
@@ -127,6 +127,8 @@
 #define SVM_VMGEXIT_AP_VMPL_SHIFT		16
 #define SVM_VMGEXIT_GET_APIC_IDS		0x80000017ull
 #define SVM_VMGEXIT_SNP_RUN_VMPL		0x80000018ull
+#define SVM_VMGEXIT_SNP_CONFIG_INT_INJ		0x8000001bull
+#define SVM_VMGEXIT_SVSM_SPECIFIC_EOI		0x8000001dull
 #define SVM_VMGEXIT_SAVIC			0x8000001aull
 #define SVM_VMGEXIT_SAVIC_REGISTER_GPA		0
 #define SVM_VMGEXIT_SAVIC_UNREGISTER_GPA	1
diff --git a/arch/x86/kvm/lapic.c b/arch/x86/kvm/lapic.c
index dc7a08831a54..9ebae1052f64 100644
--- a/arch/x86/kvm/lapic.c
+++ b/arch/x86/kvm/lapic.c
@@ -1543,7 +1543,7 @@ static bool kvm_ioapic_handles_vector(struct kvm_lapic *apic, int vector)
 	return test_bit(vector, apic->vcpu->arch.ioapic_handled_vectors);
 }
 
-static void kvm_ioapic_send_eoi(struct kvm_lapic *apic, int vector)
+void kvm_ioapic_send_eoi(struct kvm_lapic *apic, int vector)
 {
 	int __maybe_unused trigger_mode;
 
@@ -1584,6 +1584,7 @@ static void kvm_ioapic_send_eoi(struct kvm_lapic *apic, int vector)
 	kvm_ioapic_update_eoi(apic->vcpu, vector, trigger_mode);
 #endif
 }
+EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_ioapic_send_eoi);
 
 static int apic_set_eoi(struct kvm_lapic *apic)
 {
diff --git a/arch/x86/kvm/lapic.h b/arch/x86/kvm/lapic.h
index a9ede0e145d9..9d202ef3016b 100644
--- a/arch/x86/kvm/lapic.h
+++ b/arch/x86/kvm/lapic.h
@@ -254,6 +254,7 @@ void kvm_lapic_switch_to_hv_timer(struct kvm_vcpu *vcpu);
 void kvm_lapic_expired_hv_timer(struct kvm_vcpu *vcpu);
 bool kvm_lapic_hv_timer_in_use(struct kvm_vcpu *vcpu);
 void kvm_lapic_restart_hv_timer(struct kvm_vcpu *vcpu);
+void kvm_ioapic_send_eoi(struct kvm_lapic *apic, int vector);
 
 static inline enum lapic_mode kvm_apic_mode(u64 apic_base)
 {
diff --git a/arch/x86/kvm/svm/sev.c b/arch/x86/kvm/svm/sev.c
index 0736e1c778d9..924c1b968fdc 100644
--- a/arch/x86/kvm/svm/sev.c
+++ b/arch/x86/kvm/svm/sev.c
@@ -3432,6 +3432,8 @@ static void sev_es_sync_from_ghcb(struct vcpu_svm *svm)
 	memset(ghcb->save.valid_bitmap, 0, sizeof(ghcb->save.valid_bitmap));
 }
 
+#define SVM_SEV_VMPL_MAX        4
+
 static int sev_es_validate_vmgexit(struct vcpu_svm *svm)
 {
 	struct vmcb_control_area *control = &svm->vmcb->control;
@@ -3566,6 +3568,21 @@ static int sev_es_validate_vmgexit(struct vcpu_svm *svm)
 		if (!sev_snp_guest(vcpu->kvm))
 			goto vmgexit_err;
 		break;
+	case SVM_VMGEXIT_SNP_CONFIG_INT_INJ:
+		if (!sev_snp_guest(vcpu->kvm))
+			goto vmgexit_err;
+		break;
+	case SVM_VMGEXIT_SVSM_SPECIFIC_EOI: {
+		u8 vmpl;
+
+		if (!sev_snp_guest(vcpu->kvm))
+			goto vmgexit_err;
+
+		vmpl = (control->exit_info_1 >> 16) & 0xff;
+		if (vmpl >= SVM_SEV_VMPL_MAX)
+			goto vmgexit_err;
+		break;
+	}
 	default:
 		reason = GHCB_ERR_INVALID_EVENT;
 		goto vmgexit_err;
@@ -4217,8 +4234,6 @@ static unsigned int get_ap_creation_apic_id(struct vcpu_svm *svm)
 	return upper_32_bits(svm->vmcb->control.exit_info_1);
 }
 
-#define SVM_SEV_VMPL_MAX	4
-
 static int sev_snp_ap_creation(struct kvm_vcpu *vcpu)
 {
 	struct vcpu_svm *target_svm = NULL, *svm = to_svm(vcpu);
@@ -4953,6 +4968,27 @@ int sev_handle_vmgexit(struct kvm_vcpu *vcpu)
 		}
 		ret = 1;
 		break;
+	case SVM_VMGEXIT_SNP_CONFIG_INT_INJ:
+		svm->sev_es.hvdb_ai_vector = svm->vmcb->control.exit_info_1 & 0xff;
+		ret = 1;
+		break;
+	case SVM_VMGEXIT_SVSM_SPECIFIC_EOI: {
+		struct kvm_plane *target_plane;
+		struct kvm_vcpu *target_vcpu;
+		u8 vmpl = array_index_nospec((control->exit_info_1 >> 16) & 0xff,
+					       SVM_SEV_VMPL_MAX);
+		int vector = control->exit_info_1 & 0xff;
+
+		target_plane = vcpu->kvm->planes[vmpl];
+
+		if (target_plane) {
+			target_vcpu = plane_get_vcpu_by_id(target_plane, vcpu->vcpu_id);
+			if (target_vcpu)
+				kvm_ioapic_send_eoi(target_vcpu->arch.apic, vector);
+		}
+		ret = 1;
+		break;
+	}
 	case SVM_VMGEXIT_GET_APIC_IDS:
 		sev_get_apic_ids(svm);
 		ret = 1;
diff --git a/arch/x86/kvm/svm/svm.h b/arch/x86/kvm/svm/svm.h
index 7aba2cceb44d..d2e397542a84 100644
--- a/arch/x86/kvm/svm/svm.h
+++ b/arch/x86/kvm/svm/svm.h
@@ -287,6 +287,7 @@ struct vcpu_sev_es_state {
 	bool snp_has_guest_vmsa;
 
 	gpa_t hvdb_gpa;
+	u8 hvdb_ai_vector;
 };
 
 struct vcpu_svm {
-- 
2.43.0


^ permalink raw reply	[flat|nested] 10+ messages in thread

* [RFC PATCH  3/9] KVM: SVM: Add Alternate Injection support
  2026-11-10  8:28 [RFC PATCH 0/9] Alternate Injection: Secure Interrupt Delivery for SEV-SNP Guests Melody Wang
  2026-11-10  8:28 ` [RFC PATCH 1/9] x86/sev: Define extended #HV doorbell page structure Melody Wang
  2026-11-10  8:28 ` [RFC PATCH 2/9] KVM: SVM: Add support for Alternate Injection for the SEV-SNP #HV doorbell page NAE event Melody Wang
@ 2026-11-10  8:28 ` Melody Wang
  2026-11-10  8:28 ` [RFC PATCH 4/9] KVM: SVM: Add Alternate Injection path Melody Wang
                   ` (5 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Melody Wang @ 2026-11-10  8:28 UTC (permalink / raw)
  To: Sean Christopherson, Paolo Bonzini
  Cc: LKML, kvm, Joerg Roedel, Tom Lendacky, x86, Melody Wang

To avoid breaking the other guests without Alternate Injection, add
new paths for Alternate Injection, which will isolate Alternate
Injection from other interrupt injection path.

Add new functions which supports Alternate Injection.

No functionality changes yet

Signed-off-by: Melody Wang <huibo.wang@amd.com>
---
 arch/x86/kvm/irq.c     |   1 +
 arch/x86/kvm/svm/sev.c | 279 ++++++++++++++++++++++++++++++++++++++++-
 arch/x86/kvm/svm/svm.c |  34 +++++
 arch/x86/kvm/svm/svm.h |  27 ++++
 4 files changed, 339 insertions(+), 2 deletions(-)

diff --git a/arch/x86/kvm/irq.c b/arch/x86/kvm/irq.c
index abce6cf24cb5..5f7e5f3e5528 100644
--- a/arch/x86/kvm/irq.c
+++ b/arch/x86/kvm/irq.c
@@ -165,6 +165,7 @@ int kvm_cpu_get_interrupt(struct kvm_vcpu *v)
 
 	return vector;
 }
+EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_cpu_get_interrupt);
 
 void kvm_inject_pending_timer_irqs(struct kvm_vcpu *vcpu)
 {
diff --git a/arch/x86/kvm/svm/sev.c b/arch/x86/kvm/svm/sev.c
index 924c1b968fdc..78b8487627ed 100644
--- a/arch/x86/kvm/svm/sev.c
+++ b/arch/x86/kvm/svm/sev.c
@@ -5741,6 +5741,120 @@ static struct hvdb *map_hvdb(struct kvm_vcpu *vcpu, struct kvm_host_map *map)
 	return map->hva;
 }
 
+bool sev_snp_doorbell_has_injectable_vector(struct kvm_vcpu *vcpu)
+{
+	struct kvm_vcpu *vcpu_plane0;
+	struct kvm_host_map hvdb_map;
+	struct hvdb *hvdb;
+	bool ret = false;
+
+	vcpu_plane0 = vcpu->common->vcpus[0];
+	hvdb = map_hvdb(vcpu_plane0, &hvdb_map);
+	if (!hvdb)
+		return ret;
+
+	if (hvdb->events.vmpl2_irq)
+		ret = true;
+
+	unmap_hvdb(vcpu_plane0, &hvdb_map);
+
+	return ret;
+}
+
+bool sev_snp_ai_update_doorbell(enum inject_type type, struct kvm_vcpu *vcpu,
+				int vector, int trig_mode)
+{
+	struct kvm_vcpu *vcpu_plane0;
+	struct kvm_host_map hvdb_map;
+	struct hvdb *hvdb;
+
+	if (vector < FIRST_EXTERNAL_VECTOR && (type == INJECT_IRQ))
+		return false;
+
+	vcpu_plane0 = vcpu->common->vcpus[0];
+	hvdb = map_hvdb(vcpu_plane0, &hvdb_map);
+	if (!hvdb)
+		return false;
+
+	if (type == INJECT_NMI) {
+		hvdb->events.irqs[1].nmi = 1;
+	} else if (type == INJECT_MCE) {
+		hvdb->events.irqs[1].mce = 1;
+	} else {
+		struct ext_irq_desc *vmpl2 = &hvdb->events.irqs[1];
+		unsigned long *bit_vec = (unsigned long *)vmpl2->interrupt_bit_vector;
+
+		if (!hvdb->events.vmpl2_irq) {
+			/* Only one interrupt is pending, queue it. */
+			vmpl2->si_vector = vector;
+			vmpl2->multi_interrupts = 0;
+			vmpl2->trigger_mode = trig_mode;
+		} else if (trig_mode && vmpl2->trigger_mode) {
+			/*
+			 * A higher prio level-triggered overwrites a previous,
+			 * lower prio, level-triggered one.
+			 */
+			if (vmpl2->si_vector < vector)
+				vmpl2->si_vector = vector;
+		} else if (trig_mode) {
+			/*
+			 * Level-triggered takes the single vector slot from the
+			 * pending edge-triggered one, which - if there still is
+			 * a single one - moves to the bit vector.
+			 */
+			if (vmpl2->si_vector)
+				set_bit(vmpl2->si_vector - FIRST_EXTERNAL_VECTOR, bit_vec);
+
+			vmpl2->si_vector = vector;
+			vmpl2->multi_interrupts = 1;
+			vmpl2->trigger_mode = trig_mode;
+		} else if (vector != vmpl2->si_vector) {
+			/*
+			 * Edge-triggered: set mult only when the new vector
+			 * that's being queued is not the currently pending one.
+			 * The already pending vector, if any, gets a bit too.
+			 */
+			set_bit(vector - FIRST_EXTERNAL_VECTOR, bit_vec);
+
+			if (vmpl2->si_vector)
+				set_bit(vmpl2->si_vector - FIRST_EXTERNAL_VECTOR, bit_vec);
+
+			vmpl2->si_vector = 0;
+			vmpl2->multi_interrupts = 1;
+		}
+		hvdb->events.vmpl2_irq = 1;
+	}
+
+	unmap_hvdb(vcpu_plane0, &hvdb_map);
+
+	return true;
+}
+
+bool sev_snp_ai_inject(struct kvm_vcpu *vcpu)
+{
+	struct kvm_vcpu *vcpu_plane0;
+	struct kvm_host_map hvdb_map;
+	struct hvdb *hvdb;
+	struct vcpu_svm *svm0;
+
+	vcpu_plane0 = vcpu->common->vcpus[0];
+	svm0 = to_svm(vcpu_plane0);
+
+	hvdb = map_hvdb(vcpu_plane0, &hvdb_map);
+	if (!hvdb)
+		return false;
+
+	if (hvdb->events.no_further_signal)
+		goto unmap;
+
+	prepare_hv_injection(svm0, hvdb);
+
+unmap:
+	unmap_hvdb(vcpu_plane0, &hvdb_map);
+
+	return true;
+}
+
 static void __sev_snp_inject(enum inject_type type, struct kvm_vcpu *vcpu)
 {
 	struct vcpu_svm *svm = to_svm(vcpu);
@@ -5769,8 +5883,10 @@ bool sev_snp_queue_exception(struct kvm_vcpu *vcpu)
 {
 	struct vcpu_svm *svm = to_svm(vcpu);
 
-	if (!sev_snp_is_rinj_active(vcpu))
-		return false;
+	if (!vcpu->plane_level) {
+		if (!sev_snp_is_rinj_active(vcpu))
+			return false;
+	}
 
 	if (vcpu->arch.exception.vector == MC_VECTOR) {
 		__sev_snp_inject(INJECT_MCE, vcpu);
@@ -5799,6 +5915,70 @@ bool sev_snp_queue_exception(struct kvm_vcpu *vcpu)
 	return true;
 }
 
+bool sev_snp_ai_queue_exception(struct kvm_vcpu *vcpu)
+{
+	struct vcpu_svm *svm_current = to_svm(vcpu);
+	struct kvm_vcpu *vcpu_plane0;
+	struct vcpu_svm *svm;
+
+	vcpu_plane0 = vcpu->common->vcpus[0];
+	svm = to_svm(vcpu_plane0);
+
+	if (!sev_snp_is_ai_active(vcpu))
+		return false;
+
+	if (vcpu->arch.exception.vector == MC_VECTOR) {
+		if (svm->sev_es.cancel_flag) {
+			svm_current->sev_es.cancel_flag = false;
+			return sev_snp_ai_inject(vcpu);
+		}
+		sev_snp_ai_update_doorbell(INJECT_MCE, vcpu, 0, 0);
+		return sev_snp_ai_inject(vcpu);
+	}
+
+	/*
+	 * Restricted injection is enabled, only #HV is supported.
+	 * If the vector is not HV_VECTOR, do not inject the exception,
+	 * then return true to skip the original injection path.
+	 */
+	if (WARN_ONCE(vcpu->arch.exception.vector != HV_VECTOR,
+		      "restricted injection enabled, exception %u injection not supported\n",
+		      vcpu->arch.exception.vector))
+		return true;
+
+	/*
+	 * An intercept likely occurred during #HV delivery, so re-inject it
+	 * using the current HVDB pending event values.
+	 */
+	svm->vmcb->control.event_inj = HV_VECTOR |
+				       SVM_EVTINJ_TYPE_EXEPT |
+				       SVM_EVTINJ_VALID;
+	svm->vmcb->control.event_inj_err = 0;
+
+	return true;
+}
+
+static void __maybe_unused sev_snp_ai_inject_irq(struct kvm_vcpu *vcpu)
+{
+	struct kvm_lapic *apic;
+	int irq, trig_mode;
+
+	apic = vcpu->arch.apic;
+
+	irq = vcpu->arch.interrupt.nr;
+	trig_mode = !!(apic_test_vector(irq, apic->regs + APIC_TMR));
+	sev_snp_ai_update_doorbell(INJECT_IRQ, vcpu, irq, trig_mode);
+
+	do {
+		irq = kvm_cpu_get_interrupt(vcpu);
+		if (irq < 0)
+			break;
+
+		trig_mode = !!(apic_test_vector(irq, apic->regs + APIC_TMR));
+		sev_snp_ai_update_doorbell(INJECT_IRQ, vcpu, irq, trig_mode);
+	} while (true);
+}
+
 bool sev_snp_inject(enum inject_type type, struct kvm_vcpu *vcpu)
 {
 	if (!sev_snp_is_rinj_active(vcpu))
@@ -5863,6 +6043,70 @@ void sev_snp_cancel_injection(struct kvm_vcpu *vcpu)
 	unmap_hvdb(vcpu, &hvdb_map);
 }
 
+void sev_snp_cancel_ai_injection(struct kvm_vcpu *vcpu)
+{
+	struct kvm_vcpu *vcpu_plane0;
+	struct vcpu_svm *svm0;
+	struct kvm_host_map hvdb_map;
+	struct hvdb *hvdb;
+	struct vcpu_svm *svm = to_svm(vcpu);
+
+	if (!sev_snp_is_ai_active(vcpu))
+		return;
+
+	/*
+	 * AI is enabled for vmpl2 so supposed that the interrupts are
+	 * only for vmpl2, so cancel only for vmpl2
+	 */
+	vcpu_plane0 = vcpu->common->vcpus[0];
+	svm0 = to_svm(vcpu_plane0);
+	if (!svm0->vmcb->control.event_inj)
+		return;
+
+	if (WARN_ONCE((svm->vmcb->control.event_inj & SVM_EVTINJ_VEC_MASK) != HV_VECTOR,
+			"Alternate Injection enabled,  %u vector not supported\n",
+			svm->vmcb->control.event_inj & SVM_EVTINJ_VEC_MASK))
+		return;
+
+	/*
+	 * Copy the information in the doorbell page into the event injection
+	 * fields to complete the cancellation flow.
+	 */
+	hvdb = map_hvdb(vcpu_plane0, &hvdb_map);
+	if (!hvdb)
+		return;
+
+	if (!hvdb->events.irqs[1].ext_word0) {
+		/* No pending events, then event_inj field should be 0 */
+		WARN_ON_ONCE(svm0->vmcb->control.event_inj);
+		goto out;
+	}
+
+	/*
+	 * When the injection is cancelled, since the #HV is set in Plane0's
+	 * control.event_inj, clear this field, and copy the information back to current
+	 * Plane so it will be reinjected, but keep the data in the doorbell.
+	 */
+	svm0->vmcb->control.event_inj = 0;
+
+	svm->vmcb->control.event_inj = SVM_EVTINJ_VALID;
+
+	if (hvdb->events.irqs[1].multi_interrupts || hvdb->events.irqs[1].si_vector)
+		svm->vmcb->control.event_inj |= SVM_EVTINJ_TYPE_INTR;
+
+	if (hvdb->events.irqs[1].nmi)
+		svm->vmcb->control.event_inj |= SVM_EVTINJ_TYPE_NMI;
+
+	if (hvdb->events.irqs[1].mce)
+		svm->vmcb->control.event_inj |= MC_VECTOR | SVM_EVTINJ_TYPE_EXEPT;
+
+	hvdb->events.no_further_signal = 0;
+	svm->sev_es.cancel_flag = true;
+
+out:
+	unmap_hvdb(vcpu_plane0, &hvdb_map);
+}
+
 /*
  * sev_snp_blocked() is for each vector - interrupt, NMI and MCE.  It is
  * checking if there is an interrupt handled by the guest when
@@ -5895,6 +6139,37 @@ bool sev_snp_blocked(enum inject_type type, struct kvm_vcpu *vcpu)
 	return blocked;
 }
 
+bool sev_snp_ai_blocked(enum inject_type type, struct kvm_vcpu *vcpu)
+{
+	struct kvm_host_map hvdb_map;
+	struct hvdb *hvdb;
+	bool blocked;
+	struct kvm_vcpu *vcpu_plane0;
+
+	vcpu_plane0 = vcpu->common->vcpus[0];
+
+	/* Indicate interrupts are blocked if doorbell page can't be mapped */
+	hvdb = map_hvdb(vcpu_plane0, &hvdb_map);
+	if (!hvdb)
+		return true;
+
+	/* Indicate NMIs, MCEs and interrupts blocked based on guest acknowledgment */
+	if (type == INJECT_NMI)
+		blocked = hvdb->events.irqs[1].nmi;
+	else if (type == INJECT_MCE)
+		blocked = hvdb->events.irqs[1].mce;
+	else
+		/* With Alternate Injection, there is a bitmap to save all of interrupts,
+		 * so no need to block any interrupt
+		 */
+		blocked = false;
+
+
+	unmap_hvdb(vcpu_plane0, &hvdb_map);
+
+	return blocked;
+}
+
 int sev_snp_max_planes(struct kvm *kvm)
 {
 	return vmpl_levels;
diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c
index 705063c7f0f0..96d31fabb292 100644
--- a/arch/x86/kvm/svm/svm.c
+++ b/arch/x86/kvm/svm/svm.c
@@ -4065,6 +4065,40 @@ static int svm_mce_allowed(struct kvm_vcpu *vcpu)
 	return 1;
 }
 
+static int __maybe_unused svm_has_interrupt(struct kvm_vcpu *vcpu)
+{
+	int ret = 0;
+	unsigned int state;
+
+	if (!sev_snp_is_ai_active(vcpu))
+		return ret;
+	/*
+	 * Mapping the #HV doorbell page via kvm_vcpu_map() can sleep (e.g.,
+	 * on page faults). In the HLT fast-path, local IRQs are disabled, so
+	 * invoking kvm_vcpu_map() there can trigger a panic. To avoid sleeping
+	 * in atomic context, assume an event is pending during the fast-path
+	 * and defer the actual check until re-entering outside the fast-path.
+	 *
+	 * A related issue occurs during normal vCPU block polling:
+	 * kvm_vcpu_block() sets the task state to TASK_INTERRUPTIBLE before
+	 * calling kvm_vcpu_check_block(). If kvm_vcpu_map() sleeps during that
+	 * check, waking up resets the task state to TASK_RUNNING, causing the
+	 * subsequent schedule() to return immediately instead of blocking.
+	 *
+	 * Therefore, avoid calling kvm_vcpu_map() from
+	 * svm_hv_doorbell_has_events() unless the task state is TASK_RUNNING,
+	 * preventing both sleeping with IRQs disabled and livelocks during
+	 * vCPU blocking.
+	 */
+	state = get_current_state();
+	if (irqs_disabled() || state != TASK_RUNNING)
+		return true;
+
+	ret = sev_snp_doorbell_has_injectable_vector(vcpu);
+
+	return ret;
+}
+
 static void svm_enable_irq_window(struct kvm_vcpu *vcpu)
 {
 	struct vcpu_svm *svm = to_svm(vcpu);
diff --git a/arch/x86/kvm/svm/svm.h b/arch/x86/kvm/svm/svm.h
index d2e397542a84..1ec0a045b1ea 100644
--- a/arch/x86/kvm/svm/svm.h
+++ b/arch/x86/kvm/svm/svm.h
@@ -288,6 +288,7 @@ struct vcpu_sev_es_state {
 
 	gpa_t hvdb_gpa;
 	u8 hvdb_ai_vector;
+	bool cancel_flag;
 };
 
 struct vcpu_svm {
@@ -1002,6 +1003,13 @@ bool sev_snp_queue_exception(struct kvm_vcpu *vcpu);
 bool sev_snp_inject(enum inject_type type, struct kvm_vcpu *vcpu);
 void sev_snp_cancel_injection(struct kvm_vcpu *vcpu);
 bool sev_snp_blocked(enum inject_type type, struct kvm_vcpu *vcpu);
+bool sev_snp_ai_queue_exception(struct kvm_vcpu *vcpu);
+bool sev_snp_doorbell_has_injectable_vector(struct kvm_vcpu *vcpu);
+bool sev_snp_ai_update_doorbell(enum inject_type type, struct kvm_vcpu *vcpu, int vector, int trig_mode);
+void sev_snp_cancel_ai_injection(struct kvm_vcpu *vcpu);
+bool sev_snp_ai_blocked(enum inject_type type, struct kvm_vcpu *vcpu);
+bool sev_snp_ai_inject(struct kvm_vcpu *vcpu);
+
 static inline bool sev_snp_is_rinj_active(struct kvm_vcpu *vcpu)
 {
 	struct kvm_sev_info_plane *sev = &to_kvm_svm_plane(vcpu->plane)->sev_info_plane;
@@ -1009,6 +1017,18 @@ static inline bool sev_snp_is_rinj_active(struct kvm_vcpu *vcpu)
 	return is_sev_snp_guest(vcpu) &&
 		(sev->vmsa_features & SVM_SEV_FEAT_RESTRICTED_INJECTION);
 };
+
+static inline bool sev_snp_is_ai_active(struct kvm_vcpu *vcpu)
+{
+	struct kvm_vcpu *vcpu_plane0 = vcpu->common->vcpus[0];
+	struct kvm_sev_info_plane *sev0 = &to_kvm_svm_plane(vcpu_plane0->plane)->sev_info_plane;
+	struct kvm_sev_info_plane *sev  = &to_kvm_svm_plane(vcpu->plane)->sev_info_plane;
+
+	return is_sev_snp_guest(vcpu) &&
+		(sev0->vmsa_features & SVM_SEV_FEAT_RESTRICTED_INJECTION) &&
+		(sev->vmsa_features & SVM_SEV_FEAT_ALTERNATE_INJECTION);
+}
+
 int sev_nr_vcpu_planes(struct kvm *kvm);
 int sev_snp_max_planes(struct kvm *kvm);
 #else
@@ -1055,6 +1075,13 @@ static inline void sev_snp_cancel_injection(struct kvm_vcpu *vcpu) {}
 static inline bool sev_snp_blocked(enum inject_type type, struct kvm_vcpu *vcpu) { return false; }
 static inline bool sev_snp_is_rinj_active(struct kvm_vcpu *vcpu) { return false; }
 static inline unsigned sev_snp_max_planes(struct kvm *kvm) { return 1; }
+static inline bool sev_snp_ai_queue_exception(struct kvm_vcpu *vcpu) { return false; }
+static inline bool sev_snp_doorbell_has_injectable_vector(struct kvm_vcpu *vcpu) { return false; }
+static inline bool sev_snp_ai_update_doorbell(enum inject_type type, struct kvm_vcpu *vcpu, int vector, int trig_mode) { return false; }
+static inline void sev_snp_cancel_ai_injection(struct kvm_vcpu *vcpu) {}
+static inline bool sev_snp_ai_blocked(enum inject_type type, struct kvm_vcpu *vcpu) { return false; }
+static inline bool sev_snp_ai_inject(struct kvm_vcpu *vcpu) { return false; }
+static inline bool sev_snp_is_ai_active(struct kvm_vcpu *vcpu) { return false; }
 #endif
 
 /* vmenter.S */
-- 
2.43.0


^ permalink raw reply	[flat|nested] 10+ messages in thread

* [RFC PATCH  4/9] KVM: SVM: Add Alternate Injection path
  2026-11-10  8:28 [RFC PATCH 0/9] Alternate Injection: Secure Interrupt Delivery for SEV-SNP Guests Melody Wang
                   ` (2 preceding siblings ...)
  2026-11-10  8:28 ` [RFC PATCH 3/9] KVM: SVM: Add Alternate Injection support Melody Wang
@ 2026-11-10  8:28 ` Melody Wang
  2026-11-10  8:28 ` [RFC PATCH 5/9] KVM: SVM: Bypass lapic for VMPL1-3 guest with Alternate Injection enabled Melody Wang
                   ` (4 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Melody Wang @ 2026-11-10  8:28 UTC (permalink / raw)
  To: Sean Christopherson, Paolo Bonzini
  Cc: LKML, kvm, Joerg Roedel, Tom Lendacky, x86, Melody Wang

Now that everything is in place, add the Alternate Injection path to
KVM's interrupt handling flow.

Signed-off-by: Melody Wang <huibo.wang@amd.com>
---
 arch/x86/include/asm/kvm-x86-ops.h |  1 +
 arch/x86/include/asm/kvm_host.h    |  1 +
 arch/x86/kvm/irq.c                 |  3 +++
 arch/x86/kvm/svm/sev.c             | 30 +++++++++++++++++++++++++-----
 arch/x86/kvm/svm/svm.c             | 17 ++++++++++++++++-
 5 files changed, 46 insertions(+), 6 deletions(-)

diff --git a/arch/x86/include/asm/kvm-x86-ops.h b/arch/x86/include/asm/kvm-x86-ops.h
index 4f96090c04c9..16da6d1678e5 100644
--- a/arch/x86/include/asm/kvm-x86-ops.h
+++ b/arch/x86/include/asm/kvm-x86-ops.h
@@ -95,6 +95,7 @@ KVM_X86_OP_OPTIONAL(sync_pir_to_irr)
 KVM_X86_OP_OPTIONAL_RET0(set_tss_addr)
 KVM_X86_OP_OPTIONAL_RET0(set_identity_map_addr)
 KVM_X86_OP_OPTIONAL_RET0(get_mt_mask)
+KVM_X86_OP_OPTIONAL_RET0(has_interrupt)
 KVM_X86_OP(load_mmu_pgd)
 KVM_X86_OP_OPTIONAL(link_external_spt)
 KVM_X86_OP_OPTIONAL(set_external_spte)
diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h
index bfa0188d372f..985d7f4d7140 100644
--- a/arch/x86/include/asm/kvm_host.h
+++ b/arch/x86/include/asm/kvm_host.h
@@ -1873,6 +1873,7 @@ struct kvm_x86_ops {
 	u32 (*get_interrupt_shadow)(struct kvm_vcpu *vcpu);
 	void (*patch_hypercall)(struct kvm_vcpu *vcpu,
 				unsigned char *hypercall_addr);
+	int (*has_interrupt)(struct kvm_vcpu *vcpu);
 	void (*inject_irq)(struct kvm_vcpu *vcpu, bool reinjected);
 	void (*inject_nmi)(struct kvm_vcpu *vcpu);
 	void (*inject_exception)(struct kvm_vcpu *vcpu);
diff --git a/arch/x86/kvm/irq.c b/arch/x86/kvm/irq.c
index 5f7e5f3e5528..c781ce916fbd 100644
--- a/arch/x86/kvm/irq.c
+++ b/arch/x86/kvm/irq.c
@@ -111,6 +111,9 @@ EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_cpu_has_injectable_intr);
  */
 int kvm_cpu_has_interrupt(struct kvm_vcpu *v)
 {
+	if (kvm_x86_call(has_interrupt)(v))
+		return 1;
+
 	if (kvm_cpu_has_extint(v))
 		return 1;
 
diff --git a/arch/x86/kvm/svm/sev.c b/arch/x86/kvm/svm/sev.c
index 78b8487627ed..543851bfb94d 100644
--- a/arch/x86/kvm/svm/sev.c
+++ b/arch/x86/kvm/svm/sev.c
@@ -5958,7 +5958,7 @@ bool sev_snp_ai_queue_exception(struct kvm_vcpu *vcpu)
 	return true;
 }
 
-static void __maybe_unused sev_snp_ai_inject_irq(struct kvm_vcpu *vcpu)
+static void sev_snp_ai_inject_irq(struct kvm_vcpu *vcpu)
 {
 	struct kvm_lapic *apic;
 	int irq, trig_mode;
@@ -5981,12 +5981,32 @@ static void __maybe_unused sev_snp_ai_inject_irq(struct kvm_vcpu *vcpu)
 
 bool sev_snp_inject(enum inject_type type, struct kvm_vcpu *vcpu)
 {
-	if (!sev_snp_is_rinj_active(vcpu))
-		return false;
+	struct vcpu_svm *svm = to_svm(vcpu);
 
-	__sev_snp_inject(type, vcpu);
+	if (!vcpu->plane_level) {
+		if (!sev_snp_is_rinj_active(vcpu)) {
+			return false;
+		} else {
+			__sev_snp_inject(type, vcpu);
+			return true;
+		}
+	} else {
+		if (sev_snp_is_ai_active(vcpu)) {
+			if (svm->sev_es.cancel_flag) {
+				svm->sev_es.cancel_flag = false;
+				return sev_snp_ai_inject(vcpu);
+			}
+			if (type == INJECT_NMI)
+				sev_snp_ai_update_doorbell(INJECT_NMI, vcpu, 0, 0);
+			if (type == INJECT_MCE)
+				sev_snp_ai_update_doorbell(INJECT_MCE, vcpu, 0, 0);
+			if (type == INJECT_IRQ)
+				sev_snp_ai_inject_irq(vcpu);
+			return sev_snp_ai_inject(vcpu);
+		}
+	}
 
-	return true;
+	return false;
 }
 
 void sev_snp_cancel_injection(struct kvm_vcpu *vcpu)
diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c
index 96d31fabb292..7c4b3d05ae88 100644
--- a/arch/x86/kvm/svm/svm.c
+++ b/arch/x86/kvm/svm/svm.c
@@ -395,6 +395,9 @@ static void svm_inject_exception(struct kvm_vcpu *vcpu)
 	if (sev_snp_queue_exception(vcpu))
 		return;
 
+	if (sev_snp_ai_queue_exception(vcpu))
+		return;
+
 	svm->vmcb->control.event_inj = ex->vector
 		| SVM_EVTINJ_VALID
 		| (ex->has_error_code ? SVM_EVTINJ_VALID_ERR : 0)
@@ -3976,6 +3979,9 @@ bool svm_nmi_blocked(struct kvm_vcpu *vcpu)
 	if (sev_snp_is_rinj_active(vcpu))
 		return sev_snp_blocked(INJECT_NMI, vcpu);
 
+	if (sev_snp_is_ai_active(vcpu))
+		return sev_snp_ai_blocked(INJECT_NMI, vcpu);
+
 	if (is_guest_mode(vcpu) && nested_exit_on_nmi(svm))
 		return false;
 
@@ -4011,6 +4017,9 @@ bool svm_interrupt_blocked(struct kvm_vcpu *vcpu)
 	if (sev_snp_is_rinj_active(vcpu))
 		return sev_snp_blocked(INJECT_IRQ, vcpu);
 
+	if (sev_snp_is_ai_active(vcpu))
+		return sev_snp_ai_blocked(INJECT_IRQ, vcpu);
+
 	if (is_guest_mode(vcpu)) {
 		/* As long as interrupts are being delivered...  */
 		if ((svm->nested.ctl.int_ctl & V_INTR_MASKING_MASK)
@@ -4054,6 +4063,9 @@ bool svm_mce_blocked(struct kvm_vcpu *vcpu)
 	if (sev_snp_is_rinj_active(vcpu))
 		return sev_snp_blocked(INJECT_MCE, vcpu);
 
+	if (sev_snp_is_ai_active(vcpu))
+		return sev_snp_ai_blocked(INJECT_MCE, vcpu);
+
 	return false;
 }
 
@@ -4065,7 +4077,7 @@ static int svm_mce_allowed(struct kvm_vcpu *vcpu)
 	return 1;
 }
 
-static int __maybe_unused svm_has_interrupt(struct kvm_vcpu *vcpu)
+static int svm_has_interrupt(struct kvm_vcpu *vcpu)
 {
 	int ret = 0;
 	unsigned int state;
@@ -4413,6 +4425,8 @@ static void svm_cancel_injection(struct kvm_vcpu *vcpu)
 
 	sev_snp_cancel_injection(vcpu);
 
+	sev_snp_cancel_ai_injection(vcpu);
+
 	control->exit_int_info = control->event_inj;
 	control->exit_int_info_err = control->event_inj_err;
 	control->event_inj = 0;
@@ -5431,6 +5445,7 @@ struct kvm_x86_ops svm_x86_ops __initdata = {
 	.set_interrupt_shadow = svm_set_interrupt_shadow,
 	.get_interrupt_shadow = svm_get_interrupt_shadow,
 	.patch_hypercall = svm_patch_hypercall,
+	.has_interrupt = svm_has_interrupt,
 	.inject_irq = svm_inject_irq,
 	.inject_nmi = svm_inject_nmi,
 	.is_vnmi_pending = svm_is_vnmi_pending,
-- 
2.43.0


^ permalink raw reply	[flat|nested] 10+ messages in thread

* [RFC PATCH  5/9] KVM: SVM: Bypass lapic for VMPL1-3 guest with Alternate Injection enabled
  2026-11-10  8:28 [RFC PATCH 0/9] Alternate Injection: Secure Interrupt Delivery for SEV-SNP Guests Melody Wang
                   ` (3 preceding siblings ...)
  2026-11-10  8:28 ` [RFC PATCH 4/9] KVM: SVM: Add Alternate Injection path Melody Wang
@ 2026-11-10  8:28 ` Melody Wang
  2026-11-10  8:28 ` [RFC PATCH 6/9] KVM: SVM: Inject #HV into VMPL0 " Melody Wang
                   ` (3 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Melody Wang @ 2026-11-10  8:28 UTC (permalink / raw)
  To: Sean Christopherson, Paolo Bonzini
  Cc: LKML, kvm, Joerg Roedel, Tom Lendacky, x86, Melody Wang

The interrupts for VMPL1-3 are handled in the SVSM by its APIC emulation
so bypass the KVM's APIC layer and queue them into the doorbell page.

Signed-off-by: Melody Wang <huibo.wang@amd.com>
---
 arch/x86/include/asm/kvm-x86-ops.h | 1 +
 arch/x86/include/asm/kvm_host.h    | 1 +
 arch/x86/kvm/lapic.c               | 7 +++++--
 arch/x86/kvm/svm/svm.c             | 6 ++++++
 4 files changed, 13 insertions(+), 2 deletions(-)

diff --git a/arch/x86/include/asm/kvm-x86-ops.h b/arch/x86/include/asm/kvm-x86-ops.h
index 16da6d1678e5..dfd38f9408cc 100644
--- a/arch/x86/include/asm/kvm-x86-ops.h
+++ b/arch/x86/include/asm/kvm-x86-ops.h
@@ -96,6 +96,7 @@ KVM_X86_OP_OPTIONAL_RET0(set_tss_addr)
 KVM_X86_OP_OPTIONAL_RET0(set_identity_map_addr)
 KVM_X86_OP_OPTIONAL_RET0(get_mt_mask)
 KVM_X86_OP_OPTIONAL_RET0(has_interrupt)
+KVM_X86_OP_OPTIONAL_RET0(is_bypass_apic_active)
 KVM_X86_OP(load_mmu_pgd)
 KVM_X86_OP_OPTIONAL(link_external_spt)
 KVM_X86_OP_OPTIONAL(set_external_spte)
diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h
index 985d7f4d7140..60c102db1003 100644
--- a/arch/x86/include/asm/kvm_host.h
+++ b/arch/x86/include/asm/kvm_host.h
@@ -1874,6 +1874,7 @@ struct kvm_x86_ops {
 	void (*patch_hypercall)(struct kvm_vcpu *vcpu,
 				unsigned char *hypercall_addr);
 	int (*has_interrupt)(struct kvm_vcpu *vcpu);
+	bool (*is_bypass_apic_active)(struct kvm_vcpu *vcpu);
 	void (*inject_irq)(struct kvm_vcpu *vcpu, bool reinjected);
 	void (*inject_nmi)(struct kvm_vcpu *vcpu);
 	void (*inject_exception)(struct kvm_vcpu *vcpu);
diff --git a/arch/x86/kvm/lapic.c b/arch/x86/kvm/lapic.c
index 9ebae1052f64..952b6cc92ede 100644
--- a/arch/x86/kvm/lapic.c
+++ b/arch/x86/kvm/lapic.c
@@ -3189,8 +3189,11 @@ void kvm_apic_ack_interrupt(struct kvm_vcpu *vcpu, int vector)
 		 * a concurrent interrupt injection, but that would have
 		 * triggered KVM_REQ_EVENT already.
 		 */
-		apic_set_isr(vector, apic);
-		__apic_update_ppr(apic, &ppr);
+		if (!kvm_x86_call(is_bypass_apic_active)(vcpu)) {
+			apic_set_isr(vector, apic);
+			__apic_update_ppr(apic, &ppr);
+		}
+
 	}
 
 }
diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c
index 7c4b3d05ae88..756b11bebb5a 100644
--- a/arch/x86/kvm/svm/svm.c
+++ b/arch/x86/kvm/svm/svm.c
@@ -4111,6 +4111,11 @@ static int svm_has_interrupt(struct kvm_vcpu *vcpu)
 	return ret;
 }
 
+static bool svm_is_bypass_apic_active(struct kvm_vcpu *vcpu)
+{
+	return sev_snp_is_ai_active(vcpu);
+}
+
 static void svm_enable_irq_window(struct kvm_vcpu *vcpu)
 {
 	struct vcpu_svm *svm = to_svm(vcpu);
@@ -5446,6 +5451,7 @@ struct kvm_x86_ops svm_x86_ops __initdata = {
 	.get_interrupt_shadow = svm_get_interrupt_shadow,
 	.patch_hypercall = svm_patch_hypercall,
 	.has_interrupt = svm_has_interrupt,
+	.is_bypass_apic_active = svm_is_bypass_apic_active,
 	.inject_irq = svm_inject_irq,
 	.inject_nmi = svm_inject_nmi,
 	.is_vnmi_pending = svm_is_vnmi_pending,
-- 
2.43.0


^ permalink raw reply	[flat|nested] 10+ messages in thread

* [RFC PATCH  6/9] KVM: SVM: Inject #HV into VMPL0 with Alternate Injection enabled
  2026-11-10  8:28 [RFC PATCH 0/9] Alternate Injection: Secure Interrupt Delivery for SEV-SNP Guests Melody Wang
                   ` (4 preceding siblings ...)
  2026-11-10  8:28 ` [RFC PATCH 5/9] KVM: SVM: Bypass lapic for VMPL1-3 guest with Alternate Injection enabled Melody Wang
@ 2026-11-10  8:28 ` Melody Wang
  2026-11-10  8:28 ` [RFC PATCH 7/9] KVM: SVM: Notify the SVSM an intercept happened during interrupt delivery Melody Wang
                   ` (2 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Melody Wang @ 2026-11-10  8:28 UTC (permalink / raw)
  To: Sean Christopherson, Paolo Bonzini
  Cc: LKML, kvm, Joerg Roedel, Tom Lendacky, x86, Melody Wang

With Alternate Injection enabled, interrupts for VMPL1-3 are saved in
the doorbell page, and by using #HV injected into VMPL0. Then, the SVSM
at VMPL0 is run to inject them into the target VMPL of the guest.

Signed-off-by: Melody Wang <huibo.wang@amd.com>
---
 arch/x86/kvm/svm/sev.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/arch/x86/kvm/svm/sev.c b/arch/x86/kvm/svm/sev.c
index 543851bfb94d..40f55e7a78e3 100644
--- a/arch/x86/kvm/svm/sev.c
+++ b/arch/x86/kvm/svm/sev.c
@@ -5836,9 +5836,11 @@ bool sev_snp_ai_inject(struct kvm_vcpu *vcpu)
 	struct kvm_host_map hvdb_map;
 	struct hvdb *hvdb;
 	struct vcpu_svm *svm0;
+	struct vcpu_svm *svm;
 
 	vcpu_plane0 = vcpu->common->vcpus[0];
 	svm0 = to_svm(vcpu_plane0);
+	svm = to_svm(vcpu);
 
 	hvdb = map_hvdb(vcpu_plane0, &hvdb_map);
 	if (!hvdb)
@@ -5848,7 +5850,7 @@ bool sev_snp_ai_inject(struct kvm_vcpu *vcpu)
 		goto unmap;
 
 	prepare_hv_injection(svm0, hvdb);
-
+	__sev_snp_run_vmpl(svm, 0);
 unmap:
 	unmap_hvdb(vcpu_plane0, &hvdb_map);
 
-- 
2.43.0


^ permalink raw reply	[flat|nested] 10+ messages in thread

* [RFC PATCH  7/9] KVM: SVM: Notify the SVSM an intercept happened during interrupt delivery
  2026-11-10  8:28 [RFC PATCH 0/9] Alternate Injection: Secure Interrupt Delivery for SEV-SNP Guests Melody Wang
                   ` (5 preceding siblings ...)
  2026-11-10  8:28 ` [RFC PATCH 6/9] KVM: SVM: Inject #HV into VMPL0 " Melody Wang
@ 2026-11-10  8:28 ` Melody Wang
  2026-11-10  8:28 ` [RFC PATCH 8/9] KVM: SVM: Clear AVIC Enable when Alternate Injection is enabled Melody Wang
  2026-11-10  8:28 ` [RFC PATCH 9/9] KVM: SVM: Enable Alternate Injection for an SEV-SNP guest Melody Wang
  8 siblings, 0 replies; 10+ messages in thread
From: Melody Wang @ 2026-11-10  8:28 UTC (permalink / raw)
  To: Sean Christopherson, Paolo Bonzini
  Cc: LKML, kvm, Joerg Roedel, Tom Lendacky, x86, Melody Wang

When the guest VMEXITs with EXITINTINFO.V=1 (valid bit) is set, that
means an intercept happened during the interrupt delivery.

When the hypervisor sees the valid bit, it should queue the interrupt so
it does not get lost.

However, with Alternate Injection, the hypervisor can not queue the
interrupt directly but the SVSM needs to do that. Notify the SVSM when
the intercept happened.

Signed-off-by: Melody Wang <huibo.wang@amd.com>
---
 arch/x86/kvm/svm/sev.c |  2 +-
 arch/x86/kvm/svm/svm.c | 11 ++++++++++-
 arch/x86/kvm/svm/svm.h |  2 ++
 3 files changed, 13 insertions(+), 2 deletions(-)

diff --git a/arch/x86/kvm/svm/sev.c b/arch/x86/kvm/svm/sev.c
index 40f55e7a78e3..019412173062 100644
--- a/arch/x86/kvm/svm/sev.c
+++ b/arch/x86/kvm/svm/sev.c
@@ -4618,7 +4618,7 @@ static void sev_get_apic_ids(struct vcpu_svm *svm)
 	kvfree(desc);
 }
 
-static int __sev_snp_run_vmpl(struct vcpu_svm *svm, unsigned int vmpl)
+int __sev_snp_run_vmpl(struct vcpu_svm *svm, unsigned int vmpl)
 {
 	struct kvm_vcpu *vcpu = &svm->vcpu;
 	struct kvm_vcpu *target = vcpu->common->vcpus[vmpl];
diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c
index 756b11bebb5a..6d3d9debe5e8 100644
--- a/arch/x86/kvm/svm/svm.c
+++ b/arch/x86/kvm/svm/svm.c
@@ -3663,6 +3663,7 @@ static int svm_handle_exit(struct kvm_vcpu *vcpu, fastpath_t exit_fastpath)
 {
 	struct vcpu_svm *svm = to_svm(vcpu);
 	struct kvm_run *kvm_run = vcpu->run;
+	int ret;
 
 	/* SEV-ES guests must use the CR write traps to track CR registers. */
 	if (!is_sev_es_guest(vcpu)) {
@@ -3698,7 +3699,15 @@ static int svm_handle_exit(struct kvm_vcpu *vcpu, fastpath_t exit_fastpath)
 	if (exit_fastpath != EXIT_FASTPATH_NONE)
 		return 1;
 
-	return svm_invoke_exit_handler(vcpu, svm->vmcb->control.exit_code);
+	ret = svm_invoke_exit_handler(vcpu, svm->vmcb->control.exit_code);
+
+	if (sev_snp_is_ai_active(vcpu)) {
+		if (svm->vmcb->control.exit_int_info & SVM_EVTINJ_VALID)
+			__sev_snp_run_vmpl(svm, SVM_SEV_VMPL0);
+	}
+
+	return ret;
+
 }
 
 static void svm_set_nested_run_soft_int_state(struct kvm_vcpu *vcpu)
diff --git a/arch/x86/kvm/svm/svm.h b/arch/x86/kvm/svm/svm.h
index 1ec0a045b1ea..d5f42fa31565 100644
--- a/arch/x86/kvm/svm/svm.h
+++ b/arch/x86/kvm/svm/svm.h
@@ -1009,6 +1009,7 @@ bool sev_snp_ai_update_doorbell(enum inject_type type, struct kvm_vcpu *vcpu, in
 void sev_snp_cancel_ai_injection(struct kvm_vcpu *vcpu);
 bool sev_snp_ai_blocked(enum inject_type type, struct kvm_vcpu *vcpu);
 bool sev_snp_ai_inject(struct kvm_vcpu *vcpu);
+int __sev_snp_run_vmpl(struct vcpu_svm *svm, unsigned int vmpl);
 
 static inline bool sev_snp_is_rinj_active(struct kvm_vcpu *vcpu)
 {
@@ -1082,6 +1083,7 @@ static inline void sev_snp_cancel_ai_injection(struct kvm_vcpu *vcpu) {}
 static inline bool sev_snp_ai_blocked(enum inject_type type, struct kvm_vcpu *vcpu) { return false; }
 static inline bool sev_snp_ai_inject(struct kvm_vcpu *vcpu) { return false; }
 static inline bool sev_snp_is_ai_active(struct kvm_vcpu *vcpu) { return false; }
+static inline int __sev_snp_run_vmpl(struct vcpu_svm *svm, unsigned int vmpl) { return -EINVAL; };
 #endif
 
 /* vmenter.S */
-- 
2.43.0


^ permalink raw reply	[flat|nested] 10+ messages in thread

* [RFC PATCH  8/9] KVM: SVM: Clear AVIC Enable when Alternate Injection is enabled
  2026-11-10  8:28 [RFC PATCH 0/9] Alternate Injection: Secure Interrupt Delivery for SEV-SNP Guests Melody Wang
                   ` (6 preceding siblings ...)
  2026-11-10  8:28 ` [RFC PATCH 7/9] KVM: SVM: Notify the SVSM an intercept happened during interrupt delivery Melody Wang
@ 2026-11-10  8:28 ` Melody Wang
  2026-11-10  8:28 ` [RFC PATCH 9/9] KVM: SVM: Enable Alternate Injection for an SEV-SNP guest Melody Wang
  8 siblings, 0 replies; 10+ messages in thread
From: Melody Wang @ 2026-11-10  8:28 UTC (permalink / raw)
  To: Sean Christopherson, Paolo Bonzini
  Cc: LKML, kvm, Joerg Roedel, Tom Lendacky, x86, Melody Wang

VMRUN fails with VMEXIT_INVALID if the AVIC Enable bit is set while the
guest uses Alternate Injection. Historically, the AVIC Enable bit wasn’t
set during AVIC VMCB initialization under SEV because AVIC is inhibited.

Commit 3989a6d036c8 ("KVM: SVM: Initialize AVIC VMCB fields if AVIC is
enabled with in-kernel APIC") changed this by populating AVIC VMCB
fields even when AVIC is inhibited under SEV. As a result, the AVIC
Enable bit may remain set and trigger VMEXIT_INVALID with Alternate
Injection.

Rather than altering the AVIC VMCB initialization, explicitly clear the
AVIC Enable bit only when Alternate Injection is enabled.

Signed-off-by: Melody Wang <huibo.wang@amd.com>
---
 arch/x86/kvm/svm/sev.c | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/arch/x86/kvm/svm/sev.c b/arch/x86/kvm/svm/sev.c
index 019412173062..7846d4ebb278 100644
--- a/arch/x86/kvm/svm/sev.c
+++ b/arch/x86/kvm/svm/sev.c
@@ -5089,9 +5089,15 @@ static void sev_snp_init_vmcb(struct vcpu_svm *svm)
 {
 	struct kvm_sev_info_plane *sev_plane = &to_kvm_svm_plane(svm->vcpu.plane)->sev_info_plane;
 
-	/* V_NMI is not supported when Restricted Injection is enabled */
-	if (sev_plane->vmsa_features & SVM_SEV_FEAT_RESTRICTED_INJECTION)
+	/*
+	 * V_NMI and AVIC are not supported when Restricted Injection or Alternate
+	 * Injection is enabled.
+	 */
+	if (sev_plane->vmsa_features & (SVM_SEV_FEAT_RESTRICTED_INJECTION |
+					SVM_SEV_FEAT_ALTERNATE_INJECTION)) {
 		svm->vmcb->control.int_ctl &= ~V_NMI_ENABLE_MASK;
+		svm->vmcb->control.int_ctl &= ~AVIC_ENABLE_MASK;
+	}
 }
 
 static void sev_es_init_vmcb(struct vcpu_svm *svm, bool init_event)
-- 
2.43.0


^ permalink raw reply	[flat|nested] 10+ messages in thread

* [RFC PATCH  9/9] KVM: SVM: Enable Alternate Injection for an SEV-SNP guest
  2026-11-10  8:28 [RFC PATCH 0/9] Alternate Injection: Secure Interrupt Delivery for SEV-SNP Guests Melody Wang
                   ` (7 preceding siblings ...)
  2026-11-10  8:28 ` [RFC PATCH 8/9] KVM: SVM: Clear AVIC Enable when Alternate Injection is enabled Melody Wang
@ 2026-11-10  8:28 ` Melody Wang
  8 siblings, 0 replies; 10+ messages in thread
From: Melody Wang @ 2026-11-10  8:28 UTC (permalink / raw)
  To: Sean Christopherson, Paolo Bonzini
  Cc: LKML, kvm, Joerg Roedel, Tom Lendacky, x86, Melody Wang

When the SVSM detects that the hypervisor supports Alternate Injection,
it can configure Alternate Injection and create APs with it enabled in
sev_features.

Add Alternate Injection support to the hypervisor advertised features

Signed-off-by: Melody Wang <huibo.wang@amd.com>
---
 arch/x86/include/asm/cpufeatures.h |  1 +
 arch/x86/include/asm/sev-common.h  |  1 +
 arch/x86/kvm/svm/sev.c             | 12 +++++++++++-
 3 files changed, 13 insertions(+), 1 deletion(-)

diff --git a/arch/x86/include/asm/cpufeatures.h b/arch/x86/include/asm/cpufeatures.h
index 41af7bd2473c..b2746a4d926a 100644
--- a/arch/x86/include/asm/cpufeatures.h
+++ b/arch/x86/include/asm/cpufeatures.h
@@ -454,6 +454,7 @@
 #define X86_FEATURE_V_TSC_AUX		(19*32+ 9) /* Virtual TSC_AUX */
 #define X86_FEATURE_SME_COHERENT	(19*32+10) /* hardware-enforced cache coherency */
 #define X86_FEATURE_RESTRICTED_INJECTION (19*32+12) /* Restricted Injection */
+#define X86_FEATURE_ALTERNATE_INJECTION (19*32+13) /* Alternate Injection */
 #define X86_FEATURE_DEBUG_SWAP		(19*32+14) /* "debug_swap" SEV-ES full debug state swap support */
 #define X86_FEATURE_RMPREAD		(19*32+21) /* RMPREAD instruction */
 #define X86_FEATURE_SEGMENTED_RMP	(19*32+23) /* Segmented RMP support */
diff --git a/arch/x86/include/asm/sev-common.h b/arch/x86/include/asm/sev-common.h
index a09cf5690aba..62f0c3aa7549 100644
--- a/arch/x86/include/asm/sev-common.h
+++ b/arch/x86/include/asm/sev-common.h
@@ -145,6 +145,7 @@ enum psc_op {
 #define GHCB_HV_FT_SNP_RINJ		(BIT_ULL(2) | GHCB_HV_FT_SNP_AP_CREATION)
 #define GHCB_HV_FT_APIC_ID_LIST		BIT_ULL(4)
 #define GHCB_HV_FT_SNP_MULTI_VMPL	BIT_ULL(5)
+#define GHCB_HV_FT_SNP_ALT_INJ		BIT_ULL(9)
 
 /*
  * SNP Page State Change NAE event
diff --git a/arch/x86/kvm/svm/sev.c b/arch/x86/kvm/svm/sev.c
index 7846d4ebb278..fd0212abc3af 100644
--- a/arch/x86/kvm/svm/sev.c
+++ b/arch/x86/kvm/svm/sev.c
@@ -44,6 +44,7 @@
 				 GHCB_HV_FT_SNP_AP_CREATION	| \
 				 GHCB_HV_FT_SNP_RINJ		| \
 				 GHCB_HV_FT_APIC_ID_LIST	| \
+				 GHCB_HV_FT_SNP_ALT_INJ		| \
 				 GHCB_HV_FT_SNP_MULTI_VMPL)
 
 /*
@@ -72,6 +73,10 @@ module_param_named(sev_snp, sev_snp_enabled, bool, 0444);
 static bool sev_snp_restricted_injection_enabled = true;
 module_param_named(restricted_injection, sev_snp_restricted_injection_enabled, bool, 0444);
 
+/* enable/disable SEV-SNP Alternate Injection support */
+static bool sev_snp_alternate_injection_enabled = true;
+module_param_named(alternate_injection, sev_snp_alternate_injection_enabled, bool, 0444);
+
 static unsigned int __ro_after_init nr_ciphertext_hiding_asids;
 module_param_named(ciphertext_hiding_asids, nr_ciphertext_hiding_asids, uint, 0444);
 
@@ -3242,9 +3247,14 @@ void __init sev_hardware_setup(void)
 
 	if (!sev_snp_enabled || !cpu_feature_enabled(X86_FEATURE_RESTRICTED_INJECTION))
 		sev_snp_restricted_injection_enabled = false;
+	if (!sev_snp_enabled || !cpu_feature_enabled(X86_FEATURE_ALTERNATE_INJECTION))
+		sev_snp_alternate_injection_enabled = false;
 
-	if (sev_snp_restricted_injection_enabled)
+	if (sev_snp_restricted_injection_enabled) {
 		sev_supported_vmsa_features |= SVM_SEV_FEAT_RESTRICTED_INJECTION;
+		if (sev_snp_alternate_injection_enabled)
+			sev_supported_vmsa_features |= SVM_SEV_FEAT_ALTERNATE_INJECTION;
+	}
 }
 
 void sev_hardware_unsetup(void)
-- 
2.43.0


^ permalink raw reply	[flat|nested] 10+ messages in thread

end of thread, other threads:[~2026-11-10  8:31 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-10  8:28 [RFC PATCH 0/9] Alternate Injection: Secure Interrupt Delivery for SEV-SNP Guests Melody Wang
2026-11-10  8:28 ` [RFC PATCH 1/9] x86/sev: Define extended #HV doorbell page structure Melody Wang
2026-11-10  8:28 ` [RFC PATCH 2/9] KVM: SVM: Add support for Alternate Injection for the SEV-SNP #HV doorbell page NAE event Melody Wang
2026-11-10  8:28 ` [RFC PATCH 3/9] KVM: SVM: Add Alternate Injection support Melody Wang
2026-11-10  8:28 ` [RFC PATCH 4/9] KVM: SVM: Add Alternate Injection path Melody Wang
2026-11-10  8:28 ` [RFC PATCH 5/9] KVM: SVM: Bypass lapic for VMPL1-3 guest with Alternate Injection enabled Melody Wang
2026-11-10  8:28 ` [RFC PATCH 6/9] KVM: SVM: Inject #HV into VMPL0 " Melody Wang
2026-11-10  8:28 ` [RFC PATCH 7/9] KVM: SVM: Notify the SVSM an intercept happened during interrupt delivery Melody Wang
2026-11-10  8:28 ` [RFC PATCH 8/9] KVM: SVM: Clear AVIC Enable when Alternate Injection is enabled Melody Wang
2026-11-10  8:28 ` [RFC PATCH 9/9] KVM: SVM: Enable Alternate Injection for an SEV-SNP guest Melody Wang

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®