2012-10-31 17:45:37

by Mauro Carvalho Chehab

[permalink] [raw]
Subject: [RFC EDAC/GHES 0/3] Add EDAC support on GHES

It turns that making ghes to talk with EDAC to not be a hard
task.

This patch series makes ghes->edac integration.

This was compile-tested only, as its, for now, just a proof of
concept.

There are lots of space for improvements, like:

- maybe split edac-ghes into a separate file;

- add a way to retrieve the DIMM label (not sure if APEI GHES
interface supports it);

- detect the memory layout via ACPI;

Comments, sugestions etc are welcome.

Mauro Carvalho Chehab (3):
ghes: Be a good citzen with EDAC
edac: add support for raw error reports
ghes: add support for reporting errors via EDAC

drivers/acpi/apei/Kconfig | 1 +
drivers/acpi/apei/Makefile | 2 +
drivers/acpi/apei/ghes.c | 129 +++++++++++++++++++++++++++++++++++++++++++--
drivers/edac/edac_core.h | 17 ++++++
drivers/edac/edac_mc.c | 109 ++++++++++++++++++++++++++++----------
include/linux/edac.h | 5 ++
6 files changed, 233 insertions(+), 30 deletions(-)

--
1.7.11.7


2012-10-31 17:46:14

by Mauro Carvalho Chehab

[permalink] [raw]
Subject: [RFC EDAC/GHES 3/3] ghes: add support for reporting errors via EDAC

Signed-off-by: Mauro Carvalho Chehab <[email protected]>
---
drivers/acpi/apei/ghes.c | 52 +++++++++++++++++++++++++++++++++++++++++++++---
include/linux/edac.h | 1 +
2 files changed, 50 insertions(+), 3 deletions(-)

