powerpc/pseries: Flush SLB contents on SLB MCE errors.
authorMahesh Salgaonkar <mahesh@linux.vnet.ibm.com>
Tue, 11 Sep 2018 14:27:00 +0000 (19:57 +0530)
committerMichael Ellerman <mpe@ellerman.id.au>
Wed, 19 Sep 2018 11:59:22 +0000 (21:59 +1000)
On pseries, as of today system crashes if we get a machine check
exceptions due to SLB errors. These are soft errors and can be fixed
by flushing the SLBs so the kernel can continue to function instead of
system crash. We do this in real mode before turning on MMU. Otherwise
we would run into nested machine checks. This patch now fetches the
rtas error log in real mode and flushes the SLBs on SLB/ERAT errors.

Signed-off-by: Mahesh Salgaonkar <mahesh@linux.vnet.ibm.com>
Signed-off-by: Michal Suchanek <msuchanek@suse.com>
Reviewed-by: Nicholas Piggin <npiggin@gmail.com>
Signed-off-by: Michael Ellerman <mpe@ellerman.id.au>
arch/powerpc/include/asm/machdep.h
arch/powerpc/include/asm/mce.h
arch/powerpc/kernel/exceptions-64s.S
arch/powerpc/kernel/mce.c
arch/powerpc/kernel/mce_power.c
arch/powerpc/platforms/powernv/opal.c
arch/powerpc/platforms/powernv/setup.c
arch/powerpc/platforms/pseries/pseries.h
arch/powerpc/platforms/pseries/ras.c
arch/powerpc/platforms/pseries/setup.c

