2019-05-27 14:55:19 +08:00
|
|
|
// SPDX-License-Identifier: GPL-2.0-only
|
2013-02-15 17:11:57 +08:00
|
|
|
/*
|
|
|
|
* GHES/EDAC Linux driver
|
|
|
|
*
|
2014-02-07 18:03:07 +08:00
|
|
|
* Copyright (c) 2013 by Mauro Carvalho Chehab
|
2013-02-15 17:11:57 +08:00
|
|
|
*
|
2020-07-08 19:35:46 +08:00
|
|
|
* Red Hat Inc. https://www.redhat.com
|
2013-02-15 17:11:57 +08:00
|
|
|
*/
|
|
|
|
|
2013-02-15 20:06:38 +08:00
|
|
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
|
|
|
|
2013-02-15 17:11:57 +08:00
|
|
|
#include <acpi/ghes.h>
|
|
|
|
#include <linux/edac.h>
|
2013-02-14 20:11:08 +08:00
|
|
|
#include <linux/dmi.h>
|
2016-10-30 01:16:34 +08: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 08:35:41 +08:00
|
|
|
#include <ras/ras_event.h>
|
2022-10-10 10:35:54 +08:00
|
|
|
#include <linux/notifier.h>
|
2013-02-15 17:11:57 +08:00
|
|
|
|
2022-03-08 22:40:52 +08:00
|
|
|
#define OTHER_DETAIL_LEN 400
|
|
|
|
|
2020-05-19 18:44:39 +08:00
|
|
|
struct ghes_pvt {
|
2013-02-15 17:11:57 +08:00
|
|
|
struct mem_ctl_info *mci;
|
2013-02-20 06:24:12 +08:00
|
|
|
|
|
|
|
/* Buffers for the error handling routine */
|
2022-03-08 22:40:52 +08:00
|
|
|
char other_detail[OTHER_DETAIL_LEN];
|
2013-02-20 06:24:12 +08:00
|
|
|
char msg[80];
|
2013-02-15 17:11:57 +08:00
|
|
|
};
|
|
|
|
|
2019-11-06 04:07:51 +08: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 18:44:39 +08:00
|
|
|
static struct ghes_pvt *ghes_pvt;
|
2013-02-15 17:11:57 +08:00
|
|
|
|
2020-06-04 03:19:21 +08:00
|
|
|
/*
|
|
|
|
* This driver's representation of the system hardware, as collected
|
|
|
|
* from DMI.
|
|
|
|
*/
|
2022-04-21 21:53:19 +08:00
|
|
|
static struct ghes_hw_desc {
|
2020-06-04 03:19:21 +08:00
|
|
|
int num_dimms;
|
|
|
|
struct dimm_info *dimms;
|
|
|
|
} ghes_hw;
|
|
|
|
|
2019-11-06 04:07:51 +08:00
|
|
|
/* GHES registration mutex */
|
|
|
|
static DEFINE_MUTEX(ghes_reg_mutex);
|
|
|
|
|
2017-08-16 16:33:44 +08: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 20:06:38 +08:00
|
|
|
|
2020-08-27 22:04:50 +08:00
|
|
|
static bool system_scanned;
|
|
|
|
|
2022-10-10 10:35:56 +08:00
|
|
|
static struct list_head *ghes_devs;
|
|
|
|
|
2013-02-14 20:11:08 +08: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 18:13:06 +08:00
|
|
|
static struct dimm_info *find_dimm_by_handle(struct mem_ctl_info *mci, u16 handle)
|
2018-09-19 09:59:00 +08:00
|
|
|
{
|
2019-11-06 17:33:07 +08:00
|
|
|
struct dimm_info *dimm;
|
2018-09-19 09:59:00 +08:00
|
|
|
|
2019-11-06 17:33:07 +08:00
|
|
|
mci_for_each_dimm(mci, dimm) {
|
|
|
|
if (dimm->smbios_handle == handle)
|
2020-05-28 18:13:06 +08:00
|
|
|
return dimm;
|
2018-09-19 09:59:00 +08:00
|
|
|
}
|
2019-11-06 17:33:07 +08:00
|
|
|
|
2020-05-28 18:13:06 +08: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);
|
|
|
|
|
2022-07-22 02:05:03 +08:00
|
|
|
/*
|
|
|
|
* Set to a NULL string when both bank and device are zero. In this case,
|
|
|
|
* the label assigned by default will be preserved.
|
|
|
|
*/
|
|
|
|
snprintf(dimm->label, sizeof(dimm->label), "%s%s%s",
|
|
|
|
(bank && *bank) ? bank : "",
|
|
|
|
(bank && *bank && device && *device) ? " " : "",
|
|
|
|
(device && *device) ? device : "");
|
2018-09-19 09:59:00 +08:00
|
|
|
}
|
|
|
|
|
2020-06-04 03:19:21 +08:00
|
|
|
static void assign_dmi_dimm_info(struct dimm_info *dimm, struct memdev_dmi_entry *entry)
|
2013-02-14 20:11:08 +08:00
|
|
|
{
|
2020-06-04 03:19:21 +08:00
|
|
|
u16 rdr_mask = BIT(7) | BIT(13);
|
2013-02-14 20:11:08 +08:00
|
|
|
|
2020-06-04 03:19:21 +08: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 20:11:08 +08:00
|
|
|
|
2020-06-04 03:19:21 +08: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 20:11:08 +08:00
|
|
|
else
|
2020-06-04 03:19:21 +08: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 20:11:08 +08:00
|
|
|
|
2020-06-04 03:19:21 +08: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 20:11:08 +08:00
|
|
|
|
2020-06-04 03:19:21 +08:00
|
|
|
dimm_setup_label(dimm, entry->handle);
|
2018-09-19 09:59:00 +08:00
|
|
|
|
2020-06-04 03:19:21 +08: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 20:11:08 +08:00
|
|
|
}
|
2020-06-04 03:19:21 +08: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;
|
|
|
|
|
2020-12-15 11:04:12 +08:00
|
|
|
new = krealloc_array(hw->dimms, hw->num_dimms + 16,
|
|
|
|
sizeof(struct dimm_info), GFP_KERNEL);
|
2020-06-04 03:19:21 +08:00
|
|
|
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 22:04:50 +08:00
|
|
|
if (system_scanned)
|
2020-06-04 03:19:21 +08:00
|
|
|
return;
|
|
|
|
|
|
|
|
dmi_walk(enumerate_dimms, &ghes_hw);
|
|
|
|
|
2020-08-27 22:04:50 +08:00
|
|
|
system_scanned = true;
|
2013-02-14 20:11:08 +08:00
|
|
|
}
|
|
|
|
|
2022-03-08 22:40:52 +08:00
|
|
|
static int print_mem_error_other_detail(const struct cper_sec_mem_err *mem, char *msg,
|
|
|
|
const char *location, unsigned int len)
|
|
|
|
{
|
|
|
|
u32 n;
|
|
|
|
|
|
|
|
if (!msg)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
n = 0;
|
|
|
|
len -= 1;
|
|
|
|
|
|
|
|
n += scnprintf(msg + n, len - n, "APEI location: %s ", location);
|
|
|
|
|
|
|
|
if (!(mem->validation_bits & CPER_MEM_VALID_ERROR_STATUS))
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
n += scnprintf(msg + n, len - n, "status(0x%016llx): ", mem->error_status);
|
|
|
|
n += scnprintf(msg + n, len - n, "%s ", cper_mem_err_status_str(mem->error_status));
|
|
|
|
|
|
|
|
out:
|
|
|
|
msg[n] = '\0';
|
|
|
|
|
|
|
|
return n;
|
|
|
|
}
|
|
|
|
|
2022-10-10 10:35:54 +08:00
|
|
|
static int ghes_edac_report_mem_error(struct notifier_block *nb,
|
|
|
|
unsigned long val, void *data)
|
2013-02-15 17:11:57 +08:00
|
|
|
{
|
2022-10-10 10:35:54 +08:00
|
|
|
struct cper_sec_mem_err *mem_err = (struct cper_sec_mem_err *)data;
|
2022-03-08 22:40:52 +08:00
|
|
|
struct cper_mem_err_compact cmem;
|
2013-02-15 17:36:27 +08:00
|
|
|
struct edac_raw_error_desc *e;
|
|
|
|
struct mem_ctl_info *mci;
|
2022-10-10 10:35:54 +08:00
|
|
|
unsigned long sev = val;
|
2020-05-19 18:44:39 +08:00
|
|
|
struct ghes_pvt *pvt;
|
2017-08-16 16:33:44 +08:00
|
|
|
unsigned long flags;
|
2013-02-20 06:24:12 +08:00
|
|
|
char *p;
|
2013-02-15 17:36:27 +08:00
|
|
|
|
2017-08-16 16:33:44 +08: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()))
|
2022-10-10 10:35:54 +08:00
|
|
|
return NOTIFY_OK;
|
2017-08-16 16:33:44 +08:00
|
|
|
|
|
|
|
spin_lock_irqsave(&ghes_lock, flags);
|
|
|
|
|
2019-11-06 04:07:51 +08:00
|
|
|
pvt = ghes_pvt;
|
|
|
|
if (!pvt)
|
|
|
|
goto unlock;
|
|
|
|
|
2013-02-15 17:36:27 +08: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 17:33:23 +08:00
|
|
|
e->grain = 1;
|
2013-02-20 06:24:12 +08: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 17:36:27 +08:00
|
|
|
|
|
|
|
switch (sev) {
|
|
|
|
case GHES_SEV_CORRECTED:
|
2020-01-23 17:02:54 +08:00
|
|
|
e->type = HW_EVENT_ERR_CORRECTED;
|
2013-02-15 17:36:27 +08:00
|
|
|
break;
|
|
|
|
case GHES_SEV_RECOVERABLE:
|
2020-01-23 17:02:54 +08:00
|
|
|
e->type = HW_EVENT_ERR_UNCORRECTED;
|
2013-02-15 17:36:27 +08:00
|
|
|
break;
|
|
|
|
case GHES_SEV_PANIC:
|
2020-01-23 17:02:54 +08:00
|
|
|
e->type = HW_EVENT_ERR_FATAL;
|
2013-02-15 17:36:27 +08:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
case GHES_SEV_NO:
|
2020-01-23 17:02:54 +08:00
|
|
|
e->type = HW_EVENT_ERR_INFO;
|
2013-02-15 17:36:27 +08:00
|
|
|
}
|
|
|
|
|
2013-02-20 06:24:12 +08: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) {
|
2022-03-08 22:40:52 +08:00
|
|
|
u8 etype = mem_err->error_type;
|
|
|
|
|
2013-02-20 06:24:12 +08:00
|
|
|
p = pvt->msg;
|
2022-03-08 22:40:52 +08:00
|
|
|
p += snprintf(p, sizeof(pvt->msg), "%s", cper_mem_err_type_str(etype));
|
2013-02-20 06:24:12 +08:00
|
|
|
} else {
|
|
|
|
strcpy(pvt->msg, "unknown error");
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Error address */
|
2013-10-19 05:30:13 +08:00
|
|
|
if (mem_err->validation_bits & CPER_MEM_VALID_PA) {
|
2019-11-06 17:33:20 +08: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 06:24:12 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Error grain */
|
2013-10-19 05:30:13 +08:00
|
|
|
if (mem_err->validation_bits & CPER_MEM_VALID_PA_MASK)
|
2019-11-06 17:33:23 +08:00
|
|
|
e->grain = ~mem_err->physical_addr_mask + 1;
|
2013-02-20 06:24:12 +08:00
|
|
|
|
|
|
|
/* Memory error location, mapped on e->location */
|
|
|
|
p = e->location;
|
2022-03-08 22:40:52 +08:00
|
|
|
cper_mem_err_pack(mem_err, &cmem);
|
|
|
|
p += cper_mem_err_location(&cmem, p);
|
|
|
|
|
2013-10-19 05:30:38 +08:00
|
|
|
if (mem_err->validation_bits & CPER_MEM_VALID_MODULE_HANDLE) {
|
2020-05-28 18:13:06 +08:00
|
|
|
struct dimm_info *dimm;
|
2018-09-19 09:59:00 +08:00
|
|
|
|
2022-03-08 22:40:52 +08:00
|
|
|
p += cper_dimm_err_location(&cmem, p);
|
2020-05-28 18:13:06 +08: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 05:30:38 +08:00
|
|
|
}
|
2013-02-20 06:24:12 +08:00
|
|
|
if (p > e->location)
|
|
|
|
*(p - 1) = '\0';
|
|
|
|
|
2020-05-28 18:13:06 +08:00
|
|
|
if (!*e->label)
|
|
|
|
strcpy(e->label, "unknown memory");
|
|
|
|
|
2013-02-20 06:24:12 +08:00
|
|
|
/* All other fields are mapped on e->other_detail */
|
|
|
|
p = pvt->other_detail;
|
2022-03-08 22:40:52 +08:00
|
|
|
p += print_mem_error_other_detail(mem_err, p, e->location, OTHER_DETAIL_LEN);
|
2013-02-20 06:24:12 +08:00
|
|
|
if (p > pvt->other_detail)
|
|
|
|
*(p - 1) = '\0';
|
2013-02-15 17:36:27 +08:00
|
|
|
|
2020-01-23 17:02:56 +08:00
|
|
|
edac_raw_mc_handle_error(e);
|
2019-11-06 04:07:51 +08:00
|
|
|
|
|
|
|
unlock:
|
2017-08-16 16:33:44 +08:00
|
|
|
spin_unlock_irqrestore(&ghes_lock, flags);
|
2022-10-10 10:35:54 +08:00
|
|
|
|
|
|
|
return NOTIFY_OK;
|
2013-02-15 17:11:57 +08:00
|
|
|
}
|
|
|
|
|
2022-10-10 10:35:54 +08:00
|
|
|
static struct notifier_block ghes_edac_mem_err_nb = {
|
|
|
|
.notifier_call = ghes_edac_report_mem_error,
|
|
|
|
.priority = 0,
|
|
|
|
};
|
|
|
|
|
2022-10-10 10:35:56 +08:00
|
|
|
static int ghes_edac_register(struct device *dev)
|
2013-02-15 17:11:57 +08:00
|
|
|
{
|
2013-02-14 20:11:08 +08:00
|
|
|
bool fake = false;
|
2013-02-15 17:11:57 +08:00
|
|
|
struct mem_ctl_info *mci;
|
2020-05-19 18:44:39 +08:00
|
|
|
struct ghes_pvt *pvt;
|
2013-02-15 17:11:57 +08:00
|
|
|
struct edac_mc_layer layers[1];
|
2019-11-06 04:07:51 +08:00
|
|
|
unsigned long flags;
|
2020-06-04 03:19:21 +08:00
|
|
|
int rc = 0;
|
2017-08-24 06:54:45 +08:00
|
|
|
|
2019-11-06 04:07:51 +08:00
|
|
|
/* finish another registration/unregistration instance first */
|
|
|
|
mutex_lock(&ghes_reg_mutex);
|
|
|
|
|
2017-08-16 16:33:44 +08:00
|
|
|
/*
|
|
|
|
* We have only one logical memory controller to which all DIMMs belong.
|
|
|
|
*/
|
2019-11-06 04:07:51 +08:00
|
|
|
if (refcount_inc_not_zero(&ghes_refcount))
|
|
|
|
goto unlock;
|
2017-08-16 16:33:44 +08:00
|
|
|
|
2020-06-04 03:19:21 +08:00
|
|
|
ghes_scan_system();
|
2013-02-14 20:11:08 +08:00
|
|
|
|
|
|
|
/* Check if we've got a bogus BIOS */
|
2020-06-04 03:19:21 +08:00
|
|
|
if (!ghes_hw.num_dimms) {
|
2013-02-14 20:11:08 +08:00
|
|
|
fake = true;
|
2020-06-04 03:19:21 +08:00
|
|
|
ghes_hw.num_dimms = 1;
|
2013-02-14 20:11:08 +08:00
|
|
|
}
|
2013-02-15 17:11:57 +08:00
|
|
|
|
|
|
|
layers[0].type = EDAC_MC_LAYER_ALL_MEM;
|
2020-06-04 03:19:21 +08:00
|
|
|
layers[0].size = ghes_hw.num_dimms;
|
2013-02-15 17:11:57 +08:00
|
|
|
layers[0].is_virt_csrow = true;
|
|
|
|
|
2020-05-19 18:44:39 +08:00
|
|
|
mci = edac_mc_alloc(0, ARRAY_SIZE(layers), layers, sizeof(struct ghes_pvt));
|
2013-02-15 17:11:57 +08:00
|
|
|
if (!mci) {
|
2013-02-15 20:06:38 +08:00
|
|
|
pr_info("Can't allocate memory for EDAC data\n");
|
2019-11-06 04:07:51 +08:00
|
|
|
rc = -ENOMEM;
|
|
|
|
goto unlock;
|
2013-02-15 17:11:57 +08:00
|
|
|
}
|
|
|
|
|
2019-11-06 04:07:51 +08:00
|
|
|
pvt = mci->pvt_info;
|
|
|
|
pvt->mci = mci;
|
2013-02-15 17:11:57 +08:00
|
|
|
|
2017-08-16 16:33:44 +08:00
|
|
|
mci->pdev = dev;
|
2013-02-15 17:11:57 +08: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 06:54:45 +08: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");
|
2013-02-15 20:06:38 +08:00
|
|
|
}
|
|
|
|
|
2022-10-10 10:35:55 +08:00
|
|
|
pr_info("This system has %d DIMM sockets.\n", ghes_hw.num_dimms);
|
|
|
|
|
2013-02-14 20:11:08 +08:00
|
|
|
if (!fake) {
|
2020-06-04 03:19:21 +08: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 20:11:08 +08: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 17:33:02 +08:00
|
|
|
struct dimm_info *dimm = edac_get_dimm(mci, 0, 0, 0);
|
2013-02-15 17:11:57 +08:00
|
|
|
|
2013-02-15 20:06:38 +08:00
|
|
|
dimm->nr_pages = 1;
|
2013-02-14 20:11:08 +08:00
|
|
|
dimm->grain = 128;
|
|
|
|
dimm->mtype = MEM_UNKNOWN;
|
|
|
|
dimm->dtype = DEV_UNKNOWN;
|
|
|
|
dimm->edac_mode = EDAC_SECDED;
|
|
|
|
}
|
2013-02-15 17:11:57 +08:00
|
|
|
|
|
|
|
rc = edac_mc_add_mc(mci);
|
|
|
|
if (rc < 0) {
|
2020-06-04 03:19:21 +08:00
|
|
|
pr_info("Can't register with the EDAC core\n");
|
2013-02-15 17:11:57 +08:00
|
|
|
edac_mc_free(mci);
|
2019-11-06 04:07:51 +08:00
|
|
|
rc = -ENODEV;
|
|
|
|
goto unlock;
|
2013-02-15 17:11:57 +08:00
|
|
|
}
|
2019-11-06 04:07:51 +08:00
|
|
|
|
|
|
|
spin_lock_irqsave(&ghes_lock, flags);
|
|
|
|
ghes_pvt = pvt;
|
|
|
|
spin_unlock_irqrestore(&ghes_lock, flags);
|
|
|
|
|
2022-10-10 10:35:54 +08:00
|
|
|
ghes_register_report_chain(&ghes_edac_mem_err_nb);
|
|
|
|
|
2019-11-22 05:36:57 +08:00
|
|
|
/* only set on success */
|
|
|
|
refcount_set(&ghes_refcount, 1);
|
2019-11-06 04:07:51 +08:00
|
|
|
|
|
|
|
unlock:
|
2020-06-04 03:19:21 +08:00
|
|
|
|
|
|
|
/* Not needed anymore */
|
|
|
|
kfree(ghes_hw.dimms);
|
|
|
|
ghes_hw.dimms = NULL;
|
|
|
|
|
2019-11-06 04:07:51 +08:00
|
|
|
mutex_unlock(&ghes_reg_mutex);
|
|
|
|
|
|
|
|
return rc;
|
2013-02-15 17:11:57 +08:00
|
|
|
}
|
|
|
|
|
2022-10-10 10:35:56 +08:00
|
|
|
static void ghes_edac_unregister(struct ghes *ghes)
|
2013-02-15 17:11:57 +08:00
|
|
|
{
|
|
|
|
struct mem_ctl_info *mci;
|
2019-11-06 04:07:51 +08:00
|
|
|
unsigned long flags;
|
2017-08-16 16:33:44 +08:00
|
|
|
|
2019-11-06 04:07:51 +08:00
|
|
|
mutex_lock(&ghes_reg_mutex);
|
2018-04-26 18:16:49 +08:00
|
|
|
|
2020-08-27 22:04:50 +08:00
|
|
|
system_scanned = false;
|
2020-09-11 18:55:55 +08:00
|
|
|
memset(&ghes_hw, 0, sizeof(struct ghes_hw_desc));
|
2020-08-27 22:04:50 +08:00
|
|
|
|
2019-11-06 04:07:51 +08: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-15 01:19:18 +08:00
|
|
|
|
2019-11-06 04:07:51 +08: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-15 01:19:18 +08:00
|
|
|
ghes_pvt = NULL;
|
2019-11-06 04:07:51 +08:00
|
|
|
spin_unlock_irqrestore(&ghes_lock, flags);
|
|
|
|
|
|
|
|
if (!mci)
|
|
|
|
goto unlock;
|
|
|
|
|
|
|
|
mci = edac_mc_del_mc(mci->pdev);
|
|
|
|
if (mci)
|
|
|
|
edac_mc_free(mci);
|
|
|
|
|
2022-10-10 10:35:54 +08:00
|
|
|
ghes_unregister_report_chain(&ghes_edac_mem_err_nb);
|
|
|
|
|
2019-11-06 04:07:51 +08:00
|
|
|
unlock:
|
|
|
|
mutex_unlock(&ghes_reg_mutex);
|
2013-02-15 17:11:57 +08:00
|
|
|
}
|
2022-10-10 10:35:56 +08:00
|
|
|
|
|
|
|
static int __init ghes_edac_init(void)
|
|
|
|
{
|
|
|
|
struct ghes *g, *g_tmp;
|
|
|
|
|
|
|
|
ghes_devs = ghes_get_devices();
|
|
|
|
if (!ghes_devs)
|
|
|
|
return -ENODEV;
|
|
|
|
|
|
|
|
if (list_empty(ghes_devs)) {
|
|
|
|
pr_info("GHES probing device list is empty");
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
|
|
|
|
list_for_each_entry_safe(g, g_tmp, ghes_devs, elist) {
|
|
|
|
ghes_edac_register(g->dev);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
module_init(ghes_edac_init);
|
|
|
|
|
|
|
|
static void __exit ghes_edac_exit(void)
|
|
|
|
{
|
|
|
|
struct ghes *g, *g_tmp;
|
|
|
|
|
|
|
|
list_for_each_entry_safe(g, g_tmp, ghes_devs, elist) {
|
|
|
|
ghes_edac_unregister(g);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
module_exit(ghes_edac_exit);
|
|
|
|
|
|
|
|
MODULE_LICENSE("GPL");
|
|
|
|
MODULE_DESCRIPTION("Output ACPI APEI/GHES BIOS detected errors via EDAC");
|