linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v11 0/7] Introduction of PSCR Framework and Related Components
@ 2025-06-18 12:02 Oleksij Rempel
  2025-06-18 12:02 ` [PATCH v11 1/7] power: Extend power_on_reason.h for upcoming PSCRR framework Oleksij Rempel
                   ` (7 more replies)
  0 siblings, 8 replies; 14+ messages in thread
From: Oleksij Rempel @ 2025-06-18 12:02 UTC (permalink / raw)
  To: Sebastian Reichel, Srinivas Kandagatla, Benson Leung,
	Tzung-Bi Shih, Daniel Lezcano
  Cc: Oleksij Rempel, kernel, linux-kernel, Liam Girdwood, Mark Brown,
	Rafael J. Wysocki, Zhang Rui, Lukasz Luba, linux-pm,
	Søren Andersen, Guenter Roeck, Matti Vaittinen, Ahmad Fatoum,
	Andrew Morton, chrome-platform

changes v11:
- add missing break reported by kernel test robot <lkp@intel.com>

changes v10:
- add some add Reviewed-by tags
- regulator_handle_critical: set pscr = PSCR_UNKNOWN for default case
- make g_pscrr static

changes v9:
- Remove redundant pr_crit() messages before hw_protection_trigger()
- Replace psc_reason_to_str() switch with static const string array
- Mark psc_last_reason as static

changes v8:
- Use DEFINE_GUARD() and guard(g_pscrr) for scoped locking of the global
  pscrr_core struct
- Replace manual mutex_lock/unlock with automatic cleanup-based guard()
  usage
- Centralize backend and locking state in struct pscrr_core
- Prepare for future multi-backend support with clean encapsulation
- Improve sysfs documentation:
  * Added full enum psc_reason value table
  * Simplified example comments, removed redundant "may differ" phrasing
  * Added note that not all values are supported on all systems
  * Linked value definitions to include/linux/reboot.h
  * Added clear read/write usage examples for sysfs entries

changes v7:
- document expected values in sysfs documentation
- make write support optional

changes v6:
- add sysfs documentation
- rebase against latest hw_protection_reboot changes:
  https://web.git.kernel.org/pub/scm/linux/kernel/git/akpm/mm.git/commit/?h=mm-nonmm-unstable&id=212dd3f6e57f6af8ed3caa23b93adc29334f9652
- push core part of the reset reason the kernel/reboot.c

changes v5:
- fix compile with NVMEM=n and potential issues with NVMEM=m

changes v4:
- fix compile with CONFIG_PSCRR=n

changes v3
- rework to remove devicetree dependencies
- extend NVMEM to search devices and cells by names.

changes v2:
- rename the framework from PSCR to PSCRR (last R is for Recorder)
- extend power on reason header and use it to show detected reason on
  system start and in sysfs.
- remove "unknow" reason
- rebase on top of v6.8-rc1
- yaml fixes
- zero reason state on boot

Hello all,

This patch series introduces the Power State Change Reasons Recording
(PSCRR) framework and its related components into the kernel. The PSCR
framework is designed for systems where traditional methods of storing
power state change reasons, like PMICs or watchdogs, are inadequate. It
provides a structured way to store reasons for system shutdowns and
reboots, such as under-voltage or software-triggered events, in
non-volatile hardware storage.

These changes are critical for systems requiring detailed postmortem
analysis and where immediate power-down scenarios limit traditional
storage options. The framework also assists bootloaders and early-stage
system components in making informed recovery decisions.



Oleksij Rempel (7):
  power: Extend power_on_reason.h for upcoming PSCRR framework
  reboot: hw_protection_trigger: use standardized numeric
    shutdown/reboot reasons instead of strings
  power: reset: Introduce PSCR Recording Framework for Non-Volatile
    Storage
  nvmem: provide consumer access to cell size metrics
  nvmem: add support for device and sysfs-based cell lookups
  power: reset: add PSCR NVMEM Driver for Recording Power State Change
    Reasons
  Documentation: Add sysfs documentation for PSCRR reboot reason
    tracking

 .../ABI/testing/sysfs-kernel-reboot-pscrr     |  74 ++++
 drivers/nvmem/core.c                          | 134 ++++++
 drivers/platform/chrome/cros_ec_lpc.c         |   2 +-
 drivers/power/reset/Kconfig                   |  47 ++
 drivers/power/reset/Makefile                  |   2 +
 drivers/power/reset/pscrr-nvmem.c             | 254 +++++++++++
 drivers/power/reset/pscrr.c                   | 405 ++++++++++++++++++
 drivers/regulator/core.c                      |  16 +-
 drivers/regulator/irq_helpers.c               |   9 +-
 drivers/thermal/thermal_core.c                |   3 +-
 include/linux/nvmem-consumer.h                |  25 ++
 include/linux/power/power_on_reason.h         |   4 +
 include/linux/pscrr.h                         |  58 +++
 include/linux/reboot.h                        |  77 +++-
 kernel/reboot.c                               |  85 +++-
 15 files changed, 1173 insertions(+), 22 deletions(-)
 create mode 100644 Documentation/ABI/testing/sysfs-kernel-reboot-pscrr
 create mode 100644 drivers/power/reset/pscrr-nvmem.c
 create mode 100644 drivers/power/reset/pscrr.c
 create mode 100644 include/linux/pscrr.h

--
2.39.5


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

* [PATCH v11 1/7] power: Extend power_on_reason.h for upcoming PSCRR framework
  2025-06-18 12:02 [PATCH v11 0/7] Introduction of PSCR Framework and Related Components Oleksij Rempel
@ 2025-06-18 12:02 ` Oleksij Rempel
  2025-06-18 12:02 ` [PATCH v11 2/7] reboot: hw_protection_trigger: use standardized numeric shutdown/reboot reasons instead of strings Oleksij Rempel
                   ` (6 subsequent siblings)
  7 siblings, 0 replies; 14+ messages in thread
From: Oleksij Rempel @ 2025-06-18 12:02 UTC (permalink / raw)
  To: Sebastian Reichel, Srinivas Kandagatla, Benson Leung,
	Tzung-Bi Shih, Daniel Lezcano
  Cc: Oleksij Rempel, Sebastian Reichel, kernel, linux-kernel,
	Liam Girdwood, Mark Brown, Rafael J. Wysocki, Zhang Rui,
	Lukasz Luba, linux-pm, Søren Andersen, Guenter Roeck,
	Matti Vaittinen, Ahmad Fatoum, Andrew Morton, chrome-platform

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_OVER_TEMPERATURE for over temperature situations.
- POWER_ON_REASON_EC_PANIC for EC panics

Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
Reviewed-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
changes v10:
- add Reviewed-by: Sebastian Reichel ...
changes v6:
- add POWER_ON_REASON_EC_PANIC
- s/POWER_ON_REASON_OVERTEMPERATURE/POWER_ON_REASON_OVER_TEMPERATURE
---
 include/linux/power/power_on_reason.h | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/include/linux/power/power_on_reason.h b/include/linux/power/power_on_reason.h
index 95a1ec0c403c..bf9501792696 100644
--- a/include/linux/power/power_on_reason.h
+++ b/include/linux/power/power_on_reason.h
@@ -15,5 +15,9 @@
 #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_OVER_TEMPERATURE "over temperature"
+#define POWER_ON_REASON_EC_PANIC "EC panic"
 
 #endif /* POWER_ON_REASON_H */
-- 
2.39.5


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

* [PATCH v11 2/7] reboot: hw_protection_trigger: use standardized numeric shutdown/reboot reasons instead of strings
  2025-06-18 12:02 [PATCH v11 0/7] Introduction of PSCR Framework and Related Components Oleksij Rempel
  2025-06-18 12:02 ` [PATCH v11 1/7] power: Extend power_on_reason.h for upcoming PSCRR framework Oleksij Rempel
