]> git.ipfire.org Git - people/arne_f/kernel.git/commitdiff
RAS: Add a Corrected Errors Collector
authorBorislav Petkov <bp@suse.de>
Mon, 27 Mar 2017 09:33:02 +0000 (11:33 +0200)
committerIngo Molnar <mingo@kernel.org>
Tue, 28 Mar 2017 06:54:48 +0000 (08:54 +0200)
Introduce a simple data structure for collecting correctable errors
along with accessors. More detailed description in the code itself.

The error decoding is done with the decoding chain now and
mce_first_notifier() gets to see the error first and the CEC decides
whether to log it and then the rest of the chain doesn't hear about it -
basically the main reason for the CE collector - or to continue running
the notifiers.

When the CEC hits the action threshold, it will try to soft-offine the
page containing the ECC and then the whole decoding chain gets to see
the error.

Signed-off-by: Borislav Petkov <bp@suse.de>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: linux-edac <linux-edac@vger.kernel.org>
Link: http://lkml.kernel.org/r/20170327093304.10683-5-bp@alien8.de
Signed-off-by: Ingo Molnar <mingo@kernel.org>
Documentation/admin-guide/kernel-parameters.txt
arch/x86/include/asm/mce.h
arch/x86/kernel/cpu/mcheck/mce.c
arch/x86/ras/Kconfig
drivers/ras/Makefile
drivers/ras/cec.c [new file with mode: 0644]
drivers/ras/debugfs.c
drivers/ras/debugfs.h [new file with mode: 0644]
drivers/ras/ras.c
include/linux/ras.h

index 2ba45caabada3e88339e88ebdfaaedbb322085c8..927b8b8854cde18fb718441ca663fe503152571d 100644 (file)
        ramdisk_size=   [RAM] Sizes of RAM disks in kilobytes
                        See Documentation/blockdev/ramdisk.txt.
 
+       ras=option[,option,...] [KNL] RAS-specific options
+
+               cec_disable     [X86]
+                               Disable the Correctable Errors Collector,
+                               see CONFIG_RAS_CEC help text.
+
        rcu_nocbs=      [KNL]
                        The argument is a cpu list, as described above.
 
index 0512dcc11750dec675bc729bc8a8d3b85eded9a8..c5ae545d27d85a1cf299f74cfd0731f2f59a2b02 100644 (file)
@@ -191,10 +191,11 @@ extern struct mca_config mca_cfg;
 extern struct mca_msr_regs msr_ops;
 
 enum mce_notifier_prios {
-       MCE_PRIO_SRAO           = INT_MAX,
-       MCE_PRIO_EXTLOG         = INT_MAX - 1,
-       MCE_PRIO_NFIT           = INT_MAX - 2,
-       MCE_PRIO_EDAC           = INT_MAX - 3,
+       MCE_PRIO_FIRST          = INT_MAX,
+       MCE_PRIO_SRAO           = INT_MAX - 1,
+       MCE_PRIO_EXTLOG         = INT_MAX - 2,
+       MCE_PRIO_NFIT           = INT_MAX - 3,
+       MCE_PRIO_EDAC           = INT_MAX - 4,
        MCE_PRIO_LOWEST         = 0,
 };
 
index 8ada093d4e400296b096e97855b09812b9dea331..4a907758a51658e7e14949d764e1535c52d57f49 100644 (file)
@@ -35,6 +35,7 @@
 #include <linux/poll.h>
 #include <linux/nmi.h>
 #include <linux/cpu.h>
+#include <linux/ras.h>
 #include <linux/smp.h>
 #include <linux/fs.h>
 #include <linux/mm.h>
