arm64: KVM/mm: Move SEA handling behind a single 'claim' interface
authorJames Morse <james.morse@arm.com>
Tue, 29 Jan 2019 18:48:50 +0000 (18:48 +0000)
committerRafael J. Wysocki <rafael.j.wysocki@intel.com>
Thu, 7 Feb 2019 22:10:45 +0000 (23:10 +0100)
To split up APEIs in_nmi() path, the caller needs to always be
in_nmi(). Add a helper to do the work and claim the notification.

When KVM or the arch code takes an exception that might be a RAS
notification, it asks the APEI firmware-first code whether it wants
to claim the exception. A future kernel-first mechanism may be queried
afterwards, and claim the notification, otherwise we fall through
to the existing default behaviour.

The NOTIFY_SEA code was merged before considering multiple, possibly
interacting, NMI-like notifications and the need to consider kernel
first in the future. Make the 'claiming' behaviour explicit.

Restructuring the APEI code to allow multiple NMI-like notifications
means any notification that might interrupt interrupts-masked
code must always be wrapped in nmi_enter()/nmi_exit(). This will
allow APEI to use in_nmi() to use the right fixmap entries.

Mask SError over this window to prevent an asynchronous RAS error
arriving and tripping 'nmi_enter()'s BUG_ON(in_nmi()).

Signed-off-by: James Morse <james.morse@arm.com>
Acked-by: Marc Zyngier <marc.zyngier@arm.com>
Tested-by: Tyler Baicar <tbaicar@codeaurora.org>
Acked-by: Catalin Marinas <catalin.marinas@arm.com>
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
arch/arm64/include/asm/acpi.h
arch/arm64/include/asm/daifflags.h
arch/arm64/include/asm/kvm_ras.h
arch/arm64/kernel/acpi.c
arch/arm64/mm/fault.c

index 2def77ec14be7ec38b1c62dec4d931ec2f74325d..7628efbe6c12570d704813845a80d11f19aa3379 100644 (file)
@@ -18,6 +18,7 @@
 
 #include <asm/cputype.h>
 #include <asm/io.h>
+#include <asm/ptrace.h>
 #include <asm/smp_plat.h>
 #include <asm/tlbflush.h>
 
@@ -110,9 +111,10 @@ static inline u32 get_acpi_id_for_cpu(unsigned int cpu)
 
 static inline void arch_fix_phys_package_id(int num, u32 slot) { }
 void __init acpi_init_cpus(void);
-
+int apei_claim_sea(struct pt_regs *regs);
 #else
 static inline void acpi_init_cpus(void) { }
+static inline int apei_claim_sea(struct pt_regs *regs) { return -ENOENT; }
 #endif /* CONFIG_ACPI */
 
 #ifdef CONFIG_ARM64_ACPI_PARKING_PROTOCOL
index 8d91f2233135c0086509a92fec934dbef7acae89..fa90779fc752f67bd42f11ec1ca29d08a296ff4d 100644 (file)
@@ -20,6 +20,7 @@
 
 #define DAIF_PROCCTX           0
 #define DAIF_PROCCTX_NOIRQ     PSR_I_BIT
+#define DAIF_ERRCTX            (PSR_I_BIT | PSR_A_BIT)
 
 /* mask/save/unmask/restore all exceptions, including interrupts. */
 static inline void local_daif_mask(void)
index 6096f02518121fba4a0f9e20491ecda066425b8b..8ac6ee77437c78a49b342e8a551ea866b05e935e 100644 (file)
@@ -4,8 +4,22 @@
 #ifndef __ARM64_KVM_RAS_H__
 #define __ARM64_KVM_RAS_H__
 
+#include <linux/acpi.h>
+#include <linux/errno.h>
 #include <linux/types.h>
 
