From: santino.wang <santino.wang@mediatek.com>
To: Matthias Brugger <matthias.bgg@gmail.com>,
AngeloGioacchino Del Regno
<angelogioacchino.delregno@collabora.com>
Cc: <linux-mediatek@lists.infradead.org>,
<linux-arm-kernel@lists.infradead.org>,
<linux-kernel@vger.kernel.org>, <light.chen@mediatek.com>,
<Fred-WY.Chen@mediatek.com>, <arnab.layek@mediatek.com>,
Santino Wang <santino.wang@mediatek.com>
Subject: [RFC PATCH 10/11] soc: mediatek: dram-ecc: report an uncorrectable error of the previous boot
Date: Mon, 2 Nov 2026 17:23:32 +0800 [thread overview]
Message-ID: <20261102092334.138759-11-santino.wang@mediatek.com> (raw)
In-Reply-To: <20261102092334.138759-1-santino.wang@mediatek.com>
From: Santino Wang <santino.wang@mediatek.com>
A fatal uncorrectable error makes the system panic and reset. The
firmware preserves the error across the reset and UEFI publishes it in
the ACPI BERT.
When the module loads, scan the BERT for such a record, log it, emit the
standard mc_event tracepoint so tools like rasdaemon see it, count it
in the aggregate counters, and delete the UEFI variable that holds the
record so it is not reported again.
Signed-off-by: Santino Wang <santino.wang@mediatek.com>
---
drivers/soc/mediatek/dram-ecc/Kconfig | 11 +-
drivers/soc/mediatek/dram-ecc/core/Makefile | 1 +
.../soc/mediatek/dram-ecc/core/bert_check.c | 240 ++++++++++++++++++
.../soc/mediatek/dram-ecc/core/bert_check.h | 12 +
drivers/soc/mediatek/dram-ecc/core/counters.c | 20 ++
drivers/soc/mediatek/dram-ecc/core/counters.h | 1 +
drivers/soc/mediatek/dram-ecc/core/main.c | 10 +-
7 files changed, 290 insertions(+), 5 deletions(-)
create mode 100644 drivers/soc/mediatek/dram-ecc/core/bert_check.c
create mode 100644 drivers/soc/mediatek/dram-ecc/core/bert_check.h
diff --git a/drivers/soc/mediatek/dram-ecc/Kconfig b/drivers/soc/mediatek/dram-ecc/Kconfig
index c015c66635b2..bb46e00f8988 100644
--- a/drivers/soc/mediatek/dram-ecc/Kconfig
+++ b/drivers/soc/mediatek/dram-ecc/Kconfig
@@ -10,7 +10,10 @@ config MTK_DRAM_ECC
depends on HAVE_ARM_SMCCC && ACPI_APEI_GHES
default m
help
- Driver for MediaTek DRAM ECC runtime error handling. It works
- with the secure firmware (ATF), which owns the DRAM controller
- ECC interrupts and reports each corrected or uncorrectable
- error to the OS as an ACPI GHES record.
+ Driver for MediaTek DRAM ECC runtime error handling: BERT check
+ on boot for the previous boot's UCE record, GHES real-time
+ CE/UCE notification from ATF's dramc_ecc_fiq.c (independently
+ decoded from the raw ECCERR_INFO0~31/DB0~DB4 dump, mirroring
+ ATF's own decode), aggregate EFI-NV counters shared with UEFI
+ RasDxe.c, and a sysfs interface for containment policy, storm
+ protection, and leaky-bucket tuning (pushed to ATF via SMC).
diff --git a/drivers/soc/mediatek/dram-ecc/core/Makefile b/drivers/soc/mediatek/dram-ecc/core/Makefile
index 1e2764914e9c..8845eab3a67b 100644
--- a/drivers/soc/mediatek/dram-ecc/core/Makefile
+++ b/drivers/soc/mediatek/dram-ecc/core/Makefile
@@ -5,6 +5,7 @@ obj-$(CONFIG_MTK_DRAM_ECC) += mtk_dram_ecc.o
mtk_dram_ecc-y := \
main.o \
smc.o \
+ bert_check.o \
ghes_notifier.o \
netlink.o \
trace.o \
diff --git a/drivers/soc/mediatek/dram-ecc/core/bert_check.c b/drivers/soc/mediatek/dram-ecc/core/bert_check.c
new file mode 100644
index 000000000000..bac866ae7198
--- /dev/null
+++ b/drivers/soc/mediatek/dram-ecc/core/bert_check.c
@@ -0,0 +1,240 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2026 MediaTek Inc.
+ */
+/*
+ * MTK DRAM ECC BERT check
+ *
+ * On module init, scan the ACPI BERT table for fatal memory error records
+ * left by the previous boot (UCE containment reset). For each fatal CPER
+ * memory error section found, emit a trace_mc_event() directly so rasdaemon
+ * receives the event via the normal ras/mc_event tracefs path - with no
+ * side effects (no EDAC counter bump, no memory_failure(), no notifier chain).
+ * Once a record is reported, the LastUceError EFI variable UEFI built the
+ * BERT from is deleted.
+ *
+ * EFI variable writes can take tens of milliseconds.
+ * The entire check runs in a workqueue so module_init returns immediately.
+ */
+
+#include <linux/acpi.h>
+#include <linux/cper.h>
+#include <linux/workqueue.h>
+#include <ras/ras_event.h>
+#include <acpi/apei.h>
+#include <acpi/ghes.h>
+
+#include "bert_check.h"
+#include "counters.h"
+
+static void bert_check_work_fn(struct work_struct *work);
+static DECLARE_WORK(bert_check_work, bert_check_work_fn);
+
+/* CPER memory error section GUID: a5bc1114-6f64-4ede-b863-3e83ed7c83b1 */
+static const guid_t cper_mem_guid =
+ GUID_INIT(0xa5bc1114, 0x6f64, 0x4ede,
+ 0xb8, 0x63, 0x3e, 0x83, 0xed, 0x7c, 0x83, 0xb1);
+
+/* MTK DRAM ECC BERT vendor section: b2c3d4e5-f6a7-48b9-c0d1-e2f3a4b5c6d7 */
+static const guid_t mtk_dram_ecc_bert_guid =
+ GUID_INIT(0xb2c3d4e5, 0xf6a7, 0x48b9,
+ 0xc0, 0xd1, 0xe2, 0xf3, 0xa4, 0xb5, 0xc6, 0xd7);
+
+struct mtk_dram_ecc_bert_section {
+ u32 uce_detected;
+ u32 channel;
+ u32 subchannel;
+ u32 info_low;
+ u32 info_high;
+ u32 db0;
+ u32 db1;
+ u32 db2;
+ u32 db3;
+ u32 db4;
+ u32 uce_recorded;
+ u32 from_inline_ecc;
+ u32 from_ondie_ecc;
+};
+
+/* Returns 1 if this section was a matching DRAM ECC record (emitted an
+ * mc_event), 0 otherwise - so the caller can summarize the scan.
+ */
+static int mtk_bert_handle_section(struct acpi_hest_generic_data *gdata,
+ const struct mtk_dram_ecc_bert_section *vendor)
+{
+ struct cper_sec_mem_err *mem;
+ guid_t *sec_type;
+ u32 sec_sev;
+ unsigned long pfn = 0;
+
+ sec_type = (guid_t *)gdata->section_type;
+ if (!guid_equal(sec_type, &cper_mem_guid))
+ return 0;
+
+ sec_sev = gdata->error_severity;
+ if (sec_sev != CPER_SEV_FATAL && sec_sev != CPER_SEV_RECOVERABLE)
+ return 0;
+
+ /* The MTK vendor section is length-checked; the standard one must be
+ * too, or a truncated section with a valid GUID reads past its end.
+ */
+ if (acpi_hest_get_error_length(gdata) < sizeof(struct cper_sec_mem_err)) {
+ pr_warn("mtk-dram-ecc: BERT memory-error section too short: %d < %zu\n",
+ acpi_hest_get_error_length(gdata),
+ sizeof(struct cper_sec_mem_err));
+ return 0;
+ }
+
+ mem = acpi_hest_get_payload(gdata);
+ if (mem->validation_bits & CPER_MEM_VALID_PA)
+ pfn = mem->physical_addr >> PAGE_SHIFT;
+
+ pr_info("mtk-dram-ecc: BERT UCE from previous boot (pa=0x%lx)\n",
+ pfn << PAGE_SHIFT);
+
+ trace_mc_event(HW_EVENT_ERR_UNCORRECTED,
+ "multi-bit ECC",
+ "BERT UCE from previous boot",
+ 1, /* error_count */
+ 0, /* mc_index */
+ -1, -1, -1,
+ pfn,
+ 0, /* grain_bits */
+ 0, /* syndrome */
+ "mtk-dram-ecc");
+
+ /* Inline ECC detects every UCE, including an on-die one. */
+ mtk_dram_ecc_agg_uce_inline_inc();
+ if (vendor->from_ondie_ecc)
+ mtk_dram_ecc_agg_uce_ondie_inc();
+ return 1;
+}
+
+static struct mtk_dram_ecc_bert_section *
+mtk_bert_find_vendor_section(struct acpi_hest_generic_status *estatus)
+{
+ struct acpi_hest_generic_data *gdata;
+ guid_t *sec_type;
+
+ apei_estatus_for_each_section(estatus, gdata) {
+ sec_type = (guid_t *)gdata->section_type;
+ if (!guid_equal(sec_type, &mtk_dram_ecc_bert_guid))
+ continue;
+
+ if (acpi_hest_get_error_length(gdata) <
+ sizeof(struct mtk_dram_ecc_bert_section)) {
+ pr_warn("mtk-dram-ecc: BERT vendor section does not contain UceRecorded\n");
+ return NULL;
+ }
+
+ return acpi_hest_get_payload(gdata);
+ }
+
+ return NULL;
+}
+
+static int mtk_bert_handle_estatus(struct acpi_hest_generic_status *estatus)
+{
+ struct mtk_dram_ecc_bert_section *vendor;
+ struct acpi_hest_generic_data *gdata;
+
+ vendor = mtk_bert_find_vendor_section(estatus);
+ if (!vendor || !READ_ONCE(vendor->uce_detected) ||
+ READ_ONCE(vendor->uce_recorded))
+ return 0;
+
+ apei_estatus_for_each_section(estatus, gdata) {
+ if (!mtk_bert_handle_section(gdata, vendor))
+ continue;
+
+ WRITE_ONCE(vendor->uce_recorded, 1);
+ return 1;
+ }
+
+ return 0;
+}
+
+static void bert_check_work_fn(struct work_struct *work)
+{
+ mtk_dram_ecc_bert_check();
+}
+
+void mtk_dram_ecc_bert_check_schedule(void)
+{
+ schedule_work(&bert_check_work);
+}
+
+void mtk_dram_ecc_bert_check_cancel(void)
+{
+ cancel_work_sync(&bert_check_work);
+}
+
+void mtk_dram_ecc_bert_check(void)
+{
+ struct acpi_hest_generic_status *estatus;
+ struct acpi_table_bert *bert_tab;
+ unsigned int region_len, remain;
+ unsigned int fatal = 0;
+ acpi_status status;
+ void __iomem *region;
+ u32 estatus_len;
+
+ status = acpi_get_table(ACPI_SIG_BERT, 0,
+ (struct acpi_table_header **)&bert_tab);
+ if (status == AE_NOT_FOUND) {
+ pr_info("mtk-dram-ecc: BERT table not present (no previous-boot record)\n");
+ return;
+ }
+ if (ACPI_FAILURE(status)) {
+ pr_warn("mtk-dram-ecc: failed to get BERT table\n");
+ return;
+ }
+
+ region_len = bert_tab->region_length;
+ if (region_len < sizeof(*estatus))
+ goto put_table;
+
+ region = ioremap_cache(bert_tab->address, region_len);
+ if (!region) {
+ pr_warn("mtk-dram-ecc: failed to ioremap BERT region\n");
+ goto put_table;
+ }
+
+ estatus = (struct acpi_hest_generic_status *)region;
+ remain = region_len;
+
+ while (remain >= sizeof(*estatus)) {
+ if (estatus->raw_data_length)
+ estatus_len = estatus->raw_data_offset +
+ estatus->raw_data_length;
+ else
+ estatus_len = sizeof(*estatus) + estatus->data_length;
+ if (remain < estatus_len || estatus_len == 0)
+ break;
+
+ if (cper_estatus_check(estatus)) {
+ pr_warn("mtk-dram-ecc: invalid BERT estatus, stopping\n");
+ break;
+ }
+
+ fatal += mtk_bert_handle_estatus(estatus);
+
+ estatus = (void *)estatus + estatus_len;
+ remain -= estatus_len;
+ }
+
+ iounmap(region);
+put_table:
+ acpi_put_table((struct acpi_table_header *)bert_tab);
+
+ /*
+ * The BERT UCE is reported now, so drop the LastUceError variable that
+ * UEFI RasDxe built it from. Kept when nothing was reported, so the
+ * record is not lost if the BERT scan could not process it.
+ */
+ if (fatal)
+ mtk_dram_ecc_last_uce_clear();
+
+ pr_info("mtk-dram-ecc: BERT scan complete: %u fatal DRAM ECC record(s)\n",
+ fatal);
+}
diff --git a/drivers/soc/mediatek/dram-ecc/core/bert_check.h b/drivers/soc/mediatek/dram-ecc/core/bert_check.h
new file mode 100644
index 000000000000..a12dd520f6b9
--- /dev/null
+++ b/drivers/soc/mediatek/dram-ecc/core/bert_check.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2026 MediaTek Inc.
+ */
+#ifndef _MTK_DRAM_ECC_BERT_CHECK_H_
+#define _MTK_DRAM_ECC_BERT_CHECK_H_
+
+void mtk_dram_ecc_bert_check_schedule(void);
+void mtk_dram_ecc_bert_check_cancel(void);
+void mtk_dram_ecc_bert_check(void);
+
+#endif /* _MTK_DRAM_ECC_BERT_CHECK_H_ */
diff --git a/drivers/soc/mediatek/dram-ecc/core/counters.c b/drivers/soc/mediatek/dram-ecc/core/counters.c
index 8e3fd83cf60a..b3c207190855 100644
--- a/drivers/soc/mediatek/dram-ecc/core/counters.c
+++ b/drivers/soc/mediatek/dram-ecc/core/counters.c
@@ -36,6 +36,8 @@ static efi_guid_t mtk_ecc_efi_guid =
static efi_char16_t mtk_ce_var_name[] = L"MtkDramEccCeCount";
static efi_char16_t mtk_uce_inline_var_name[] = L"MtkDramEccUceCount_inline";
static efi_char16_t mtk_uce_ondie_var_name[] = L"MtkDramEccUceCount_ondie";
+/* Written by UEFI RasDxe for the UCE that the BERT carries; same GUID. */
+static efi_char16_t mtk_last_uce_var_name[] = L"LastUceError";
static struct kobject *counters_kobj;
static struct kobject *retired_kobj;
@@ -110,6 +112,24 @@ static void counters_flush_work_fn(struct work_struct *work)
efi_counter_inc(mtk_uce_ondie_var_name, uce_ondie_delta);
}
+/*
+ * UEFI keeps LastUceError until the kernel has processed it, so the UCE is
+ * rebuilt into the BERT on every boot until then. Delete it once the BERT
+ * record has been reported. A zero DataSize deletes the variable.
+ */
+void mtk_dram_ecc_last_uce_clear(void)
+{
+ efi_status_t status;
+
+ if (!efi_rt_services_supported(EFI_RT_SUPPORTED_SET_VARIABLE))
+ return;
+
+ status = efi.set_variable(mtk_last_uce_var_name, &mtk_ecc_efi_guid,
+ MTK_ECC_EFI_ATTR, 0, NULL);
+ if (status != EFI_SUCCESS && status != EFI_NOT_FOUND)
+ pr_warn("mtk-dram-ecc: delete LastUceError failed: 0x%lx\n", status);
+}
+
void mtk_dram_ecc_agg_ce_inc(void)
{
atomic_inc(&pending_ce_inc);
diff --git a/drivers/soc/mediatek/dram-ecc/core/counters.h b/drivers/soc/mediatek/dram-ecc/core/counters.h
index af2e5ea16b90..a818ff398769 100644
--- a/drivers/soc/mediatek/dram-ecc/core/counters.h
+++ b/drivers/soc/mediatek/dram-ecc/core/counters.h
@@ -19,6 +19,7 @@ void mtk_dram_ecc_counters_exit(void);
void mtk_dram_ecc_agg_ce_inc(void);
void mtk_dram_ecc_agg_uce_inline_inc(void);
void mtk_dram_ecc_agg_uce_ondie_inc(void);
+void mtk_dram_ecc_last_uce_clear(void);
void mtk_dram_ecc_record_retired_page(unsigned long pfn,
enum mtk_dram_ecc_retire_reason reason,
int memory_failure_ret);
diff --git a/drivers/soc/mediatek/dram-ecc/core/main.c b/drivers/soc/mediatek/dram-ecc/core/main.c
index 320631b3a044..0c3bc4d6ef06 100644
--- a/drivers/soc/mediatek/dram-ecc/core/main.c
+++ b/drivers/soc/mediatek/dram-ecc/core/main.c
@@ -12,6 +12,7 @@
#include "addresses.h"
#include "netlink.h"
#include "trace.h"
+#include "bert_check.h"
static struct class *mtk_dram_ecc_class;
static struct device *mtk_dram_ecc_dev;
@@ -63,8 +64,14 @@ static int __init mtk_dram_ecc_init(void)
if (ret)
goto err_addresses;
+ mtk_dram_ecc_bert_check_schedule();
+
trace_mtk_dram_ecc_lifecycle(0);
- dev_info(mtk_dram_ecc_dev, "initialized (GHES vendor notifier registered)\n");
+ /* Log the initialization independently of the tracepoints, so the log
+ * shows that the module is up, the GHES vendor notifier is registered
+ * and the BERT scan is scheduled.
+ */
+ dev_info(mtk_dram_ecc_dev, "initialized (GHES vendor notifier registered, BERT scan scheduled)\n");
return 0;
err_addresses:
@@ -90,6 +97,7 @@ static void __exit mtk_dram_ecc_exit(void)
* state it touches is torn down.
*/
mtk_dram_ecc_ghes_exit();
+ mtk_dram_ecc_bert_check_cancel();
mtk_dram_ecc_addresses_exit();
mtk_dram_ecc_counters_exit();
mtk_dram_ecc_policy_exit(mtk_dram_ecc_dev);
--
2.45.2
next prev parent reply other threads:[~2026-11-02 9:25 UTC|newest]
Thread overview: 20+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-02 9:23 [RFC PATCH 00/11] soc: mediatek: add a DRAM ECC error handling driver santino.wang
2026-11-02 9:23 ` [RFC PATCH 01/11] soc: mediatek: dram-ecc: add the DRAM ECC driver skeleton santino.wang
2026-11-02 13:41 ` Krzysztof Kozlowski
2026-11-02 9:23 ` [RFC PATCH 02/11] soc: mediatek: dram-ecc: add the SMC interface to the secure firmware santino.wang
2026-11-02 9:23 ` [RFC PATCH 03/11] soc: mediatek: dram-ecc: add tracepoints santino.wang
2026-11-02 19:57 ` Jonathan Cameron
2026-11-02 9:23 ` [RFC PATCH 04/11] soc: mediatek: dram-ecc: handle GHES vendor records from the firmware santino.wang
2026-11-02 9:23 ` [RFC PATCH 05/11] soc: mediatek: dram-ecc: add error counters and the address lists santino.wang
2026-11-02 13:42 ` Krzysztof Kozlowski
2026-11-02 9:23 ` [RFC PATCH 06/11] soc: mediatek: dram-ecc: keep error counters across boots santino.wang
2026-11-02 9:23 ` [RFC PATCH 07/11] soc: mediatek: dram-ecc: add containment policy and storm protection santino.wang
2026-11-02 9:23 ` [RFC PATCH 08/11] soc: mediatek: dram-ecc: add the corrected error leaky bucket santino.wang
2026-11-02 20:19 ` Jonathan Cameron
2026-11-02 9:23 ` [RFC PATCH 09/11] soc: mediatek: dram-ecc: add a generic netlink event interface santino.wang
2026-11-02 20:20 ` Jonathan Cameron
2026-11-02 9:23 ` santino.wang [this message]
2026-11-02 13:44 ` [RFC PATCH 10/11] soc: mediatek: dram-ecc: report an uncorrectable error of the previous boot Krzysztof Kozlowski
2026-11-02 9:23 ` [RFC PATCH 11/11] Documentation: ABI: add the sysfs attributes of the MediaTek DRAM ECC driver santino.wang
2026-11-02 20:36 ` [RFC PATCH 00/11] soc: mediatek: add a DRAM ECC error handling driver Jonathan Cameron
2026-11-02 20:44 ` Jonathan Cameron
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261102092334.138759-11-santino.wang@mediatek.com \
--to=santino.wang@mediatek.com \
--cc=Fred-WY.Chen@mediatek.com \
--cc=angelogioacchino.delregno@collabora.com \
--cc=arnab.layek@mediatek.com \
--cc=light.chen@mediatek.com \
--cc=linux-arm-kernel@lists.infradead.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-mediatek@lists.infradead.org \
--cc=matthias.bgg@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®