]> www.infradead.org Git - users/mchehab/rasdaemon.git/commitdiff
Add a basic handler for MCE logs
authorMauro Carvalho Chehab <mchehab@redhat.com>
Wed, 15 May 2013 18:16:53 +0000 (15:16 -0300)
committerMauro Carvalho Chehab <mchehab@redhat.com>
Wed, 15 May 2013 18:24:01 +0000 (15:24 -0300)
For now, this handler just detects the CPU type and parses all
fields at the MCE event trace.

Latter patches will add decoding capabilities to the event.

Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
Makefile.am
configure.ac
ras-events.c
ras-events.h
ras-mce-handler.c [new file with mode: 0644]
ras-mce-handler.h [new file with mode: 0644]

index 916d93e77f6717061357147aeaf31fbab9168383..e70a51477ce5274c3388668f59e09ed40b140ddc 100644 (file)
@@ -10,6 +10,9 @@ endif
 if WITH_AER
    rasdaemon_SOURCES += ras-aer-handler.c
 endif
+if WITH_MCE
+   rasdaemon_SOURCES += ras-mce-handler.c
+endif
 rasdaemon_LDADD = -lpthread $(SQLITE3_LIBS) libtrace/libtrace.a
 
 include_HEADERS = config.h  ras-events.h  ras-logger.h  ras-mc-handler.h  ras-record.h
index c567367d82c1fea5a8590c7ae5b2b635b84ed99e..3ca33c45d66af33e9dd7a2d34551d23a78fcdc84 100644 (file)
@@ -41,9 +41,17 @@ AS_IF([test "x$enable_aer" = "xyes"], [
   AC_DEFINE(HAVE_AER,1,"have PCIe AER events collect")
   AC_SUBST([WITH_AER])
 ])
-
 AM_CONDITIONAL([WITH_AER], [test x$enable_aer = xyes])
 
+AC_ARG_ENABLE([mce],
+    AS_HELP_STRING([--enable-mce], [enable MCE events (currently experimental)]))
+
+AS_IF([test "x$enable_mce" = "xyes"], [
+  AC_DEFINE(HAVE_MCE,1,"have PCIe MCE events collect")
+  AC_SUBST([WITH_MCE])
+])
+AM_CONDITIONAL([WITH_MCE], [test x$enable_mce = xyes])
+
 test "$sysconfdir" = '${prefix}/etc' && sysconfdir=/etc
 
 AC_OUTPUT
index 1c56f1abed8d8d23b9ae97674841bda8fded6243..d1e907d3a7d01c667ee66a4f32dce74385c3d3b7 100644 (file)
@@ -498,8 +498,15 @@ int handle_ras_events(int record_events)
        }
 
 #ifdef HAVE_MCE_HANDLER
