mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: santino.wang <santino.wang@mediatek.com>
To: Matthias Brugger <matthias.bgg@gmail.com>,
	AngeloGioacchino Del Regno
	<angelogioacchino.delregno@collabora.com>
Cc: <linux-mediatek@lists.infradead.org>,
	<linux-arm-kernel@lists.infradead.org>,
	<linux-kernel@vger.kernel.org>, <light.chen@mediatek.com>,
	<Fred-WY.Chen@mediatek.com>, <arnab.layek@mediatek.com>,
	Santino Wang <santino.wang@mediatek.com>
Subject: [RFC PATCH 07/11] soc: mediatek: dram-ecc: add containment policy and storm protection
Date: Mon, 2 Nov 2026 17:23:29 +0800	[thread overview]
Message-ID: <20261102092334.138759-8-santino.wang@mediatek.com> (raw)
In-Reply-To: <20261102092334.138759-1-santino.wang@mediatek.com>

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


  parent reply	other threads:[~2026-11-02  9:25 UTC|newest]

Thread overview: 15+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-02  9:23 [RFC PATCH 00/11] soc: mediatek: add a DRAM ECC error handling driver santino.wang
2026-11-02  9:23 ` [RFC PATCH 01/11] soc: mediatek: dram-ecc: add the DRAM ECC driver skeleton santino.wang
2026-11-02 13:41   ` Krzysztof Kozlowski
2026-11-02  9:23 ` [RFC PATCH 02/11] soc: mediatek: dram-ecc: add the SMC interface to the secure firmware santino.wang
2026-11-02  9:23 ` [RFC PATCH 03/11] soc: mediatek: dram-ecc: add tracepoints santino.wang
2026-11-02  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 ` santino.wang [this message]
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 ` [RFC PATCH 09/11] soc: mediatek: dram-ecc: add a generic netlink event interface santino.wang
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

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261102092334.138759-8-santino.wang@mediatek.com \
    --to=santino.wang@mediatek.com \
    --cc=Fred-WY.Chen@mediatek.com \
    --cc=angelogioacchino.delregno@collabora.com \
    --cc=arnab.layek@mediatek.com \
    --cc=light.chen@mediatek.com \
    --cc=linux-arm-kernel@lists.infradead.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-mediatek@lists.infradead.org \
    --cc=matthias.bgg@gmail.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®