index a47de82fb8e2701041a74c575bde8aa54315f1f4..b4831f1338db56484582bcb7770dcbc6e7da6467 100644 (file)
@@ -108,6 +108,7 @@ struct machdep_calls {
 
        /* Early exception handlers called in realmode */
        int             (*hmi_exception_early)(struct pt_regs *regs);
+       long            (*machine_check_early)(struct pt_regs *regs);
 
        /* Called during machine check exception to retrive fixup address. */
        bool            (*mce_check_early_recovery)(struct pt_regs *regs);
index 3a1226e9b4659ab8d34cb239c31e42409625eef6..a8b8903e184408b4623d842d9dc9f457639cccd3 100644 (file)
@@ -210,4 +210,7 @@ extern void release_mce_event(void);
 extern void machine_check_queue_event(void);
 extern void machine_check_print_event_info(struct machine_check_event *evt,
                                           bool user_mode);
+#ifdef CONFIG_PPC_BOOK3S_64
+void flush_and_reload_slb(void);
+#endif /* CONFIG_PPC_BOOK3S_64 */
 #endif /* __ASM_PPC64_MCE_H__ */
index ea04dfb8c0927f71e1a89937526f3e1f2d7fd241..b36e11d737025cd5c64039b2400af99107fddd25 100644 (file)
@@ -331,6 +331,9 @@ TRAMP_REAL_BEGIN(machine_check_pSeries)
 machine_check_fwnmi:
        SET_SCRATCH0(r13)               /* save r13 */
        EXCEPTION_PROLOG_0(PACA_EXMC)
+BEGIN_FTR_SECTION
+       b       machine_check_pSeries_early
+END_FTR_SECTION_IFCLR(CPU_FTR_HVMODE)
 machine_check_pSeries_0:
        EXCEPTION_PROLOG_1(PACA_EXMC, KVMTEST_PR, 0x200)
        /*
@@ -342,6 +345,103 @@ machine_check_pSeries_0:
 
 TRAMP_KVM_SKIP(PACA_EXMC, 0x200)
 
+TRAMP_REAL_BEGIN(machine_check_pSeries_early)
+BEGIN_FTR_SECTION
+       EXCEPTION_PROLOG_1(PACA_EXMC, NOTEST, 0x200)
+       mr      r10,r1                  /* Save r1 */
+       lhz     r11,PACA_IN_MCE(r13)
+       cmpwi   r11,0                   /* Are we in nested machine check */
+       bne     0f                      /* Yes, we are. */
+       /* First machine check entry */
+       ld      r1,PACAMCEMERGSP(r13)   /* Use MC emergency stack */
+0:     subi    r1,r1,INT_FRAME_SIZE    /* alloc stack frame */
+       addi    r11,r11,1               /* increment paca->in_mce */
+       sth     r11,PACA_IN_MCE(r13)
+       /* Limit nested MCE to level 4 to avoid stack overflow */
+       cmpwi   r11,MAX_MCE_DEPTH
+       bgt     1f                      /* Check if we hit limit of 4 */
+       mfspr   r11,SPRN_SRR0           /* Save SRR0 */
+       mfspr   r12,SPRN_SRR1           /* Save SRR1 */
+       EXCEPTION_PROLOG_COMMON_1()
+       EXCEPTION_PROLOG_COMMON_2(PACA_EXMC)
+       EXCEPTION_PROLOG_COMMON_3(0x200)
+       addi    r3,r1,STACK_FRAME_OVERHEAD
+       BRANCH_LINK_TO_FAR(machine_check_early) /* Function call ABI */
+       ld      r12,_MSR(r1)
+       andi.   r11,r12,MSR_PR          /* See if coming from user. */
+       bne     2f                      /* continue in V mode if we are. */
+
+       /*
+        * At this point we are not sure about what context we come from.
+        * We may be in the middle of switching stack. r1 may not be valid.
+        * Hence stay on emergency stack, call machine_check_exception and
+        * return from the interrupt.
+        * But before that, check if this is an un-recoverable exception.
+        * If yes, then stay on emergency stack and panic.
+        */
+       andi.   r11,r12,MSR_RI
+       beq     1f
+
+       /*
+        * Check if we have successfully handled/recovered from error, if not
+        * then stay on emergency stack and panic.
+        */
+       cmpdi   r3,0            /* see if we handled MCE successfully */
+       beq     1f              /* if !handled then panic */
+
+       /* Stay on emergency stack and return from interrupt. */
+       LOAD_HANDLER(r10,mce_return)
+       mtspr   SPRN_SRR0,r10
+       ld      r10,PACAKMSR(r13)
+       mtspr   SPRN_SRR1,r10
+       RFI_TO_KERNEL
+       b       .
+
+1:     LOAD_HANDLER(r10,unrecover_mce)
+       mtspr   SPRN_SRR0,r10
+       ld      r10,PACAKMSR(r13)
+       /*
+        * We are going down. But there are chances that we might get hit by
+        * another MCE during panic path and we may run into unstable state
+        * with no way out. Hence, turn ME bit off while going down, so that
+        * when another MCE is hit during panic path, hypervisor will
+        * power cycle the lpar, instead of getting into MCE loop.
+        */
+       li      r3,MSR_ME
+       andc    r10,r10,r3              /* Turn off MSR_ME */
+       mtspr   SPRN_SRR1,r10
+       RFI_TO_KERNEL
+       b       .
+
+       /* Move original SRR0 and SRR1 into the respective regs */
+2:     ld      r9,_MSR(r1)
+       mtspr   SPRN_SRR1,r9
+       ld      r3,_NIP(r1)
+       mtspr   SPRN_SRR0,r3
+       ld      r9,_CTR(r1)
+       mtctr   r9
+       ld      r9,_XER(r1)
+       mtxer   r9
+       ld      r9,_LINK(r1)
+       mtlr    r9
+       REST_GPR(0, r1)
+       REST_8GPRS(2, r1)
+       REST_GPR(10, r1)
+       ld      r11,_CCR(r1)
+       mtcr    r11
+       /* Decrement paca->in_mce. */
+       lhz     r12,PACA_IN_MCE(r13)
+       subi    r12,r12,1
+       sth     r12,PACA_IN_MCE(r13)
+       REST_GPR(11, r1)
+       REST_2GPRS(12, r1)
+       /* restore original r1. */
+       ld      r1,GPR1(r1)
+       SET_SCRATCH0(r13)               /* save r13 */
+       EXCEPTION_PROLOG_0(PACA_EXMC)
+       b       machine_check_pSeries_0
+END_FTR_SECTION_IFCLR(CPU_FTR_HVMODE)
+
 EXC_COMMON_BEGIN(machine_check_common)
        /*
         * Machine check is different because we use a different
@@ -535,6 +635,35 @@ EXC_COMMON_BEGIN(unrecover_mce)
        bl      unrecoverable_exception
        b       1b
 
+EXC_COMMON_BEGIN(mce_return)
+       /* Invoke machine_check_exception to print MCE event and return. */
+       addi    r3,r1,STACK_FRAME_OVERHEAD
+       bl      machine_check_exception
+       ld      r9,_MSR(r1)
+       mtspr   SPRN_SRR1,r9
+       ld      r3,_NIP(r1)
+       mtspr   SPRN_SRR0,r3
+       ld      r9,_CTR(r1)
+       mtctr   r9
+       ld      r9,_XER(r1)
+       mtxer   r9
+       ld      r9,_LINK(r1)
+       mtlr    r9
+       REST_GPR(0, r1)
+       REST_8GPRS(2, r1)
+       REST_GPR(10, r1)
+       ld      r11,_CCR(r1)
+       mtcr    r11
+       /* Decrement paca->in_mce. */
+       lhz     r12,PACA_IN_MCE(r13)
+       subi    r12,r12,1
+       sth     r12,PACA_IN_MCE(r13)
+       REST_GPR(11, r1)
+       REST_2GPRS(12, r1)
+       /* restore original r1. */
+       ld      r1,GPR1(r1)
+       RFI_TO_KERNEL
+       b       .
 
 EXC_REAL(data_access, 0x300, 0x80)
 EXC_VIRT(data_access, 0x4300, 0x80, 0x300)
index efdd16a79075f699ecf866f4dfc22abd794699fa..bd933a75f0bcbe6dda0ede7abefe69038ada36e4 100644 (file)
@@ -488,10 +488,11 @@ long machine_check_early(struct pt_regs *regs)
 {
        long handled = 0;
 
-       __this_cpu_inc(irq_stat.mce_exceptions);
-
-       if (cur_cpu_spec && cur_cpu_spec->machine_check_early)
-               handled = cur_cpu_spec->machine_check_early(regs);
+       /*
+        * See if platform is capable of handling machine check.
+        */
+       if (ppc_md.machine_check_early)
+               handled = ppc_md.machine_check_early(regs);
        return handled;
 }
 
index 3497c8329c1d70bc7d010e97a3aeb66f524b9e1d..2016b58d564f0e58cfe1ffcb51a36b542ac815ad 100644 (file)
@@ -60,7 +60,7 @@ static unsigned long addr_to_pfn(struct pt_regs *regs, unsigned long addr)
 
 /* flush SLBs and reload */
 #ifdef CONFIG_PPC_BOOK3S_64
-static void flush_and_reload_slb(void)
+void flush_and_reload_slb(void)
 {
        /* Invalidate all SLBs */
        slb_flush_all_realmode();
index 38fe4087484a61e7ead4fed3ea89db130b596b8b..62c291e23dbe2118fea6ffa4729ff70f58dec8a4 100644 (file)
@@ -578,6 +578,8 @@ int opal_machine_check(struct pt_regs *regs)
 {
        struct machine_check_event evt;
 
+       __this_cpu_inc(irq_stat.mce_exceptions);
+
        if (!get_mce_event(&evt, MCE_EVENT_RELEASE))
                return 0;
 
index adddde023622754d00f2c33168e18bfd16d95a80..c9cbd11a442e767e92c170083c24e9a14b5f6171 100644 (file)
@@ -437,6 +437,16 @@ static unsigned long pnv_get_proc_freq(unsigned int cpu)
        return ret_freq;
 }
 
+static long pnv_machine_check_early(struct pt_regs *regs)
+{
+       long handled = 0;
+
+       if (cur_cpu_spec && cur_cpu_spec->machine_check_early)
+               handled = cur_cpu_spec->machine_check_early(regs);
+
+       return handled;
+}
+
 define_machine(powernv) {
        .name                   = "PowerNV",
        .probe                  = pnv_probe,
@@ -448,6 +458,7 @@ define_machine(powernv) {
        .machine_shutdown       = pnv_shutdown,
        .power_save             = NULL,
        .calibrate_decr         = generic_calibrate_decr,
+       .machine_check_early    = pnv_machine_check_early,
 #ifdef CONFIG_KEXEC_CORE
        .kexec_cpu_down         = pnv_kexec_cpu_down,
 #endif
index 60db2ee511fb4669bc53d84686fddc317bda4148..619f8f3fa173fa1d38edf0ec405b3f49508b051f 100644 (file)
@@ -24,6 +24,7 @@ struct pt_regs;
 
 extern int pSeries_system_reset_exception(struct pt_regs *regs);
 extern int pSeries_machine_check_exception(struct pt_regs *regs);
+extern long pseries_machine_check_realmode(struct pt_regs *regs);
 
 #ifdef CONFIG_SMP
 extern void smp_init_pseries(void);
index 3500ad9827066c23de16960ec60c0ed92ec3b024..0578c243ef0180906bc8dfbc42fdf07ab3d78617 100644 (file)
@@ -27,6 +27,7 @@
 #include <asm/machdep.h>
 #include <asm/rtas.h>
 #include <asm/firmware.h>
+#include <asm/mce.h>
 
 #include "pseries.h"
 
@@ -522,6 +523,43 @@ int pSeries_system_reset_exception(struct pt_regs *regs)
        return 0; /* need to perform reset */
 }
 
+static int mce_handle_error(struct rtas_error_log *errp)
+{
+       struct pseries_errorlog *pseries_log;
+       struct pseries_mc_errorlog *mce_log;
+       int disposition = rtas_error_disposition(errp);
+       u8 error_type;
+
+       if (!rtas_error_extended(errp))
+               goto out;
+
+       pseries_log = get_pseries_errorlog(errp, PSERIES_ELOG_SECT_ID_MCE);
+       if (pseries_log == NULL)
+               goto out;
+
+       mce_log = (struct pseries_mc_errorlog *)pseries_log->data;
+       error_type = mce_log->error_type;
+
+#ifdef CONFIG_PPC_BOOK3S_64
+       if (disposition == RTAS_DISP_NOT_RECOVERED) {
+               switch (error_type) {
+               case    MC_ERROR_TYPE_SLB:
+               case    MC_ERROR_TYPE_ERAT:
+                       /* Store the old slb content someplace. */
+                       flush_and_reload_slb();
+                       disposition = RTAS_DISP_FULLY_RECOVERED;
+                       rtas_set_disposition_recovered(errp);
+                       break;
+               default:
+                       break;
+               }
+       }
+#endif
+
+out:
+       return disposition;
+}
+
 /*
  * Process MCE rtas errlog event.
  */
@@ -598,11 +636,31 @@ int pSeries_machine_check_exception(struct pt_regs *regs)
        struct rtas_error_log *errp;
 
        if (fwnmi_active) {
-               errp = fwnmi_get_errinfo(regs);
                fwnmi_release_errinfo();
+               errp = fwnmi_get_errlog();
                if (errp && recover_mce(regs, errp))
                        return 1;
        }
 
        return 0;
 }
+
+long pseries_machine_check_realmode(struct pt_regs *regs)
+{
+       struct rtas_error_log *errp;
+       int disposition;
+
+       if (fwnmi_active) {
+               errp = fwnmi_get_errinfo(regs);
+               /*
+                * Call to fwnmi_release_errinfo() in real mode causes kernel
+                * to panic. Hence we will call it as soon as we go into
+                * virtual mode.
+                */
+               disposition = mce_handle_error(errp);
+               if (disposition == RTAS_DISP_FULLY_RECOVERED)
+                       return 1;
+       }
+
+       return 0;
+}
index ba1791fd3234dbbfd5fac997ea1433119d44f92d..e03f62a786495f4c3f91dc78d0099262c817a51c 100644 (file)
@@ -1017,6 +1017,7 @@ define_machine(pseries) {
        .calibrate_decr         = generic_calibrate_decr,
        .progress               = rtas_progress,
        .system_reset_exception = pSeries_system_reset_exception,
+       .machine_check_early    = pseries_machine_check_realmode,
        .machine_check_exception = pSeries_machine_check_exception,
 #ifdef CONFIG_KEXEC_CORE
        .machine_kexec          = pSeries_machine_kexec,