From: Melody Wang <[email protected]>

When Restricted Injection is active, only #HV exceptions can be injected into
the SEV-SNP guest. Detect that, and then follow the #HV doorbell communication
from the GHCB specification to inject the interrupt or exception.

Co-developed-by: Thomas Lendacky <[email protected]>
Signed-off-by: Thomas Lendacky <[email protected]>
Signed-off-by: Melody Wang <[email protected]>
Signed-off-by: Joerg Roedel <[email protected]>
---
 arch/x86/kvm/svm/sev.c | 164 +++++++++++++++++++++++++++++++++++++++++
 arch/x86/kvm/svm/svm.c |  14 +++-
 arch/x86/kvm/svm/svm.h |  21 ++++++
 3 files changed, 197 insertions(+), 2 deletions(-)

diff --git a/arch/x86/kvm/svm/sev.c b/arch/x86/kvm/svm/sev.c
index b9ad1169cb2c..f2f40f81ba86 100644
--- a/arch/x86/kvm/svm/sev.c
+++ b/arch/x86/kvm/svm/sev.c
@@ -5380,3 +5380,167 @@ void sev_free_decrypted_vmsa(struct kvm_vcpu *vcpu, 
struct vmcb_save_area *vmsa)
 
        free_page((unsigned long)vmsa);
 }
+
+static void prepare_hv_injection(struct vcpu_svm *svm, struct hvdb *hvdb)
+{
+       if (hvdb->events.no_further_signal)
+               return;
+
+       svm->vmcb->control.event_inj = HV_VECTOR |
+                                      SVM_EVTINJ_TYPE_EXEPT |
+                                      SVM_EVTINJ_VALID;
+       svm->vmcb->control.event_inj_err = 0;
+
+       hvdb->events.no_further_signal = 1;
+}
+
+static void unmap_hvdb(struct kvm_vcpu *vcpu, struct kvm_host_map *map)
+{
+       kvm_vcpu_unmap(vcpu, map);
+}
+
+static struct hvdb *map_hvdb(struct kvm_vcpu *vcpu, struct kvm_host_map *map)
+{
+       struct vcpu_svm *svm = to_svm(vcpu);
+
+       if (!VALID_PAGE(svm->sev_es.hvdb_gpa))
+               return NULL;
+
+       if (kvm_vcpu_map(vcpu, gpa_to_gfn(svm->sev_es.hvdb_gpa), map)) {
+               vcpu_unimpl(vcpu, "snp: error mapping #HV doorbell page [%#llx] 
from guest\n",
+                           svm->sev_es.hvdb_gpa);
+
+               return NULL;
+       }
+
+       return map->hva;
+}
+
+static void __sev_snp_inject(enum inject_type type, struct kvm_vcpu *vcpu)
+{
+       struct vcpu_svm *svm = to_svm(vcpu);
+       struct kvm_host_map hvdb_map;
+       struct hvdb *hvdb;
+
+       hvdb = map_hvdb(vcpu, &hvdb_map);
+       if (!hvdb) {
+               WARN_ONCE(1, "Restricted Injection enabled, hvdb page mapping 
failed\n");
+               return;
+       }
+
+       hvdb->events.vector = vcpu->arch.interrupt.nr;
+
+       prepare_hv_injection(svm, hvdb);
+
+       unmap_hvdb(vcpu, &hvdb_map);
+}
+
+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;
+
+       /*
+        * 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 vector %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;
+}
+
+bool sev_snp_inject(enum inject_type type, struct kvm_vcpu *vcpu)
+{
+       if (!sev_snp_is_rinj_active(vcpu))
+               return false;
+
+       __sev_snp_inject(type, vcpu);
+
+       return true;
+}
+
+void sev_snp_cancel_injection(struct kvm_vcpu *vcpu)
+{
+       struct vcpu_svm *svm = to_svm(vcpu);
+       struct kvm_host_map hvdb_map;
+       struct hvdb *hvdb;
+
+       if (!sev_snp_is_rinj_active(vcpu))
+               return;
+
+       if (!svm->vmcb->control.event_inj)
+               return;
+
+       if (WARN_ONCE((svm->vmcb->control.event_inj & SVM_EVTINJ_VEC_MASK) != 
HV_VECTOR,
+                       "Restricted 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, &hvdb_map);
+       if (!hvdb)
+               return;
+
+       if (!hvdb->events.pending_events) {
+               /* No pending events, then event_inj field should be 0 */
+               WARN_ON_ONCE(svm->vmcb->control.event_inj);
+               goto out;
+       }
+
+       /* Copy info back into event_inj field (replaces #HV) */
+       svm->vmcb->control.event_inj = SVM_EVTINJ_VALID;
+
+       if (hvdb->events.vector)
+               svm->vmcb->control.event_inj |= hvdb->events.vector |
+                                               SVM_EVTINJ_TYPE_INTR;
+
+       hvdb->events.pending_events = 0;
+
+out:
+       unmap_hvdb(vcpu, &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
+ * another interrupt is pending. So hvdb->events.vector will be used for
+ * checking while no_further_signal is signaling to the guest that a #HV
+ * is presented by the hypervisor. So no_further_signal is checked when
+ * a #HV needs to be presented to the guest.
+ */
+bool sev_snp_blocked(enum inject_type type, struct kvm_vcpu *vcpu)
+{
+       struct kvm_host_map hvdb_map;
+       struct hvdb *hvdb;
+       bool blocked;
+
+       /* Indicate interrupts are blocked if doorbell page can't be mapped */
+       hvdb = map_hvdb(vcpu, &hvdb_map);
+       if (!hvdb)
+               return true;
+
+       /* Indicate interrupts blocked based on guest acknowledgment */
+       blocked = !!hvdb->events.vector;
+
+       unmap_hvdb(vcpu, &hvdb_map);
+
+       return blocked;
+}
diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c
index 7981e7583384..7253936c460c 100644
--- a/arch/x86/kvm/svm/svm.c
+++ b/arch/x86/kvm/svm/svm.c
@@ -392,6 +392,9 @@ static void svm_inject_exception(struct kvm_vcpu *vcpu)
            svm_update_soft_interrupt_rip(vcpu, ex->vector))
                return;
 
