Linux-ARM-Kernel Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [RFC PATCH 00/11] soc: mediatek: add a DRAM ECC error handling driver
@ 2026-11-02  9:23 santino.wang
  2026-11-02  9:23 ` [RFC PATCH 01/11] soc: mediatek: dram-ecc: add the DRAM ECC driver skeleton santino.wang
                   ` (11 more replies)
  0 siblings, 12 replies; 20+ messages in thread
From: santino.wang @ 2026-11-02  9:23 UTC (permalink / raw)
  To: Matthias Brugger, AngeloGioacchino Del Regno
  Cc: linux-mediatek, linux-arm-kernel, linux-kernel, light.chen,
	Fred-WY.Chen, arnab.layek, Santino Wang

From: Santino Wang <santino.wang@mediatek.com>

On the MediaTek mt8992 SoC the DRAM controller (DRAMC) raises an interrupt for
every corrected (CE) and uncorrectable (UCE) ECC error. The interrupt is
owned by the secure firmware (Arm Trusted Firmware at EL3). It reads the
DRAMC error registers, applies the error containment policy and the CE
storm protection, and hands the error to the OS firmware-first as an ACPI
GHES record in a buffer that UEFI describes in the HEST. The OS is
notified with an edge interrupt that the DSDT delivers through a Generic
Event Device and a Hardware Error Device (GSIV notification).

The record carries a standard Platform Memory Error section and a
MediaTek vendor section with the raw DRAMC error registers. This series
adds the driver that consumes the vendor section through the existing
ghes_register_vendor_record_notifier() interface. No change to the GHES
core or to arm64 code is needed.

The driver
 - decodes the most severe error of a record and keeps counters, also
   across boots in EFI variables,
 - isolates pages with memory_failure(): immediately for a recoverable
   UCE on memory that Linux manages, and for a page whose corrected
   error count reaches a threshold (leaky bucket); memory owned by a
   device is only reported to its owner,
 - lets in-kernel clients and user space (generic netlink, tracepoints)
   follow the events,
 - exposes the containment policy, the storm protection and per range
   severity overrides in sysfs and pushes them to the secure firmware
   through a vendor SMC call,
 - after a fatal UCE (GHES panic and reset), finds the error in the BERT
   of the next boot and reports it through the mc_event tracepoint.

The driver depends on firmware that is not part of this series: the
secure firmware provides the SMC interface used by patch 2 and writes the
vendor section decoded by patch 4. The firmware side (the TF-A and
edk2-platforms changes for the mt8992 SoC, plus the preloader that keeps
the error across the reset) is being developed in the vendor's BSP trees
and is not published yet.

Open question for this RFC: is drivers/soc/mediatek the right place for a
driver that is built on GHES vendor records and a vendor SMC interface, or
would the maintainers prefer another location?

Testing: each patch builds with the arm64 defconfig plus
CONFIG_MTK_DRAM_ECC=m on linux-next 20261030 (W=1, no warnings).
The series has been tested pre-silicon on a Palladium emulation platform.

Santino Wang (11):
  soc: mediatek: dram-ecc: add the DRAM ECC driver skeleton
  soc: mediatek: dram-ecc: add the SMC interface to the secure firmware
  soc: mediatek: dram-ecc: add tracepoints
  soc: mediatek: dram-ecc: handle GHES vendor records from the firmware
  soc: mediatek: dram-ecc: add error counters and the address lists
  soc: mediatek: dram-ecc: keep error counters across boots
  soc: mediatek: dram-ecc: add containment policy and storm protection
  soc: mediatek: dram-ecc: add the corrected error leaky bucket
  soc: mediatek: dram-ecc: add a generic netlink event interface
  soc: mediatek: dram-ecc: report an uncorrectable error of the previous
    boot
  Documentation: ABI: add the sysfs attributes of the MediaTek DRAM ECC
    driver

 .../ABI/testing/sysfs-class-mtk-dram-ecc      | 182 +++++++++
 MAINTAINERS                                   |  10 +
 drivers/soc/mediatek/Kconfig                  |   2 +
 drivers/soc/mediatek/Makefile                 |   1 +
 drivers/soc/mediatek/dram-ecc/Kconfig         |  19 +
 drivers/soc/mediatek/dram-ecc/Makefile        |   3 +
 drivers/soc/mediatek/dram-ecc/core/Makefile   |  20 +
 .../soc/mediatek/dram-ecc/core/addresses.c    |  88 ++++
 .../soc/mediatek/dram-ecc/core/addresses.h    |  13 +
 .../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 | 336 ++++++++++++++++
 drivers/soc/mediatek/dram-ecc/core/counters.h |  29 ++
 .../mediatek/dram-ecc/core/ghes_notifier.c    | 377 ++++++++++++++++++
 .../mediatek/dram-ecc/core/ghes_notifier.h    |  22 +
 drivers/soc/mediatek/dram-ecc/core/main.c     | 113 ++++++
 .../soc/mediatek/dram-ecc/core/mtk_dram_ecc.h |  27 ++
 .../dram-ecc/core/mtk_dram_ecc_event.h        |  47 +++
 drivers/soc/mediatek/dram-ecc/core/netlink.c  |  83 ++++
 drivers/soc/mediatek/dram-ecc/core/netlink.h  |  14 +
 .../dram-ecc/core/policy/address_range.c      | 120 ++++++
 .../dram-ecc/core/policy/containment.c        | 107 +++++
 .../dram-ecc/core/policy/leaky_bucket.c       | 277 +++++++++++++
 .../mediatek/dram-ecc/core/policy/policy.c    |  57 +++
 .../mediatek/dram-ecc/core/policy/policy.h    |  23 ++
 .../dram-ecc/core/policy/storm_protection.c   | 137 +++++++
 drivers/soc/mediatek/dram-ecc/core/smc.c      | 137 +++++++
 drivers/soc/mediatek/dram-ecc/core/smc.h      |  55 +++
 drivers/soc/mediatek/dram-ecc/core/trace.c    |   6 +
 drivers/soc/mediatek/dram-ecc/core/trace.h    | 119 ++++++
 include/uapi/linux/mtk_dram_ecc_netlink.h     |  44 ++
 31 files changed, 2720 insertions(+)
 create mode 100644 Documentation/ABI/testing/sysfs-class-mtk-dram-ecc
 create mode 100644 drivers/soc/mediatek/dram-ecc/Kconfig
 create mode 100644 drivers/soc/mediatek/dram-ecc/Makefile
 create mode 100644 drivers/soc/mediatek/dram-ecc/core/Makefile
 create mode 100644 drivers/soc/mediatek/dram-ecc/core/addresses.c
 create mode 100644 drivers/soc/mediatek/dram-ecc/core/addresses.h
 create mode 100644 drivers/soc/mediatek/dram-ecc/core/bert_check.c
 create mode 100644 drivers/soc/mediatek/dram-ecc/core/bert_check.h
 create mode 100644 drivers/soc/mediatek/dram-ecc/core/counters.c
 create mode 100644 drivers/soc/mediatek/dram-ecc/core/counters.h
 create mode 100644 drivers/soc/mediatek/dram-ecc/core/ghes_notifier.c
 create mode 100644 drivers/soc/mediatek/dram-ecc/core/ghes_notifier.h
 create mode 100644 drivers/soc/mediatek/dram-ecc/core/main.c
 create mode 100644 drivers/soc/mediatek/dram-ecc/core/mtk_dram_ecc.h
 create mode 100644 drivers/soc/mediatek/dram-ecc/core/mtk_dram_ecc_event.h
 create mode 100644 drivers/soc/mediatek/dram-ecc/core/netlink.c
 create mode 100644 drivers/soc/mediatek/dram-ecc/core/netlink.h
 create mode 100644 drivers/soc/mediatek/dram-ecc/core/policy/address_range.c
 create mode 100644 drivers/soc/mediatek/dram-ecc/core/policy/containment.c
 create mode 100644 drivers/soc/mediatek/dram-ecc/core/policy/leaky_bucket.c
 create mode 100644 drivers/soc/mediatek/dram-ecc/core/policy/policy.c
 create mode 100644 drivers/soc/mediatek/dram-ecc/core/policy/policy.h
 create mode 100644 drivers/soc/mediatek/dram-ecc/core/policy/storm_protection.c
 create mode 100644 drivers/soc/mediatek/dram-ecc/core/smc.c
 create mode 100644 drivers/soc/mediatek/dram-ecc/core/smc.h
 create mode 100644 drivers/soc/mediatek/dram-ecc/core/trace.c
 create mode 100644 drivers/soc/mediatek/dram-ecc/core/trace.h
 create mode 100644 include/uapi/linux/mtk_dram_ecc_netlink.h

-- 
2.45.2



^ permalink raw reply	[flat|nested] 20+ messages in thread

* [RFC PATCH 01/11] soc: mediatek: dram-ecc: add the DRAM ECC driver skeleton
  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 ` 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
                   ` (10 subsequent siblings)
  11 siblings, 1 reply; 20+ messages in thread
From: santino.wang @ 2026-11-02  9:23 UTC (permalink / raw)
  To: Matthias Brugger, AngeloGioacchino Del Regno
  Cc: linux-mediatek, linux-arm-kernel, linux-kernel, light.chen,
	Fred-WY.Chen, arnab.layek, Santino Wang

From: Santino Wang <santino.wang@mediatek.com>

On the MediaTek mt8992 SoC the secure firmware (ATF) owns the DRAM
controller ECC interrupts and reports each corrected (CE) or
uncorrectable (UCE) error to the OS as an ACPI GHES record. The
following patches add the driver that consumes those records.

Add only the module skeleton here: the Kconfig entry, the Makefiles, a
class device that the later patches attach their sysfs attributes to,
and the MAINTAINERS entry.

Signed-off-by: Santino Wang <santino.wang@mediatek.com>
---
 MAINTAINERS                                   |  9 ++++
 drivers/soc/mediatek/Kconfig                  |  2 +
 drivers/soc/mediatek/Makefile                 |  1 +
 drivers/soc/mediatek/dram-ecc/Kconfig         | 16 ++++++
 drivers/soc/mediatek/dram-ecc/Makefile        |  3 ++
 drivers/soc/mediatek/dram-ecc/core/Makefile   |  6 +++
 drivers/soc/mediatek/dram-ecc/core/main.c     | 53 +++++++++++++++++++
 .../soc/mediatek/dram-ecc/core/mtk_dram_ecc.h | 24 +++++++++
 8 files changed, 114 insertions(+)
 create mode 100644 drivers/soc/mediatek/dram-ecc/Kconfig
 create mode 100644 drivers/soc/mediatek/dram-ecc/Makefile
 create mode 100644 drivers/soc/mediatek/dram-ecc/core/Makefile
 create mode 100644 drivers/soc/mediatek/dram-ecc/core/main.c
 create mode 100644 drivers/soc/mediatek/dram-ecc/core/mtk_dram_ecc.h

diff --git a/MAINTAINERS b/MAINTAINERS
index aaf2825ac945..e7d25be97f24 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -17026,6 +17026,15 @@ S:	Maintained
 F:	Documentation/devicetree/bindings/dma/mediatek,*
 F:	drivers/dma/mediatek/
 
+MEDIATEK DRAM ECC DRIVER
+M:	Santino Wang <santino.wang@mediatek.com>
+L:	linux-kernel@vger.kernel.org
+L:	linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
+L:	linux-mediatek@lists.infradead.org (moderated for non-subscribers)
+S:	Maintained
+F:	drivers/soc/mediatek/dram-ecc/
+F:	include/uapi/linux/mtk_dram_ecc_netlink.h
+
 MEDIATEK ETHERNET DRIVER
 M:	Felix Fietkau <nbd@nbd.name>
 M:	Lorenzo Bianconi <lorenzo@kernel.org>
diff --git a/drivers/soc/mediatek/Kconfig b/drivers/soc/mediatek/Kconfig
index d7293977f06e..91aed7d29be0 100644
--- a/drivers/soc/mediatek/Kconfig
+++ b/drivers/soc/mediatek/Kconfig
@@ -89,4 +89,6 @@ config MTK_SOCINFO
 	  information about the SoC to the userspace including the
 	  manufacturer name, marketing name and soc name.
 
+source "drivers/soc/mediatek/dram-ecc/Kconfig"
+
 endmenu
diff --git a/drivers/soc/mediatek/Makefile b/drivers/soc/mediatek/Makefile
index 0665573e3c4b..880342bed606 100644
--- a/drivers/soc/mediatek/Makefile
+++ b/drivers/soc/mediatek/Makefile
@@ -9,3 +9,4 @@ obj-$(CONFIG_MTK_MMSYS) += mtk-mmsys.o
 obj-$(CONFIG_MTK_MMSYS) += mtk-mutex.o
 obj-$(CONFIG_MTK_SVS) += mtk-svs.o
 obj-$(CONFIG_MTK_SOCINFO) += mtk-socinfo.o
+obj-y += dram-ecc/
diff --git a/drivers/soc/mediatek/dram-ecc/Kconfig b/drivers/soc/mediatek/dram-ecc/Kconfig
new file mode 100644
index 000000000000..c015c66635b2
--- /dev/null
+++ b/drivers/soc/mediatek/dram-ecc/Kconfig
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# MediaTek DRAM ECC error handling driver
+# See plat/mediatek/drivers/dram_ecc/ in ATF for the SMC handler side.
+#
+
+# core/ -> mtk_dram_ecc.ko
+config MTK_DRAM_ECC
+	tristate "MediaTek DRAM ECC Error Handling Driver"
+	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.
diff --git a/drivers/soc/mediatek/dram-ecc/Makefile b/drivers/soc/mediatek/dram-ecc/Makefile
new file mode 100644
index 000000000000..977922150133
--- /dev/null
+++ b/drivers/soc/mediatek/dram-ecc/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+obj-$(CONFIG_MTK_DRAM_ECC)              += core/
diff --git a/drivers/soc/mediatek/dram-ecc/core/Makefile b/drivers/soc/mediatek/dram-ecc/core/Makefile
new file mode 100644
index 000000000000..77751c836027
--- /dev/null
+++ b/drivers/soc/mediatek/dram-ecc/core/Makefile
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+obj-$(CONFIG_MTK_DRAM_ECC) += mtk_dram_ecc.o
+
+mtk_dram_ecc-y := \
+	main.o
diff --git a/drivers/soc/mediatek/dram-ecc/core/main.c b/drivers/soc/mediatek/dram-ecc/core/main.c
new file mode 100644
index 000000000000..5000d1f3c7e3
--- /dev/null
+++ b/drivers/soc/mediatek/dram-ecc/core/main.c
@@ -0,0 +1,53 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2026 MediaTek Inc.
+ */
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/device/class.h>
+
+#include "mtk_dram_ecc.h"
+
+static struct class *mtk_dram_ecc_class;
+static struct device *mtk_dram_ecc_dev;
+
+struct mtk_dram_ecc_data mtk_ecc_data = {
+	.lb_threshold         = MTK_ECC_LB_THRESHOLD_DEFAULT,
+	.lb_decay_interval_sec = MTK_ECC_LB_DECAY_INTERVAL_DEFAULT,
+	.lb_max_tracked       = MTK_ECC_LB_MAX_TRACKED_DEFAULT,
+};
+
+static int __init mtk_dram_ecc_init(void)
+{
+	int ret;
+
+	mtk_dram_ecc_class = class_create("mtk-dram-ecc");
+	if (IS_ERR(mtk_dram_ecc_class))
+		return PTR_ERR(mtk_dram_ecc_class);
+
+	mtk_dram_ecc_dev = device_create(mtk_dram_ecc_class, NULL,
+					 MKDEV(0, 0), &mtk_ecc_data,
+					 "mtk-dram-ecc0");
+	if (IS_ERR(mtk_dram_ecc_dev)) {
+		ret = PTR_ERR(mtk_dram_ecc_dev);
+		goto err_class;
+	}
+
+	dev_info(mtk_dram_ecc_dev, "initialized\n");
+	return 0;
+
+err_class:
+	class_destroy(mtk_dram_ecc_class);
+	return ret;
+}
+
+static void __exit mtk_dram_ecc_exit(void)
+{
+	device_destroy(mtk_dram_ecc_class, MKDEV(0, 0));
+	class_destroy(mtk_dram_ecc_class);
+}
+
+module_init(mtk_dram_ecc_init);
+module_exit(mtk_dram_ecc_exit);
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("MediaTek DRAM ECC Error Handling Driver");
diff --git a/drivers/soc/mediatek/dram-ecc/core/mtk_dram_ecc.h b/drivers/soc/mediatek/dram-ecc/core/mtk_dram_ecc.h
new file mode 100644
index 000000000000..456961818ccd
--- /dev/null
+++ b/drivers/soc/mediatek/dram-ecc/core/mtk_dram_ecc.h
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2026 MediaTek Inc.
+ */
+#ifndef _MTK_DRAM_ECC_H_
+#define _MTK_DRAM_ECC_H_
+
+#include <linux/types.h>
+#include <linux/device.h>
+
+struct mtk_dram_ecc_data {
+	/* [leaky_bucket] */
+	u32 lb_threshold;
+	u32 lb_decay_interval_sec;
+	u32 lb_max_tracked;
+
+#define MTK_ECC_LB_THRESHOLD_DEFAULT      100
+#define MTK_ECC_LB_DECAY_INTERVAL_DEFAULT 3600
+#define MTK_ECC_LB_MAX_TRACKED_DEFAULT    16
+};
+
+extern struct mtk_dram_ecc_data mtk_ecc_data;
+
+#endif /* _MTK_DRAM_ECC_H_ */
-- 
2.45.2



