[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20250912150855.2901211-2-eugen.hristev@linaro.org>
Date: Fri, 12 Sep 2025 18:08:40 +0300
From: Eugen Hristev <eugen.hristev@...aro.org>
To: linux-arm-msm@...r.kernel.org,
linux-kernel@...r.kernel.org,
linux-mm@...ck.org,
tglx@...utronix.de,
andersson@...nel.org,
pmladek@...e.com,
rdunlap@...radead.org,
corbet@....net,
david@...hat.com,
mhocko@...e.com
Cc: tudor.ambarus@...aro.org,
mukesh.ojha@....qualcomm.com,
linux-arm-kernel@...ts.infradead.org,
linux-hardening@...r.kernel.org,
jonechou@...gle.com,
rostedt@...dmis.org,
linux-doc@...r.kernel.org,
devicetree@...r.kernel.org,
Eugen Hristev <eugen.hristev@...aro.org>
Subject: [RFC][PATCH v3 01/16] kmemdump: Introduce kmemdump
Kmemdump mechanism allows any driver to mark a specific memory area
for later dumping/debugging purpose, depending on the functionality
of the attached backend.
The backend would interface any hardware mechanism that will allow
dumping to complete regardless of the state of the kernel
(running, frozen, crashed, or any particular state).
Signed-off-by: Eugen Hristev <eugen.hristev@...aro.org>
---
MAINTAINERS | 6 ++
include/linux/kmemdump.h | 63 ++++++++++++
mm/Kconfig.debug | 2 +
mm/Makefile | 1 +
mm/kmemdump/Kconfig.debug | 14 +++
mm/kmemdump/Makefile | 3 +
mm/kmemdump/kmemdump.c | 202 ++++++++++++++++++++++++++++++++++++++
7 files changed, 291 insertions(+)
create mode 100644 include/linux/kmemdump.h
create mode 100644 mm/kmemdump/Kconfig.debug
create mode 100644 mm/kmemdump/Makefile
create mode 100644 mm/kmemdump/kmemdump.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 8cf4990a8ff6..1713cccefc91 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -13810,6 +13810,12 @@ L: linux-iio@...r.kernel.org
S: Supported
F: drivers/iio/accel/kionix-kx022a*
+KMEMDUMP
+M: Eugen Hristev <eugen.hristev@...aro.org>
+S: Maintained
+F: include/linux/kmemdump.h
+F: mm/kmemdump/kmemdump.c
+
KMEMLEAK
M: Catalin Marinas <catalin.marinas@....com>
S: Maintained
diff --git a/include/linux/kmemdump.h b/include/linux/kmemdump.h
new file mode 100644
index 000000000000..8e764bb2d8ac
--- /dev/null
+++ b/include/linux/kmemdump.h
@@ -0,0 +1,63 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef _KMEMDUMP_H
+#define _KMEMDUMP_H
+
+enum kmemdump_uid {
+ KMEMDUMP_ID_START = 0,
+ KMEMDUMP_ID_USER_START,
+ KMEMDUMP_ID_USER_END,
+ KMEMDUMP_ID_NO_ID,
+};
+
+#ifdef CONFIG_KMEMDUMP
+/**
+ * struct kmemdump_zone - region mark zone information
+ * @id: unique id for this zone
+ * @zone: pointer to the memory area for this zone
+ * @size: size of the memory area of this zone
+ */
+struct kmemdump_zone {
+ enum kmemdump_uid id;
+ void *zone;
+ size_t size;
+};
+
+#define KMEMDUMP_BACKEND_MAX_NAME 128
+/**
+ * struct kmemdump_backend - region mark backend information
+ * @name: the name of the backend
+ * @register_region: callback to register region in the backend
+ * @unregister_region: callback to unregister region in the backend
+ */
+struct kmemdump_backend {
+ char name[KMEMDUMP_BACKEND_MAX_NAME];
+ int (*register_region)(const struct kmemdump_backend *be,
+ enum kmemdump_uid uid, void *vaddr, size_t size);
+ int (*unregister_region)(const struct kmemdump_backend *be,
+ enum kmemdump_uid uid);
+};
+
+int kmemdump_register_backend(const struct kmemdump_backend *backend);
+void kmemdump_unregister_backend(const struct kmemdump_backend *backend);
+
+int kmemdump_register_id(enum kmemdump_uid id, void *zone, size_t size);
+
+#define kmemdump_register(...) \
+ kmemdump_register_id(KMEMDUMP_ID_NO_ID, __VA_ARGS__) \
+
+void kmemdump_unregister(enum kmemdump_uid id);
+#else
+static inline int kmemdump_register_id(enum kmemdump_uid uid, void *area,
+ size_t size)
+{
+ return 0;
+}
+
+#define kmemdump_register(...)
+
+static inline void kmemdump_unregister(enum kmemdump_uid id)
+{
+}
+#endif
+
+#endif
diff --git a/mm/Kconfig.debug b/mm/Kconfig.debug
index 32b65073d0cc..b6aad5cb09c1 100644
--- a/mm/Kconfig.debug
+++ b/mm/Kconfig.debug
@@ -309,3 +309,5 @@ config PER_VMA_LOCK_STATS
overhead in the page fault path.
If in doubt, say N.
+
+source "mm/kmemdump/Kconfig.debug"
diff --git a/mm/Makefile b/mm/Makefile
index 21abb3353550..ca1691dd8924 100644
--- a/mm/Makefile
+++ b/mm/Makefile
@@ -90,6 +90,7 @@ obj-$(CONFIG_MMU_NOTIFIER) += mmu_notifier.o
obj-$(CONFIG_KSM) += ksm.o
obj-$(CONFIG_PAGE_POISONING) += page_poison.o
obj-$(CONFIG_KASAN) += kasan/
+obj-$(CONFIG_KMEMDUMP) += kmemdump/
obj-$(CONFIG_KFENCE) += kfence/
obj-$(CONFIG_KMSAN) += kmsan/
obj-$(CONFIG_FAILSLAB) += failslab.o
diff --git a/mm/kmemdump/Kconfig.debug b/mm/kmemdump/Kconfig.debug
new file mode 100644
index 000000000000..5654180141c0
--- /dev/null
+++ b/mm/kmemdump/Kconfig.debug
@@ -0,0 +1,14 @@
+# SPDX-License-Identifier: GPL-2.0
+
+config KMEMDUMP
+ bool "KMEMDUMP: Allow the kernel to register memory regions for dumping purpose"
+ help
+ Kmemdump mechanism allows any driver to mark a specific memory area
+ for later dumping/debugging purpose, depending on the functionality
+ of the attached backend.
+ The backend would interface any hardware mechanism that will allow
+ dumping to complete regardless of the state of the kernel
+ (running, frozen, crashed, or any particular state).
+
+ Note that modules using this feature must be rebuilt if option
+ changes.
diff --git a/mm/kmemdump/Makefile b/mm/kmemdump/Makefile
new file mode 100644
index 000000000000..f5b917a6ef5e
--- /dev/null
+++ b/mm/kmemdump/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0
+
+obj-y += kmemdump.o
diff --git a/mm/kmemdump/kmemdump.c b/mm/kmemdump/kmemdump.c
new file mode 100644
index 000000000000..c016457620a4
--- /dev/null
+++ b/mm/kmemdump/kmemdump.c
@@ -0,0 +1,202 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/module.h>
+#include <linux/kmemdump.h>
+
+#define MAX_ZONES 201
+
+static int default_register_region(const struct kmemdump_backend *be,
+ enum kmemdump_uid id, void *area, size_t sz)
+{
+ return 0;
+}
+
+static int default_unregister_region(const struct kmemdump_backend *be,
+ enum kmemdump_uid id)
+{
+ return 0;
+}
+
+static const struct kmemdump_backend kmemdump_default_backend = {
+ .name = "default",
+ .register_region = default_register_region,
+ .unregister_region = default_unregister_region,
+};
+
+static const struct kmemdump_backend *backend = &kmemdump_default_backend;
+static DEFINE_MUTEX(kmemdump_lock);
+static struct kmemdump_zone kmemdump_zones[MAX_ZONES];
+
+/**
+ * kmemdump_register_id() - Register region into kmemdump with given ID.
+ * @req_id: Requested unique kmemdump_uid that identifies the region
+ * This can be KMEMDUMP_ID_NO_ID, in which case the function will
+ * find an unused ID and return it.
+ * @zone: pointer to the zone of memory
+ * @size: region size
+ *
+ * Return: On success, it returns the unique id for the region.
+ * On failure, it returns negative error value.
+ */
+int kmemdump_register_id(enum kmemdump_uid req_id, void *zone, size_t size)
+{
+ struct kmemdump_zone *z;
+ enum kmemdump_uid uid = req_id;
+ int ret;
+
+ if (uid < KMEMDUMP_ID_START)
+ return -EINVAL;
+
+ if (uid >= MAX_ZONES)
+ return -ENOSPC;
+
+ mutex_lock(&kmemdump_lock);
+
+ if (uid == KMEMDUMP_ID_NO_ID)
+ while (uid < MAX_ZONES) {
+ if (!kmemdump_zones[uid].id)
+ break;
+ uid++;
+ }
+
+ if (uid == MAX_ZONES) {
+ mutex_unlock(&kmemdump_lock);
+ return -ENOSPC;
+ }
+
+ z = &kmemdump_zones[uid];
+
+ if (z->id) {
+ mutex_unlock(&kmemdump_lock);
+ return -EALREADY;
+ }
+
+ ret = backend->register_region(backend, uid, zone, size);
+ if (ret) {
+ mutex_unlock(&kmemdump_lock);
+ return ret;
+ }
+
+ z->zone = zone;
+ z->size = size;
+ z->id = uid;
+
+ mutex_unlock(&kmemdump_lock);
+
+ return uid;
+}
+EXPORT_SYMBOL_GPL(kmemdump_register_id);
+
+/**
+ * kmemdump_unregister() - Unregister region from kmemdump.
+ * @id: unique id that was returned when this region was successfully
+ * registered initially.
+ *
+ * Return: None
+ */
+void kmemdump_unregister(enum kmemdump_uid id)
+{
+ struct kmemdump_zone *z = NULL;
+
+ mutex_lock(&kmemdump_lock);
+
+ z = &kmemdump_zones[id];
+ if (!z->id) {
+ mutex_unlock(&kmemdump_lock);
+ return;
+ }
+
+ backend->unregister_region(backend, z->id);
+
+ memset(z, 0, sizeof(*z));
+
+ mutex_unlock(&kmemdump_lock);
+}
+EXPORT_SYMBOL_GPL(kmemdump_unregister);
+
+/**
+ * kmemdump_register_backend() - Register a backend into kmemdump.
+ * @be: Pointer to a driver allocated backend. This backend must have
+ * two callbacks for registering and deregistering a zone from the
+ * backend.
+ *
+ * Only one backend is supported at a time.
+ *
+ * Return: On success, it returns 0, negative error value otherwise.
+ */
+int kmemdump_register_backend(const struct kmemdump_backend *be)
+{
+ enum kmemdump_uid uid;
+ int ret;
+
+ if (!be || !be->register_region || !be->unregister_region)
+ return -EINVAL;
+
+ mutex_lock(&kmemdump_lock);
+
+ /* Try to call the old backend for all existing regions */
+ for (uid = KMEMDUMP_ID_START; uid < MAX_ZONES; uid++)
+ if (kmemdump_zones[uid].id)
+ backend->unregister_region(backend,
+ kmemdump_zones[uid].id);
+
+ backend = be;
+ pr_debug("kmemdump backend %s registered successfully.\n",
+ backend->name);
+
+ /* Call the new backend for all existing regions */
+ for (uid = KMEMDUMP_ID_START; uid < MAX_ZONES; uid++) {
+ if (!kmemdump_zones[uid].id)
+ continue;
+ ret = backend->register_region(backend,
+ kmemdump_zones[uid].id,
+ kmemdump_zones[uid].zone,
+ kmemdump_zones[uid].size);
+ if (ret)
+ pr_debug("register region failed with %d\n", ret);
+ }
+
+ mutex_unlock(&kmemdump_lock);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(kmemdump_register_backend);
+
+/**
+ * kmemdump_unregister_backend() - Unregister the backend from kmemdump.
+ * @be: Pointer to a driver allocated backend. This backend must match
+ * the initially registered backend.
+ *
+ * Only one backend is supported at a time.
+ * Before deregistering, this will call the backend to unregister all the
+ * previously registered zones.
+ *
+ * Return: None
+ */
+void kmemdump_unregister_backend(const struct kmemdump_backend *be)
+{
+ enum kmemdump_uid uid;
+
+ mutex_lock(&kmemdump_lock);
+
+ if (backend != be) {
+ mutex_unlock(&kmemdump_lock);
+ return;
+ }
+
+ /* Try to call the old backend for all existing regions */
+ for (uid = KMEMDUMP_ID_START; uid < MAX_ZONES; uid++)
+ if (kmemdump_zones[uid].id)
+ backend->unregister_region(backend,
+ kmemdump_zones[uid].id);
+
+ pr_debug("kmemdump backend %s removed successfully.\n", be->name);
+
+ backend = &kmemdump_default_backend;
+
+ mutex_unlock(&kmemdump_lock);
+}
+EXPORT_SYMBOL_GPL(kmemdump_unregister_backend);
+
--
2.43.0
Powered by blists - more mailing lists