2019-05-27 09:55:19 +03:00
|
|
|
// SPDX-License-Identifier: GPL-2.0-only
|
2013-02-15 13:11:57 +04:00
|
|
|
/*
|
|
|
|
* GHES/EDAC Linux driver
|
|
|
|
*
|
2014-02-07 14:03:07 +04:00
|
|
|
* Copyright (c) 2013 by Mauro Carvalho Chehab
|
2013-02-15 13:11:57 +04:00
|
|
|
*
|
2020-07-08 14:35:46 +03:00
|
|
|
* Red Hat Inc. https://www.redhat.com
|
2013-02-15 13:11:57 +04:00
|
|
|
*/
|
|
|
|
|
2013-02-15 16:06:38 +04:00
|
|
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
|
|
|
|
2013-02-15 13:11:57 +04:00
|
|
|
#include <acpi/ghes.h>
|
|
|
|
#include <linux/edac.h>
|
2013-02-14 16:11:08 +04:00
|
|
|
#include <linux/dmi.h>
|
2016-10-29 20:16:34 +03:00
|
|
|
#include "edac_module.h"
|
ghes_edac: Fix RAS tracing
With the current version of CPER, there's no way to associate an
error with the memory error. So, the error location in EDAC
layers is unused.
As CPER has its own idea about memory architectural layers, just
output whatever is there inside the driver's detail at the RAS
tracepoint.
The EDAC location keeps untouched, in the case that, in some future,
we could actually map the error into the dimm labels.
Now, the error message:
[ 72.396625] {1}[Hardware Error]: Hardware error from APEI Generic Hardware Error Source: 0
[ 72.396627] {1}[Hardware Error]: APEI generic hardware error status
[ 72.396628] {1}[Hardware Error]: severity: 2, corrected
[ 72.396630] {1}[Hardware Error]: section: 0, severity: 2, corrected
[ 72.396632] {1}[Hardware Error]: flags: 0x01
[ 72.396634] {1}[Hardware Error]: primary
[ 72.396635] {1}[Hardware Error]: section_type: memory error
[ 72.396637] {1}[Hardware Error]: error_status: 0x0000000000000400
[ 72.396638] {1}[Hardware Error]: node: 3
[ 72.396639] {1}[Hardware Error]: card: 0
[ 72.396640] {1}[Hardware Error]: module: 0
[ 72.396641] {1}[Hardware Error]: device: 0
[ 72.396643] {1}[Hardware Error]: error_type: 18, unknown
[ 72.396666] EDAC MC0: 1 CE reserved error (18) on unknown label (node:3 card:0 module:0 page:0x0 offset:0x0 grain:0 syndrome:0x0 - status(0x0000000000000400): Storage error in DRAM memory)
Is properly represented on the trace event:
kworker/0:2-584 [000] .... 72.396657: mc_event: 1 Corrected error: reserved error (18) on unknown label (mc:0 location:-1:-1:-1 address:0x00000000 grain:1 syndrome:0x00000000 APEI location: node:3 card:0 module:0 status(0x0000000000000400): Storage error in DRAM memory)
Tested on a 4 sockets E5-4650 Sandy Bridge machine.
Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
2013-02-20 04:35:41 +04:00
|
|
|
#include <ras/ras_event.h>
|
2013-02-15 13:11:57 +04:00
|
|
|
|
2020-05-19 13:44:39 +03:00
|
|
|
struct ghes_pvt {
|
2013-02-15 13:11:57 +04:00
|
|
|
struct mem_ctl_info *mci;
|
2013-02-20 02:24:12 +04:00
|
|
|
|
|
|
|
/* Buffers for the error handling routine */
|
2019-11-06 12:33:25 +03:00
|
|
|
char other_detail[400];
|
2013-02-20 02:24:12 +04:00
|
|
|
char msg[80];
|
2013-02-15 13:11:57 +04:00
|
|
|
};
|
|
|
|
|
2019-11-05 23:07:51 +03:00
|
|
|
static refcount_t ghes_refcount = REFCOUNT_INIT(0);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Access to ghes_pvt must be protected by ghes_lock. The spinlock
|
|
|
|
* also provides the necessary (implicit) memory barrier for the SMP
|
|
|
|
* case to make the pointer visible on another CPU.
|
|
|
|
*/
|
2020-05-19 13:44:39 +03:00
|
|
|
static struct ghes_pvt *ghes_pvt;
|
2013-02-15 13:11:57 +04:00
|
|
|
|
2020-06-03 22:19:21 +03:00
|
|
|
/*
|
|
|
|
* This driver's representation of the system hardware, as collected
|
|
|
|
* from DMI.
|
|
|
|
*/
|
|
|
|
struct ghes_hw_desc {
|
|
|
|
int num_dimms;
|
|
|
|
struct dimm_info *dimms;
|
|
|
|
} ghes_hw;
|
|
|
|
|
2019-11-05 23:07:51 +03:00
|
|
|
/* GHES registration mutex */
|
|
|
|
static DEFINE_MUTEX(ghes_reg_mutex);
|
|
|
|
|
2017-08-16 11:33:44 +03:00
|
|
|
/*
|
|
|
|
* Sync with other, potentially concurrent callers of
|
|
|
|
* ghes_edac_report_mem_error(). We don't know what the
|
|
|
|
* "inventive" firmware would do.
|
|
|
|
*/
|
|
|
|
static DEFINE_SPINLOCK(ghes_lock);
|
2013-02-15 16:06:38 +04:00
|
|
|
|
2017-08-24 01:54:45 +03:00
|
|
|
/* "ghes_edac.force_load=1" skips the platform check */
|
|
|
|
static bool __read_mostly force_load;
|
|
|
|
module_param(force_load, bool, 0);
|
|
|
|
|
2020-08-27 17:04:50 +03:00
|
|
|
static bool system_scanned;
|
|
|
|
|
2013-02-14 16:11:08 +04:00
|
|
|
/* Memory Device - Type 17 of SMBIOS spec */
|
|
|
|
struct memdev_dmi_entry {
|
|
|
|
u8 type;
|
|
|
|
u8 length;
|
|
|
|
u16 handle;
|
|
|
|
u16 phys_mem_array_handle;
|
|
|
|
u16 mem_err_info_handle;
|
|
|
|
u16 total_width;
|
|
|
|
u16 data_width;
|
|
|
|
u16 size;
|
|
|
|
u8 form_factor;
|
|
|
|
u8 device_set;
|
|
|
|
u8 device_locator;
|
|
|
|
u8 bank_locator;
|
|
|
|
u8 memory_type;
|
|
|
|
u16 type_detail;
|
|
|
|
u16 speed;
|
|
|
|
u8 manufacturer;
|
|
|
|
u8 serial_number;
|
|
|
|
u8 asset_tag;
|
|
|
|
u8 part_number;
|
|
|
|
u8 attributes;
|
|
|
|
u32 extended_size;
|
|
|
|
u16 conf_mem_clk_speed;
|
|
|
|
} __attribute__((__packed__));
|
|
|
|
|
2020-05-28 13:13:06 +03:00
|
|
|
static struct dimm_info *find_dimm_by_handle(struct mem_ctl_info *mci, u16 handle)
|
2018-09-19 04:59:00 +03:00
|
|
|
{
|
2019-11-06 12:33:07 +03:00
|
|
|
struct dimm_info *dimm;
|
2018-09-19 04:59:00 +03:00
|
|
|
|
2019-11-06 12:33:07 +03:00
|
|
|
mci_for_each_dimm(mci, dimm) {
|
|
|
|
if (dimm->smbios_handle == handle)
|
2020-05-28 13:13:06 +03:00
|
|
|
return dimm;
|
2018-09-19 04:59:00 +03:00
|
|
|
}
|
2019-11-06 12:33:07 +03:00
|
|
|
|
2020-05-28 13:13:06 +03:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dimm_setup_label(struct dimm_info *dimm, u16 handle)
|
|
|
|
{
|
|
|
|
const char *bank = NULL, *device = NULL;
|
|
|
|
|
|
|
|
dmi_memdev_name(handle, &bank, &device);
|
|
|
|
|
|
|
|
/* both strings must be non-zero */
|
|
|
|
if (bank && *bank && device && *device)
|
|
|
|
snprintf(dimm->label, sizeof(dimm->label), "%s %s", bank, device);
|
2018-09-19 04:59:00 +03:00
|
|
|
}
|
|
|
|
|
2020-06-03 22:19:21 +03:00
|
|
|
static void assign_dmi_dimm_info(struct dimm_info *dimm, struct memdev_dmi_entry *entry)
|
2013-02-14 16:11:08 +04:00
|
|
|
{
|
2020-06-03 22:19:21 +03:00
|
|
|
u16 rdr_mask = BIT(7) | BIT(13);
|
2013-02-14 16:11:08 +04:00
|
|
|
|
2020-06-03 22:19:21 +03:00
|
|
|
if (entry->size == 0xffff) {
|
|
|
|
pr_info("Can't get DIMM%i size\n", dimm->idx);
|
|
|
|
dimm->nr_pages = MiB_TO_PAGES(32);/* Unknown */
|
|
|
|
} else if (entry->size == 0x7fff) {
|
|
|
|
dimm->nr_pages = MiB_TO_PAGES(entry->extended_size);
|
|
|
|
} else {
|
|
|
|
if (entry->size & BIT(15))
|
|
|
|
dimm->nr_pages = MiB_TO_PAGES((entry->size & 0x7fff) << 10);
|
|
|
|
else
|
|
|
|
dimm->nr_pages = MiB_TO_PAGES(entry->size);
|
|
|
|
}
|
2013-02-14 16:11:08 +04:00
|
|
|
|
2020-06-03 22:19:21 +03:00
|
|
|
switch (entry->memory_type) {
|
|
|
|
case 0x12:
|
|
|
|
if (entry->type_detail & BIT(13))
|
|
|
|
dimm->mtype = MEM_RDDR;
|
|
|
|
else
|
|
|
|
dimm->mtype = MEM_DDR;
|
|
|
|
break;
|
|
|
|
case 0x13:
|
|
|
|
if (entry->type_detail & BIT(13))
|
|
|
|
dimm->mtype = MEM_RDDR2;
|
2013-02-14 16:11:08 +04:00
|
|
|
else
|
2020-06-03 22:19:21 +03:00
|
|
|
dimm->mtype = MEM_DDR2;
|
|
|
|
break;
|
|
|
|
case 0x14:
|
|
|
|
dimm->mtype = MEM_FB_DDR2;
|
|
|
|
break;
|
|
|
|
case 0x18:
|
|
|
|
if (entry->type_detail & BIT(12))
|
|
|
|
dimm->mtype = MEM_NVDIMM;
|
|
|
|
else if (entry->type_detail & BIT(13))
|
|
|
|
dimm->mtype = MEM_RDDR3;
|
|
|
|
else
|
|
|
|
dimm->mtype = MEM_DDR3;
|
|
|
|
break;
|
|
|
|
case 0x1a:
|
|
|
|
if (entry->type_detail & BIT(12))
|
|
|
|
dimm->mtype = MEM_NVDIMM;
|
|
|
|
else if (entry->type_detail & BIT(13))
|
|
|
|
dimm->mtype = MEM_RDDR4;
|
|
|
|
else
|
|
|
|
dimm->mtype = MEM_DDR4;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
if (entry->type_detail & BIT(6))
|
|
|
|
dimm->mtype = MEM_RMBS;
|
|
|
|
else if ((entry->type_detail & rdr_mask) == rdr_mask)
|
|
|
|
dimm->mtype = MEM_RDR;
|
|
|
|
else if (entry->type_detail & BIT(7))
|
|
|
|
dimm->mtype = MEM_SDR;
|
|
|
|
else if (entry->type_detail & BIT(9))
|
|
|
|
dimm->mtype = MEM_EDO;
|
|
|
|
else
|
|
|
|
dimm->mtype = MEM_UNKNOWN;
|
|
|
|
}
|
2013-02-14 16:11:08 +04:00
|
|
|
|
2020-06-03 22:19:21 +03:00
|
|
|
/*
|
|
|
|
* Actually, we can only detect if the memory has bits for
|
|
|
|
* checksum or not
|
|
|
|
*/
|
|
|
|
if (entry->total_width == entry->data_width)
|
|
|
|
dimm->edac_mode = EDAC_NONE;
|
|
|
|
else
|
|
|
|
dimm->edac_mode = EDAC_SECDED;
|
|
|
|
|
|
|
|
dimm->dtype = DEV_UNKNOWN;
|
|
|
|
dimm->grain = 128; /* Likely, worse case */
|
2013-02-14 16:11:08 +04:00
|
|
|
|
2020-06-03 22:19:21 +03:00
|
|
|
dimm_setup_label(dimm, entry->handle);
|
2018-09-19 04:59:00 +03:00
|
|
|
|
2020-06-03 22:19:21 +03:00
|
|
|
if (dimm->nr_pages) {
|
|
|
|
edac_dbg(1, "DIMM%i: %s size = %d MB%s\n",
|
|
|
|
dimm->idx, edac_mem_types[dimm->mtype],
|
|
|
|
PAGES_TO_MiB(dimm->nr_pages),
|
|
|
|
(dimm->edac_mode != EDAC_NONE) ? "(ECC)" : "");
|
|
|
|
edac_dbg(2, "\ttype %d, detail 0x%02x, width %d(total %d)\n",
|
|
|
|
entry->memory_type, entry->type_detail,
|
|
|
|
entry->total_width, entry->data_width);
|
2013-02-14 16:11:08 +04:00
|
|
|
}
|
2020-06-03 22:19:21 +03:00
|
|
|
|
|
|
|
dimm->smbios_handle = entry->handle;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void enumerate_dimms(const struct dmi_header *dh, void *arg)
|
|
|
|
{
|
|
|
|
struct memdev_dmi_entry *entry = (struct memdev_dmi_entry *)dh;
|
|
|
|
struct ghes_hw_desc *hw = (struct ghes_hw_desc *)arg;
|
|
|
|
struct dimm_info *d;
|
|
|
|
|
|
|
|
if (dh->type != DMI_ENTRY_MEM_DEVICE)
|
|
|
|
return;
|
|
|
|
|
|
|
|
/* Enlarge the array with additional 16 */
|
|
|
|
if (!hw->num_dimms || !(hw->num_dimms % 16)) {
|
|
|
|
struct dimm_info *new;
|
|
|
|
|
|
|
|
new = krealloc(hw->dimms, (hw->num_dimms + 16) * sizeof(struct dimm_info),
|
|
|
|
GFP_KERNEL);
|
|
|
|
if (!new) {
|
|
|
|
WARN_ON_ONCE(1);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
hw->dimms = new;
|
|
|
|
}
|
|
|
|
|
|
|
|
d = &hw->dimms[hw->num_dimms];
|
|
|
|
d->idx = hw->num_dimms;
|
|
|
|
|
|
|
|
assign_dmi_dimm_info(d, entry);
|
|
|
|
|
|
|
|
hw->num_dimms++;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void ghes_scan_system(void)
|
|
|
|
{
|
2020-08-27 17:04:50 +03:00
|
|
|
if (system_scanned)
|
2020-06-03 22:19:21 +03:00
|
|
|
return;
|
|
|
|
|
|
|
|
dmi_walk(enumerate_dimms, &ghes_hw);
|
|
|
|
|
2020-08-27 17:04:50 +03:00
|
|
|
system_scanned = true;
|
2013-02-14 16:11:08 +04:00
|
|
|
}
|
|
|
|
|
2018-05-01 00:33:50 +03:00
|
|
|
void ghes_edac_report_mem_error(int sev, struct cper_sec_mem_err *mem_err)
|
2013-02-15 13:11:57 +04:00
|
|
|
{
|
2013-02-15 13:36:27 +04:00
|
|
|
struct edac_raw_error_desc *e;
|
|
|
|
struct mem_ctl_info *mci;
|
2020-05-19 13:44:39 +03:00
|
|
|
struct ghes_pvt *pvt;
|
2017-08-16 11:33:44 +03:00
|
|
|
unsigned long flags;
|
2013-02-20 02:24:12 +04:00
|
|
|
char *p;
|
2013-02-15 13:36:27 +04:00
|
|
|
|
2017-08-16 11:33:44 +03:00
|
|
|
/*
|
|
|
|
* We can do the locking below because GHES defers error processing
|
|
|
|
* from NMI to IRQ context. Whenever that changes, we'd at least
|
|
|
|
* know.
|
|
|
|
*/
|
|
|
|
if (WARN_ON_ONCE(in_nmi()))
|
|
|
|
return;
|
|
|
|
|
|
|
|
spin_lock_irqsave(&ghes_lock, flags);
|
|
|
|
|
2019-11-05 23:07:51 +03:00
|
|
|
pvt = ghes_pvt;
|
|
|
|
if (!pvt)
|
|
|
|
goto unlock;
|
|
|
|
|
2013-02-15 13:36:27 +04:00
|
|
|
mci = pvt->mci;
|
|
|
|
e = &mci->error_desc;
|
|
|
|
|
|
|
|
/* Cleans the error report buffer */
|
|
|
|
memset(e, 0, sizeof (*e));
|
|
|
|
e->error_count = 1;
|
2019-11-06 12:33:23 +03:00
|
|
|
e->grain = 1;
|
2013-02-20 02:24:12 +04:00
|
|
|
e->msg = pvt->msg;
|
|
|
|
e->other_detail = pvt->other_detail;
|
|
|
|
e->top_layer = -1;
|
|
|
|
e->mid_layer = -1;
|
|
|
|
e->low_layer = -1;
|
|
|
|
*pvt->other_detail = '\0';
|
|
|
|
*pvt->msg = '\0';
|
2013-02-15 13:36:27 +04:00
|
|
|
|
|
|
|
switch (sev) {
|
|
|
|
case GHES_SEV_CORRECTED:
|
2020-01-23 12:02:54 +03:00
|
|
|
e->type = HW_EVENT_ERR_CORRECTED;
|
2013-02-15 13:36:27 +04:00
|
|
|
break;
|
|
|
|
case GHES_SEV_RECOVERABLE:
|
2020-01-23 12:02:54 +03:00
|
|
|
e->type = HW_EVENT_ERR_UNCORRECTED;
|
2013-02-15 13:36:27 +04:00
|
|
|
break;
|
|
|
|
case GHES_SEV_PANIC:
|
2020-01-23 12:02:54 +03:00
|
|
|
e->type = HW_EVENT_ERR_FATAL;
|
2013-02-15 13:36:27 +04:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
case GHES_SEV_NO:
|
2020-01-23 12:02:54 +03:00
|
|
|
e->type = HW_EVENT_ERR_INFO;
|
2013-02-15 13:36:27 +04:00
|
|
|
}
|
|
|
|
|
2013-02-20 02:24:12 +04:00
|
|
|
edac_dbg(1, "error validation_bits: 0x%08llx\n",
|
|
|
|
(long long)mem_err->validation_bits);
|
|
|
|
|
|
|
|
/* Error type, mapped on e->msg */
|
|
|
|
if (mem_err->validation_bits & CPER_MEM_VALID_ERROR_TYPE) {
|
|
|
|
p = pvt->msg;
|
|
|
|
switch (mem_err->error_type) {
|
|
|
|
case 0:
|
|
|
|
p += sprintf(p, "Unknown");
|
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
p += sprintf(p, "No error");
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
p += sprintf(p, "Single-bit ECC");
|
|
|
|
break;
|
|
|
|
case 3:
|
|
|
|
p += sprintf(p, "Multi-bit ECC");
|
|
|
|
break;
|
|
|
|
case 4:
|
|
|
|
p += sprintf(p, "Single-symbol ChipKill ECC");
|
|
|
|
break;
|
|
|
|
case 5:
|
|
|
|
p += sprintf(p, "Multi-symbol ChipKill ECC");
|
|
|
|
break;
|
|
|
|
case 6:
|
|
|
|
p += sprintf(p, "Master abort");
|
|
|
|
break;
|
|
|
|
case 7:
|
|
|
|
p += sprintf(p, "Target abort");
|
|
|
|
break;
|
|
|
|
case 8:
|
|
|
|
p += sprintf(p, "Parity Error");
|
|
|
|
break;
|
|
|
|
case 9:
|
|
|
|
p += sprintf(p, "Watchdog timeout");
|
|
|
|
break;
|
|
|
|
case 10:
|
|
|
|
p += sprintf(p, "Invalid address");
|
|
|
|
break;
|
|
|
|
case 11:
|
|
|
|
p += sprintf(p, "Mirror Broken");
|
|
|
|
break;
|
|
|
|
case 12:
|
|
|
|
p += sprintf(p, "Memory Sparing");
|
|
|
|
break;
|
|
|
|
case 13:
|
|
|
|
p += sprintf(p, "Scrub corrected error");
|
|
|
|
break;
|
|
|
|
case 14:
|
|
|
|
p += sprintf(p, "Scrub uncorrected error");
|
|
|
|
break;
|
|
|
|
case 15:
|
|
|
|
p += sprintf(p, "Physical Memory Map-out event");
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
p += sprintf(p, "reserved error (%d)",
|
|
|
|
mem_err->error_type);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
strcpy(pvt->msg, "unknown error");
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Error address */
|
2013-10-19 01:30:13 +04:00
|
|
|
if (mem_err->validation_bits & CPER_MEM_VALID_PA) {
|
2019-11-06 12:33:20 +03:00
|
|
|
e->page_frame_number = PHYS_PFN(mem_err->physical_addr);
|
|
|
|
e->offset_in_page = offset_in_page(mem_err->physical_addr);
|
2013-02-20 02:24:12 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Error grain */
|
2013-10-19 01:30:13 +04:00
|
|
|
if (mem_err->validation_bits & CPER_MEM_VALID_PA_MASK)
|
2019-11-06 12:33:23 +03:00
|
|
|
e->grain = ~mem_err->physical_addr_mask + 1;
|
2013-02-20 02:24:12 +04:00
|
|
|
|
|
|
|
/* Memory error location, mapped on e->location */
|
|
|
|
p = e->location;
|
|
|
|
if (mem_err->validation_bits & CPER_MEM_VALID_NODE)
|
|
|
|
p += sprintf(p, "node:%d ", mem_err->node);
|
|
|
|
if (mem_err->validation_bits & CPER_MEM_VALID_CARD)
|
|
|
|
p += sprintf(p, "card:%d ", mem_err->card);
|
|
|
|
if (mem_err->validation_bits & CPER_MEM_VALID_MODULE)
|
|
|
|
p += sprintf(p, "module:%d ", mem_err->module);
|
2013-10-19 01:30:38 +04:00
|
|
|
if (mem_err->validation_bits & CPER_MEM_VALID_RANK_NUMBER)
|
|
|
|
p += sprintf(p, "rank:%d ", mem_err->rank);
|
2013-02-20 02:24:12 +04:00
|
|
|
if (mem_err->validation_bits & CPER_MEM_VALID_BANK)
|
|
|
|
p += sprintf(p, "bank:%d ", mem_err->bank);
|
2020-08-19 17:35:44 +03:00
|
|
|
if (mem_err->validation_bits & CPER_MEM_VALID_BANK_GROUP)
|
|
|
|
p += sprintf(p, "bank_group:%d ",
|
|
|
|
mem_err->bank >> CPER_MEM_BANK_GROUP_SHIFT);
|
|
|
|
if (mem_err->validation_bits & CPER_MEM_VALID_BANK_ADDRESS)
|
|
|
|
p += sprintf(p, "bank_address:%d ",
|
|
|
|
mem_err->bank & CPER_MEM_BANK_ADDRESS_MASK);
|
2020-08-19 17:35:43 +03:00
|
|
|
if (mem_err->validation_bits & (CPER_MEM_VALID_ROW | CPER_MEM_VALID_ROW_EXT)) {
|
|
|
|
u32 row = mem_err->row;
|
|
|
|
|
|
|
|
row |= cper_get_mem_extension(mem_err->validation_bits, mem_err->extended);
|
|
|
|
p += sprintf(p, "row:%d ", row);
|
|
|
|
}
|
2013-02-20 02:24:12 +04:00
|
|
|
if (mem_err->validation_bits & CPER_MEM_VALID_COLUMN)
|
|
|
|
p += sprintf(p, "col:%d ", mem_err->column);
|
|
|
|
if (mem_err->validation_bits & CPER_MEM_VALID_BIT_POSITION)
|
|
|
|
p += sprintf(p, "bit_pos:%d ", mem_err->bit_pos);
|
2013-10-19 01:30:38 +04:00
|
|
|
if (mem_err->validation_bits & CPER_MEM_VALID_MODULE_HANDLE) {
|
|
|
|
const char *bank = NULL, *device = NULL;
|
2020-05-28 13:13:06 +03:00
|
|
|
struct dimm_info *dimm;
|
2018-09-19 04:59:00 +03:00
|
|
|
|
2013-10-19 01:30:38 +04:00
|
|
|
dmi_memdev_name(mem_err->mem_dev_handle, &bank, &device);
|
|
|
|
if (bank != NULL && device != NULL)
|
|
|
|
p += sprintf(p, "DIMM location:%s %s ", bank, device);
|
|
|
|
else
|
|
|
|
p += sprintf(p, "DIMM DMI handle: 0x%.4x ",
|
|
|
|
mem_err->mem_dev_handle);
|
2018-09-19 04:59:00 +03:00
|
|
|
|
2020-05-28 13:13:06 +03:00
|
|
|
dimm = find_dimm_by_handle(mci, mem_err->mem_dev_handle);
|
|
|
|
if (dimm) {
|
|
|
|
e->top_layer = dimm->idx;
|
|
|
|
strcpy(e->label, dimm->label);
|
|
|
|
}
|
2013-10-19 01:30:38 +04:00
|
|
|
}
|
2020-08-19 17:35:44 +03:00
|
|
|
if (mem_err->validation_bits & CPER_MEM_VALID_CHIP_ID)
|
|
|
|
p += sprintf(p, "chipID: %d ",
|
|
|
|
mem_err->extended >> CPER_MEM_CHIP_ID_SHIFT);
|
2013-02-20 02:24:12 +04:00
|
|
|
if (p > e->location)
|
|
|
|
*(p - 1) = '\0';
|
|
|
|
|
2020-05-28 13:13:06 +03:00
|
|
|
if (!*e->label)
|
|
|
|
strcpy(e->label, "unknown memory");
|
|
|
|
|
2013-02-20 02:24:12 +04:00
|
|
|
/* All other fields are mapped on e->other_detail */
|
|
|
|
p = pvt->other_detail;
|
2019-11-06 12:33:25 +03:00
|
|
|
p += snprintf(p, sizeof(pvt->other_detail),
|
|
|
|
"APEI location: %s ", e->location);
|
2013-02-20 02:24:12 +04:00
|
|
|
if (mem_err->validation_bits & CPER_MEM_VALID_ERROR_STATUS) {
|
|
|
|
u64 status = mem_err->error_status;
|
|
|
|
|
|
|
|
p += sprintf(p, "status(0x%016llx): ", (long long)status);
|
|
|
|
switch ((status >> 8) & 0xff) {
|
|
|
|
case 1:
|
|
|
|
p += sprintf(p, "Error detected internal to the component ");
|
|
|
|
break;
|
|
|
|
case 16:
|
|
|
|
p += sprintf(p, "Error detected in the bus ");
|
|
|
|
break;
|
|
|
|
case 4:
|
|
|
|
p += sprintf(p, "Storage error in DRAM memory ");
|
|
|
|
break;
|
|
|
|
case 5:
|
|
|
|
p += sprintf(p, "Storage error in TLB ");
|
|
|
|
break;
|
|
|
|
case 6:
|
|
|
|
p += sprintf(p, "Storage error in cache ");
|
|
|
|
break;
|
|
|
|
case 7:
|
|
|
|
p += sprintf(p, "Error in one or more functional units ");
|
|
|
|
break;
|
|
|
|
case 8:
|
|
|
|
p += sprintf(p, "component failed self test ");
|
|
|
|
break;
|
|
|
|
case 9:
|
|
|
|
p += sprintf(p, "Overflow or undervalue of internal queue ");
|
|
|
|
break;
|
|
|
|
case 17:
|
|
|
|
p += sprintf(p, "Virtual address not found on IO-TLB or IO-PDIR ");
|
|
|
|
break;
|
|
|
|
case 18:
|
|
|
|
p += sprintf(p, "Improper access error ");
|
|
|
|
break;
|
|
|
|
case 19:
|
|
|
|
p += sprintf(p, "Access to a memory address which is not mapped to any component ");
|
|
|
|
break;
|
|
|
|
case 20:
|
|
|
|
p += sprintf(p, "Loss of Lockstep ");
|
|
|
|
break;
|
|
|
|
case 21:
|
|
|
|
p += sprintf(p, "Response not associated with a request ");
|
|
|
|
break;
|
|
|
|
case 22:
|
|
|
|
p += sprintf(p, "Bus parity error - must also set the A, C, or D Bits ");
|
|
|
|
break;
|
|
|
|
case 23:
|
|
|
|
p += sprintf(p, "Detection of a PATH_ERROR ");
|
|
|
|
break;
|
|
|
|
case 25:
|
|
|
|
p += sprintf(p, "Bus operation timeout ");
|
|
|
|
break;
|
|
|
|
case 26:
|
|
|
|
p += sprintf(p, "A read was issued to data that has been poisoned ");
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
p += sprintf(p, "reserved ");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (mem_err->validation_bits & CPER_MEM_VALID_REQUESTOR_ID)
|
|
|
|
p += sprintf(p, "requestorID: 0x%016llx ",
|
|
|
|
(long long)mem_err->requestor_id);
|
|
|
|
if (mem_err->validation_bits & CPER_MEM_VALID_RESPONDER_ID)
|
|
|
|
p += sprintf(p, "responderID: 0x%016llx ",
|
|
|
|
(long long)mem_err->responder_id);
|
|
|
|
if (mem_err->validation_bits & CPER_MEM_VALID_TARGET_ID)
|
|
|
|
p += sprintf(p, "targetID: 0x%016llx ",
|
|
|
|
(long long)mem_err->responder_id);
|
|
|
|
if (p > pvt->other_detail)
|
|
|
|
*(p - 1) = '\0';
|
2013-02-15 13:36:27 +04:00
|
|
|
|
2020-01-23 12:02:56 +03:00
|
|
|
edac_raw_mc_handle_error(e);
|
2019-11-05 23:07:51 +03:00
|
|
|
|
|
|
|
unlock:
|
2017-08-16 11:33:44 +03:00
|
|
|
spin_unlock_irqrestore(&ghes_lock, flags);
|
2013-02-15 13:11:57 +04:00
|
|
|
}
|
|
|
|
|
2017-08-24 01:54:45 +03:00
|
|
|
/*
|
|
|
|
* Known systems that are safe to enable this module.
|
|
|
|
*/
|
|
|
|
static struct acpi_platform_list plat_list[] = {
|
|
|
|
{"HPE ", "Server ", 0, ACPI_SIG_FADT, all_versions},
|
|
|
|
{ } /* End */
|
|
|
|
};
|
|
|
|
|
2013-02-15 13:11:57 +04:00
|
|
|
int ghes_edac_register(struct ghes *ghes, struct device *dev)
|
|
|
|
{
|
2013-02-14 16:11:08 +04:00
|
|
|
bool fake = false;
|
2013-02-15 13:11:57 +04:00
|
|
|
struct mem_ctl_info *mci;
|
2020-05-19 13:44:39 +03:00
|
|
|
struct ghes_pvt *pvt;
|
2013-02-15 13:11:57 +04:00
|
|
|
struct edac_mc_layer layers[1];
|
2019-11-05 23:07:51 +03:00
|
|
|
unsigned long flags;
|
2018-05-18 14:13:31 +03:00
|
|
|
int idx = -1;
|
2020-06-03 22:19:21 +03:00
|
|
|
int rc = 0;
|
2017-08-24 01:54:45 +03:00
|
|
|
|
2018-05-18 14:13:31 +03:00
|
|
|
if (IS_ENABLED(CONFIG_X86)) {
|
|
|
|
/* Check if safe to enable on this system */
|
|
|
|
idx = acpi_match_platform_list(plat_list);
|
|
|
|
if (!force_load && idx < 0)
|
|
|
|
return -ENODEV;
|
|
|
|
} else {
|
2020-09-11 19:17:30 +03:00
|
|
|
force_load = true;
|
2018-05-18 14:13:31 +03:00
|
|
|
idx = 0;
|
|
|
|
}
|
2013-02-14 16:11:08 +04:00
|
|
|
|
2019-11-05 23:07:51 +03:00
|
|
|
/* finish another registration/unregistration instance first */
|
|
|
|
mutex_lock(&ghes_reg_mutex);
|
|
|
|
|
2017-08-16 11:33:44 +03:00
|
|
|
/*
|
|
|
|
* We have only one logical memory controller to which all DIMMs belong.
|
|
|
|
*/
|
2019-11-05 23:07:51 +03:00
|
|
|
if (refcount_inc_not_zero(&ghes_refcount))
|
|
|
|
goto unlock;
|
2017-08-16 11:33:44 +03:00
|
|
|
|
2020-06-03 22:19:21 +03:00
|
|
|
ghes_scan_system();
|
2013-02-14 16:11:08 +04:00
|
|
|
|
|
|
|
/* Check if we've got a bogus BIOS */
|
2020-06-03 22:19:21 +03:00
|
|
|
if (!ghes_hw.num_dimms) {
|
2013-02-14 16:11:08 +04:00
|
|
|
fake = true;
|
2020-06-03 22:19:21 +03:00
|
|
|
ghes_hw.num_dimms = 1;
|
2013-02-14 16:11:08 +04:00
|
|
|
}
|
2013-02-15 13:11:57 +04:00
|
|
|
|
|
|
|
layers[0].type = EDAC_MC_LAYER_ALL_MEM;
|
2020-06-03 22:19:21 +03:00
|
|
|
layers[0].size = ghes_hw.num_dimms;
|
2013-02-15 13:11:57 +04:00
|
|
|
layers[0].is_virt_csrow = true;
|
|
|
|
|
2020-05-19 13:44:39 +03:00
|
|
|
mci = edac_mc_alloc(0, ARRAY_SIZE(layers), layers, sizeof(struct ghes_pvt));
|
2013-02-15 13:11:57 +04:00
|
|
|
if (!mci) {
|
2013-02-15 16:06:38 +04:00
|
|
|
pr_info("Can't allocate memory for EDAC data\n");
|
2019-11-05 23:07:51 +03:00
|
|
|
rc = -ENOMEM;
|
|
|
|
goto unlock;
|
2013-02-15 13:11:57 +04:00
|
|
|
}
|
|
|
|
|
2019-11-05 23:07:51 +03:00
|
|
|
pvt = mci->pvt_info;
|
|
|
|
pvt->mci = mci;
|
2013-02-15 13:11:57 +04:00
|
|
|
|
2017-08-16 11:33:44 +03:00
|
|
|
mci->pdev = dev;
|
2013-02-15 13:11:57 +04:00
|
|
|
mci->mtype_cap = MEM_FLAG_EMPTY;
|
|
|
|
mci->edac_ctl_cap = EDAC_FLAG_NONE;
|
|
|
|
mci->edac_cap = EDAC_FLAG_NONE;
|
|
|
|
mci->mod_name = "ghes_edac.c";
|
|
|
|
mci->ctl_name = "ghes_edac";
|
|
|
|
mci->dev_name = "ghes";
|
|
|
|
|
2017-08-24 01:54:45 +03:00
|
|
|
if (fake) {
|
|
|
|
pr_info("This system has a very crappy BIOS: It doesn't even list the DIMMS.\n");
|
|
|
|
pr_info("Its SMBIOS info is wrong. It is doubtful that the error report would\n");
|
|
|
|
pr_info("work on such system. Use this driver with caution\n");
|
|
|
|
} else if (idx < 0) {
|
2017-08-16 11:33:44 +03:00
|
|
|
pr_info("This EDAC driver relies on BIOS to enumerate memory and get error reports.\n");
|
|
|
|
pr_info("Unfortunately, not all BIOSes reflect the memory layout correctly.\n");
|
|
|
|
pr_info("So, the end result of using this driver varies from vendor to vendor.\n");
|
|
|
|
pr_info("If you find incorrect reports, please contact your hardware vendor\n");
|
|
|
|
pr_info("to correct its BIOS.\n");
|
2020-06-03 22:19:21 +03:00
|
|
|
pr_info("This system has %d DIMM sockets.\n", ghes_hw.num_dimms);
|
2013-02-15 16:06:38 +04:00
|
|
|
}
|
|
|
|
|
2013-02-14 16:11:08 +04:00
|
|
|
if (!fake) {
|
2020-06-03 22:19:21 +03:00
|
|
|
struct dimm_info *src, *dst;
|
|
|
|
int i = 0;
|
|
|
|
|
|
|
|
mci_for_each_dimm(mci, dst) {
|
|
|
|
src = &ghes_hw.dimms[i];
|
|
|
|
|
|
|
|
dst->idx = src->idx;
|
|
|
|
dst->smbios_handle = src->smbios_handle;
|
|
|
|
dst->nr_pages = src->nr_pages;
|
|
|
|
dst->mtype = src->mtype;
|
|
|
|
dst->edac_mode = src->edac_mode;
|
|
|
|
dst->dtype = src->dtype;
|
|
|
|
dst->grain = src->grain;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If no src->label, preserve default label assigned
|
|
|
|
* from EDAC core.
|
|
|
|
*/
|
|
|
|
if (strlen(src->label))
|
|
|
|
memcpy(dst->label, src->label, sizeof(src->label));
|
|
|
|
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
|
2013-02-14 16:11:08 +04:00
|
|
|
} else {
|
EDAC: Replace EDAC_DIMM_PTR() macro with edac_get_dimm() function
The EDAC_DIMM_PTR() macro takes 3 arguments from struct mem_ctl_info.
Clean up this interface to only pass the mci struct and replace this
macro with a new function edac_get_dimm().
Also introduce an edac_get_dimm_by_index() function for later use.
This allows it to get a DIMM pointer only by a given index. This can
be useful if the DIMM's position within the layers of the memory
controller or the exact size of the layers are unknown.
Small style changes made for some hunks after applying the semantic
patch.
Semantic patch used:
@@ expression mci, a, b,c; @@
-EDAC_DIMM_PTR(mci->layers, mci->dimms, mci->n_layers, a, b, c)
+edac_get_dimm(mci, a, b, c)
[ bp: Touchups. ]
Signed-off-by: Robert Richter <rrichter@marvell.com>
Signed-off-by: Borislav Petkov <bp@suse.de>
Reviewed-by: Mauro Carvalho Chehab <mchehab@kernel.org>
Cc: "linux-edac@vger.kernel.org" <linux-edac@vger.kernel.org>
Cc: James Morse <james.morse@arm.com>
Cc: Jason Baron <jbaron@akamai.com>
Cc: Qiuxu Zhuo <qiuxu.zhuo@intel.com>
Cc: Tero Kristo <t-kristo@ti.com>
Cc: Tony Luck <tony.luck@intel.com>
Link: https://lkml.kernel.org/r/20191106093239.25517-2-rrichter@marvell.com
2019-11-06 12:33:02 +03:00
|
|
|
struct dimm_info *dimm = edac_get_dimm(mci, 0, 0, 0);
|
2013-02-15 13:11:57 +04:00
|
|
|
|
2013-02-15 16:06:38 +04:00
|
|
|
dimm->nr_pages = 1;
|
2013-02-14 16:11:08 +04:00
|
|
|
dimm->grain = 128;
|
|
|
|
dimm->mtype = MEM_UNKNOWN;
|
|
|
|
dimm->dtype = DEV_UNKNOWN;
|
|
|
|
dimm->edac_mode = EDAC_SECDED;
|
|
|
|
}
|
2013-02-15 13:11:57 +04:00
|
|
|
|
|
|
|
rc = edac_mc_add_mc(mci);
|
|
|
|
if (rc < 0) {
|
2020-06-03 22:19:21 +03:00
|
|
|
pr_info("Can't register with the EDAC core\n");
|
2013-02-15 13:11:57 +04:00
|
|
|
edac_mc_free(mci);
|
2019-11-05 23:07:51 +03:00
|
|
|
rc = -ENODEV;
|
|
|
|
goto unlock;
|
2013-02-15 13:11:57 +04:00
|
|
|
}
|
2019-11-05 23:07:51 +03:00
|
|
|
|
|
|
|
spin_lock_irqsave(&ghes_lock, flags);
|
|
|
|
ghes_pvt = pvt;
|
|
|
|
spin_unlock_irqrestore(&ghes_lock, flags);
|
|
|
|
|
2019-11-22 00:36:57 +03:00
|
|
|
/* only set on success */
|
|
|
|
refcount_set(&ghes_refcount, 1);
|
2019-11-05 23:07:51 +03:00
|
|
|
|
|
|
|
unlock:
|
2020-06-03 22:19:21 +03:00
|
|
|
|
|
|
|
/* Not needed anymore */
|
|
|
|
kfree(ghes_hw.dimms);
|
|
|
|
ghes_hw.dimms = NULL;
|
|
|
|
|
2019-11-05 23:07:51 +03:00
|
|
|
mutex_unlock(&ghes_reg_mutex);
|
|
|
|
|
|
|
|
return rc;
|
2013-02-15 13:11:57 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
void ghes_edac_unregister(struct ghes *ghes)
|
|
|
|
{
|
|
|
|
struct mem_ctl_info *mci;
|
2019-11-05 23:07:51 +03:00
|
|
|
unsigned long flags;
|
2017-08-16 11:33:44 +03:00
|
|
|
|
2020-09-11 19:17:30 +03:00
|
|
|
if (!force_load)
|
|
|
|
return;
|
|
|
|
|
2019-11-05 23:07:51 +03:00
|
|
|
mutex_lock(&ghes_reg_mutex);
|
2018-04-26 13:16:49 +03:00
|
|
|
|
2020-08-27 17:04:50 +03:00
|
|
|
system_scanned = false;
|
2020-09-11 13:55:55 +03:00
|
|
|
memset(&ghes_hw, 0, sizeof(struct ghes_hw_desc));
|
2020-08-27 17:04:50 +03:00
|
|
|
|
2019-11-05 23:07:51 +03:00
|
|
|
if (!refcount_dec_and_test(&ghes_refcount))
|
|
|
|
goto unlock;
|
EDAC/ghes: Fix Use after free in ghes_edac remove path
ghes_edac models a single logical memory controller, and uses a global
ghes_init variable to ensure only the first ghes_edac_register() will
do anything.
ghes_edac is registered the first time a GHES entry in the HEST is
probed. There may be multiple entries, so subsequent attempts to
register ghes_edac are silently ignored as the work has already been
done.
When a GHES entry is unregistered, it calls ghes_edac_unregister(),
which free()s the memory behind the global variables in ghes_edac.
But there may be multiple GHES entries, the next call to
ghes_edac_unregister() will dereference the free()d memory, and attempt
to free it a second time.
This may also be triggered on a platform with one GHES entry, if the
driver is unbound/re-bound and unbound. The re-bind step will do
nothing because of ghes_init, the second unbind will then do the same
work as the first.
Doing the unregister work on the first call is unsafe, as another
CPU may be processing a notification in ghes_edac_report_mem_error(),
using the memory we are about to free.
ghes_init is already half of the reference counting. We only need
to do the register work for the first call, and the unregister work
for the last. Add the unregister check.
This means we no longer free ghes_edac's memory while there are
GHES entries that may receive a notification.
This was detected by KASAN and DEBUG_TEST_DRIVER_REMOVE.
[ bp: merge into a single patch. ]
Fixes: 0fe5f281f749 ("EDAC, ghes: Model a single, logical memory controller")
Reported-by: John Garry <john.garry@huawei.com>
Signed-off-by: James Morse <james.morse@arm.com>
Signed-off-by: Borislav Petkov <bp@suse.de>
Cc: linux-edac <linux-edac@vger.kernel.org>
Cc: Mauro Carvalho Chehab <mchehab@kernel.org>
Cc: Robert Richter <rrichter@marvell.com>
Cc: Tony Luck <tony.luck@intel.com>
Cc: <stable@vger.kernel.org>
Link: https://lkml.kernel.org/r/20191014171919.85044-2-james.morse@arm.com
Link: https://lkml.kernel.org/r/304df85b-8b56-b77e-1a11-aa23769f2e7c@huawei.com
2019-10-14 20:19:18 +03:00
|
|
|
|
2019-11-05 23:07:51 +03:00
|
|
|
/*
|
|
|
|
* Wait for the irq handler being finished.
|
|
|
|
*/
|
|
|
|
spin_lock_irqsave(&ghes_lock, flags);
|
|
|
|
mci = ghes_pvt ? ghes_pvt->mci : NULL;
|
EDAC/ghes: Fix Use after free in ghes_edac remove path
ghes_edac models a single logical memory controller, and uses a global
ghes_init variable to ensure only the first ghes_edac_register() will
do anything.
ghes_edac is registered the first time a GHES entry in the HEST is
probed. There may be multiple entries, so subsequent attempts to
register ghes_edac are silently ignored as the work has already been
done.
When a GHES entry is unregistered, it calls ghes_edac_unregister(),
which free()s the memory behind the global variables in ghes_edac.
But there may be multiple GHES entries, the next call to
ghes_edac_unregister() will dereference the free()d memory, and attempt
to free it a second time.
This may also be triggered on a platform with one GHES entry, if the
driver is unbound/re-bound and unbound. The re-bind step will do
nothing because of ghes_init, the second unbind will then do the same
work as the first.
Doing the unregister work on the first call is unsafe, as another
CPU may be processing a notification in ghes_edac_report_mem_error(),
using the memory we are about to free.
ghes_init is already half of the reference counting. We only need
to do the register work for the first call, and the unregister work
for the last. Add the unregister check.
This means we no longer free ghes_edac's memory while there are
GHES entries that may receive a notification.
This was detected by KASAN and DEBUG_TEST_DRIVER_REMOVE.
[ bp: merge into a single patch. ]
Fixes: 0fe5f281f749 ("EDAC, ghes: Model a single, logical memory controller")
Reported-by: John Garry <john.garry@huawei.com>
Signed-off-by: James Morse <james.morse@arm.com>
Signed-off-by: Borislav Petkov <bp@suse.de>
Cc: linux-edac <linux-edac@vger.kernel.org>
Cc: Mauro Carvalho Chehab <mchehab@kernel.org>
Cc: Robert Richter <rrichter@marvell.com>
Cc: Tony Luck <tony.luck@intel.com>
Cc: <stable@vger.kernel.org>
Link: https://lkml.kernel.org/r/20191014171919.85044-2-james.morse@arm.com
Link: https://lkml.kernel.org/r/304df85b-8b56-b77e-1a11-aa23769f2e7c@huawei.com
2019-10-14 20:19:18 +03:00
|
|
|
ghes_pvt = NULL;
|
2019-11-05 23:07:51 +03:00
|
|
|
spin_unlock_irqrestore(&ghes_lock, flags);
|
|
|
|
|
|
|
|
if (!mci)
|
|
|
|
goto unlock;
|
|
|
|
|
|
|
|
mci = edac_mc_del_mc(mci->pdev);
|
|
|
|
if (mci)
|
|
|
|
edac_mc_free(mci);
|
|
|
|
|
|
|
|
unlock:
|
|
|
|
mutex_unlock(&ghes_reg_mutex);
|
2013-02-15 13:11:57 +04:00
|
|
|
}
|