@@ -160,47 +161,8 @@ static struct mce_log_buffer mcelog_buf = {
 
 void mce_log(struct mce *m)
 {
-       unsigned next, entry;
-
-       /* Emit the trace record: */
-       trace_mce_record(m);
-
        if (!mce_gen_pool_add(m))
                irq_work_queue(&mce_irq_work);
-
-       wmb();
-       for (;;) {
-               entry = mce_log_get_idx_check(mcelog_buf.next);
-               for (;;) {
-
-                       /*
-                        * When the buffer fills up discard new entries.
-                        * Assume that the earlier errors are the more
-                        * interesting ones:
-                        */
-                       if (entry >= MCE_LOG_LEN) {
-                               set_bit(MCE_OVERFLOW,
-                                       (unsigned long *)&mcelog_buf.flags);
-                               return;
-                       }
-                       /* Old left over entry. Skip: */
-                       if (mcelog_buf.entry[entry].finished) {
-                               entry++;
-                               continue;
-                       }
-                       break;
-               }
-               smp_rmb();
-               next = entry + 1;
-               if (cmpxchg(&mcelog_buf.next, entry, next) == entry)
-                       break;
-       }
-       memcpy(mcelog_buf.entry + entry, m, sizeof(struct mce));
-       wmb();
-       mcelog_buf.entry[entry].finished = 1;
-       wmb();
-
-       set_bit(0, &mce_need_notify);
 }
 
 void mce_inject_log(struct mce *m)
@@ -213,6 +175,12 @@ EXPORT_SYMBOL_GPL(mce_inject_log);
 
 static struct notifier_block mce_srao_nb;
 
+/*
+ * We run the default notifier if we have only the SRAO, the first and the
+ * default notifier registered. I.e., the mandatory NUM_DEFAULT_NOTIFIERS
+ * notifiers registered on the chain.
+ */
+#define NUM_DEFAULT_NOTIFIERS  3
 static atomic_t num_notifiers;
 
 void mce_register_decode_chain(struct notifier_block *nb)
@@ -522,7 +490,6 @@ static void mce_schedule_work(void)
 
 static void mce_irq_work_cb(struct irq_work *entry)
 {
-       mce_notify_irq();
        mce_schedule_work();
 }
 
@@ -565,6 +532,111 @@ static int mce_usable_address(struct mce *m)
        return 1;
 }
 
+static bool memory_error(struct mce *m)
+{
+       struct cpuinfo_x86 *c = &boot_cpu_data;
+
+       if (c->x86_vendor == X86_VENDOR_AMD) {
+               /* ErrCodeExt[20:16] */
+               u8 xec = (m->status >> 16) & 0x1f;
+
+               return (xec == 0x0 || xec == 0x8);
+       } else if (c->x86_vendor == X86_VENDOR_INTEL) {
+               /*
+                * Intel SDM Volume 3B - 15.9.2 Compound Error Codes
+                *
+                * Bit 7 of the MCACOD field of IA32_MCi_STATUS is used for
+                * indicating a memory error. Bit 8 is used for indicating a
+                * cache hierarchy error. The combination of bit 2 and bit 3
+                * is used for indicating a `generic' cache hierarchy error
+                * But we can't just blindly check the above bits, because if
+                * bit 11 is set, then it is a bus/interconnect error - and
+                * either way the above bits just gives more detail on what
+                * bus/interconnect error happened. Note that bit 12 can be
+                * ignored, as it's the "filter" bit.
+                */
+               return (m->status & 0xef80) == BIT(7) ||
+                      (m->status & 0xef00) == BIT(8) ||
+                      (m->status & 0xeffc) == 0xc;
+       }
+
+       return false;
+}
+
+static bool cec_add_mce(struct mce *m)
+{
+       if (!m)
+               return false;
+
+       /* We eat only correctable DRAM errors with usable addresses. */
+       if (memory_error(m) &&
+           !(m->status & MCI_STATUS_UC) &&
+           mce_usable_address(m))
+               if (!cec_add_elem(m->addr >> PAGE_SHIFT))
+                       return true;
+
+       return false;
+}
+
+static int mce_first_notifier(struct notifier_block *nb, unsigned long val,
+                             void *data)
+{
+       struct mce *m = (struct mce *)data;
+       unsigned int next, entry;
+
+       if (!m)
+               return NOTIFY_DONE;
+
+       if (cec_add_mce(m))
+               return NOTIFY_STOP;
+
+       /* Emit the trace record: */
+       trace_mce_record(m);
+
+       wmb();
+       for (;;) {
+               entry = mce_log_get_idx_check(mcelog_buf.next);
+               for (;;) {
+
+                       /*
+                        * When the buffer fills up discard new entries.
+                        * Assume that the earlier errors are the more
+                        * interesting ones:
+                        */
+                       if (entry >= MCE_LOG_LEN) {
+                               set_bit(MCE_OVERFLOW,
+                                       (unsigned long *)&mcelog_buf.flags);
+                               return NOTIFY_DONE;
+                       }
+                       /* Old left over entry. Skip: */
+                       if (mcelog_buf.entry[entry].finished) {
+                               entry++;
+                               continue;
+                       }
+                       break;
+               }
+               smp_rmb();
+               next = entry + 1;
+               if (cmpxchg(&mcelog_buf.next, entry, next) == entry)
+                       break;
+       }
+       memcpy(mcelog_buf.entry + entry, m, sizeof(struct mce));
+       wmb();
+       mcelog_buf.entry[entry].finished = 1;
+       wmb();
+
+       set_bit(0, &mce_need_notify);
+
+       mce_notify_irq();
+
+       return NOTIFY_DONE;
+}
+
+static struct notifier_block first_nb = {
+       .notifier_call  = mce_first_notifier,
+       .priority       = MCE_PRIO_FIRST,
+};
+
 static int srao_decode_notifier(struct notifier_block *nb, unsigned long val,
                                void *data)
 {
@@ -594,11 +666,7 @@ static int mce_default_notifier(struct notifier_block *nb, unsigned long val,
        if (!m)
                return NOTIFY_DONE;
 
-       /*
-        * Run the default notifier if we have only the SRAO
-        * notifier and us registered.
-        */
-       if (atomic_read(&num_notifiers) > 2)
+       if (atomic_read(&num_notifiers) > NUM_DEFAULT_NOTIFIERS)
                return NOTIFY_DONE;
 
        /* Don't print when mcelog is running */
@@ -655,37 +723,6 @@ static void mce_read_aux(struct mce *m, int i)
        }
 }
 
