diff options
Diffstat (limited to 'arch/powerpc/platforms/pseries/ras.c')
-rw-r--r-- | arch/powerpc/platforms/pseries/ras.c | 102 |
1 files changed, 69 insertions, 33 deletions
diff --git a/arch/powerpc/platforms/pseries/ras.c b/arch/powerpc/platforms/pseries/ras.c index a4fc6da87c2e..c55d7ad9c648 100644 --- a/arch/powerpc/platforms/pseries/ras.c +++ b/arch/powerpc/platforms/pseries/ras.c @@ -54,7 +54,8 @@ static unsigned char ras_log_buf[RTAS_ERROR_LOG_MAX]; static DEFINE_SPINLOCK(ras_log_buf_lock); -static char mce_data_buf[RTAS_ERROR_LOG_MAX]; +static char global_mce_data_buf[RTAS_ERROR_LOG_MAX]; +static DEFINE_PER_CPU(__u64, mce_data_buf); static int ras_get_sensor_state_token; static int ras_check_exception_token; @@ -196,12 +197,24 @@ static irqreturn_t ras_error_interrupt(int irq, void *dev_id) return IRQ_HANDLED; } -/* Get the error information for errors coming through the +/* + * Some versions of FWNMI place the buffer inside the 4kB page starting at + * 0x7000. Other versions place it inside the rtas buffer. We check both. + */ +#define VALID_FWNMI_BUFFER(A) \ + ((((A) >= 0x7000) && ((A) < 0x7ff0)) || \ + (((A) >= rtas.base) && ((A) < (rtas.base + rtas.size - 16)))) + +/* + * Get the error information for errors coming through the * FWNMI vectors. The pt_regs' r3 will be updated to reflect * the actual r3 if possible, and a ptr to the error log entry * will be returned if found. * - * The mce_data_buf does not have any locks or protection around it, + * If the RTAS error is not of the extended type, then we put it in a per + * cpu 64bit buffer. If it is the extended type we use global_mce_data_buf. + * + * The global_mce_data_buf does not have any locks or protection around it, * if a second machine check comes in, or a system reset is done * before we have logged the error, then we will get corruption in the * error log. This is preferable over holding off on calling @@ -210,20 +223,31 @@ static irqreturn_t ras_error_interrupt(int irq, void *dev_id) */ static struct rtas_error_log *fwnmi_get_errinfo(struct pt_regs *regs) { - unsigned long errdata = regs->gpr[3]; - struct rtas_error_log *errhdr = NULL; unsigned long *savep; + struct rtas_error_log *h, *errhdr = NULL; + + if (!VALID_FWNMI_BUFFER(regs->gpr[3])) { + printk(KERN_ERR "FWNMI: corrupt r3\n"); + return NULL; + } - if ((errdata >= 0x7000 && errdata < 0x7fff0) || - (errdata >= rtas.base && errdata < rtas.base + rtas.size - 16)) { - savep = __va(errdata); - regs->gpr[3] = savep[0]; /* restore original r3 */ - memset(mce_data_buf, 0, RTAS_ERROR_LOG_MAX); - memcpy(mce_data_buf, (char *)(savep + 1), RTAS_ERROR_LOG_MAX); - errhdr = (struct rtas_error_log *)mce_data_buf; + savep = __va(regs->gpr[3]); + regs->gpr[3] = savep[0]; /* restore original r3 */ + + /* If it isn't an extended log we can use the per cpu 64bit buffer */ + h = (struct rtas_error_log *)&savep[1]; + if (!h->extended) { + memcpy(&__get_cpu_var(mce_data_buf), h, sizeof(__u64)); + errhdr = (struct rtas_error_log *)&__get_cpu_var(mce_data_buf); } else { - printk("FWNMI: corrupt r3\n"); + int len; + + len = max_t(int, 8+h->extended_log_length, RTAS_ERROR_LOG_MAX); + memset(global_mce_data_buf, 0, RTAS_ERROR_LOG_MAX); + memcpy(global_mce_data_buf, h, len); + errhdr = (struct rtas_error_log *)global_mce_data_buf; } + return errhdr; } @@ -235,7 +259,7 @@ static void fwnmi_release_errinfo(void) { int ret = rtas_call(rtas_token("ibm,nmi-interlock"), 0, 1, NULL); if (ret != 0) - printk("FWNMI: nmi-interlock failed: %d\n", ret); + printk(KERN_ERR "FWNMI: nmi-interlock failed: %d\n", ret); } int pSeries_system_reset_exception(struct pt_regs *regs) @@ -259,31 +283,43 @@ int pSeries_system_reset_exception(struct pt_regs *regs) * Return 1 if corrected (or delivered a signal). * Return 0 if there is nothing we can do. */ -static int recover_mce(struct pt_regs *regs, struct rtas_error_log * err) +static int recover_mce(struct pt_regs *regs, struct rtas_error_log *err) { - int nonfatal = 0; + int recovered = 0; - if (err->disposition == RTAS_DISP_FULLY_RECOVERED) { + if (!(regs->msr & MSR_RI)) { + /* If MSR_RI isn't set, we cannot recover */ + recovered = 0; + + } else if (err->disposition == RTAS_DISP_FULLY_RECOVERED) { /* Platform corrected itself */ - nonfatal = 1; - } else if ((regs->msr & MSR_RI) && - user_mode(regs) && - err->severity == RTAS_SEVERITY_ERROR_SYNC && - err->disposition == RTAS_DISP_NOT_RECOVERED && - err->target == RTAS_TARGET_MEMORY && - err->type == RTAS_TYPE_ECC_UNCORR && - !(current->pid == 0 || is_global_init(current))) { - /* Kill off a user process with an ECC error */ - printk(KERN_ERR "MCE: uncorrectable ecc error for pid %d\n", - current->pid); - /* XXX something better for ECC error? */ - _exception(SIGBUS, regs, BUS_ADRERR, regs->nip); - nonfatal = 1; + recovered = 1; + + } else if (err->disposition == RTAS_DISP_LIMITED_RECOVERY) { + /* Platform corrected itself but could be degraded */ + printk(KERN_ERR "MCE: limited recovery, system may " + "be degraded\n"); + recovered = 1; + + } else if (user_mode(regs) && !is_global_init(current) && + err->severity == RTAS_SEVERITY_ERROR_SYNC) { + + /* + * If we received a synchronous error when in userspace + * kill the task. Firmware may report details of the fail + * asynchronously, so we can't rely on the target and type + * fields being valid here. + */ + printk(KERN_ERR "MCE: uncorrectable error, killing task " + "%s:%d\n", current->comm, current->pid); + + _exception(SIGBUS, regs, BUS_MCEERR_AR, regs->nip); + recovered = 1; } - log_error((char *)err, ERR_TYPE_RTAS_LOG, !nonfatal); + log_error((char *)err, ERR_TYPE_RTAS_LOG, 0); - return nonfatal; + return recovered; } /* |