-       pevent_register_event_handler(pevent, -1, "mce", "mce_record",
-                                     ras_mce_event_handler, ras);
+       rc = register_mce_handler(ras);
+       if (rc) {
+               log(SYSLOG, LOG_INFO, "Can't register mce handler\n");
+               free(page);
+               goto free_pevent;
+       }
+       if (ras->mce)
+               pevent_register_event_handler(pevent, -1, "mce", "mce_record",
+                                             ras_mce_event_handler, ras);
 #endif
        rc = pevent_parse_event(pevent, page, size, "mce");
        if (rc) {
index a65e0aa3c33bec8cd77e40f2413b534c6ad56b78..027d97336ef543aedddc556da2b692003d429af1 100644 (file)
@@ -27,6 +27,8 @@
 #define MAX_PATH 1024
 #define STR(x) #x
 
+struct mce_priv;
+
 struct ras_events {
        char debugfs[MAX_PATH + 1];
        char tracing[MAX_PATH + 1];
@@ -42,6 +44,9 @@ struct ras_events {
 
        /* For ras-record */
        void            *db_priv;
+
+       /* For the mce handler */
+       struct mce_priv *mce_priv;
 };
 
 struct pthread_data {
diff --git a/ras-mce-handler.c b/ras-mce-handler.c
new file mode 100644 (file)
index 0000000..a0d3c32
--- /dev/null
@@ -0,0 +1,311 @@
+/*
+ * Copyright (C) 2013 Mauro Carvalho Chehab <mchehab@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include "libtrace/kbuffer.h"
+#include "ras-mce-handler.h"
+#include "ras-record.h"
+#include "ras-logger.h"
+
+static char *cputype_name[] = {
+       [CPU_GENERIC] = "generic CPU",
+       [CPU_P6OLD] = "Intel PPro/P2/P3/old Xeon",
+       [CPU_CORE2] = "Intel Core", /* 65nm and 45nm */
+       [CPU_K8] = "AMD K8 and derivates",
+       [CPU_P4] = "Intel P4",
+       [CPU_NEHALEM] = "Intel Xeon 5500 series / Core i3/5/7 (\"Nehalem/Westmere\")",
+       [CPU_DUNNINGTON] = "Intel Xeon 7400 series",
+       [CPU_TULSA] = "Intel Xeon 7100 series",
+       [CPU_INTEL] = "Intel generic architectural MCA",
+       [CPU_XEON75XX] = "Intel Xeon 7500 series",
+       [CPU_SANDY_BRIDGE] = "Sandy Bridge",            /* Fill in better name */
+       [CPU_SANDY_BRIDGE_EP] = "Sandy Bridge EP",      /* Fill in better name */
+       [CPU_IVY_BRIDGE] = "Ivy Bridge",                /* Fill in better name */
+       [CPU_IVY_BRIDGE_EPEX] = "Ivy Bridge EP/EX",     /* Fill in better name */
+};
+
+enum cputype select_intel_cputype(struct ras_events *ras)
+{
+       struct mce_priv *mce = ras->mce_priv;
+
+       if (mce->family == 15) {
+               if (mce->model == 6)
+                       return CPU_TULSA;
+               return CPU_P4;
+       }
+       if (mce->family == 6) {
+               if (mce->model >= 0x1a && mce->model != 28)
+                       mce->mc_error_support = 1;
+
+               if (mce->model < 0xf)
+                       return CPU_P6OLD;
+               else if (mce->model == 0xf || mce->model == 0x17) /* Merom/Penryn */
+                       return CPU_CORE2;
+               else if (mce->model == 0x1d)
+                       return CPU_DUNNINGTON;
+               else if (mce->model == 0x1a || mce->model == 0x2c ||
+                        mce->model == 0x1e || mce->model == 0x25)
+                       return CPU_NEHALEM;
+               else if (mce->model == 0x2e || mce->model == 0x2f)
+                       return CPU_XEON75XX;
+               else if (mce->model == 0x2a || mce->model == 0x3a)
+                       return CPU_SANDY_BRIDGE;
+               else if (mce->model == 0x2d)
+                       return CPU_SANDY_BRIDGE_EP;
+               else if (mce->model == 0x3a)
+                       return CPU_IVY_BRIDGE;
+               else if (mce->model == 0x3e)
+                       return CPU_IVY_BRIDGE_EPEX;
+               if (mce->model > 0x1a) {
+                       log(ALL, LOG_INFO,
+                           "Family 6 Model %x CPU: only decoding architectural errors\n",
+                           mce->model);
+                       return CPU_INTEL;
+               }
+       }
+       if (mce->family > 6) {
+               log(ALL, LOG_INFO,
+                   "Family %u Model %x CPU: only decoding architectural errors\n",
+                   mce->family, mce->model);
+               return CPU_INTEL;
+       }
+       log(ALL, LOG_INFO,
+           "Unknown Intel CPU type Family %x Model %x\n",
+            mce->family, mce->model);
+       return mce->family == 6 ? CPU_P6OLD : CPU_GENERIC;
+}
+
+int detect_cpu(struct ras_events *ras)
+{
+       struct mce_priv *mce = ras->mce_priv;
+       FILE *f;
+       int ret = 0;
+       char *line = NULL;
+       size_t linelen = 0;
+       enum {
+               VENDOR = 1,
+               FAMILY = 2,
+               MODEL = 4,
+               MHZ = 8,
+               FLAGS = 16,
+               ALL_FLAGS = 0x1f
+       } seen = 0;
+
+       mce->family = 0;
+       mce->model = 0;
+       mce->mhz = 0;
+       mce->vendor[0] = '\0';
+
+       f = fopen("/proc/cpuinfo","r");
+       if (!f) {
+               log(ALL, LOG_INFO, "Can't open /proc/cpuinfo\n");
+               return errno;
+       }
+
+       while (getdelim(&line, &linelen, '\n', f) > 0 && seen != ALL) {
+               if (sscanf(line, "mce->vendor_id : %63[^\n]", &mce->vendor) == 1)
+                       seen |= VENDOR;
+               if (sscanf(line, "cpu mce->family : %d", &mce->family) == 1)
+                       seen |= FAMILY;
+               if (sscanf(line, "mce->model : %d", &mce->model) == 1)
+                       seen |= MODEL;
+               if (sscanf(line, "cpu MHz : %lf", &mce->mhz) == 1)
+                       seen |= MHZ;
+               if (!strncmp(line, "flags", 5) && isspace(line[6])) {
+                       if (mce->processor_flags)
+                               free(mce->processor_flags);
+                       mce->processor_flags = line;
+                       line = NULL;
+                       linelen = 0;
+                       seen |= ALL_FLAGS;
+               }
+       }
+
+       if (!(seen == ALL)) {
+               log(ALL, LOG_INFO, "Can't parse /proc/cpuinfo\n");
+               ret = EINVAL;
+               goto ret;
+       }
+
+       /* Handle only Intel and AMD CPUs */
+       ret = 0;
+
+       if (!strcmp(mce->vendor, "AuthenticAMD")) {
+               if (mce->family == 15)
+                       mce->cputype = CPU_K8;
+               if (mce->family > 15) {
+                       log(ALL, LOG_INFO,
+                           "Can't parse MCE for this AMD CPU yet\n");
+                       ret = EINVAL;
+               }
+               goto ret;
+       } else if (!strcmp(mce->vendor,"GenuineIntel")) {
+               mce->cputype = select_intel_cputype(ras);
+       } else {
+               ret = EINVAL;
+       }
+
+ret:
+       fclose(f);
+       free(line);
+
+       return ret;
+}
+
+int register_mce_handler(struct ras_events *ras)
+{
+       int rc;
+       struct mce_priv *mce;
+
+       ras->mce_priv = calloc(1, sizeof(struct mce_priv));
+       if (!ras->mce_priv) {
+               log(ALL, LOG_INFO, "Can't allocate memory MCE data\n");
+               return ENOMEM;
+       }
+
+       rc = detect_cpu(ras);
+       if (rc) {
+               mce = ras->mce_priv;
+               if (mce->processor_flags)
+                       free (mce->processor_flags);
+               free (ras->mce_priv);
+               ras->mce_priv = NULL;
+               return (rc);
+       }
+
+       return rc;
+}
+
+static void dump_mce_event(struct trace_seq *s, struct mce_event *e)
+{
+       trace_seq_printf(s, "mcgcap= %d ", e->mcgcap);
+       trace_seq_printf(s, ", mcgstatus= %d ", e->mcgstatus);
+       trace_seq_printf(s, ", status= %d ", e->status);
+       trace_seq_printf(s, ", addr= %d ", e->addr);
+       trace_seq_printf(s, ", misc= %d ", e->misc);
+       trace_seq_printf(s, ", ip= %d ", e->ip);
+       trace_seq_printf(s, ", tsc= %d ", e->tsc);
+       trace_seq_printf(s, ", walltime= %d ", e->walltime);
+       trace_seq_printf(s, ", cpu= %d ", e->cpu);
+       trace_seq_printf(s, ", cpuid= %d ", e->cpuid);
+       trace_seq_printf(s, ", apicid= %d ", e->apicid);
+       trace_seq_printf(s, ", socketid= %d ", e->socketid);
+       trace_seq_printf(s, ", cs= %d ", e->cs);
+       trace_seq_printf(s, ", bank= %d ", e->bank);
+       trace_seq_printf(s, ", cpuvendor= %d", e->cpuvendor);
+}
+
+
+int ras_mce_event_handler(struct trace_seq *s,
+                         struct pevent_record *record,
+                         struct event_format *event, void *context)
+{
+       int len;
+       unsigned long long val;
+       struct ras_events *ras = context;
+       time_t now;
+       struct tm *tm;
+       struct ras_aer_event ev;
+       char buf[1024];
+       struct mce_priv *mce = ras->mce_priv;
+       struct mce_event e;
+
+       /*
+        * Newer kernels (3.10-rc1 or upper) provide an uptime clock.
+        * On previous kernels, the way to properly generate an event would
+        * be to inject a fake one, measure its timestamp and diff it against
+        * gettimeofday. We won't do it here. Instead, let's use uptime,
+        * falling-back to the event report's time, if "uptime" clock is
+        * not available (legacy kernels).
+        */
+
+       if (ras->use_uptime)
+               now = record->ts/1000000000L + ras->uptime_diff;
+       else
+               now = time(NULL);
+
+       tm = localtime(&now);
+       if (tm)
+               strftime(ev.timestamp, sizeof(ev.timestamp),
+                        "%Y-%m-%d %H:%M:%S %z", tm);
+       trace_seq_printf(s, "%s ", ev.timestamp);
+
+       trace_seq_printf(s, "CPU: %s, ", cputype_name[mce->cputype]);
+
+       /* Parse the MCE error data */
+       if (pevent_get_field_val(s, event, "mcgcap", record, &val, 1) < 0)
+               return -1;
+       e.mcgcap = val;
+       if (pevent_get_field_val(s, event, "mcgstatus", record, &val, 1) < 0)
+               return -1;
+       e.mcgstatus = val;
+       if (pevent_get_field_val(s, event, "status", record, &val, 1) < 0)
+               return -1;
+       e.status = val;
+       if (pevent_get_field_val(s, event, "addr", record, &val, 1) < 0)
+               return -1;
+       e.addr = val;
+       if (pevent_get_field_val(s, event, "misc", record, &val, 1) < 0)
+               return -1;
+       e.misc = val;
+       if (pevent_get_field_val(s, event, "ip", record, &val, 1) < 0)
+               return -1;
+       e.ip = val;
+       if (pevent_get_field_val(s, event, "tsc", record, &val, 1) < 0)
+               return -1;
+       e.tsc = val;
+       if (pevent_get_field_val(s, event, "walltime", record, &val, 1) < 0)
+               return -1;
+       e.walltime = val;
+       if (pevent_get_field_val(s, event, "cpu", record, &val, 1) < 0)
+               return -1;
+       e.cpu = val;
+       if (pevent_get_field_val(s, event, "cpuid", record, &val, 1) < 0)
+               return -1;
+       e.cpuid = val;
+       if (pevent_get_field_val(s, event, "apicid", record, &val, 1) < 0)
+               return -1;
+       e.apicid = val;
+       if (pevent_get_field_val(s, event, "socketid", record, &val, 1) < 0)
+               return -1;
+       e.socketid = val;
+       if (pevent_get_field_val(s, event, "cs", record, &val, 1) < 0)
+               return -1;
+       e.cs = val;
+       if (pevent_get_field_val(s, event, "bank", record, &val, 1) < 0)
+               return -1;
+       e.bank = val;
+       if (pevent_get_field_val(s, event, "cpuvendor", record, &val, 1) < 0)
+               return -1;
+       e.cpuvendor = val;
+
+       /*
+        * Default handler is to just output whatever is there.
+        *
+        * Latter patches will add parsing capabilities to the MCE events,
+        * in order to make them understandable by the end user, falling
+        * back to the simple dump if, for some reason, the parser is not
+        * able to properly decode it.
+        */
+       dump_mce_event(s, &e);
+
+       return 0;
+}
diff --git a/ras-mce-handler.h b/ras-mce-handler.h
new file mode 100644 (file)
index 0000000..a39e676
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2013 Mauro Carvalho Chehab <mchehab@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#ifndef __RAS_AER_HANDLER_H
+#define __RAS_AER_HANDLER_H
+
+#include <stdint.h>
+
+#include "ras-events.h"
+#include "libtrace/event-parse.h"
+
+enum cputype {
+       CPU_GENERIC,
+       CPU_P6OLD,
+       CPU_CORE2, /* 65nm and 45nm */
+       CPU_K8,
+       CPU_P4,
+       CPU_NEHALEM,
+       CPU_DUNNINGTON,
+       CPU_TULSA,
+       CPU_INTEL, /* Intel architectural errors */
+       CPU_XEON75XX,
+       CPU_SANDY_BRIDGE,
+       CPU_SANDY_BRIDGE_EP,
+       CPU_IVY_BRIDGE,
+       CPU_IVY_BRIDGE_EPEX,
+};
+
+struct mce_event {
+       uint64_t        mcgcap;
+       uint64_t        mcgstatus;
+       uint64_t        status;
+       uint64_t        addr;
+       uint64_t        misc;
+       uint64_t        ip;
+       uint64_t        tsc;
+       uint64_t        walltime;
+       uint32_t        cpu;
+       uint32_t        cpuid;
+       uint32_t        apicid;
+       uint32_t        socketid;
+       uint8_t         cs;
+       uint8_t         bank;
+       uint8_t         cpuvendor;
+};
+
+struct mce_priv {
+       /* CPU Info */
+       char vendor[64];
+       unsigned int family, model;
+       double mhz;
+       enum cputype cputype;
+       unsigned mc_error_support:1;
+       char *processor_flags;
+};
+
+int register_mce_handler(struct ras_events *ras);
+int ras_mce_event_handler(struct trace_seq *s,
+                         struct pevent_record *record,
+                         struct event_format *event, void *context);
+
+#endif