-static bool memory_error(struct mce *m)
-{
-       struct cpuinfo_x86 *c = &boot_cpu_data;
-
-       if (c->x86_vendor == X86_VENDOR_AMD) {
-               /* ErrCodeExt[20:16] */
-               u8 xec = (m->status >> 16) & 0x1f;
-
-               return (xec == 0x0 || xec == 0x8);
-       } else if (c->x86_vendor == X86_VENDOR_INTEL) {
-               /*
-                * Intel SDM Volume 3B - 15.9.2 Compound Error Codes
-                *
-                * Bit 7 of the MCACOD field of IA32_MCi_STATUS is used for
-                * indicating a memory error. Bit 8 is used for indicating a
-                * cache hierarchy error. The combination of bit 2 and bit 3
-                * is used for indicating a `generic' cache hierarchy error
-                * But we can't just blindly check the above bits, because if
-                * bit 11 is set, then it is a bus/interconnect error - and
-                * either way the above bits just gives more detail on what
-                * bus/interconnect error happened. Note that bit 12 can be
-                * ignored, as it's the "filter" bit.
-                */
-               return (m->status & 0xef80) == BIT(7) ||
-                      (m->status & 0xef00) == BIT(8) ||
-                      (m->status & 0xeffc) == 0xc;
-       }
-
-       return false;
-}
-
 DEFINE_PER_CPU(unsigned, mce_poll_count);
 
 /*
@@ -2167,6 +2204,7 @@ __setup("mce", mcheck_enable);
 int __init mcheck_init(void)
 {
        mcheck_intel_therm_init();
+       mce_register_decode_chain(&first_nb);
        mce_register_decode_chain(&mce_srao_nb);
        mce_register_decode_chain(&mce_default_nb);
        mcheck_vendor_init_severity();
@@ -2716,6 +2754,7 @@ static int __init mcheck_late_init(void)
                static_branch_inc(&mcsafe_key);
 
        mcheck_debugfs_init();
+       cec_init();
 
        /*
         * Flush out everything that has been logged during early boot, now that
index 0bc60a30873088208ddf221dfaac8000890ee8ca..2a2d89d39af6d489b60e9bc67b02f56e4af2d0f6 100644 (file)
@@ -7,3 +7,17 @@ config MCE_AMD_INJ
          aspects of the MCE handling code.
 
          WARNING: Do not even assume this interface is staying stable!
+
+config RAS_CEC
+       bool "Correctable Errors Collector"
+       depends on X86_MCE && MEMORY_FAILURE && DEBUG_FS
+       ---help---
+         This is a small cache which collects correctable memory errors per 4K
+         page PFN and counts their repeated occurrence. Once the counter for a
+         PFN overflows, we try to soft-offline that page as we take it to mean
+         that it has reached a relatively high error count and would probably
+         be best if we don't use it anymore.
+
+         Bear in mind that this is absolutely useless if your platform doesn't
+         have ECC DIMMs and doesn't have DRAM ECC checking enabled in the BIOS.
+
index d7f73341ced35a88da5ca3331237c1a46270effa..7b26dd3aa5d09407f4fc7f138a978c9cb8648a9a 100644 (file)
@@ -1 +1,2 @@
-obj-$(CONFIG_RAS) += ras.o debugfs.o
+obj-$(CONFIG_RAS)      += ras.o debugfs.o
+obj-$(CONFIG_RAS_CEC)  += cec.o
diff --git a/drivers/ras/cec.c b/drivers/ras/cec.c
new file mode 100644 (file)
index 0000000..6aab46d
--- /dev/null
@@ -0,0 +1,532 @@
+#include <linux/mm.h>
+#include <linux/gfp.h>
+#include <linux/kernel.h>
+
+#include <asm/mce.h>
+
+#include "debugfs.h"
+
+/*
+ * RAS Correctable Errors Collector
+ *
+ * This is a simple gadget which collects correctable errors and counts their
+ * occurrence per physical page address.
+ *
+ * We've opted for possibly the simplest data structure to collect those - an
+ * array of the size of a memory page. It stores 512 u64's with the following
+ * structure:
+ *
+ * [63 ... PFN ... 12 | 11 ... generation ... 10 | 9 ... count ... 0]
+ *
+ * The generation in the two highest order bits is two bits which are set to 11b
+ * on every insertion. During the course of each entry's existence, the
+ * generation field gets decremented during spring cleaning to 10b, then 01b and
+ * then 00b.
+ *
+ * This way we're employing the natural numeric ordering to make sure that newly
+ * inserted/touched elements have higher 12-bit counts (which we've manufactured)
+ * and thus iterating over the array initially won't kick out those elements
+ * which were inserted last.
+ *
+ * Spring cleaning is what we do when we reach a certain number CLEAN_ELEMS of
+ * elements entered into the array, during which, we're decaying all elements.
+ * If, after decay, an element gets inserted again, its generation is set to 11b
+ * to make sure it has higher numerical count than other, older elements and
+ * thus emulate an an LRU-like behavior when deleting elements to free up space
+ * in the page.
+ *
+ * When an element reaches it's max count of count_threshold, we try to poison
+ * it by assuming that errors triggered count_threshold times in a single page
+ * are excessive and that page shouldn't be used anymore. count_threshold is
+ * initialized to COUNT_MASK which is the maximum.
+ *
+ * That error event entry causes cec_add_elem() to return !0 value and thus
+ * signal to its callers to log the error.
+ *
+ * To the question why we've chosen a page and moving elements around with
+ * memmove(), it is because it is a very simple structure to handle and max data
+ * movement is 4K which on highly optimized modern CPUs is almost unnoticeable.
+ * We wanted to avoid the pointer traversal of more complex structures like a
+ * linked list or some sort of a balancing search tree.
+ *
+ * Deleting an element takes O(n) but since it is only a single page, it should
+ * be fast enough and it shouldn't happen all too often depending on error
+ * patterns.
+ */
+
+#undef pr_fmt
+#define pr_fmt(fmt) "RAS: " fmt
+
+/*
+ * We use DECAY_BITS bits of PAGE_SHIFT bits for counting decay, i.e., how long
+ * elements have stayed in the array without having been accessed again.
+ */
+#define DECAY_BITS             2
+#define DECAY_MASK             ((1ULL << DECAY_BITS) - 1)
+#define MAX_ELEMS              (PAGE_SIZE / sizeof(u64))
+
+/*
+ * Threshold amount of inserted elements after which we start spring
+ * cleaning.
+ */
+#define CLEAN_ELEMS            (MAX_ELEMS >> DECAY_BITS)
+
+/* Bits which count the number of errors happened in this 4K page. */
+#define COUNT_BITS             (PAGE_SHIFT - DECAY_BITS)
+#define COUNT_MASK             ((1ULL << COUNT_BITS) - 1)
+#define FULL_COUNT_MASK                (PAGE_SIZE - 1)
+
+/*
+ * u64: [ 63 ... 12 | DECAY_BITS | COUNT_BITS ]
+ */
+
+#define PFN(e)                 ((e) >> PAGE_SHIFT)
+#define DECAY(e)               (((e) >> COUNT_BITS) & DECAY_MASK)
+#define COUNT(e)               ((unsigned int)(e) & COUNT_MASK)
+#define FULL_COUNT(e)          ((e) & (PAGE_SIZE - 1))
+
+static struct ce_array {
+       u64 *array;                     /* container page */
+       unsigned int n;                 /* number of elements in the array */
+
+       unsigned int decay_count;       /*
+                                        * number of element insertions/increments
+                                        * since the last spring cleaning.
+                                        */
+
+       u64 pfns_poisoned;              /*
+                                        * number of PFNs which got poisoned.
+                                        */
+
+       u64 ces_entered;                /*
+                                        * The number of correctable errors
+                                        * entered into the collector.
+                                        */
+
+       u64 decays_done;                /*
+                                        * Times we did spring cleaning.
+                                        */
+
+       union {
+               struct {
+                       __u32   disabled : 1,   /* cmdline disabled */
+                       __resv   : 31;
+               };
+               __u32 flags;
+       };
+} ce_arr;
+
+static DEFINE_MUTEX(ce_mutex);
+static u64 dfs_pfn;
+
+/* Amount of errors after which we offline */
+static unsigned int count_threshold = COUNT_MASK;
+
+/*
+ * The timer "decays" element count each timer_interval which is 24hrs by
+ * default.
+ */
+
+#define CEC_TIMER_DEFAULT_INTERVAL     24 * 60 * 60    /* 24 hrs */
+#define CEC_TIMER_MIN_INTERVAL          1 * 60 * 60    /* 1h */
+#define CEC_TIMER_MAX_INTERVAL    30 * 24 * 60 * 60    /* one month */
+static struct timer_list cec_timer;
+static u64 timer_interval = CEC_TIMER_DEFAULT_INTERVAL;
+
+/*
+ * Decrement decay value. We're using DECAY_BITS bits to denote decay of an
+ * element in the array. On insertion and any access, it gets reset to max.
+ */
+static void do_spring_cleaning(struct ce_array *ca)
+{
+       int i;
+
+       for (i = 0; i < ca->n; i++) {
+               u8 decay = DECAY(ca->array[i]);
+
+               if (!decay)
+                       continue;
+
+               decay--;
+
+               ca->array[i] &= ~(DECAY_MASK << COUNT_BITS);
+               ca->array[i] |= (decay << COUNT_BITS);
+       }
+       ca->decay_count = 0;
+       ca->decays_done++;
+}
+
+/*
+ * @interval in seconds
+ */
+static void cec_mod_timer(struct timer_list *t, unsigned long interval)
+{
+       unsigned long iv;
+
+       iv = interval * HZ + jiffies;
+
+       mod_timer(t, round_jiffies(iv));
+}
+
+static void cec_timer_fn(unsigned long data)
+{
+       struct ce_array *ca = (struct ce_array *)data;
+
+       do_spring_cleaning(ca);
+
+       cec_mod_timer(&cec_timer, timer_interval);
+}
+
+/*
+ * @to: index of the smallest element which is >= then @pfn.
+ *
+ * Return the index of the pfn if found, otherwise negative value.
+ */
+static int __find_elem(struct ce_array *ca, u64 pfn, unsigned int *to)
+{
+       u64 this_pfn;
+       int min = 0, max = ca->n;
+
+       while (min < max) {
+               int tmp = (max + min) >> 1;
+
+               this_pfn = PFN(ca->array[tmp]);
+
+               if (this_pfn < pfn)
+                       min = tmp + 1;
+               else if (this_pfn > pfn)
+                       max = tmp;
+               else {
+                       min = tmp;
+                       break;
+               }
+       }
+
+       if (to)
+               *to = min;
+
+       this_pfn = PFN(ca->array[min]);
+
+       if (this_pfn == pfn)
+               return min;
+
+       return -ENOKEY;
+}
+
+static int find_elem(struct ce_array *ca, u64 pfn, unsigned int *to)
+{
+       WARN_ON(!to);
+
+       if (!ca->n) {
+               *to = 0;
+               return -ENOKEY;
+       }
+       return __find_elem(ca, pfn, to);
+}
+
+static void del_elem(struct ce_array *ca, int idx)
+{
+       /* Save us a function call when deleting the last element. */
+       if (ca->n - (idx + 1))
+               memmove((void *)&ca->array[idx],
+                       (void *)&ca->array[idx + 1],
+                       (ca->n - (idx + 1)) * sizeof(u64));
+
+       ca->n--;
+}
+
+static u64 del_lru_elem_unlocked(struct ce_array *ca)
+{
+       unsigned int min = FULL_COUNT_MASK;
+       int i, min_idx = 0;
+
+       for (i = 0; i < ca->n; i++) {
+               unsigned int this = FULL_COUNT(ca->array[i]);
+
+               if (min > this) {
+                       min = this;
+                       min_idx = i;
+               }
+       }
+
+       del_elem(ca, min_idx);
+
+       return PFN(ca->array[min_idx]);
+}
+
+/*
+ * We return the 0th pfn in the error case under the assumption that it cannot
+ * be poisoned and excessive CEs in there are a serious deal anyway.
+ */
+static u64 __maybe_unused del_lru_elem(void)
+{
+       struct ce_array *ca = &ce_arr;
+       u64 pfn;
+
+       if (!ca->n)
+               return 0;
+
+       mutex_lock(&ce_mutex);
+       pfn = del_lru_elem_unlocked(ca);
+       mutex_unlock(&ce_mutex);
+
+       return pfn;
+}
+
+
+int cec_add_elem(u64 pfn)
+{
+       struct ce_array *ca = &ce_arr;
+       unsigned int to;
+       int count, ret = 0;
+
+       /*
+        * We can be called very early on the identify_cpu() path where we are
+        * not initialized yet. We ignore the error for simplicity.
+        */
+       if (!ce_arr.array || ce_arr.disabled)
+               return -ENODEV;
+
+       ca->ces_entered++;
+
+       mutex_lock(&ce_mutex);
+
+       if (ca->n == MAX_ELEMS)
+               WARN_ON(!del_lru_elem_unlocked(ca));
+
+       ret = find_elem(ca, pfn, &to);
+       if (ret < 0) {
+               /*
+                * Shift range [to-end] to make room for one more element.
+                */
+               memmove((void *)&ca->array[to + 1],
+                       (void *)&ca->array[to],
+                       (ca->n - to) * sizeof(u64));
+
+               ca->array[to] = (pfn << PAGE_SHIFT) |
+                               (DECAY_MASK << COUNT_BITS) | 1;
+
+               ca->n++;
+
+               ret = 0;
+
+               goto decay;
+       }
+
+       count = COUNT(ca->array[to]);
+
+       if (count < count_threshold) {
+               ca->array[to] |= (DECAY_MASK << COUNT_BITS);
+               ca->array[to]++;
+
+               ret = 0;
+       } else {
+               u64 pfn = ca->array[to] >> PAGE_SHIFT;
+
+               if (!pfn_valid(pfn)) {
+                       pr_warn("CEC: Invalid pfn: 0x%llx\n", pfn);
+               } else {
+                       /* We have reached max count for this page, soft-offline it. */
+                       pr_err("Soft-offlining pfn: 0x%llx\n", pfn);
+                       memory_failure_queue(pfn, 0, MF_SOFT_OFFLINE);
+                       ca->pfns_poisoned++;
+               }
+
+               del_elem(ca, to);
+
+               /*
+                * Return a >0 value to denote that we've reached the offlining
+                * threshold.
+                */
+               ret = 1;
+
+               goto unlock;
+       }
+
+decay:
+       ca->decay_count++;
+
+       if (ca->decay_count >= CLEAN_ELEMS)
+               do_spring_cleaning(ca);
+
+unlock:
+       mutex_unlock(&ce_mutex);
+
+       return ret;
+}
+
+static int u64_get(void *data, u64 *val)
+{
+       *val = *(u64 *)data;
+
+       return 0;
+}
+
+static int pfn_set(void *data, u64 val)
+{
+       *(u64 *)data = val;
+
+       return cec_add_elem(val);
+}
+
+DEFINE_DEBUGFS_ATTRIBUTE(pfn_ops, u64_get, pfn_set, "0x%llx\n");
+
+static int decay_interval_set(void *data, u64 val)
+{
+       *(u64 *)data = val;
+
+       if (val < CEC_TIMER_MIN_INTERVAL)
+               return -EINVAL;
+
+       if (val > CEC_TIMER_MAX_INTERVAL)
+               return -EINVAL;
+
+       timer_interval = val;
+
+       cec_mod_timer(&cec_timer, timer_interval);
+       return 0;
+}
+DEFINE_DEBUGFS_ATTRIBUTE(decay_interval_ops, u64_get, decay_interval_set, "%lld\n");
+
+static int count_threshold_set(void *data, u64 val)
+{
+       *(u64 *)data = val;
+
+       if (val > COUNT_MASK)
+               val = COUNT_MASK;
+
+       count_threshold = val;
+
+       return 0;
+}
+DEFINE_DEBUGFS_ATTRIBUTE(count_threshold_ops, u64_get, count_threshold_set, "%lld\n");
+
+static int array_dump(struct seq_file *m, void *v)
+{
+       struct ce_array *ca = &ce_arr;
+       u64 prev = 0;
+       int i;
+
+       mutex_lock(&ce_mutex);
+
+       seq_printf(m, "{ n: %d\n", ca->n);
+       for (i = 0; i < ca->n; i++) {
+               u64 this = PFN(ca->array[i]);
+
+               seq_printf(m, " %03d: [%016llx|%03llx]\n", i, this, FULL_COUNT(ca->array[i]));
+
+               WARN_ON(prev > this);
+
+               prev = this;
+       }
+
+       seq_printf(m, "}\n");
+
+       seq_printf(m, "Stats:\nCEs: %llu\nofflined pages: %llu\n",
+                  ca->ces_entered, ca->pfns_poisoned);
+
+       seq_printf(m, "Flags: 0x%x\n", ca->flags);
+
+       seq_printf(m, "Timer interval: %lld seconds\n", timer_interval);
+       seq_printf(m, "Decays: %lld\n", ca->decays_done);
+
+       seq_printf(m, "Action threshold: %d\n", count_threshold);
+
+       mutex_unlock(&ce_mutex);
+
+       return 0;
+}
+
+static int array_open(struct inode *inode, struct file *filp)
+{
+       return single_open(filp, array_dump, NULL);
+}
+
+static const struct file_operations array_ops = {
+       .owner   = THIS_MODULE,
+       .open    = array_open,
+       .read    = seq_read,
+       .llseek  = seq_lseek,
+       .release = single_release,
+};
+
+static int __init create_debugfs_nodes(void)
+{
+       struct dentry *d, *pfn, *decay, *count, *array;
+
+       d = debugfs_create_dir("cec", ras_debugfs_dir);
+       if (!d) {
+               pr_warn("Error creating cec debugfs node!\n");
+               return -1;
+       }
+
+       pfn = debugfs_create_file("pfn", S_IRUSR | S_IWUSR, d, &dfs_pfn, &pfn_ops);
+       if (!pfn) {
+               pr_warn("Error creating pfn debugfs node!\n");
+               goto err;
+       }
+
+       array = debugfs_create_file("array", S_IRUSR, d, NULL, &array_ops);
+       if (!array) {
+               pr_warn("Error creating array debugfs node!\n");
+               goto err;
+       }
+
+       decay = debugfs_create_file("decay_interval", S_IRUSR | S_IWUSR, d,
+                                   &timer_interval, &decay_interval_ops);
+       if (!decay) {
+               pr_warn("Error creating decay_interval debugfs node!\n");
+               goto err;
+       }
+
+       count = debugfs_create_file("count_threshold", S_IRUSR | S_IWUSR, d,
+                                   &count_threshold, &count_threshold_ops);
+       if (!decay) {
+               pr_warn("Error creating count_threshold debugfs node!\n");
+               goto err;
+       }
+
+
+       return 0;
+
+err:
+       debugfs_remove_recursive(d);
+
+       return 1;
+}
+
+void __init cec_init(void)
+{
+       if (ce_arr.disabled)
+               return;
+
+       ce_arr.array = (void *)get_zeroed_page(GFP_KERNEL);
+       if (!ce_arr.array) {
+               pr_err("Error allocating CE array page!\n");
+               return;
+       }
+
+       if (create_debugfs_nodes())
+               return;
+
+       setup_timer(&cec_timer, cec_timer_fn, (unsigned long)&ce_arr);
+       cec_mod_timer(&cec_timer, CEC_TIMER_DEFAULT_INTERVAL);
+
+       pr_info("Correctable Errors collector initialized.\n");
+}
+
+int __init parse_cec_param(char *str)
+{
+       if (!str)
+               return 0;
+
+       if (*str == '=')
+               str++;
+
+       if (!strncmp(str, "cec_disable", 7))
+               ce_arr.disabled = 1;
+       else
+               return 0;
+
+       return 1;
+}
index 0322acf67ea5efc88c6656b9dca3868d9764dc71..501603057dffead22f4f795159afc3e41eb09921 100644 (file)
@@ -1,6 +1,6 @@
 #include <linux/debugfs.h>
 
