Some systems may report spurious MCA errors. In general, spurious MCA errors may be disabled by clearing a particular bit in MCA_CTL. However, clearing a bit in MCA_CTL may not be recommended for some errors, so the only option is to ignore them. An MCA error is printed and handled after it has been added to the MCE event pool. So an MCA error can be ignored by not adding it to that pool in the first place. Add such a filtering function. [ bp: Move function prototype to the internal header and massage. ] Signed-off-by: Yazen Ghannam <yazen.ghannam@amd.com> Signed-off-by: Borislav Petkov <bp@suse.de> Cc: Arnd Bergmann <arnd@arndb.de> Cc: "clemej@gmail.com" <clemej@gmail.com> Cc: "H. Peter Anvin" <hpa@zytor.com> Cc: Ingo Molnar <mingo@redhat.com> Cc: Pu Wen <puwen@hygon.cn> Cc: Qiuxu Zhuo <qiuxu.zhuo@intel.com> Cc: "rafal@milecki.pl" <rafal@milecki.pl> Cc: Shirish S <Shirish.S@amd.com> Cc: <stable@vger.kernel.org> # 5.0.x Cc: Thomas Gleixner <tglx@linutronix.de> Cc: Tony Luck <tony.luck@intel.com> Cc: Vishal Verma <vishal.l.verma@intel.com> Cc: x86-ml <x86@kernel.org> Link: https://lkml.kernel.org/r/20190325163410.171021-1-Yazen.Ghannam@amd.com
149 lines
3.3 KiB
C
149 lines
3.3 KiB
C
/*
|
|
* MCE event pool management in MCE context
|
|
*
|
|
* Copyright (C) 2015 Intel Corp.
|
|
* Author: Chen, Gong <gong.chen@linux.intel.com>
|
|
*
|
|
* This file is licensed under GPLv2.
|
|
*/
|
|
#include <linux/smp.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/genalloc.h>
|
|
#include <linux/llist.h>
|
|
#include "internal.h"
|
|
|
|
/*
|
|
* printk() is not safe in MCE context. This is a lock-less memory allocator
|
|
* used to save error information organized in a lock-less list.
|
|
*
|
|
* This memory pool is only to be used to save MCE records in MCE context.
|
|
* MCE events are rare, so a fixed size memory pool should be enough. Use
|
|
* 2 pages to save MCE events for now (~80 MCE records at most).
|
|
*/
|
|
#define MCE_POOLSZ (2 * PAGE_SIZE)
|
|
|
|
static struct gen_pool *mce_evt_pool;
|
|
static LLIST_HEAD(mce_event_llist);
|
|
static char gen_pool_buf[MCE_POOLSZ];
|
|
|
|
/*
|
|
* Compare the record "t" with each of the records on list "l" to see if
|
|
* an equivalent one is present in the list.
|
|
*/
|
|
static bool is_duplicate_mce_record(struct mce_evt_llist *t, struct mce_evt_llist *l)
|
|
{
|
|
struct mce_evt_llist *node;
|
|
struct mce *m1, *m2;
|
|
|
|
m1 = &t->mce;
|
|
|
|
llist_for_each_entry(node, &l->llnode, llnode) {
|
|
m2 = &node->mce;
|
|
|
|
if (!mce_cmp(m1, m2))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* The system has panicked - we'd like to peruse the list of MCE records
|
|
* that have been queued, but not seen by anyone yet. The list is in
|
|
* reverse time order, so we need to reverse it. While doing that we can
|
|
* also drop duplicate records (these were logged because some banks are
|
|
* shared between cores or by all threads on a socket).
|
|
*/
|
|
struct llist_node *mce_gen_pool_prepare_records(void)
|
|
{
|
|
struct llist_node *head;
|
|
LLIST_HEAD(new_head);
|
|
struct mce_evt_llist *node, *t;
|
|
|
|
head = llist_del_all(&mce_event_llist);
|
|
if (!head)
|
|
return NULL;
|
|
|
|
/* squeeze out duplicates while reversing order */
|
|
llist_for_each_entry_safe(node, t, head, llnode) {
|
|
if (!is_duplicate_mce_record(node, t))
|
|
llist_add(&node->llnode, &new_head);
|
|
}
|
|
|
|
return new_head.first;
|
|
}
|
|
|
|
void mce_gen_pool_process(struct work_struct *__unused)
|
|
{
|
|
struct llist_node *head;
|
|
struct mce_evt_llist *node, *tmp;
|
|
struct mce *mce;
|
|
|
|
head = llist_del_all(&mce_event_llist);
|
|
if (!head)
|
|
return;
|
|
|
|
head = llist_reverse_order(head);
|
|
llist_for_each_entry_safe(node, tmp, head, llnode) {
|
|
mce = &node->mce;
|
|
blocking_notifier_call_chain(&x86_mce_decoder_chain, 0, mce);
|
|
gen_pool_free(mce_evt_pool, (unsigned long)node, sizeof(*node));
|
|
}
|
|
}
|
|
|
|
bool mce_gen_pool_empty(void)
|
|
{
|
|
return llist_empty(&mce_event_llist);
|
|
}
|
|
|
|
int mce_gen_pool_add(struct mce *mce)
|
|
{
|
|
struct mce_evt_llist *node;
|
|
|
|
if (filter_mce(mce))
|
|
return -EINVAL;
|
|
|
|
if (!mce_evt_pool)
|
|
return -EINVAL;
|
|
|
|
node = (void *)gen_pool_alloc(mce_evt_pool, sizeof(*node));
|
|
if (!node) {
|
|
pr_warn_ratelimited("MCE records pool full!\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
memcpy(&node->mce, mce, sizeof(*mce));
|
|
llist_add(&node->llnode, &mce_event_llist);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mce_gen_pool_create(void)
|
|
{
|
|
struct gen_pool *tmpp;
|
|
int ret = -ENOMEM;
|
|
|
|
tmpp = gen_pool_create(ilog2(sizeof(struct mce_evt_llist)), -1);
|
|
if (!tmpp)
|
|
goto out;
|
|
|
|
ret = gen_pool_add(tmpp, (unsigned long)gen_pool_buf, MCE_POOLSZ, -1);
|
|
if (ret) {
|
|
gen_pool_destroy(tmpp);
|
|
goto out;
|
|
}
|
|
|
|
mce_evt_pool = tmpp;
|
|
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
int mce_gen_pool_init(void)
|
|
{
|
|
/* Just init mce_gen_pool once. */
|
|
if (mce_evt_pool)
|
|
return 0;
|
|
|
|
return mce_gen_pool_create();
|
|
}
|