@ 2025-06-18 12:02 ` Oleksij Rempel
  2025-06-18 12:02 ` [PATCH v11 3/7] power: reset: Introduce PSCR Recording Framework for Non-Volatile Storage Oleksij Rempel
                   ` (5 subsequent siblings)
  7 siblings, 0 replies; 14+ messages in thread
From: Oleksij Rempel @ 2025-06-18 12:02 UTC (permalink / raw)
  To: Sebastian Reichel, Srinivas Kandagatla, Benson Leung,
	Tzung-Bi Shih, Daniel Lezcano
  Cc: Oleksij Rempel, Matti Vaittinen, Mark Brown, kernel, linux-kernel,
	Liam Girdwood, Rafael J. Wysocki, Zhang Rui, Lukasz Luba,
	linux-pm, Søren Andersen, Guenter Roeck, Ahmad Fatoum,
	Andrew Morton, chrome-platform

Prepares the kernel for the Power State Change Reason (PSCR) recorder,
which will store shutdown and reboot reasons in persistent storage.

Instead of using string-based reason descriptions, which are often too
large to fit within limited storage spaces (e.g., RTC clocks with only 8
bits of battery-backed storage), we introduce `enum psc_reason`. This
enumerates predefined reasons for power state changes, making it
efficient to store and retrieve shutdown causes.

Key changes:
- Introduced `enum psc_reason`, defining structured reasons for power state
  changes.
- Replaced string-based shutdown reasons with `psc_reason` identifiers.
- Implemented `get_psc_reason()` and `set_psc_reason()` for tracking the
  last shutdown cause.
- Added `psc_reason_to_str()` to map enum values to human-readable strings.
- Updated `hw_protection_trigger()` to use `psc_reason` instead of string
  parameters.
- Updated all consumers of `hw_protection_trigger()` to pass an appropriate
  `psc_reason` value instead of a string.
- All structured logs now go through a single `pr_emerg()` in
  `__hw_protection_trigger()`, providing consistent output:
    HARDWARE PROTECTION <action>: <reason-code> (<reason-string>)

Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
Reviewed-by: Matti Vaittinen <mazziesaccount@gmail.com>
Acked-by: Mark Brown <broonie@kernel.org>
Acked-by: Tzung-Bi Shih <tzungbi@kernel.org>
Acked-by: Daniel Lezcano <daniel.lezcano@linaro.org>
---
changes v11:
- add missing break reported by kernel test robot <lkp@intel.com>
  https://lore.kernel.org/oe-kbuild-all/202506180402.ly0g2TQe-lkp@intel.com/
changes v10
- regulator_handle_critical: set pscr = PSCR_UNKNOWN for default case
- add Acked-by: Daniel Lezcano ..
changes v9:
- Remove redundant pr_crit() messages before hw_protection_trigger()
- Replace psc_reason_to_str() switch with static const string array
- Mark psc_last_reason as static
changes v8:
- add Acked/Reviewed-by.
changes v6:
- added in this version
---
 drivers/platform/chrome/cros_ec_lpc.c |  2 +-
 drivers/regulator/core.c              | 16 ++---
 drivers/regulator/irq_helpers.c       |  9 +--
 drivers/thermal/thermal_core.c        |  3 +-
 include/linux/reboot.h                | 77 +++++++++++++++++++++++-
 kernel/reboot.c                       | 85 +++++++++++++++++++++++++--
 6 files changed, 170 insertions(+), 22 deletions(-)

diff --git a/drivers/platform/chrome/cros_ec_lpc.c b/drivers/platform/chrome/cros_ec_lpc.c
index 7d9a78289c96..20d792f99f13 100644
--- a/drivers/platform/chrome/cros_ec_lpc.c
+++ b/drivers/platform/chrome/cros_ec_lpc.c
@@ -455,7 +455,7 @@ static void cros_ec_lpc_acpi_notify(acpi_handle device, u32 value, void *data)
 		blocking_notifier_call_chain(&ec_dev->panic_notifier, 0, ec_dev);
 		kobject_uevent_env(&ec_dev->dev->kobj, KOBJ_CHANGE, (char **)env);
 		/* Begin orderly shutdown. EC will force reset after a short period. */
-		__hw_protection_trigger("CrOS EC Panic", -1, HWPROT_ACT_SHUTDOWN);
+		__hw_protection_trigger(PSCR_EC_PANIC, -1, HWPROT_ACT_SHUTDOWN);
 		/* Do not query for other events after a panic is reported */
 		return;
 	}
diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c
index ab74d86f822a..e909cdae634a 100644
--- a/drivers/regulator/core.c
+++ b/drivers/regulator/core.c
@@ -5324,26 +5324,26 @@ EXPORT_SYMBOL_GPL(regulator_bulk_free);
 static void regulator_handle_critical(struct regulator_dev *rdev,
 				      unsigned long event)
 {
-	const char *reason = NULL;
+	enum psc_reason pscr;
 
 	if (!rdev->constraints->system_critical)
 		return;
 
 	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;
+		break;
+	default:
+		pscr = PSCR_UNKNOWN;
 	}
 
-	if (!reason)
-		return;
-
-	hw_protection_trigger(reason,
+	hw_protection_trigger(pscr,
 			      rdev->constraints->uv_less_critical_window_ms);
 }
 
diff --git a/drivers/regulator/irq_helpers.c b/drivers/regulator/irq_helpers.c
index 5742faee8071..25f5ba3a3a6a 100644
--- a/drivers/regulator/irq_helpers.c
+++ b/drivers/regulator/irq_helpers.c
@@ -64,15 +64,16 @@ static void regulator_notifier_isr_work(struct work_struct *work)
 reread:
 	if (d->fatal_cnt && h->retry_cnt > d->fatal_cnt) {
 		if (!d->die)
-			return hw_protection_trigger("Regulator HW failure? - no IC recovery",
+			return hw_protection_trigger(PSCR_REGULATOR_FAILURE,
 						     REGULATOR_FORCED_SAFETY_SHUTDOWN_WAIT_MS);
+
 		ret = d->die(rid);
 		/*
 		 * If the 'last resort' IC recovery failed we will have
 		 * nothing else left to do...
 		 */
 		if (ret)
-			return hw_protection_trigger("Regulator HW failure. IC recovery failed",
+			return hw_protection_trigger(PSCR_REGULATOR_FAILURE,
 						     REGULATOR_FORCED_SAFETY_SHUTDOWN_WAIT_MS);
 
 		/*
@@ -263,13 +264,13 @@ static irqreturn_t regulator_notifier_isr(int irq, void *data)
 	if (d->fatal_cnt && h->retry_cnt > d->fatal_cnt) {
 		/* If we have no recovery, just try shut down straight away */
 		if (!d->die) {
-			hw_protection_trigger("Regulator failure. Retry count exceeded",
+			hw_protection_trigger(PSCR_REGULATOR_FAILURE,
 					      REGULATOR_FORCED_SAFETY_SHUTDOWN_WAIT_MS);
 		} else {
 			ret = d->die(rid);
 			/* If die() failed shut down as a last attempt to save the HW */
 			if (ret)
-				hw_protection_trigger("Regulator failure. Recovery failed",
+				hw_protection_trigger(PSCR_REGULATOR_FAILURE,
 						      REGULATOR_FORCED_SAFETY_SHUTDOWN_WAIT_MS);
 		}
 	}
diff --git a/drivers/thermal/thermal_core.c b/drivers/thermal/thermal_core.c
index 17ca5c082643..9f13213f0722 100644
--- a/drivers/thermal/thermal_core.c
+++ b/drivers/thermal/thermal_core.c
@@ -377,11 +377,10 @@ static void thermal_zone_device_halt(struct thermal_zone_device *tz,
 	 * Its a must for forced_emergency_poweroff_work to be scheduled.
 	 */
 	int poweroff_delay_ms = CONFIG_THERMAL_EMERGENCY_POWEROFF_DELAY_MS;
-	const char *msg = "Temperature too high";
 
 	dev_emerg(&tz->device, "%s: critical temperature reached\n", tz->type);
 
-	__hw_protection_trigger(msg, poweroff_delay_ms, action);
+	__hw_protection_trigger(PSCR_OVER_TEMPERATURE, poweroff_delay_ms, action);
 }
 
 void thermal_zone_device_critical(struct thermal_zone_device *tz)
diff --git a/include/linux/reboot.h b/include/linux/reboot.h
index aa08c3bbbf59..6477910c6a9e 100644
--- a/include/linux/reboot.h
+++ b/include/linux/reboot.h
@@ -178,6 +178,73 @@ void ctrl_alt_del(void);
 extern void orderly_poweroff(bool force);
 extern void orderly_reboot(void);
 
+
+/**
+ * enum psc_reason - Enumerates reasons for power state changes.
+ *
+ * This enum defines various reasons why a system might transition into a
+ * shutdown, reboot, or kexec state. While originally intended for hardware
+ * protection events, `psc_reason` can be extended to track other system
+ * transitions, such as controlled reboots triggered by software or
+ * maintenance operations.
+ *
+ * The values in this enumeration provide structured and standardized
+ * identifiers that replace free-form string descriptions. They are designed
+ * to be stored efficiently, making them suitable for use in environments
+ * with limited storage, such as battery-backed RTC registers, non-volatile
+ * memory, or bootloader communication mechanisms.
+ *
+ * Importantly, the order of these values **must remain stable**, as
+ * bootloaders, user-space tools, or post-mortem investigation utilities
+ * may rely on their numerical representation for consistent behavior.
+ *
+ * @PSCR_UNKNOWN: Unknown or unspecified reason for the power state change.
+ *	This value serves as a default when no explicit cause is recorded.
+ *
+ * @PSCR_UNDER_VOLTAGE: Shutdown or reboot triggered due to supply voltage
+ *      dropping below a safe threshold. This helps prevent instability or
+ *      corruption caused by insufficient power.
+ *
+ * @PSCR_OVER_CURRENT: System shutdown or reboot due to excessive current draw,
+ *      which may indicate a short circuit, an overloaded power rail, or other
+ *      hardware faults requiring immediate action.
+ *
+ * @PSCR_REGULATOR_FAILURE: A critical failure in a voltage regulator, causing
+ *      improper power delivery. This may be due to internal component failure,
+ *      transient conditions, or external load issues requiring mitigation.
+ *
+ * @PSCR_OVER_TEMPERATURE: System shutdown or reboot due to excessive thermal
+ *	conditions. This attempts to prevent hardware damage when temperature
+ *	sensors detect unsafe levels, often impacting CPUs, GPUs, or power
+ *	components.
+ *
+ * @PSCR_EC_PANIC: Shutdown or reboot triggered by an Embedded Controller (EC)
+ *	panic. The EC is a microcontroller responsible for low-level system
+ *	management, including power sequencing, thermal control, and battery
+ *	management. An EC panic may indicate critical firmware issues, power
+ *	management errors, or an unrecoverable hardware fault requiring
+ *	immediate response.
+ *
+ * @PSCR_REASON_COUNT: Number of defined power state change reasons. This
+ *	value is useful for range checking and potential future extensions
+ *	while maintaining compatibility.
+ */
+enum psc_reason {
+	PSCR_UNKNOWN,
+	PSCR_UNDER_VOLTAGE,
+	PSCR_OVER_CURRENT,
+	PSCR_REGULATOR_FAILURE,
+	PSCR_OVER_TEMPERATURE,
+	PSCR_EC_PANIC,
+
+	/* Number of reasons */
+	PSCR_REASON_COUNT,
+};
+
+#define PSCR_MAX_REASON	(PSCR_REASON_COUNT - 1)
+
+const char *psc_reason_to_str(enum psc_reason reason);
+
 /**
  * enum hw_protection_action - Hardware protection action
  *
@@ -191,13 +258,13 @@ extern void orderly_reboot(void);
  */
 enum hw_protection_action { HWPROT_ACT_DEFAULT, HWPROT_ACT_SHUTDOWN, HWPROT_ACT_REBOOT };
 
-void __hw_protection_trigger(const char *reason, int ms_until_forced,
+void __hw_protection_trigger(enum psc_reason reason, int ms_until_forced,
 			     enum hw_protection_action action);
 
 /**
  * hw_protection_trigger - Trigger default emergency system hardware protection action
  *
- * @reason:		Reason of emergency shutdown or reboot to be printed.
+ * @reason:		Reason of emergency shutdown or reboot.
  * @ms_until_forced:	Time to wait for orderly shutdown or reboot before
  *			triggering it. Negative value disables the forced
  *			shutdown or reboot.
@@ -206,11 +273,15 @@ void __hw_protection_trigger(const char *reason, int ms_until_forced,
  * hardware from further damage. The exact action taken is controllable at
  * runtime and defaults to shutdown.
  */
-static inline void hw_protection_trigger(const char *reason, int ms_until_forced)
+static inline void hw_protection_trigger(enum psc_reason reason,
+					 int ms_until_forced)
 {
 	__hw_protection_trigger(reason, ms_until_forced, HWPROT_ACT_DEFAULT);
 }
 
+enum psc_reason get_psc_reason(void);
+void set_psc_reason(enum psc_reason reason);
+
 /*
  * Emergency restart, callable from an interrupt handler.
  */
diff --git a/kernel/reboot.c b/kernel/reboot.c
index ec087827c85c..87972b4a77b2 100644
--- a/kernel/reboot.c
+++ b/kernel/reboot.c
@@ -13,6 +13,7 @@
 #include <linux/kexec.h>
 #include <linux/kmod.h>
 #include <linux/kmsg_dump.h>
+#include <linux/power/power_on_reason.h>
 #include <linux/reboot.h>
 #include <linux/suspend.h>
 #include <linux/syscalls.h>
@@ -49,6 +50,7 @@ int reboot_default = 1;
 int reboot_cpu;
 enum reboot_type reboot_type = BOOT_ACPI;
 int reboot_force;
+static enum psc_reason psc_last_reason = PSCR_UNKNOWN;
 
 struct sys_off_handler {
 	struct notifier_block nb;
@@ -1010,10 +1012,82 @@ static void hw_failure_emergency_schedule(enum hw_protection_action action,
 			      msecs_to_jiffies(action_delay_ms));
 }
 
+/**
+ * get_psc_reason - Retrieve the last recorded power state change reason.
+ *
+ * This function returns the most recent power state change reason stored
+ * in `psc_last_reason`. The value is set using `set_psc_reason()` when a
+ * shutdown, reboot, or kexec event occurs.
+ *
+ * The reason can be used for system diagnostics, post-mortem analysis, or
+ * debugging unexpected power state changes. Bootloaders or user-space tools
+ * may retrieve this value to determine why the system last transitioned to
+ * a new power state.
+ *
+ * Return: A value from `enum psc_reason`, indicating the last known power
+ * state change reason.
+ */
+enum psc_reason get_psc_reason(void)
+{
+	return READ_ONCE(psc_last_reason);
+}
+EXPORT_SYMBOL_GPL(get_psc_reason);
+
+/**
+ * set_psc_reason - Set the reason for the last power state change.
+ *
+ * @reason: A value from `enum psc_reason` indicating the cause of the power
+ *          state change.
+ *
+ * This function records the reason for a shutdown, reboot, or kexec event
+ * by storing it in `psc_last_reason`. It ensures that the value remains
+ * consistent within the running system, allowing retrieval via
+ * `get_psc_reason()` for diagnostics, logging, or post-mortem analysis.
+ *
+ * Persistence Consideration:
+ * - This function **does not persist** the recorded reason across power cycles.
+ * - After a system reset or complete power loss, the recorded reason is lost.
+ * - To store power state change reasons persistently, additional tools such as
+ *   the Power State Change Reason Recorder (PSCRR) framework should be used.
+ */
+void set_psc_reason(enum psc_reason reason)
+{
+	WRITE_ONCE(psc_last_reason, reason);
+}
+EXPORT_SYMBOL_GPL(set_psc_reason);
+
+static const char * const pscr_reason_strs[] = {
+	[PSCR_UNKNOWN]            = POWER_ON_REASON_UNKNOWN,
+	[PSCR_UNDER_VOLTAGE]      = POWER_ON_REASON_BROWN_OUT,
+	[PSCR_OVER_CURRENT]       = POWER_ON_REASON_OVER_CURRENT,
+	[PSCR_REGULATOR_FAILURE]  = POWER_ON_REASON_REGULATOR_FAILURE,
+	[PSCR_OVER_TEMPERATURE]   = POWER_ON_REASON_OVER_TEMPERATURE,
+	[PSCR_EC_PANIC]           = POWER_ON_REASON_EC_PANIC,
+};
+
+/**
+ * psc_reason_to_str - Converts a power state change reason enum to a string.
+ * @reason: The `psc_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 `psc_reason` value.
+ * - `"Invalid"` if the value is not recognized.
+ */
+const char *psc_reason_to_str(enum psc_reason reason)
+{
+	if (reason < 0 || reason >= PSCR_REASON_COUNT)
+		return "Invalid";
+	return pscr_reason_strs[reason];
+}
+EXPORT_SYMBOL_GPL(psc_reason_to_str);
+
 /**
  * __hw_protection_trigger - Trigger an emergency system shutdown or reboot
  *
- * @reason:		Reason of emergency shutdown or reboot to be printed.
+ * @reason:		Reason of emergency shutdown or reboot.
  * @ms_until_forced:	Time to wait for orderly shutdown or reboot before
  *			triggering it. Negative value disables the forced
  *			shutdown or reboot.
@@ -1025,7 +1099,7 @@ static void hw_failure_emergency_schedule(enum hw_protection_action action,
  * pending even if the previous request has given a large timeout for forced
  * shutdown/reboot.
  */