-static struct dentry *ras_debugfs_dir;
+struct dentry *ras_debugfs_dir;
 
 static atomic_t trace_count = ATOMIC_INIT(0);
 
diff --git a/drivers/ras/debugfs.h b/drivers/ras/debugfs.h
new file mode 100644 (file)
index 0000000..db72e45
--- /dev/null
@@ -0,0 +1,8 @@
+#ifndef __RAS_DEBUGFS_H__
+#define __RAS_DEBUGFS_H__
+
+#include <linux/debugfs.h>
+
+extern struct dentry *ras_debugfs_dir;
+
+#endif /* __RAS_DEBUGFS_H__ */
index b67dd362b7b6cff2f62d2f431c9d25c4d444df79..94f8038864b4eeabea875b1fb888efcff52b517d 100644 (file)
@@ -27,3 +27,14 @@ subsys_initcall(ras_init);
 EXPORT_TRACEPOINT_SYMBOL_GPL(extlog_mem_event);
 #endif
 EXPORT_TRACEPOINT_SYMBOL_GPL(mc_event);
+
+
+int __init parse_ras_param(char *str)
+{
+#ifdef CONFIG_RAS_CEC
+       parse_cec_param(str);
+#endif
+
+       return 1;
+}
+__setup("ras", parse_ras_param);
index 2aceeafd6fe51fb34f5b99a4c506f92e3aa0353c..ffb147185e8df8ae62e4566d7da1b666fc290e1a 100644 (file)
@@ -1,14 +1,25 @@
 #ifndef __RAS_H__
 #define __RAS_H__
 
+#include <asm/errno.h>
+
 #ifdef CONFIG_DEBUG_FS
 int ras_userspace_consumers(void);
 void ras_debugfs_init(void);
 int ras_add_daemon_trace(void);
 #else
 static inline int ras_userspace_consumers(void) { return 0; }
-static inline void ras_debugfs_init(void) { return; }
+static inline void ras_debugfs_init(void) { }
 static inline int ras_add_daemon_trace(void) { return 0; }
 #endif
 
+#ifdef CONFIG_RAS_CEC
+void __init cec_init(void);
+int __init parse_cec_param(char *str);
+int cec_add_elem(u64 pfn);
+#else
+static inline void __init cec_init(void)       { }
+static inline int cec_add_elem(u64 pfn)                { return -ENODEV; }
 #endif
+
+#endif /* __RAS_H__ */