^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [RFC PATCH 02/11] soc: mediatek: dram-ecc: add the SMC interface to the secure firmware
  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  9:23 ` santino.wang
  2026-11-02  9:23 ` [RFC PATCH 03/11] soc: mediatek: dram-ecc: add tracepoints santino.wang
                   ` (9 subsequent siblings)
  11 siblings, 0 replies; 20+ messages in thread
From: santino.wang @ 2026-11-02  9:23 UTC (permalink / raw)
  To: Matthias Brugger, AngeloGioacchino Del Regno
  Cc: linux-mediatek, linux-arm-kernel, linux-kernel, light.chen,
	Fred-WY.Chen, arnab.layek, Santino Wang

From: Santino Wang <santino.wang@mediatek.com>

The error containment policy and the CE storm protection are enforced
by the secure firmware. Add helpers that read and write the
corresponding settings through vendor-specific SMC calls, so the
sysfs attributes added by later patches can configure them.

Nothing calls these helpers yet.

Signed-off-by: Santino Wang <santino.wang@mediatek.com>
---
 drivers/soc/mediatek/dram-ecc/core/Makefile |   3 +-
 drivers/soc/mediatek/dram-ecc/core/smc.c    | 137 ++++++++++++++++++++
 drivers/soc/mediatek/dram-ecc/core/smc.h    |  55 ++++++++
 3 files changed, 194 insertions(+), 1 deletion(-)
 create mode 100644 drivers/soc/mediatek/dram-ecc/core/smc.c
 create mode 100644 drivers/soc/mediatek/dram-ecc/core/smc.h

diff --git a/drivers/soc/mediatek/dram-ecc/core/Makefile b/drivers/soc/mediatek/dram-ecc/core/Makefile
index 77751c836027..429347bfd057 100644
--- a/drivers/soc/mediatek/dram-ecc/core/Makefile
+++ b/drivers/soc/mediatek/dram-ecc/core/Makefile
@@ -3,4 +3,5 @@
 obj-$(CONFIG_MTK_DRAM_ECC) += mtk_dram_ecc.o
 
 mtk_dram_ecc-y := \
-	main.o
+	main.o \
+	smc.o
diff --git a/drivers/soc/mediatek/dram-ecc/core/smc.c b/drivers/soc/mediatek/dram-ecc/core/smc.c
new file mode 100644
index 000000000000..1f7a195d8b58
--- /dev/null
+++ b/drivers/soc/mediatek/dram-ecc/core/smc.c
@@ -0,0 +1,137 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2026 MediaTek Inc.
+ */
+#include <linux/arm-smccc.h>
+#include <linux/errno.h>
+
+#include "smc.h"
+
+static int smc_set(u64 subcmd, u64 val)
+{
+	struct arm_smccc_res res;
+
+	arm_smccc_smc(MTK_SIP_DRAM_ECC_SET_POLICY_AARCH64,
+		      subcmd, val, 0, 0, 0, 0, 0, &res);
+	return (int)(long)res.a0;
+}
+
+static int smc_get(u64 subcmd, u64 *val)
+{
+	struct arm_smccc_res res;
+
+	arm_smccc_smc(MTK_SIP_DRAM_ECC_SET_POLICY_AARCH64,
+		      subcmd, 0, 0, 0, 0, 0, 0, &res);
+	if ((int)(long)res.a0 != 0)
+		return (int)(long)res.a0;
+	*val = res.a1;
+	return 0;
+}
+
+int mtk_dram_ecc_smc_containment_set_enabled(bool enabled)
+{
+	return smc_set(MTK_ECC_SMC_CONTAINMENT_SET_ENABLED, enabled ? 1 : 0);
+}
+
+int mtk_dram_ecc_smc_containment_get_enabled(bool *enabled)
+{
+	u64 val;
+	int ret = smc_get(MTK_ECC_SMC_CONTAINMENT_GET_ENABLED, &val);
+
+	if (!ret)
+		*enabled = (val != 0);
+	return ret;
+}
+
+int mtk_dram_ecc_smc_containment_set_default_severity(bool recoverable)
+{
+	return smc_set(MTK_ECC_SMC_CONTAINMENT_SET_DEFAULT_SEVERITY, recoverable ? 1 : 0);
+}
+
+int mtk_dram_ecc_smc_containment_get_default_severity(bool *recoverable)
+{
+	u64 val;
+	int ret = smc_get(MTK_ECC_SMC_CONTAINMENT_GET_DEFAULT_SEVERITY, &val);
+
+	if (!ret)
+		*recoverable = (val != 0);
+	return ret;
+}
+
+int mtk_dram_ecc_smc_storm_set_threshold(u32 val)
+{
+	return smc_set(MTK_ECC_SMC_STORM_SET_THRESHOLD, val);
+}
+
+int mtk_dram_ecc_smc_storm_get_threshold(u32 *val)
+{
+	u64 v;
+	int ret = smc_get(MTK_ECC_SMC_STORM_GET_THRESHOLD, &v);
+
+	if (!ret)
+		*val = (u32)v;
+	return ret;
+}
+
+int mtk_dram_ecc_smc_storm_set_time_window(u32 val)
+{
+	return smc_set(MTK_ECC_SMC_STORM_SET_TIME_WINDOW, val);
+}
+
+int mtk_dram_ecc_smc_storm_get_time_window(u32 *val)
+{
+	u64 v;
+	int ret = smc_get(MTK_ECC_SMC_STORM_GET_TIME_WINDOW, &v);
+
+	if (!ret)
+		*val = (u32)v;
+	return ret;
+}
+
+int mtk_dram_ecc_smc_storm_set_cooldown(u32 val)
+{
+	return smc_set(MTK_ECC_SMC_STORM_SET_COOLDOWN, val);
+}
+
+int mtk_dram_ecc_smc_storm_get_cooldown(u32 *val)
+{
+	u64 v;
+	int ret = smc_get(MTK_ECC_SMC_STORM_GET_COOLDOWN, &v);
+
+	if (!ret)
+		*val = (u32)v;
+	return ret;
+}
+
+int mtk_dram_ecc_smc_range_add(u64 addr_from, u64 addr_to, bool recoverable)
+{
+	struct arm_smccc_res res;
+
+	arm_smccc_smc(MTK_SIP_DRAM_ECC_SET_POLICY_AARCH64, MTK_ECC_SMC_RANGE_ADD,
+		      addr_from, addr_to, recoverable ? 1 : 0, 0, 0, 0, &res);
+	return (int)(long)res.a0;
+}
+
+int mtk_dram_ecc_smc_range_clear(void)
+{
+	struct arm_smccc_res res;
+
+	arm_smccc_smc(MTK_SIP_DRAM_ECC_SET_POLICY_AARCH64, MTK_ECC_SMC_RANGE_CLEAR,
+		      0, 0, 0, 0, 0, 0, &res);
+	return (int)(long)res.a0;
+}
+
+int mtk_dram_ecc_smc_range_get(u32 index, u64 *addr_from, u64 *addr_to, bool *recoverable)
+{
+	struct arm_smccc_res res;
+
+	arm_smccc_smc(MTK_SIP_DRAM_ECC_SET_POLICY_AARCH64, MTK_ECC_SMC_RANGE_GET,
+		      index, 0, 0, 0, 0, 0, &res);
+	if ((int)(long)res.a0 != 0)
+		return -ENOENT;
+
+	*addr_from = res.a1;
+	*addr_to = res.a2;
+	*recoverable = (res.a3 != 0);
+	return 0;
+}
diff --git a/drivers/soc/mediatek/dram-ecc/core/smc.h b/drivers/soc/mediatek/dram-ecc/core/smc.h
new file mode 100644
index 000000000000..712c01d28bf4
--- /dev/null
+++ b/drivers/soc/mediatek/dram-ecc/core/smc.h
@@ -0,0 +1,55 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2026 MediaTek Inc.
+ */
+#ifndef _MTK_DRAM_ECC_SMC_H_
+#define _MTK_DRAM_ECC_SMC_H_
+
+#include <linux/arm-smccc.h>
+
+#define MTK_SIP_DRAM_ECC_SET_POLICY_AARCH64 \
+	ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, ARM_SMCCC_SMC_64, \
+			   ARM_SMCCC_OWNER_SIP, 0x542)
+
+/*
+ * Sub-commands for MTK_SIP_DRAM_ECC_SET_POLICY:
+ *   x1 = sub-command
+ *   x2 = payload
+ *   return a0 = 0 on success, negative on error
+ *   return a1 = value (GET commands only)
+ */
+enum mtk_dram_ecc_smc_subcmd {
+	MTK_ECC_SMC_CONTAINMENT_SET_ENABLED          = 0,
+	MTK_ECC_SMC_CONTAINMENT_GET_ENABLED          = 1,
+	MTK_ECC_SMC_CONTAINMENT_SET_DEFAULT_SEVERITY = 2,
+	MTK_ECC_SMC_CONTAINMENT_GET_DEFAULT_SEVERITY = 3,
+	MTK_ECC_SMC_STORM_SET_THRESHOLD              = 4,
+	MTK_ECC_SMC_STORM_GET_THRESHOLD              = 5,
+	MTK_ECC_SMC_STORM_SET_TIME_WINDOW            = 6,
+	MTK_ECC_SMC_STORM_GET_TIME_WINDOW            = 7,
+	MTK_ECC_SMC_STORM_SET_COOLDOWN               = 8,
+	MTK_ECC_SMC_STORM_GET_COOLDOWN               = 9,
+	MTK_ECC_SMC_STORM_SET_FORCE_REENABLE         = 10,
+	MTK_ECC_SMC_RANGE_ADD                        = 11,
+	MTK_ECC_SMC_RANGE_CLEAR                      = 12,
+	MTK_ECC_SMC_RANGE_GET                        = 13,
+};
+
+int mtk_dram_ecc_smc_containment_set_enabled(bool enabled);
+int mtk_dram_ecc_smc_containment_get_enabled(bool *enabled);
+int mtk_dram_ecc_smc_containment_set_default_severity(bool recoverable);
+int mtk_dram_ecc_smc_containment_get_default_severity(bool *recoverable);
+int mtk_dram_ecc_smc_storm_set_threshold(u32 val);
+int mtk_dram_ecc_smc_storm_get_threshold(u32 *val);
+int mtk_dram_ecc_smc_storm_set_time_window(u32 val);
+int mtk_dram_ecc_smc_storm_get_time_window(u32 *val);
+int mtk_dram_ecc_smc_storm_set_cooldown(u32 val);
+int mtk_dram_ecc_smc_storm_get_cooldown(u32 *val);
+
+/* addr_from/addr_to: [addr_from, addr_to) range, recoverable: false=fatal/true=recoverable. */
+int mtk_dram_ecc_smc_range_add(u64 addr_from, u64 addr_to, bool recoverable);
+int mtk_dram_ecc_smc_range_clear(void);
+/* Returns -ENOENT if index has no entry (also signals end-of-table). */
+int mtk_dram_ecc_smc_range_get(u32 index, u64 *addr_from, u64 *addr_to, bool *recoverable);
+
+#endif /* _MTK_DRAM_ECC_SMC_H_ */
-- 
2.45.2



^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [RFC PATCH 03/11] soc: mediatek: dram-ecc: add tracepoints
  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  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 ` 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
                   ` (8 subsequent siblings)
  11 siblings, 1 reply; 20+ messages in thread
From: santino.wang @ 2026-11-02  9:23 UTC (permalink / raw)
  To: Matthias Brugger, AngeloGioacchino Del Regno
  Cc: linux-mediatek, linux-arm-kernel, linux-kernel, light.chen,
	Fred-WY.Chen, arnab.layek, Santino Wang

From: Santino Wang <santino.wang@mediatek.com>

Add tracepoints for the driver lifecycle, corrected and uncorrectable
errors, and page retirement, so error handling can be followed with
ftrace without relying on the kernel log.

Signed-off-by: Santino Wang <santino.wang@mediatek.com>
---
 drivers/soc/mediatek/dram-ecc/core/Makefile |   5 +-
 drivers/soc/mediatek/dram-ecc/core/main.c   |   3 +
 drivers/soc/mediatek/dram-ecc/core/trace.c  |   6 +
 drivers/soc/mediatek/dram-ecc/core/trace.h  | 119 ++++++++++++++++++++
 4 files changed, 132 insertions(+), 1 deletion(-)
 create mode 100644 drivers/soc/mediatek/dram-ecc/core/trace.c
 create mode 100644 drivers/soc/mediatek/dram-ecc/core/trace.h

diff --git a/drivers/soc/mediatek/dram-ecc/core/Makefile b/drivers/soc/mediatek/dram-ecc/core/Makefile
index 429347bfd057..66ce5d306a45 100644
--- a/drivers/soc/mediatek/dram-ecc/core/Makefile
+++ b/drivers/soc/mediatek/dram-ecc/core/Makefile
@@ -4,4 +4,7 @@ obj-$(CONFIG_MTK_DRAM_ECC) += mtk_dram_ecc.o
 
 mtk_dram_ecc-y := \
 	main.o \
-	smc.o
+	smc.o \
+	trace.o
+
+CFLAGS_trace.o := -I$(src)
diff --git a/drivers/soc/mediatek/dram-ecc/core/main.c b/drivers/soc/mediatek/dram-ecc/core/main.c
index 5000d1f3c7e3..ce6bd39fa303 100644
--- a/drivers/soc/mediatek/dram-ecc/core/main.c
+++ b/drivers/soc/mediatek/dram-ecc/core/main.c
@@ -7,6 +7,7 @@
 #include <linux/device/class.h>
 
 #include "mtk_dram_ecc.h"
+#include "trace.h"
 
 static struct class *mtk_dram_ecc_class;
 static struct device *mtk_dram_ecc_dev;
@@ -33,6 +34,7 @@ static int __init mtk_dram_ecc_init(void)
 		goto err_class;
 	}
 
+	trace_mtk_dram_ecc_lifecycle(0);
 	dev_info(mtk_dram_ecc_dev, "initialized\n");
 	return 0;
 
@@ -45,6 +47,7 @@ static void __exit mtk_dram_ecc_exit(void)
 {
 	device_destroy(mtk_dram_ecc_class, MKDEV(0, 0));
 	class_destroy(mtk_dram_ecc_class);
+	trace_mtk_dram_ecc_lifecycle(1);
 }
 
 module_init(mtk_dram_ecc_init);
diff --git a/drivers/soc/mediatek/dram-ecc/core/trace.c b/drivers/soc/mediatek/dram-ecc/core/trace.c
new file mode 100644
index 000000000000..9a8b20e48069
--- /dev/null
+++ b/drivers/soc/mediatek/dram-ecc/core/trace.c
@@ -0,0 +1,6 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2026 MediaTek Inc.
+ */
+#define CREATE_TRACE_POINTS
+#include "trace.h"
diff --git a/drivers/soc/mediatek/dram-ecc/core/trace.h b/drivers/soc/mediatek/dram-ecc/core/trace.h
new file mode 100644
index 000000000000..d603fc38f0a7
--- /dev/null
+++ b/drivers/soc/mediatek/dram-ecc/core/trace.h
@@ -0,0 +1,119 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2026 MediaTek Inc.
+ */
+#undef TRACE_SYSTEM
+#define TRACE_SYSTEM mtk_dram_ecc
+
+#if !defined(_TRACE_MTK_DRAM_ECC_H) || defined(TRACE_HEADER_MULTI_READ)
+#define _TRACE_MTK_DRAM_ECC_H
+
+#include <linux/tracepoint.h>
+
+/* driver lifecycle - state: 0=init, 1=exit */
+
+TRACE_EVENT(mtk_dram_ecc_lifecycle,
+	TP_PROTO(int state),
+	TP_ARGS(state),
+	TP_STRUCT__entry(
+		__field(int, state)
+	),
+	TP_fast_assign(
+		__entry->state = state;
+	),
+	TP_printk("%s", __entry->state ? "driver removed" : "driver initialized")
+);
+
+/*
+ * ECC events - spa/channel/subchannel/rank/bank/row/column decoded from the
+ * ECCERR_INFO0/1 entry that determined severity (see ghes_notifier.c),
+ * mirroring ATF dram_ecc/mt8992/fiq.c's decode. No agent/master ID: ATF's
+ * GHES vendor section doesn't compute them (see mtk_dram_ecc_event.h).
+ */
+
+TRACE_EVENT(mtk_dram_ecc_ce,
+	TP_PROTO(u64 spa, u8 channel, u8 subch, u16 rank, u16 bank, u32 row,
+		 u16 column),
+	TP_ARGS(spa, channel, subch, rank, bank, row, column),
+	TP_STRUCT__entry(
+		__field(u64, spa)
+		__field(u8, channel)
+		__field(u8, subch)
+		__field(u16, rank)
+		__field(u16, bank)
+		__field(u32, row)
+		__field(u16, column)
+	),
+	TP_fast_assign(
+		__entry->spa     = spa;
+		__entry->channel = channel;
+		__entry->subch   = subch;
+		__entry->rank    = rank;
+		__entry->bank    = bank;
+		__entry->row     = row;
+		__entry->column  = column;
+	),
+	TP_printk("spa=0x%016llx ch=%u subch=%u rank=%u bank=%u row=%u column=%u",
+		  __entry->spa, __entry->channel, __entry->subch,
+		  __entry->rank, __entry->bank, __entry->row,
+		  __entry->column)
+);
+
+TRACE_EVENT(mtk_dram_ecc_uce,
+	TP_PROTO(u64 spa, u8 channel, u8 subch, u16 rank, u16 bank, u32 row,
+		 u16 column),
+	TP_ARGS(spa, channel, subch, rank, bank, row, column),
+	TP_STRUCT__entry(
+		__field(u64, spa)
+		__field(u8, channel)
+		__field(u8, subch)
+		__field(u16, rank)
+		__field(u16, bank)
+		__field(u32, row)
+		__field(u16, column)
+	),
+	TP_fast_assign(
+		__entry->spa     = spa;
+		__entry->channel = channel;
+		__entry->subch   = subch;
+		__entry->rank    = rank;
+		__entry->bank    = bank;
+		__entry->row     = row;
+		__entry->column  = column;
+	),
+	TP_printk("spa=0x%016llx ch=%u subch=%u rank=%u bank=%u row=%u column=%u",
+		  __entry->spa, __entry->channel, __entry->subch,
+		  __entry->rank, __entry->bank, __entry->row,
+		  __entry->column)
+);
+
+/* page retire */
+
+TRACE_EVENT(mtk_dram_ecc_page_retire,
+	TP_PROTO(u64 pfn, u32 count, u32 threshold, bool retired),
+	TP_ARGS(pfn, count, threshold, retired),
+	TP_STRUCT__entry(
+		__field(u64,  pfn)
+		__field(u32,  count)
+		__field(u32,  threshold)
+		__field(bool, retired)
+	),
+	TP_fast_assign(
+		__entry->pfn       = pfn;
+		__entry->count     = count;
+		__entry->threshold = threshold;
+		__entry->retired   = retired;
+	),
+	TP_printk("pfn=0x%llx count=%u threshold=%u retired=%s",
+		  __entry->pfn, __entry->count, __entry->threshold,
+		  __entry->retired ? "yes" : "no")
+);
+
+#endif /* _TRACE_MTK_DRAM_ECC_H */
+
+#undef TRACE_INCLUDE_PATH
+#define TRACE_INCLUDE_PATH .
+#undef TRACE_INCLUDE_FILE
+#define TRACE_INCLUDE_FILE trace
+
+#include <trace/define_trace.h>
-- 
2.45.2



^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [RFC PATCH 04/11] soc: mediatek: dram-ecc: handle GHES vendor records from the firmware
  2026-11-02  9:23 [RFC PATCH 00/11] soc: mediatek: add a DRAM ECC error handling driver santino.wang
                   ` (2 preceding siblings ...)
  2026-11-02  9:23 ` [RFC PATCH 03/11] soc: mediatek: dram-ecc: add tracepoints santino.wang
@ 2026-11-02  9:23 ` santino.wang
  2026-11-02  9:23 ` [RFC PATCH 05/11] soc: mediatek: dram-ecc: add error counters and the address lists santino.wang
                   ` (7 subsequent siblings)
  11 siblings, 0 replies; 20+ messages in thread
From: santino.wang @ 2026-11-02  9:23 UTC (permalink / raw)
  To: Matthias Brugger, AngeloGioacchino Del Regno
  Cc: linux-mediatek, linux-arm-kernel, linux-kernel, light.chen,
	Fred-WY.Chen, arnab.layek, Santino Wang

From: Santino Wang <santino.wang@mediatek.com>

The secure firmware reports an error as a CPER record that carries,
next to the standard memory error section, a MediaTek vendor section
with the raw DRAM controller error registers. Register a GHES vendor
record notifier that decodes the most severe entry of that dump and
then:

 - tallies corrected and uncorrectable errors since boot and keeps
   the first addresses of each kind,
 - for a recoverable uncorrectable error on memory that Linux manages,
   isolates the page with memory_failure() and panics if that fails,
   while memory owned by a device (a carveout) is only reported to
   its owner,
 - passes the decoded event to in-kernel clients through a notifier
   chain, for drivers such as a GPU driver that must react to errors
   in memory they own.

Signed-off-by: Santino Wang <santino.wang@mediatek.com>
---
 drivers/soc/mediatek/dram-ecc/core/Makefile   |   1 +
 .../mediatek/dram-ecc/core/ghes_notifier.c    | 364 ++++++++++++++++++
 .../mediatek/dram-ecc/core/ghes_notifier.h    |  22 ++
 drivers/soc/mediatek/dram-ecc/core/main.c     |  10 +-
 .../dram-ecc/core/mtk_dram_ecc_event.h        |  47 +++
 5 files changed, 443 insertions(+), 1 deletion(-)
 create mode 100644 drivers/soc/mediatek/dram-ecc/core/ghes_notifier.c
 create mode 100644 drivers/soc/mediatek/dram-ecc/core/ghes_notifier.h
 create mode 100644 drivers/soc/mediatek/dram-ecc/core/mtk_dram_ecc_event.h

diff --git a/drivers/soc/mediatek/dram-ecc/core/Makefile b/drivers/soc/mediatek/dram-ecc/core/Makefile
index 66ce5d306a45..304117845420 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 \
+	ghes_notifier.o \
 	trace.o
 
 CFLAGS_trace.o := -I$(src)
diff --git a/drivers/soc/mediatek/dram-ecc/core/ghes_notifier.c b/drivers/soc/mediatek/dram-ecc/core/ghes_notifier.c
new file mode 100644
index 000000000000..4ba65875e427
--- /dev/null
+++ b/drivers/soc/mediatek/dram-ecc/core/ghes_notifier.c
@@ -0,0 +1,364 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2026 MediaTek Inc.
+ */
+/*
+ * GHES vendor-section consumer for DRAM ECC CE/UCE notifications
+ * (delivered via ACPI_HEST_NOTIFY_GSIV, SPI 248 - not SDEI).
+ *
+ * ATF's dram_ecc/mt8992/fiq.c only makes a minimal CE-vs-UCE call itself (to
+ * pick the GHES instance); it hands off the full raw
+ * ECCERR_INFO0~31/INFO32/DB0~DB4 dump for both sub-channels via the
+ * vendor section instead of throwing away everything but one entry. So
+ * this driver makes the same adjustment ATF made: it independently scans
+ * that raw dump for the worst (MBE > SBE) entry and decodes it itself,
+ * using the exact same algorithm as ATF's dram_ecc/mt8992/fiq.c
+ * (get_error_type()/decode_physical_address()) - kernel and ATF must agree on
+ * what "the" error for this notification is.
+ */
+#include <linux/notifier.h>
+#include <linux/atomic.h>
+#include <linux/spinlock.h>
+#include <linux/slab.h>
+#include <linux/mm.h>
+#include <linux/panic.h>
+#include <acpi/ghes.h>
+
+#include "mtk_dram_ecc.h"
+#include "mtk_dram_ecc_event.h"
+#include "ghes_notifier.h"
+#include "trace.h"
+
+static ATOMIC_NOTIFIER_HEAD(mtk_dram_ecc_notifier_chain);
+
+int mtk_dram_ecc_register_notifier(struct notifier_block *nb)
+{
+	return atomic_notifier_chain_register(&mtk_dram_ecc_notifier_chain, nb);
+}
+EXPORT_SYMBOL_GPL(mtk_dram_ecc_register_notifier);
+
+int mtk_dram_ecc_unregister_notifier(struct notifier_block *nb)
+{
+	return atomic_notifier_chain_unregister(&mtk_dram_ecc_notifier_chain, nb);
+}
+EXPORT_SYMBOL_GPL(mtk_dram_ecc_unregister_notifier);
+
+static atomic64_t volatile_ce_count = ATOMIC64_INIT(0);
+static atomic64_t volatile_uce_inline_count = ATOMIC64_INIT(0);
+static atomic64_t volatile_uce_ondie_count = ATOMIC64_INIT(0);
+
+static u64 ce_addrs[MTK_ECC_MAX_BOOT_ADDRS];
+static u64 uce_addrs[MTK_ECC_MAX_BOOT_ADDRS];
+static int ce_addr_count;
+static int uce_addr_count;
+static DEFINE_SPINLOCK(addr_lock);
+
+static const guid_t mtk_vendor_guid =
+	GUID_INIT(0x4E117621, 0x9725, 0x48E4,
+		  0xA3, 0xA0, 0xD2, 0xE5, 0xB1, 0x64, 0x8C, 0x5A);
+
+/*
+ * Raw ECCERR_INFO0~31 + INFO32 + DB0~DB4 dump for one sub-channel - must
+ * match ATF apei/ghes.h's struct mtk_dram_ecc_subch_dump exactly, byte
+ * for byte (ATF is the writer, ACPI GHES entry data is opaque bytes).
+ */
+struct mtk_vendor_subch_dump {
+	u32 info[16][2];
+	u32 info32;
+	u32 db0;
+	u32 db1;
+	u32 db2;
+	u32 db3;
+	u32 db4;
+};
+
+/*
+ * Must match ATF apei/ghes.h's struct mtk_dram_ecc_raw_dump exactly.
+ *
+ * physical_addr is decoded by ATF, which already needs it for the standard
+ * memory-error section. Consuming it here avoids keeping a second copy of the
+ * DRAMC bit-scatter map in sync with ATF's decode_physical_address(). The raw
+ * dump is retained for engineer post-mortem via the CPER hex dump.
+ */
+struct mtk_vendor_section {
+	u64 physical_addr;
+	u32 ch_idx;
+	/*
+	 * Inline ECC sees every UCE, so from_inline_ecc is 1; from_ondie_ecc is
+	 * also 1 when the error originated in the DRAM die.
+	 */
+	u16 from_inline_ecc;
+	u16 from_ondie_ecc;
+	struct mtk_vendor_subch_dump subch[2];
+};
+
+/* ERR_TYPE_DLEP/LECC encoding (info0[31:30]/[29:28]): 0=no err, 1=SBE, 2=MBE, 3=parity SBE */
+#define MTK_ECC_ERR_TYPE_NONE 0U
+#define MTK_ECC_ERR_TYPE_SBE  1U
+#define MTK_ECC_ERR_TYPE_MBE  2U
+
+/* Worse of ERR_TYPE_DLEP/ERR_TYPE_LECC for one entry; 0 if neither reports an error. */
+static u32 entry_err_type(u32 info0)
+{
+	u32 t_dlep = (info0 >> 30) & 0x3U;
+	u32 t_lecc = (info0 >> 28) & 0x3U;
+
+	if (t_dlep == MTK_ECC_ERR_TYPE_MBE || t_lecc == MTK_ECC_ERR_TYPE_MBE)
+		return MTK_ECC_ERR_TYPE_MBE;
+	if (t_dlep != 0U || t_lecc != 0U)
+		return MTK_ECC_ERR_TYPE_SBE;
+	return MTK_ECC_ERR_TYPE_NONE;
+}
+
+/*
+ * Scan both sub-channels' ECCERR_INFO0~31 for the single worst (MBE > SBE)
+ * entry and fill @evt from it. Mirrors ATF dram_ecc/mt8992/fiq.c's
+ * scan_subchannel()/get_error_type() selection order, so the entry picked here
+ * is the same one ATF decoded. Returns MTK_ECC_ERR_TYPE_NONE if every entry in
+ * the dump is empty.
+ *
+ * rank/bank/row/column are plain field extractions from the selected entry.
+ * The physical address is not re-derived: ATF publishes the value it already
+ * decoded for the standard memory-error section in vs->physical_addr, which
+ * keeps the DRAMC bit-scatter map in exactly one place.
+ */
+static u32 decode_worst_entry(const struct mtk_vendor_section *vs,
+			      struct mtk_dram_ecc_event *evt)
+{
+	u32 severity = MTK_ECC_ERR_TYPE_NONE;
+	u32 sel_info0 = 0, sel_info1 = 0;
+	int sel_subch = 0;
+	int subch, e;
+
+	for (subch = 0; subch < 2; subch++) {
+		const struct mtk_vendor_subch_dump *d = &vs->subch[subch];
+
+		for (e = 0; e < 16; e++) {
+			u32 info0 = d->info[e][0];
+			u32 info1 = d->info[e][1];
+			u32 type = entry_err_type(info0);
+
+			if (type > severity) {
+				severity = type;
+				sel_info0 = info0;
+				sel_info1 = info1;
+				sel_subch = subch;
+			}
+		}
+	}
+
+	if (severity == MTK_ECC_ERR_TYPE_NONE)
+		return severity;
+
+	evt->spa    = vs->physical_addr;
+	evt->rank   = (u16)(sel_info0 & 0x3U);
+	evt->bank   = (u16)((sel_info0 >> 2) & 0x1fU);
+	evt->row    = (sel_info1 >> 11) & 0x3ffffU;
+	evt->column = (u16)(sel_info1 & 0x7ffU);
+	evt->channel = (u8)vs->ch_idx;
+	evt->subchannel = (u8)sel_subch;
+
+	return severity;
+}
+
+/*
+ * Is this PFN memory Linux itself manages, i.e. memory we are entitled to
+ * poison?
+ *
+ * page_is_ram() on its own is not an ownership test: a device carveout
+ * reserved out of a System RAM resource still satisfies it. Such a carveout
+ * keeps PageReserved set, so additionally require a struct page that is not
+ * reserved. Everything else belongs to its device owner - a recoverable UCE
+ * there is only reported to the owner, never isolated here.
+ */
+static bool pfn_is_linux_managed(unsigned long pfn)
+{
+	if (!pfn_valid(pfn) || !page_is_ram(pfn))
+		return false;
+
+	return !PageReserved(pfn_to_page(pfn));
+}
+
+static int mtk_ghes_vendor_cb(struct notifier_block *nb, unsigned long severity,
+			      void *data)
+{
+	struct acpi_hest_generic_data *gdata = data;
+	const struct mtk_vendor_section *vs;
+	struct mtk_dram_ecc_event evt = { 0 };
+	guid_t *sec_type;
+	int ret;
+
+	sec_type = (guid_t *)gdata->section_type;
+	if (!guid_equal(sec_type, &mtk_vendor_guid)) {
+		/* Not our vendor record - log the GUID (rate-limited) so a
+		 * GUID mismatch between ATF and this driver is not a silent
+		 * "nothing happened".
+		 */
+		pr_warn_ratelimited("mtk-dram-ecc: GHES vendor record with non-MTK section GUID %pUl, ignoring\n",
+				    sec_type);
+		return NOTIFY_DONE;
+	}
+
+	if (severity == GHES_SEV_CORRECTED) {
+		evt.severity = MTK_DRAM_ECC_NOTIFY_CE;
+	} else if (severity == GHES_SEV_RECOVERABLE) {
+		evt.severity = MTK_DRAM_ECC_NOTIFY_UCE;
+	} else {
+		pr_warn_ratelimited("mtk-dram-ecc: unsupported GHES vendor-record severity %lu, ignoring\n",
+				    severity);
+		return NOTIFY_DONE;
+	}
+
+	/*
+	 * Generic GHES only guarantees the declared section fits inside the
+	 * copied error-status block; it knows nothing about this MTK-specific
+	 * minimum. Reject a truncated record before the decoder reads past the
+	 * section.
+	 */
+	if (acpi_hest_get_error_length(gdata) < sizeof(struct mtk_vendor_section)) {
+		pr_warn_ratelimited("mtk-dram-ecc: MTK vendor section too short: %d < %zu, ignoring\n",
+				    acpi_hest_get_error_length(gdata),
+				    sizeof(struct mtk_vendor_section));
+		return NOTIFY_DONE;
+	}
+
+	vs = acpi_hest_get_payload(gdata);
+
+	if (decode_worst_entry(vs, &evt) == MTK_ECC_ERR_TYPE_NONE) {
+		pr_warn("mtk-dram-ecc: GHES vendor section CH%u has no non-zero ECCERR_INFO entry\n",
+			vs->ch_idx);
+		return NOTIFY_OK;
+	}
+
+	/* Announce the decoded event before taking any action, independent of
+	 * tracepoints / the optional notify_sink modules, so the path is
+	 * observable in dmesg even if a downstream step fails.
+	 */
+	pr_info("mtk-dram-ecc: %s CH%u subch%u spa=0x%llx rank=%u bank=%u row=%u col=%u\n",
+		(evt.severity == MTK_DRAM_ECC_NOTIFY_CE) ? "CE" : "UCE",
+		vs->ch_idx, evt.subchannel, evt.spa, evt.rank, evt.bank, evt.row,
+		evt.column);
+
+	if (evt.severity == MTK_DRAM_ECC_NOTIFY_CE) {
+		atomic64_inc(&volatile_ce_count);
+		spin_lock(&addr_lock);
+		if (ce_addr_count < MTK_ECC_MAX_BOOT_ADDRS)
+			ce_addrs[ce_addr_count++] = evt.spa;
+		spin_unlock(&addr_lock);
+		trace_mtk_dram_ecc_ce(evt.spa, evt.channel, evt.subchannel, evt.rank,
+				      evt.bank, evt.row, evt.column);
+		ret = atomic_notifier_call_chain(&mtk_dram_ecc_notifier_chain,
+						 MTK_DRAM_ECC_NOTIFY_CE, &evt);
+		if (ret == NOTIFY_BAD)
+			pr_warn_ratelimited("mtk-dram-ecc: kernel client rejected CE event for spa=0x%llx\n",
+					    evt.spa);
+	} else {
+		unsigned long pfn = evt.spa >> PAGE_SHIFT;
+
+		/* Inline ECC detects every UCE, including an on-die one. */
+		atomic64_inc(&volatile_uce_inline_count);
+		if (vs->from_ondie_ecc)
+			atomic64_inc(&volatile_uce_ondie_count);
+		spin_lock(&addr_lock);
+		if (uce_addr_count < MTK_ECC_MAX_BOOT_ADDRS)
+			uce_addrs[uce_addr_count++] = evt.spa;
+		spin_unlock(&addr_lock);
+		trace_mtk_dram_ecc_uce(evt.spa, evt.channel, evt.subchannel, evt.rank,
+				       evt.bank, evt.row, evt.column);
+		/*
+		 * memory_failure() owns Linux-managed memory only. A device
+		 * carveout is recovered by the subscriber that owns the SPA
+		 * range, so this path only notifies for it. Generic GHES
+		 * cannot pre-empt that decision either: with
+		 * CONFIG_ACPI_APEI_MEMORY_FAILURE=y it does call
+		 * memory_failure() off the standard section, but
+		 * pfn_to_online_page() returns NULL for a carveout, so that
+		 * call fails with -ENXIO without poisoning anything. On
+		 * Linux-managed memory the two paths race benignly; see the
+		 * -EHWPOISON handling in mtk_dram_ecc_record_retired_page().
+		 */
+		if (pfn_is_linux_managed(pfn)) {
+			ret = memory_failure(pfn, 0);
+			pr_info("mtk-dram-ecc: memory_failure(pfn=0x%lx) returned %d\n",
+				pfn, ret);
+			if (ret != 0 && ret != -EHWPOISON) {
+				pr_emerg("mtk-dram-ecc: recoverable UCE isolation failed: pfn=0x%lx ret=%d\n",
+					 pfn, ret);
+				panic("DRAM ECC recoverable UCE isolation failed");
+			}
+		} else {
+			pr_info("mtk-dram-ecc: notify-only UCE spa=0x%llx (not Linux-managed memory)\n",
+				evt.spa);
+		}
+		ret = atomic_notifier_call_chain(&mtk_dram_ecc_notifier_chain,
+						 MTK_DRAM_ECC_NOTIFY_UCE, &evt);
+		if (ret == NOTIFY_BAD)
+			pr_warn_ratelimited("mtk-dram-ecc: kernel client rejected UCE event for spa=0x%llx\n",
+					    evt.spa);
+	}
+
+	return NOTIFY_OK;
+}
+
+static struct notifier_block mtk_ghes_vendor_nb = {
+	.notifier_call = mtk_ghes_vendor_cb,
+};
+
+u64 mtk_dram_ecc_get_volatile_ce(void)
+{
+	return atomic64_read(&volatile_ce_count);
+}
+EXPORT_SYMBOL_GPL(mtk_dram_ecc_get_volatile_ce);
+
+u64 mtk_dram_ecc_get_volatile_uce_inline(void)
+{
+	return atomic64_read(&volatile_uce_inline_count);
+}
+EXPORT_SYMBOL_GPL(mtk_dram_ecc_get_volatile_uce_inline);
+
+u64 mtk_dram_ecc_get_volatile_uce_ondie(void)
+{
+	return atomic64_read(&volatile_uce_ondie_count);
+}
+EXPORT_SYMBOL_GPL(mtk_dram_ecc_get_volatile_uce_ondie);
+
+/* Total UCE count: every UCE is seen by inline ECC (on-die is a subset). */
+u64 mtk_dram_ecc_get_volatile_uce(void)
+{
+	return mtk_dram_ecc_get_volatile_uce_inline();
+}
+EXPORT_SYMBOL_GPL(mtk_dram_ecc_get_volatile_uce);
+
+int mtk_dram_ecc_get_ce_addrs(u64 *buf, int max)
+{
+	int count;
+
+	spin_lock(&addr_lock);
+	count = min(ce_addr_count, max);
+	memcpy(buf, ce_addrs, count * sizeof(u64));
+	spin_unlock(&addr_lock);
+	return count;
+}
+EXPORT_SYMBOL_GPL(mtk_dram_ecc_get_ce_addrs);
+
+int mtk_dram_ecc_get_uce_addrs(u64 *buf, int max)
+{
+	int count;
+
+	spin_lock(&addr_lock);
+	count = min(uce_addr_count, max);
+	memcpy(buf, uce_addrs, count * sizeof(u64));
+	spin_unlock(&addr_lock);
+	return count;
+}
+EXPORT_SYMBOL_GPL(mtk_dram_ecc_get_uce_addrs);
+
+int mtk_dram_ecc_ghes_init(void)
+{
+	return ghes_register_vendor_record_notifier(&mtk_ghes_vendor_nb);
+}
+
+void mtk_dram_ecc_ghes_exit(void)
+{
+	ghes_unregister_vendor_record_notifier(&mtk_ghes_vendor_nb);
+}
diff --git a/drivers/soc/mediatek/dram-ecc/core/ghes_notifier.h b/drivers/soc/mediatek/dram-ecc/core/ghes_notifier.h
new file mode 100644
index 000000000000..649e2d5fe928
--- /dev/null
+++ b/drivers/soc/mediatek/dram-ecc/core/ghes_notifier.h
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2026 MediaTek Inc.
+ */
+#ifndef _MTK_DRAM_ECC_GHES_NOTIFIER_H_
+#define _MTK_DRAM_ECC_GHES_NOTIFIER_H_
+
+#include <linux/types.h>
+
+#define MTK_ECC_MAX_BOOT_ADDRS 256
+
+int mtk_dram_ecc_ghes_init(void);
+void mtk_dram_ecc_ghes_exit(void);
+
+u64 mtk_dram_ecc_get_volatile_ce(void);
+u64 mtk_dram_ecc_get_volatile_uce_inline(void);
+u64 mtk_dram_ecc_get_volatile_uce_ondie(void);
+u64 mtk_dram_ecc_get_volatile_uce(void);
+int mtk_dram_ecc_get_ce_addrs(u64 *buf, int max);
+int mtk_dram_ecc_get_uce_addrs(u64 *buf, int max);
+
+#endif /* _MTK_DRAM_ECC_GHES_NOTIFIER_H_ */
diff --git a/drivers/soc/mediatek/dram-ecc/core/main.c b/drivers/soc/mediatek/dram-ecc/core/main.c
index ce6bd39fa303..3232046394e3 100644
--- a/drivers/soc/mediatek/dram-ecc/core/main.c
+++ b/drivers/soc/mediatek/dram-ecc/core/main.c
@@ -7,6 +7,7 @@
 #include <linux/device/class.h>
 
 #include "mtk_dram_ecc.h"