-void __hw_protection_trigger(const char *reason, int ms_until_forced,
+void __hw_protection_trigger(enum psc_reason reason, int ms_until_forced,
 			     enum hw_protection_action action)
 {
 	static atomic_t allow_proceed = ATOMIC_INIT(1);
@@ -1033,8 +1107,11 @@ void __hw_protection_trigger(const char *reason, int ms_until_forced,
 	if (action == HWPROT_ACT_DEFAULT)
 		action = hw_protection_action;
 
-	pr_emerg("HARDWARE PROTECTION %s (%s)\n",
-		 hw_protection_action_str(action), reason);
+	set_psc_reason(reason);
+
+	pr_emerg("HARDWARE PROTECTION %s: %i (%s)\n",
+		 hw_protection_action_str(action), reason,
+		 psc_reason_to_str(reason));
 
 	/* Shutdown should be initiated only once. */
 	if (!atomic_dec_and_test(&allow_proceed))
-- 
2.39.5


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

* [PATCH v11 3/7] power: reset: Introduce PSCR Recording Framework for Non-Volatile Storage
  2025-06-18 12:02 [PATCH v11 0/7] Introduction of PSCR Framework and Related Components Oleksij Rempel
  2025-06-18 12:02 ` [PATCH v11 1/7] power: Extend power_on_reason.h for upcoming PSCRR framework Oleksij Rempel
  2025-06-18 12:02 ` [PATCH v11 2/7] reboot: hw_protection_trigger: use standardized numeric shutdown/reboot reasons instead of strings Oleksij Rempel
@ 2025-06-18 12:02 ` Oleksij Rempel
  2025-07-16 12:43   ` Greg KH
  2025-06-18 12:02 ` [PATCH v11 4/7] nvmem: provide consumer access to cell size metrics Oleksij Rempel
                   ` (4 subsequent siblings)
  7 siblings, 1 reply; 14+ messages in thread
From: Oleksij Rempel @ 2025-06-18 12:02 UTC (permalink / raw)
  To: Sebastian Reichel, Srinivas Kandagatla, Benson Leung,
	Tzung-Bi Shih, Daniel Lezcano
  Cc: Oleksij Rempel, Matti Vaittinen, kernel, linux-kernel,
	Liam Girdwood, Mark Brown, Rafael J. Wysocki, Zhang Rui,
	Lukasz Luba, linux-pm, Søren Andersen, Guenter Roeck,
	Ahmad Fatoum, Andrew Morton, chrome-platform

This commit introduces the Power State Change Reasons Recording (PSCRR)
framework. It provides a generic mechanism to store shutdown or reboot
reasons, such as under-voltage, thermal events, or software-triggered
actions, into non-volatile storage.

PSCRR is primarily intended for systems where software is able to detect
a power event in time and store the reason—typically when backup power
(e.g., capacitors) allows a short window before shutdown. This enables
reliable postmortem diagnostics, even on devices where traditional storage
like eMMC or NAND may not survive abrupt power loss.

In its current form, PSCRR focuses on software-reported reasons. However,
the framework is also designed with future extensibility in mind and could
serve as a central frontend for exposing hardware-reported reset reasons
from sources such as PMICs, watchdogs, or SoC-specific registers.

This version does not yet integrate such hardware-based reporting.

Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
Reviewed-by: Matti Vaittinen <mazziesaccount@gmail.com>
---
changes v10:
- make g_pscrr static
changes v8:
- Introduce struct pscrr_core to encapsulate backend and locking
- Replace global mutex and backend pointer with centralized pscrr_core
- Use DEFINE_GUARD() + guard(g_pscrr) for scoped mutex locking
- Simplify code using local backend pointer after locking
- Prepare code structure for future multi-backend support
changes v7:
- make write_reason optional
- update documentation
changes v6:
- move enum pscr_reason to kernel reboot core
- move reason storage to reboot core
- add locking
---
 drivers/power/reset/Kconfig  |  25 +++
 drivers/power/reset/Makefile |   1 +
 drivers/power/reset/pscrr.c  | 405 +++++++++++++++++++++++++++++++++++
 include/linux/pscrr.h        |  58 +++++
 4 files changed, 489 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 e71f0af4e378..69e038e20731 100644
--- a/drivers/power/reset/Kconfig
+++ b/drivers/power/reset/Kconfig
@@ -329,3 +329,28 @@ config POWER_MLXBF
 	  This driver supports reset or low power mode handling for Mellanox BlueField.
 
 endif
+
+menuconfig PSCRR
+	bool "Power State Change Reasons Recording (PSCRR) Framework"
+	help
+	  Enables the Power State Change Reasons Recording (PSCRR) framework.
+
+	  This framework allows software to store a reason for system shutdown
+	  or reboot in non-volatile storage. It is useful on systems where
+	  power failures can be detected in advance, giving software a short
+	  window (e.g., via backup capacitors) to persist a shutdown reason
+	  before full power loss.
+
+	  This mechanism supports diagnostics and recovery logic in bootloaders
+	  or early-stage software. It avoids reliance on block-based storage,
+	  which may not survive hard shutdowns.
+
+	  The framework is designed to be extensible and can also support
+	  hardware-backed reset reason sources (e.g., PMIC or watchdog
+	  registers), allowing a uniform interface for both software-defined
+	  and hardware-reported reasons.
+
+	  Sudden power cuts, CPU freezes, or other uncontrolled resets may not
+	  be recorded unless hardware provides the reset cause.
+
+	  If unsure, say N.
diff --git a/drivers/power/reset/Makefile b/drivers/power/reset/Makefile
index 1b9b63a1a873..025da19cb335 100644
--- a/drivers/power/reset/Makefile
+++ b/drivers/power/reset/Makefile
@@ -33,6 +33,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..92e8ef97421c
--- /dev/null
+++ b/drivers/power/reset/pscrr.c
@@ -0,0 +1,405 @@
+// 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 or shutdown, in scenarios where software can detect the shutdown cause
+ * early enough, or where hardware components (e.g., PMICs) retain this
+ * information across reboots.
+ *
+ * Unlike traditional logging mechanisms that rely on block storage (e.g., NAND,
+ * eMMC), PSCRR enables persistent recording of shutdown reasons using
+ * lightweight non-volatile memory, suitable for early boot diagnostics.
+ *
+ * Purpose:
+ * --------
+ * The primary goal of PSCRR is to help developers and system operators analyze
+ * field failures by tracking the reason for each power state change. This
+ * improves root cause analysis and can aid in future recovery strategies.
+ *
+ * The framework is useful when the system includes backup power (e.g.,
+ * capacitors) and early power-fail detection, allowing software enough time to
+ * record the reason. It can also support hardware-reported reasons, if
+ * available.
+ *
+ * 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?
+ * -------------------
+ * On many embedded systems:
+ *   - Block storage cannot be updated safely during power loss.
+ *   - Power-down may be too fast to allow clean shutdown.
+ *
+ * To enable reliable postmortem diagnostics, alternate non-volatile storage
+ * should be used, such as:
+ *   - Battery-backed RTC scratchpads
+ *   - EEPROM or NVMEM cells
+ *   - FRAM or other persistent low-power memory
+ *
+ * How PSCRR Works:
+ * ----------------
+ *   - A driver detects a protection event (e.g., regulator failure) and calls:
+ *       hw_protection_trigger(PSCR_REGULATOR_FAILURE,
+ *                              REGULATOR_FORCED_SAFETY_SHUTDOWN_WAIT_MS);
+ *   - Or, userspace sets the reason before shutdown:
+ *       echo 3 > /sys/kernel/pscrr/reason
+ *   - On reboot, PSCRR stores the reason using the backend’s .write_reason().
+ *   - The next boot reads it via .read_reason() and exposes it through sysfs.
+ *
+ * If only hardware-backed sources are used (e.g., PMIC or watchdog), the reason
+ * is not written by PSCRR but read from the hardware after the system restarts.
+ */
+
+#include <linux/kernel.h>
+#include <linux/kobject.h>
+#include <linux/module.h>
+#include <linux/mutex.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_backend {
+	const struct pscrr_backend_ops *ops;
+
+	enum psc_reason last_boot_reason;
+};
+
+struct pscrr_core {
+	struct mutex lock;
+	struct pscrr_backend *backend;
+	/* Kobject for sysfs */
+	struct kobject *kobj;
+	struct notifier_block reboot_nb;
+};
+
+static struct pscrr_core g_pscrr = {
+	.lock = __MUTEX_INITIALIZER(g_pscrr.lock),
+};
+
+DEFINE_GUARD(g_pscrr, struct pscrr_core *, mutex_lock(&_T->lock),
+	     mutex_unlock(&_T->lock));
+
+/**
+ * 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)
+{
+	struct pscrr_backend *backend;
+	int ret;
+
+	guard(g_pscrr)(&g_pscrr);
+
+	backend = g_pscrr.backend;
+
+	if (!backend || !backend->ops || !backend->ops->write_reason)
+		return NOTIFY_DONE;
+
+	ret = backend->ops->write_reason(get_psc_reason());
+	if (ret) {
+		pr_err("PSCRR: Failed to store reason %d (%s) at reboot, err=%pe\n",
+		       get_psc_reason(), psc_reason_to_str(get_psc_reason()),
+		       ERR_PTR(ret));
+	} else {
+		pr_info("PSCRR: Stored reason %d (%s) at reboot.\n",
+			get_psc_reason(), psc_reason_to_str(get_psc_reason()));
+	}
+
+	/*
+	 * Return NOTIFY_OK to allow reboot to proceed despite failure, in
+	 * case there is any.
+	 */
+	return NOTIFY_OK;
+}
+
+/*----------------------------------------------------------------------*/
+/* 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 psc_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)
+{
+	struct pscrr_backend *backend;
+	enum psc_reason r;
+
+	guard(g_pscrr)(&g_pscrr);
+
+	backend = g_pscrr.backend;
+
+	if (!backend || !backend->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 (backend->ops->read_reason) {
+		if (backend->ops->read_reason(&r) == 0) {
+			/* Also update our cached value for consistency */
+			set_psc_reason(r);
+		} else {
+			/* If read fails, fallback to cached. */
+			r = get_psc_reason();
+		}
+	} else {
+		r = get_psc_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)
+{
+	struct pscrr_backend *backend;
+	long val;
+	int ret;
+
+	guard(g_pscrr)(&g_pscrr);
+
+	backend = g_pscrr.backend;
+
+	if (!backend || !backend->ops || !backend->ops->write_reason)
+		return -ENODEV;
+
+	ret = kstrtol(buf, 10, &val);
+	if (ret)
+		return ret;
+
+	if (val > U32_MAX)
+		return -ERANGE;
+
+	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 = backend->ops->write_reason((enum psc_reason)val);
+	if (ret) {
+		pr_err("PSCRR: write_reason(%ld) failed, err=%d\n", val, ret);
+		return ret;
+	}
+
+	set_psc_reason((enum psc_reason)val);
+
+	return count; /* number of bytes consumed */
+}
+
+static struct kobj_attribute reason_attr = __ATTR(reason, 0644, 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 psc_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)
+{
+	enum psc_reason last_boot_reason;
+	struct pscrr_backend *backend;
+
+	guard(g_pscrr)(&g_pscrr);
+
+	backend = g_pscrr.backend;
+
+	if (!backend)
+		return -ENODEV;
+
+	last_boot_reason = backend->last_boot_reason;
+
+	return scnprintf(buf, PAGE_SIZE, "%d\n", 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 psc_reason stored_val = PSCR_UNKNOWN;
+	struct pscrr_backend *backend;
+	int ret;
+
+	guard(g_pscrr)(&g_pscrr);
+
+	backend = g_pscrr.backend;
+
+	if (backend) {
+		pr_err("PSCRR: Core is already initialized!\n");
+		return -EBUSY;
+	}
+
+	if (!ops->read_reason) {
+		pr_err("PSCRR: Backend must provide read callbacks\n");
+		return -EINVAL;
+	}
+
+	backend = kzalloc(sizeof(*backend), GFP_KERNEL);
+	if (!backend)
+		return -ENOMEM;
+
+	backend->ops = ops;
+	backend->last_boot_reason = PSCR_UNKNOWN;
+	g_pscrr.backend = backend;
+
+	ret = ops->read_reason(&stored_val);
+	if (!ret) {
+		backend->last_boot_reason = stored_val;
+		pr_info("PSCRR: Initial read_reason: %d (%s)\n",
+			stored_val, psc_reason_to_str(stored_val));
+	} else {
+		pr_warn("PSCRR: read_reason failed, err=%pe\n",
+			ERR_PTR(ret));
+	}
+
+	/* Setup the reboot notifier */
+	g_pscrr.reboot_nb.notifier_call = pscrr_reboot_notifier;
+	ret = register_reboot_notifier(&g_pscrr.reboot_nb);
+	if (ret) {
+		pr_err("PSCRR: Failed to register reboot notifier, err=%pe\n",
+		       ERR_PTR(ret));
+		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");
+		ret = -ENOMEM;
+		goto err_unreg_reboot;
+	}
+
+	ret = sysfs_create_group(g_pscrr.kobj, &pscrr_attr_group);
+	if (ret) {
+		pr_err("PSCRR: Failed to create sysfs group, err=%pe\n",
+		       ERR_PTR(ret));
+		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.backend);
+	g_pscrr.backend = NULL;
+	return ret;
+}
+EXPORT_SYMBOL_GPL(pscrr_core_init);
+
+void pscrr_core_exit(void)
+{
+	guard(g_pscrr)(&g_pscrr);
+
+	if (!g_pscrr.backend)
+		return;
+
+	if (g_pscrr.kobj) {
+		sysfs_remove_group(g_pscrr.kobj, &pscrr_attr_group);
+		kobject_put(g_pscrr.kobj);
+	}
+
+	unregister_reboot_notifier(&g_pscrr.reboot_nb);
+
+	kfree(g_pscrr.backend);
+	g_pscrr.backend = 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..b208f1a12b97
--- /dev/null
+++ b/include/linux/pscrr.h
@@ -0,0 +1,58 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * pscrr.h - Public header for Power State Change Reason Recording (PSCRR).
+ */
+
+#ifndef __PSCRR_H__
+#define __PSCRR_H__
+
+#include <linux/reboot.h>
+
+/**
+ * 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.
+ *
+ * Some systems may have **read-only** hardware-based providers, such as PMICs
+ * (Power Management ICs), that automatically log reset reasons without software
+ * intervention. In such cases, the backend may implement only the `read_reason`
+ * function, while `write_reason` remains unused or unimplemented.
+ *
+ * @write_reason: Function pointer to store the specified `psc_reason` in
+ *                persistent storage. This function is called before a reboot
+ *                to record the last power state change reason. Some hardware
+ *                may not support software-initiated writes, in which case
+ *                this function may not be required.
+ * @read_reason:  Function pointer to retrieve the last stored `psc_reason`
+ *                from persistent storage. This function is called at boot to
+ *                restore the shutdown reason. On read-only hardware providers
+ *                (e.g., PMICs with built-in reset reason registers), this may
+ *                be the only function implemented.
+ */
+struct pscrr_backend_ops {
+	int (*write_reason)(enum psc_reason reason);
+	int (*read_reason)(enum psc_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);
+
+#endif /* __PSCRR_H__ */
-- 
2.39.5


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

* [PATCH v11 4/7] nvmem: provide consumer access to cell size metrics
  2025-06-18 12:02 [PATCH v11 0/7] Introduction of PSCR Framework and Related Components Oleksij Rempel
                   ` (2 preceding siblings ...)
  2025-06-18 12:02 ` [PATCH v11 3/7] power: reset: Introduce PSCR Recording Framework for Non-Volatile Storage Oleksij Rempel
@ 2025-06-18 12:02 ` Oleksij Rempel
  2025-06-18 12:02 ` [PATCH v11 5/7] nvmem: add support for device and sysfs-based cell lookups Oleksij Rempel
                   ` (3 subsequent siblings)
  7 siblings, 0 replies; 14+ messages in thread
From: Oleksij Rempel @ 2025-06-18 12:02 UTC (permalink / raw)
  To: Sebastian Reichel, Srinivas Kandagatla, Benson Leung,
	Tzung-Bi Shih, Daniel Lezcano
  Cc: Oleksij Rempel, Sebastian Reichel, kernel, linux-kernel,
	Liam Girdwood, Mark Brown, Rafael J. Wysocki, Zhang Rui,
	Lukasz Luba, linux-pm, Søren Andersen, Guenter Roeck,
	Matti Vaittinen, Ahmad Fatoum, Andrew Morton, chrome-platform

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>
Reviewed-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
changes v10:
- add Reviewed-by: Sebastian Reichel ...
changes v6:
- update function comment for nvmem_cell_get_size()
---
 drivers/nvmem/core.c           | 29 +++++++++++++++++++++++++++++
 include/linux/nvmem-consumer.h |  7 +++++++
 2 files changed, 36 insertions(+)

diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c
index fd2a9698d1c9..59c295a11d86 100644
--- a/drivers/nvmem/core.c
+++ b/drivers/nvmem/core.c
@@ -1810,6 +1810,35 @@ int nvmem_cell_write(struct nvmem_cell *cell, void *buf, size_t len)
 
 EXPORT_SYMBOL_GPL(nvmem_cell_write);
 
+/**
+ * nvmem_cell_get_size() - Retrieve the storage size of an NVMEM cell.
+ * @cell: Pointer to the NVMEM cell structure.
+ * @bytes: Optional pointer to store the cell size in bytes (can be NULL).
+ * @bits: Optional pointer to store the cell size in bits (can be NULL).
+ *
+ * This function allows consumers to retrieve the size of a specific NVMEM
+ * cell before performing read/write operations. It is useful for validating
+ * buffer sizes to prevent mismatched writes.
+ *
+ * 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] 14+ messages in thread

* [PATCH v11 5/7] nvmem: add support for device and sysfs-based cell lookups
  2025-06-18 12:02 [PATCH v11 0/7] Introduction of PSCR Framework and Related Components Oleksij Rempel
                   ` (3 preceding siblings ...)
  2025-06-18 12:02 ` [PATCH v11 4/7] nvmem: provide consumer access to cell size metrics Oleksij Rempel
@ 2025-06-18 12:02 ` Oleksij Rempel
  2025-07-16 12:48   ` Greg KH
  2025-06-18 12:02 ` [PATCH v11 6/7] power: reset: add PSCR NVMEM Driver for Recording Power State Change Reasons Oleksij Rempel
                   ` (2 subsequent siblings)
  7 siblings, 1 reply; 14+ messages in thread
From: Oleksij Rempel @ 2025-06-18 12:02 UTC (permalink / raw)
  To: Sebastian Reichel, Srinivas Kandagatla, Benson Leung,
	Tzung-Bi Shih, Daniel Lezcano
  Cc: Oleksij Rempel, kernel, linux-kernel, Liam Girdwood, Mark Brown,
	Rafael J. Wysocki, Zhang Rui, Lukasz Luba, linux-pm,
	Søren Andersen, Guenter Roeck, Matti Vaittinen, Ahmad Fatoum,
	Andrew Morton, chrome-platform

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           | 105 +++++++++++++++++++++++++++++++++
 include/linux/nvmem-consumer.h |  18 ++++++
 2 files changed, 123 insertions(+)

diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c
index 59c295a11d86..d310fd8ca9c0 100644
--- a/drivers/nvmem/core.c
+++ b/drivers/nvmem/core.c
@@ -1177,6 +1177,23 @@ 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)
+{
+	if (!name)
+		return ERR_PTR(-EINVAL);
+
+	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
  *
@@ -1490,6 +1507,94 @@ 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[NVMEM_CELL_NAME_MAX];
+
+	mutex_lock(&nvmem_mutex);
+	list_for_each_entry(entry, &nvmem->cells, node) {
+		int len = snprintf(tmp, NVMEM_CELL_NAME_MAX, "%s@%x,%u",
+				   entry->name, entry->offset,
+				   entry->bit_offset);
+
+		if (len >= NVMEM_CELL_NAME_MAX) {
+			pr_warn("nvmem: cell name too long (max %zu bytes): %s\n",
+				NVMEM_CELL_NAME_MAX, sysfs_name);
+			continue;
+		}
+
+		if (len < 0) {
+			pr_warn("nvmem: error formatting cell name\n");
+			continue;
+		}
+
+		if (!strcmp(tmp, sysfs_name)) {
+			mutex_unlock(&nvmem_mutex);
+			return entry;
+		}
+	}
+
+	mutex_unlock(&nvmem_mutex);
+	return NULL;
+}
+
+/**
+ * nvmem_cell_get_by_sysfs_name - Retrieve an NVMEM cell using a sysfs-style
+ *				  name.
+ * @nvmem: Pointer to the `struct nvmem_device` containing the cell.
+ * @sysfs_name: The sysfs-style cell name, formatted as
+ *		"<cell_name>@<offset>,<bits>".
+ *
+ * This function enables dynamic lookup of NVMEM cells via sysfs-style
+ * identifiers. It is useful when devicetree-based lookup is unavailable or when
+ * cells are managed dynamically.
+ *
+ * Example Usage:
+ *   nvmem_cell_get_by_sysfs_name(nvmem, "mycell@0x100,8");
+ *
+ * Return: Pointer to `struct nvmem_cell` on success. On error, an ERR_PTR() is
+ * returned with the appropriate error code.
+ */
+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..2b5e06f457b0 100644
--- a/include/linux/nvmem-consumer.h
+++ b/include/linux/nvmem-consumer.h
@@ -20,6 +20,10 @@ struct nvmem_cell;
 struct nvmem_device;
 struct nvmem_cell_info;
 
+#define NVMEM_CELL_NAME_MAX \
+	(sizeof("very_long_cell_name_with_some_extra_chars_12345678@0x12345678,128"))
+
+
 /**
  * struct nvmem_cell_lookup - cell lookup entry
  *
@@ -52,6 +56,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 +76,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 +116,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 +198,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] 14+ messages in thread

* [PATCH v11 6/7] power: reset: add PSCR NVMEM Driver for Recording Power State Change Reasons
  2025-06-18 12:02 [PATCH v11 0/7] Introduction of PSCR Framework and Related Components Oleksij Rempel
                   ` (4 preceding siblings ...)
  2025-06-18 12:02 ` [PATCH v11 5/7] nvmem: add support for device and sysfs-based cell lookups Oleksij Rempel
@ 2025-06-18 12:02 ` Oleksij Rempel
  2025-06-20 15:56   ` Francesco Valla
  2025-07-16 12:51   ` Greg KH
  2025-06-18 12:02 ` [PATCH v11 7/7] Documentation: Add sysfs documentation for PSCRR reboot reason tracking Oleksij Rempel
  2025-07-14 10:17 ` [PATCH v11 0/7] Introduction of PSCR Framework and Related Components Oleksij Rempel
  7 siblings, 2 replies; 14+ messages in thread
From: Oleksij Rempel @ 2025-06-18 12:02 UTC (permalink / raw)
  To: Sebastian Reichel, Srinivas Kandagatla, Benson Leung,
	Tzung-Bi Shih, Daniel Lezcano
  Cc: Oleksij Rempel, kernel, linux-kernel, Liam Girdwood, Mark Brown,
	Rafael J. Wysocki, Zhang Rui, Lukasz Luba, linux-pm,
	Søren Andersen, Guenter Roeck, Matti Vaittinen, Ahmad Fatoum,
	Andrew Morton, chrome-platform

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 v6:
- rename pscr_reason to psc_reason
changes v5:
- avoid a build against NVMEM=m
changes v4:
- remove devicetree dependencies
---
 drivers/power/reset/Kconfig       |  22 +++
 drivers/power/reset/Makefile      |   1 +
 drivers/power/reset/pscrr-nvmem.c | 254 ++++++++++++++++++++++++++++++
 3 files changed, 277 insertions(+)
 create mode 100644 drivers/power/reset/pscrr-nvmem.c

diff --git a/drivers/power/reset/Kconfig b/drivers/power/reset/Kconfig
index 69e038e20731..3affef932e4d 100644
--- a/drivers/power/reset/Kconfig
+++ b/drivers/power/reset/Kconfig
@@ -354,3 +354,25 @@ menuconfig PSCRR
 	  be recorded unless hardware provides the reset cause.
 
 	  If unsure, say N.
+
+if PSCRR
+
+config PSCRR_NVMEM
+	tristate "Generic NVMEM-based Power State Change Reason Recorder"
+	depends on NVMEM || !NVMEM
+	help
+	  This option enables support for storing power state change reasons
+	  (such as shutdown, reboot, or power failure events) into a designated
+	  NVMEM (Non-Volatile Memory) cell.
+
+	  This feature allows embedded systems to retain power transition
+	  history even after a full system restart or power loss. It is useful
+	  for post-mortem debugging, automated recovery strategies, and
+	  improving system reliability.
+
+	  The NVMEM cell used for storing these reasons can be dynamically
+	  configured via module parameters.
+
+	  If unsure, say N.
+
+endif
diff --git a/drivers/power/reset/Makefile b/drivers/power/reset/Makefile
index 025da19cb335..cc9008c8bb02 100644
--- a/drivers/power/reset/Makefile
+++ b/drivers/power/reset/Makefile
@@ -34,6 +34,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..7d02d989893f
--- /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;
+
+	size_t total_bits;
+	size_t max_val;
+};
+
+static struct pscrr_nvmem_priv *priv;
+
+static int pscrr_nvmem_write_reason(enum psc_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 %zu-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 psc_reason *reason)
+{
+	size_t required_bytes, len;
+	unsigned int val;
+	int ret = 0;
+	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 %zu 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 psc_reason)val;
+
+	pr_debug("PSCRR-nvmem: Read reason => %d (from %zu bytes, %zu 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=%zu 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=%zu\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 (%zu) 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] 14+ messages in thread

* [PATCH v11 7/7] Documentation: Add sysfs documentation for PSCRR reboot reason tracking
  2025-06-18 12:02 [PATCH v11 0/7] Introduction of PSCR Framework and Related Components Oleksij Rempel
                   ` (5 preceding siblings ...)
  2025-06-18 12:02 ` [PATCH v11 6/7] power: reset: add PSCR NVMEM Driver for Recording Power State Change Reasons Oleksij Rempel
@ 2025-06-18 12:02 ` Oleksij Rempel
  2025-07-14 10:17 ` [PATCH v11 0/7] Introduction of PSCR Framework and Related Components Oleksij Rempel
  7 siblings, 0 replies; 14+ messages in thread
From: Oleksij Rempel @ 2025-06-18 12:02 UTC (permalink / raw)
  To: Sebastian Reichel, Srinivas Kandagatla, Benson Leung,
	Tzung-Bi Shih, Daniel Lezcano
  Cc: Oleksij Rempel, Matti Vaittinen, kernel, linux-kernel,
	Liam Girdwood, Mark Brown, Rafael J. Wysocki, Zhang Rui,
	Lukasz Luba, linux-pm, Søren Andersen, Guenter Roeck,
	Ahmad Fatoum, Andrew Morton, chrome-platform

Add documentation for the Power State Change Reason Recorder (PSCRR)
sysfs interface, which allows tracking of system shutdown and reboot
reasons. The documentation provides details on available sysfs entries
under `/sys/kernel/pscrr/`, explaining their functionality, example usage,
and how they interact with different backend storage options (e.g., NVMEM).

Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
Reviewed-by: Matti Vaittinen <mazziesaccount@gmail.com>
---
changes v8:
- Simplify and clarify example sysfs value comments
- Add note that not all values are meaningful on every system
changes v7:
- document expected values
---
 .../ABI/testing/sysfs-kernel-reboot-pscrr     | 74 +++++++++++++++++++
 1 file changed, 74 insertions(+)
 create mode 100644 Documentation/ABI/testing/sysfs-kernel-reboot-pscrr

diff --git a/Documentation/ABI/testing/sysfs-kernel-reboot-pscrr b/Documentation/ABI/testing/sysfs-kernel-reboot-pscrr
new file mode 100644
index 000000000000..96369422ed6e
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-kernel-reboot-pscrr
@@ -0,0 +1,74 @@
+What:		/sys/kernel/pscrr/reason
+Date:		April 2025
+KernelVersion:  6.16
+Contact:	Oleksij Rempel <o.rempel@pengutronix.de>
+Description:
+		This file provides access to the current power state
+		change reason. If supported, the reason may be stored
+		persistently in an NVMEM cell or another backend.
+
+		Reading this file returns an integer representing the
+		current shutdown or reboot cause.
+
+		Writing an integer value to this file sets the reason
+		to be stored for system analysis on next reboot.
+
+		Example usage:
+		  Read:
+			$ cat /sys/kernel/pscrr/reason
+			1   # (Example: Under-voltage shutdown)
+
+		  Write:
+			$ echo 4 > /sys/kernel/pscrr/reason
+			# Sets the reason to 4 (Example: Over-temperature shutdown)
+
+		Note:
+		  Not all systems support all reason values. Hardware or
+		  configuration may limit which reasons are applicable.
+
+		Values are defined in:
+		  - include/linux/reboot.h (enum psc_reason)
+
+		Supported Values:
+		  (from include/linux/reboot.h)
+
+		+-------+---------------------------+--------------------------+
+		| Value | Symbol                    | Description              |
+		+-------+---------------------------+--------------------------+
+		| 0     | PSCR_UNKNOWN              | Unknown or unspecified   |
+		|       |                           | power state change reason|
+		+-------+---------------------------+--------------------------+
+		| 1     | PSCR_UNDER_VOLTAGE        | Supply voltage dropped   |
+		|       |                           | below safe threshold     |
+		+-------+---------------------------+--------------------------+
+		| 2     | PSCR_OVER_CURRENT         | Excessive current draw or|
+		|       |                           | potential short circuit  |
+		+-------+---------------------------+--------------------------+
+		| 3     | PSCR_REGULATOR_FAILURE    | Voltage regulator failure|
+		|       |                           | preventing stable supply |
+		+-------+---------------------------+--------------------------+
+		| 4     | PSCR_OVER_TEMPERATURE     | Unsafe system temperature|
+		|       |                           | detected by sensors      |
+		+-------+---------------------------+--------------------------+
+		| 5     | PSCR_EC_PANIC             | Reboot triggered by EC   |
+		|       |                           | (Embedded Controller)    |
+		+-------+---------------------------+--------------------------+
+
+What:		/sys/kernel/pscrr/reason_boot
+Date:		April 2025
+KernelVersion:  6.16
+Contact:	Oleksij Rempel <o.rempel@pengutronix.de>
+Description:
+		This file provides the last recorded power state change
+		reason from before the current boot. If a supported backend
+		is configured (e.g., NVMEM), the value is retained across
+		reboots.
+
+		Example usage:
+		  Read:
+			$ cat /sys/kernel/pscrr/reason_boot
+			4   # (Example: Over-temperature shutdown)
+
+		Supported values:
+		  Same as /sys/kernel/pscrr/reason (see above).
+
-- 
2.39.5


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

* Re: [PATCH v11 6/7] power: reset: add PSCR NVMEM Driver for Recording Power State Change Reasons
  2025-06-18 12:02 ` [PATCH v11 6/7] power: reset: add PSCR NVMEM Driver for Recording Power State Change Reasons Oleksij Rempel
@ 2025-06-20 15:56   ` Francesco Valla
  2025-07-16 12:51   ` Greg KH
  1 sibling, 0 replies; 14+ messages in thread
From: Francesco Valla @ 2025-06-20 15:56 UTC (permalink / raw)
  To: Sebastian Reichel, Srinivas Kandagatla, Benson Leung,
	Tzung-Bi Shih, Daniel Lezcano, Oleksij Rempel
  Cc: Oleksij Rempel, kernel, linux-kernel, Liam Girdwood, Mark Brown,
	Rafael J. Wysocki, Zhang Rui, Lukasz Luba, linux-pm,
	Søren Andersen, Guenter Roeck, Matti Vaittinen, Ahmad Fatoum,
	Andrew Morton, chrome-platform

Hi Oleksij,

On Wednesday, 18 June 2025 at 14:02:54 Oleksij Rempel <o.rempel@pengutronix.de> wrote:
> 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 v6:
> - rename pscr_reason to psc_reason
> changes v5:
> - avoid a build against NVMEM=m
> changes v4:
> - remove devicetree dependencies
> ---
>  drivers/power/reset/Kconfig       |  22 +++
>  drivers/power/reset/Makefile      |   1 +
>  drivers/power/reset/pscrr-nvmem.c | 254 ++++++++++++++++++++++++++++++
>  3 files changed, 277 insertions(+)
>  create mode 100644 drivers/power/reset/pscrr-nvmem.c
> 

Tested-by: Francesco Valla <francesco@valla.it>

I tested this on a i.MX93 FRDM using the on-board EEPROM as storage and
a single-byte cell. Unfortunately, the on-board RTC does not have a
scratchpad.

PSCR was set in two different ways during subsequent tests:

 - manually from userspace
 - simulating a over-temperature condition through the emul_temp sysfs

In both cases, it was re-read correctly after reboot.

This will be very useful to detect and debug anomalous shutdowns or
reboots on the field.

Thank you!

Regards,
Francesco



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

* Re: [PATCH v11 0/7] Introduction of PSCR Framework and Related Components
  2025-06-18 12:02 [PATCH v11 0/7] Introduction of PSCR Framework and Related Components Oleksij Rempel
                   ` (6 preceding siblings ...)
  2025-06-18 12:02 ` [PATCH v11 7/7] Documentation: Add sysfs documentation for PSCRR reboot reason tracking Oleksij Rempel
@ 2025-07-14 10:17 ` Oleksij Rempel
  2025-07-16 12:34   ` Greg Kroah-Hartman
  7 siblings, 1 reply; 14+ messages in thread
From: Oleksij Rempel @ 2025-07-14 10:17 UTC (permalink / raw)
  To: Sebastian Reichel, Srinivas Kandagatla, Benson Leung,
	Tzung-Bi Shih, Daniel Lezcano, Greg Kroah-Hartman
  Cc: kernel, linux-kernel, Liam Girdwood, Mark Brown,
	Rafael J. Wysocki, Zhang Rui, Lukasz Luba, linux-pm,
	Søren Andersen, Guenter Roeck, Matti Vaittinen, Ahmad Fatoum,
	Andrew Morton, chrome-platform

Hi Greg,

this patch series doesn’t belong to any single existing subsystem. It
spans drivers like power/reset/, touches nvmem, regulator, and adds new
interfaces.

Since there's no clear maintainer fit and the code is self-contained,
I’d like to ask you to pick it up.

The latest version is v11 and has addressed all review comments.

Thanks!
Oleksij

On Wed, Jun 18, 2025 at 02:02:48PM +0200, Oleksij Rempel wrote:
> changes v11:
> - add missing break reported by kernel test robot <lkp@intel.com>
> 
> changes v10:
> - add some add Reviewed-by tags
> - regulator_handle_critical: set pscr = PSCR_UNKNOWN for default case
> - make g_pscrr static
> 
> changes v9:
> - Remove redundant pr_crit() messages before hw_protection_trigger()
> - Replace psc_reason_to_str() switch with static const string array
> - Mark psc_last_reason as static
> 
> changes v8:
> - Use DEFINE_GUARD() and guard(g_pscrr) for scoped locking of the global
>   pscrr_core struct
> - Replace manual mutex_lock/unlock with automatic cleanup-based guard()
>   usage
> - Centralize backend and locking state in struct pscrr_core
> - Prepare for future multi-backend support with clean encapsulation
> - Improve sysfs documentation:
>   * Added full enum psc_reason value table
>   * Simplified example comments, removed redundant "may differ" phrasing
>   * Added note that not all values are supported on all systems
>   * Linked value definitions to include/linux/reboot.h
>   * Added clear read/write usage examples for sysfs entries
> 
> changes v7:
> - document expected values in sysfs documentation
> - make write support optional
> 
> changes v6:
> - add sysfs documentation
> - rebase against latest hw_protection_reboot changes:
>   https://web.git.kernel.org/pub/scm/linux/kernel/git/akpm/mm.git/commit/?h=mm-nonmm-unstable&id=212dd3f6e57f6af8ed3caa23b93adc29334f9652
> - push core part of the reset reason the kernel/reboot.c
> 
> changes v5:
> - fix compile with NVMEM=n and potential issues with NVMEM=m
> 
> changes v4:
> - fix compile with CONFIG_PSCRR=n
> 
> changes v3
> - rework to remove devicetree dependencies
> - extend NVMEM to search devices and cells by names.
> 
> changes v2:
> - rename the framework from PSCR to PSCRR (last R is for Recorder)
> - extend power on reason header and use it to show detected reason on
>   system start and in sysfs.
> - remove "unknow" reason
> - rebase on top of v6.8-rc1
> - yaml fixes
> - zero reason state on boot
> 
> Hello all,
> 
> This patch series introduces the Power State Change Reasons Recording
> (PSCRR) framework and its related components into the kernel. The PSCR
> framework is designed for systems where traditional methods of storing
> power state change reasons, like PMICs or watchdogs, are inadequate. It
> provides a structured way to store reasons for system shutdowns and
> reboots, such as under-voltage or software-triggered events, in
> non-volatile hardware storage.
> 
> These changes are critical for systems requiring detailed postmortem
> analysis and where immediate power-down scenarios limit traditional
> storage options. The framework also assists bootloaders and early-stage
> system components in making informed recovery decisions.
> 
> 
> 
> Oleksij Rempel (7):
>   power: Extend power_on_reason.h for upcoming PSCRR framework
>   reboot: hw_protection_trigger: use standardized numeric
>     shutdown/reboot reasons instead of strings
>   power: reset: Introduce PSCR Recording Framework for Non-Volatile
>     Storage
>   nvmem: provide consumer access to cell size metrics
>   nvmem: add support for device and sysfs-based cell lookups
>   power: reset: add PSCR NVMEM Driver for Recording Power State Change
>     Reasons
>   Documentation: Add sysfs documentation for PSCRR reboot reason
>     tracking
> 
>  .../ABI/testing/sysfs-kernel-reboot-pscrr     |  74 ++++
>  drivers/nvmem/core.c                          | 134 ++++++
>  drivers/platform/chrome/cros_ec_lpc.c         |   2 +-
>  drivers/power/reset/Kconfig                   |  47 ++
>  drivers/power/reset/Makefile                  |   2 +
>  drivers/power/reset/pscrr-nvmem.c             | 254 +++++++++++
>  drivers/power/reset/pscrr.c                   | 405 ++++++++++++++++++
>  drivers/regulator/core.c                      |  16 +-
>  drivers/regulator/irq_helpers.c               |   9 +-
>  drivers/thermal/thermal_core.c                |   3 +-
>  include/linux/nvmem-consumer.h                |  25 ++
>  include/linux/power/power_on_reason.h         |   4 +
>  include/linux/pscrr.h                         |  58 +++
>  include/linux/reboot.h                        |  77 +++-
>  kernel/reboot.c                               |  85 +++-
>  15 files changed, 1173 insertions(+), 22 deletions(-)
>  create mode 100644 Documentation/ABI/testing/sysfs-kernel-reboot-pscrr
>  create mode 100644 drivers/power/reset/pscrr-nvmem.c
>  create mode 100644 drivers/power/reset/pscrr.c
>  create mode 100644 include/linux/pscrr.h
> 
> --
> 2.39.5
> 
> 

-- 
Pengutronix e.K.                           |                             |
Steuerwalder Str. 21                       | http://www.pengutronix.de/  |
31137 Hildesheim, Germany                  | Phone: +49-5121-206917-0    |
Amtsgericht Hildesheim, HRA 2686           | Fax:   +49-5121-206917-5555 |

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

* Re: [PATCH v11 0/7] Introduction of PSCR Framework and Related Components
  2025-07-14 10:17 ` [PATCH v11 0/7] Introduction of PSCR Framework and Related Components Oleksij Rempel
@ 2025-07-16 12:34   ` Greg Kroah-Hartman
  0 siblings, 0 replies; 14+ messages in thread
From: Greg Kroah-Hartman @ 2025-07-16 12:34 UTC (permalink / raw)
  To: Oleksij Rempel
  Cc: Sebastian Reichel, Srinivas Kandagatla, Benson Leung,
	Tzung-Bi Shih, Daniel Lezcano, kernel, linux-kernel,
	Liam Girdwood, Mark Brown, Rafael J. Wysocki, Zhang Rui,
	Lukasz Luba, linux-pm, Søren Andersen, Guenter Roeck,
	Matti Vaittinen, Ahmad Fatoum, Andrew Morton, chrome-platform

On Mon, Jul 14, 2025 at 12:17:48PM +0200, Oleksij Rempel wrote:
> Hi Greg,
> 
> this patch series doesn’t belong to any single existing subsystem. It
> spans drivers like power/reset/, touches nvmem, regulator, and adds new
> interfaces.
> 
> Since there's no clear maintainer fit and the code is self-contained,
> I’d like to ask you to pick it up.
> 
> The latest version is v11 and has addressed all review comments.

I have some comments as I haven't reviewed this yet :)

Let me go download the series and respond to what I found...

thanks,

greg k-h

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

* Re: [PATCH v11 3/7] power: reset: Introduce PSCR Recording Framework for Non-Volatile Storage
  2025-06-18 12:02 ` [PATCH v11 3/7] power: reset: Introduce PSCR Recording Framework for Non-Volatile Storage Oleksij Rempel
@ 2025-07-16 12:43   ` Greg KH
  0 siblings, 0 replies; 14+ messages in thread
From: Greg KH @ 2025-07-16 12:43 UTC (permalink / raw)
  To: Oleksij Rempel, Sebastian Reichel, Srinivas Kandagatla,
	Benson Leung, Tzung-Bi Shih, Daniel Lezcano
  Cc: Oleksij Rempel, kernel, linux-kernel, Liam Girdwood, Mark Brown,
	Rafael J. Wysocki, Zhang Rui, Lukasz Luba, linux-pm,
	Søren Andersen, Guenter Roeck, Matti Vaittinen, Ahmad Fatoum,
	Andrew Morton, chrome-platform

Overall, no real issues, just some very minor ones:

On Wed, Jun 18, 2025 at 02:02:51PM +0200, Oleksij Rempel wrote:
> + * 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

The sysfs documentation is in the ABI file, so it's not needed here.

> +static int pscrr_reboot_notifier(struct notifier_block *nb,
> +				 unsigned long action, void *unused)
> +{
> +	struct pscrr_backend *backend;
> +	int ret;
> +
> +	guard(g_pscrr)(&g_pscrr);
> +
> +	backend = g_pscrr.backend;
> +
> +	if (!backend || !backend->ops || !backend->ops->write_reason)
> +		return NOTIFY_DONE;
> +
> +	ret = backend->ops->write_reason(get_psc_reason());
> +	if (ret) {
> +		pr_err("PSCRR: Failed to store reason %d (%s) at reboot, err=%pe\n",
> +		       get_psc_reason(), psc_reason_to_str(get_psc_reason()),
> +		       ERR_PTR(ret));
> +	} else {
> +		pr_info("PSCRR: Stored reason %d (%s) at reboot.\n",
> +			get_psc_reason(), psc_reason_to_str(get_psc_reason()));

Why print anything?  If this works properly, it should be quiet, right?

> +static ssize_t reason_show(struct kobject *kobj, struct kobj_attribute *attr,
> +			   char *buf)
> +{
> +	struct pscrr_backend *backend;
> +	enum psc_reason r;
> +
> +	guard(g_pscrr)(&g_pscrr);
> +
> +	backend = g_pscrr.backend;
> +
> +	if (!backend || !backend->ops)
> +		return scnprintf(buf, PAGE_SIZE, "No backend registered\n");

So a string, or an int will be returned?  That's crazy, just return an
error here, -ENODEV?

> +
> +	/* If the backend can read from hardware, do so. Otherwise, use our cached value. */
> +	if (backend->ops->read_reason) {
> +		if (backend->ops->read_reason(&r) == 0) {
> +			/* Also update our cached value for consistency */
> +			set_psc_reason(r);
> +		} else {
> +			/* If read fails, fallback to cached. */
> +			r = get_psc_reason();
> +		}
> +	} else {
> +		r = get_psc_reason();
> +	}
> +
> +	return scnprintf(buf, PAGE_SIZE, "%d\n", r);

sysfs files should use sysfs_emit() so you don't get people sending you
patches later on to convert it :)

> +static ssize_t reason_store(struct kobject *kobj, struct kobj_attribute *attr,
> +			    const char *buf, size_t count)
> +{
> +	struct pscrr_backend *backend;
> +	long val;
> +	int ret;
> +
> +	guard(g_pscrr)(&g_pscrr);
> +
> +	backend = g_pscrr.backend;
> +
> +	if (!backend || !backend->ops || !backend->ops->write_reason)
> +		return -ENODEV;
> +
> +	ret = kstrtol(buf, 10, &val);
> +	if (ret)
> +		return ret;
> +
> +	if (val > U32_MAX)
> +		return -ERANGE;
> +
> +	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);

Do not let userspace cause a DoS of kernel log messages just because it
sent you an invalid data range.

> +static struct kobj_attribute reason_attr = __ATTR(reason, 0644, reason_show,
> +						  reason_store);

__ATTR_RW(), right?  If not, why not?

> +static struct kobj_attribute reason_boot_attr =
> +	__ATTR(reason_boot, 0444, reason_boot_show, NULL); /* Read-only */

__ATTR_RO(), right?  Then no comment is needed :)

> +int pscrr_core_init(const struct pscrr_backend_ops *ops)
> +{
> +	enum psc_reason stored_val = PSCR_UNKNOWN;
> +	struct pscrr_backend *backend;
> +	int ret;
> +
> +	guard(g_pscrr)(&g_pscrr);
> +
> +	backend = g_pscrr.backend;
> +
> +	if (backend) {
> +		pr_err("PSCRR: Core is already initialized!\n");

All of the "PSCRR:" stuff should just be set with pr_fmt being defined
at the top of this file, don't put it everywhere manually.

> +		return -EBUSY;
> +	}
> +
> +	if (!ops->read_reason) {
> +		pr_err("PSCRR: Backend must provide read callbacks\n");
> +		return -EINVAL;
> +	}
> +
> +	backend = kzalloc(sizeof(*backend), GFP_KERNEL);
> +	if (!backend)
> +		return -ENOMEM;
> +
> +	backend->ops = ops;
> +	backend->last_boot_reason = PSCR_UNKNOWN;
> +	g_pscrr.backend = backend;
> +
> +	ret = ops->read_reason(&stored_val);
> +	if (!ret) {
> +		backend->last_boot_reason = stored_val;
> +		pr_info("PSCRR: Initial read_reason: %d (%s)\n",
> +			stored_val, psc_reason_to_str(stored_val));

When code works properly, it should be quiet.  Don't spam the boot log
please.

> +	ret = sysfs_create_group(g_pscrr.kobj, &pscrr_attr_group);
> +	if (ret) {
> +		pr_err("PSCRR: Failed to create sysfs group, err=%pe\n",
> +		       ERR_PTR(ret));
> +		goto err_kobj_put;
> +	}
> +
> +	pr_info("PSCRR: initialized successfully.\n");

Same here, be quiet.

> +void pscrr_core_exit(void)
> +{
> +	guard(g_pscrr)(&g_pscrr);
> +
> +	if (!g_pscrr.backend)
> +		return;
> +
> +	if (g_pscrr.kobj) {
> +		sysfs_remove_group(g_pscrr.kobj, &pscrr_attr_group);
> +		kobject_put(g_pscrr.kobj);
> +	}
> +
> +	unregister_reboot_notifier(&g_pscrr.reboot_nb);
> +
> +	kfree(g_pscrr.backend);
> +	g_pscrr.backend = NULL;
> +
> +	pr_info("PSCRR: exited.\n");

Same here, please be quiet.

thanks,

greg k-h

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

* Re: [PATCH v11 5/7] nvmem: add support for device and sysfs-based cell lookups
  2025-06-18 12:02 ` [PATCH v11 5/7] nvmem: add support for device and sysfs-based cell lookups Oleksij Rempel
@ 2025-07-16 12:48   ` Greg KH
  0 siblings, 0 replies; 14+ messages in thread
From: Greg KH @ 2025-07-16 12:48 UTC (permalink / raw)
  To: Oleksij Rempel, Sebastian Reichel, Srinivas Kandagatla,
	Benson Leung, Tzung-Bi Shih, Daniel Lezcano
  Cc: Oleksij Rempel, kernel, linux-kernel, Liam Girdwood, Mark Brown,
	Rafael J. Wysocki, Zhang Rui, Lukasz Luba, linux-pm,
	Søren Andersen, Guenter Roeck, Matti Vaittinen, Ahmad Fatoum,
	Andrew Morton, chrome-platform

On Wed, Jun 18, 2025 at 02:02:53PM +0200, Oleksij Rempel wrote:
> 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           | 105 +++++++++++++++++++++++++++++++++
>  include/linux/nvmem-consumer.h |  18 ++++++
>  2 files changed, 123 insertions(+)
> 
> diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c
> index 59c295a11d86..d310fd8ca9c0 100644
> --- a/drivers/nvmem/core.c
> +++ b/drivers/nvmem/core.c
> @@ -1177,6 +1177,23 @@ 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)
> +{
> +	if (!name)
> +		return ERR_PTR(-EINVAL);
> +
> +	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
>   *
> @@ -1490,6 +1507,94 @@ 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[NVMEM_CELL_NAME_MAX];

That's a lot of bytes on the stack, are you sure?

> +
> +	mutex_lock(&nvmem_mutex);

Use a guard?

> +	list_for_each_entry(entry, &nvmem->cells, node) {
> +		int len = snprintf(tmp, NVMEM_CELL_NAME_MAX, "%s@%x,%u",

You have a naming scheme here that you now need to keep in sync with a
naming scheme else where.  Why?  How is that going to happen?

sysfs names are NOT static, or deterministic, and will be totally random
depending on the boot order and the phase of the moon.  Attempting to
look, within the kernel, at a sysfs path name is almost always the sign
of something gone wrong.

Please don't do this, it will only cause headaches and issues over time,
trust me.

> +				   entry->name, entry->offset,
> +				   entry->bit_offset);
> +
> +		if (len >= NVMEM_CELL_NAME_MAX) {
> +			pr_warn("nvmem: cell name too long (max %zu bytes): %s\n",
> +				NVMEM_CELL_NAME_MAX, sysfs_name);

What can a user do about this?  

> +			continue;

Shouldn't you have errored out?

> +		}
> +
> +		if (len < 0) {
> +			pr_warn("nvmem: error formatting cell name\n");
> +			continue;

No error?

> +		}
> +
> +		if (!strcmp(tmp, sysfs_name)) {
> +			mutex_unlock(&nvmem_mutex);
> +			return entry;
> +		}
> +	}
> +
> +	mutex_unlock(&nvmem_mutex);
> +	return NULL;
> +}
> +
> +/**
> + * nvmem_cell_get_by_sysfs_name - Retrieve an NVMEM cell using a sysfs-style
> + *				  name.
> + * @nvmem: Pointer to the `struct nvmem_device` containing the cell.
> + * @sysfs_name: The sysfs-style cell name, formatted as
> + *		"<cell_name>@<offset>,<bits>".

Again, who is keeping this naming scheme in sync?  And what happens when
the firmware changes it?

Please don't.

> + *
> + * This function enables dynamic lookup of NVMEM cells via sysfs-style
> + * identifiers. It is useful when devicetree-based lookup is unavailable or when
> + * cells are managed dynamically.
> + *
> + * Example Usage:
> + *   nvmem_cell_get_by_sysfs_name(nvmem, "mycell@0x100,8");
> + *
> + * Return: Pointer to `struct nvmem_cell` on success. On error, an ERR_PTR() is
> + * returned with the appropriate error code.
> + */
> +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);

Why are you messing with a module owner field, when no other nvmem call
uses that?  Who will decrement it?  Are you sure you didn't just race
with it being unloaded?  module owner fields are almost always wrong
these days.

thanks,

greg k-h

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

* Re: [PATCH v11 6/7] power: reset: add PSCR NVMEM Driver for Recording Power State Change Reasons
  2025-06-18 12:02 ` [PATCH v11 6/7] power: reset: add PSCR NVMEM Driver for Recording Power State Change Reasons Oleksij Rempel
  2025-06-20 15:56   ` Francesco Valla
@ 2025-07-16 12:51   ` Greg KH
  1 sibling, 0 replies; 14+ messages in thread
From: Greg KH @ 2025-07-16 12:51 UTC (permalink / raw)
  To: Oleksij Rempel, Sebastian Reichel, Srinivas Kandagatla,
	Benson Leung, Tzung-Bi Shih, Daniel Lezcano
  Cc: Oleksij Rempel, kernel, linux-kernel, Liam Girdwood, Mark Brown,
	Rafael J. Wysocki, Zhang Rui, Lukasz Luba, linux-pm,
	Søren Andersen, Guenter Roeck, Matti Vaittinen, Ahmad Fatoum,
	Andrew Morton, chrome-platform

On Wed, Jun 18, 2025 at 02:02:54PM +0200, Oleksij Rempel wrote:
> 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>
> Tested-by: Francesco Valla <francesco@valla.it>
> ---
> changes v6:
> - rename pscr_reason to psc_reason
> changes v5:
> - avoid a build against NVMEM=m
> changes v4:
> - remove devicetree dependencies
> ---
>  drivers/power/reset/Kconfig       |  22 +++
>  drivers/power/reset/Makefile      |   1 +
>  drivers/power/reset/pscrr-nvmem.c | 254 ++++++++++++++++++++++++++++++
>  3 files changed, 277 insertions(+)
>  create mode 100644 drivers/power/reset/pscrr-nvmem.c
> 
> diff --git a/drivers/power/reset/Kconfig b/drivers/power/reset/Kconfig
> index 69e038e20731..3affef932e4d 100644
> --- a/drivers/power/reset/Kconfig
> +++ b/drivers/power/reset/Kconfig
> @@ -354,3 +354,25 @@ menuconfig PSCRR
>  	  be recorded unless hardware provides the reset cause.
>  
>  	  If unsure, say N.
> +
> +if PSCRR
> +
> +config PSCRR_NVMEM
> +	tristate "Generic NVMEM-based Power State Change Reason Recorder"
> +	depends on NVMEM || !NVMEM
> +	help
> +	  This option enables support for storing power state change reasons
> +	  (such as shutdown, reboot, or power failure events) into a designated
> +	  NVMEM (Non-Volatile Memory) cell.
> +
> +	  This feature allows embedded systems to retain power transition
> +	  history even after a full system restart or power loss. It is useful
> +	  for post-mortem debugging, automated recovery strategies, and
> +	  improving system reliability.
> +
> +	  The NVMEM cell used for storing these reasons can be dynamically
> +	  configured via module parameters.

Module parameters?  Why?

> +
> +	  If unsure, say N.
> +
> +endif
> diff --git a/drivers/power/reset/Makefile b/drivers/power/reset/Makefile
> index 025da19cb335..cc9008c8bb02 100644
> --- a/drivers/power/reset/Makefile
> +++ b/drivers/power/reset/Makefile
> @@ -34,6 +34,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..7d02d989893f
> --- /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

Ugh, no, this isn't the 1990's anymore.  Module parameters don't make
sense for dynamic systems with multiple devices and names and the like.
Please use a proper api for this instead of this static one.

> +/*
> + * 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)");

Again, don't.  Please don't.

You now only have one name, and one device.  WHat happens to the next
system that has multiple ones?  And who is enforcing this arbitrary
naming scheme?  Why is that now a user/kernel abi that can never change?

> +	pr_info("PSCRR-nvmem: Loaded (nvmem=%s, cell=%s), can store 0..%zu\n",
> +		nvmem_name, cell_name, priv->max_val);

Again, when code works, it is quiet.

> +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");

Again, please be quiet.

And use pr_fmt().

thanks,

greg k-h

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

end of thread, other threads:[~2025-07-16 12:51 UTC | newest]

Thread overview: 14+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-06-18 12:02 [PATCH v11 0/7] Introduction of PSCR Framework and Related Components Oleksij Rempel
2025-06-18 12:02 ` [PATCH v11 1/7] power: Extend power_on_reason.h for upcoming PSCRR framework Oleksij Rempel
2025-06-18 12:02 ` [PATCH v11 2/7] reboot: hw_protection_trigger: use standardized numeric shutdown/reboot reasons instead of strings Oleksij Rempel
2025-06-18 12:02 ` [PATCH v11 3/7] power: reset: Introduce PSCR Recording Framework for Non-Volatile Storage Oleksij Rempel
2025-07-16 12:43   ` Greg KH
2025-06-18 12:02 ` [PATCH v11 4/7] nvmem: provide consumer access to cell size metrics Oleksij Rempel
2025-06-18 12:02 ` [PATCH v11 5/7] nvmem: add support for device and sysfs-based cell lookups Oleksij Rempel
2025-07-16 12:48   ` Greg KH
2025-06-18 12:02 ` [PATCH v11 6/7] power: reset: add PSCR NVMEM Driver for Recording Power State Change Reasons Oleksij Rempel
2025-06-20 15:56   ` Francesco Valla
2025-07-16 12:51   ` Greg KH
2025-06-18 12:02 ` [PATCH v11 7/7] Documentation: Add sysfs documentation for PSCRR reboot reason tracking Oleksij Rempel
2025-07-14 10:17 ` [PATCH v11 0/7] Introduction of PSCR Framework and Related Components Oleksij Rempel
2025-07-16 12:34   ` Greg Kroah-Hartman

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).