From 0f5d0238ca7fb963a687a3c50c96c5f37a599c6b Mon Sep 17 00:00:00 2001
From: Tony Luck <tony.luck@intel.com>
Date: Thu, 13 Sep 2012 16:38:51 -0700
Subject: [PATCH] Add support for Sandy Bridge extended error logging

Sandy Bridge -EP processors have a mode where extra logging information
for corrected errors is provided in the memory controller bank allowing
us to identify the DIMM that was the source of the error.

This mode is disabled in hardware by default (so that legacy systems
are not confused by seeing two banks report each corrected error),
but can be turned on by setting a bit in MSR 0x17f.

Add code to turn this mode on in --daemon mode (with a --no-imc-log
option to turn it off if there are problems, or to --ascii decode logs
from systems where it is not enabled).

Signed-off-by: Tony Luck <tony.luck@intel.com>
Signed-off-by: Andi Kleen <ak@linux.intel.com>
Signed-off-by: Robert Milasan <rmilasan@suse.com>
---

Index: mcelog-1.0.2010.03.10/Makefile
===================================================================
--- mcelog-1.0.2010.03.10.orig/Makefile
+++ mcelog-1.0.2010.03.10/Makefile
@@ -33,7 +33,7 @@ OBJ := p4.o k8.o mcelog.o dmi.o tsc.o co
        nehalem.o dunnington.o tulsa.o config.o memutil.o msg.o   \
        eventloop.o leaky-bucket.o memdb.o server.o trigger.o 	 \
        client.o cache.o sysfs.o yellow.o page.o rbtree.o 	 \
-       xeon75xx.o sandy-bridge.o
+       xeon75xx.o sandy-bridge.o msr.o
 DISKDB_OBJ := diskdb.o dimm.o db.o
 EMAIL_OBJ := email.o
 CLEAN := mcelog dmi tsc dbquery .depend .depend.X dbquery.o ${DISKDB_OBJ} ${EMAIL_OBJ}
Index: mcelog-1.0.2010.03.10/intel.c
===================================================================
--- mcelog-1.0.2010.03.10.orig/intel.c
+++ mcelog-1.0.2010.03.10/intel.c
@@ -98,6 +98,9 @@ static int intel_memory_error(struct mce
 		case CPU_XEON75XX:
 			xeon75xx_memory_error(m, recordlen, channel, dimm);
 			break;
+		case CPU_SANDY_BRIDGE_EP:
+			sandy_bridge_ep_memerr_misc(m, channel, dimm);
+			break;
 		default:
 			break;
 		} 
Index: mcelog-1.0.2010.03.10/mcelog.8
===================================================================
--- mcelog-1.0.2010.03.10.orig/mcelog.8
+++ mcelog-1.0.2010.03.10/mcelog.8
@@ -214,6 +214,13 @@ option writes the process id of the daem
 .I file.
 Only valid in daemon mode.
 
+Mcelog will enable extended error reporting from the memory
+controller on processors that support it unless you tell it
+not to with the
+.B \-\-no-imc-log
+option. You might need this option when decoding old logs
+from a system where this mode was not enabled.
+
 .\".B \-\-database filename
 .\"specifies the memory module error database file. Default is
 .\"/var/lib/memory-errors.  It is only used together with DMI decoding.
Index: mcelog-1.0.2010.03.10/mcelog.c
===================================================================
--- mcelog-1.0.2010.03.10.orig/mcelog.c
+++ mcelog-1.0.2010.03.10/mcelog.c
@@ -84,6 +84,7 @@ static char pidfile_default[] = PID_FILE
 static char logfile_default[] = LOG_FILE;
 static char *pidfile = pidfile_default;
 static char *logfile;
+int imc_log = -1;
 
 static void check_cpu(void);
 
@@ -795,6 +796,7 @@ void usage(void)
 "--foreground        Keep in foreground (for debugging)\n"
 "--num-errors N      Only process N errors (for testing)\n"
 "--pidfile file	     Write pid of daemon into file\n"
+"--no-imc-log        Disable extended iMC logging\n"
 		);
 	email_usage();
 	diskdb_usage();