+#include "ghes_notifier.h"
 #include "trace.h"
 
 static struct class *mtk_dram_ecc_class;
@@ -34,10 +35,16 @@ static int __init mtk_dram_ecc_init(void)
 		goto err_class;
 	}
 
+	ret = mtk_dram_ecc_ghes_init();
+	if (ret)
+		goto err_dev;
+
 	trace_mtk_dram_ecc_lifecycle(0);
-	dev_info(mtk_dram_ecc_dev, "initialized\n");
+	dev_info(mtk_dram_ecc_dev, "initialized (GHES vendor notifier registered)\n");
 	return 0;
 
+err_dev:
+	device_destroy(mtk_dram_ecc_class, MKDEV(0, 0));
 err_class:
 	class_destroy(mtk_dram_ecc_class);
 	return ret;
@@ -45,6 +52,7 @@ static int __init mtk_dram_ecc_init(void)
 
 static void __exit mtk_dram_ecc_exit(void)
 {
+	mtk_dram_ecc_ghes_exit();
 	device_destroy(mtk_dram_ecc_class, MKDEV(0, 0));
 	class_destroy(mtk_dram_ecc_class);
 	trace_mtk_dram_ecc_lifecycle(1);
diff --git a/drivers/soc/mediatek/dram-ecc/core/mtk_dram_ecc_event.h b/drivers/soc/mediatek/dram-ecc/core/mtk_dram_ecc_event.h
new file mode 100644
index 000000000000..ae7bd7c3abfa
--- /dev/null
+++ b/drivers/soc/mediatek/dram-ecc/core/mtk_dram_ecc_event.h
@@ -0,0 +1,47 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2026 MediaTek Inc.
+ */
+#ifndef _MTK_DRAM_ECC_EVENT_H_
+#define _MTK_DRAM_ECC_EVENT_H_
+
+#include <linux/types.h>
+#include <linux/notifier.h>
+
+/**
+ * struct mtk_dram_ecc_event - payload passed to notifier chain subscribers
+ * @spa:      System Physical Address of the error, decoded from the
+ *            ECCERR_INFO0/1 entry that determined severity (see
+ *            ghes_notifier.c) - mirrors ATF dram_ecc/mt8992/fiq.c's decode.
+ * @rank:     DRAMC rank address (RANK_ADDR, info0[1:0])
+ * @bank:     DRAMC bank address (BANK_ADDR, info0[6:2])
+ * @row:      DRAMC row address (ROW_ADDR, info1[28:11])
+ * @column:   DRAMC column address (COL_ADDR, info1[10:0])
+ * @severity: MTK_DRAM_ECC_NOTIFY_CE or MTK_DRAM_ECC_NOTIFY_UCE
+ * @channel:  DRAM channel index reported by the firmware for this record
+ * @subchannel: sub-channel (0 or 1) of the ECCERR_INFO0/1 entry
+ *
+ * No agent/master ID fields: ATF's GHES vendor section no longer computes
+ * them (see ATF apei/ghes.c) - MASTER_ID/RID are still in the raw
+ * ECCERR_INFO0/1 dump the vendor section carries, just not surfaced here
+ * yet.
+ */
+struct mtk_dram_ecc_event {
+	u64 spa;
+	u16 rank;
+	u16 bank;
+	u32 row;
+	u16 column;
+	u8  severity;
+	u8  channel;
+	u8  subchannel;
+};
+
+/* notifier_call val argument */
+#define MTK_DRAM_ECC_NOTIFY_CE  0
+#define MTK_DRAM_ECC_NOTIFY_UCE 1
+
+int mtk_dram_ecc_register_notifier(struct notifier_block *nb);
+int mtk_dram_ecc_unregister_notifier(struct notifier_block *nb);
+
+#endif /* _MTK_DRAM_ECC_EVENT_H_ */
-- 
2.45.2



^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [RFC PATCH 05/11] soc: mediatek: dram-ecc: add error counters and the address lists
  2026-11-02  9:23 [RFC PATCH 00/11] soc: mediatek: add a DRAM ECC error handling driver santino.wang
                   ` (3 preceding siblings ...)
  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 ` 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
                   ` (6 subsequent siblings)
  11 siblings, 1 reply; 20+ messages in thread
From: santino.wang @ 2026-11-02  9:23 UTC (permalink / raw)
  To: Matthias Brugger, AngeloGioacchino Del Regno
  Cc: linux-mediatek, linux-arm-kernel, linux-kernel, light.chen,
	Fred-WY.Chen, arnab.layek, Santino Wang

From: Santino Wang <santino.wang@mediatek.com>

Export the volatile error counters, the pages retired because of
corrected and uncorrectable errors, and the recorded error addresses
through sysfs under the driver's class device.

A page is counted as retired once per boot. A page that generic GHES
already poisoned through memory_failure() before the vendor record
notifier runs (-EHWPOISON) counts as retired as well.

Signed-off-by: Santino Wang <santino.wang@mediatek.com>
---
 drivers/soc/mediatek/dram-ecc/core/Makefile   |   4 +-
 .../soc/mediatek/dram-ecc/core/addresses.c    |  88 +++++++++
 .../soc/mediatek/dram-ecc/core/addresses.h    |  13 ++
 drivers/soc/mediatek/dram-ecc/core/counters.c | 177 ++++++++++++++++++
 drivers/soc/mediatek/dram-ecc/core/counters.h |  25 +++
 .../mediatek/dram-ecc/core/ghes_notifier.c    |   4 +
 drivers/soc/mediatek/dram-ecc/core/main.c     |  28 ++-
 7 files changed, 337 insertions(+), 2 deletions(-)
 create mode 100644 drivers/soc/mediatek/dram-ecc/core/addresses.c
 create mode 100644 drivers/soc/mediatek/dram-ecc/core/addresses.h
 create mode 100644 drivers/soc/mediatek/dram-ecc/core/counters.c
 create mode 100644 drivers/soc/mediatek/dram-ecc/core/counters.h

diff --git a/drivers/soc/mediatek/dram-ecc/core/Makefile b/drivers/soc/mediatek/dram-ecc/core/Makefile
index 304117845420..96d37c6a3b9c 100644
--- a/drivers/soc/mediatek/dram-ecc/core/Makefile
+++ b/drivers/soc/mediatek/dram-ecc/core/Makefile
@@ -6,6 +6,8 @@ mtk_dram_ecc-y := \
 	main.o \
 	smc.o \
 	ghes_notifier.o \
-	trace.o
+	trace.o \
+	counters.o \
+	addresses.o
 
 CFLAGS_trace.o := -I$(src)
diff --git a/drivers/soc/mediatek/dram-ecc/core/addresses.c b/drivers/soc/mediatek/dram-ecc/core/addresses.c
new file mode 100644
index 000000000000..aeb61ea5deef
--- /dev/null
+++ b/drivers/soc/mediatek/dram-ecc/core/addresses.c
@@ -0,0 +1,88 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2026 MediaTek Inc.
+ */
+#include <linux/kobject.h>
+#include <linux/sysfs.h>
+#include <linux/slab.h>
+
+#include "mtk_dram_ecc.h"
+#include "ghes_notifier.h"
+#include "addresses.h"
+
+static struct kobject *addresses_kobj;
+
+#define MTK_ADDR_BUF_SIZE (MTK_ECC_MAX_BOOT_ADDRS * sizeof(u64))
+
+static ssize_t current_boot_ce_show(struct kobject *kobj,
+				    struct kobj_attribute *attr, char *buf)
+{
+	u64 *addrs;
+	int count, i;
+	ssize_t len = 0;
+
+	addrs = kmalloc(MTK_ADDR_BUF_SIZE, GFP_KERNEL);
+	if (!addrs)
+		return -ENOMEM;
+
+	count = mtk_dram_ecc_get_ce_addrs(addrs, MTK_ECC_MAX_BOOT_ADDRS);
+	for (i = 0; i < count; i++)
+		len += sysfs_emit_at(buf, len, "0x%llx\n", addrs[i]);
+
+	kfree(addrs);
+	return len;
+}
+
+static ssize_t current_boot_uce_show(struct kobject *kobj,
+				     struct kobj_attribute *attr, char *buf)
+{
+	u64 *addrs;
+	int count, i;
+	ssize_t len = 0;
+
+	addrs = kmalloc(MTK_ADDR_BUF_SIZE, GFP_KERNEL);
+	if (!addrs)
+		return -ENOMEM;
+
+	count = mtk_dram_ecc_get_uce_addrs(addrs, MTK_ECC_MAX_BOOT_ADDRS);
+	for (i = 0; i < count; i++)
+		len += sysfs_emit_at(buf, len, "0x%llx\n", addrs[i]);
+
+	kfree(addrs);
+	return len;
+}
+
+static struct kobj_attribute ce_attr = __ATTR_RO(current_boot_ce);
+static struct kobj_attribute uce_attr = __ATTR_RO(current_boot_uce);
+
+static struct attribute *attrs[] = {
+	&ce_attr.attr,
+	&uce_attr.attr,
+	NULL,
+};
+
+static const struct attribute_group group = {
+	.attrs = attrs,
+};
+
+int mtk_dram_ecc_addresses_init(struct kobject *parent)
+{
+	int ret;
+
+	addresses_kobj = kobject_create_and_add("addresses", parent);
+	if (!addresses_kobj)
+		return -ENOMEM;
+
+	ret = sysfs_create_group(addresses_kobj, &group);
+	if (ret) {
+		kobject_put(addresses_kobj);
+		return ret;
+	}
+	return 0;
+}
+
+void mtk_dram_ecc_addresses_exit(void)
+{
+	sysfs_remove_group(addresses_kobj, &group);
+	kobject_put(addresses_kobj);
+}
diff --git a/drivers/soc/mediatek/dram-ecc/core/addresses.h b/drivers/soc/mediatek/dram-ecc/core/addresses.h
new file mode 100644
index 000000000000..b55a067f9717
--- /dev/null
+++ b/drivers/soc/mediatek/dram-ecc/core/addresses.h
@@ -0,0 +1,13 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2026 MediaTek Inc.
+ */
+#ifndef _MTK_DRAM_ECC_ADDRESSES_H_
+#define _MTK_DRAM_ECC_ADDRESSES_H_
+
+#include <linux/kobject.h>
+
+int mtk_dram_ecc_addresses_init(struct kobject *parent);
+void mtk_dram_ecc_addresses_exit(void);
+
+#endif /* _MTK_DRAM_ECC_ADDRESSES_H_ */
diff --git a/drivers/soc/mediatek/dram-ecc/core/counters.c b/drivers/soc/mediatek/dram-ecc/core/counters.c
new file mode 100644
index 000000000000..88d7961975db
--- /dev/null
+++ b/drivers/soc/mediatek/dram-ecc/core/counters.c
@@ -0,0 +1,177 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2026 MediaTek Inc.
+ */
+/*
+ * Volatile (reset every boot) CE/UCE counters come from ghes_notifier.c's
+ * in-memory tally; retired pages are tracked per PFN so each page is
+ * counted once.
+ */
+#include <linux/kobject.h>
+#include <linux/sysfs.h>
+#include <linux/export.h>
+#include <linux/atomic.h>
+#include <linux/xarray.h>
+
+#include "mtk_dram_ecc.h"
+#include "ghes_notifier.h"
+#include "counters.h"
+
+static struct kobject *counters_kobj;
+static struct kobject *retired_kobj;
+static atomic64_t retired_by_ce = ATOMIC64_INIT(0);
+static atomic64_t retired_by_uce = ATOMIC64_INIT(0);
+static DEFINE_XARRAY(retired_pfns);
+
+void mtk_dram_ecc_record_retired_page(unsigned long pfn,
+				      enum mtk_dram_ecc_retire_reason reason,
+				      int memory_failure_ret)
+{
+	int ret;
+
+	/*
+	 * With CONFIG_ACPI_APEI_MEMORY_FAILURE=y, generic GHES calls
+	 * memory_failure() off the standard CPER section and may poison a
+	 * recoverable-UCE page before the deferred MTK vendor callback runs.
+	 * Treating -EHWPOISON as already isolated is what makes that race
+	 * benign, so the page is still counted exactly once. Count each PFN
+	 * only once during the current boot.
+	 */
+	if (memory_failure_ret != 0 && memory_failure_ret != -EHWPOISON)
+		return;
+
+	ret = xa_insert(&retired_pfns, pfn, xa_mk_value(reason), GFP_KERNEL);
+	if (ret == -EBUSY)
+		return;
+	if (ret) {
+		pr_warn("mtk-dram-ecc: failed to track retired PFN 0x%lx: %d\n",
+			pfn, ret);
+		return;
+	}
+
+	if (reason == MTK_DRAM_ECC_RETIRE_CE)
+		atomic64_inc(&retired_by_ce);
+	else
+		atomic64_inc(&retired_by_uce);
+}
+
+u64 mtk_dram_ecc_get_retired_by_ce(void)
+{
+	return atomic64_read(&retired_by_ce);
+}
+EXPORT_SYMBOL_GPL(mtk_dram_ecc_get_retired_by_ce);
+
+u64 mtk_dram_ecc_get_retired_by_uce(void)
+{
+	return atomic64_read(&retired_by_uce);
+}
+EXPORT_SYMBOL_GPL(mtk_dram_ecc_get_retired_by_uce);
+
+static ssize_t volatile_ce_show(struct kobject *kobj,
+				struct kobj_attribute *attr, char *buf)
+{
+	return sysfs_emit(buf, "%llu\n", mtk_dram_ecc_get_volatile_ce());
+}
+
+static ssize_t volatile_uce_show(struct kobject *kobj,
+				 struct kobj_attribute *attr, char *buf)
+{
+	return sysfs_emit(buf, "%llu\n", mtk_dram_ecc_get_volatile_uce());
+}
+
+static ssize_t volatile_uce_inline_show(struct kobject *kobj,
+					struct kobj_attribute *attr, char *buf)
+{
+	return sysfs_emit(buf, "%llu\n", mtk_dram_ecc_get_volatile_uce_inline());
+}
+
+static ssize_t volatile_uce_ondie_show(struct kobject *kobj,
+				       struct kobj_attribute *attr, char *buf)
+{
+	return sysfs_emit(buf, "%llu\n", mtk_dram_ecc_get_volatile_uce_ondie());
+}
+
+static struct kobj_attribute volatile_ce_attr = __ATTR_RO(volatile_ce);
+static struct kobj_attribute volatile_uce_attr = __ATTR_RO(volatile_uce);
+static struct kobj_attribute volatile_uce_inline_attr = __ATTR_RO(volatile_uce_inline);
+static struct kobj_attribute volatile_uce_ondie_attr = __ATTR_RO(volatile_uce_ondie);
+
+static struct attribute *attrs[] = {
+	&volatile_ce_attr.attr,
+	&volatile_uce_attr.attr,
+	&volatile_uce_inline_attr.attr,
+	&volatile_uce_ondie_attr.attr,
+	NULL,
+};
+
+static const struct attribute_group group = {
+	.attrs = attrs,
+};
+
+static ssize_t by_ce_show(struct kobject *kobj,
+			  struct kobj_attribute *attr, char *buf)
+{
+	return sysfs_emit(buf, "%llu\n", mtk_dram_ecc_get_retired_by_ce());
+}
+
+static ssize_t by_uce_show(struct kobject *kobj,
+			   struct kobj_attribute *attr, char *buf)
+{
+	return sysfs_emit(buf, "%llu\n", mtk_dram_ecc_get_retired_by_uce());
+}
+
+static struct kobj_attribute retired_by_ce_attr = __ATTR_RO(by_ce);
+static struct kobj_attribute retired_by_uce_attr = __ATTR_RO(by_uce);
+
+static struct attribute *retired_attrs[] = {
+	&retired_by_ce_attr.attr,
+	&retired_by_uce_attr.attr,
+	NULL,
+};
+
+static const struct attribute_group retired_group = {
+	.attrs = retired_attrs,
+};
+
+int mtk_dram_ecc_counters_init(struct kobject *parent)
+{
+	int ret;
+
+	counters_kobj = kobject_create_and_add("counters", parent);
+	if (!counters_kobj)
+		return -ENOMEM;
+
+	ret = sysfs_create_group(counters_kobj, &group);
+	if (ret) {
+		kobject_put(counters_kobj);
+		return ret;
+	}
+
+	retired_kobj = kobject_create_and_add("retired", parent);
+	if (!retired_kobj) {
+		ret = -ENOMEM;
+		goto err_counters;
+	}
+
+	ret = sysfs_create_group(retired_kobj, &retired_group);
+	if (ret)
+		goto err_retired;
+
+	return 0;
+
+err_retired:
+	kobject_put(retired_kobj);
+err_counters:
+	sysfs_remove_group(counters_kobj, &group);
+	kobject_put(counters_kobj);
+	return ret;
+}
+
+void mtk_dram_ecc_counters_exit(void)
+{
+	sysfs_remove_group(retired_kobj, &retired_group);
+	kobject_put(retired_kobj);
+	sysfs_remove_group(counters_kobj, &group);
+	kobject_put(counters_kobj);
+	xa_destroy(&retired_pfns);
+}
diff --git a/drivers/soc/mediatek/dram-ecc/core/counters.h b/drivers/soc/mediatek/dram-ecc/core/counters.h
new file mode 100644
index 000000000000..fd718dc45ea6
--- /dev/null
+++ b/drivers/soc/mediatek/dram-ecc/core/counters.h
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2026 MediaTek Inc.
+ */
+#ifndef _MTK_DRAM_ECC_COUNTERS_H_
+#define _MTK_DRAM_ECC_COUNTERS_H_
+
+#include <linux/kobject.h>
+#include <linux/types.h>
+
+enum mtk_dram_ecc_retire_reason {
+	MTK_DRAM_ECC_RETIRE_CE = 1,
+	MTK_DRAM_ECC_RETIRE_UCE,
+};
+
+int mtk_dram_ecc_counters_init(struct kobject *parent);
+void mtk_dram_ecc_counters_exit(void);
+
+void mtk_dram_ecc_record_retired_page(unsigned long pfn,
+				      enum mtk_dram_ecc_retire_reason reason,
+				      int memory_failure_ret);
+u64 mtk_dram_ecc_get_retired_by_ce(void);
+u64 mtk_dram_ecc_get_retired_by_uce(void);
+
+#endif /* _MTK_DRAM_ECC_COUNTERS_H_ */
diff --git a/drivers/soc/mediatek/dram-ecc/core/ghes_notifier.c b/drivers/soc/mediatek/dram-ecc/core/ghes_notifier.c
index 4ba65875e427..c1d885f0bf15 100644
--- a/drivers/soc/mediatek/dram-ecc/core/ghes_notifier.c
+++ b/drivers/soc/mediatek/dram-ecc/core/ghes_notifier.c
@@ -27,6 +27,7 @@
 #include "mtk_dram_ecc.h"
 #include "mtk_dram_ecc_event.h"
 #include "ghes_notifier.h"
+#include "counters.h"
 #include "trace.h"
 
 static ATOMIC_NOTIFIER_HEAD(mtk_dram_ecc_notifier_chain);
@@ -286,6 +287,9 @@ static int mtk_ghes_vendor_cb(struct notifier_block *nb, unsigned long severity,
 					 pfn, ret);
 				panic("DRAM ECC recoverable UCE isolation failed");
 			}
+			mtk_dram_ecc_record_retired_page(pfn,
+							 MTK_DRAM_ECC_RETIRE_UCE,
+							 ret);
 		} else {
 			pr_info("mtk-dram-ecc: notify-only UCE spa=0x%llx (not Linux-managed memory)\n",
 				evt.spa);
diff --git a/drivers/soc/mediatek/dram-ecc/core/main.c b/drivers/soc/mediatek/dram-ecc/core/main.c
index 3232046394e3..b859af25bcc0 100644
--- a/drivers/soc/mediatek/dram-ecc/core/main.c
+++ b/drivers/soc/mediatek/dram-ecc/core/main.c
@@ -8,6 +8,8 @@
 
 #include "mtk_dram_ecc.h"
 #include "ghes_notifier.h"
+#include "counters.h"
+#include "addresses.h"
 #include "trace.h"
 
 static struct class *mtk_dram_ecc_class;
@@ -35,14 +37,31 @@ static int __init mtk_dram_ecc_init(void)
 		goto err_class;
 	}
 
-	ret = mtk_dram_ecc_ghes_init();
+	ret = mtk_dram_ecc_counters_init(&mtk_dram_ecc_dev->kobj);
 	if (ret)
 		goto err_dev;
 
+	ret = mtk_dram_ecc_addresses_init(&mtk_dram_ecc_dev->kobj);
+	if (ret)
+		goto err_counters;
+
+	/*
+	 * Register the GHES vendor notifier last: the callback uses the policy,
+	 * counter and address state above, so a record delivered before they
+	 * are ready would run leaky_bucket_hit() against uninitialised state.
+	 */
+	ret = mtk_dram_ecc_ghes_init();
+	if (ret)
+		goto err_addresses;
+
 	trace_mtk_dram_ecc_lifecycle(0);
 	dev_info(mtk_dram_ecc_dev, "initialized (GHES vendor notifier registered)\n");
 	return 0;
 
+err_addresses:
+	mtk_dram_ecc_addresses_exit();
+err_counters:
+	mtk_dram_ecc_counters_exit();
 err_dev:
 	device_destroy(mtk_dram_ecc_class, MKDEV(0, 0));
 err_class:
@@ -52,7 +71,14 @@ static int __init mtk_dram_ecc_init(void)
 
 static void __exit mtk_dram_ecc_exit(void)
 {
+	/*
+	 * Unregister the GHES vendor notifier first, so the blocking-notifier
+	 * unregistration synchronises against an in-flight callback before the
+	 * state it touches is torn down.
+	 */
 	mtk_dram_ecc_ghes_exit();
+	mtk_dram_ecc_addresses_exit();
+	mtk_dram_ecc_counters_exit();
 	device_destroy(mtk_dram_ecc_class, MKDEV(0, 0));
 	class_destroy(mtk_dram_ecc_class);
 	trace_mtk_dram_ecc_lifecycle(1);
-- 
2.45.2



^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [RFC PATCH 06/11] soc: mediatek: dram-ecc: keep error counters across boots
  2026-11-02  9:23 [RFC PATCH 00/11] soc: mediatek: add a DRAM ECC error handling driver santino.wang
                   ` (4 preceding siblings ...)
  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  9:23 ` santino.wang
  2026-11-02  9:23 ` [RFC PATCH 07/11] soc: mediatek: dram-ecc: add containment policy and storm protection santino.wang
                   ` (5 subsequent siblings)
  11 siblings, 0 replies; 20+ messages in thread
From: santino.wang @ 2026-11-02  9:23 UTC (permalink / raw)
  To: Matthias Brugger, AngeloGioacchino Del Regno
  Cc: linux-mediatek, linux-arm-kernel, linux-kernel, light.chen,
	Fred-WY.Chen, arnab.layek, Santino Wang

From: Santino Wang <santino.wang@mediatek.com>

Keep aggregate corrected and uncorrectable error counters in EFI
variables, so they survive a reboot. The firmware creates the
variables; the driver increments them.

EFI SetVariable() can take tens of milliseconds, so the increments are
accumulated and written from a work item instead of the GHES notifier
context.

Signed-off-by: Santino Wang <santino.wang@mediatek.com>
---
 drivers/soc/mediatek/dram-ecc/core/counters.c | 145 +++++++++++++++++-
 drivers/soc/mediatek/dram-ecc/core/counters.h |   3 +
 .../mediatek/dram-ecc/core/ghes_notifier.c    |   6 +-
 3 files changed, 150 insertions(+), 4 deletions(-)

diff --git a/drivers/soc/mediatek/dram-ecc/core/counters.c b/drivers/soc/mediatek/dram-ecc/core/counters.c
index 88d7961975db..8e3fd83cf60a 100644
--- a/drivers/soc/mediatek/dram-ecc/core/counters.c
+++ b/drivers/soc/mediatek/dram-ecc/core/counters.c
@@ -3,13 +3,20 @@
  * Copyright (c) 2026 MediaTek Inc.
  */
 /*
- * Volatile (reset every boot) CE/UCE counters come from ghes_notifier.c's
- * in-memory tally; retired pages are tracked per PFN so each page is
- * counted once.
+ * Aggregate CE/UCE counters. "volatile" counters (reset every boot) come
+ * from ghes_notifier.c's in-memory tally; "agg" counters persist across
+ * boots via the same EFI NV variable UEFI RasDxe.c initializes
+ * (MtkDramEccCeCount, MtkDramEccUceCount_inline, MtkDramEccUceCount_ondie,
+ * GUID must match RasDxe.c's mMtkEccCounterGuid). The UCE count is kept
+ * per ECC source. Inline ECC detects every UCE, including one that
+ * originates in the DRAM die, so agg_uce_inline is the total; the
+ * agg_uce_ondie subset counts those that on-die ECC also flagged.
  */
 #include <linux/kobject.h>
 #include <linux/sysfs.h>
+#include <linux/efi.h>
 #include <linux/export.h>
+#include <linux/workqueue.h>
 #include <linux/atomic.h>
 #include <linux/xarray.h>
 
@@ -17,12 +24,110 @@
 #include "ghes_notifier.h"
 #include "counters.h"
 
+#define MTK_ECC_EFI_ATTR \
+	(EFI_VARIABLE_NON_VOLATILE | \
+	 EFI_VARIABLE_BOOTSERVICE_ACCESS | \
+	 EFI_VARIABLE_RUNTIME_ACCESS)
+
+static efi_guid_t mtk_ecc_efi_guid =
+	EFI_GUID(0x5a3f1e8c, 0x0b2d, 0x4e7f,
+		 0x9c, 0x6a, 0x1d, 0x2b, 0x3e, 0x4f, 0x5a, 0x6b);
+
+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";
+
 static struct kobject *counters_kobj;
 static struct kobject *retired_kobj;
 static atomic64_t retired_by_ce = ATOMIC64_INIT(0);
 static atomic64_t retired_by_uce = ATOMIC64_INIT(0);
 static DEFINE_XARRAY(retired_pfns);
 
+/*
+ * Deferred EFI variable write.  SetVariable NV can take tens of
+ * milliseconds, so it should not be called from the GHES vendor-record
+ * notifier's atomic/IRQ context.  We accumulate pending increments and
+ * flush them from a workqueue instead.
+ */
+static atomic_t pending_ce_inc  = ATOMIC_INIT(0);
+static atomic_t pending_uce_inline_inc = ATOMIC_INIT(0);
+static atomic_t pending_uce_ondie_inc  = ATOMIC_INIT(0);
+static void counters_flush_work_fn(struct work_struct *work);
+static DECLARE_WORK(counters_flush_work, counters_flush_work_fn);
+
+static u64 efi_counter_get(efi_char16_t *name)
+{
+	unsigned long size = sizeof(u64);
+	u64 val = 0;
+	efi_status_t status;
+
+	if (!efi_rt_services_supported(EFI_RT_SUPPORTED_GET_VARIABLE))
+		return 0;
+
+	status = efi.get_variable(name, &mtk_ecc_efi_guid, NULL, &size, &val);
+
+	if (status != EFI_SUCCESS && status != EFI_NOT_FOUND)
+		pr_warn("mtk-dram-ecc: get EFI var failed: 0x%lx\n", status);
+
+	return (status == EFI_SUCCESS) ? val : 0;
+}
+
+static void efi_counter_inc(efi_char16_t *name, int delta)
+{
+	unsigned long size = sizeof(u64);
+	u64 val = 0;
+	efi_status_t status;
+
+	if (!efi_rt_services_supported(EFI_RT_SUPPORTED_SET_VARIABLE))
+		return;
+
+	status = efi.get_variable(name, &mtk_ecc_efi_guid, NULL, &size, &val);
+	if (status == EFI_NOT_FOUND)
+		val = 0;
+	else if (status != EFI_SUCCESS)
+		return;
+
+	val += delta;
+	status = efi.set_variable(name, &mtk_ecc_efi_guid,
+				  MTK_ECC_EFI_ATTR, sizeof(u64), &val);
+	if (status != EFI_SUCCESS)
+		pr_warn("mtk-dram-ecc: set EFI var failed: 0x%lx\n", status);
+}
+
+static void counters_flush_work_fn(struct work_struct *work)
+{
+	int ce_delta, uce_inline_delta, uce_ondie_delta;
+
+	ce_delta = atomic_xchg(&pending_ce_inc, 0);
+	uce_inline_delta = atomic_xchg(&pending_uce_inline_inc, 0);
+	uce_ondie_delta = atomic_xchg(&pending_uce_ondie_inc, 0);
+
+	if (ce_delta > 0)
+		efi_counter_inc(mtk_ce_var_name, ce_delta);
+	if (uce_inline_delta > 0)
+		efi_counter_inc(mtk_uce_inline_var_name, uce_inline_delta);
+	if (uce_ondie_delta > 0)
+		efi_counter_inc(mtk_uce_ondie_var_name, uce_ondie_delta);
+}
+
+void mtk_dram_ecc_agg_ce_inc(void)
+{
+	atomic_inc(&pending_ce_inc);
+	schedule_work(&counters_flush_work);
+}
+
+void mtk_dram_ecc_agg_uce_inline_inc(void)
+{
+	atomic_inc(&pending_uce_inline_inc);
+	schedule_work(&counters_flush_work);
+}
+
+void mtk_dram_ecc_agg_uce_ondie_inc(void)
+{
+	atomic_inc(&pending_uce_ondie_inc);
+	schedule_work(&counters_flush_work);
+}
+
 void mtk_dram_ecc_record_retired_page(unsigned long pfn,
 				      enum mtk_dram_ecc_retire_reason reason,
 				      int memory_failure_ret)
@@ -79,6 +184,12 @@ static ssize_t volatile_uce_show(struct kobject *kobj,
 	return sysfs_emit(buf, "%llu\n", mtk_dram_ecc_get_volatile_uce());
 }
 
+static ssize_t agg_ce_show(struct kobject *kobj,
+			   struct kobj_attribute *attr, char *buf)
+{
+	return sysfs_emit(buf, "%llu\n", efi_counter_get(mtk_ce_var_name));
+}
+
 static ssize_t volatile_uce_inline_show(struct kobject *kobj,
 					struct kobj_attribute *attr, char *buf)
 {
@@ -91,16 +202,43 @@ static ssize_t volatile_uce_ondie_show(struct kobject *kobj,
 	return sysfs_emit(buf, "%llu\n", mtk_dram_ecc_get_volatile_uce_ondie());
 }
 
+static ssize_t agg_uce_inline_show(struct kobject *kobj,
+				   struct kobj_attribute *attr, char *buf)
+{
+	return sysfs_emit(buf, "%llu\n", efi_counter_get(mtk_uce_inline_var_name));
+}
+
+static ssize_t agg_uce_ondie_show(struct kobject *kobj,
+				  struct kobj_attribute *attr, char *buf)
+{
+	return sysfs_emit(buf, "%llu\n", efi_counter_get(mtk_uce_ondie_var_name));
+}
+
+/* Total UCE count across boots: every UCE is seen by inline ECC. */
+static ssize_t agg_uce_show(struct kobject *kobj,
+			    struct kobj_attribute *attr, char *buf)
+{
+	return sysfs_emit(buf, "%llu\n", efi_counter_get(mtk_uce_inline_var_name));
+}
+
 static struct kobj_attribute volatile_ce_attr = __ATTR_RO(volatile_ce);
 static struct kobj_attribute volatile_uce_attr = __ATTR_RO(volatile_uce);
+static struct kobj_attribute agg_ce_attr = __ATTR_RO(agg_ce);
+static struct kobj_attribute agg_uce_attr = __ATTR_RO(agg_uce);
 static struct kobj_attribute volatile_uce_inline_attr = __ATTR_RO(volatile_uce_inline);
 static struct kobj_attribute volatile_uce_ondie_attr = __ATTR_RO(volatile_uce_ondie);
+static struct kobj_attribute agg_uce_inline_attr = __ATTR_RO(agg_uce_inline);
+static struct kobj_attribute agg_uce_ondie_attr = __ATTR_RO(agg_uce_ondie);
 
 static struct attribute *attrs[] = {
 	&volatile_ce_attr.attr,
 	&volatile_uce_attr.attr,
+	&agg_ce_attr.attr,
+	&agg_uce_attr.attr,
 	&volatile_uce_inline_attr.attr,
 	&volatile_uce_ondie_attr.attr,
+	&agg_uce_inline_attr.attr,
+	&agg_uce_ondie_attr.attr,
 	NULL,
 };
 
@@ -169,6 +307,7 @@ int mtk_dram_ecc_counters_init(struct kobject *parent)
 
 void mtk_dram_ecc_counters_exit(void)
 {
+	cancel_work_sync(&counters_flush_work);
 	sysfs_remove_group(retired_kobj, &retired_group);
 	kobject_put(retired_kobj);
 	sysfs_remove_group(counters_kobj, &group);
diff --git a/drivers/soc/mediatek/dram-ecc/core/counters.h b/drivers/soc/mediatek/dram-ecc/core/counters.h
index fd718dc45ea6..af2e5ea16b90 100644
--- a/drivers/soc/mediatek/dram-ecc/core/counters.h
+++ b/drivers/soc/mediatek/dram-ecc/core/counters.h
@@ -16,6 +16,9 @@ enum mtk_dram_ecc_retire_reason {
 int mtk_dram_ecc_counters_init(struct kobject *parent);
 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_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/ghes_notifier.c b/drivers/soc/mediatek/dram-ecc/core/ghes_notifier.c
index c1d885f0bf15..633bf89e2231 100644
--- a/drivers/soc/mediatek/dram-ecc/core/ghes_notifier.c
+++ b/drivers/soc/mediatek/dram-ecc/core/ghes_notifier.c
@@ -242,6 +242,7 @@ static int mtk_ghes_vendor_cb(struct notifier_block *nb, unsigned long severity,
 
 	if (evt.severity == MTK_DRAM_ECC_NOTIFY_CE) {
 		atomic64_inc(&volatile_ce_count);
+		mtk_dram_ecc_agg_ce_inc();
 		spin_lock(&addr_lock);
 		if (ce_addr_count < MTK_ECC_MAX_BOOT_ADDRS)
 			ce_addrs[ce_addr_count++] = evt.spa;
@@ -258,8 +259,11 @@ static int mtk_ghes_vendor_cb(struct notifier_block *nb, unsigned long severity,
 
 		/* Inline ECC detects every UCE, including an on-die one. */
 		atomic64_inc(&volatile_uce_inline_count);
-		if (vs->from_ondie_ecc)
+		mtk_dram_ecc_agg_uce_inline_inc();
+		if (vs->from_ondie_ecc) {
 			atomic64_inc(&volatile_uce_ondie_count);
+			mtk_dram_ecc_agg_uce_ondie_inc();
+		}
 		spin_lock(&addr_lock);
 		if (uce_addr_count < MTK_ECC_MAX_BOOT_ADDRS)
 			uce_addrs[uce_addr_count++] = evt.spa;
-- 
2.45.2



^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [RFC PATCH 07/11] soc: mediatek: dram-ecc: add containment policy and storm protection
  2026-11-02  9:23 [RFC PATCH 00/11] soc: mediatek: add a DRAM ECC error handling driver santino.wang
                   ` (5 preceding siblings ...)
  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 ` santino.wang
  2026-11-02  9:23 ` [RFC PATCH 08/11] soc: mediatek: dram-ecc: add the corrected error leaky bucket santino.wang
                   ` (4 subsequent siblings)
  11 siblings, 0 replies; 20+ messages in thread
From: santino.wang @ 2026-11-02  9:23 UTC (permalink / raw)
  To: Matthias Brugger, AngeloGioacchino Del Regno
  Cc: linux-mediatek, linux-arm-kernel, linux-kernel, light.chen,
	Fred-WY.Chen, arnab.layek, Santino Wang

From: Santino Wang <santino.wang@mediatek.com>

Add the sysfs attributes that configure the error handling done by the
secure firmware and push each setting to it through SMC:

 - whether uncorrectable errors are contained and the default severity
   of a contained error,
 - the CE storm protection threshold, time window and cool-down,
 - per address range severity overrides.

Signed-off-by: Santino Wang <santino.wang@mediatek.com>
---
 drivers/soc/mediatek/dram-ecc/core/Makefile   |   6 +-
 drivers/soc/mediatek/dram-ecc/core/main.c     |   9 +-
 .../soc/mediatek/dram-ecc/core/mtk_dram_ecc.h |   3 +
 .../dram-ecc/core/policy/address_range.c      | 120 +++++++++++++++
 .../dram-ecc/core/policy/containment.c        | 107 ++++++++++++++
 .../mediatek/dram-ecc/core/policy/policy.c    |  50 +++++++
 .../mediatek/dram-ecc/core/policy/policy.h    |  19 +++
 .../dram-ecc/core/policy/storm_protection.c   | 137 ++++++++++++++++++
 8 files changed, 449 insertions(+), 2 deletions(-)
 create mode 100644 drivers/soc/mediatek/dram-ecc/core/policy/address_range.c
 create mode 100644 drivers/soc/mediatek/dram-ecc/core/policy/containment.c
 create mode 100644 drivers/soc/mediatek/dram-ecc/core/policy/policy.c
 create mode 100644 drivers/soc/mediatek/dram-ecc/core/policy/policy.h
 create mode 100644 drivers/soc/mediatek/dram-ecc/core/policy/storm_protection.c

diff --git a/drivers/soc/mediatek/dram-ecc/core/Makefile b/drivers/soc/mediatek/dram-ecc/core/Makefile
index 96d37c6a3b9c..d8d878f321d4 100644
--- a/drivers/soc/mediatek/dram-ecc/core/Makefile
+++ b/drivers/soc/mediatek/dram-ecc/core/Makefile
@@ -8,6 +8,10 @@ mtk_dram_ecc-y := \
 	ghes_notifier.o \
 	trace.o \
 	counters.o \
-	addresses.o
+	addresses.o \
+	policy/policy.o \
+	policy/containment.o \
+	policy/storm_protection.o \
+	policy/address_range.o
 
 CFLAGS_trace.o := -I$(src)
diff --git a/drivers/soc/mediatek/dram-ecc/core/main.c b/drivers/soc/mediatek/dram-ecc/core/main.c
index b859af25bcc0..315bdcbd650a 100644
--- a/drivers/soc/mediatek/dram-ecc/core/main.c
+++ b/drivers/soc/mediatek/dram-ecc/core/main.c
@@ -37,10 +37,14 @@ static int __init mtk_dram_ecc_init(void)
 		goto err_class;
 	}
 
-	ret = mtk_dram_ecc_counters_init(&mtk_dram_ecc_dev->kobj);
+	ret = mtk_dram_ecc_policy_init(mtk_dram_ecc_dev);
 	if (ret)
 		goto err_dev;
 
+	ret = mtk_dram_ecc_counters_init(&mtk_dram_ecc_dev->kobj);
+	if (ret)
+		goto err_policy;
+
 	ret = mtk_dram_ecc_addresses_init(&mtk_dram_ecc_dev->kobj);
 	if (ret)
 		goto err_counters;
@@ -62,6 +66,8 @@ static int __init mtk_dram_ecc_init(void)
 	mtk_dram_ecc_addresses_exit();
 err_counters:
 	mtk_dram_ecc_counters_exit();
+err_policy:
+	mtk_dram_ecc_policy_exit(mtk_dram_ecc_dev);
 err_dev:
 	device_destroy(mtk_dram_ecc_class, MKDEV(0, 0));
 err_class:
@@ -79,6 +85,7 @@ static void __exit mtk_dram_ecc_exit(void)
 	mtk_dram_ecc_ghes_exit();
 	mtk_dram_ecc_addresses_exit();
 	mtk_dram_ecc_counters_exit();
+	mtk_dram_ecc_policy_exit(mtk_dram_ecc_dev);
 	device_destroy(mtk_dram_ecc_class, MKDEV(0, 0));
 	class_destroy(mtk_dram_ecc_class);
 	trace_mtk_dram_ecc_lifecycle(1);
diff --git a/drivers/soc/mediatek/dram-ecc/core/mtk_dram_ecc.h b/drivers/soc/mediatek/dram-ecc/core/mtk_dram_ecc.h
index 456961818ccd..c8a75450dac5 100644
--- a/drivers/soc/mediatek/dram-ecc/core/mtk_dram_ecc.h
+++ b/drivers/soc/mediatek/dram-ecc/core/mtk_dram_ecc.h
@@ -21,4 +21,7 @@ struct mtk_dram_ecc_data {
 
 extern struct mtk_dram_ecc_data mtk_ecc_data;
 
+int mtk_dram_ecc_policy_init(struct device *dev);
+void mtk_dram_ecc_policy_exit(struct device *dev);
+
 #endif /* _MTK_DRAM_ECC_H_ */
diff --git a/drivers/soc/mediatek/dram-ecc/core/policy/address_range.c b/drivers/soc/mediatek/dram-ecc/core/policy/address_range.c
new file mode 100644
index 000000000000..4ead23d52d7b
--- /dev/null
+++ b/drivers/soc/mediatek/dram-ecc/core/policy/address_range.c
@@ -0,0 +1,120 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2026 MediaTek Inc.
+ */
+#include <linux/kobject.h>
+#include <linux/sysfs.h>
+#include <linux/string.h>
+
+#include "../mtk_dram_ecc.h"
+#include "../smc.h"
+#include "policy.h"
+
+/*
+ * Kernel-side sanity cap on entries_show()'s enumeration loop only - not
+ * the real limit. ATF owns the actual table size (policy.c's
+ * DRAM_ECC_MAX_RANGE_ENTRIES) and is the source of truth; this just
+ * bounds the loop in case ATF ever misbehaves.
+ */
+#define ADDRESS_RANGE_ENUM_CAP 64
+
+static struct kobject *address_range_kobj;
+
+static ssize_t entries_show(struct kobject *kobj, struct kobj_attribute *attr,
+			    char *buf)
+{
+	ssize_t len = 0;
+	u32 i;
+
+	for (i = 0; i < ADDRESS_RANGE_ENUM_CAP; i++) {
+		u64 addr_from, addr_to;
+		bool recoverable;
+
+		if (mtk_dram_ecc_smc_range_get(i, &addr_from, &addr_to, &recoverable))
+			break;
+
+		len += sysfs_emit_at(buf, len, "0x%llx 0x%llx %s\n",
+				     addr_from, addr_to, recoverable ? "recoverable" : "fatal");
+	}
+
+	return len;
+}
+
+/*
+ * entries_store - add or update one range. Format: "addr_from addr_to
+ * fatal|recoverable". No stored state on the kernel side - ATF holds the
+ * table; entries_show() always reads it back fresh.
+ */
+static ssize_t entries_store(struct kobject *kobj, struct kobj_attribute *attr,
+			     const char *buf, size_t count)
+{
+	u64 addr_from, addr_to;
+	char severity_str[16];
+	bool recoverable;
+	int ret;
+
+	if (sscanf(buf, "%llx %llx %15s", &addr_from, &addr_to, severity_str) != 3)
+		return -EINVAL;
+
+	if (sysfs_streq(severity_str, "recoverable"))
+		recoverable = true;
+	else if (sysfs_streq(severity_str, "fatal"))
+		recoverable = false;
+	else
+		return -EINVAL;
+
+	ret = mtk_dram_ecc_smc_range_add(addr_from, addr_to, recoverable);
+	if (ret)
+		return -EIO;
+
+	return count;
+}
+
+/* clear_store - any write clears the whole ATF-side range table. */
+static ssize_t clear_store(struct kobject *kobj, struct kobj_attribute *attr,
+			   const char *buf, size_t count)
+{
+	int ret = mtk_dram_ecc_smc_range_clear();
+
+	if (ret)
+		return -EIO;
+
+	return count;
+}
+
+static struct kobj_attribute entries_attr =
+	__ATTR(entries, 0644, entries_show, entries_store);
+static struct kobj_attribute clear_attr =
+	__ATTR(clear, 0200, NULL, clear_store);
+
+static struct attribute *attrs[] = {
+	&entries_attr.attr,
+	&clear_attr.attr,
+	NULL,
+};
+
+static const struct attribute_group group = {
+	.attrs = attrs,
+};
+
+int address_range_init(struct kobject *parent)
+{
+	int ret;
+
+	address_range_kobj = kobject_create_and_add("address_range", parent);
+	if (!address_range_kobj)
+		return -ENOMEM;
+
+	ret = sysfs_create_group(address_range_kobj, &group);
+	if (ret) {
+		kobject_put(address_range_kobj);
+		return ret;
+	}
+	return 0;
+}
+
+void address_range_exit(struct kobject *parent)
+{
+	sysfs_remove_group(address_range_kobj, &group);
+	kobject_put(address_range_kobj);
+}
diff --git a/drivers/soc/mediatek/dram-ecc/core/policy/containment.c b/drivers/soc/mediatek/dram-ecc/core/policy/containment.c
new file mode 100644
index 000000000000..98c7ec6fafb0
--- /dev/null
+++ b/drivers/soc/mediatek/dram-ecc/core/policy/containment.c
@@ -0,0 +1,107 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2026 MediaTek Inc.
+ */
+#include <linux/kobject.h>
+#include <linux/sysfs.h>
+#include <linux/string.h>
+
+#include "../mtk_dram_ecc.h"
+#include "../smc.h"
+#include "policy.h"
+
+static struct kobject *containment_kobj;
+
+static ssize_t enabled_show(struct kobject *kobj, struct kobj_attribute *attr,
+			    char *buf)
+{
+	bool enabled;
+
+	if (mtk_dram_ecc_smc_containment_get_enabled(&enabled))
+		return -EIO;
+	return sysfs_emit(buf, "%d\n", enabled ? 1 : 0);
+}
+
+static ssize_t enabled_store(struct kobject *kobj, struct kobj_attribute *attr,
+			     const char *buf, size_t count)
+{
+	bool val;
+	int ret;
+
+	ret = kstrtobool(buf, &val);
+	if (ret)
+		return ret;
+
+	ret = mtk_dram_ecc_smc_containment_set_enabled(val);
+	if (ret)
+		return -EIO;
+
+	return count;
+}
+
+static ssize_t default_severity_show(struct kobject *kobj, struct kobj_attribute *attr,
+				     char *buf)
+{
+	bool recoverable;
+
+	if (mtk_dram_ecc_smc_containment_get_default_severity(&recoverable))
+		return -EIO;
+	return sysfs_emit(buf, "%s\n", recoverable ? "recoverable" : "fatal");
+}
+
+static ssize_t default_severity_store(struct kobject *kobj, struct kobj_attribute *attr,
+				      const char *buf, size_t count)
+{
+	bool recoverable;
+	int ret;
+
+	if (sysfs_streq(buf, "recoverable"))
+		recoverable = true;
+	else if (sysfs_streq(buf, "fatal"))
+		recoverable = false;
+	else
+		return -EINVAL;
+
+	ret = mtk_dram_ecc_smc_containment_set_default_severity(recoverable);
+	if (ret)
+		return -EIO;
+
+	return count;
+}
+
+static struct kobj_attribute enabled_attr =
+	__ATTR(enabled, 0644, enabled_show, enabled_store);
+static struct kobj_attribute default_severity_attr =
+	__ATTR(default_severity, 0644, default_severity_show, default_severity_store);
+
+static struct attribute *attrs[] = {
+	&enabled_attr.attr,
+	&default_severity_attr.attr,
+	NULL,
+};
+
+static const struct attribute_group group = {
+	.attrs = attrs,
+};
+
+int containment_init(struct kobject *parent)
+{
+	int ret;
+
+	containment_kobj = kobject_create_and_add("containment", parent);
+	if (!containment_kobj)
+		return -ENOMEM;
+
+	ret = sysfs_create_group(containment_kobj, &group);
+	if (ret) {
+		kobject_put(containment_kobj);
+		return ret;
+	}
+	return 0;
+}
+
+void containment_exit(struct kobject *parent)
+{
+	sysfs_remove_group(containment_kobj, &group);
+	kobject_put(containment_kobj);
+}
diff --git a/drivers/soc/mediatek/dram-ecc/core/policy/policy.c b/drivers/soc/mediatek/dram-ecc/core/policy/policy.c
new file mode 100644
index 000000000000..ecd0a430de6d
--- /dev/null
+++ b/drivers/soc/mediatek/dram-ecc/core/policy/policy.c
@@ -0,0 +1,50 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2026 MediaTek Inc.
+ */
+#include <linux/kobject.h>
+#include <linux/device.h>
+
+#include "../mtk_dram_ecc.h"
+#include "policy.h"
+
+static struct kobject *policy_kobj;
+
+int mtk_dram_ecc_policy_init(struct device *dev)
+{
+	int ret;
+
+	policy_kobj = kobject_create_and_add("policy", &dev->kobj);
+	if (!policy_kobj)
+		return -ENOMEM;
+
+	ret = containment_init(policy_kobj);
+	if (ret)
+		goto err_policy;
+
+	ret = storm_protection_init(policy_kobj);
+	if (ret)
+		goto err_containment;
+
+	ret = address_range_init(policy_kobj);
+	if (ret)
+		goto err_storm;
+
+	return 0;
+
+err_storm:
+	storm_protection_exit(policy_kobj);
+err_containment:
+	containment_exit(policy_kobj);
+err_policy:
+	kobject_put(policy_kobj);
+	return ret;
+}
+
+void mtk_dram_ecc_policy_exit(struct device *dev)
+{
+	address_range_exit(policy_kobj);
+	storm_protection_exit(policy_kobj);
+	containment_exit(policy_kobj);
+	kobject_put(policy_kobj);
+}
diff --git a/drivers/soc/mediatek/dram-ecc/core/policy/policy.h b/drivers/soc/mediatek/dram-ecc/core/policy/policy.h
new file mode 100644
index 000000000000..a535e2ce0e6d
--- /dev/null
+++ b/drivers/soc/mediatek/dram-ecc/core/policy/policy.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2026 MediaTek Inc.
+ */
+#ifndef _MTK_DRAM_ECC_POLICY_H_
+#define _MTK_DRAM_ECC_POLICY_H_
+
+#include <linux/kobject.h>
+
+int containment_init(struct kobject *parent);
+void containment_exit(struct kobject *parent);
+
+int storm_protection_init(struct kobject *parent);
+void storm_protection_exit(struct kobject *parent);
+
+int address_range_init(struct kobject *parent);
+void address_range_exit(struct kobject *parent);
+
+#endif /* _MTK_DRAM_ECC_POLICY_H_ */
diff --git a/drivers/soc/mediatek/dram-ecc/core/policy/storm_protection.c b/drivers/soc/mediatek/dram-ecc/core/policy/storm_protection.c
new file mode 100644
index 000000000000..971ad8bdbaae
--- /dev/null
+++ b/drivers/soc/mediatek/dram-ecc/core/policy/storm_protection.c
@@ -0,0 +1,137 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2026 MediaTek Inc.
+ */
+#include <linux/kobject.h>
+#include <linux/sysfs.h>
+#include <linux/string.h>
+
+#include "../mtk_dram_ecc.h"
+#include "../smc.h"
+#include "policy.h"
+
+static struct kobject *storm_protection_kobj;
+
+static ssize_t threshold_show(struct kobject *kobj,
+			      struct kobj_attribute *attr, char *buf)
+{
+	u32 val;
+
+	if (mtk_dram_ecc_smc_storm_get_threshold(&val))
+		return -EIO;
+	return sysfs_emit(buf, "%u\n", val);
+}
+
+static ssize_t threshold_store(struct kobject *kobj,
+			       struct kobj_attribute *attr,
+			       const char *buf, size_t count)
+{
+	u32 val;
+	int ret;
+
+	ret = kstrtou32(buf, 0, &val);
+	if (ret)
+		return ret;
+
+	ret = mtk_dram_ecc_smc_storm_set_threshold(val);
+	if (ret)
+		return -EIO;
+
+	return count;
+}
+
+static ssize_t time_window_show(struct kobject *kobj,
+				struct kobj_attribute *attr, char *buf)
+{
+	u32 val;
+
+	if (mtk_dram_ecc_smc_storm_get_time_window(&val))
+		return -EIO;
+	return sysfs_emit(buf, "%u\n", val);
+}
+
+static ssize_t time_window_store(struct kobject *kobj,
+				 struct kobj_attribute *attr,
+				 const char *buf, size_t count)
+{
+	u32 val;
+	int ret;
+
+	ret = kstrtou32(buf, 0, &val);
+	if (ret)
+		return ret;
+
+	ret = mtk_dram_ecc_smc_storm_set_time_window(val);
+	if (ret)
+		return -EIO;
+
+	return count;
+}
+
+static ssize_t cooldown_show(struct kobject *kobj,
+			     struct kobj_attribute *attr, char *buf)
+{
+	u32 val;
+
+	if (mtk_dram_ecc_smc_storm_get_cooldown(&val))
+		return -EIO;
+	return sysfs_emit(buf, "%u\n", val);
+}
+
+static ssize_t cooldown_store(struct kobject *kobj,
+			      struct kobj_attribute *attr,
+			      const char *buf, size_t count)
+{
+	u32 val;
+	int ret;
+
+	ret = kstrtou32(buf, 0, &val);
+	if (ret)
+		return ret;
+
+	ret = mtk_dram_ecc_smc_storm_set_cooldown(val);
+	if (ret)
+		return -EIO;
+
+	return count;
+}
+
+static struct kobj_attribute threshold_attr =
+	__ATTR_RW(threshold);
+static struct kobj_attribute time_window_attr =
+	__ATTR_RW(time_window);
+static struct kobj_attribute cooldown_attr =
+	__ATTR_RW(cooldown);
+
+static struct attribute *attrs[] = {
+	&threshold_attr.attr,
+	&time_window_attr.attr,
+	&cooldown_attr.attr,
+	NULL,
+};
+
+static const struct attribute_group group = {
+	.attrs = attrs,
+};
+
+int storm_protection_init(struct kobject *parent)
+{
+	int ret;
+
+	storm_protection_kobj = kobject_create_and_add("storm_protection", parent);
+	if (!storm_protection_kobj)
+		return -ENOMEM;
+
+	ret = sysfs_create_group(storm_protection_kobj, &group);
+	if (ret) {
+		kobject_put(storm_protection_kobj);
+		return ret;
+	}
+	return 0;
+}
+
+void storm_protection_exit(struct kobject *parent)
+{
+	sysfs_remove_group(storm_protection_kobj, &group);
+	kobject_put(storm_protection_kobj);
+}
-- 
2.45.2



^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [RFC PATCH 08/11] soc: mediatek: dram-ecc: add the corrected error leaky bucket
  2026-11-02  9:23 [RFC PATCH 00/11] soc: mediatek: add a DRAM ECC error handling driver santino.wang
                   ` (6 preceding siblings ...)
  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 ` 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
                   ` (3 subsequent siblings)
  11 siblings, 1 reply; 20+ messages in thread
From: santino.wang @ 2026-11-02  9:23 UTC (permalink / raw)
  To: Matthias Brugger, AngeloGioacchino Del Regno
  Cc: linux-mediatek, linux-arm-kernel, linux-kernel, light.chen,
	Fred-WY.Chen, arnab.layek, Santino Wang

From: Santino Wang <santino.wang@mediatek.com>

A page with repeated corrected errors is likely to fail. Count the
corrected errors per page in a leaky bucket and retire the page with
memory_failure() when the count reaches a threshold. The bucket decays
over time, and its threshold, decay interval and the number of tracked
pages are configurable through sysfs.

Signed-off-by: Santino Wang <santino.wang@mediatek.com>
---
 drivers/soc/mediatek/dram-ecc/core/Makefile   |   1 +
 .../mediatek/dram-ecc/core/ghes_notifier.c    |   2 +
 .../dram-ecc/core/policy/leaky_bucket.c       | 277 ++++++++++++++++++
 .../mediatek/dram-ecc/core/policy/policy.c    |   9 +-
 .../mediatek/dram-ecc/core/policy/policy.h    |   4 +
 5 files changed, 292 insertions(+), 1 deletion(-)
 create mode 100644 drivers/soc/mediatek/dram-ecc/core/policy/leaky_bucket.c

diff --git a/drivers/soc/mediatek/dram-ecc/core/Makefile b/drivers/soc/mediatek/dram-ecc/core/Makefile
index d8d878f321d4..7c973831c9ef 100644
--- a/drivers/soc/mediatek/dram-ecc/core/Makefile
+++ b/drivers/soc/mediatek/dram-ecc/core/Makefile
@@ -12,6 +12,7 @@ mtk_dram_ecc-y := \
 	policy/policy.o \
 	policy/containment.o \
 	policy/storm_protection.o \
+	policy/leaky_bucket.o \
 	policy/address_range.o
 
 CFLAGS_trace.o := -I$(src)
diff --git a/drivers/soc/mediatek/dram-ecc/core/ghes_notifier.c b/drivers/soc/mediatek/dram-ecc/core/ghes_notifier.c
index 633bf89e2231..a010ca652e86 100644
--- a/drivers/soc/mediatek/dram-ecc/core/ghes_notifier.c
+++ b/drivers/soc/mediatek/dram-ecc/core/ghes_notifier.c
@@ -28,6 +28,7 @@
 #include "mtk_dram_ecc_event.h"
 #include "ghes_notifier.h"
 #include "counters.h"
+#include "policy/policy.h"
 #include "trace.h"
 
 static ATOMIC_NOTIFIER_HEAD(mtk_dram_ecc_notifier_chain);
@@ -249,6 +250,7 @@ static int mtk_ghes_vendor_cb(struct notifier_block *nb, unsigned long severity,
 		spin_unlock(&addr_lock);
 		trace_mtk_dram_ecc_ce(evt.spa, evt.channel, evt.subchannel, evt.rank,
 				      evt.bank, evt.row, evt.column);
+		leaky_bucket_hit(evt.spa);
 		ret = atomic_notifier_call_chain(&mtk_dram_ecc_notifier_chain,
 						 MTK_DRAM_ECC_NOTIFY_CE, &evt);
 		if (ret == NOTIFY_BAD)
diff --git a/drivers/soc/mediatek/dram-ecc/core/policy/leaky_bucket.c b/drivers/soc/mediatek/dram-ecc/core/policy/leaky_bucket.c
new file mode 100644
index 000000000000..a51793d53efb
--- /dev/null
+++ b/drivers/soc/mediatek/dram-ecc/core/policy/leaky_bucket.c
@@ -0,0 +1,277 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2026 MediaTek Inc.
+ */
+#include <linux/kobject.h>
+#include <linux/sysfs.h>
+#include <linux/string.h>
+#include <linux/hashtable.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/workqueue.h>
+#include <linux/mm.h>
+#include <linux/jiffies.h>
+#include <linux/errno.h>
+#include <linux/printk.h>
+
+#include "../counters.h"
+#include "../mtk_dram_ecc.h"
+#include "../trace.h"
+#include "policy.h"
+
+/* ------------------------------------------------------------------ */
+/* Per-page counter table                                               */
+/* ------------------------------------------------------------------ */
+
+struct lb_entry {
+	struct hlist_node node;
+	u64 pfn;
+	u32 count;
+};
+
+#define LB_HASH_BITS 8   /* 256 buckets */
+
+static DECLARE_HASHTABLE(lb_table, LB_HASH_BITS);
+static DEFINE_SPINLOCK(lb_lock);
+static unsigned int lb_tracked;  /* number of entries currently in table */
+
+/* ------------------------------------------------------------------ */
+/* Decay timer (delayed_work)                                           */
+/* ------------------------------------------------------------------ */
+
+static struct delayed_work lb_decay_work;
+
+static void lb_decay_fn(struct work_struct *work)
+{
+	struct lb_entry *e;
+	struct hlist_node *tmp;
+	unsigned int bkt;
+	bool reschedule = false;
+
+	spin_lock(&lb_lock);
+	hash_for_each_safe(lb_table, bkt, tmp, e, node) {
+		if (e->count > 0)
+			e->count--;
+		if (e->count == 0) {
+			hash_del(&e->node);
+			kfree(e);
+			lb_tracked--;
+		} else {
+			reschedule = true;
+		}
+	}
+	spin_unlock(&lb_lock);
+
+	if (reschedule)
+		schedule_delayed_work(&lb_decay_work,
+				      secs_to_jiffies(mtk_ecc_data.lb_decay_interval_sec));
+}
+
+/* ------------------------------------------------------------------ */
+/* leaky_bucket_hit - called from ghes_notifier.c on every CE event    */
+/* ------------------------------------------------------------------ */
+
+void leaky_bucket_hit(u64 spa)
+{
+	u64 pfn = spa >> PAGE_SHIFT;
+	struct lb_entry *e;
+	bool found = false;
+	bool start_timer = false;
+	bool retired = false;
+	u32 count = 0;
+	int ret;
+
+	spin_lock(&lb_lock);
+
+	hash_for_each_possible(lb_table, e, node, pfn) {
+		if (e->pfn == pfn) {
+			e->count++;
+			count = e->count;
+			found = true;
+			break;
+		}
+	}
+
+	if (!found) {
+		if (lb_tracked < mtk_ecc_data.lb_max_tracked) {
+			e = kzalloc_obj(*e, GFP_ATOMIC);
+			if (e) {
+				e->pfn   = pfn;
+				e->count = 1;
+				count    = 1;
+				hash_add(lb_table, &e->node, pfn);
+				lb_tracked++;
+				/* start timer only when table was empty */
+				if (lb_tracked == 1)
+					start_timer = true;
+			}
+		}
+	}
+
+	spin_unlock(&lb_lock);
+
+	if (start_timer)
+		schedule_delayed_work(&lb_decay_work,
+				      secs_to_jiffies(mtk_ecc_data.lb_decay_interval_sec));
+
+	if (count >= mtk_ecc_data.lb_threshold && mtk_ecc_data.lb_threshold > 0) {
+		ret = memory_failure(pfn, 0);
+		if (ret == 0 || ret == -EHWPOISON) {
+			mtk_dram_ecc_record_retired_page(pfn,
+							 MTK_DRAM_ECC_RETIRE_CE,
+							 ret);
+			retired = true;
+		} else {
+			pr_warn_ratelimited("mtk-dram-ecc: CE page isolation failed: pfn=0x%llx ret=%d\n",
+					    pfn, ret);
+		}
+	}
+
+	trace_mtk_dram_ecc_page_retire(pfn, count, mtk_ecc_data.lb_threshold,
+				       retired);
+}
+
+/* ------------------------------------------------------------------ */
+/* sysfs                                                                */
+/* ------------------------------------------------------------------ */
+
+static struct kobject *leaky_bucket_kobj;
+
+#define MTK_ECC_LB_THRESHOLD_MAX		10000U
+#define MTK_ECC_LB_DECAY_INTERVAL_MAX	86400U
+#define MTK_ECC_LB_MAX_TRACKED_MAX	16U
+
+static ssize_t threshold_show(struct kobject *kobj, struct kobj_attribute *attr,
+			      char *buf)
+{
+	return sysfs_emit(buf, "%u\n", mtk_ecc_data.lb_threshold);
+}
+
+static ssize_t threshold_store(struct kobject *kobj, struct kobj_attribute *attr,
+			       const char *buf, size_t count)
+{
+	u32 val;
+	int ret;
+
+	ret = kstrtou32(buf, 0, &val);
+	if (ret)
+		return ret;
+	if (val == 0)
+		val = MTK_ECC_LB_THRESHOLD_DEFAULT;
+	else if (val > MTK_ECC_LB_THRESHOLD_MAX)
+		return -EINVAL;
+
+	mtk_ecc_data.lb_threshold = val;
+	return count;
+}
+
+static ssize_t decay_interval_sec_show(struct kobject *kobj,
+				       struct kobj_attribute *attr, char *buf)
+{
+	return sysfs_emit(buf, "%u\n", mtk_ecc_data.lb_decay_interval_sec);
+}
+
+static ssize_t decay_interval_sec_store(struct kobject *kobj,
+					struct kobj_attribute *attr,
+					const char *buf, size_t count)
+{
+	u32 val;
+	int ret;
+
+	ret = kstrtou32(buf, 0, &val);
+	if (ret)
+		return ret;
+	if (val == 0)
+		val = MTK_ECC_LB_DECAY_INTERVAL_DEFAULT;
+	else if (val > MTK_ECC_LB_DECAY_INTERVAL_MAX)
+		return -EINVAL;
+
+	mtk_ecc_data.lb_decay_interval_sec = val;
+	return count;
+}
+
+static ssize_t max_tracked_show(struct kobject *kobj,
+				struct kobj_attribute *attr, char *buf)
+{
+	return sysfs_emit(buf, "%u\n", mtk_ecc_data.lb_max_tracked);
+}
+
+static ssize_t max_tracked_store(struct kobject *kobj,
+				 struct kobj_attribute *attr,
+				 const char *buf, size_t count)
+{
+	u32 val;
+	int ret;
+
+	ret = kstrtou32(buf, 0, &val);
+	if (ret)
+		return ret;
+	if (val == 0)
+		val = MTK_ECC_LB_MAX_TRACKED_DEFAULT;
+	else if (val > MTK_ECC_LB_MAX_TRACKED_MAX)
+		return -EINVAL;
+
+	mtk_ecc_data.lb_max_tracked = val;
+	return count;
+}
+
+static struct kobj_attribute threshold_attr =
+	__ATTR(threshold, 0644, threshold_show, threshold_store);
+static struct kobj_attribute decay_interval_sec_attr =
+	__ATTR(decay_interval_sec, 0644, decay_interval_sec_show, decay_interval_sec_store);
+static struct kobj_attribute max_tracked_attr =
+	__ATTR(max_tracked, 0644, max_tracked_show, max_tracked_store);
+
+static struct attribute *attrs[] = {
+	&threshold_attr.attr,
+	&decay_interval_sec_attr.attr,
+	&max_tracked_attr.attr,
+	NULL,
+};
+
+static const struct attribute_group group = {
+	.attrs = attrs,
+};
+
+/* ------------------------------------------------------------------ */
+/* init / exit                                                          */
+/* ------------------------------------------------------------------ */
+
+int leaky_bucket_init(struct kobject *parent)
+{
+	int ret;
+
+	hash_init(lb_table);
+	INIT_DELAYED_WORK(&lb_decay_work, lb_decay_fn);
+
+	leaky_bucket_kobj = kobject_create_and_add("leaky_bucket", parent);
+	if (!leaky_bucket_kobj)
+		return -ENOMEM;
+
+	ret = sysfs_create_group(leaky_bucket_kobj, &group);
+	if (ret) {
+		kobject_put(leaky_bucket_kobj);
+		return ret;
+	}
+	return 0;
+}
+
+void leaky_bucket_exit(struct kobject *parent)
+{
+	struct lb_entry *e;
+	struct hlist_node *tmp;
+	unsigned int bkt;
+
+	cancel_delayed_work_sync(&lb_decay_work);
+
+	spin_lock(&lb_lock);
+	hash_for_each_safe(lb_table, bkt, tmp, e, node) {
+		hash_del(&e->node);
+		kfree(e);
+	}
+	lb_tracked = 0;
+	spin_unlock(&lb_lock);
+
+	sysfs_remove_group(leaky_bucket_kobj, &group);
+	kobject_put(leaky_bucket_kobj);
+}
diff --git a/drivers/soc/mediatek/dram-ecc/core/policy/policy.c b/drivers/soc/mediatek/dram-ecc/core/policy/policy.c
index ecd0a430de6d..5a4181b8de7d 100644
--- a/drivers/soc/mediatek/dram-ecc/core/policy/policy.c
+++ b/drivers/soc/mediatek/dram-ecc/core/policy/policy.c
@@ -26,12 +26,18 @@ int mtk_dram_ecc_policy_init(struct device *dev)
 	if (ret)
 		goto err_containment;
 
-	ret = address_range_init(policy_kobj);
+	ret = leaky_bucket_init(policy_kobj);
 	if (ret)
 		goto err_storm;
 
+	ret = address_range_init(policy_kobj);
+	if (ret)
+		goto err_leaky_bucket;
+
 	return 0;
 
+err_leaky_bucket:
+	leaky_bucket_exit(policy_kobj);
 err_storm:
 	storm_protection_exit(policy_kobj);
 err_containment:
@@ -44,6 +50,7 @@ int mtk_dram_ecc_policy_init(struct device *dev)
 void mtk_dram_ecc_policy_exit(struct device *dev)
 {
 	address_range_exit(policy_kobj);
+	leaky_bucket_exit(policy_kobj);
 	storm_protection_exit(policy_kobj);
 	containment_exit(policy_kobj);
 	kobject_put(policy_kobj);
diff --git a/drivers/soc/mediatek/dram-ecc/core/policy/policy.h b/drivers/soc/mediatek/dram-ecc/core/policy/policy.h
index a535e2ce0e6d..c9926cbc11be 100644
--- a/drivers/soc/mediatek/dram-ecc/core/policy/policy.h
+++ b/drivers/soc/mediatek/dram-ecc/core/policy/policy.h
@@ -13,6 +13,10 @@ void containment_exit(struct kobject *parent);
 int storm_protection_init(struct kobject *parent);
 void storm_protection_exit(struct kobject *parent);
 
+int leaky_bucket_init(struct kobject *parent);
+void leaky_bucket_exit(struct kobject *parent);
+void leaky_bucket_hit(u64 spa);
+
 int address_range_init(struct kobject *parent);
 void address_range_exit(struct kobject *parent);
 
-- 
2.45.2



^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [RFC PATCH 09/11] soc: mediatek: dram-ecc: add a generic netlink event interface
  2026-11-02  9:23 [RFC PATCH 00/11] soc: mediatek: add a DRAM ECC error handling driver santino.wang
                   ` (7 preceding siblings ...)
  2026-11-02  9:23 ` [RFC PATCH 08/11] soc: mediatek: dram-ecc: add the corrected error leaky bucket santino.wang
@ 2026-11-02  9:23 ` 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
                   ` (2 subsequent siblings)
  11 siblings, 1 reply; 20+ messages in thread
From: santino.wang @ 2026-11-02  9:23 UTC (permalink / raw)
  To: Matthias Brugger, AngeloGioacchino Del Regno
  Cc: linux-mediatek, linux-arm-kernel, linux-kernel, light.chen,
	Fred-WY.Chen, arnab.layek, Santino Wang

From: Santino Wang <santino.wang@mediatek.com>

Multicast every decoded corrected and uncorrectable error to user
space through a generic netlink family, so a monitoring daemon can
subscribe to events instead of polling the counters.

Signed-off-by: Santino Wang <santino.wang@mediatek.com>
---
 drivers/soc/mediatek/dram-ecc/core/Makefile   |  1 +
 .../mediatek/dram-ecc/core/ghes_notifier.c    |  3 +
 drivers/soc/mediatek/dram-ecc/core/main.c     | 10 ++-
 drivers/soc/mediatek/dram-ecc/core/netlink.c  | 83 +++++++++++++++++++
 drivers/soc/mediatek/dram-ecc/core/netlink.h  | 14 ++++
 include/uapi/linux/mtk_dram_ecc_netlink.h     | 44 ++++++++++
 6 files changed, 154 insertions(+), 1 deletion(-)
 create mode 100644 drivers/soc/mediatek/dram-ecc/core/netlink.c
 create mode 100644 drivers/soc/mediatek/dram-ecc/core/netlink.h
 create mode 100644 include/uapi/linux/mtk_dram_ecc_netlink.h

diff --git a/drivers/soc/mediatek/dram-ecc/core/Makefile b/drivers/soc/mediatek/dram-ecc/core/Makefile
index 7c973831c9ef..1e2764914e9c 100644
--- a/drivers/soc/mediatek/dram-ecc/core/Makefile
+++ b/drivers/soc/mediatek/dram-ecc/core/Makefile
@@ -6,6 +6,7 @@ mtk_dram_ecc-y := \
 	main.o \
 	smc.o \
 	ghes_notifier.o \
+	netlink.o \
 	trace.o \
 	counters.o \
 	addresses.o \
diff --git a/drivers/soc/mediatek/dram-ecc/core/ghes_notifier.c b/drivers/soc/mediatek/dram-ecc/core/ghes_notifier.c
index a010ca652e86..701b0b28d358 100644
--- a/drivers/soc/mediatek/dram-ecc/core/ghes_notifier.c
+++ b/drivers/soc/mediatek/dram-ecc/core/ghes_notifier.c
@@ -28,6 +28,7 @@
 #include "mtk_dram_ecc_event.h"
 #include "ghes_notifier.h"
 #include "counters.h"
+#include "netlink.h"
 #include "policy/policy.h"
 #include "trace.h"
 
@@ -256,6 +257,7 @@ static int mtk_ghes_vendor_cb(struct notifier_block *nb, unsigned long severity,
 		if (ret == NOTIFY_BAD)
 			pr_warn_ratelimited("mtk-dram-ecc: kernel client rejected CE event for spa=0x%llx\n",
 					    evt.spa);
+		mtk_dram_ecc_netlink_notify(&evt);
 	} else {
 		unsigned long pfn = evt.spa >> PAGE_SHIFT;
 
@@ -305,6 +307,7 @@ static int mtk_ghes_vendor_cb(struct notifier_block *nb, unsigned long severity,
 		if (ret == NOTIFY_BAD)
 			pr_warn_ratelimited("mtk-dram-ecc: kernel client rejected UCE event for spa=0x%llx\n",
 					    evt.spa);
+		mtk_dram_ecc_netlink_notify(&evt);
 	}
 
 	return NOTIFY_OK;
diff --git a/drivers/soc/mediatek/dram-ecc/core/main.c b/drivers/soc/mediatek/dram-ecc/core/main.c
index 315bdcbd650a..320631b3a044 100644
--- a/drivers/soc/mediatek/dram-ecc/core/main.c
+++ b/drivers/soc/mediatek/dram-ecc/core/main.c
@@ -10,6 +10,7 @@
 #include "ghes_notifier.h"
 #include "counters.h"
 #include "addresses.h"
+#include "netlink.h"
 #include "trace.h"
 
 static struct class *mtk_dram_ecc_class;
@@ -37,10 +38,14 @@ static int __init mtk_dram_ecc_init(void)
 		goto err_class;
 	}
 
-	ret = mtk_dram_ecc_policy_init(mtk_dram_ecc_dev);
+	ret = mtk_dram_ecc_netlink_init();
 	if (ret)
 		goto err_dev;
 
+	ret = mtk_dram_ecc_policy_init(mtk_dram_ecc_dev);
+	if (ret)
+		goto err_netlink;
+
 	ret = mtk_dram_ecc_counters_init(&mtk_dram_ecc_dev->kobj);
 	if (ret)
 		goto err_policy;
@@ -68,6 +73,8 @@ static int __init mtk_dram_ecc_init(void)
 	mtk_dram_ecc_counters_exit();
 err_policy:
 	mtk_dram_ecc_policy_exit(mtk_dram_ecc_dev);
+err_netlink:
+	mtk_dram_ecc_netlink_exit();
 err_dev:
 	device_destroy(mtk_dram_ecc_class, MKDEV(0, 0));
 err_class:
@@ -86,6 +93,7 @@ static void __exit mtk_dram_ecc_exit(void)
 	mtk_dram_ecc_addresses_exit();
 	mtk_dram_ecc_counters_exit();
 	mtk_dram_ecc_policy_exit(mtk_dram_ecc_dev);
+	mtk_dram_ecc_netlink_exit();
 	device_destroy(mtk_dram_ecc_class, MKDEV(0, 0));
 	class_destroy(mtk_dram_ecc_class);
 	trace_mtk_dram_ecc_lifecycle(1);
diff --git a/drivers/soc/mediatek/dram-ecc/core/netlink.c b/drivers/soc/mediatek/dram-ecc/core/netlink.c
new file mode 100644
index 000000000000..bad96bbe4ed6
--- /dev/null
+++ b/drivers/soc/mediatek/dram-ecc/core/netlink.c
@@ -0,0 +1,83 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2026 MediaTek Inc.
+ */
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/netlink.h>
+#include <linux/printk.h>
+#include <net/genetlink.h>
+#include <uapi/linux/mtk_dram_ecc_netlink.h>
+
+#include "mtk_dram_ecc_event.h"
+#include "netlink.h"
+
+static const struct genl_multicast_group mtk_dram_ecc_mcgrps[] = {
+	{ .name = MTK_DRAM_ECC_MCAST_GRP },
+};
+
+static struct genl_family mtk_dram_ecc_genl_family = {
+	.name      = MTK_DRAM_ECC_GENL_NAME,
+	.version   = MTK_DRAM_ECC_GENL_VERSION,
+	.maxattr   = MTK_DRAM_ECC_ATTR_MAX,
+	.mcgrps    = mtk_dram_ecc_mcgrps,
+	.n_mcgrps  = ARRAY_SIZE(mtk_dram_ecc_mcgrps),
+	.module    = THIS_MODULE,
+};
+
+int mtk_dram_ecc_netlink_init(void)
+{
+	return genl_register_family(&mtk_dram_ecc_genl_family);
+}
+
+void mtk_dram_ecc_netlink_exit(void)
+{
+	genl_unregister_family(&mtk_dram_ecc_genl_family);
+}
+
+void mtk_dram_ecc_netlink_notify(const struct mtk_dram_ecc_event *evt)
+{
+	struct sk_buff *skb;
+	void *hdr;
+	int ret;
+	u8 severity;
+
+	skb = genlmsg_new(NLMSG_GOODSIZE, GFP_ATOMIC);
+	if (!skb) {
+		pr_warn_ratelimited("mtk-dram-ecc: failed to allocate netlink event for spa=0x%llx\n",
+				    evt->spa);
+		return;
+	}
+
+	hdr = genlmsg_put(skb, 0, 0, &mtk_dram_ecc_genl_family, 0,
+			  MTK_DRAM_ECC_CMD_EVENT);
+	if (!hdr)
+		goto err;
+
+	severity = (evt->severity == MTK_DRAM_ECC_NOTIFY_CE) ?
+		   MTK_DRAM_ECC_SEV_CE : MTK_DRAM_ECC_SEV_UCE;
+
+	if (nla_put_u64_64bit(skb, MTK_DRAM_ECC_ATTR_SPA, evt->spa,
+			      MTK_DRAM_ECC_ATTR_UNSPEC) ||
+	    nla_put_u8(skb, MTK_DRAM_ECC_ATTR_SEVERITY, severity) ||
+	    nla_put_u8(skb, MTK_DRAM_ECC_ATTR_CHANNEL, evt->channel) ||
+	    nla_put_u8(skb, MTK_DRAM_ECC_ATTR_SUBCHANNEL, evt->subchannel) ||
+	    nla_put_u16(skb, MTK_DRAM_ECC_ATTR_RANK, evt->rank) ||
+	    nla_put_u16(skb, MTK_DRAM_ECC_ATTR_BANK, evt->bank) ||
+	    nla_put_u32(skb, MTK_DRAM_ECC_ATTR_ROW, evt->row) ||
+	    nla_put_u16(skb, MTK_DRAM_ECC_ATTR_COLUMN, evt->column))
+		goto err;
+
+	genlmsg_end(skb, hdr);
+	ret = genlmsg_multicast(&mtk_dram_ecc_genl_family, skb, 0, 0,
+				GFP_ATOMIC);
+	if (ret && ret != -ESRCH)
+		pr_warn_ratelimited("mtk-dram-ecc: failed to publish netlink event for spa=0x%llx: %d\n",
+				    evt->spa, ret);
+	return;
+
+err:
+	pr_warn_ratelimited("mtk-dram-ecc: failed to build netlink event for spa=0x%llx\n",
+			    evt->spa);
+	nlmsg_free(skb);
+}
diff --git a/drivers/soc/mediatek/dram-ecc/core/netlink.h b/drivers/soc/mediatek/dram-ecc/core/netlink.h
new file mode 100644
index 000000000000..a2e64789d7d7
--- /dev/null
+++ b/drivers/soc/mediatek/dram-ecc/core/netlink.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2026 MediaTek Inc.
+ */
+#ifndef _MTK_DRAM_ECC_NETLINK_H_
+#define _MTK_DRAM_ECC_NETLINK_H_
+
+#include "mtk_dram_ecc_event.h"
+
+int mtk_dram_ecc_netlink_init(void);
+void mtk_dram_ecc_netlink_exit(void);
+void mtk_dram_ecc_netlink_notify(const struct mtk_dram_ecc_event *evt);
+
+#endif /* _MTK_DRAM_ECC_NETLINK_H_ */
diff --git a/include/uapi/linux/mtk_dram_ecc_netlink.h b/include/uapi/linux/mtk_dram_ecc_netlink.h
new file mode 100644
index 000000000000..8b4a77722bdb
--- /dev/null
+++ b/include/uapi/linux/mtk_dram_ecc_netlink.h
@@ -0,0 +1,44 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/*
+ * Copyright (c) 2026 MediaTek Inc.
+ */
+#ifndef _UAPI_MTK_DRAM_ECC_NETLINK_H_
+#define _UAPI_MTK_DRAM_ECC_NETLINK_H_
+
+#define MTK_DRAM_ECC_GENL_NAME    "mtk_dram_ecc"
+#define MTK_DRAM_ECC_GENL_VERSION 1
+#define MTK_DRAM_ECC_MCAST_GRP    "events"
+
+/* Commands */
+enum mtk_dram_ecc_cmd {
+	MTK_DRAM_ECC_CMD_UNSPEC,
+	MTK_DRAM_ECC_CMD_EVENT,
+	__MTK_DRAM_ECC_CMD_MAX,
+};
+
+#define MTK_DRAM_ECC_CMD_MAX (__MTK_DRAM_ECC_CMD_MAX - 1)
+
+/*
+ * Attributes. rank, bank, row and column are the DRAM controller address
+ * fields of the error.
+ */
+enum mtk_dram_ecc_attr {
+	MTK_DRAM_ECC_ATTR_UNSPEC,
+	MTK_DRAM_ECC_ATTR_SPA,       /* u64 - System Physical Address */
+	MTK_DRAM_ECC_ATTR_SEVERITY,  /* u8  - 0=CE, 1=UCE */
+	MTK_DRAM_ECC_ATTR_CHANNEL,   /* u8  - DRAM channel index */
+	MTK_DRAM_ECC_ATTR_SUBCHANNEL, /* u8  - DRAM sub-channel, 0 or 1 */
+	MTK_DRAM_ECC_ATTR_RANK,      /* u16 - DRAMC rank address */
+	MTK_DRAM_ECC_ATTR_BANK,      /* u16 - DRAMC bank address */
+	MTK_DRAM_ECC_ATTR_ROW,       /* u32 - DRAMC row address */
+	MTK_DRAM_ECC_ATTR_COLUMN,    /* u16 - DRAMC column address */
+	__MTK_DRAM_ECC_ATTR_MAX,
+};
+
+#define MTK_DRAM_ECC_ATTR_MAX (__MTK_DRAM_ECC_ATTR_MAX - 1)
+
+/* Severity values for MTK_DRAM_ECC_ATTR_SEVERITY */
+#define MTK_DRAM_ECC_SEV_CE  0
+#define MTK_DRAM_ECC_SEV_UCE 1
+
+#endif /* _UAPI_MTK_DRAM_ECC_NETLINK_H_ */
-- 
2.45.2



^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [RFC PATCH 10/11] soc: mediatek: dram-ecc: report an uncorrectable error of the previous boot
  2026-11-02  9:23 [RFC PATCH 00/11] soc: mediatek: add a DRAM ECC error handling driver santino.wang
                   ` (8 preceding siblings ...)
  2026-11-02  9:23 ` [RFC PATCH 09/11] soc: mediatek: dram-ecc: add a generic netlink event interface santino.wang
@ 2026-11-02  9:23 ` santino.wang
  2026-11-02 13:44   ` 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
  11 siblings, 1 reply; 20+ messages in thread
From: santino.wang @ 2026-11-02  9:23 UTC (permalink / raw)
  To: Matthias Brugger, AngeloGioacchino Del Regno
  Cc: linux-mediatek, linux-arm-kernel, linux-kernel, light.chen,
	Fred-WY.Chen, arnab.layek, Santino Wang

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



^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [RFC PATCH 11/11] Documentation: ABI: add the sysfs attributes of the MediaTek DRAM ECC driver
  2026-11-02  9:23 [RFC PATCH 00/11] soc: mediatek: add a DRAM ECC error handling driver santino.wang
                   ` (9 preceding siblings ...)
  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  9:23 ` santino.wang
  2026-11-02 20:36 ` [RFC PATCH 00/11] soc: mediatek: add a DRAM ECC error handling driver Jonathan Cameron
  11 siblings, 0 replies; 20+ messages in thread
From: santino.wang @ 2026-11-02  9:23 UTC (permalink / raw)
  To: Matthias Brugger, AngeloGioacchino Del Regno
  Cc: linux-mediatek, linux-arm-kernel, linux-kernel, light.chen,
	Fred-WY.Chen, arnab.layek, Santino Wang

From: Santino Wang <santino.wang@mediatek.com>

Document the error counters, the retired page counters, the recorded
error addresses and the policy attributes that the driver adds under
its class device.

Signed-off-by: Santino Wang <santino.wang@mediatek.com>
---
 .../ABI/testing/sysfs-class-mtk-dram-ecc      | 182 ++++++++++++++++++
 MAINTAINERS                                   |   1 +
 2 files changed, 183 insertions(+)
 create mode 100644 Documentation/ABI/testing/sysfs-class-mtk-dram-ecc

diff --git a/Documentation/ABI/testing/sysfs-class-mtk-dram-ecc b/Documentation/ABI/testing/sysfs-class-mtk-dram-ecc
new file mode 100644
index 000000000000..7b31bd663c53
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-class-mtk-dram-ecc
@@ -0,0 +1,182 @@
+What:		/sys/class/mtk-dram-ecc/mtk-dram-ecc0/counters/volatile_ce
+		/sys/class/mtk-dram-ecc/mtk-dram-ecc0/counters/volatile_uce_inline
+		/sys/class/mtk-dram-ecc/mtk-dram-ecc0/counters/volatile_uce_ondie
+		/sys/class/mtk-dram-ecc/mtk-dram-ecc0/counters/volatile_uce
+Date:		November 2026
+KernelVersion:	7.4
+Contact:	Santino Wang <santino.wang@mediatek.com>
+Description:
+		Number of corrected (CE) and uncorrectable (UCE) DRAM ECC errors
+		reported since the system booted, as a decimal number.
+
+		Inline ECC detects every UCE, including one that originates in the
+		DRAM die, so volatile_uce_inline is the total number of UCEs and
+		volatile_uce is equal to it. volatile_uce_ondie counts the subset
+		that on-die ECC also flagged.
+
+		Access: Read
+
+What:		/sys/class/mtk-dram-ecc/mtk-dram-ecc0/counters/agg_ce
+		/sys/class/mtk-dram-ecc/mtk-dram-ecc0/counters/agg_uce_inline
+		/sys/class/mtk-dram-ecc/mtk-dram-ecc0/counters/agg_uce_ondie
+		/sys/class/mtk-dram-ecc/mtk-dram-ecc0/counters/agg_uce
+Date:		November 2026
+KernelVersion:	7.4
+Contact:	Santino Wang <santino.wang@mediatek.com>
+Description:
+		Number of corrected (CE) and uncorrectable (UCE) DRAM ECC errors
+		accumulated across boots, as a decimal number. The counters are kept
+		in EFI variables that the system firmware creates.
+
+		As for the volatile counters, agg_uce_inline is the total number of
+		UCEs, agg_uce is equal to it and agg_uce_ondie counts the subset that
+		on-die ECC also flagged.
+
+		Access: Read
+
+What:		/sys/class/mtk-dram-ecc/mtk-dram-ecc0/retired/by_ce
+		/sys/class/mtk-dram-ecc/mtk-dram-ecc0/retired/by_uce
+Date:		November 2026
+KernelVersion:	7.4
+Contact:	Santino Wang <santino.wang@mediatek.com>
+Description:
+		Number of pages retired since the system booted because of
+		corrected errors (by_ce) or uncorrectable errors (by_uce), as a
+		decimal number. Each page is counted once.
+
+		Access: Read
+
+What:		/sys/class/mtk-dram-ecc/mtk-dram-ecc0/addresses/current_boot_ce
+		/sys/class/mtk-dram-ecc/mtk-dram-ecc0/addresses/current_boot_uce
+Date:		November 2026
+KernelVersion:	7.4
+Contact:	Santino Wang <santino.wang@mediatek.com>
+Description:
+		System physical addresses of the corrected (current_boot_ce) and
+		uncorrectable (current_boot_uce) errors reported since the system
+		booted, one hexadecimal address per line. At most 256 addresses are
+		kept for each kind.
+
+		Access: Read
+
+What:		/sys/class/mtk-dram-ecc/mtk-dram-ecc0/policy/containment/enabled
+Date:		November 2026
+KernelVersion:	7.4
+Contact:	Santino Wang <santino.wang@mediatek.com>
+Description:
+		Whether the secure firmware handles and reports DRAM ECC errors.
+		While it is disabled, the secure firmware only clears the error
+		source and publishes no record. Writing the value sends it to
+		the secure firmware.
+
+		Access: Read, Write
+		Valid values: Represented as boolean
+
+What:		/sys/class/mtk-dram-ecc/mtk-dram-ecc0/policy/containment/default_severity
+Date:		November 2026
+KernelVersion:	7.4
+Contact:	Santino Wang <santino.wang@mediatek.com>
+Description:
+		Severity the secure firmware reports for a contained uncorrectable
+		error that is not covered by an entry of policy/address_range.
+		Writing the value sends it to the secure firmware.
+
+		Access: Read, Write
+		Valid values: "fatal" or "recoverable"
+
+What:		/sys/class/mtk-dram-ecc/mtk-dram-ecc0/policy/storm_protection/threshold
+Date:		November 2026
+KernelVersion:	7.4
+Contact:	Santino Wang <santino.wang@mediatek.com>
+Description:
+		Number of error interrupts within policy/storm_protection/time_window
+		at which the secure firmware stops reporting DRAM ECC interrupts, to
+		protect the system from an interrupt storm. Writing the value sends it
+		to the secure firmware.
+
+		Access: Read, Write
+		Valid values: Represented as unsigned integer
+
+What:		/sys/class/mtk-dram-ecc/mtk-dram-ecc0/policy/storm_protection/time_window
+Date:		November 2026
+KernelVersion:	7.4
+Contact:	Santino Wang <santino.wang@mediatek.com>
+Description:
+		Length of the window, in milliseconds, in which the secure firmware
+		counts error interrupts for policy/storm_protection/threshold.
+		Writing the value sends it to the secure firmware.
+
+		Access: Read, Write
+		Valid values: Represented as unsigned integer
+
+What:		/sys/class/mtk-dram-ecc/mtk-dram-ecc0/policy/storm_protection/cooldown
+Date:		November 2026
+KernelVersion:	7.4
+Contact:	Santino Wang <santino.wang@mediatek.com>
+Description:
+		Time, in milliseconds, after which the secure firmware reports DRAM
+		ECC interrupts again once storm protection has stopped them. Writing
+		the value sends it to the secure firmware.
+
+		Access: Read, Write
+		Valid values: Represented as unsigned integer
+
+What:		/sys/class/mtk-dram-ecc/mtk-dram-ecc0/policy/leaky_bucket/threshold
+Date:		November 2026
+KernelVersion:	7.4
+Contact:	Santino Wang <santino.wang@mediatek.com>
+Description:
+		Number of corrected errors on a page at which the page is retired.
+		Writing 0 restores the default of 100.
+
+		Access: Read, Write
+		Valid values: 0 to 10000
+
+What:		/sys/class/mtk-dram-ecc/mtk-dram-ecc0/policy/leaky_bucket/decay_interval_sec
+Date:		November 2026
+KernelVersion:	7.4
+Contact:	Santino Wang <santino.wang@mediatek.com>
+Description:
+		Interval, in seconds, at which the corrected error count of every
+		tracked page is decreased by one. Writing 0 restores the default of
+		3600.
+
+		Access: Read, Write
+		Valid values: 0 to 86400
+
+What:		/sys/class/mtk-dram-ecc/mtk-dram-ecc0/policy/leaky_bucket/max_tracked
+Date:		November 2026
+KernelVersion:	7.4
+Contact:	Santino Wang <santino.wang@mediatek.com>
+Description:
+		Maximum number of pages whose corrected error count is tracked at
+		the same time. Writing 0 restores the default of 16.
+
+		Access: Read, Write
+		Valid values: 0 to 16
+
+What:		/sys/class/mtk-dram-ecc/mtk-dram-ecc0/policy/address_range/entries
+Date:		November 2026
+KernelVersion:	7.4
+Contact:	Santino Wang <santino.wang@mediatek.com>
+Description:
+		Address ranges for which the secure firmware overrides the default
+		severity of an uncorrectable error. Reading lists the table held by
+		the secure firmware, one range per line, as
+		"<start> <end> <severity>" with hexadecimal addresses, where the end
+		address is exclusive.
+
+		Writing "<start> <end> <severity>" adds or updates one range. The
+		severity is "fatal" or "recoverable".
+
+		Access: Read, Write
+
+What:		/sys/class/mtk-dram-ecc/mtk-dram-ecc0/policy/address_range/clear
+Date:		November 2026
+KernelVersion:	7.4
+Contact:	Santino Wang <santino.wang@mediatek.com>
+Description:
+		Writing anything to this attribute removes every range from the
+		table of policy/address_range/entries.
+
+		Access: Write
diff --git a/MAINTAINERS b/MAINTAINERS
index e7d25be97f24..d1ab5806ad9c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -17032,6 +17032,7 @@ L:	linux-kernel@vger.kernel.org
 L:	linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
 L:	linux-mediatek@lists.infradead.org (moderated for non-subscribers)
 S:	Maintained
+F:	Documentation/ABI/testing/sysfs-class-mtk-dram-ecc
 F:	drivers/soc/mediatek/dram-ecc/
 F:	include/uapi/linux/mtk_dram_ecc_netlink.h
 
-- 
2.45.2



^ permalink raw reply related	[flat|nested] 20+ messages in thread

* Re: [RFC PATCH 01/11] soc: mediatek: dram-ecc: add the DRAM ECC driver skeleton
  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
  0 siblings, 0 replies; 20+ messages in thread
From: Krzysztof Kozlowski @ 2026-11-02 13:41 UTC (permalink / raw)
  To: santino.wang, Matthias Brugger, AngeloGioacchino Del Regno
  Cc: linux-mediatek, linux-arm-kernel, linux-kernel, light.chen,
	Fred-WY.Chen, arnab.layek

On 02/11/2026 10:23, santino.wang wrote:
> From: Santino Wang <santino.wang@mediatek.com>
> 
> On the MediaTek mt8992 SoC the secure firmware (ATF) owns the DRAM
> controller ECC interrupts and reports each corrected (CE) or
> uncorrectable (UCE) error to the OS as an ACPI GHES record. The
> following patches add the driver that consumes those records.
> 
> Add only the module skeleton here: the Kconfig entry, the Makefiles, a
> class device that the later patches attach their sysfs attributes to,
> and the MAINTAINERS entry.
> 
> Signed-off-by: Santino Wang <santino.wang@mediatek.com>
> ---
>  MAINTAINERS                                   |  9 ++++
>  drivers/soc/mediatek/Kconfig                  |  2 +
>  drivers/soc/mediatek/Makefile                 |  1 +
>  drivers/soc/mediatek/dram-ecc/Kconfig         | 16 ++++++
>  drivers/soc/mediatek/dram-ecc/Makefile        |  3 ++
>  drivers/soc/mediatek/dram-ecc/core/Makefile   |  6 +++
>  drivers/soc/mediatek/dram-ecc/core/main.c     | 53 +++++++++++++++++++
>  .../soc/mediatek/dram-ecc/core/mtk_dram_ecc.h | 24 +++++++++
>  8 files changed, 114 insertions(+)
>  create mode 100644 drivers/soc/mediatek/dram-ecc/Kconfig
>  create mode 100644 drivers/soc/mediatek/dram-ecc/Makefile
>  create mode 100644 drivers/soc/mediatek/dram-ecc/core/Makefile
>  create mode 100644 drivers/soc/mediatek/dram-ecc/core/main.c
>  create mode 100644 drivers/soc/mediatek/dram-ecc/core/mtk_dram_ecc.h
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index aaf2825ac945..e7d25be97f24 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -17026,6 +17026,15 @@ S:	Maintained
>  F:	Documentation/devicetree/bindings/dma/mediatek,*
>  F:	drivers/dma/mediatek/
>  
> +MEDIATEK DRAM ECC DRIVER
> +M:	Santino Wang <santino.wang@mediatek.com>
> +L:	linux-kernel@vger.kernel.org
> +L:	linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
> +L:	linux-mediatek@lists.infradead.org (moderated for non-subscribers)
> +S:	Maintained
> +F:	drivers/soc/mediatek/dram-ecc/
> +F:	include/uapi/linux/mtk_dram_ecc_netlink.h
> +
>  MEDIATEK ETHERNET DRIVER
>  M:	Felix Fietkau <nbd@nbd.name>
>  M:	Lorenzo Bianconi <lorenzo@kernel.org>
> diff --git a/drivers/soc/mediatek/Kconfig b/drivers/soc/mediatek/Kconfig
> index d7293977f06e..91aed7d29be0 100644
> --- a/drivers/soc/mediatek/Kconfig
> +++ b/drivers/soc/mediatek/Kconfig
> @@ -89,4 +89,6 @@ config MTK_SOCINFO
>  	  information about the SoC to the userspace including the
>  	  manufacturer name, marketing name and soc name.
>  
> +source "drivers/soc/mediatek/dram-ecc/Kconfig"
> +
>  endmenu
> diff --git a/drivers/soc/mediatek/Makefile b/drivers/soc/mediatek/Makefile
> index 0665573e3c4b..880342bed606 100644
> --- a/drivers/soc/mediatek/Makefile
> +++ b/drivers/soc/mediatek/Makefile
> @@ -9,3 +9,4 @@ obj-$(CONFIG_MTK_MMSYS) += mtk-mmsys.o
>  obj-$(CONFIG_MTK_MMSYS) += mtk-mutex.o
>  obj-$(CONFIG_MTK_SVS) += mtk-svs.o
>  obj-$(CONFIG_MTK_SOCINFO) += mtk-socinfo.o
> +obj-y += dram-ecc/
> diff --git a/drivers/soc/mediatek/dram-ecc/Kconfig b/drivers/soc/mediatek/dram-ecc/Kconfig
> new file mode 100644
> index 000000000000..c015c66635b2
> --- /dev/null
> +++ b/drivers/soc/mediatek/dram-ecc/Kconfig
> @@ -0,0 +1,16 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +#
> +# MediaTek DRAM ECC error handling driver
> +# See plat/mediatek/drivers/dram_ecc/ in ATF for the SMC handler side.
> +#
> +
> +# core/ -> mtk_dram_ecc.ko
> +config MTK_DRAM_ECC
> +	tristate "MediaTek DRAM ECC Error Handling Driver"
> +	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.

Why is EDAC-like driver outside of edac subsystem? drivers/soc/ is not a
dumping ground.

> +
> +static int __init mtk_dram_ecc_init(void)
> +{
> +	int ret;
> +
> +	mtk_dram_ecc_class = class_create("mtk-dram-ecc");
> +	if (IS_ERR(mtk_dram_ecc_class))
> +		return PTR_ERR(mtk_dram_ecc_class);
> +
> +	mtk_dram_ecc_dev = device_create(mtk_dram_ecc_class, NULL,
> +					 MKDEV(0, 0), &mtk_ecc_data,
> +					 "mtk-dram-ecc0");
> +	if (IS_ERR(mtk_dram_ecc_dev)) {
> +		ret = PTR_ERR(mtk_dram_ecc_dev);
> +		goto err_class;
> +	}
> +
> +	dev_info(mtk_dram_ecc_dev, "initialized\n");

No, that's not a code style which exists in upstream.

> +	return 0;
> +
> +err_class:
> +	class_destroy(mtk_dram_ecc_class);
> +	return ret;
> +}
> +
> +static void __exit mtk_dram_ecc_exit(void)
> +{
> +	device_destroy(mtk_dram_ecc_class, MKDEV(0, 0));
> +	class_destroy(mtk_dram_ecc_class);
> +}
> +
Best regards,
Krzysztof


^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [RFC PATCH 05/11] soc: mediatek: dram-ecc: add error counters and the address lists
  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
  0 siblings, 0 replies; 20+ messages in thread
From: Krzysztof Kozlowski @ 2026-11-02 13:42 UTC (permalink / raw)
  To: santino.wang, Matthias Brugger, AngeloGioacchino Del Regno
  Cc: linux-mediatek, linux-arm-kernel, linux-kernel, light.chen,
	Fred-WY.Chen, arnab.layek

On 02/11/2026 10:23, santino.wang wrote:
> +
> +static ssize_t by_ce_show(struct kobject *kobj,
> +			  struct kobj_attribute *attr, char *buf)
> +{
> +	return sysfs_emit(buf, "%llu\n", mtk_dram_ecc_get_retired_by_ce());
> +}
> +
> +static ssize_t by_uce_show(struct kobject *kobj,
> +			   struct kobj_attribute *attr, char *buf)
> +{
> +	return sysfs_emit(buf, "%llu\n", mtk_dram_ecc_get_retired_by_uce());
> +}
> +
> +static struct kobj_attribute retired_by_ce_attr = __ATTR_RO(by_ce);
> +static struct kobj_attribute retired_by_uce_attr = __ATTR_RO(by_uce);
> +
> +static struct attribute *retired_attrs[] = {
> +	&retired_by_ce_attr.attr,
> +	&retired_by_uce_attr.attr,
> +	NULL,
> +};
> +
> +static const struct attribute_group retired_group = {
> +	.attrs = retired_attrs,
> +};
> +
> +int mtk_dram_ecc_counters_init(struct kobject *parent)
> +{
> +	int ret;
> +
> +	counters_kobj = kobject_create_and_add("counters", parent);
> +	if (!counters_kobj)
> +		return -ENOMEM;
> +
> +	ret = sysfs_create_group(counters_kobj, &group);
> +	if (ret) {
> +		kobject_put(counters_kobj);
> +		return ret;
> +	}
> +

Sorry, but no. You do not get your own interface, especially
undocumented one. Use existing subsystems. Also, drivers/soc/ cannot
expose interface to user space.


Best regards,
Krzysztof


^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [RFC PATCH 10/11] soc: mediatek: dram-ecc: report an uncorrectable error of the previous boot
  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
  0 siblings, 0 replies; 20+ messages in thread
From: Krzysztof Kozlowski @ 2026-11-02 13:44 UTC (permalink / raw)
  To: santino.wang, Matthias Brugger, AngeloGioacchino Del Regno
  Cc: linux-mediatek, linux-arm-kernel, linux-kernel, light.chen,
	Fred-WY.Chen, arnab.layek

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


^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [RFC PATCH 03/11] soc: mediatek: dram-ecc: add tracepoints
  2026-11-02  9:23 ` [RFC PATCH 03/11] soc: mediatek: dram-ecc: add tracepoints santino.wang
@ 2026-11-02 19:57   ` Jonathan Cameron
  0 siblings, 0 replies; 20+ messages in thread
From: Jonathan Cameron @ 2026-11-02 19:57 UTC (permalink / raw)
  To: santino.wang
  Cc: Matthias Brugger, AngeloGioacchino Del Regno, linux-mediatek,
	linux-arm-kernel, linux-kernel, light.chen, Fred-WY.Chen,
	arnab.layek


> diff --git a/drivers/soc/mediatek/dram-ecc/core/trace.c b/drivers/soc/mediatek/dram-ecc/core/trace.c
> new file mode 100644
> index 000000000000..9a8b20e48069
> --- /dev/null
> +++ b/drivers/soc/mediatek/dram-ecc/core/trace.c
> @@ -0,0 +1,6 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (c) 2026 MediaTek Inc.
> + */
> +#define CREATE_TRACE_POINTS
> +#include "trace.h"
> diff --git a/drivers/soc/mediatek/dram-ecc/core/trace.h b/drivers/soc/mediatek/dram-ecc/core/trace.h
> new file mode 100644
> index 000000000000..d603fc38f0a7
> --- /dev/null
> +++ b/drivers/soc/mediatek/dram-ecc/core/trace.h
> @@ -0,0 +1,119 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright (c) 2026 MediaTek Inc.
> + */
> +#undef TRACE_SYSTEM
> +#define TRACE_SYSTEM mtk_dram_ecc
> +
> +#if !defined(_TRACE_MTK_DRAM_ECC_H) || defined(TRACE_HEADER_MULTI_READ)
> +#define _TRACE_MTK_DRAM_ECC_H
> +
> +#include <linux/tracepoint.h>
> +
> +/* driver lifecycle - state: 0=init, 1=exit */
> +
> +TRACE_EVENT(mtk_dram_ecc_lifecycle,
> +	TP_PROTO(int state),
> +	TP_ARGS(state),
> +	TP_STRUCT__entry(
> +		__field(int, state)
> +	),
> +	TP_fast_assign(
> +		__entry->state = state;
> +	),
> +	TP_printk("%s", __entry->state ? "driver removed" : "driver initialized")
> +);
> +
> +/*
> + * ECC events - spa/channel/subchannel/rank/bank/row/column decoded from the
> + * ECCERR_INFO0/1 entry that determined severity (see ghes_notifier.c),
> + * mirroring ATF dram_ecc/mt8992/fiq.c's decode. No agent/master ID: ATF's
> + * GHES vendor section doesn't compute them (see mtk_dram_ecc_event.h).
> + */
> +
> +TRACE_EVENT(mtk_dram_ecc_ce,

Why push these through a custom tracepoint?  This look very similar to what
is in the Memory Error Section 2 CPER.

> +	TP_PROTO(u64 spa, u8 channel, u8 subch, u16 rank, u16 bank, u32 row,
> +		 u16 column),
> +	TP_ARGS(spa, channel, subch, rank, bank, row, column),
> +	TP_STRUCT__entry(
> +		__field(u64, spa)
> +		__field(u8, channel)
> +		__field(u8, subch)
> +		__field(u16, rank)
> +		__field(u16, bank)
> +		__field(u32, row)
> +		__field(u16, column)
> +	),
> +	TP_fast_assign(
> +		__entry->spa     = spa;
> +		__entry->channel = channel;
> +		__entry->subch   = subch;
> +		__entry->rank    = rank;
> +		__entry->bank    = bank;
> +		__entry->row     = row;
> +		__entry->column  = column;
> +	),
> +	TP_printk("spa=0x%016llx ch=%u subch=%u rank=%u bank=%u row=%u column=%u",
> +		  __entry->spa, __entry->channel, __entry->subch,
> +		  __entry->rank, __entry->bank, __entry->row,
> +		  __entry->column)
> +);
> +
> +TRACE_EVENT(mtk_dram_ecc_uce,
> +	TP_PROTO(u64 spa, u8 channel, u8 subch, u16 rank, u16 bank, u32 row,
> +		 u16 column),
> +	TP_ARGS(spa, channel, subch, rank, bank, row, column),
> +	TP_STRUCT__entry(
> +		__field(u64, spa)
> +		__field(u8, channel)
> +		__field(u8, subch)
> +		__field(u16, rank)
> +		__field(u16, bank)
> +		__field(u32, row)
> +		__field(u16, column)
> +	),
> +	TP_fast_assign(
> +		__entry->spa     = spa;
> +		__entry->channel = channel;
> +		__entry->subch   = subch;
> +		__entry->rank    = rank;
> +		__entry->bank    = bank;
> +		__entry->row     = row;
> +		__entry->column  = column;
> +	),
> +	TP_printk("spa=0x%016llx ch=%u subch=%u rank=%u bank=%u row=%u column=%u",
> +		  __entry->spa, __entry->channel, __entry->subch,
> +		  __entry->rank, __entry->bank, __entry->row,
> +		  __entry->column)
> +);



^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [RFC PATCH 08/11] soc: mediatek: dram-ecc: add the corrected error leaky bucket
  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
  0 siblings, 0 replies; 20+ messages in thread
From: Jonathan Cameron @ 2026-11-02 20:19 UTC (permalink / raw)
  To: santino.wang
  Cc: Matthias Brugger, AngeloGioacchino Del Regno, linux-mediatek,
	linux-arm-kernel, linux-kernel, light.chen, Fred-WY.Chen,
	arnab.layek, Tony Luck, Borislav Petkov

On Mon, 2 Nov 2026 17:23:30 +0800
santino.wang <santino.wang@mediatek.com> wrote:

> From: Santino Wang <santino.wang@mediatek.com>
> 
> A page with repeated corrected errors is likely to fail. Count the
> corrected errors per page in a leaky bucket and retire the page with
> memory_failure() when the count reaches a threshold. The bucket decays
> over time, and its threshold, decay interval and the number of tracked
> pages are configurable through sysfs.

Other than registering on your own record (that replicates
the standard one as far as I can tell) what does this bring you over
the stuff that edac provides already or can be done easily in
userspace? (where in my view most policy related stuff should be!)
From my initial read I think this patch set needs a lot more
explanatory description and comparison with existing infrastructure.
Not to mention send it to the edac maintainers and mailing list.

Just so they are in the loop I've +cc Tony and Borislav

Jonathan

> 
> Signed-off-by: Santino Wang <santino.wang@mediatek.com>
> ---
>  drivers/soc/mediatek/dram-ecc/core/Makefile   |   1 +
>  .../mediatek/dram-ecc/core/ghes_notifier.c    |   2 +
>  .../dram-ecc/core/policy/leaky_bucket.c       | 277 ++++++++++++++++++
>  .../mediatek/dram-ecc/core/policy/policy.c    |   9 +-
>  .../mediatek/dram-ecc/core/policy/policy.h    |   4 +
>  5 files changed, 292 insertions(+), 1 deletion(-)
>  create mode 100644 drivers/soc/mediatek/dram-ecc/core/policy/leaky_bucket.c
> 
> diff --git a/drivers/soc/mediatek/dram-ecc/core/Makefile b/drivers/soc/mediatek/dram-ecc/core/Makefile
> index d8d878f321d4..7c973831c9ef 100644
> --- a/drivers/soc/mediatek/dram-ecc/core/Makefile
> +++ b/drivers/soc/mediatek/dram-ecc/core/Makefile
> @@ -12,6 +12,7 @@ mtk_dram_ecc-y := \
>  	policy/policy.o \
>  	policy/containment.o \
>  	policy/storm_protection.o \
> +	policy/leaky_bucket.o \
>  	policy/address_range.o
>  
>  CFLAGS_trace.o := -I$(src)
> diff --git a/drivers/soc/mediatek/dram-ecc/core/ghes_notifier.c b/drivers/soc/mediatek/dram-ecc/core/ghes_notifier.c
> index 633bf89e2231..a010ca652e86 100644
> --- a/drivers/soc/mediatek/dram-ecc/core/ghes_notifier.c
> +++ b/drivers/soc/mediatek/dram-ecc/core/ghes_notifier.c
> @@ -28,6 +28,7 @@
>  #include "mtk_dram_ecc_event.h"
>  #include "ghes_notifier.h"
>  #include "counters.h"
> +#include "policy/policy.h"
>  #include "trace.h"
>  
>  static ATOMIC_NOTIFIER_HEAD(mtk_dram_ecc_notifier_chain);
> @@ -249,6 +250,7 @@ static int mtk_ghes_vendor_cb(struct notifier_block *nb, unsigned long severity,
>  		spin_unlock(&addr_lock);
>  		trace_mtk_dram_ecc_ce(evt.spa, evt.channel, evt.subchannel, evt.rank,
>  				      evt.bank, evt.row, evt.column);
> +		leaky_bucket_hit(evt.spa);
>  		ret = atomic_notifier_call_chain(&mtk_dram_ecc_notifier_chain,
>  						 MTK_DRAM_ECC_NOTIFY_CE, &evt);
>  		if (ret == NOTIFY_BAD)
> diff --git a/drivers/soc/mediatek/dram-ecc/core/policy/leaky_bucket.c b/drivers/soc/mediatek/dram-ecc/core/policy/leaky_bucket.c
> new file mode 100644
> index 000000000000..a51793d53efb
> --- /dev/null
> +++ b/drivers/soc/mediatek/dram-ecc/core/policy/leaky_bucket.c
> @@ -0,0 +1,277 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (c) 2026 MediaTek Inc.
> + */
> +#include <linux/kobject.h>
> +#include <linux/sysfs.h>
> +#include <linux/string.h>
> +#include <linux/hashtable.h>
> +#include <linux/slab.h>
> +#include <linux/spinlock.h>
> +#include <linux/workqueue.h>
> +#include <linux/mm.h>
> +#include <linux/jiffies.h>
> +#include <linux/errno.h>
> +#include <linux/printk.h>
> +
> +#include "../counters.h"
> +#include "../mtk_dram_ecc.h"
> +#include "../trace.h"
> +#include "policy.h"
> +
> +/* ------------------------------------------------------------------ */
> +/* Per-page counter table                                               */
> +/* ------------------------------------------------------------------ */
> +
> +struct lb_entry {
> +	struct hlist_node node;
> +	u64 pfn;
> +	u32 count;
> +};
> +
> +#define LB_HASH_BITS 8   /* 256 buckets */
> +
> +static DECLARE_HASHTABLE(lb_table, LB_HASH_BITS);
> +static DEFINE_SPINLOCK(lb_lock);
> +static unsigned int lb_tracked;  /* number of entries currently in table */
> +
> +/* ------------------------------------------------------------------ */
> +/* Decay timer (delayed_work)                                           */
> +/* ------------------------------------------------------------------ */
> +
> +static struct delayed_work lb_decay_work;
> +
> +static void lb_decay_fn(struct work_struct *work)
> +{
> +	struct lb_entry *e;
> +	struct hlist_node *tmp;
> +	unsigned int bkt;
> +	bool reschedule = false;
> +
> +	spin_lock(&lb_lock);
> +	hash_for_each_safe(lb_table, bkt, tmp, e, node) {
> +		if (e->count > 0)
> +			e->count--;
> +		if (e->count == 0) {
> +			hash_del(&e->node);
> +			kfree(e);
> +			lb_tracked--;
> +		} else {
> +			reschedule = true;
> +		}
> +	}
> +	spin_unlock(&lb_lock);
> +
> +	if (reschedule)
> +		schedule_delayed_work(&lb_decay_work,
> +				      secs_to_jiffies(mtk_ecc_data.lb_decay_interval_sec));
> +}
> +
> +/* ------------------------------------------------------------------ */
> +/* leaky_bucket_hit - called from ghes_notifier.c on every CE event    */
> +/* ------------------------------------------------------------------ */
> +
> +void leaky_bucket_hit(u64 spa)
> +{
> +	u64 pfn = spa >> PAGE_SHIFT;
> +	struct lb_entry *e;
> +	bool found = false;
> +	bool start_timer = false;
> +	bool retired = false;
> +	u32 count = 0;
> +	int ret;
> +
> +	spin_lock(&lb_lock);
> +
> +	hash_for_each_possible(lb_table, e, node, pfn) {
> +		if (e->pfn == pfn) {
> +			e->count++;
> +			count = e->count;
> +			found = true;
> +			break;
> +		}
> +	}
> +
> +	if (!found) {
> +		if (lb_tracked < mtk_ecc_data.lb_max_tracked) {
> +			e = kzalloc_obj(*e, GFP_ATOMIC);
> +			if (e) {
> +				e->pfn   = pfn;
> +				e->count = 1;
> +				count    = 1;
> +				hash_add(lb_table, &e->node, pfn);
> +				lb_tracked++;
> +				/* start timer only when table was empty */
> +				if (lb_tracked == 1)
> +					start_timer = true;
> +			}
> +		}
> +	}
> +
> +	spin_unlock(&lb_lock);
> +
> +	if (start_timer)
> +		schedule_delayed_work(&lb_decay_work,
> +				      secs_to_jiffies(mtk_ecc_data.lb_decay_interval_sec));
> +
> +	if (count >= mtk_ecc_data.lb_threshold && mtk_ecc_data.lb_threshold > 0) {
> +		ret = memory_failure(pfn, 0);
> +		if (ret == 0 || ret == -EHWPOISON) {
> +			mtk_dram_ecc_record_retired_page(pfn,
> +							 MTK_DRAM_ECC_RETIRE_CE,
> +							 ret);
> +			retired = true;
> +		} else {
> +			pr_warn_ratelimited("mtk-dram-ecc: CE page isolation failed: pfn=0x%llx ret=%d\n",
> +					    pfn, ret);
> +		}
> +	}
> +
> +	trace_mtk_dram_ecc_page_retire(pfn, count, mtk_ecc_data.lb_threshold,
> +				       retired);
> +}
> +
> +/* ------------------------------------------------------------------ */
> +/* sysfs                                                                */
> +/* ------------------------------------------------------------------ */
> +
> +static struct kobject *leaky_bucket_kobj;
> +
> +#define MTK_ECC_LB_THRESHOLD_MAX		10000U
> +#define MTK_ECC_LB_DECAY_INTERVAL_MAX	86400U
> +#define MTK_ECC_LB_MAX_TRACKED_MAX	16U
> +
> +static ssize_t threshold_show(struct kobject *kobj, struct kobj_attribute *attr,
> +			      char *buf)
> +{
> +	return sysfs_emit(buf, "%u\n", mtk_ecc_data.lb_threshold);
> +}
> +
> +static ssize_t threshold_store(struct kobject *kobj, struct kobj_attribute *attr,
> +			       const char *buf, size_t count)
> +{
> +	u32 val;
> +	int ret;
> +
> +	ret = kstrtou32(buf, 0, &val);
> +	if (ret)
> +		return ret;
> +	if (val == 0)
> +		val = MTK_ECC_LB_THRESHOLD_DEFAULT;
> +	else if (val > MTK_ECC_LB_THRESHOLD_MAX)
> +		return -EINVAL;
> +
> +	mtk_ecc_data.lb_threshold = val;
> +	return count;
> +}
> +
> +static ssize_t decay_interval_sec_show(struct kobject *kobj,
> +				       struct kobj_attribute *attr, char *buf)
> +{
> +	return sysfs_emit(buf, "%u\n", mtk_ecc_data.lb_decay_interval_sec);
> +}
> +
> +static ssize_t decay_interval_sec_store(struct kobject *kobj,
> +					struct kobj_attribute *attr,
> +					const char *buf, size_t count)
> +{
> +	u32 val;
> +	int ret;
> +
> +	ret = kstrtou32(buf, 0, &val);
> +	if (ret)
> +		return ret;
> +	if (val == 0)
> +		val = MTK_ECC_LB_DECAY_INTERVAL_DEFAULT;
> +	else if (val > MTK_ECC_LB_DECAY_INTERVAL_MAX)
> +		return -EINVAL;
> +
> +	mtk_ecc_data.lb_decay_interval_sec = val;
> +	return count;
> +}
> +
> +static ssize_t max_tracked_show(struct kobject *kobj,
> +				struct kobj_attribute *attr, char *buf)
> +{
> +	return sysfs_emit(buf, "%u\n", mtk_ecc_data.lb_max_tracked);
> +}
> +
> +static ssize_t max_tracked_store(struct kobject *kobj,
> +				 struct kobj_attribute *attr,
> +				 const char *buf, size_t count)
> +{
> +	u32 val;
> +	int ret;
> +
> +	ret = kstrtou32(buf, 0, &val);
> +	if (ret)
> +		return ret;
> +	if (val == 0)
> +		val = MTK_ECC_LB_MAX_TRACKED_DEFAULT;
> +	else if (val > MTK_ECC_LB_MAX_TRACKED_MAX)
> +		return -EINVAL;
> +
> +	mtk_ecc_data.lb_max_tracked = val;
> +	return count;
> +}
> +
> +static struct kobj_attribute threshold_attr =
> +	__ATTR(threshold, 0644, threshold_show, threshold_store);
> +static struct kobj_attribute decay_interval_sec_attr =
> +	__ATTR(decay_interval_sec, 0644, decay_interval_sec_show, decay_interval_sec_store);
> +static struct kobj_attribute max_tracked_attr =
> +	__ATTR(max_tracked, 0644, max_tracked_show, max_tracked_store);
> +
> +static struct attribute *attrs[] = {
> +	&threshold_attr.attr,
> +	&decay_interval_sec_attr.attr,
> +	&max_tracked_attr.attr,
> +	NULL,
> +};
> +
> +static const struct attribute_group group = {
> +	.attrs = attrs,
> +};
> +
> +/* ------------------------------------------------------------------ */
> +/* init / exit                                                          */
> +/* ------------------------------------------------------------------ */
> +
> +int leaky_bucket_init(struct kobject *parent)
> +{
> +	int ret;
> +
> +	hash_init(lb_table);
> +	INIT_DELAYED_WORK(&lb_decay_work, lb_decay_fn);
> +
> +	leaky_bucket_kobj = kobject_create_and_add("leaky_bucket", parent);
> +	if (!leaky_bucket_kobj)
> +		return -ENOMEM;
> +
> +	ret = sysfs_create_group(leaky_bucket_kobj, &group);
> +	if (ret) {
> +		kobject_put(leaky_bucket_kobj);
> +		return ret;
> +	}
> +	return 0;
> +}
> +
> +void leaky_bucket_exit(struct kobject *parent)
> +{
> +	struct lb_entry *e;
> +	struct hlist_node *tmp;
> +	unsigned int bkt;
> +
> +	cancel_delayed_work_sync(&lb_decay_work);
> +
> +	spin_lock(&lb_lock);
> +	hash_for_each_safe(lb_table, bkt, tmp, e, node) {
> +		hash_del(&e->node);
> +		kfree(e);
> +	}
> +	lb_tracked = 0;
> +	spin_unlock(&lb_lock);
> +
> +	sysfs_remove_group(leaky_bucket_kobj, &group);
> +	kobject_put(leaky_bucket_kobj);
> +}
> diff --git a/drivers/soc/mediatek/dram-ecc/core/policy/policy.c b/drivers/soc/mediatek/dram-ecc/core/policy/policy.c
> index ecd0a430de6d..5a4181b8de7d 100644
> --- a/drivers/soc/mediatek/dram-ecc/core/policy/policy.c
> +++ b/drivers/soc/mediatek/dram-ecc/core/policy/policy.c
> @@ -26,12 +26,18 @@ int mtk_dram_ecc_policy_init(struct device *dev)
>  	if (ret)
>  		goto err_containment;
>  
> -	ret = address_range_init(policy_kobj);
> +	ret = leaky_bucket_init(policy_kobj);
>  	if (ret)
>  		goto err_storm;
>  
> +	ret = address_range_init(policy_kobj);
> +	if (ret)
> +		goto err_leaky_bucket;
> +
>  	return 0;
>  
> +err_leaky_bucket:
> +	leaky_bucket_exit(policy_kobj);
>  err_storm:
>  	storm_protection_exit(policy_kobj);
>  err_containment:
> @@ -44,6 +50,7 @@ int mtk_dram_ecc_policy_init(struct device *dev)
>  void mtk_dram_ecc_policy_exit(struct device *dev)
>  {
>  	address_range_exit(policy_kobj);
> +	leaky_bucket_exit(policy_kobj);
>  	storm_protection_exit(policy_kobj);
>  	containment_exit(policy_kobj);
>  	kobject_put(policy_kobj);
> diff --git a/drivers/soc/mediatek/dram-ecc/core/policy/policy.h b/drivers/soc/mediatek/dram-ecc/core/policy/policy.h
> index a535e2ce0e6d..c9926cbc11be 100644
> --- a/drivers/soc/mediatek/dram-ecc/core/policy/policy.h
> +++ b/drivers/soc/mediatek/dram-ecc/core/policy/policy.h
> @@ -13,6 +13,10 @@ void containment_exit(struct kobject *parent);
>  int storm_protection_init(struct kobject *parent);
>  void storm_protection_exit(struct kobject *parent);
>  
> +int leaky_bucket_init(struct kobject *parent);
> +void leaky_bucket_exit(struct kobject *parent);
> +void leaky_bucket_hit(u64 spa);
> +
>  int address_range_init(struct kobject *parent);
>  void address_range_exit(struct kobject *parent);
>  



^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [RFC PATCH 09/11] soc: mediatek: dram-ecc: add a generic netlink event interface
  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
  0 siblings, 0 replies; 20+ messages in thread
From: Jonathan Cameron @ 2026-11-02 20:20 UTC (permalink / raw)
  To: santino.wang
  Cc: Matthias Brugger, AngeloGioacchino Del Regno, linux-mediatek,
	linux-arm-kernel, linux-kernel, light.chen, Fred-WY.Chen,
	arnab.layek

On Mon, 2 Nov 2026 17:23:31 +0800
santino.wang <santino.wang@mediatek.com> wrote:

> From: Santino Wang <santino.wang@mediatek.com>
> 
> Multicast every decoded corrected and uncorrectable error to user
> space through a generic netlink family, so a monitoring daemon can
> subscribe to events instead of polling the counters.
> 
> Signed-off-by: Santino Wang <santino.wang@mediatek.com>
There is a long history of using tracepoints for this. Why netlink?

This patch needs a lot more background information.

Jonathan


^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [RFC PATCH 00/11] soc: mediatek: add a DRAM ECC error handling driver
  2026-11-02  9:23 [RFC PATCH 00/11] soc: mediatek: add a DRAM ECC error handling driver santino.wang
                   ` (10 preceding siblings ...)
  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 ` Jonathan Cameron
  2026-11-02 20:44   ` Jonathan Cameron
  11 siblings, 1 reply; 20+ messages in thread
From: Jonathan Cameron @ 2026-11-02 20:36 UTC (permalink / raw)
  To: santino.wang
  Cc: Matthias Brugger, AngeloGioacchino Del Regno, linux-mediatek,
	linux-arm-kernel, linux-kernel, light.chen, Fred-WY.Chen,
	arnab.layek, Tony Luck, Borislav Petkov, linux-edac

On Mon, 2 Nov 2026 17:23:22 +0800
santino.wang <santino.wang@mediatek.com> wrote:

> From: Santino Wang <santino.wang@mediatek.com>
> 
> On the MediaTek mt8992 SoC the DRAM controller (DRAMC) raises an interrupt for
> every corrected (CE) and uncorrectable (UCE) ECC error. The interrupt is
> owned by the secure firmware (Arm Trusted Firmware at EL3). It reads the
> DRAMC error registers, applies the error containment policy and the CE
> storm protection, and hands the error to the OS firmware-first as an ACPI
> GHES record in a buffer that UEFI describes in the HEST. The OS is
> notified with an edge interrupt that the DSDT delivers through a Generic
> Event Device and a Hardware Error Device (GSIV notification).

So far this all sounds standard for asynchronous error reporting.
I'm assuming you have a something else for synchronous consumption.
From a Linux side of things we just care that error record shows up
and is signaled via GSIV (with normal Generic Event Device and Hardware
Error Device in the DSDT I guess)

> 
> The record carries a standard Platform Memory Error section and a
> MediaTek vendor section with the raw DRAMC error registers.

I'd like some details here on what those raw registers are bringing
to the OS wrt to handling these errors. 

What extra info?  I see you have a subchannel in there, but Memory
Error Section 2 brings that.  So why isn't this being reported
as a standard CPER?

> This series
> adds the driver that consumes the vendor section through the existing
> ghes_register_vendor_record_notifier() interface. No change to the GHES
> core or to arm64 code is needed.

Given a lot here doesn't sound mediatek specific it feels like it
shouldn't necessarily be in the vendor defined path.  More discussion
of why you have gone that way would be help.

> 
> The driver
>  - decodes the most severe error of a record and keeps counters, also
>    across boots in EFI variables,

Interesting.  How does this compare with just making it a userspace problem?
Perhaps you can't do that for some reason but it would take a lot to
convince me that it makes sense to have an upstream solution that relies
on custom EFI variable path.

>  - isolates pages with memory_failure(): immediately for a recoverable
>    UCE on memory that Linux manages, and for a page whose corrected
>    error count reaches a threshold (leaky bucket); memory owned by a
>    device is only reported to its owner,

This sounds standard, so I'm missing why you need a vendor driver for it.
Please provides some more info.

>  - lets in-kernel clients and user space (generic netlink, tracepoints)
>    follow the events,

Why your own netlink for this?  These are memory errors so I'd expect
existing paths to be sufficient, or perhaps there to be a need to
extend those as Pierre is doing with the Memory Error Section 2 series.

>  - exposes the containment policy, the storm protection and per range
>    severity overrides in sysfs and pushes them to the secure firmware
>    through a vendor SMC call,

The pushing of policy down to firmware is possibly something that needs
a custom driver.  I need to think a bit more on that and check a few things.
Input from Borislav or Tony might help here.

>  - after a fatal UCE (GHES panic and reset), finds the error in the BERT
>    of the next boot and reports it through the mc_event tracepoint.

If this were using the standard records that should just work. What
is missing that you need?

> 
> The driver depends on firmware that is not part of this series: the
> secure firmware provides the SMC interface used by patch 2 and writes the
> vendor section decoded by patch 4. The firmware side (the TF-A and
> edk2-platforms changes for the mt8992 SoC, plus the preloader that keeps
> the error across the reset) is being developed in the vendor's BSP trees
> and is not published yet.
> 
> Open question for this RFC: is drivers/soc/mediatek the right place for a
> driver that is built on GHES vendor records and a vendor SMC interface, or
> would the maintainers prefer another location?

You already got a reply on that. Almost nothing should ever go in drivers/soc
and definitely not something RAS related. +CC linux-edac, Borislav and Tony.

Anyhow, always interesting to discuss expanding what we do for
memory error handling, so I'll be interested to see what other feedback
this series gets.

Thanks,

Jonathan

> 
> Testing: each patch builds with the arm64 defconfig plus
> CONFIG_MTK_DRAM_ECC=m on linux-next 20261030 (W=1, no warnings).
> The series has been tested pre-silicon on a Palladium emulation platform.
> 
> Santino Wang (11):
>   soc: mediatek: dram-ecc: add the DRAM ECC driver skeleton
>   soc: mediatek: dram-ecc: add the SMC interface to the secure firmware
>   soc: mediatek: dram-ecc: add tracepoints
>   soc: mediatek: dram-ecc: handle GHES vendor records from the firmware
>   soc: mediatek: dram-ecc: add error counters and the address lists
>   soc: mediatek: dram-ecc: keep error counters across boots
>   soc: mediatek: dram-ecc: add containment policy and storm protection
>   soc: mediatek: dram-ecc: add the corrected error leaky bucket
>   soc: mediatek: dram-ecc: add a generic netlink event interface
>   soc: mediatek: dram-ecc: report an uncorrectable error of the previous
>     boot
>   Documentation: ABI: add the sysfs attributes of the MediaTek DRAM ECC
>     driver
> 
>  .../ABI/testing/sysfs-class-mtk-dram-ecc      | 182 +++++++++
>  MAINTAINERS                                   |  10 +
>  drivers/soc/mediatek/Kconfig                  |   2 +
>  drivers/soc/mediatek/Makefile                 |   1 +
>  drivers/soc/mediatek/dram-ecc/Kconfig         |  19 +
>  drivers/soc/mediatek/dram-ecc/Makefile        |   3 +
>  drivers/soc/mediatek/dram-ecc/core/Makefile   |  20 +
>  .../soc/mediatek/dram-ecc/core/addresses.c    |  88 ++++
>  .../soc/mediatek/dram-ecc/core/addresses.h    |  13 +
>  .../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 | 336 ++++++++++++++++
>  drivers/soc/mediatek/dram-ecc/core/counters.h |  29 ++
>  .../mediatek/dram-ecc/core/ghes_notifier.c    | 377 ++++++++++++++++++
>  .../mediatek/dram-ecc/core/ghes_notifier.h    |  22 +
>  drivers/soc/mediatek/dram-ecc/core/main.c     | 113 ++++++
>  .../soc/mediatek/dram-ecc/core/mtk_dram_ecc.h |  27 ++
>  .../dram-ecc/core/mtk_dram_ecc_event.h        |  47 +++
>  drivers/soc/mediatek/dram-ecc/core/netlink.c  |  83 ++++
>  drivers/soc/mediatek/dram-ecc/core/netlink.h  |  14 +
>  .../dram-ecc/core/policy/address_range.c      | 120 ++++++
>  .../dram-ecc/core/policy/containment.c        | 107 +++++
>  .../dram-ecc/core/policy/leaky_bucket.c       | 277 +++++++++++++
>  .../mediatek/dram-ecc/core/policy/policy.c    |  57 +++
>  .../mediatek/dram-ecc/core/policy/policy.h    |  23 ++
>  .../dram-ecc/core/policy/storm_protection.c   | 137 +++++++
>  drivers/soc/mediatek/dram-ecc/core/smc.c      | 137 +++++++
>  drivers/soc/mediatek/dram-ecc/core/smc.h      |  55 +++
>  drivers/soc/mediatek/dram-ecc/core/trace.c    |   6 +
>  drivers/soc/mediatek/dram-ecc/core/trace.h    | 119 ++++++
>  include/uapi/linux/mtk_dram_ecc_netlink.h     |  44 ++
>  31 files changed, 2720 insertions(+)
>  create mode 100644 Documentation/ABI/testing/sysfs-class-mtk-dram-ecc
>  create mode 100644 drivers/soc/mediatek/dram-ecc/Kconfig
>  create mode 100644 drivers/soc/mediatek/dram-ecc/Makefile
>  create mode 100644 drivers/soc/mediatek/dram-ecc/core/Makefile
>  create mode 100644 drivers/soc/mediatek/dram-ecc/core/addresses.c
>  create mode 100644 drivers/soc/mediatek/dram-ecc/core/addresses.h
>  create mode 100644 drivers/soc/mediatek/dram-ecc/core/bert_check.c
>  create mode 100644 drivers/soc/mediatek/dram-ecc/core/bert_check.h
>  create mode 100644 drivers/soc/mediatek/dram-ecc/core/counters.c
>  create mode 100644 drivers/soc/mediatek/dram-ecc/core/counters.h
>  create mode 100644 drivers/soc/mediatek/dram-ecc/core/ghes_notifier.c
>  create mode 100644 drivers/soc/mediatek/dram-ecc/core/ghes_notifier.h
>  create mode 100644 drivers/soc/mediatek/dram-ecc/core/main.c
>  create mode 100644 drivers/soc/mediatek/dram-ecc/core/mtk_dram_ecc.h
>  create mode 100644 drivers/soc/mediatek/dram-ecc/core/mtk_dram_ecc_event.h
>  create mode 100644 drivers/soc/mediatek/dram-ecc/core/netlink.c
>  create mode 100644 drivers/soc/mediatek/dram-ecc/core/netlink.h
>  create mode 100644 drivers/soc/mediatek/dram-ecc/core/policy/address_range.c
>  create mode 100644 drivers/soc/mediatek/dram-ecc/core/policy/containment.c
>  create mode 100644 drivers/soc/mediatek/dram-ecc/core/policy/leaky_bucket.c
>  create mode 100644 drivers/soc/mediatek/dram-ecc/core/policy/policy.c
>  create mode 100644 drivers/soc/mediatek/dram-ecc/core/policy/policy.h
>  create mode 100644 drivers/soc/mediatek/dram-ecc/core/policy/storm_protection.c
>  create mode 100644 drivers/soc/mediatek/dram-ecc/core/smc.c
>  create mode 100644 drivers/soc/mediatek/dram-ecc/core/smc.h
>  create mode 100644 drivers/soc/mediatek/dram-ecc/core/trace.c
>  create mode 100644 drivers/soc/mediatek/dram-ecc/core/trace.h
>  create mode 100644 include/uapi/linux/mtk_dram_ecc_netlink.h
> 



^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [RFC PATCH 00/11] soc: mediatek: add a DRAM ECC error handling driver
  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
  0 siblings, 0 replies; 20+ messages in thread
From: Jonathan Cameron @ 2026-11-02 20:44 UTC (permalink / raw)
  To: santino.wang
  Cc: Matthias Brugger, AngeloGioacchino Del Regno, linux-mediatek,
	linux-arm-kernel, linux-kernel, light.chen, Fred-WY.Chen,
	arnab.layek, Tony Luck, Borislav Petkov, linux-edac,
	Mauro Carvalho Chehab

On Mon, 2 Nov 2026 12:36:42 -0800
Jonathan Cameron <jonathan.cameron@oss.qualcomm.com> wrote:

> On Mon, 2 Nov 2026 17:23:22 +0800
> santino.wang <santino.wang@mediatek.com> wrote:
> 

+CC Mauro as well as I realized I asked about userspace part and
he may have input on that as well as on the kernel side of things.

> > From: Santino Wang <santino.wang@mediatek.com>
> > 
> > On the MediaTek mt8992 SoC the DRAM controller (DRAMC) raises an interrupt for
> > every corrected (CE) and uncorrectable (UCE) ECC error. The interrupt is
> > owned by the secure firmware (Arm Trusted Firmware at EL3). It reads the
> > DRAMC error registers, applies the error containment policy and the CE
> > storm protection, and hands the error to the OS firmware-first as an ACPI
> > GHES record in a buffer that UEFI describes in the HEST. The OS is
> > notified with an edge interrupt that the DSDT delivers through a Generic
> > Event Device and a Hardware Error Device (GSIV notification).  
> 
> So far this all sounds standard for asynchronous error reporting.
> I'm assuming you have a something else for synchronous consumption.
> From a Linux side of things we just care that error record shows up
> and is signaled via GSIV (with normal Generic Event Device and Hardware
> Error Device in the DSDT I guess)
> 
> > 
> > The record carries a standard Platform Memory Error section and a
> > MediaTek vendor section with the raw DRAMC error registers.  
> 
> I'd like some details here on what those raw registers are bringing
> to the OS wrt to handling these errors. 
> 
> What extra info?  I see you have a subchannel in there, but Memory
> Error Section 2 brings that.  So why isn't this being reported
> as a standard CPER?
> 
> > This series
> > adds the driver that consumes the vendor section through the existing
> > ghes_register_vendor_record_notifier() interface. No change to the GHES
> > core or to arm64 code is needed.  
> 
> Given a lot here doesn't sound mediatek specific it feels like it
> shouldn't necessarily be in the vendor defined path.  More discussion
> of why you have gone that way would be help.
> 
> > 
> > The driver
> >  - decodes the most severe error of a record and keeps counters, also
> >    across boots in EFI variables,  
> 
> Interesting.  How does this compare with just making it a userspace problem?
> Perhaps you can't do that for some reason but it would take a lot to
> convince me that it makes sense to have an upstream solution that relies
> on custom EFI variable path.
> 
> >  - isolates pages with memory_failure(): immediately for a recoverable
> >    UCE on memory that Linux manages, and for a page whose corrected
> >    error count reaches a threshold (leaky bucket); memory owned by a
> >    device is only reported to its owner,  
> 
> This sounds standard, so I'm missing why you need a vendor driver for it.
> Please provides some more info.
> 
> >  - lets in-kernel clients and user space (generic netlink, tracepoints)
> >    follow the events,  
> 
> Why your own netlink for this?  These are memory errors so I'd expect
> existing paths to be sufficient, or perhaps there to be a need to
> extend those as Pierre is doing with the Memory Error Section 2 series.
> 
> >  - exposes the containment policy, the storm protection and per range
> >    severity overrides in sysfs and pushes them to the secure firmware
> >    through a vendor SMC call,  
> 
> The pushing of policy down to firmware is possibly something that needs
> a custom driver.  I need to think a bit more on that and check a few things.
> Input from Borislav or Tony might help here.
> 
> >  - after a fatal UCE (GHES panic and reset), finds the error in the BERT
> >    of the next boot and reports it through the mc_event tracepoint.  
> 
> If this were using the standard records that should just work. What
> is missing that you need?
> 
> > 
> > The driver depends on firmware that is not part of this series: the
> > secure firmware provides the SMC interface used by patch 2 and writes the
> > vendor section decoded by patch 4. The firmware side (the TF-A and
> > edk2-platforms changes for the mt8992 SoC, plus the preloader that keeps
> > the error across the reset) is being developed in the vendor's BSP trees
> > and is not published yet.
> > 
> > Open question for this RFC: is drivers/soc/mediatek the right place for a
> > driver that is built on GHES vendor records and a vendor SMC interface, or
> > would the maintainers prefer another location?  
> 
> You already got a reply on that. Almost nothing should ever go in drivers/soc
> and definitely not something RAS related. +CC linux-edac, Borislav and Tony.
> 
> Anyhow, always interesting to discuss expanding what we do for
> memory error handling, so I'll be interested to see what other feedback
> this series gets.
> 
> Thanks,
> 
> Jonathan
> 
> > 
> > Testing: each patch builds with the arm64 defconfig plus
> > CONFIG_MTK_DRAM_ECC=m on linux-next 20261030 (W=1, no warnings).
> > The series has been tested pre-silicon on a Palladium emulation platform.
> > 
> > Santino Wang (11):
> >   soc: mediatek: dram-ecc: add the DRAM ECC driver skeleton
> >   soc: mediatek: dram-ecc: add the SMC interface to the secure firmware
> >   soc: mediatek: dram-ecc: add tracepoints
> >   soc: mediatek: dram-ecc: handle GHES vendor records from the firmware
> >   soc: mediatek: dram-ecc: add error counters and the address lists
> >   soc: mediatek: dram-ecc: keep error counters across boots
> >   soc: mediatek: dram-ecc: add containment policy and storm protection
> >   soc: mediatek: dram-ecc: add the corrected error leaky bucket
> >   soc: mediatek: dram-ecc: add a generic netlink event interface
> >   soc: mediatek: dram-ecc: report an uncorrectable error of the previous
> >     boot
> >   Documentation: ABI: add the sysfs attributes of the MediaTek DRAM ECC
> >     driver
> > 
> >  .../ABI/testing/sysfs-class-mtk-dram-ecc      | 182 +++++++++
> >  MAINTAINERS                                   |  10 +
> >  drivers/soc/mediatek/Kconfig                  |   2 +
> >  drivers/soc/mediatek/Makefile                 |   1 +
> >  drivers/soc/mediatek/dram-ecc/Kconfig         |  19 +
> >  drivers/soc/mediatek/dram-ecc/Makefile        |   3 +
> >  drivers/soc/mediatek/dram-ecc/core/Makefile   |  20 +
> >  .../soc/mediatek/dram-ecc/core/addresses.c    |  88 ++++
> >  .../soc/mediatek/dram-ecc/core/addresses.h    |  13 +
> >  .../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 | 336 ++++++++++++++++
> >  drivers/soc/mediatek/dram-ecc/core/counters.h |  29 ++
> >  .../mediatek/dram-ecc/core/ghes_notifier.c    | 377 ++++++++++++++++++
> >  .../mediatek/dram-ecc/core/ghes_notifier.h    |  22 +
> >  drivers/soc/mediatek/dram-ecc/core/main.c     | 113 ++++++
> >  .../soc/mediatek/dram-ecc/core/mtk_dram_ecc.h |  27 ++
> >  .../dram-ecc/core/mtk_dram_ecc_event.h        |  47 +++
> >  drivers/soc/mediatek/dram-ecc/core/netlink.c  |  83 ++++
> >  drivers/soc/mediatek/dram-ecc/core/netlink.h  |  14 +
> >  .../dram-ecc/core/policy/address_range.c      | 120 ++++++
> >  .../dram-ecc/core/policy/containment.c        | 107 +++++
> >  .../dram-ecc/core/policy/leaky_bucket.c       | 277 +++++++++++++
> >  .../mediatek/dram-ecc/core/policy/policy.c    |  57 +++
> >  .../mediatek/dram-ecc/core/policy/policy.h    |  23 ++
> >  .../dram-ecc/core/policy/storm_protection.c   | 137 +++++++
> >  drivers/soc/mediatek/dram-ecc/core/smc.c      | 137 +++++++
> >  drivers/soc/mediatek/dram-ecc/core/smc.h      |  55 +++
> >  drivers/soc/mediatek/dram-ecc/core/trace.c    |   6 +
> >  drivers/soc/mediatek/dram-ecc/core/trace.h    | 119 ++++++
> >  include/uapi/linux/mtk_dram_ecc_netlink.h     |  44 ++
> >  31 files changed, 2720 insertions(+)
> >  create mode 100644 Documentation/ABI/testing/sysfs-class-mtk-dram-ecc
> >  create mode 100644 drivers/soc/mediatek/dram-ecc/Kconfig
> >  create mode 100644 drivers/soc/mediatek/dram-ecc/Makefile
> >  create mode 100644 drivers/soc/mediatek/dram-ecc/core/Makefile
> >  create mode 100644 drivers/soc/mediatek/dram-ecc/core/addresses.c
> >  create mode 100644 drivers/soc/mediatek/dram-ecc/core/addresses.h
> >  create mode 100644 drivers/soc/mediatek/dram-ecc/core/bert_check.c
> >  create mode 100644 drivers/soc/mediatek/dram-ecc/core/bert_check.h
> >  create mode 100644 drivers/soc/mediatek/dram-ecc/core/counters.c
> >  create mode 100644 drivers/soc/mediatek/dram-ecc/core/counters.h
> >  create mode 100644 drivers/soc/mediatek/dram-ecc/core/ghes_notifier.c
> >  create mode 100644 drivers/soc/mediatek/dram-ecc/core/ghes_notifier.h
> >  create mode 100644 drivers/soc/mediatek/dram-ecc/core/main.c
> >  create mode 100644 drivers/soc/mediatek/dram-ecc/core/mtk_dram_ecc.h
> >  create mode 100644 drivers/soc/mediatek/dram-ecc/core/mtk_dram_ecc_event.h
> >  create mode 100644 drivers/soc/mediatek/dram-ecc/core/netlink.c
> >  create mode 100644 drivers/soc/mediatek/dram-ecc/core/netlink.h
> >  create mode 100644 drivers/soc/mediatek/dram-ecc/core/policy/address_range.c
> >  create mode 100644 drivers/soc/mediatek/dram-ecc/core/policy/containment.c
> >  create mode 100644 drivers/soc/mediatek/dram-ecc/core/policy/leaky_bucket.c
> >  create mode 100644 drivers/soc/mediatek/dram-ecc/core/policy/policy.c
> >  create mode 100644 drivers/soc/mediatek/dram-ecc/core/policy/policy.h
> >  create mode 100644 drivers/soc/mediatek/dram-ecc/core/policy/storm_protection.c
> >  create mode 100644 drivers/soc/mediatek/dram-ecc/core/smc.c
> >  create mode 100644 drivers/soc/mediatek/dram-ecc/core/smc.h
> >  create mode 100644 drivers/soc/mediatek/dram-ecc/core/trace.c
> >  create mode 100644 drivers/soc/mediatek/dram-ecc/core/trace.h
> >  create mode 100644 include/uapi/linux/mtk_dram_ecc_netlink.h
> >   
> 



^ permalink raw reply	[flat|nested] 20+ messages in thread

end of thread, other threads:[~2026-11-02 20:44 UTC | newest]

Thread overview: 20+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
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
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

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox