Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d368514c authored by Anton Blanchard's avatar Anton Blanchard Committed by Benjamin Herrenschmidt
Browse files

powerpc: Fix corruption when grabbing FWNMI data



The FWNMI code uses a global buffer without any locks to read the RTAS error
information. If two CPUs take a machine check at once then we will corrupt
this buffer.

Since most FWNMI rtas messages are not of the extended type, we can create a
64bit percpu buffer and use it where possible. If we do receive an extended
RTAS log then we fall back to the old behaviour of using the global buffer.

Signed-off-by: default avatarAnton Blanchard <anton@samba.org>
Signed-off-by: default avatarBenjamin Herrenschmidt <benh@kernel.crashing.org>
parent d47d1d8a
Loading
Loading
Loading
Loading
+38 −14
Original line number Original line Diff line number Diff line
@@ -54,7 +54,8 @@
static unsigned char ras_log_buf[RTAS_ERROR_LOG_MAX];
static unsigned char ras_log_buf[RTAS_ERROR_LOG_MAX];
static DEFINE_SPINLOCK(ras_log_buf_lock);
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_get_sensor_state_token;
static int ras_check_exception_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;
	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
 * FWNMI vectors.  The pt_regs' r3 will be updated to reflect
 * the actual r3 if possible, and a ptr to the error log entry
 * the actual r3 if possible, and a ptr to the error log entry
 * will be returned if found.
 * 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
 * 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
 * before we have logged the error, then we will get corruption in the
 * error log.  This is preferable over holding off on calling
 * 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)
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;
	unsigned long *savep;
	struct rtas_error_log *h, *errhdr = NULL;


	if ((errdata >= 0x7000 && errdata < 0x7fff0) ||
	if (!VALID_FWNMI_BUFFER(regs->gpr[3])) {
	    (errdata >= rtas.base && errdata < rtas.base + rtas.size - 16)) {
		printk(KERN_ERR "FWNMI: corrupt r3\n");
		savep = __va(errdata);
		return NULL;
	}

	savep = __va(regs->gpr[3]);
	regs->gpr[3] = savep[0];	/* restore original r3 */
	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);
	/* If it isn't an extended log we can use the per cpu 64bit buffer */
		errhdr = (struct rtas_error_log *)mce_data_buf;
	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 {
	} 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;
	return errhdr;
}
}


@@ -235,7 +259,7 @@ static void fwnmi_release_errinfo(void)
{
{
	int ret = rtas_call(rtas_token("ibm,nmi-interlock"), 0, 1, NULL);
	int ret = rtas_call(rtas_token("ibm,nmi-interlock"), 0, 1, NULL);
	if (ret != 0)
	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)
int pSeries_system_reset_exception(struct pt_regs *regs)