* [PATCH v5 1/7] power: Extend power_on_reason.h for upcoming PSCRR framework
2025-03-10 10:37 [PATCH v5 0/7] Introduction of PSCR Framework and Related Components Oleksij Rempel
@ 2025-03-10 10:37 ` Oleksij Rempel
2025-03-10 10:37 ` [PATCH v5 2/7] power: reset: Introduce PSCR Recording Framework for Non-Volatile Storage Oleksij Rempel
` (5 subsequent siblings)
6 siblings, 0 replies; 11+ messages in thread
From: Oleksij Rempel @ 2025-03-10 10:37 UTC (permalink / raw)
To: Sebastian Reichel, Srinivas Kandagatla
Cc: Oleksij Rempel, kernel, linux-kernel, Liam Girdwood, Mark Brown,
Rafael J. Wysocki, Daniel Lezcano, Zhang Rui, Lukasz Luba,
linux-pm, Søren Andersen
Prepare for the introduction of the Power State Change Reason Recorder
(PSCRR) framework by expanding the power_on_reason.h header. This
extension includes new power-on reasons:
- POWER_ON_REASON_OVER_CURRENT for over-current conditions.
- POWER_ON_REASON_REGULATOR_FAILURE for regulator failures.
- POWER_ON_REASON_OVERTEMPERATURE for over temperature situations.
Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
---
include/linux/power/power_on_reason.h | 3 +++
1 file changed, 3 insertions(+)
diff --git a/include/linux/power/power_on_reason.h b/include/linux/power/power_on_reason.h
index 95a1ec0c403c..f2446cece277 100644
--- a/include/linux/power/power_on_reason.h
+++ b/include/linux/power/power_on_reason.h
@@ -15,5 +15,8 @@
#define POWER_ON_REASON_XTAL_FAIL "crystal oscillator failure"
#define POWER_ON_REASON_BROWN_OUT "brown-out reset"
#define POWER_ON_REASON_UNKNOWN "unknown reason"
+#define POWER_ON_REASON_OVER_CURRENT "over current"
+#define POWER_ON_REASON_REGULATOR_FAILURE "regulator failure"
+#define POWER_ON_REASON_OVERTEMPERATURE "overtemperature"
#endif /* POWER_ON_REASON_H */
--
2.39.5
^ permalink raw reply related [flat|nested] 11+ messages in thread* [PATCH v5 2/7] power: reset: Introduce PSCR Recording Framework for Non-Volatile Storage
2025-03-10 10:37 [PATCH v5 0/7] Introduction of PSCR Framework and Related Components Oleksij Rempel
2025-03-10 10:37 ` [PATCH v5 1/7] power: Extend power_on_reason.h for upcoming PSCRR framework Oleksij Rempel
@ 2025-03-10 10:37 ` Oleksij Rempel
2025-03-10 10:37 ` [PATCH v5 3/7] nvmem: provide consumer access to cell size metrics Oleksij Rempel
` (4 subsequent siblings)
6 siblings, 0 replies; 11+ messages in thread
From: Oleksij Rempel @ 2025-03-10 10:37 UTC (permalink / raw)
To: Sebastian Reichel, Srinivas Kandagatla
Cc: Oleksij Rempel, kernel, linux-kernel, Liam Girdwood, Mark Brown,
Rafael J. Wysocki, Daniel Lezcano, Zhang Rui, Lukasz Luba,
linux-pm, Søren Andersen
This commit introduces the Power State Change Reasons Recording (PSCRR)
framework into the kernel. The framework is vital for systems where
PMICs or watchdogs cannot provide information on power state changes. It
stores reasons for system shutdowns and reboots, like under-voltage or
software-triggered events, in non-volatile hardware storage. This
approach is essential for postmortem analysis in scenarios where
traditional storage methods (block devices, RAM) are not feasible. The
framework aids bootloaders and early-stage system components in recovery
decision-making, although it does not cover resets caused by hardware
issues like system freezes or watchdog timeouts.
Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
---
drivers/power/reset/Kconfig | 19 ++
drivers/power/reset/Makefile | 1 +
drivers/power/reset/pscrr.c | 408 +++++++++++++++++++++++++++++++++++
include/linux/pscrr.h | 89 ++++++++
4 files changed, 517 insertions(+)
create mode 100644 drivers/power/reset/pscrr.c
create mode 100644 include/linux/pscrr.h
diff --git a/drivers/power/reset/Kconfig b/drivers/power/reset/Kconfig
index 60bf0ca64cf3..0f6aee415f1c 100644
--- a/drivers/power/reset/Kconfig
+++ b/drivers/power/reset/Kconfig
@@ -316,3 +316,22 @@ config POWER_MLXBF
This driver supports reset or low power mode handling for Mellanox BlueField.
endif
+
+menuconfig PSCRR
+ bool "Power State Change Reasons (PSCR) Recording Framework"
+ help
+ Enables the Power State Change Reasons (PSCR) Recording framework.
+
+ This framework is designed to store reasons for system shutdowns or
+ reboots, like under voltage or software-triggered events, in non-volatile
+ hardware storage. It is particularly useful for postmortem analysis, where
+ traditional storage methods (like block devices or RAM) are not feasible
+ due to immediate power-down requirements or insufficient power to retain
+ data.
+
+ This is useful for bootloaders or other early-stage system components to
+ make recovery decisions based on the last known system state. Note that it
+ does not cover hardware-induced resets like system freezes or watchdog
+ timeouts.
+
+ If unsure, say N.
diff --git a/drivers/power/reset/Makefile b/drivers/power/reset/Makefile
index 10782d32e1da..dbd6ae6b26a4 100644
--- a/drivers/power/reset/Makefile
+++ b/drivers/power/reset/Makefile
@@ -32,6 +32,7 @@ obj-$(CONFIG_POWER_RESET_KEYSTONE) += keystone-reset.o
obj-$(CONFIG_POWER_RESET_SYSCON) += syscon-reboot.o
obj-$(CONFIG_POWER_RESET_SYSCON_POWEROFF) += syscon-poweroff.o
obj-$(CONFIG_POWER_RESET_RMOBILE) += rmobile-reset.o
+obj-$(CONFIG_PSCRR) += pscrr.o
obj-$(CONFIG_REBOOT_MODE) += reboot-mode.o
obj-$(CONFIG_SYSCON_REBOOT_MODE) += syscon-reboot-mode.o
obj-$(CONFIG_POWER_RESET_SC27XX) += sc27xx-poweroff.o
diff --git a/drivers/power/reset/pscrr.c b/drivers/power/reset/pscrr.c
new file mode 100644
index 000000000000..2b2f0115184b
--- /dev/null
+++ b/drivers/power/reset/pscrr.c
@@ -0,0 +1,408 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * pscrr_core.c - Core Power State Change Reason Recording
+ *
+ * This framework provides a method for recording the cause of the last system
+ * reboot, particularly in scenarios where **hardware protection events** (e.g.,
+ * undervoltage, overcurrent, thermal shutdown) force an immediate reset. Unlike
+ * traditional logging mechanisms that rely on block storage (e.g., NAND, eMMC),
+ * PSCRR ensures shutdown reasons are preserved in a way that survives power
+ * loss for later analysis.
+ *
+ * Purpose:
+ * --------
+ * The primary goal of PSCRR is to help developers and system operators analyze
+ * real-world failures by identifying what conditions embedded devices
+ * experience in the field. By persisting power state change reasons across
+ * reboots, engineers can gain insight into why and how systems fail, enabling
+ * better debugging and long-term system improvements.
+ *
+ * At the time of developing this framework, no specific recovery strategies
+ * were designed. Instead, the focus is on reliable event recording to support
+ * future diagnostic and recovery efforts.
+ *
+ * Sysfs Interface:
+ * ----------------
+ * /sys/kernel/pscrr/reason - Read/write current power state change
+ * reason
+ * /sys/kernel/pscrr/reason_boot - Read-only last recorded reason from
+ * previous boot
+ *
+ * Why is this needed?
+ * --------------------
+ * Many embedded systems experience power-related faults where **safe shutdown
+ * of block storage (e.g., NAND, eMMC) is not possible**:
+ * - Undervoltage protection triggers a hard shutdown before data can be
+ * written.
+ * - eMMC/NAND cannot be safely updated during power failure.
+ *
+ * To ensure post-mortem analysis is possible, alternate non-volatile storage
+ * should be used, such as:
+ * - Battery-backed RTC scratchpad
+ * - EEPROM or small NVMEM regions
+ * - FRAM or other fast, low-power persistent memory
+ *
+ * How PSCRR Works:
+ * ----------------
+ * - A driver detects a problem (e.g., overtemperature) and calls:
+ * set_power_state_change_reason(PSCR_OVERTEMPERATURE).
+ * - Before reboot, PSCRR writes the reason to hardware storage
+ * via the backend's `.write_reason()` callback.
+ * - On the next boot, the stored reason is retrieved from persistent storage
+ * and exposed via `/sys/kernel/pscrr/reason_boot` for analysis.
+ * - Userspace can dynamically set `/sys/kernel/pscrr/reason` to
+ * update the shutdown reason before a reboot.
+ */
+
+#include <linux/kernel.h>
+#include <linux/kobject.h>
+#include <linux/module.h>
+#include <linux/notifier.h>
+#include <linux/power/power_on_reason.h>
+#include <linux/pscrr.h>
+#include <linux/reboot.h>
+#include <linux/slab.h>
+#include <linux/sysfs.h>
+
+struct pscrr_data {
+ struct notifier_block reboot_nb;
+ const struct pscrr_backend_ops *ops;
+
+ enum pscr_reason last_reason;
+ enum pscr_reason last_boot_reason;
+
+ /* Kobject for sysfs */
+ struct kobject *kobj;
+};
+
+static struct pscrr_data *g_pscrr;
+
+/**
+ * pscrr_reason_to_str - Converts a power state change reason enum to a string.
+ * @reason: The `pscr_reason` enum value to be converted.
+ *
+ * This function provides a human-readable string representation of the power
+ * state change reason, making it easier to interpret logs and debug messages.
+ *
+ * Return:
+ * - A string corresponding to the given `pscr_reason` value.
+ * - `"Invalid"` if the value is not recognized.
+ */
+static const char *pscrr_reason_to_str(enum pscr_reason reason)
+{
+ switch (reason) {
+ case PSCR_UNKNOWN:
+ return POWER_ON_REASON_UNKNOWN;
+ case PSCR_UNDER_VOLTAGE:
+ return POWER_ON_REASON_BROWN_OUT;
+ case PSCR_OVER_CURRENT:
+ return POWER_ON_REASON_OVER_CURRENT;
+ case PSCR_REGULATOR_FAILURE:
+ return POWER_ON_REASON_REGULATOR_FAILURE;
+ case PSCR_OVERTEMPERATURE:
+ return POWER_ON_REASON_OVERTEMPERATURE;
+ default:
+ return "Invalid";
+ }
+}
+
+/**
+ * pscrr_reboot_notifier - Stores the last power state change reason before
+ * reboot.
+ * @nb: Notifier block structure (unused in this function).
+ * @action: The type of reboot action (unused in this function).
+ * @unused: Unused parameter.
+ *
+ * This function is called when the system is about to reboot or shut down. It
+ * writes the last recorded power state change reason to persistent storage
+ * using the registered backend’s write_reason() function.
+ *
+ * If writing fails, an error message is logged, but the reboot sequence is
+ * not blocked. The function always returns `NOTIFY_OK` to ensure that the
+ * system can reboot safely even if the reason cannot be stored.
+ *
+ * Return:
+ * - `NOTIFY_OK` on success or failure, allowing reboot to proceed.
+ * - `NOTIFY_DONE` if the PSCRR subsystem is not initialized.
+ */
+static int pscrr_reboot_notifier(struct notifier_block *nb,
+ unsigned long action, void *unused)
+{
+ int ret;
+
+ if (!g_pscrr || !g_pscrr->ops || !g_pscrr->ops->write_reason)
+ return NOTIFY_DONE;
+
+ ret = g_pscrr->ops->write_reason(g_pscrr->last_reason);
+ if (ret) {
+ pr_err("PSCRR: Failed to store reason %d (%s) at reboot, err=%pe\n",
+ g_pscrr->last_reason,
+ pscrr_reason_to_str(g_pscrr->last_reason),
+ ERR_PTR(ret));
+ } else {
+ pr_info("PSCRR: Stored reason %d (%s) at reboot.\n",
+ g_pscrr->last_reason,
+ pscrr_reason_to_str(g_pscrr->last_reason));
+ }
+
+ /*
+ * Return NOTIFY_OK to allow reboot to proceed despite failure, in
+ * case there is any.
+ */
+ return NOTIFY_OK;
+}
+
+/**
+ * set_power_state_change_reason - Sets the power state change reason for
+ * reboot.
+ * @reason: The `pscr_reason` enum value indicating the reason for reboot.
+ *
+ * This function updates the last recorded power state change reason, which will
+ * be stored in persistent storage when the system reboots. It allows various
+ * subsystems (e.g., power management, thermal management) to indicate the cause
+ * of a system reset.
+ *
+ * The reason is only updated if the PSCRR core is initialized.
+ */
+void set_power_state_change_reason(enum pscr_reason reason)
+{
+ if (g_pscrr)
+ g_pscrr->last_reason = reason;
+}
+EXPORT_SYMBOL_GPL(set_power_state_change_reason);
+
+/*----------------------------------------------------------------------*/
+/* Sysfs Interface */
+/*----------------------------------------------------------------------*/
+
+/**
+ * reason_show - Retrieves the current power state change reason via sysfs.
+ * @kobj: Kernel object associated with this attribute (unused).
+ * @attr: The sysfs attribute being accessed (unused).
+ * @buf: Buffer to store the output string.
+ *
+ * This function is used to read the current power state change reason from
+ * the `/sys/kernel/pscrr/reason` sysfs entry.
+ *
+ * If the PSCRR subsystem is not initialized, the function returns a message
+ * indicating that no backend is registered.
+ *
+ * The returned value is formatted as an integer (`enum pscr_reason`) followed
+ * by a newline (`\n`) for compatibility with standard sysfs behavior.
+ *
+ * Return:
+ * - Number of bytes written to `buf` (formatted integer string).
+ * - `"No backend registered\n"` if the PSCRR subsystem is uninitialized.
+ */
+static ssize_t reason_show(struct kobject *kobj, struct kobj_attribute *attr,
+ char *buf)
+{
+ enum pscr_reason r;
+
+ if (!g_pscrr || !g_pscrr->ops)
+ return scnprintf(buf, PAGE_SIZE, "No backend registered\n");
+
+ /* If the backend can read from hardware, do so. Otherwise, use our cached value. */
+ if (g_pscrr->ops->read_reason) {
+ if (g_pscrr->ops->read_reason(&r) == 0) {
+ /* Also update our cached value for consistency */
+ g_pscrr->last_reason = r;
+ } else {
+ /* If read fails, fallback to cached. */
+ r = g_pscrr->last_reason;
+ }
+ } else {
+ r = g_pscrr->last_reason;
+ }
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n", r);
+}
+
+/**
+ * reason_store - Updates the current power state change reason via sysfs.
+ * @kobj: Kernel object associated with this attribute (unused).
+ * @attr: The sysfs attribute being modified (unused).
+ * @buf: User-provided input buffer containing the reason value.
+ * @count: Number of bytes written to the attribute.
+ *
+ * This function allows users to set the power state change reason through
+ * the `/sys/kernel/pscrr/reason` sysfs entry.
+ *
+ * If the reason is out of range, a warning is logged but the write is still
+ * attempted. If the backend write fails, an error is logged, and the function
+ * returns the error code.
+ *
+ * Return:
+ * - `count` on success (indicating the number of bytes processed).
+ * - `-ENODEV` if the PSCRR subsystem is not initialized.
+ * - Any other error code returned by the backend’s `write_reason()`.
+ */
+static ssize_t reason_store(struct kobject *kobj, struct kobj_attribute *attr,
+ const char *buf, size_t count)
+{
+ int ret;
+ long val;
+
+ if (!g_pscrr || !g_pscrr->ops || !g_pscrr->ops->write_reason)
+ return -ENODEV;
+
+ ret = kstrtol(buf, 0, &val);
+ if (ret)
+ return ret;
+
+ if (val < PSCR_UNKNOWN || val > PSCR_MAX_REASON)
+ /*
+ * Log a warning, but still attempt to write the value. In
+ * case the backend can handle it, we don't want to block it.
+ */
+ pr_warn("PSCRR: writing unknown reason %ld (out of range)\n",
+ val);
+
+ ret = g_pscrr->ops->write_reason((enum pscr_reason)val);
+ if (ret) {
+ pr_err("PSCRR: write_reason(%ld) failed, err=%d\n", val, ret);
+ return ret;
+ }
+
+ g_pscrr->last_reason = (enum pscr_reason)val;
+
+ return count; /* number of bytes consumed */
+}
+
+static struct kobj_attribute reason_attr = __ATTR(reason, 0664, reason_show,
+ reason_store);
+
+/**
+ * reason_boot_show - Retrieves the last recorded power state change reason.
+ * @kobj: Kernel object associated with this attribute (unused).
+ * @attr: The sysfs attribute being accessed (unused).
+ * @buf: Buffer to store the output string.
+ *
+ * This function provides access to the `/sys/kernel/pscrr/reason_boot` sysfs
+ * entry, which contains the last recorded power state change reason from the
+ * **previous boot**. The value is retrieved from `priv->last_boot_reason`,
+ * which is initialized at module load time by reading from persistent storage.
+ *
+ * If the PSCRR NVMEM backend (`priv`) is not initialized, the function returns
+ * `-ENODEV` to indicate that the value is unavailable.
+ *
+ * The returned value is formatted as an integer (`enum pscr_reason`) followed
+ * by a newline (`\n`) for sysfs compatibility.
+ *
+ * Return:
+ * - Number of bytes written to `buf` (formatted integer string).
+ * - `-ENODEV` if the PSCRR backend is not initialized.
+ */
+static ssize_t reason_boot_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ if (!g_pscrr)
+ return -ENODEV;
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n", g_pscrr->last_boot_reason);
+}
+
+static struct kobj_attribute reason_boot_attr =
+ __ATTR(reason_boot, 0444, reason_boot_show, NULL); /* Read-only */
+
+static struct attribute *pscrr_attrs[] = {
+ &reason_attr.attr,
+ &reason_boot_attr.attr,
+ NULL,
+};
+
+static struct attribute_group pscrr_attr_group = {
+ .attrs = pscrr_attrs,
+};
+
+int pscrr_core_init(const struct pscrr_backend_ops *ops)
+{
+ enum pscr_reason stored_val;
+ int err;
+
+ if (g_pscrr) {
+ pr_err("PSCRR: Core is already initialized!\n");
+ return -EBUSY;
+ }
+
+
+ if (!ops->read_reason || !ops->write_reason) {
+ pr_err("PSCRR: Backend must provide read and write callbacks\n");
+ return -EINVAL;
+ }
+
+ g_pscrr = kzalloc(sizeof(*g_pscrr), GFP_KERNEL);
+ if (!g_pscrr)
+ return -ENOMEM;
+
+ g_pscrr->ops = ops;
+ g_pscrr->last_reason = PSCR_UNKNOWN;
+ g_pscrr->last_boot_reason = PSCR_UNKNOWN;
+
+ err = ops->read_reason(&stored_val);
+ if (!err) {
+ g_pscrr->last_boot_reason = stored_val;
+ pr_info("PSCRR: Initial read_reason: %d (%s)\n",
+ stored_val, pscrr_reason_to_str(stored_val));
+ } else {
+ pr_warn("PSCRR: read_reason failed, err=%pe\n",
+ ERR_PTR(err));
+ }
+
+ /* Setup the reboot notifier */
+ g_pscrr->reboot_nb.notifier_call = pscrr_reboot_notifier;
+ err = register_reboot_notifier(&g_pscrr->reboot_nb);
+ if (err) {
+ pr_err("PSCRR: Failed to register reboot notifier, err=%pe\n",
+ ERR_PTR(err));
+ goto err_free;
+ }
+
+ /* Create a kobject and sysfs group under /sys/kernel/pscrr */
+ g_pscrr->kobj = kobject_create_and_add("pscrr", kernel_kobj);
+ if (!g_pscrr->kobj) {
+ pr_err("PSCRR: Failed to create /sys/kernel/pscrr\n");
+ err = -ENOMEM;
+ goto err_unreg_reboot;
+ }
+
+ err = sysfs_create_group(g_pscrr->kobj, &pscrr_attr_group);
+ if (err) {
+ pr_err("PSCRR: Failed to create sysfs group, err=%pe\n",
+ ERR_PTR(err));
+ goto err_kobj_put;
+ }
+
+ pr_info("PSCRR: initialized successfully.\n");
+ return 0;
+
+err_kobj_put:
+ kobject_put(g_pscrr->kobj);
+err_unreg_reboot:
+ unregister_reboot_notifier(&g_pscrr->reboot_nb);
+err_free:
+ kfree(g_pscrr);
+ g_pscrr = NULL;
+ return err;
+}
+EXPORT_SYMBOL_GPL(pscrr_core_init);
+
+void pscrr_core_exit(void)
+{
+ if (!g_pscrr)
+ return;
+
+ sysfs_remove_group(g_pscrr->kobj, &pscrr_attr_group);
+ kobject_put(g_pscrr->kobj);
+
+ unregister_reboot_notifier(&g_pscrr->reboot_nb);
+
+ kfree(g_pscrr);
+ g_pscrr = NULL;
+ pr_info("PSCRR: exited.\n");
+}
+EXPORT_SYMBOL_GPL(pscrr_core_exit);
+
+MODULE_AUTHOR("Oleksij Rempel <o.rempel@pengutronix.de>");
+MODULE_DESCRIPTION("Power State Change Reason Recording (PSCRR) core");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/pscrr.h b/include/linux/pscrr.h
new file mode 100644
index 000000000000..2b8f4f1d475b
--- /dev/null
+++ b/include/linux/pscrr.h
@@ -0,0 +1,89 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * pscrr.h - Public header for Power State Change Reason Recording (PSCRR).
+ */
+
+#ifndef __PSCRR_H__
+#define __PSCRR_H__
+
+/*
+ * enum pscr - Enumerates reasons for power state changes.
+ *
+ * This enum lists the various reasons why a power state change might
+ * occur in a system. Each value represents a specific condition that
+ * could trigger a change in power state, such as shutdown or reboot.
+ *
+ * PSCR_UNKNOWN: Represents an unknown or unspecified reason.
+ * PSCR_UNDER_VOLTAGE: Indicates a power state change due to under-voltage.
+ * PSCR_OVER_CURRENT: Indicates a power state change due to over-current.
+ * PSCR_REGULATOR_FAILURE: Indicates a failure in a voltage regulator.
+ * PSCR_OVERTEMPERATURE: Indicates an over-temperature condition.
+ */
+enum pscr_reason {
+ PSCR_UNKNOWN,
+ PSCR_UNDER_VOLTAGE,
+ PSCR_OVER_CURRENT,
+ PSCR_REGULATOR_FAILURE,
+ PSCR_OVERTEMPERATURE,
+ PSCR_REASON_COUNT,
+};
+
+#define PSCR_MAX_REASON (PSCR_REASON_COUNT - 1)
+
+/**
+ * struct pscrr_backend_ops - Backend operations for storing power state change
+ * reasons.
+ *
+ * This structure defines the interface for backend implementations that handle
+ * the persistent storage of power state change reasons. Different backends
+ * (e.g., NVMEM, EEPROM, battery-backed RAM) can implement these operations to
+ * store and retrieve shutdown reasons across reboots.
+ *
+ * @write_reason: Function pointer to store the specified `pscr_reason` in
+ * persistent storage. This function is called before a reboot
+ * to record the last power state change reason.
+ * @read_reason: Function pointer to retrieve the last stored `pscr_reason`
+ * from persistent storage. This function is called at boot to
+ * restore the shutdown reason.
+ */
+struct pscrr_backend_ops {
+ int (*write_reason)(enum pscr_reason reason);
+ int (*read_reason)(enum pscr_reason *reason);
+};
+
+/**
+ * pscrr_core_init - Initialize the PSCRR core with a given backend
+ * @ops: Backend operations that the core will call
+ *
+ * Return: 0 on success, negative error code on failure.
+ * The core sets up sysfs, registers reboot notifier, etc.
+ */
+int pscrr_core_init(const struct pscrr_backend_ops *ops);
+
+/**
+ * pscrr_core_exit - De-initialize the PSCRR core
+ *
+ * Unregisters the reboot notifier, removes the sysfs entries, etc.
+ * Should be called by the backend driver at removal/shutdown.
+ */
+void pscrr_core_exit(void);
+
+#if IS_ENABLED(CONFIG_PSCRR)
+
+/**
+ * set_power_state_change_reason - Record reason for next reboot/shutdown
+ * @reason: The enumerated reason code to record
+ *
+ * Other drivers (e.g. regulator, thermal) call this whenever they detect
+ * a condition that may lead to or cause a reboot.
+ */
+void set_power_state_change_reason(enum pscr_reason reason);
+
+#else
+
+static inline void set_power_state_change_reason(enum pscr_reason pscr)
+{
+}
+#endif
+
+#endif /* __PSCRR_H__ */
--
2.39.5
^ permalink raw reply related [flat|nested] 11+ messages in thread* [PATCH v5 3/7] nvmem: provide consumer access to cell size metrics
2025-03-10 10:37 [PATCH v5 0/7] Introduction of PSCR Framework and Related Components Oleksij Rempel
2025-03-10 10:37 ` [PATCH v5 1/7] power: Extend power_on_reason.h for upcoming PSCRR framework Oleksij Rempel
2025-03-10 10:37 ` [PATCH v5 2/7] power: reset: Introduce PSCR Recording Framework for Non-Volatile Storage Oleksij Rempel
@ 2025-03-10 10:37 ` Oleksij Rempel
2025-03-10 10:37 ` [PATCH v5 4/7] nvmem: add support for device and sysfs-based cell lookups Oleksij Rempel
` (3 subsequent siblings)
6 siblings, 0 replies; 11+ messages in thread
From: Oleksij Rempel @ 2025-03-10 10:37 UTC (permalink / raw)
To: Sebastian Reichel, Srinivas Kandagatla
Cc: Oleksij Rempel, kernel, linux-kernel, Liam Girdwood, Mark Brown,
Rafael J. Wysocki, Daniel Lezcano, Zhang Rui, Lukasz Luba,
linux-pm, Søren Andersen
Add nvmem_cell_get_size() function to provide access to cell size
metrics. In some cases we may get cell size less as consumer would
expect it. So, nvmem_cell_write() would fail with incorrect buffer size.
Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
---
drivers/nvmem/core.c | 25 +++++++++++++++++++++++++
include/linux/nvmem-consumer.h | 7 +++++++
2 files changed, 32 insertions(+)
diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c
index fff85bbf0ecd..928ac15f04a0 100644
--- a/drivers/nvmem/core.c
+++ b/drivers/nvmem/core.c
@@ -1828,6 +1828,31 @@ int nvmem_cell_write(struct nvmem_cell *cell, void *buf, size_t len)
EXPORT_SYMBOL_GPL(nvmem_cell_write);
+/**
+ * nvmem_cell_get_size() - Get the size of a given nvmem cell
+ * @cell: nvmem cell to be queried.
+ * @bytes: Pointer to store the size of the cell in bytes. Can be NULL.
+ * @bits: Pointer to store the size of the cell in bits. Can be NULL.
+ *
+ * Return: 0 on success or negative on failure.
+ */
+int nvmem_cell_get_size(struct nvmem_cell *cell, size_t *bytes, size_t *bits)
+{
+ struct nvmem_cell_entry *entry = cell->entry;
+
+ if (!entry->nvmem)
+ return -EINVAL;
+
+ if (bytes)
+ *bytes = entry->bytes;
+
+ if (bits)
+ *bits = entry->nbits;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(nvmem_cell_get_size);
+
static int nvmem_cell_read_common(struct device *dev, const char *cell_id,
void *val, size_t count)
{
diff --git a/include/linux/nvmem-consumer.h b/include/linux/nvmem-consumer.h
index 34c0e58dfa26..bcb0e17e415d 100644
--- a/include/linux/nvmem-consumer.h
+++ b/include/linux/nvmem-consumer.h
@@ -56,6 +56,7 @@ void nvmem_cell_put(struct nvmem_cell *cell);
void devm_nvmem_cell_put(struct device *dev, struct nvmem_cell *cell);
void *nvmem_cell_read(struct nvmem_cell *cell, size_t *len);
int nvmem_cell_write(struct nvmem_cell *cell, void *buf, size_t len);
+int nvmem_cell_get_size(struct nvmem_cell *cell, size_t *bytes, size_t *bits);
int nvmem_cell_read_u8(struct device *dev, const char *cell_id, u8 *val);
int nvmem_cell_read_u16(struct device *dev, const char *cell_id, u16 *val);
int nvmem_cell_read_u32(struct device *dev, const char *cell_id, u32 *val);
@@ -128,6 +129,12 @@ static inline int nvmem_cell_write(struct nvmem_cell *cell,
return -EOPNOTSUPP;
}
+static inline int nvmem_cell_get_size(struct nvmem_cell *cell, size_t *bytes,
+ size_t *bits)
+{
+ return -EOPNOTSUPP;
+}
+
static inline int nvmem_cell_read_u8(struct device *dev,
const char *cell_id, u8 *val)
{
--
2.39.5
^ permalink raw reply related [flat|nested] 11+ messages in thread* [PATCH v5 4/7] nvmem: add support for device and sysfs-based cell lookups
2025-03-10 10:37 [PATCH v5 0/7] Introduction of PSCR Framework and Related Components Oleksij Rempel
` (2 preceding siblings ...)
2025-03-10 10:37 ` [PATCH v5 3/7] nvmem: provide consumer access to cell size metrics Oleksij Rempel
@ 2025-03-10 10:37 ` Oleksij Rempel
2025-03-10 10:37 ` [PATCH v5 5/7] power: reset: add PSCR NVMEM Driver for Recording Power State Change Reasons Oleksij Rempel
` (2 subsequent siblings)
6 siblings, 0 replies; 11+ messages in thread
From: Oleksij Rempel @ 2025-03-10 10:37 UTC (permalink / raw)
To: Sebastian Reichel, Srinivas Kandagatla
Cc: Oleksij Rempel, kernel, linux-kernel, Liam Girdwood, Mark Brown,
Rafael J. Wysocki, Daniel Lezcano, Zhang Rui, Lukasz Luba,
linux-pm, Søren Andersen
Introduce new API functions to allow looking up NVMEM devices and cells
by name, enhancing flexibility in cases where devicetree-based lookup
is not available.
Key changes:
- Added `nvmem_device_get_by_name()`: Enables retrieving an NVMEM device by its
name for systems where direct device reference is needed.
- Added `nvmem_cell_get_by_sysfs_name()`: Allows retrieving an NVMEM cell based
on its sysfs-style name (e.g., "cell@offset,bits"), making it possible to
identify cells dynamically.
- Introduced `nvmem_find_cell_entry_by_sysfs_name()`: A helper function that
constructs sysfs-like names and searches for matching cell entries.
Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
---
changes v5:
- fix build we NVMEM=n
---
drivers/nvmem/core.c | 91 ++++++++++++++++++++++++++++++++++
include/linux/nvmem-consumer.h | 14 ++++++
2 files changed, 105 insertions(+)
diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c
index 928ac15f04a0..2ff0926dea23 100644
--- a/drivers/nvmem/core.c
+++ b/drivers/nvmem/core.c
@@ -1203,6 +1203,20 @@ struct nvmem_device *of_nvmem_device_get(struct device_node *np, const char *id)
EXPORT_SYMBOL_GPL(of_nvmem_device_get);
#endif
+/**
+ * nvmem_device_get_by_name - Look up an NVMEM device by its device name
+ * @name: String matching device name in the provider
+ *
+ * Return: A valid pointer to struct nvmem_device on success,
+ * or ERR_PTR(...) on failure. The caller must later call nvmem_device_put() to
+ * release the reference.
+ */
+struct nvmem_device *nvmem_device_get_by_name(const char *name)
+{
+ return __nvmem_device_get((void *)name, device_match_name);
+}
+EXPORT_SYMBOL_GPL(nvmem_device_get_by_name);
+
/**
* nvmem_device_get() - Get nvmem device from a given id
*
@@ -1516,6 +1530,83 @@ struct nvmem_cell *of_nvmem_cell_get(struct device_node *np, const char *id)
EXPORT_SYMBOL_GPL(of_nvmem_cell_get);
#endif
+/**
+ * nvmem_find_cell_entry_by_sysfs_name - Find an NVMEM cell entry by its sysfs
+ * name.
+ * @nvmem: The nvmem_device pointer where the cell is located.
+ * @sysfs_name: The full sysfs cell name, e.g. "mycell@0x100,8".
+ *
+ * This function constructs the sysfs-like name for each cell and compares it
+ * to @sysfs_name. If a match is found, the matching nvmem_cell_entry pointer
+ * is returned. This is analogous to nvmem_find_cell_entry_by_name(), except
+ * it matches on the sysfs naming convention used in the device's attributes.
+ *
+ * Return: Pointer to the matching nvmem_cell_entry on success, or NULL if no
+ * match is found.
+ */
+static struct nvmem_cell_entry *
+nvmem_find_cell_entry_by_sysfs_name(struct nvmem_device *nvmem,
+ const char *sysfs_name)
+{
+ struct nvmem_cell_entry *entry;
+ char *tmp;
+
+ mutex_lock(&nvmem_mutex);
+ list_for_each_entry(entry, &nvmem->cells, node) {
+ /* Reconstruct how the sysfs name is assigned */
+ tmp = kasprintf(GFP_KERNEL, "%s@%x,%u", entry->name,
+ entry->offset, entry->bit_offset);
+ if (!tmp)
+ continue;
+
+ if (!strcmp(tmp, sysfs_name)) {
+ kfree(tmp);
+ mutex_unlock(&nvmem_mutex);
+ return entry;
+ }
+ kfree(tmp);
+ }
+ mutex_unlock(&nvmem_mutex);
+
+ return NULL;
+}
+
+/**
+ * nvmem_cell_get_by_sysfs_name - Get a cell by its sysfs name from a given
+ * nvmem_device.
+ * @nvmem: The nvmem_device pointer where the cell is located.
+ * @sysfs_name: The sysfs-style name, e.g. "mycell@0x100,8".
+ *
+ * This function uses nvmem_find_cell_entry_by_sysfs_name() to locate the cell
+ * entry, increments the reference counts for the matching NVMEM device, and
+ * then creates a struct nvmem_cell for the caller.
+ *
+ * Return: On success, a valid pointer to an nvmem_cell. On failure, an
+ * ERR_PTR() encoded error (e.g., -ENOENT if the cell entry is not found).
+ */
+struct nvmem_cell *nvmem_cell_get_by_sysfs_name(struct nvmem_device *nvmem,
+ const char *sysfs_name)
+{
+ struct nvmem_cell_entry *entry;
+ struct nvmem_cell *cell;
+
+ entry = nvmem_find_cell_entry_by_sysfs_name(nvmem, sysfs_name);
+ if (!entry)
+ return ERR_PTR(-ENOENT);
+
+ if (!try_module_get(nvmem->owner))
+ return ERR_PTR(-EINVAL);
+
+ kref_get(&nvmem->refcnt);
+
+ cell = nvmem_create_cell(entry, entry->name, 0);
+ if (IS_ERR(cell))
+ __nvmem_device_put(nvmem);
+
+ return cell;
+}
+EXPORT_SYMBOL_GPL(nvmem_cell_get_by_sysfs_name);
+
/**
* nvmem_cell_get() - Get nvmem cell of device form a given cell name
*
diff --git a/include/linux/nvmem-consumer.h b/include/linux/nvmem-consumer.h
index bcb0e17e415d..574d9f45e955 100644
--- a/include/linux/nvmem-consumer.h
+++ b/include/linux/nvmem-consumer.h
@@ -52,6 +52,8 @@ enum {
/* Cell based interface */
struct nvmem_cell *nvmem_cell_get(struct device *dev, const char *id);
struct nvmem_cell *devm_nvmem_cell_get(struct device *dev, const char *id);
+struct nvmem_cell *nvmem_cell_get_by_sysfs_name(struct nvmem_device *nvmem,
+ const char *cell_name);
void nvmem_cell_put(struct nvmem_cell *cell);
void devm_nvmem_cell_put(struct device *dev, struct nvmem_cell *cell);
void *nvmem_cell_read(struct nvmem_cell *cell, size_t *len);
@@ -70,6 +72,7 @@ int nvmem_cell_read_variable_le_u64(struct device *dev, const char *cell_id,
struct nvmem_device *nvmem_device_get(struct device *dev, const char *name);
struct nvmem_device *devm_nvmem_device_get(struct device *dev,
const char *name);
+struct nvmem_device *nvmem_device_get_by_name(const char *name);
void nvmem_device_put(struct nvmem_device *nvmem);
void devm_nvmem_device_put(struct device *dev, struct nvmem_device *nvmem);
int nvmem_device_read(struct nvmem_device *nvmem, unsigned int offset,
@@ -109,6 +112,12 @@ static inline struct nvmem_cell *devm_nvmem_cell_get(struct device *dev,
return ERR_PTR(-EOPNOTSUPP);
}
+static inline struct nvmem_cell *
+nvmem_cell_get_by_sysfs_name(struct nvmem_device *nvmem, const char *cell_name)
+{
+ return ERR_PTR(-EOPNOTSUPP);
+}
+
static inline void devm_nvmem_cell_put(struct device *dev,
struct nvmem_cell *cell)
{
@@ -185,6 +194,11 @@ static inline struct nvmem_device *devm_nvmem_device_get(struct device *dev,
return ERR_PTR(-EOPNOTSUPP);
}
+static inline struct nvmem_device *nvmem_device_get_by_name(const char *name)
+{
+ return ERR_PTR(-EOPNOTSUPP);
+}
+
static inline void nvmem_device_put(struct nvmem_device *nvmem)
{
}
--
2.39.5
^ permalink raw reply related [flat|nested] 11+ messages in thread* [PATCH v5 5/7] power: reset: add PSCR NVMEM Driver for Recording Power State Change Reasons
2025-03-10 10:37 [PATCH v5 0/7] Introduction of PSCR Framework and Related Components Oleksij Rempel
` (3 preceding siblings ...)
2025-03-10 10:37 ` [PATCH v5 4/7] nvmem: add support for device and sysfs-based cell lookups Oleksij Rempel
@ 2025-03-10 10:37 ` Oleksij Rempel
2025-03-10 10:37 ` [PATCH v5 6/7] regulator: set Power State Change Reason before hw_protection_shutdown() Oleksij Rempel
2025-03-10 10:37 ` [PATCH v5 7/7] thermal: core: Record PSCR " Oleksij Rempel
6 siblings, 0 replies; 11+ messages in thread
From: Oleksij Rempel @ 2025-03-10 10:37 UTC (permalink / raw)
To: Sebastian Reichel, Srinivas Kandagatla
Cc: Oleksij Rempel, kernel, linux-kernel, Liam Girdwood, Mark Brown,
Rafael J. Wysocki, Daniel Lezcano, Zhang Rui, Lukasz Luba,
linux-pm, Søren Andersen
This driver utilizes the Power State Change Reasons Recording (PSCRR)
framework to store specific power state change information, such as
shutdown or reboot reasons, into a designated non-volatile memory
(NVMEM) cell.
Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
---
changes v5:
- avoid a build against NVMEM=m
changes v4:
- remove devicetree dependencies
---
drivers/power/reset/Kconfig | 11 ++
drivers/power/reset/Makefile | 1 +
drivers/power/reset/pscrr-nvmem.c | 254 ++++++++++++++++++++++++++++++
3 files changed, 266 insertions(+)
create mode 100644 drivers/power/reset/pscrr-nvmem.c
diff --git a/drivers/power/reset/Kconfig b/drivers/power/reset/Kconfig
index 0f6aee415f1c..a4e48ed01c20 100644
--- a/drivers/power/reset/Kconfig
+++ b/drivers/power/reset/Kconfig
@@ -335,3 +335,14 @@ menuconfig PSCRR
timeouts.
If unsure, say N.
+
+if PSCRR
+
+config PSCRR_NVMEM
+ tristate "Generic NVMEM-based Power State Change Reason Recorder"
+ depends on NVMEM || !NVMEM
+ help
+ Enabling this option adds support for recording power state change
+ reasons in a NVMEM cell.
+
+endif
diff --git a/drivers/power/reset/Makefile b/drivers/power/reset/Makefile
index dbd6ae6b26a4..532698552d40 100644
--- a/drivers/power/reset/Makefile
+++ b/drivers/power/reset/Makefile
@@ -33,6 +33,7 @@ obj-$(CONFIG_POWER_RESET_SYSCON) += syscon-reboot.o
obj-$(CONFIG_POWER_RESET_SYSCON_POWEROFF) += syscon-poweroff.o
obj-$(CONFIG_POWER_RESET_RMOBILE) += rmobile-reset.o
obj-$(CONFIG_PSCRR) += pscrr.o
+obj-$(CONFIG_PSCRR_NVMEM) += pscrr-nvmem.o
obj-$(CONFIG_REBOOT_MODE) += reboot-mode.o
obj-$(CONFIG_SYSCON_REBOOT_MODE) += syscon-reboot-mode.o
obj-$(CONFIG_POWER_RESET_SC27XX) += sc27xx-poweroff.o
diff --git a/drivers/power/reset/pscrr-nvmem.c b/drivers/power/reset/pscrr-nvmem.c
new file mode 100644
index 000000000000..c6912a1a4f7b
--- /dev/null
+++ b/drivers/power/reset/pscrr-nvmem.c
@@ -0,0 +1,254 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * pscrr_nvmem.c - PSCRR backend for storing shutdown reasons in small NVMEM
+ * cells
+ *
+ * This backend provides a way to persist power state change reasons in a
+ * non-volatile memory (NVMEM) cell, ensuring that reboot causes can be
+ * analyzed post-mortem. Unlike traditional logging to eMMC or NAND, which
+ * may be unreliable during power failures, this approach allows storing
+ * reboot reasons in small, fast-access storage like RTC scratchpads, EEPROM,
+ * or FRAM.
+ *
+ * The module allows dynamic configuration of the NVMEM device and cell
+ * via module parameters:
+ *
+ * Example usage:
+ * modprobe pscrr-nvmem nvmem_name=pcf85063_nvram0 cell_name=pscr@0,0
+ */
+
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/pscrr.h>
+#include <linux/slab.h>
+
+/*
+ * Module parameters:
+ * nvmem_name: Name of the NVMEM device (e.g. "pcf85063_nvram0").
+ * cell_name : Sysfs name of the cell on that device (e.g. "pscr@0,0").
+ */
+static char *nvmem_name;
+module_param(nvmem_name, charp, 0444);
+MODULE_PARM_DESC(nvmem_name, "Name of the NVMEM device (e.g. pcf85063_nvram0)");
+
+static char *cell_name;
+module_param(cell_name, charp, 0444);
+MODULE_PARM_DESC(cell_name, "Sysfs name of the NVMEM cell (e.g. pscr@0,0)");
+
+struct pscrr_nvmem_priv {
+ struct nvmem_device *nvmem;
+ struct nvmem_cell *cell;
+
+ unsigned int total_bits;
+ size_t max_val;
+};
+
+static struct pscrr_nvmem_priv *priv;
+
+static int pscrr_nvmem_write_reason(enum pscr_reason reason)
+{
+ size_t required_bytes;
+ u32 val;
+ int ret;
+
+ if (!priv || !priv->cell)
+ return -ENODEV;
+
+ /* Ensure reason fits in the available storage */
+ if (reason > priv->max_val) {
+ pr_err("PSCRR-nvmem: Reason %d exceeds max storable value %zu for %u-bit cell\n",
+ reason, priv->max_val, priv->total_bits);
+ return -ERANGE;
+ }
+
+ val = reason;
+
+ /* Determine required bytes for storing total_bits */
+ required_bytes = (priv->total_bits + 7) / 8;
+
+ /* Write the reason to the NVMEM cell */
+ ret = nvmem_cell_write(priv->cell, &val, required_bytes);
+ if (ret < 0) {
+ pr_err("PSCRR-nvmem: Failed to write reason %d, err=%d (%pe)\n",
+ reason, ret, ERR_PTR(ret));
+ return ret;
+ }
+
+ pr_debug("PSCRR-nvmem: Successfully wrote reason %d\n", reason);
+
+ return 0;
+}
+
+static int pscrr_nvmem_read_reason(enum pscr_reason *reason)
+{
+ unsigned int required_bytes, val;
+ int ret = 0;
+ size_t len;
+ void *buf;
+
+ if (!priv || !priv->cell)
+ return -ENODEV;
+
+ buf = nvmem_cell_read(priv->cell, &len);
+ if (IS_ERR(buf)) {
+ ret = PTR_ERR(buf);
+ pr_err("PSCRR-nvmem: Failed to read cell, err=%d (%pe)\n", ret,
+ ERR_PTR(ret));
+ return ret;
+ }
+
+ /* Calculate the required number of bytes */
+ required_bytes = (priv->total_bits + 7) / 8;
+
+ /* Validate that the returned length is large enough */
+ if (len < required_bytes) {
+ pr_err("PSCRR-nvmem: Read length %zu is too small (need at least %u bytes)\n",
+ len, required_bytes);
+ kfree(buf);
+ return -EIO;
+ }
+
+ /* Extract value safely with proper memory alignment handling */
+ val = 0;
+ memcpy(&val, buf, required_bytes);
+
+ /* Mask only the necessary bits to avoid garbage data */
+ val &= (1U << priv->total_bits) - 1;
+
+ kfree(buf);
+
+ *reason = (enum pscr_reason)val;
+
+ pr_debug("PSCRR-nvmem: Read reason => %d (from %zu bytes, %u bits used)\n",
+ *reason, len, priv->total_bits);
+
+ return 0;
+}
+
+static const struct pscrr_backend_ops pscrr_nvmem_ops = {
+ .write_reason = pscrr_nvmem_write_reason,
+ .read_reason = pscrr_nvmem_read_reason,
+};
+
+static int __init pscrr_nvmem_init(void)
+{
+ size_t bytes, bits;
+ int ret;
+
+ if (!nvmem_name || !cell_name) {
+ pr_err("PSCRR-nvmem: Must specify both nvmem_name and cell_name.\n");
+ return -EINVAL;
+ }
+
+ priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->nvmem = nvmem_device_get_by_name(nvmem_name);
+ if (IS_ERR(priv->nvmem)) {
+ ret = PTR_ERR(priv->nvmem);
+ pr_err("PSCRR-nvmem: nvmem_device_get_by_name(%s) failed: %d\n",
+ nvmem_name, ret);
+ priv->nvmem = NULL;
+ goto err_free;
+ }
+
+ priv->cell = nvmem_cell_get_by_sysfs_name(priv->nvmem, cell_name);
+ if (IS_ERR(priv->cell)) {
+ ret = PTR_ERR(priv->cell);
+ pr_err("PSCRR-nvmem: nvmem_cell_get_by_sysfs_name(%s) failed, err=%pe\n",
+ cell_name, ERR_PTR(ret));
+ priv->cell = NULL;
+ goto err_dev_put;
+ }
+
+ ret = nvmem_cell_get_size(priv->cell, &bytes, &bits);
+ if (ret < 0) {
+ pr_err("PSCRR-nvmem: Failed to get cell size, err=%pe\n",
+ ERR_PTR(ret));
+ goto err_cell_put;
+ }
+
+ if (bits)
+ priv->total_bits = bits;
+ else
+ priv->total_bits = bytes * 8;
+
+ if (priv->total_bits > 31) {
+ pr_err("PSCRR-nvmem: total_bits=%u is too large (max 31 allowed)\n",
+ priv->total_bits);
+ return -EOVERFLOW;
+ }
+
+ priv->max_val = (1 << priv->total_bits) - 1;
+ pr_debug("PSCRR-nvmem: Cell size: %zu bytes + %zu bits => total_bits=%u\n",
+ bytes, bits, priv->total_bits);
+
+ /*
+ * If we store reasons 0..PSCR_MAX_REASON, the largest needed is
+ * 'PSCR_MAX_REASON'. That must fit within total_bits.
+ * So the max storable integer is (1 << total_bits) - 1.
+ */
+ if (priv->max_val < PSCR_MAX_REASON) {
+ pr_err("PSCRR-nvmem: Not enough bits (%u) to store up to reason=%d\n",
+ priv->total_bits, PSCR_MAX_REASON);
+ ret = -ENOSPC;
+ goto err_cell_put;
+ }
+
+ /* 4. Register with pscrr_core. */
+ ret = pscrr_core_init(&pscrr_nvmem_ops);
+ if (ret) {
+ pr_err("PSCRR-nvmem: pscrr_core_init() failed: %d\n", ret);
+ goto err_cell_put;
+ }
+
+ pr_info("PSCRR-nvmem: Loaded (nvmem=%s, cell=%s), can store 0..%zu\n",
+ nvmem_name, cell_name, priv->max_val);
+ return 0;
+
+err_cell_put:
+ if (priv->cell) {
+ nvmem_cell_put(priv->cell);
+ priv->cell = NULL;
+ }
+err_dev_put:
+ if (priv->nvmem) {
+ nvmem_device_put(priv->nvmem);
+ priv->nvmem = NULL;
+ }
+err_free:
+ kfree(priv);
+ priv = NULL;
+ return ret;
+}
+
+static void __exit pscrr_nvmem_exit(void)
+{
+ pscrr_core_exit();
+
+ if (priv) {
+ if (priv->cell) {
+ nvmem_cell_put(priv->cell);
+ priv->cell = NULL;
+ }
+ if (priv->nvmem) {
+ nvmem_device_put(priv->nvmem);
+ priv->nvmem = NULL;
+ }
+ kfree(priv);
+ priv = NULL;
+ }
+
+ pr_info("pscrr-nvmem: Unloaded\n");
+}
+
+module_init(pscrr_nvmem_init);
+module_exit(pscrr_nvmem_exit);
+
+MODULE_AUTHOR("Oleksij Rempel <o.rempel@pengutronix.de>");
+MODULE_DESCRIPTION("PSCRR backend for storing reason code in NVMEM");
+MODULE_LICENSE("GPL");
--
2.39.5
^ permalink raw reply related [flat|nested] 11+ messages in thread* [PATCH v5 6/7] regulator: set Power State Change Reason before hw_protection_shutdown()
2025-03-10 10:37 [PATCH v5 0/7] Introduction of PSCR Framework and Related Components Oleksij Rempel
` (4 preceding siblings ...)
2025-03-10 10:37 ` [PATCH v5 5/7] power: reset: add PSCR NVMEM Driver for Recording Power State Change Reasons Oleksij Rempel
@ 2025-03-10 10:37 ` Oleksij Rempel
2025-03-14 10:26 ` kernel test robot
2025-03-18 12:36 ` kernel test robot
2025-03-10 10:37 ` [PATCH v5 7/7] thermal: core: Record PSCR " Oleksij Rempel
6 siblings, 2 replies; 11+ messages in thread
From: Oleksij Rempel @ 2025-03-10 10:37 UTC (permalink / raw)
To: Sebastian Reichel, Srinivas Kandagatla
Cc: Oleksij Rempel, Mark Brown, kernel, linux-kernel, Liam Girdwood,
Rafael J. Wysocki, Daniel Lezcano, Zhang Rui, Lukasz Luba,
linux-pm, Søren Andersen
Store the state change reason to some black box, for later
investigation.
Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
Reviewed-by: Mark Brown <broonie@kernel.org>
---
drivers/regulator/core.c | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c
index 4ddf0efead68..7ccadc7e6d04 100644
--- a/drivers/regulator/core.c
+++ b/drivers/regulator/core.c
@@ -19,6 +19,7 @@
#include <linux/delay.h>
#include <linux/gpio/consumer.h>
#include <linux/of.h>
+#include <linux/pscrr.h>
#include <linux/reboot.h>
#include <linux/regmap.h>
#include <linux/regulator/of_regulator.h>
@@ -5243,6 +5244,7 @@ EXPORT_SYMBOL_GPL(regulator_bulk_free);
static void regulator_handle_critical(struct regulator_dev *rdev,
unsigned long event)
{
+ enum pscr_reason pscr;
const char *reason = NULL;
if (!rdev->constraints->system_critical)
@@ -5251,17 +5253,21 @@ static void regulator_handle_critical(struct regulator_dev *rdev,
switch (event) {
case REGULATOR_EVENT_UNDER_VOLTAGE:
reason = "System critical regulator: voltage drop detected";
+ pscr = PSCR_UNDER_VOLTAGE;
break;
case REGULATOR_EVENT_OVER_CURRENT:
reason = "System critical regulator: over-current detected";
+ pscr = PSCR_OVER_CURRENT;
break;
case REGULATOR_EVENT_FAIL:
reason = "System critical regulator: unknown error";
+ pscr = PSCR_REGULATOR_FAILURE;
}
if (!reason)
return;
+ set_power_state_change_reason(pscr);
hw_protection_shutdown(reason,
rdev->constraints->uv_less_critical_window_ms);
}
--
2.39.5
^ permalink raw reply related [flat|nested] 11+ messages in thread* Re: [PATCH v5 6/7] regulator: set Power State Change Reason before hw_protection_shutdown()
2025-03-10 10:37 ` [PATCH v5 6/7] regulator: set Power State Change Reason before hw_protection_shutdown() Oleksij Rempel
@ 2025-03-14 10:26 ` kernel test robot
2025-03-18 12:36 ` kernel test robot
1 sibling, 0 replies; 11+ messages in thread
From: kernel test robot @ 2025-03-14 10:26 UTC (permalink / raw)
To: Oleksij Rempel, Sebastian Reichel, Srinivas Kandagatla
Cc: oe-kbuild-all, Oleksij Rempel, Mark Brown, kernel, linux-kernel,
Liam Girdwood, Rafael J. Wysocki, Daniel Lezcano, Zhang Rui,
Lukasz Luba, linux-pm, Søren Andersen
Hi Oleksij,
kernel test robot noticed the following build errors:
[auto build test ERROR on sre-power-supply/for-next]
[also build test ERROR on broonie-regulator/for-next rafael-pm/thermal linus/master v6.14-rc6]
[cannot apply to next-20250314]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Oleksij-Rempel/power-Extend-power_on_reason-h-for-upcoming-PSCRR-framework/20250310-184319
base: https://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-power-supply.git for-next
patch link: https://lore.kernel.org/r/20250310103732.423542-7-o.rempel%40pengutronix.de
patch subject: [PATCH v5 6/7] regulator: set Power State Change Reason before hw_protection_shutdown()
config: powerpc64-randconfig-r122-20250314 (https://download.01.org/0day-ci/archive/20250314/202503141835.M6AeKZTU-lkp@intel.com/config)
compiler: powerpc64-linux-gcc (GCC) 14.2.0
reproduce: (https://download.01.org/0day-ci/archive/20250314/202503141835.M6AeKZTU-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202503141835.M6AeKZTU-lkp@intel.com/
All errors (new ones prefixed by >>):
powerpc64-linux-ld: warning: discarding dynamic section .glink
powerpc64-linux-ld: warning: discarding dynamic section .plt
powerpc64-linux-ld: linkage table error against `set_power_state_change_reason'
powerpc64-linux-ld: stubs don't match calculated size
powerpc64-linux-ld: can not build stubs: bad value
powerpc64-linux-ld: drivers/regulator/core.o: in function `.regulator_notifier_call_chain':
>> core.c:(.text+0xc55c): undefined reference to `.set_power_state_change_reason'
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v5 6/7] regulator: set Power State Change Reason before hw_protection_shutdown()
2025-03-10 10:37 ` [PATCH v5 6/7] regulator: set Power State Change Reason before hw_protection_shutdown() Oleksij Rempel
2025-03-14 10:26 ` kernel test robot
@ 2025-03-18 12:36 ` kernel test robot
1 sibling, 0 replies; 11+ messages in thread
From: kernel test robot @ 2025-03-18 12:36 UTC (permalink / raw)
To: Oleksij Rempel, Sebastian Reichel, Srinivas Kandagatla
Cc: oe-kbuild-all, Oleksij Rempel, Mark Brown, kernel, linux-kernel,
Liam Girdwood, Rafael J. Wysocki, Daniel Lezcano, Zhang Rui,
Lukasz Luba, linux-pm, Søren Andersen
Hi Oleksij,
kernel test robot noticed the following build errors:
[auto build test ERROR on sre-power-supply/for-next]
[also build test ERROR on broonie-regulator/for-next rafael-pm/thermal linus/master v6.14-rc7]
[cannot apply to next-20250317]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Oleksij-Rempel/power-Extend-power_on_reason-h-for-upcoming-PSCRR-framework/20250310-184319
base: https://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-power-supply.git for-next
patch link: https://lore.kernel.org/r/20250310103732.423542-7-o.rempel%40pengutronix.de
patch subject: [PATCH v5 6/7] regulator: set Power State Change Reason before hw_protection_shutdown()
config: m68k-randconfig-r073-20250314 (https://download.01.org/0day-ci/archive/20250318/202503182046.kOuLXxTn-lkp@intel.com/config)
compiler: m68k-linux-gcc (GCC) 14.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20250318/202503182046.kOuLXxTn-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202503182046.kOuLXxTn-lkp@intel.com/
All errors (new ones prefixed by >>):
m68k-linux-ld: drivers/regulator/core.o: in function `regulator_handle_critical':
>> drivers/regulator/core.c:5270:(.text+0x20c6): undefined reference to `set_power_state_change_reason'
vim +5270 drivers/regulator/core.c
5234
5235 /**
5236 * regulator_handle_critical - Handle events for system-critical regulators.
5237 * @rdev: The regulator device.
5238 * @event: The event being handled.
5239 *
5240 * This function handles critical events such as under-voltage, over-current,
5241 * and unknown errors for regulators deemed system-critical. On detecting such
5242 * events, it triggers a hardware protection shutdown with a defined timeout.
5243 */
5244 static void regulator_handle_critical(struct regulator_dev *rdev,
5245 unsigned long event)
5246 {
5247 enum pscr_reason pscr;
5248 const char *reason = NULL;
5249
5250 if (!rdev->constraints->system_critical)
5251 return;
5252
5253 switch (event) {
5254 case REGULATOR_EVENT_UNDER_VOLTAGE:
5255 reason = "System critical regulator: voltage drop detected";
5256 pscr = PSCR_UNDER_VOLTAGE;
5257 break;
5258 case REGULATOR_EVENT_OVER_CURRENT:
5259 reason = "System critical regulator: over-current detected";
5260 pscr = PSCR_OVER_CURRENT;
5261 break;
5262 case REGULATOR_EVENT_FAIL:
5263 reason = "System critical regulator: unknown error";
5264 pscr = PSCR_REGULATOR_FAILURE;
5265 }
5266
5267 if (!reason)
5268 return;
5269
> 5270 set_power_state_change_reason(pscr);
5271 hw_protection_shutdown(reason,
5272 rdev->constraints->uv_less_critical_window_ms);
5273 }
5274
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v5 7/7] thermal: core: Record PSCR before hw_protection_shutdown()
2025-03-10 10:37 [PATCH v5 0/7] Introduction of PSCR Framework and Related Components Oleksij Rempel
` (5 preceding siblings ...)
2025-03-10 10:37 ` [PATCH v5 6/7] regulator: set Power State Change Reason before hw_protection_shutdown() Oleksij Rempel
@ 2025-03-10 10:37 ` Oleksij Rempel
2025-03-18 19:33 ` kernel test robot
6 siblings, 1 reply; 11+ messages in thread
From: Oleksij Rempel @ 2025-03-10 10:37 UTC (permalink / raw)
To: Sebastian Reichel, Srinivas Kandagatla
Cc: Oleksij Rempel, kernel, linux-kernel, Liam Girdwood, Mark Brown,
Rafael J. Wysocki, Daniel Lezcano, Zhang Rui, Lukasz Luba,
linux-pm, Søren Andersen
Enhance the thermal core to record the Power State Change Reason (PSCR)
prior to invoking hw_protection_shutdown(). This change integrates the
PSCR framework with the thermal subsystem, ensuring that reasons for
power state changes, such as overtemperature events, are stored in a
dedicated non-volatile memory (NVMEM) cell.
This 'black box' recording is crucial for post-mortem analysis, enabling
a deeper understanding of system failures and abrupt shutdowns,
especially in scenarios where PMICs or watchdog timers are incapable of
logging such events. The recorded data can be utilized during system
recovery routines in the bootloader or early kernel stages of subsequent
boots, significantly enhancing system diagnostics, reliability, and
debugging capabilities.
Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
---
drivers/thermal/thermal_core.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/drivers/thermal/thermal_core.c b/drivers/thermal/thermal_core.c
index 2328ac0d8561..af4e9cf22bf6 100644
--- a/drivers/thermal/thermal_core.c
+++ b/drivers/thermal/thermal_core.c
@@ -16,6 +16,7 @@
#include <linux/kdev_t.h>
#include <linux/idr.h>
#include <linux/thermal.h>
+#include <linux/pscrr.h>
#include <linux/reboot.h>
#include <linux/string.h>
#include <linux/of.h>
@@ -380,6 +381,8 @@ static void thermal_zone_device_halt(struct thermal_zone_device *tz, bool shutdo
dev_emerg(&tz->device, "%s: critical temperature reached\n", tz->type);
+ set_power_state_change_reason(PSCR_OVERTEMPERATURE);
+
if (shutdown)
hw_protection_shutdown(msg, poweroff_delay_ms);
else
--
2.39.5
^ permalink raw reply related [flat|nested] 11+ messages in thread* Re: [PATCH v5 7/7] thermal: core: Record PSCR before hw_protection_shutdown()
2025-03-10 10:37 ` [PATCH v5 7/7] thermal: core: Record PSCR " Oleksij Rempel
@ 2025-03-18 19:33 ` kernel test robot
0 siblings, 0 replies; 11+ messages in thread
From: kernel test robot @ 2025-03-18 19:33 UTC (permalink / raw)
To: Oleksij Rempel, Sebastian Reichel, Srinivas Kandagatla
Cc: oe-kbuild-all, Oleksij Rempel, kernel, linux-kernel,
Liam Girdwood, Mark Brown, Rafael J. Wysocki, Daniel Lezcano,
Zhang Rui, Lukasz Luba, linux-pm, Søren Andersen
Hi Oleksij,
kernel test robot noticed the following build errors:
[auto build test ERROR on sre-power-supply/for-next]
[also build test ERROR on broonie-regulator/for-next rafael-pm/thermal linus/master v6.14-rc7]
[cannot apply to next-20250318]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Oleksij-Rempel/power-Extend-power_on_reason-h-for-upcoming-PSCRR-framework/20250310-184319
base: https://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-power-supply.git for-next
patch link: https://lore.kernel.org/r/20250310103732.423542-8-o.rempel%40pengutronix.de
patch subject: [PATCH v5 7/7] thermal: core: Record PSCR before hw_protection_shutdown()
config: m68k-randconfig-r073-20250314 (https://download.01.org/0day-ci/archive/20250319/202503190248.stJdS2ru-lkp@intel.com/config)
compiler: m68k-linux-gcc (GCC) 14.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20250319/202503190248.stJdS2ru-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202503190248.stJdS2ru-lkp@intel.com/
All errors (new ones prefixed by >>):
m68k-linux-ld: drivers/thermal/thermal_core.o: in function `thermal_zone_device_halt':
>> drivers/thermal/thermal_core.c:384:(.text.unlikely+0x24): undefined reference to `set_power_state_change_reason'
m68k-linux-ld: drivers/regulator/core.o: in function `regulator_handle_critical':
drivers/regulator/core.c:5270:(.text+0x20c6): undefined reference to `set_power_state_change_reason'
vim +384 drivers/thermal/thermal_core.c
372
373 static void thermal_zone_device_halt(struct thermal_zone_device *tz, bool shutdown)
374 {
375 /*
376 * poweroff_delay_ms must be a carefully profiled positive value.
377 * Its a must for forced_emergency_poweroff_work to be scheduled.
378 */
379 int poweroff_delay_ms = CONFIG_THERMAL_EMERGENCY_POWEROFF_DELAY_MS;
380 const char *msg = "Temperature too high";
381
382 dev_emerg(&tz->device, "%s: critical temperature reached\n", tz->type);
383
> 384 set_power_state_change_reason(PSCR_OVERTEMPERATURE);
385
386 if (shutdown)
387 hw_protection_shutdown(msg, poweroff_delay_ms);
388 else
389 hw_protection_reboot(msg, poweroff_delay_ms);
390 }
391
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 11+ messages in thread