diff --git a/drivers/acpi/apei/ghes.c b/drivers/acpi/apei/ghes.c
index 9466d36..54c2d97 100644
--- a/drivers/acpi/apei/ghes.c
+++ b/drivers/acpi/apei/ghes.c
@@ -115,6 +115,7 @@ struct ghes {
struct ghes_estatus_node {
struct llist_node llnode;
struct acpi_hest_generic *generic;
+ struct ghes *ghes;
};

struct ghes_estatus_cache {
@@ -457,7 +458,49 @@ static void ghes_clear_estatus(struct ghes *ghes)
ghes->flags &= ~GHES_TO_CLEAR;
}

-static void ghes_do_proc(const struct acpi_hest_generic_status *estatus)
+static void ghes_edac_report_mem_error(struct ghes *ghes, int sev,
+ struct cper_sec_mem_err *mem_err)
+{
+#ifdef CONFIG_EDAC_MM_EDAC
+ enum hw_event_mc_err_type type;
+ unsigned long page = 0, offset = 0, grain = 0;
+ char location[80];
+ char *label = "unknown";
+
+ if (mem_err->validation_bits & CPER_MEM_VALID_PHYSICAL_ADDRESS) {
+ page = mem_err->physical_addr >> PAGE_SHIFT;
+ offset = mem_err->physical_addr & ~PAGE_MASK;
+ grain = ~(mem_err->physical_addr_mask & ~PAGE_MASK);
+ }
+
+ switch(sev) {
+ case GHES_SEV_CORRECTED:
+ type = HW_EVENT_ERR_CORRECTED;
+ break;
+ case GHES_SEV_RECOVERABLE:
+ type = HW_EVENT_ERR_UNCORRECTED;
+ break;
+ case GHES_SEV_PANIC:
+ type = HW_EVENT_ERR_FATAL;
+ break;
+ default:
+ case GHES_SEV_NO:
+ type = HW_EVENT_ERR_INFO;
+ }
+
+ sprintf(location,"node:%d card:%d module:%d bank:%d device:%d row: %d column:%d bit_pos:%d",
+ mem_err->node, mem_err->card, mem_err->module,
+ mem_err->bank, mem_err->device, mem_err->row, mem_err->column,
+ mem_err->bit_pos);
+
+ edac_raw_mc_handle_error(type, ghes->mci, grain, 1, 0, 0, 0,
+ page, offset, 0,
+ "APEI", location, label, "", 0);
+#endif
+}
+
+static void ghes_do_proc(struct ghes *ghes,
+ const struct acpi_hest_generic_status *estatus)
{
int sev, sec_sev;
struct acpi_hest_generic_data *gdata;
@@ -469,6 +512,8 @@ static void ghes_do_proc(const struct acpi_hest_generic_status *estatus)
CPER_SEC_PLATFORM_MEM)) {
struct cper_sec_mem_err *mem_err;
mem_err = (struct cper_sec_mem_err *)(gdata+1);
+ ghes_edac_report_mem_error(ghes, sev, mem_err);
+
#ifdef CONFIG_X86_MCE
apei_mce_report_mem_error(sev == GHES_SEV_CORRECTED,
mem_err);
@@ -687,7 +732,7 @@ static int ghes_proc(struct ghes *ghes)
if (ghes_print_estatus(NULL, ghes->generic, ghes->estatus))
ghes_estatus_cache_add(ghes->generic, ghes->estatus);
}
- ghes_do_proc(ghes->estatus);
+ ghes_do_proc(ghes, ghes->estatus);
out:
ghes_clear_estatus(ghes);
return 0;
@@ -780,7 +825,7 @@ static void ghes_proc_in_irq(struct irq_work *irq_work)
estatus = GHES_ESTATUS_FROM_NODE(estatus_node);
len = apei_estatus_len(estatus);
node_len = GHES_ESTATUS_NODE_LEN(len);
- ghes_do_proc(estatus);
+ ghes_do_proc(estatus_node->ghes, estatus);
if (!ghes_estatus_cached(estatus)) {
generic = estatus_node->generic;
if (ghes_print_estatus(NULL, generic, estatus))
@@ -869,6 +914,7 @@ static int ghes_notify_nmi(unsigned int cmd, struct pt_regs *regs)
estatus_node = (void *)gen_pool_alloc(ghes_estatus_pool,
node_len);
if (estatus_node) {
+ estatus_node->ghes = ghes;
estatus_node->generic = ghes->generic;
estatus = GHES_ESTATUS_FROM_NODE(estatus_node);
memcpy(estatus, ghes->estatus, len);
diff --git a/include/linux/edac.h b/include/linux/edac.h
index 1e9d19b..f26fe40 100644
--- a/include/linux/edac.h
+++ b/include/linux/edac.h
@@ -100,6 +100,7 @@ enum hw_event_mc_err_type {
HW_EVENT_ERR_CORRECTED,
HW_EVENT_ERR_UNCORRECTED,
HW_EVENT_ERR_FATAL,
+ HW_EVENT_ERR_INFO,
};

/**
--
1.7.11.7

2012-10-31 17:47:05

by Mauro Carvalho Chehab

[permalink] [raw]
Subject: [RFC EDAC/GHES 1/3] ghes: Be a good citzen with EDAC

Register itself at EDAC MC core, in order to avoid other
drivers to get it.

The edac core will warrant that just one driver will be used,
so the first one to register will be the one that will be
reporting the hardware errors.

Signed-off-by: Mauro Carvalho Chehab <[email protected]>
---
drivers/acpi/apei/Kconfig | 1 +
drivers/acpi/apei/Makefile | 2 ++
drivers/acpi/apei/ghes.c | 77 ++++++++++++++++++++++++++++++++++++++++++++++
include/linux/edac.h | 4 +++
4 files changed, 84 insertions(+)

diff --git a/drivers/acpi/apei/Kconfig b/drivers/acpi/apei/Kconfig
index f0c1ce9..a9c8088 100644
--- a/drivers/acpi/apei/Kconfig
+++ b/drivers/acpi/apei/Kconfig
@@ -12,6 +12,7 @@ config ACPI_APEI
config ACPI_APEI_GHES
bool "APEI Generic Hardware Error Source"
depends on ACPI_APEI && X86
+ depends on !(EDAC_MM_EDAC=m)
select ACPI_HED
select IRQ_WORK
select GENERIC_ALLOCATOR
diff --git a/drivers/acpi/apei/Makefile b/drivers/acpi/apei/Makefile
index d1d1bc0..ee7d80f 100644
--- a/drivers/acpi/apei/Makefile
+++ b/drivers/acpi/apei/Makefile
@@ -4,3 +4,5 @@ obj-$(CONFIG_ACPI_APEI_EINJ) += einj.o
obj-$(CONFIG_ACPI_APEI_ERST_DEBUG) += erst-dbg.o

apei-y := apei-base.o hest.o cper.o erst.o
+
+ccflags-y += -I$(srctree)/drivers/edac
diff --git a/drivers/acpi/apei/ghes.c b/drivers/acpi/apei/ghes.c
index 1599566..9466d36 100644
--- a/drivers/acpi/apei/ghes.c
+++ b/drivers/acpi/apei/ghes.c
@@ -48,6 +48,9 @@
#include <linux/genalloc.h>
#include <linux/pci.h>
#include <linux/aer.h>
+#include <linux/edac.h>
+#include <edac_core.h>
+
#include <acpi/apei.h>
#include <acpi/hed.h>
#include <asm/mce.h>
@@ -105,6 +108,8 @@ struct ghes {
struct timer_list timer;
unsigned int irq;
};
+
+ struct mem_ctl_info *mci;
};

struct ghes_estatus_node {
@@ -901,6 +906,71 @@ static unsigned long ghes_esource_prealloc_size(
return prealloc_size;
}

+static int __devinit ghes_edac_register(struct ghes *ghes, struct device *dev)
+{
+#ifdef CONFIG_EDAC_MM_EDAC
+ int rc;
+ struct mem_ctl_info *mci;
+ struct edac_mc_layer layers[1];
+ struct csrow_info *csrow;
+ struct dimm_info *dimm;
+
+ layers[0].type = EDAC_MC_LAYER_ALL_MEM;
+ layers[0].size = 0;
+ layers[0].is_virt_csrow = true;
+ mci = edac_mc_alloc(0, ARRAY_SIZE(layers), layers, 0);
+ if (!mci) {
+ pr_info(GHES_PFX "Can't allocate memory for EDAC data\n");
+ return -ENOMEM;
+ }
+
+ mci->pvt_info = ghes;
+ mci->pdev = dev;
+
+#if 0
+ mci->mtype_cap = MEM_FLAG_RDDR | MEM_FLAG_DDR;
+ mci->edac_ctl_cap = EDAC_FLAG_NONE | EDAC_FLAG_SECDED;
+ mci->edac_cap = EDAC_FLAG_SECDED;
+ mci->mod_name = EDAC_MOD_STR;
+ mci->mod_ver = MV64x60_REVISION;
+ mci->ctl_name = mv64x60_ctl_name;
+#endif
+ csrow = mci->csrows[0];
+ dimm = csrow->channels[0]->dimm;
+
+ /* FIXME: FAKE DATA */
+ dimm->nr_pages = 1000;
+ dimm->grain = 128;
+ dimm->mtype = MEM_UNKNOWN;
+ dimm->dtype = DEV_UNKNOWN;
+ dimm->edac_mode = EDAC_SECDED;
+
+ rc = edac_mc_add_mc(mci, THIS_MODULE);
+ if (rc < 0) {
+ pr_info(GHES_PFX "Can't register at EDAC core\n");
+ edac_mc_free(mci);
+
+ return -ENODEV;
+ }
+
+ ghes->mci = mci;
+#endif
+ return 0;
+}
+
+static void __devexit ghes_edac_unregister(struct ghes *ghes)
+{
+ struct mem_ctl_info *mci = ghes->mci;
+
+#ifdef CONFIG_EDAC_MM_EDAC
+ if (!mci)
+ return;
+
+ edac_mc_del_mc(mci->pdev);
+ edac_mc_free(mci);
+#endif
+}
+
static int __devinit ghes_probe(struct platform_device *ghes_dev)
{
struct acpi_hest_generic *generic;
@@ -985,6 +1055,10 @@ static int __devinit ghes_probe(struct platform_device *ghes_dev)
}
platform_set_drvdata(ghes_dev, ghes);

+ rc = ghes_edac_register(ghes, &ghes_dev->dev);
+ if (rc < 0)
+ goto err;
+
return 0;
err:
if (ghes) {
@@ -1038,6 +1112,9 @@ static int __devexit ghes_remove(struct platform_device *ghes_dev)
}

ghes_fini(ghes);
+
+ ghes_edac_unregister(ghes);
+
kfree(ghes);

platform_set_drvdata(ghes_dev, NULL);
diff --git a/include/linux/edac.h b/include/linux/edac.h
index aeddb3f..1e9d19b 100644
--- a/include/linux/edac.h
+++ b/include/linux/edac.h
@@ -375,6 +375,9 @@ enum scrub_type {
* @EDAC_MC_LAYER_CHANNEL: memory layer is named "channel"
* @EDAC_MC_LAYER_SLOT: memory layer is named "slot"
* @EDAC_MC_LAYER_CHIP_SELECT: memory layer is named "chip select"
+ * @EDAC_MC_LAYER_UNKNOWN: memory layout is unknown. All memory is mapped
+ * as a single memory area. This is used when
+ * retrieving errors from apei/ghes driver.
*
* This enum is used by the drivers to tell edac_mc_sysfs what name should
* be used when describing a memory stick location.
@@ -384,6 +387,7 @@ enum edac_mc_layer_type {
EDAC_MC_LAYER_CHANNEL,
EDAC_MC_LAYER_SLOT,
EDAC_MC_LAYER_CHIP_SELECT,
+ EDAC_MC_LAYER_ALL_MEM,
};

/**
--
1.7.11.7

2012-10-31 17:47:01

by Mauro Carvalho Chehab

[permalink] [raw]
Subject: [RFC EDAC/GHES 2/3] edac: add support for raw error reports

That allows APEI GHES driver to report errors directly, using
the EDAC error report API.

Signed-off-by: Mauro Carvalho Chehab <[email protected]>
---
drivers/edac/edac_core.h | 17 ++++++++
drivers/edac/edac_mc.c | 109 +++++++++++++++++++++++++++++++++++------------
2 files changed, 99 insertions(+), 27 deletions(-)

diff --git a/drivers/edac/edac_core.h b/drivers/edac/edac_core.h
index db30694..d936958 100644
--- a/drivers/edac/edac_core.h
+++ b/drivers/edac/edac_core.h
@@ -453,6 +453,23 @@ extern struct mem_ctl_info *find_mci_by_dev(struct device *dev);
extern struct mem_ctl_info *edac_mc_del_mc(struct device *dev);
extern int edac_mc_find_csrow_by_page(struct mem_ctl_info *mci,
unsigned long page);
+
+void edac_raw_mc_handle_error(const enum hw_event_mc_err_type type,
+ struct mem_ctl_info *mci,
+ long grain,
+ const u16 error_count,
+ const int top_layer,
+ const int mid_layer,
+ const int low_layer,
+ const unsigned long page_frame_number,
+ const unsigned long offset_in_page,
+ const unsigned long syndrome,
+ const char *msg,
+ const char *location,
+ const char *label,
+ const char *other_detail,
+ const bool enable_per_layer_report);
+
void edac_mc_handle_error(const enum hw_event_mc_err_type type,
struct mem_ctl_info *mci,
const u16 error_count,
diff --git a/drivers/edac/edac_mc.c b/drivers/edac/edac_mc.c
index d4d891a..d3e9341 100644
--- a/drivers/edac/edac_mc.c
+++ b/drivers/edac/edac_mc.c
@@ -1079,6 +1079,81 @@ static void edac_ue_error(struct mem_ctl_info *mci,
#define OTHER_LABEL " or "

/**
+ * edac_raw_mc_handle_error - reports a memory event to userspace without doing
+ * anything to discover the error location
+ *
+ * @type: severity of the error (CE/UE/Fatal)
+ * @mci: a struct mem_ctl_info pointer
+ * @grain: error granularity
+ * @error_count: Number of errors of the same type
+ * @top_layer: Memory layer[0] position
+ * @mid_layer: Memory layer[1] position
+ * @low_layer: Memory layer[2] position
+ * @page_frame_number: mem page where the error occurred
+ * @offset_in_page: offset of the error inside the page
+ * @syndrome: ECC syndrome
+ * @msg: Message meaningful to the end users that
+ * explains the event\
+ * @location: location of the error, like "csrow:0 channel:1"
+ * @label: DIMM labels for the affected memory(ies)
+ * @other_detail: Technical details about the event that
+ * may help hardware manufacturers and
+ * EDAC developers to analyse the event
+ * @enable_per_layer_report: should it increment per-layer error counts?
+ *
+ * This raw function is used internally by edac_mc_handle_error(). It should
+ * only be called directly when the hardware error come directly from BIOS,
+ * like in the case of APEI GHES driver.
+ */
+void edac_raw_mc_handle_error(const enum hw_event_mc_err_type type,
+ struct mem_ctl_info *mci,
+ long grain,
+ const u16 error_count,
+ const int top_layer,
+ const int mid_layer,
+ const int low_layer,
+ const unsigned long page_frame_number,
+ const unsigned long offset_in_page,
+ const unsigned long syndrome,
+ const char *msg,
+ const char *location,
+ const char *label,
+ const char *other_detail,
+ const bool enable_per_layer_report)
+{
+ char detail[80];
+ u8 grain_bits;
+ int pos[EDAC_MAX_LAYERS] = { top_layer, mid_layer, low_layer };
+
+ /* Report the error via the trace interface */
+
+ grain_bits = fls_long(grain) + 1;
+ trace_mc_event(type, msg, label, error_count,
+ mci->mc_idx, top_layer, mid_layer, low_layer,
+ PAGES_TO_MiB(page_frame_number) | offset_in_page,
+ grain_bits, syndrome, other_detail);
+
+ /* Memory type dependent details about the error */
+ if (type == HW_EVENT_ERR_CORRECTED) {
+ snprintf(detail, sizeof(detail),
+ "page:0x%lx offset:0x%lx grain:%ld syndrome:0x%lx",
+ page_frame_number, offset_in_page,
+ grain, syndrome);
+ edac_ce_error(mci, error_count, pos, msg, location, label,
+ detail, other_detail, enable_per_layer_report,
+ page_frame_number, offset_in_page, grain);
+ } else {
+ snprintf(detail, sizeof(detail),
+ "page:0x%lx offset:0x%lx grain:%ld",
+ page_frame_number, offset_in_page, grain);
+
+ edac_ue_error(mci, error_count, pos, msg, location, label,
+ detail, other_detail, enable_per_layer_report);
+ }
+}
+EXPORT_SYMBOL_GPL(edac_raw_mc_handle_error);
+
+/**
* edac_mc_handle_error - reports a memory event to userspace
*
* @type: severity of the error (CE/UE/Fatal)
@@ -1109,7 +1184,7 @@ void edac_mc_handle_error(const enum hw_event_mc_err_type type,
const char *other_detail)
{
/* FIXME: too much for stack: move it to some pre-alocated area */
- char detail[80], location[80];
+ char location[80];
char label[(EDAC_MC_LABEL_LEN + 1 + sizeof(OTHER_LABEL)) * mci->tot_dimms];
char *p;
int row = -1, chan = -1;
@@ -1117,7 +1192,6 @@ void edac_mc_handle_error(const enum hw_event_mc_err_type type,
int i;
long grain;
bool enable_per_layer_report = false;
- u8 grain_bits;

edac_dbg(3, "MC%d\n", mci->mc_idx);

@@ -1242,30 +1316,11 @@ void edac_mc_handle_error(const enum hw_event_mc_err_type type,
if (p > location)
*(p - 1) = '\0';

- /* Report the error via the trace interface */
-
- grain_bits = fls_long(grain) + 1;
- trace_mc_event(type, msg, label, error_count,
- mci->mc_idx, top_layer, mid_layer, low_layer,
- PAGES_TO_MiB(page_frame_number) | offset_in_page,
- grain_bits, syndrome, other_detail);
-
- /* Memory type dependent details about the error */
- if (type == HW_EVENT_ERR_CORRECTED) {
- snprintf(detail, sizeof(detail),
- "page:0x%lx offset:0x%lx grain:%ld syndrome:0x%lx",
- page_frame_number, offset_in_page,
- grain, syndrome);
- edac_ce_error(mci, error_count, pos, msg, location, label,
- detail, other_detail, enable_per_layer_report,
- page_frame_number, offset_in_page, grain);
- } else {
- snprintf(detail, sizeof(detail),
- "page:0x%lx offset:0x%lx grain:%ld",
- page_frame_number, offset_in_page, grain);
-
- edac_ue_error(mci, error_count, pos, msg, location, label,
- detail, other_detail, enable_per_layer_report);
- }
+ edac_raw_mc_handle_error(type, mci, grain, error_count,
+ top_layer, mid_layer, low_layer,
+ page_frame_number, offset_in_page,
+ syndrome,
+ msg, location, label, other_detail,
+ enable_per_layer_report);
}
EXPORT_SYMBOL_GPL(edac_mc_handle_error);
--
1.7.11.7