-int kvm_handle_guest_sea(phys_addr_t addr, unsigned int esr);
+#include <asm/acpi.h>
+
+/*
+ * Was this synchronous external abort a RAS notification?
+ * Returns '0' for errors handled by some RAS subsystem, or -ENOENT.
+ */
+static inline int kvm_handle_guest_sea(phys_addr_t addr, unsigned int esr)
+{
+       /* apei_claim_sea(NULL) expects to mask interrupts itself */
+       lockdep_assert_irqs_enabled();
+
+       return apei_claim_sea(NULL);
+}
 
 #endif /* __ARM64_KVM_RAS_H__ */
index 44e3c351e1ea6efe09426697b2ca7a99f3cd6040..803f0494dd3e3b63f15678ca4700258c2e14cb61 100644 (file)
 #include <linux/smp.h>
 #include <linux/serial_core.h>
 
+#include <acpi/ghes.h>
 #include <asm/cputype.h>
 #include <asm/cpu_ops.h>
+#include <asm/daifflags.h>
 #include <asm/pgtable.h>
 #include <asm/smp_plat.h>
 
@@ -256,3 +258,32 @@ pgprot_t __acpi_get_mem_attribute(phys_addr_t addr)
                return __pgprot(PROT_NORMAL_NC);
        return __pgprot(PROT_DEVICE_nGnRnE);
 }
+
+/*
+ * Claim Synchronous External Aborts as a firmware first notification.
+ *
+ * Used by KVM and the arch do_sea handler.
+ * @regs may be NULL when called from process context.
+ */
+int apei_claim_sea(struct pt_regs *regs)
+{
+       int err = -ENOENT;
+       unsigned long current_flags;
+
+       if (!IS_ENABLED(CONFIG_ACPI_APEI_GHES))
+               return err;
+
+       current_flags = arch_local_save_flags();
+
+       /*
+        * SEA can interrupt SError, mask it and describe this as an NMI so
+        * that APEI defers the handling.
+        */
+       local_daif_restore(DAIF_ERRCTX);
+       nmi_enter();
+       err = ghes_notify_sea();
+       nmi_exit();
+       local_daif_restore(current_flags);
+
+       return err;
+}
index c76dc981e3fc80fdca0f9da263622e750ed41a5a..e1c84c2e1cab48a4c32eb78ad7e77cec7121a41a 100644 (file)
@@ -18,6 +18,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+#include <linux/acpi.h>
 #include <linux/extable.h>
 #include <linux/signal.h>
 #include <linux/mm.h>
@@ -33,6 +34,7 @@
 #include <linux/preempt.h>
 #include <linux/hugetlb.h>
 
+#include <asm/acpi.h>
 #include <asm/bug.h>
 #include <asm/cmpxchg.h>
 #include <asm/cpufeature.h>
@@ -47,8 +49,6 @@
 #include <asm/tlbflush.h>
 #include <asm/traps.h>
 
-#include <acpi/ghes.h>
-
 struct fault_info {
        int     (*fn)(unsigned long addr, unsigned int esr,
                      struct pt_regs *regs);
@@ -643,19 +643,10 @@ static int do_sea(unsigned long addr, unsigned int esr, struct pt_regs *regs)
        inf = esr_to_fault_info(esr);
 
        /*
-        * Synchronous aborts may interrupt code which had interrupts masked.
-        * Before calling out into the wider kernel tell the interested
-        * subsystems.
+        * Return value ignored as we rely on signal merging.
+        * Future patches will make this more robust.
         */
-       if (IS_ENABLED(CONFIG_ACPI_APEI_SEA)) {
-               if (interrupts_enabled(regs))
-                       nmi_enter();
-
-               ghes_notify_sea();
-
-               if (interrupts_enabled(regs))
-                       nmi_exit();
-       }
+       apei_claim_sea(regs);
 
        if (esr & ESR_ELx_FnV)
                siaddr = NULL;
@@ -733,11 +724,6 @@ static const struct fault_info fault_info[] = {
        { do_bad,               SIGKILL, SI_KERNEL,     "unknown 63"                    },
 };
 
-int kvm_handle_guest_sea(phys_addr_t addr, unsigned int esr)
-{
-       return ghes_notify_sea();
-}
-
 asmlinkage void __exception do_mem_abort(unsigned long addr, unsigned int esr,
                                         struct pt_regs *regs)
 {