mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Krzysztof Kozlowski <krzk@kernel.org>
To: "santino.wang" <santino.wang@mediatek.com>,
	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
Subject: Re: [RFC PATCH 10/11] soc: mediatek: dram-ecc: report an uncorrectable error of the previous boot
Date: Mon, 2 Nov 2026 14:44:22 +0100	[thread overview]
Message-ID: <7a9e1729-96e3-46ab-86c0-f76349fc4a8c@kernel.org> (raw)
In-Reply-To: <20261102092334.138759-11-santino.wang@mediatek.com>

On 02/11/2026 10:23, santino.wang wrote:
> 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

What is GHES vendor?

> +	 * and the BERT scan is scheduled.
> +	 */
> +	dev_info(mtk_dram_ecc_dev, "initialized (GHES vendor notifier registered, BERT scan scheduled)\n");

Drop. Why would you driver print always message on every other
non-mediatek machine?


Krzysztof

  reply	other threads:[~2026-11-02 13:44 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 ` [RFC PATCH 10/11] soc: mediatek: dram-ecc: report an uncorrectable error of the previous boot santino.wang
2026-11-02 13:44   ` Krzysztof Kozlowski [this message]
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=7a9e1729-96e3-46ab-86c0-f76349fc4a8c@kernel.org \
    --to=krzk@kernel.org \
    --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 \
    --cc=santino.wang@mediatek.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®