x86/sev-es: Handle NMI State
authorJoerg Roedel <jroedel@suse.de>
Mon, 7 Sep 2020 13:16:11 +0000 (15:16 +0200)
committerBorislav Petkov <bp@suse.de>
Wed, 9 Sep 2020 16:02:35 +0000 (18:02 +0200)
When running under SEV-ES, the kernel has to tell the hypervisor when to
open the NMI window again after an NMI was injected. This is done with
an NMI-complete message to the hypervisor.

Add code to the kernel's NMI handler to send this message right at the
beginning of do_nmi(). This always allows nesting NMIs.

 [ bp: Mark __sev_es_nmi_complete() noinstr:
   vmlinux.o: warning: objtool: exc_nmi()+0x17: call to __sev_es_nmi_complete()
leaves .noinstr.text section
   While at it, use __pa_nodebug() for the same reason due to
   CONFIG_DEBUG_VIRTUAL=y:
   vmlinux.o: warning: objtool: __sev_es_nmi_complete()+0xd9: call to __phys_addr()
    leaves .noinstr.text section ]

Signed-off-by: Joerg Roedel <jroedel@suse.de>
Signed-off-by: Borislav Petkov <bp@suse.de>
Link: https://lkml.kernel.org/r/20200907131613.12703-71-joro@8bytes.org
arch/x86/include/asm/sev-es.h
arch/x86/include/uapi/asm/svm.h
arch/x86/kernel/nmi.c
arch/x86/kernel/sev-es.c

index db88e1c3442d94b1e7471f71f5cf86691e506456..e919f09ae33c5d179ee2d7247bbe307c4636b1ec 100644 (file)
@@ -96,10 +96,17 @@ static __always_inline void sev_es_ist_exit(void)
                __sev_es_ist_exit();
 }
 extern int sev_es_setup_ap_jump_table(struct real_mode_header *rmh);
+extern void __sev_es_nmi_complete(void);
+static __always_inline void sev_es_nmi_complete(void)
+{
+       if (static_branch_unlikely(&sev_es_enable_key))
+               __sev_es_nmi_complete();
+}
 #else
 static inline void sev_es_ist_enter(struct pt_regs *regs) { }
 static inline void sev_es_ist_exit(void) { }
 static inline int sev_es_setup_ap_jump_table(struct real_mode_header *rmh) { return 0; }
+static inline void sev_es_nmi_complete(void) { }
 #endif
 
 #endif
index c1dcf3e114e4152da4a6ff03b12fc26cfd8b53a9..a7a3403645e59c106516c0ece61abd4b845f4434 100644 (file)
@@ -84,6 +84,7 @@
 /* SEV-ES software-defined VMGEXIT events */
 #define SVM_VMGEXIT_MMIO_READ                  0x80000001
 #define SVM_VMGEXIT_MMIO_WRITE                 0x80000002
+#define SVM_VMGEXIT_NMI_COMPLETE               0x80000003
 #define SVM_VMGEXIT_AP_HLT_LOOP                        0x80000004
 #define SVM_VMGEXIT_AP_JUMP_TABLE              0x80000005
 #define SVM_VMGEXIT_SET_AP_JUMP_TABLE          0
index 4c89c4d3cb822f777f69a8440b628b492c096bf4..56b64d779856eaaba8573832e585988d56314bbb 100644 (file)
@@ -478,6 +478,12 @@ DEFINE_IDTENTRY_RAW(exc_nmi)
 {
        bool irq_state;
 
+       /*
+        * Re-enable NMIs right here when running as an SEV-ES guest. This might
+        * cause nested NMIs, but those can be handled safely.
+        */
+       sev_es_nmi_complete();
+
        if (IS_ENABLED(CONFIG_SMP) && arch_cpu_is_offline(smp_processor_id()))
                return;
 
index d1bcd0dca583205e9398125a44600d838f29f90f..b6518e96dedbf2695ba5d4817215ffe04573eb94 100644 (file)
@@ -408,6 +408,24 @@ static bool vc_slow_virt_to_phys(struct ghcb *ghcb, struct es_em_ctxt *ctxt,
 /* Include code shared with pre-decompression boot stage */
 #include "sev-es-shared.c"
 
+void noinstr __sev_es_nmi_complete(void)
+{
+       struct ghcb_state state;
+       struct ghcb *ghcb;
+
+       ghcb = sev_es_get_ghcb(&state);
+
+       vc_ghcb_invalidate(ghcb);
+       ghcb_set_sw_exit_code(ghcb, SVM_VMGEXIT_NMI_COMPLETE);
+       ghcb_set_sw_exit_info_1(ghcb, 0);
+       ghcb_set_sw_exit_info_2(ghcb, 0);
+
+       sev_es_wr_ghcb_msr(__pa_nodebug(ghcb));
+       VMGEXIT();
+
+       sev_es_put_ghcb(&state);
+}
+
 static u64 get_jump_table_addr(void)
 {
        struct ghcb_state state;