@@ -828,6 +830,7 @@ enum options {
 	O_FOREGROUND,
 	O_NUMERRORS,
 	O_PIDFILE,
+	O_NO_IMC_LOG,
 };
 
 static struct option options[] = {
@@ -859,6 +862,7 @@ static struct option options[] = {
 	{ "client", 0, NULL, O_CLIENT },
 	{ "num-errors", 1, NULL, O_NUMERRORS },
 	{ "pidfile", 1, NULL, O_PIDFILE },
+	{ "no-imc-log", 0, NULL, O_NO_IMC_LOG },
 	DISKDB_OPTIONS
 	EMAIL_OPTIONS
 	{}
@@ -961,6 +965,9 @@ static int modifier(int opt)
 	case O_CONFIG_FILE:
 		/* parsed in config.c */
 		break;
+	 case O_NO_IMC_LOG:
+		imc_log = 0;
+		break;
 	case 0:
 		break;
 	default:
@@ -1260,7 +1267,20 @@ int main(int ac, char **av)
 			email_mode = 1;
 		} else if (opt == 0)
 			break;		    
-	} 
+	}
+
+	/* If the user didn't tell us not to use iMC logging, check if CPU supports it */
+	if (imc_log == -1) {
+		switch (cputype) {
+		case CPU_SANDY_BRIDGE_EP:
+			imc_log = 1;
+			break;
+		default:
+			imc_log = 0;
+			break;
+		}
+	}
+
 	modifier_finish();
 	if (av[optind])
 		logfn = av[optind++];
@@ -1292,6 +1312,8 @@ int main(int ac, char **av)
 			closedmi();
 		server_setup();
 		page_setup();
+		if (imc_log)
+			set_imc_log(cputype);
 		drop_cred();
 		register_pollcb(fd, POLLIN, process_mcefd, &d);
 		if (!foreground && daemon(0, need_stdout()) < 0)
Index: mcelog-1.0.2010.03.10/mcelog.h
===================================================================
--- mcelog-1.0.2010.03.10.orig/mcelog.h
+++ mcelog-1.0.2010.03.10/mcelog.h
@@ -142,3 +142,5 @@ extern enum syslog_opt syslog_opt;
 extern int syslog_level;
 extern enum cputype cputype;
 extern int filter_memory_errors;
+extern int imc_log;
+extern void set_imc_log(int cputype);
Index: mcelog-1.0.2010.03.10/msr.c
===================================================================
--- /dev/null
+++ mcelog-1.0.2010.03.10/msr.c
@@ -0,0 +1,60 @@
+#include "mcelog.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+
+static void domsr(int cpu, int msr, int bit)
+{
+       char fpath[32];
+       unsigned long long data;
+       int fd;
+
+       sprintf(fpath, "/dev/cpu/%d/msr", cpu);
+       fd = open(fpath, O_RDWR);
+       if (fd == -1) {
+               switch (errno) {
+               case ENOENT:
+                       SYSERRprintf("Warning: cpu %d offline?, imc_log not set\n", cpu);
+                       return;
+               default:
+                       SYSERRprintf("Cannot open %s to set imc_log\n", fpath);
+                       exit(1);
+               }
+       }
+       if (pread(fd, &data, sizeof data, msr) != sizeof data) {
+               SYSERRprintf("Cannot read MSR_ERROR_CONTROL from %s\n", fpath);
+               exit(1);
+       }
+       data |= bit;
+       if (pwrite(fd, &data, sizeof data, msr) != sizeof data) {
+               SYSERRprintf("Cannot write MSR_ERROR_CONTROL to %s\n", fpath);
+               exit(1);
+       }
+       if (pread(fd, &data, sizeof data, msr) != sizeof data) {
+               SYSERRprintf("Cannot re-read MSR_ERROR_CONTROL from %s\n", fpath);
+               exit(1);
+       }
+       if ((data & bit) == 0) {
+               SYSERRprintf("Failed to set imc_log on cpu %d\n", cpu);
+               exit(1);
+       }
+        close(fd);
+}
+
+void set_imc_log(int cputype)
+{
+       int cpu, ncpus = sysconf(_SC_NPROCESSORS_CONF);
+       int     msr, bit;
+
+       switch (cputype) {
+       case CPU_SANDY_BRIDGE_EP:
+               msr = 0x17f;    /* MSR_ERROR_CONTROL */
+               bit = 0x2;      /* MemError Log Enable */
+               break;
+       }
+
+       for (cpu = 0; cpu < ncpus; cpu++)
+               domsr(cpu, msr, bit);
+}
Index: mcelog-1.0.2010.03.10/p4.c
===================================================================
--- mcelog-1.0.2010.03.10.orig/p4.c
+++ mcelog-1.0.2010.03.10/p4.c
@@ -354,7 +354,7 @@ void decode_intel_mc(struct mce *log, in
 		break;
 	case CPU_SANDY_BRIDGE:
 	case CPU_SANDY_BRIDGE_EP:
-		snb_decode_model(cputype, log->bank, log->status, size);
+		snb_decode_model(cputype, log->bank, log->status, log->misc);
 		break;
 	}
 }