+       if (sev_snp_queue_exception(vcpu))
+               return;
+
        svm->vmcb->control.event_inj = ex->vector
                | SVM_EVTINJ_VALID
                | (ex->has_error_code ? SVM_EVTINJ_VALID_ERR : 0)
@@ -3818,9 +3821,11 @@ static void svm_inject_irq(struct kvm_vcpu *vcpu, bool 
reinjected)
        }
 
        trace_kvm_inj_virq(intr->nr, intr->soft, reinjected);
-       ++vcpu->stat.irq_injections;
 
-       svm->vmcb->control.event_inj = intr->nr | SVM_EVTINJ_VALID | type;
+       if (!sev_snp_inject(INJECT_IRQ, vcpu))
+               svm->vmcb->control.event_inj = intr->nr | SVM_EVTINJ_VALID | 
type;
+
+       ++vcpu->stat.irq_injections;
 }
 
 static void svm_fixup_nested_rips(struct kvm_vcpu *vcpu)
@@ -3995,6 +4000,9 @@ bool svm_interrupt_blocked(struct kvm_vcpu *vcpu)
        if (!gif_set(svm))
                return true;
 
+       if (sev_snp_is_rinj_active(vcpu))
+               return sev_snp_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)
@@ -4345,6 +4353,8 @@ static void svm_cancel_injection(struct kvm_vcpu *vcpu)
        struct vcpu_svm *svm = to_svm(vcpu);
        struct vmcb_control_area *control = &svm->vmcb->control;
 
+       sev_snp_cancel_injection(vcpu);
+
        control->exit_int_info = control->event_inj;
        control->exit_int_info_err = control->event_inj_err;
        control->event_inj = 0;
diff --git a/arch/x86/kvm/svm/svm.h b/arch/x86/kvm/svm/svm.h
index fb956c37c941..a22ad5de03ea 100644
--- a/arch/x86/kvm/svm/svm.h
+++ b/arch/x86/kvm/svm/svm.h
@@ -55,6 +55,10 @@ extern int tsc_aux_uret_slot __ro_after_init;
 
 extern struct kvm_x86_ops svm_x86_ops __initdata;
 
+enum inject_type {
+       INJECT_IRQ,
+};
+
 /*
  * Clean bits in VMCB.
  * VMCB_ALL_CLEAN_MASK might also need to
@@ -971,6 +975,17 @@ void sev_gmem_invalidate(kvm_pfn_t start, kvm_pfn_t end);
 int sev_gmem_max_mapping_level(struct kvm *kvm, kvm_pfn_t pfn, bool 
is_private);
 struct vmcb_save_area *sev_decrypt_vmsa(struct kvm_vcpu *vcpu);
 void sev_free_decrypted_vmsa(struct kvm_vcpu *vcpu, struct vmcb_save_area 
*vmsa);
+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);
+static inline bool sev_snp_is_rinj_active(struct kvm_vcpu *vcpu)
+{
+       struct kvm_sev_info *sev = &to_kvm_svm(vcpu->kvm)->sev_info;
+
+       return is_sev_snp_guest(vcpu) &&
+               (sev->vmsa_features & SVM_SEV_FEAT_RESTRICTED_INJECTION);
+};
 #else
 static inline struct page *snp_safe_alloc_page_node(int node, gfp_t gfp)
 {
@@ -1008,6 +1023,12 @@ static inline struct vmcb_save_area 
*sev_decrypt_vmsa(struct kvm_vcpu *vcpu)
        return NULL;
 }
 static inline void sev_free_decrypted_vmsa(struct kvm_vcpu *vcpu, struct 
vmcb_save_area *vmsa) {}
+
+static inline bool sev_snp_queue_exception(struct kvm_vcpu *vcpu) { return 
false; }
+static inline bool sev_snp_inject(enum inject_type type, struct kvm_vcpu 
*vcpu) { return false; }
+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; }
 #endif
 
 /* vmenter.S */
-- 
2.53.0


Reply via email to