powerpc/powernv: Machine check exception handling.
authorMahesh Salgaonkar <mahesh@linux.vnet.ibm.com>
Wed, 30 Oct 2013 14:36:13 +0000 (20:06 +0530)
committerBenjamin Herrenschmidt <benh@kernel.crashing.org>
Thu, 5 Dec 2013 05:06:06 +0000 (16:06 +1100)
Add basic error handling in machine check exception handler.

- If MSR_RI isn't set, we can not recover.
- Check if disposition set to OpalMCE_DISPOSITION_RECOVERED.
- Check if address at fault is inside kernel address space, if not then send
  SIGBUS to process if we hit exception when in userspace.
- If address at fault is not provided then and if we get a synchronous machine
  check while in userspace then kill the task.

Signed-off-by: Mahesh Salgaonkar <mahesh@linux.vnet.ibm.com>
Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
arch/powerpc/include/asm/mce.h
arch/powerpc/kernel/mce.c
arch/powerpc/platforms/powernv/opal.c

index 3276b409299cfe503160d523eb2de68a73d260dd..a2b8c7b35fbaf040243e3d4678e7a8264a9f038d 100644 (file)
@@ -193,5 +193,6 @@ extern void release_mce_event(void);
 extern void machine_check_queue_event(void);
 extern void machine_check_process_queued_event(void);
 extern void machine_check_print_event_info(struct machine_check_event *evt);
+extern uint64_t get_mce_fault_addr(struct machine_check_event *evt);
 
 #endif /* __ASM_PPC64_MCE_H__ */
index 1c6d15701c5616c9845577d52ad5b17e970ce253..c0c52ec1fca7002b996e5b9e60f036a6f55e5f90 100644 (file)
@@ -316,3 +316,30 @@ void machine_check_print_event_info(struct machine_check_event *evt)
                break;
        }
 }
+
+uint64_t get_mce_fault_addr(struct machine_check_event *evt)
+{
+       switch (evt->error_type) {
+       case MCE_ERROR_TYPE_UE:
+               if (evt->u.ue_error.effective_address_provided)
+                       return evt->u.ue_error.effective_address;
+               break;
+       case MCE_ERROR_TYPE_SLB:
+               if (evt->u.slb_error.effective_address_provided)
+                       return evt->u.slb_error.effective_address;
+               break;
+       case MCE_ERROR_TYPE_ERAT:
+               if (evt->u.erat_error.effective_address_provided)
+                       return evt->u.erat_error.effective_address;
+               break;
+       case MCE_ERROR_TYPE_TLB:
+               if (evt->u.tlb_error.effective_address_provided)
+                       return evt->u.tlb_error.effective_address;
+               break;
+       default:
+       case MCE_ERROR_TYPE_UNKNOWN:
+               break;
+       }
+       return 0;
+}
+EXPORT_SYMBOL(get_mce_fault_addr);
index f348bd49487c1e986c378c2f0e52abec6f63cd37..01e74cbc67e95fca9e93acce5de9a333eddd26ec 100644 (file)
@@ -18,6 +18,7 @@
 #include <linux/interrupt.h>
 #include <linux/notifier.h>
 #include <linux/slab.h>
+#include <linux/sched.h>
 #include <linux/kobject.h>
 #include <asm/opal.h>
 #include <asm/firmware.h>
@@ -251,6 +252,44 @@ int opal_put_chars(uint32_t vtermno, const char *data, int total_len)
        return written;
 }
 
+static int opal_recover_mce(struct pt_regs *regs,
+                                       struct machine_check_event *evt)
+{
+       int recovered = 0;
+       uint64_t ea = get_mce_fault_addr(evt);
+
+       if (!(regs->msr & MSR_RI)) {
+               /* If MSR_RI isn't set, we cannot recover */
+               recovered = 0;
+       } else if (evt->disposition == MCE_DISPOSITION_RECOVERED) {
+               /* Platform corrected itself */
+               recovered = 1;
+       } else if (ea && !is_kernel_addr(ea)) {
+               /*
+                * Faulting address is not in kernel text. We should be fine.
+                * We need to find which process uses this address.
+                * For now, kill the task if we have received exception when
+                * in userspace.
+                *
+                * TODO: Queue up this address for hwpoisioning later.
+                */
+               if (user_mode(regs) && !is_global_init(current)) {
+                       _exception(SIGBUS, regs, BUS_MCEERR_AR, regs->nip);
+                       recovered = 1;
+               } else
+                       recovered = 0;
+       } else if (user_mode(regs) && !is_global_init(current) &&
+               evt->severity == MCE_SEV_ERROR_SYNC) {
+               /*
+                * If we have received a synchronous error when in userspace
+                * kill the task.
+                */
+               _exception(SIGBUS, regs, BUS_MCEERR_AR, regs->nip);
+               recovered = 1;
+       }
+       return recovered;
+}
+
 int opal_machine_check(struct pt_regs *regs)
 {
        struct machine_check_event evt;
@@ -266,7 +305,9 @@ int opal_machine_check(struct pt_regs *regs)
        }
        machine_check_print_event_info(&evt);
 
-       return evt.severity == MCE_SEV_FATAL ? 0 : 1;
+       if (opal_recover_mce(regs, &evt))
+               return 1;
+       return 0;
 }
 
 static irqreturn_t opal_interrupt(int irq, void *data)