Index: mcelog-1.0.2010.03.10/sandy-bridge.c
===================================================================
--- mcelog-1.0.2010.03.10.orig/sandy-bridge.c
+++ mcelog-1.0.2010.03.10/sandy-bridge.c
@@ -75,3 +75,61 @@ void snb_decode_model(int cputype, int b
 		break;
 	}
 }
+
+/*
+ * Sandy Bridge EP and EP4S processors (family 6, model 45) support additional
+ * logging for corrected errors in the integrated memory controller (IMC)
+ * banks. The mode is off by default, but can be enabled by setting the
+ * "MemError Log Enable" * bit in MSR_ERROR_CONTROL (MSR 0x17f).
+ * The documentation in the August 2012 edition of Intel's Software developer
+ * manual has some minor errors because the worng version of table 16-16
+ * "Intel IMC MC Error Codes for IA32_MCi_MISC (i= 8, 11)" was included.
+ * Corrections are:
+ *  Bit 62 is the "VALID" bit for the "first-device" bits in MISC and STATUS
+ *  Bit 63 is the "VALID" bit for the "second-device" bits in MISC
+ *  Bits 58:56 and 61:59 should be marked as "reserved".
+ * There should also be a footnote explaining how the "failing rank" fields
+ * can be converted to a DIMM number within a channel for systems with either
+ * two or three DIMMs per channel.
+ */
+static int failrank2dimm(unsigned failrank, int socket, int channel)
+{
+       switch (failrank) {
+       case 0: case 1: case 2: case 3:
+               return 0;
+       case 4: case 5:
+               return 1;
+       case 6: case 7:
+               if (get_memdimm(socket, channel, 2, 0))
+                       return 2;
+               else
+                       return 1;
+       }
+       return -1;
+}
+
+void sandy_bridge_ep_memerr_misc(struct mce *m, int *channel, int *dimm)
+{
+       u64 status = m->status;
+       unsigned        failrank, chan;
+
+       /* Ignore unless this is an corrected extended error from an iMC bank */
+       if (!imc_log || m->bank < 8 || m->bank > 11 || (status & MCI_STATUS_UC) ||
+               !test_prefix(7, status & 0xefff))
+               return;
+
+       chan = EXTRACT(status, 0, 3);
+       if (chan == 0xf)
+               return;
+
+       if (EXTRACT(m->misc, 62, 62)) {
+               failrank = EXTRACT(m->misc, 46, 50);
+               dimm[0] = failrank2dimm(failrank, m->socketid, chan);
+               channel[0] = chan;
+       }
+       if (EXTRACT(m->misc, 63, 63)) {
+               failrank = EXTRACT(m->misc, 51, 55);
+               dimm[1] = failrank2dimm(failrank, m->socketid, chan);
+               channel[1] = chan;
+       }
+}
Index: mcelog-1.0.2010.03.10/sandy-bridge.h
===================================================================
--- mcelog-1.0.2010.03.10.orig/sandy-bridge.h
+++ mcelog-1.0.2010.03.10/sandy-bridge.h
@@ -1 +1,2 @@
 void snb_decode_model(int cputype, int bank, u64 status, u64 misc);
+void sandy_bridge_ep_memerr_misc(struct mce *m, int *channel, int *dimm);
