* [PATCH v12 1/6] power: Extend power_on_reason.h for upcoming PSCRR framework
2026-07-31 9:59 [PATCH v12 0/6] Introduce the Power State Change Reasons Recording (PSCRR) framework Oleksij Rempel
@ 2026-07-31 9:59 ` Oleksij Rempel
2026-07-31 9:59 ` [PATCH v12 2/6] reboot: hw_protection_trigger: use standardized numeric shutdown/reboot reasons instead of strings Oleksij Rempel
` (5 subsequent siblings)
6 siblings, 0 replies; 9+ messages in thread
From: Oleksij Rempel @ 2026-07-31 9:59 UTC (permalink / raw)
To: Sebastian Reichel, 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, Kees Cook,
Faruque Ansari, Francesco Valla, Greg Kroah-Hartman,
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.
- POWER_ON_REASON_EXTERNAL for an external reset source.
Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
Reviewed-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
changes v12:
- add POWER_ON_REASON_EXTERNAL
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
(cherry picked from commit a6fb08a81864e2ed17cebba9f249af3d44e42c3f)
---
include/linux/power/power_on_reason.h | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/include/linux/power/power_on_reason.h b/include/linux/power/power_on_reason.h
index 95a1ec0c403c..13e61ace14f5 100644
--- a/include/linux/power/power_on_reason.h
+++ b/include/linux/power/power_on_reason.h
@@ -15,5 +15,10 @@
#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"
+#define POWER_ON_REASON_EXTERNAL "external reset"
#endif /* POWER_ON_REASON_H */
--
2.47.3
^ permalink raw reply related [flat|nested] 9+ messages in thread* [PATCH v12 2/6] reboot: hw_protection_trigger: use standardized numeric shutdown/reboot reasons instead of strings
2026-07-31 9:59 [PATCH v12 0/6] Introduce the Power State Change Reasons Recording (PSCRR) framework Oleksij Rempel
2026-07-31 9:59 ` [PATCH v12 1/6] power: Extend power_on_reason.h for upcoming PSCRR framework Oleksij Rempel
@ 2026-07-31 9:59 ` Oleksij Rempel
2026-07-31 9:59 ` [PATCH v12 3/6] reboot: add parsable tokens for power state change reasons Oleksij Rempel
` (4 subsequent siblings)
6 siblings, 0 replies; 9+ messages in thread
From: Oleksij Rempel @ 2026-07-31 9:59 UTC (permalink / raw)
To: Sebastian Reichel, 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, Kees Cook, Faruque Ansari, Francesco Valla,
Greg Kroah-Hartman, 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 v12:
- drop Tested-by: Faruque Ansari and Francesco Valla; the end-to-end
recording flow they tested has been reworked (multi-provider) and the
recorder driver is no longer part of the series - re-testing is welcome
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
(cherry picked from commit 29591d88486c392e2264de73c4e90106f181b33c)
---
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 | 78 +++++++++++++++++-
kernel/reboot.c | 112 +++++++++++++++++++++++++-
6 files changed, 198 insertions(+), 22 deletions(-)
diff --git a/drivers/platform/chrome/cros_ec_lpc.c b/drivers/platform/chrome/cros_ec_lpc.c
index 78cfff80cdea..00e98d12ddbc 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 dc5d67767336..2075cf838bd4 100644
--- a/drivers/regulator/core.c
+++ b/drivers/regulator/core.c
@@ -5532,26 +5532,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 5b3cfac28667..712e0ce840e7 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 28a20d4b475c..df8fa11f78dc 100644
--- a/drivers/thermal/thermal_core.c
+++ b/drivers/thermal/thermal_core.c
@@ -319,11 +319,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..08a7549bbc40 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,16 @@ 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);
+enum psc_reason get_psc_first_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 695c33e75efd..29c68441fa53 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,8 @@ 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;
+static enum psc_reason psc_first_reason = PSCR_UNKNOWN;
struct sys_off_handler {
struct notifier_block nb;
@@ -1010,10 +1013,108 @@ 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);
+
+/**
+ * get_psc_first_reason - Retrieve the first power state change reason.
+ *
+ * Returns the first meaningful (non-PSCR_UNKNOWN) reason recorded during this
+ * boot, i.e. the root cause, as opposed to get_psc_reason() which returns the
+ * most recent one. A later event (a watchdog pretimeout, a kernel panic, ...)
+ * updates the last reason but leaves this one untouched, so a recorder using
+ * the "first" record policy can preserve the original cause.
+ *
+ * Return: A value from `enum psc_reason`.
+ */
+enum psc_reason get_psc_first_reason(void)
+{
+ return READ_ONCE(psc_first_reason);
+}
+EXPORT_SYMBOL_GPL(get_psc_first_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);
+
+ /*
+ * Latch the first meaningful reason of this boot as the root cause, so
+ * that a later event overwriting the last reason does not hide it from
+ * a recorder using the "first" record policy.
+ */
+ if (reason != PSCR_UNKNOWN &&
+ READ_ONCE(psc_first_reason) == PSCR_UNKNOWN)
+ WRITE_ONCE(psc_first_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 +1126,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 +1134,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.47.3
^ permalink raw reply related [flat|nested] 9+ messages in thread* [PATCH v12 3/6] reboot: add parsable tokens for power state change reasons
2026-07-31 9:59 [PATCH v12 0/6] Introduce the Power State Change Reasons Recording (PSCRR) framework Oleksij Rempel
2026-07-31 9:59 ` [PATCH v12 1/6] power: Extend power_on_reason.h for upcoming PSCRR framework Oleksij Rempel
2026-07-31 9:59 ` [PATCH v12 2/6] reboot: hw_protection_trigger: use standardized numeric shutdown/reboot reasons instead of strings Oleksij Rempel
@ 2026-07-31 9:59 ` Oleksij Rempel
2026-07-31 9:59 ` [PATCH v12 4/6] reboot: extend psc_reason with power-on and reset causes Oleksij Rempel
` (3 subsequent siblings)
6 siblings, 0 replies; 9+ messages in thread
From: Oleksij Rempel @ 2026-07-31 9:59 UTC (permalink / raw)
To: Sebastian Reichel, 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, Kees Cook, Faruque Ansari, Francesco Valla,
Greg Kroah-Hartman, chrome-platform
psc_reason_to_str() returns human-readable labels that contain spaces
(e.g. "over temperature"). Those are fine for logs but unusable as
values in a space-separated sysfs list or as a write target.
Extend the single reason descriptor table with a stable, space-free
token next to the existing label, and add psc_reason_to_token() and
psc_reason_from_token() so consumers can emit and parse reasons without
inventing their own string table.
Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
---
changes v12:
- new patch
---
include/linux/reboot.h | 2 ++
kernel/reboot.c | 77 +++++++++++++++++++++++++++++++++---------
2 files changed, 63 insertions(+), 16 deletions(-)
diff --git a/include/linux/reboot.h b/include/linux/reboot.h
index 08a7549bbc40..4c5327dd7645 100644
--- a/include/linux/reboot.h
+++ b/include/linux/reboot.h
@@ -244,6 +244,8 @@ enum psc_reason {
#define PSCR_MAX_REASON (PSCR_REASON_COUNT - 1)
const char *psc_reason_to_str(enum psc_reason reason);
+const char *psc_reason_to_token(enum psc_reason reason);
+int psc_reason_from_token(const char *token, enum psc_reason *reason);
/**
* enum hw_protection_action - Hardware protection action
diff --git a/kernel/reboot.c b/kernel/reboot.c
index 29c68441fa53..929496be20c0 100644
--- a/kernel/reboot.c
+++ b/kernel/reboot.c
@@ -1083,34 +1083,79 @@ void set_psc_reason(enum psc_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,
+/**
+ * struct psc_reason_desc - Descriptor for a power state change reason.
+ * @token: Stable, parsable identifier without spaces (e.g. "over-temperature").
+ * Suitable for use in sysfs values and as a user/kernel contract.
+ * @label: Human-readable description (e.g. "over temperature"), for logs.
+ */
+struct psc_reason_desc {
+ const char *token;
+ const char *label;
+};
+
+static const struct psc_reason_desc psc_reason_descs[] = {
+ [PSCR_UNKNOWN] = { "unknown", POWER_ON_REASON_UNKNOWN },
+ [PSCR_UNDER_VOLTAGE] = { "under-voltage", POWER_ON_REASON_BROWN_OUT },
+ [PSCR_OVER_CURRENT] = { "over-current", POWER_ON_REASON_OVER_CURRENT },
+ [PSCR_REGULATOR_FAILURE] = { "regulator-failure", POWER_ON_REASON_REGULATOR_FAILURE },
+ [PSCR_OVER_TEMPERATURE] = { "over-temperature", POWER_ON_REASON_OVER_TEMPERATURE },
+ [PSCR_EC_PANIC] = { "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.
+ * psc_reason_to_str - Human-readable label for a power state change reason.
+ * @reason: The `psc_reason` value to convert.
*
- * Return:
- * - A string corresponding to the given `psc_reason` value.
- * - `"Invalid"` if the value is not recognized.
+ * Return: The label string, or "Invalid" if @reason is out of range. For a
+ * stable, parsable form use psc_reason_to_token() instead.
*/
const char *psc_reason_to_str(enum psc_reason reason)
{
if (reason < 0 || reason >= PSCR_REASON_COUNT)
return "Invalid";
- return pscr_reason_strs[reason];
+ return psc_reason_descs[reason].label;
}
EXPORT_SYMBOL_GPL(psc_reason_to_str);
+/**
+ * psc_reason_to_token - Stable, parsable token for a power state change reason.
+ * @reason: The `psc_reason` value to convert.
+ *
+ * Return: The token string (no spaces), or "invalid" if @reason is out of
+ * range. Round-trips with psc_reason_from_token().
+ */
+const char *psc_reason_to_token(enum psc_reason reason)
+{
+ if (reason < 0 || reason >= PSCR_REASON_COUNT)
+ return "invalid";
+ return psc_reason_descs[reason].token;
+}
+EXPORT_SYMBOL_GPL(psc_reason_to_token);
+
+/**
+ * psc_reason_from_token - Parse a reason token into a `psc_reason` value.
+ * @token: A token as returned by psc_reason_to_token(). A trailing newline is
+ * tolerated.
+ * @reason: Output; set on success.
+ *
+ * Return: 0 on success or -EINVAL if @token matches no known reason.
+ */
+int psc_reason_from_token(const char *token, enum psc_reason *reason)
+{
+ int i;
+
+ for (i = 0; i < PSCR_REASON_COUNT; i++) {
+ if (sysfs_streq(token, psc_reason_descs[i].token)) {
+ *reason = i;
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(psc_reason_from_token);
+
/**
* __hw_protection_trigger - Trigger an emergency system shutdown or reboot
*
--
2.47.3
^ permalink raw reply related [flat|nested] 9+ messages in thread* [PATCH v12 4/6] reboot: extend psc_reason with power-on and reset causes
2026-07-31 9:59 [PATCH v12 0/6] Introduce the Power State Change Reasons Recording (PSCRR) framework Oleksij Rempel
` (2 preceding siblings ...)
2026-07-31 9:59 ` [PATCH v12 3/6] reboot: add parsable tokens for power state change reasons Oleksij Rempel
@ 2026-07-31 9:59 ` Oleksij Rempel
2026-07-31 9:59 ` [PATCH v12 5/6] power: reset: Introduce PSCR Recording Framework for Non-Volatile Storage Oleksij Rempel
` (2 subsequent siblings)
6 siblings, 0 replies; 9+ messages in thread
From: Oleksij Rempel @ 2026-07-31 9:59 UTC (permalink / raw)
To: Sebastian Reichel, 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, Kees Cook, Faruque Ansari, Francesco Valla,
Greg Kroah-Hartman, chrome-platform
psc_reason covered only the protection reasons. Extend it to mirror the
full POWER_ON_REASON_* vocabulary - power-on, watchdog, software,
external, RTC, reset button, CPU clock and crystal failures - so a
provider can report why the system last powered on, not only why it was
shut down. New values are appended to keep the existing numeric ABI.
Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
---
changes v12:
- new patch
---
include/linux/reboot.h | 28 ++++++++++++++++++++++++++++
kernel/reboot.c | 8 ++++++++
2 files changed, 36 insertions(+)
diff --git a/include/linux/reboot.h b/include/linux/reboot.h
index 4c5327dd7645..13f004ad1066 100644
--- a/include/linux/reboot.h
+++ b/include/linux/reboot.h
@@ -225,9 +225,28 @@ extern void orderly_reboot(void);
* management errors, or an unrecoverable hardware fault requiring
* immediate response.
*
+ * @PSCR_POWER_ON: Regular cold power-on (e.g. via a PMIC power-on request).
+ *
+ * @PSCR_WATCHDOG: Reset caused by a watchdog timeout.
+ *
+ * @PSCR_SOFTWARE: Software-initiated reset or reboot.
+ *
+ * @PSCR_EXTERNAL: Reset asserted through an external pin or reset input.
+ *
+ * @PSCR_RTC: Wake-up or power-on triggered by the RTC.
+ *
+ * @PSCR_RESET_BUTTON: Reset triggered by a user reset button.
+ *
+ * @PSCR_CPU_CLK_FAIL: Reset caused by a CPU clock failure.
+ *
+ * @PSCR_XTAL_FAIL: Reset caused by a crystal oscillator failure.
+ *
* @PSCR_REASON_COUNT: Number of defined power state change reasons. This
* value is useful for range checking and potential future extensions
* while maintaining compatibility.
+ *
+ * The reasons mirror the POWER_ON_REASON_* strings in
+ * <linux/power/power_on_reason.h>; keep the two in sync when extending.
*/
enum psc_reason {
PSCR_UNKNOWN,
@@ -236,6 +255,15 @@ enum psc_reason {
PSCR_REGULATOR_FAILURE,
PSCR_OVER_TEMPERATURE,
PSCR_EC_PANIC,
+ /* Append new reasons here; the numeric order is ABI. */
+ PSCR_POWER_ON,
+ PSCR_WATCHDOG,
+ PSCR_SOFTWARE,
+ PSCR_EXTERNAL,
+ PSCR_RTC,
+ PSCR_RESET_BUTTON,
+ PSCR_CPU_CLK_FAIL,
+ PSCR_XTAL_FAIL,
/* Number of reasons */
PSCR_REASON_COUNT,
diff --git a/kernel/reboot.c b/kernel/reboot.c
index 929496be20c0..3e262c4450c5 100644
--- a/kernel/reboot.c
+++ b/kernel/reboot.c
@@ -1101,6 +1101,14 @@ static const struct psc_reason_desc psc_reason_descs[] = {
[PSCR_REGULATOR_FAILURE] = { "regulator-failure", POWER_ON_REASON_REGULATOR_FAILURE },
[PSCR_OVER_TEMPERATURE] = { "over-temperature", POWER_ON_REASON_OVER_TEMPERATURE },
[PSCR_EC_PANIC] = { "ec-panic", POWER_ON_REASON_EC_PANIC },
+ [PSCR_POWER_ON] = { "power-on", POWER_ON_REASON_REGULAR },
+ [PSCR_WATCHDOG] = { "watchdog", POWER_ON_REASON_WATCHDOG },
+ [PSCR_SOFTWARE] = { "software", POWER_ON_REASON_SOFTWARE },
+ [PSCR_EXTERNAL] = { "external", POWER_ON_REASON_EXTERNAL },
+ [PSCR_RTC] = { "rtc", POWER_ON_REASON_RTC },
+ [PSCR_RESET_BUTTON] = { "reset-button", POWER_ON_REASON_RST_BTN },
+ [PSCR_CPU_CLK_FAIL] = { "cpu-clock-failure", POWER_ON_REASON_CPU_CLK_FAIL },
+ [PSCR_XTAL_FAIL] = { "crystal-failure", POWER_ON_REASON_XTAL_FAIL },
};
/**
--
2.47.3
^ permalink raw reply related [flat|nested] 9+ messages in thread* [PATCH v12 5/6] power: reset: Introduce PSCR Recording Framework for Non-Volatile Storage
2026-07-31 9:59 [PATCH v12 0/6] Introduce the Power State Change Reasons Recording (PSCRR) framework Oleksij Rempel
` (3 preceding siblings ...)
2026-07-31 9:59 ` [PATCH v12 4/6] reboot: extend psc_reason with power-on and reset causes Oleksij Rempel
@ 2026-07-31 9:59 ` Oleksij Rempel
2026-07-31 9:59 ` [PATCH v12 6/6] Documentation: Add sysfs documentation for PSCRR Oleksij Rempel
2026-08-05 9:34 ` [PATCH v12 0/6] Introduce the Power State Change Reasons Recording (PSCRR) framework Faruque Ansari
6 siblings, 0 replies; 9+ messages in thread
From: Oleksij Rempel @ 2026-07-31 9:59 UTC (permalink / raw)
To: Sebastian Reichel, 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, Kees Cook, Faruque Ansari, Francesco Valla,
Greg Kroah-Hartman, chrome-platform
Introduce the Power State Change Reasons Recording (PSCRR) framework: a
generic mechanism to record why the last power state change (shutdown or
reboot) happened - under-voltage, thermal, watchdog, software-triggered,
etc. - so a postmortem reason survives a reboot or an abrupt power loss.
PSCRR is built around providers. A provider is either a hardware reason
source (a PMIC, SoC reset controller or watchdog exposing a reset cause)
or a recorder that persists the reason across a power cycle (an NVMEM or
RTC scratch cell). Each provider gets a directory under
/sys/kernel/pscrr/providerN/ exposing its name, backing device, the set
of observed reasons (as tokens), its capabilities, the reasons it
supports and - for recorders - a record policy. The reason set is
deliberately not collapsed to a single winning cause, since resets are
often multi-causal.
Reasons are the numeric enum psc_reason values from reboot.h, shared with
the POWER_ON_REASON_* vocabulary, so they store compactly in small
battery-backed cells. The current reason (get/set_psc_reason(), set by
the thermal/regulator/hw_protection paths) is written to every recorder
from the reboot notifier.
Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
---
changes v12:
- rework into a multi-provider framework: one sysfs directory per
provider instead of a single backend
- add per-provider capabilities, supported_reasons and record policy
- register providers through a const ops table via
(devm_)pscrr_provider_register()
- report reasons as parsable tokens
- move the sources under drivers/power/reset/pscrr/
- add a MAINTAINERS entry; PSCRR depends on POWER_RESET
- drop Reviewed-by: Matti Vaittinen; the framework was substantially
reworked since it was given
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
---
MAINTAINERS | 10 +
drivers/power/reset/Kconfig | 2 +
drivers/power/reset/Makefile | 1 +
drivers/power/reset/pscrr/Kconfig | 33 ++
drivers/power/reset/pscrr/Makefile | 2 +
drivers/power/reset/pscrr/pscrr.c | 634 +++++++++++++++++++++++++++++
include/linux/pscrr.h | 107 +++++
7 files changed, 789 insertions(+)
create mode 100644 drivers/power/reset/pscrr/Kconfig
create mode 100644 drivers/power/reset/pscrr/Makefile
create mode 100644 drivers/power/reset/pscrr/pscrr.c
create mode 100644 include/linux/pscrr.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 15011f5752a9..35195d9d4c4e 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -21526,6 +21526,16 @@ F: Documentation/devicetree/bindings/connector/pcie-m2-e-connector.yaml
F: Documentation/devicetree/bindings/connector/pcie-m2-m-connector.yaml
F: drivers/power/sequencing/pwrseq-pcie-m2.c
+POWER STATE CHANGE REASON RECORDING (PSCRR)
+M: Oleksij Rempel <o.rempel@pengutronix.de>
+R: Pengutronix Kernel Team <kernel@pengutronix.de>
+L: linux-pm@vger.kernel.org
+S: Maintained
+F: Documentation/ABI/testing/sysfs-kernel-pscrr
+F: drivers/power/reset/pscrr/
+F: include/linux/pscrr.h
+F: tools/testing/selftests/pscrr/
+
POWER STATE COORDINATION INTERFACE (PSCI)
M: Mark Rutland <mark.rutland@arm.com>
M: Lorenzo Pieralisi <lpieralisi@kernel.org>
diff --git a/drivers/power/reset/Kconfig b/drivers/power/reset/Kconfig
index 124afb99febe..83e87794d976 100644
--- a/drivers/power/reset/Kconfig
+++ b/drivers/power/reset/Kconfig
@@ -365,3 +365,5 @@ config POWER_RESET_QEMU_VIRT_CTRL
provides this controller, such as the m68k virt machine.
endif
+
+source "drivers/power/reset/pscrr/Kconfig"
diff --git a/drivers/power/reset/Makefile b/drivers/power/reset/Makefile
index d7ae97241a83..60cf967cde21 100644
--- a/drivers/power/reset/Makefile
+++ b/drivers/power/reset/Makefile
@@ -36,6 +36,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/
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/Kconfig b/drivers/power/reset/pscrr/Kconfig
new file mode 100644
index 000000000000..72de82731b53
--- /dev/null
+++ b/drivers/power/reset/pscrr/Kconfig
@@ -0,0 +1,33 @@
+# SPDX-License-Identifier: GPL-2.0-only
+menuconfig PSCRR
+ bool "Power State Change Reasons Recording (PSCRR) Framework"
+ depends on POWER_RESET
+ help
+ Enables the Power State Change Reasons Recording (PSCRR) framework.
+
+ PSCRR records why the system last shut down or rebooted into
+ non-volatile storage, so the reason survives the reset and can be
+ read by the bootloader or early user space on the next boot. Reasons
+ come from software (thermal or regulator hardware-protection events,
+ a watchdog pretimeout, a kernel panic, a controlled reboot) or from
+ hardware reset-cause registers (PMIC, SoC reset controller, watchdog).
+
+ It helps whenever the real cause would otherwise be lost or ambiguous
+ on the next boot, for example:
+
+ - the reset is abrupt and block storage (eMMC/NAND) may not survive
+ it; on systems that can detect the event in time - such as an
+ imminent power loss with a short backup-capacitor window - the
+ reason can still be persisted first;
+
+ - the hardware does not preserve the cause and the boot looks like a
+ plain power-on reset, e.g. a watchdog pretimeout that escalates
+ into a panic reboot would otherwise be indistinguishable from a
+ POR.
+
+ The framework is extensible and exposes software-defined and
+ hardware-reported reasons through one interface. Sudden power cuts,
+ CPU freezes or other uncontrolled resets may still go unrecorded
+ unless hardware provides the reset cause.
+
+ If unsure, say N.
diff --git a/drivers/power/reset/pscrr/Makefile b/drivers/power/reset/pscrr/Makefile
new file mode 100644
index 000000000000..e5530a858971
--- /dev/null
+++ b/drivers/power/reset/pscrr/Makefile
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0
+obj-$(CONFIG_PSCRR) += pscrr.o
diff --git a/drivers/power/reset/pscrr/pscrr.c b/drivers/power/reset/pscrr/pscrr.c
new file mode 100644
index 000000000000..6f23f4c4f590
--- /dev/null
+++ b/drivers/power/reset/pscrr/pscrr.c
@@ -0,0 +1,634 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * pscrr.c - Core Power State Change Reason Recording
+ *
+ * PSCRR records why the last power state change (shutdown/reboot) happened.
+ * Reasons come from providers: hardware reset-cause registers (PMIC, SoC reset
+ * controller, watchdog), persistent recorders (NVMEM/RTC scratch), the
+ * bootloader's device-tree /chosen/reset-source (a built-in provider here), or
+ * test stubs. Each provider gets a directory under /sys/kernel/pscrr/ and reports
+ * the full set of reasons it observed - the picture is deliberately not
+ * collapsed to a single "winning" cause, since resets are often multi-causal.
+ *
+ * Sysfs (per provider, under /sys/kernel/pscrr/providerN/):
+ * name ro human label of the provider
+ * device symlink to the backing device (if any)
+ * reason the provider's reason set, as tokens; writable (record
+ * one reason) when the provider supports it
+ * caps ro non-default capabilities ("writable"); empty for a
+ * read-only, single-slot provider
+ * supported_reasons ro reasons this provider can report or record
+ * record_policy recorders only: keep the "first" or "last" reason
+ * recorded in a power cycle
+ *
+ * The kernel keeps the first (root cause) and last power-state-change reason
+ * (get_psc_first_reason()/get_psc_reason(), set by the thermal, regulator and
+ * hw_protection paths). At reboot each recorder is given the first or the last
+ * reason according to its record policy.
+ *
+ * Copyright (C) 2025 Pengutronix, Oleksij Rempel <o.rempel@pengutronix.de>
+ */
+
+#define pr_fmt(fmt) "PSCRR: " fmt
+
+#include <linux/bitmap.h>
+#include <linux/device.h>
+#include <linux/idr.h>
+#include <linux/kernel.h>
+#include <linux/kobject.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/notifier.h>
+#include <linux/of.h>
+#include <linux/pscrr.h>
+#include <linux/reboot.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/sysfs.h>
+
+static struct kobject *pscrr_root;
+static DEFINE_MUTEX(pscrr_lock);
+static LIST_HEAD(pscrr_dirs);
+static DEFINE_IDA(pscrr_ida);
+
+/*
+ * Record policy: when several reasons are recorded in one power cycle, keep the
+ * first (root cause) or overwrite with the last. Global, tunable via sysfs.
+ */
+enum pscrr_record_policy {
+ PSCRR_RECORD_FIRST,
+ PSCRR_RECORD_LAST,
+};
+
+/*
+ * Per-provider sysfs directory. Core-owned and self-freeing on kobject_put(),
+ * so its lifetime is decoupled from the caller-owned struct pscrr_provider.
+ */
+struct pscrr_provider_dir {
+ struct kobject kobj;
+ struct pscrr_provider *provider;
+ struct list_head node;
+ int id;
+ enum pscrr_record_policy policy; /* single-slot recorder: keep first/last */
+ bool recorded; /* a reason was recorded this power cycle */
+};
+
+static inline struct pscrr_provider_dir *to_pscrr_dir(struct kobject *kobj)
+{
+ return container_of(kobj, struct pscrr_provider_dir, kobj);
+}
+
+/*----------------------------------------------------------------------*/
+/* Per-provider attributes */
+/*----------------------------------------------------------------------*/
+
+static ssize_t name_show(struct kobject *kobj, struct kobj_attribute *attr,
+ char *buf)
+{
+ return sysfs_emit(buf, "%s\n", to_pscrr_dir(kobj)->provider->name);
+}
+
+static struct kobj_attribute pscrr_name_attr = __ATTR_RO(name);
+
+static int pscrr_parse_reason(const char *buf, enum psc_reason *out)
+{
+ unsigned int val;
+
+ /* Accept either a decimal index or a reason token. */
+ if (!kstrtouint(buf, 0, &val)) {
+ if (val >= PSCR_REASON_COUNT)
+ return -ERANGE;
+ *out = val;
+ return 0;
+ }
+
+ return psc_reason_from_token(buf, out);
+}
+
+static ssize_t reason_show(struct kobject *kobj, struct kobj_attribute *attr,
+ char *buf)
+{
+ struct pscrr_provider *p = to_pscrr_dir(kobj)->provider;
+ DECLARE_BITMAP(reasons, PSCR_REASON_COUNT);
+ ssize_t len = 0;
+ int bit, ret;
+
+ bitmap_zero(reasons, PSCR_REASON_COUNT);
+
+ ret = p->ops->read_reasons(p, reasons);
+ if (ret)
+ return ret;
+
+ for_each_set_bit(bit, reasons, PSCR_REASON_COUNT)
+ len += sysfs_emit_at(buf, len, "%s%s", len ? " " : "",
+ psc_reason_to_token(bit));
+ len += sysfs_emit_at(buf, len, "\n");
+
+ return len;
+}
+
+/* Record @reason into @dir honouring the global record policy. */
+static int pscrr_do_record(struct pscrr_provider_dir *dir, enum psc_reason reason)
+{
+ struct pscrr_provider *p = dir->provider;
+ int ret;
+
+ if (!p->ops->write_reason)
+ return -EPERM;
+
+ /*
+ * PSCR_UNKNOWN clears the slot and releases the latch, regardless of
+ * policy, so a reason recorded afterwards is taken again.
+ */
+ if (reason == PSCR_UNKNOWN) {
+ ret = p->ops->write_reason(p, reason);
+ if (ret)
+ return ret;
+
+ dir->recorded = false;
+ return 0;
+ }
+
+ /* Reject reasons the provider does not advertise (NULL == all). */
+ if (p->supported_reasons && !test_bit(reason, p->supported_reasons))
+ return -EOPNOTSUPP;
+
+ /* "first" policy: keep the first reason recorded this power cycle. */
+ if (dir->policy == PSCRR_RECORD_FIRST && dir->recorded)
+ return 0;
+
+ ret = p->ops->write_reason(p, reason);
+ if (ret)
+ return ret;
+
+ dir->recorded = true;
+
+ return 0;
+}
+
+static ssize_t reason_store(struct kobject *kobj, struct kobj_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct pscrr_provider_dir *dir = to_pscrr_dir(kobj);
+ enum psc_reason reason;
+ int ret;
+
+ if (!dir->provider->ops->write_reason)
+ return -EPERM;
+
+ ret = pscrr_parse_reason(buf, &reason);
+ if (ret)
+ return ret;
+
+ /* Serialise the record state against concurrent stores and the notifier. */
+ scoped_guard(mutex, &pscrr_lock)
+ ret = pscrr_do_record(dir, reason);
+ if (ret)
+ return ret;
+
+ return count;
+}
+
+static struct kobj_attribute pscrr_reason_attr =
+ __ATTR(reason, 0644, reason_show, reason_store);
+
+static ssize_t caps_show(struct kobject *kobj, struct kobj_attribute *attr,
+ char *buf)
+{
+ struct pscrr_provider *p = to_pscrr_dir(kobj)->provider;
+ ssize_t len = 0;
+
+ /* Readable and single-slot are the defaults and not listed. */
+ if (p->ops->write_reason)
+ len += sysfs_emit_at(buf, len, "writable");
+
+ return len + sysfs_emit_at(buf, len, "\n");
+}
+
+static struct kobj_attribute pscrr_caps_attr = __ATTR_RO(caps);
+
+static ssize_t supported_reasons_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ struct pscrr_provider *p = to_pscrr_dir(kobj)->provider;
+ const unsigned long *sup = p->supported_reasons;
+ ssize_t len = 0;
+ int i;
+
+ for (i = 0; i < PSCR_REASON_COUNT; i++) {
+ if (sup && !test_bit(i, sup)) /* NULL means all */
+ continue;
+ len += sysfs_emit_at(buf, len, "%s%s", len ? " " : "",
+ psc_reason_to_token(i));
+ }
+
+ return len + sysfs_emit_at(buf, len, "\n");
+}
+
+static struct kobj_attribute pscrr_supported_attr =
+ __ATTR(supported_reasons, 0444, supported_reasons_show, NULL);
+
+static ssize_t record_policy_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ struct pscrr_provider_dir *dir = to_pscrr_dir(kobj);
+
+ return sysfs_emit(buf, "%s\n",
+ READ_ONCE(dir->policy) == PSCRR_RECORD_FIRST ?
+ "first" : "last");
+}
+
+static ssize_t record_policy_store(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct pscrr_provider_dir *dir = to_pscrr_dir(kobj);
+
+ guard(mutex)(&pscrr_lock);
+
+ if (sysfs_streq(buf, "first"))
+ WRITE_ONCE(dir->policy, PSCRR_RECORD_FIRST);
+ else if (sysfs_streq(buf, "last"))
+ WRITE_ONCE(dir->policy, PSCRR_RECORD_LAST);
+ else
+ return -EINVAL;
+
+ return count;
+}
+
+static struct kobj_attribute pscrr_record_policy_attr =
+ __ATTR(record_policy, 0644, record_policy_show, record_policy_store);
+
+static struct attribute *pscrr_dir_attrs[] = {
+ &pscrr_name_attr.attr,
+ &pscrr_reason_attr.attr,
+ &pscrr_caps_attr.attr,
+ &pscrr_supported_attr.attr,
+ &pscrr_record_policy_attr.attr,
+ NULL,
+};
+
+static umode_t pscrr_dir_is_visible(struct kobject *kobj, struct attribute *attr,
+ int n)
+{
+ struct pscrr_provider *p = to_pscrr_dir(kobj)->provider;
+
+ /* A provider without write_reason() exposes reason read-only. */
+ if (attr == &pscrr_reason_attr.attr && !p->ops->write_reason)
+ return 0444;
+
+ /* record_policy only applies to a (single-slot) recorder. */
+ if (attr == &pscrr_record_policy_attr.attr && !p->ops->write_reason)
+ return 0;
+
+ return attr->mode;
+}
+
+static const struct attribute_group pscrr_dir_group = {
+ .attrs = pscrr_dir_attrs,
+ .is_visible = pscrr_dir_is_visible,
+};
+
+static const struct attribute_group *pscrr_dir_groups[] = {
+ &pscrr_dir_group,
+ NULL,
+};
+
+static void pscrr_dir_release(struct kobject *kobj)
+{
+ kfree(to_pscrr_dir(kobj));
+}
+
+static const struct kobj_type pscrr_dir_ktype = {
+ .sysfs_ops = &kobj_sysfs_ops,
+ .release = pscrr_dir_release,
+ .default_groups = pscrr_dir_groups,
+};
+
+/*----------------------------------------------------------------------*/
+/* Provider registration */
+/*----------------------------------------------------------------------*/
+
+/**
+ * pscrr_provider_register - register a power state change reason provider
+ * @p: caller-owned provider description
+ *
+ * Creates /sys/kernel/pscrr/providerN/ with "name" and "reason" attributes
+ * and, when @p->dev is set, a "device" symlink. @p->reason is writable when
+ * @p provides write_reason(). The provider must outlive the matching
+ * pscrr_provider_unregister() call.
+ *
+ * Return: 0 on success or a negative errno.
+ */
+int pscrr_provider_register(struct pscrr_provider *p)
+{
+ struct pscrr_provider_dir *dir;
+ int ret;
+
+ if (!p || !p->name || !p->ops || !p->ops->read_reasons)
+ return -EINVAL;
+
+ dir = kzalloc_obj(*dir);
+ if (!dir)
+ return -ENOMEM;
+
+ dir->provider = p;
+ dir->policy = PSCRR_RECORD_FIRST;
+
+ scoped_guard(mutex, &pscrr_lock) {
+ if (!pscrr_root) {
+ kfree(dir);
+ return -ENODEV;
+ }
+
+ dir->id = ida_alloc(&pscrr_ida, GFP_KERNEL);
+ if (dir->id < 0) {
+ ret = dir->id;
+ kfree(dir);
+ return ret;
+ }
+
+ ret = kobject_init_and_add(&dir->kobj, &pscrr_dir_ktype,
+ pscrr_root, "provider%d", dir->id);
+ if (ret) {
+ /*
+ * kobject_init_and_add() failed: per its contract only
+ * kobject_put() may follow, no kobject_del(). The
+ * kobject is not in sysfs, so this is safe under the
+ * lock.
+ */
+ ida_free(&pscrr_ida, dir->id);
+ kobject_put(&dir->kobj);
+ return ret;
+ }
+
+ if (p->dev) {
+ ret = sysfs_create_link(&dir->kobj, &p->dev->kobj,
+ "device");
+ if (ret)
+ break;
+ }
+
+ list_add_tail(&dir->node, &pscrr_dirs);
+ return 0;
+ }
+
+ /*
+ * sysfs_create_link() failed after the directory was created: tear it
+ * down outside pscrr_lock (kobject_del() drains sysfs stores that take
+ * the lock) and release the id only once the directory is gone.
+ */
+ kobject_del(&dir->kobj);
+ ida_free(&pscrr_ida, dir->id);
+ kobject_put(&dir->kobj);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(pscrr_provider_register);
+
+/**
+ * pscrr_provider_unregister - remove a previously registered provider
+ * @p: the provider passed to pscrr_provider_register()
+ */
+void pscrr_provider_unregister(struct pscrr_provider *p)
+{
+ struct pscrr_provider_dir *dir = NULL, *iter;
+
+ scoped_guard(mutex, &pscrr_lock) {
+ list_for_each_entry(iter, &pscrr_dirs, node) {
+ if (iter->provider == p) {
+ dir = iter;
+ list_del(&dir->node);
+ break;
+ }
+ }
+ }
+
+ if (!dir)
+ return;
+
+ /*
+ * Tear the sysfs directory down outside pscrr_lock: kobject_del()
+ * drains in-flight reason/record_policy stores, which take pscrr_lock,
+ * so holding it here would deadlock. Release the id only once the
+ * directory is gone, so a concurrent register cannot reuse it and
+ * collide on the providerN name.
+ */
+ if (p->dev)
+ sysfs_remove_link(&dir->kobj, "device");
+ kobject_del(&dir->kobj);
+ ida_free(&pscrr_ida, dir->id);
+ kobject_put(&dir->kobj);
+}
+EXPORT_SYMBOL_GPL(pscrr_provider_unregister);
+
+static void pscrr_provider_devm_release(void *p)
+{
+ pscrr_provider_unregister(p);
+}
+
+/**
+ * devm_pscrr_provider_register - device-managed reason provider registration
+ * @dev: device the provider belongs to; also backs the "device" symlink
+ * @name: human-readable provider label
+ * @ops: provider callback table; read_reasons() is required, write_reason() is
+ * optional and makes the provider a recorder
+ * @supported_reasons: bitmap of the reasons the provider supports, or NULL
+ * for all; set before the provider is exposed in sysfs
+ * @priv: provider private data, handed back to the @ops callbacks
+ *
+ * Allocates and registers a struct pscrr_provider and schedules its
+ * unregistration when @dev is unbound, so the caller keeps no reference to it.
+ *
+ * Return: the registered provider on success or an ERR_PTR() on failure. When
+ * CONFIG_PSCRR is disabled the call resolves to a stub returning NULL, so
+ * callers need no IS_ENABLED() guard.
+ */
+struct pscrr_provider *
+devm_pscrr_provider_register(struct device *dev, const char *name,
+ const struct pscrr_provider_ops *ops,
+ const unsigned long *supported_reasons, void *priv)
+{
+ struct pscrr_provider *p;
+ int ret;
+
+ p = devm_kzalloc(dev, sizeof(*p), GFP_KERNEL);
+ if (!p)
+ return ERR_PTR(-ENOMEM);
+
+ p->name = name;
+ p->dev = dev;
+ p->ops = ops;
+ p->supported_reasons = supported_reasons;
+ p->priv = priv;
+
+ ret = pscrr_provider_register(p);
+ if (ret)
+ return ERR_PTR(ret);
+
+ ret = devm_add_action_or_reset(dev, pscrr_provider_devm_release, p);
+ if (ret)
+ return ERR_PTR(ret);
+
+ return p;
+}
+EXPORT_SYMBOL_GPL(devm_pscrr_provider_register);
+
+/*----------------------------------------------------------------------*/
+/* Record path: reboot notifier writes the current reason to recorders */
+/*----------------------------------------------------------------------*/
+
+/*
+ * Record the current power-state-change reason into every provider, giving
+ * each the first (root cause) or the last reason according to its record
+ * policy. The kernel keeps both, so "first" is meaningful even when later
+ * events overwrite the last reason. The caller holds pscrr_lock, or runs where
+ * the provider list is stable (panic).
+ */
+static void pscrr_record_current(void)
+{
+ enum psc_reason first = get_psc_first_reason();
+ enum psc_reason last = get_psc_reason();
+ struct pscrr_provider_dir *dir;
+
+ list_for_each_entry(dir, &pscrr_dirs, node)
+ pscrr_do_record(dir, dir->policy == PSCRR_RECORD_FIRST ?
+ first : last);
+}
+
+static int pscrr_reboot_notifier(struct notifier_block *nb,
+ unsigned long action, void *unused)
+{
+ guard(mutex)(&pscrr_lock);
+
+ /*
+ * A reboot, halt or power-off that reaches here with no more specific
+ * reason is software-initiated by definition. Record it as such rather
+ * than leaving it unattributed; a real cause set earlier (thermal,
+ * under-voltage, ...) is already latched and left untouched.
+ */
+ if (get_psc_reason() == PSCR_UNKNOWN)
+ set_psc_reason(PSCR_SOFTWARE);
+
+ pscrr_record_current();
+
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block pscrr_reboot_nb = {
+ .notifier_call = pscrr_reboot_notifier,
+};
+
+/*----------------------------------------------------------------------*/
+/* Built-in provider: device-tree /chosen/reset-source */
+/*----------------------------------------------------------------------*/
+
+/*
+ * Bootloaders such as barebox record the SoC reset cause in the standard
+ * device-tree /chosen/reset-source property. When it is present, surface it as
+ * a read-only, device-less provider so the bootloader's view of the last reset
+ * shows up next to any hardware or software providers - the framework just
+ * reads the property already there, with no dedicated node or new binding.
+ */
+static const struct {
+ const char *name;
+ enum psc_reason reason;
+} pscrr_reset_source_map[] = {
+ { "POR", PSCR_POWER_ON },
+ { "RST", PSCR_SOFTWARE },
+ { "WDG", PSCR_WATCHDOG },
+ { "THERM", PSCR_OVER_TEMPERATURE },
+ { "EXT", PSCR_EXTERNAL },
+ { "BROWNOUT", PSCR_UNDER_VOLTAGE },
+};
+
+static const unsigned long
+pscrr_reset_source_supported[BITS_TO_LONGS(PSCR_REASON_COUNT)] = {
+ BIT(PSCR_UNDER_VOLTAGE) | BIT(PSCR_OVER_TEMPERATURE) |
+ BIT(PSCR_POWER_ON) | BIT(PSCR_WATCHDOG) | BIT(PSCR_SOFTWARE) |
+ BIT(PSCR_EXTERNAL),
+};
+
+/* Parsed once at init; read back by the provider's read_reasons(). */
+static enum psc_reason pscrr_reset_source_reason = PSCR_UNKNOWN;
+
+static int pscrr_reset_source_read(struct pscrr_provider *p,
+ unsigned long *reasons)
+{
+ set_bit(pscrr_reset_source_reason, reasons);
+
+ return 0;
+}
+
+static const struct pscrr_provider_ops pscrr_reset_source_ops = {
+ .read_reasons = pscrr_reset_source_read,
+};
+
+static struct pscrr_provider pscrr_reset_source_provider = {
+ .name = "reset-source",
+ .ops = &pscrr_reset_source_ops,
+ .supported_reasons = pscrr_reset_source_supported,
+};
+
+static void __init pscrr_register_reset_source(void)
+{
+ const char *name;
+ int i, ret;
+
+ if (!IS_ENABLED(CONFIG_OF) || !of_chosen)
+ return;
+
+ if (of_property_read_string(of_chosen, "reset-source", &name))
+ return;
+
+ for (i = 0; i < ARRAY_SIZE(pscrr_reset_source_map); i++)
+ if (!strcmp(name, pscrr_reset_source_map[i].name)) {
+ pscrr_reset_source_reason = pscrr_reset_source_map[i].reason;
+ break;
+ }
+
+ ret = pscrr_provider_register(&pscrr_reset_source_provider);
+ if (ret)
+ pr_warn("failed to register the reset-source provider: %d\n", ret);
+}
+
+/*----------------------------------------------------------------------*/
+/* Module init/exit */
+/*----------------------------------------------------------------------*/
+
+static int __init pscrr_core_init(void)
+{
+ int ret;
+
+ pscrr_root = kobject_create_and_add("pscrr", kernel_kobj);
+ if (!pscrr_root)
+ return -ENOMEM;
+
+ ret = register_reboot_notifier(&pscrr_reboot_nb);
+ if (ret) {
+ kobject_put(pscrr_root);
+ pscrr_root = NULL;
+ return ret;
+ }
+
+ pscrr_register_reset_source();
+
+ return 0;
+}
+
+static void __exit pscrr_core_exit(void)
+{
+ pscrr_provider_unregister(&pscrr_reset_source_provider);
+ unregister_reboot_notifier(&pscrr_reboot_nb);
+ kobject_put(pscrr_root);
+ pscrr_root = NULL;
+ ida_destroy(&pscrr_ida);
+}
+
+/* Bring the core up before device drivers probe and register providers. */
+subsys_initcall(pscrr_core_init);
+module_exit(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..330d6ac191bd
--- /dev/null
+++ b/include/linux/pscrr.h
@@ -0,0 +1,107 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * pscrr.h - Public header for Power State Change Reason Recording (PSCRR).
+ *
+ * Copyright (C) 2025 Pengutronix, Oleksij Rempel <o.rempel@pengutronix.de>
+ */
+
+#ifndef __PSCRR_H__
+#define __PSCRR_H__
+
+#include <linux/reboot.h>
+
+struct device;
+struct pscrr_provider;
+
+/**
+ * struct pscrr_provider_ops - Provider callbacks.
+ * @read_reasons: Fill @reasons with the complete set this source observed.
+ * @reasons is a bitmap of PSCR_REASON_COUNT bits; set members
+ * with set_bit(PSCR_x, reasons). Return 0 on success or a
+ * negative errno. Required. Driver state is reached via p->priv.
+ * @write_reason: Persist @reason. Called from the reboot notifier for
+ * recorder-capable providers so the cause survives the power
+ * cycle. Leave NULL for read-only hardware sources.
+ *
+ * A const ops table per provider type; extend it with new callbacks without
+ * touching the registration signature or existing callers.
+ */
+struct pscrr_provider_ops {
+ int (*read_reasons)(struct pscrr_provider *p, unsigned long *reasons);
+ int (*write_reason)(struct pscrr_provider *p, enum psc_reason reason);
+};
+
+/**
+ * struct pscrr_provider - A source (and optionally recorder) of power state
+ * change reasons.
+ *
+ * A provider represents one place the system can learn *why* the last power
+ * state change happened: a hardware reset-cause register (PMIC, SoC SRC,
+ * watchdog), a persistent recorder (NVMEM/RTC scratch), or a test stub. Each
+ * registered provider gets its own directory under /sys/kernel/pscrr/, so the
+ * full, un-prioritised picture is visible: several providers - and several
+ * reasons within one provider - can be reported simultaneously.
+ *
+ * @name: Human-readable label, exported as the "name" attribute. The
+ * directory itself is core-indexed (providerN), so this need not
+ * be unique. Required.
+ * @dev: Backing device. When set it is exported as the "device" symlink
+ * in the provider directory, tying the reason to real hardware.
+ * May be NULL (e.g. for a test provider).
+ * @ops: Provider callbacks. Required.
+ * @supported_reasons: Bitmap of the reasons this provider can store or report,
+ * limited e.g. by the storage size. 0 means all reasons.
+ * @priv: Provider private data, passed back through the callbacks.
+ *
+ * Providers are readable and single-slot by default; only capabilities beyond
+ * that (currently: writable) are advertised.
+ */
+struct pscrr_provider {
+ const char *name;
+ struct device *dev;
+ const struct pscrr_provider_ops *ops;
+ const unsigned long *supported_reasons;
+ void *priv;
+};
+
+#if IS_ENABLED(CONFIG_PSCRR)
+int pscrr_provider_register(struct pscrr_provider *p);
+void pscrr_provider_unregister(struct pscrr_provider *p);
+
+/**
+ * devm_pscrr_provider_register - allocate, fill and register a provider
+ * @dev: device the provider belongs to (also the "device" symlink target)
+ * @name: provider label
+ * @ops: provider callbacks
+ * @supported_reasons: bitmap of supported reasons, or NULL for all
+ * @priv: driver state passed back through the callbacks
+ *
+ * The provider is unregistered automatically on device teardown.
+ *
+ * Return: the provider on success, ERR_PTR() on failure, or NULL when PSCRR
+ * is not built (so the caller need not guard the call).
+ */
+struct pscrr_provider *
+devm_pscrr_provider_register(struct device *dev, const char *name,
+ const struct pscrr_provider_ops *ops,
+ const unsigned long *supported_reasons, void *priv);
+#else
+static inline int pscrr_provider_register(struct pscrr_provider *p)
+{
+ return -EOPNOTSUPP;
+}
+
+static inline void pscrr_provider_unregister(struct pscrr_provider *p)
+{
+}
+
+static inline struct pscrr_provider *
+devm_pscrr_provider_register(struct device *dev, const char *name,
+ const struct pscrr_provider_ops *ops,
+ const unsigned long *supported_reasons, void *priv)
+{
+ return NULL;
+}
+#endif
+
+#endif /* __PSCRR_H__ */
--
2.47.3
^ permalink raw reply related [flat|nested] 9+ messages in thread* [PATCH v12 6/6] Documentation: Add sysfs documentation for PSCRR
2026-07-31 9:59 [PATCH v12 0/6] Introduce the Power State Change Reasons Recording (PSCRR) framework Oleksij Rempel
` (4 preceding siblings ...)
2026-07-31 9:59 ` [PATCH v12 5/6] power: reset: Introduce PSCR Recording Framework for Non-Volatile Storage Oleksij Rempel
@ 2026-07-31 9:59 ` Oleksij Rempel
2026-08-05 9:34 ` [PATCH v12 0/6] Introduce the Power State Change Reasons Recording (PSCRR) framework Faruque Ansari
6 siblings, 0 replies; 9+ messages in thread
From: Oleksij Rempel @ 2026-07-31 9:59 UTC (permalink / raw)
To: Sebastian Reichel, 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, Kees Cook, Faruque Ansari, Francesco Valla,
Greg Kroah-Hartman, chrome-platform
Document the Power State Change Reasons Recording (PSCRR) sysfs interface
under /sys/kernel/pscrr/: the per-provider directories and their name,
device, reason, caps, supported_reasons and record_policy attributes,
including the stable reason token values.
Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
---
changes v12:
- rewrite for the per-provider interface (providerN/ directories with
caps, supported_reasons and record_policy)
- rename the file to sysfs-kernel-pscrr to match the sysfs path
- refresh KernelVersion/Date
- drop Reviewed-by: Matti Vaittinen; the documentation was rewritten
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
---
Documentation/ABI/testing/sysfs-kernel-pscrr | 108 +++++++++++++++++++
1 file changed, 108 insertions(+)
create mode 100644 Documentation/ABI/testing/sysfs-kernel-pscrr
diff --git a/Documentation/ABI/testing/sysfs-kernel-pscrr b/Documentation/ABI/testing/sysfs-kernel-pscrr
new file mode 100644
index 000000000000..63aa411b7362
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-kernel-pscrr
@@ -0,0 +1,108 @@
+What: /sys/kernel/pscrr/
+Date: July 2026
+KernelVersion: 7.2
+Contact: Oleksij Rempel <o.rempel@pengutronix.de>
+Description:
+ Root directory of the Power State Change Reason Recording
+ (PSCRR) framework. It contains one subdirectory per registered
+ reason provider, named providerN (N is an arbitrary, stable
+ index assigned at registration).
+
+ A provider is either a hardware reason source (a PMIC, SoC
+ reset controller or watchdog exposing a reset cause) or a
+ recorder that persists the current reason across a power cycle
+ (e.g. an NVMEM or RTC scratch cell). The set of reasons is
+ deliberately not collapsed to a single "winning" cause, since
+ resets are often multi-causal.
+
+What: /sys/kernel/pscrr/providerN/name
+Date: July 2026
+KernelVersion: 7.2
+Contact: Oleksij Rempel <o.rempel@pengutronix.de>
+Description:
+ (RO) Human-readable label identifying the provider, e.g.
+ "pca9450" or "nvmem".
+
+What: /sys/kernel/pscrr/providerN/device
+Date: July 2026
+KernelVersion: 7.2
+Contact: Oleksij Rempel <o.rempel@pengutronix.de>
+Description:
+ Symbolic link to the backing struct device of the provider.
+ Present only for providers that are bound to a device.
+
+What: /sys/kernel/pscrr/providerN/reason
+Date: July 2026
+KernelVersion: 7.2
+Contact: Oleksij Rempel <o.rempel@pengutronix.de>
+Description:
+ The set of power state change reasons observed by this
+ provider, as a space-separated list of reason tokens (an
+ empty line means no reason is recorded).
+
+ The attribute is writable only for providers that can record
+ a reason; for a pure hardware source it is read-only. A write
+ records one reason and accepts either a reason token or its
+ decimal index. The tokens and their stable numeric values are:
+
+ == ================= ============================================
+ 0 unknown Unknown or unspecified reason
+ 1 under-voltage Supply voltage dropped below a safe level
+ 2 over-current Excessive current draw / possible short
+ 3 regulator-failure Voltage regulator failure
+ 4 over-temperature Unsafe temperature detected
+ 5 ec-panic Embedded controller (EC) panic
+ 6 power-on Regular cold power-on
+ 7 watchdog Watchdog timeout
+ 8 software Software-initiated reset or reboot
+ 9 external External reset input asserted
+ 10 rtc RTC-triggered wake-up or power-on
+ 11 reset-button User reset button
+ 12 cpu-clock-failure CPU clock failure
+ 13 crystal-failure Crystal oscillator failure
+ == ================= ============================================
+
+ The numeric order is stable ABI: new reasons are only ever
+ appended. A provider may support only a subset of these; see
+ "supported_reasons".
+
+What: /sys/kernel/pscrr/providerN/caps
+Date: July 2026
+KernelVersion: 7.2
+Contact: Oleksij Rempel <o.rempel@pengutronix.de>
+Description:
+ (RO) Space-separated list of the provider's non-default
+ capabilities. Being readable and storing a single reason are
+ the defaults and are not listed. Currently defined:
+
+ ======== ==============================================
+ writable the provider can record a reason (see "reason"
+ and "record_policy")
+ ======== ==============================================
+
+ An empty line therefore denotes a read-only, single-slot
+ provider.
+
+What: /sys/kernel/pscrr/providerN/supported_reasons
+Date: July 2026
+KernelVersion: 7.2
+Contact: Oleksij Rempel <o.rempel@pengutronix.de>
+Description:
+ (RO) Space-separated list of the reason tokens (see "reason")
+ this provider is able to report or record. A provider that
+ supports every reason lists them all.
+
+What: /sys/kernel/pscrr/providerN/record_policy
+Date: July 2026
+KernelVersion: 7.2
+Contact: Oleksij Rempel <o.rempel@pengutronix.de>
+Description:
+ (RW) Policy used when more than one reason is recorded during a
+ single power cycle. Present only for providers that can record
+ (see "caps"). Valid values are:
+
+ ===== =================================================
+ first keep the first reason recorded this cycle (the
+ root cause); this is the default
+ last overwrite with the most recently recorded reason
+ ===== =================================================
--
2.47.3
^ permalink raw reply related [flat|nested] 9+ messages in thread* Re: [PATCH v12 0/6] Introduce the Power State Change Reasons Recording (PSCRR) framework
2026-07-31 9:59 [PATCH v12 0/6] Introduce the Power State Change Reasons Recording (PSCRR) framework Oleksij Rempel
` (5 preceding siblings ...)
2026-07-31 9:59 ` [PATCH v12 6/6] Documentation: Add sysfs documentation for PSCRR Oleksij Rempel
@ 2026-08-05 9:34 ` Faruque Ansari
2026-08-05 11:07 ` Oleksij Rempel
6 siblings, 1 reply; 9+ messages in thread
From: Faruque Ansari @ 2026-08-05 9:34 UTC (permalink / raw)
To: Oleksij Rempel, Sebastian Reichel, Benson Leung, Tzung-Bi Shih,
Daniel Lezcano
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, Kees Cook, Francesco Valla, Greg Kroah-Hartman,
chrome-platform
Hi Oleksij,
On 31-Jul-26 3:29 PM, Oleksij Rempel wrote:
> changes v12:
> - Drop all provider drivers and their tests; post the framework alone. The
> NVMEM-cell binding a recorder needs to pick its storage is still
> deadlocked, and several projects already need the framework - so unblock
> the core now and let the providers (NVMEM, PMIC, ...) follow separately.
> - Rework into a multi-provider design (per-provider /sys/kernel/pscrr/
> directories); add reason tokens and a built-in /chosen/reset-source
> provider.
>
When do you plan to post the provider series (NVMEM, PMIC, etc.)
separately to the mailing list?
Thanks
Faruque Ansari
^ permalink raw reply [flat|nested] 9+ messages in thread* Re: [PATCH v12 0/6] Introduce the Power State Change Reasons Recording (PSCRR) framework
2026-08-05 9:34 ` [PATCH v12 0/6] Introduce the Power State Change Reasons Recording (PSCRR) framework Faruque Ansari
@ 2026-08-05 11:07 ` Oleksij Rempel
0 siblings, 0 replies; 9+ messages in thread
From: Oleksij Rempel @ 2026-08-05 11:07 UTC (permalink / raw)
To: Faruque Ansari
Cc: Sebastian Reichel, 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, Kees Cook, Francesco Valla, Greg Kroah-Hartman,
chrome-platform
Hi Faruque,
On Wed, Aug 05, 2026 at 03:04:42PM +0530, Faruque Ansari wrote:
> Hi Oleksij,
>
> On 31-Jul-26 3:29 PM, Oleksij Rempel wrote:
> > changes v12:
> > - Drop all provider drivers and their tests; post the framework alone. The
> > NVMEM-cell binding a recorder needs to pick its storage is still
> > deadlocked, and several projects already need the framework - so unblock
> > the core now and let the providers (NVMEM, PMIC, ...) follow separately.
> > - Rework into a multi-provider design (per-provider /sys/kernel/pscrr/
> > directories); add reason tokens and a built-in /chosen/reset-source
> > provider.
> >
>
> When do you plan to post the provider series (NVMEM, PMIC, etc.) separately
> to the mailing list?
After this patch stack is merged.
Currently I do not have capacity to continue. If you can, it would be
good it if you can take over next upstreaming rounds for this core. All
related follow-up patches are in my git repo.
Best Regards,
Oleksij
--
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] 9+ messages in thread