From patchwork Tue Jun 17 09:49:40 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Oleksij Rempel X-Patchwork-Id: 897461 Received: from metis.whiteo.stw.pengutronix.de (metis.whiteo.stw.pengutronix.de [185.203.201.7]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id B3E6529826D for ; Tue, 17 Jun 2025 09:50:23 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=185.203.201.7 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1750153825; cv=none; b=WTsi177K4SZyLsUwsHNED5lLM2ANR6E8NqQoJnbjd5VXTx8EGyrRG4Wu2HDhNmf/hkzDh9jTLuxfuJgK3n0AP/mv7rwdVFL1i5Agch8tLRhGAmnbfYtZZpCBkrTeBeZW5YRpdleu0tF3lALY5ZgNL5tkx6bW8Cb8J9SPED+bndM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1750153825; c=relaxed/simple; bh=wxUMNvc2FMzuZ5LOPgpl9/dleYGBc18u9muLWUbgoXo=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=inOnzycSw4Y+kBWtZOdQ88MwvYdDfo7gzrgUPi50egv1ZdwGS9eYClk4BsS8ME0OWeEx5kFAh19A/RtvSVnYPXO18rVhjSawwrC/A32AL6wXwMWXNZcoFeurg6/e3C1Oy+Xn0O0UXODSiLXiEr2vuZuMLp86P/kdj2kzRIGkEtY= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=pengutronix.de; spf=pass smtp.mailfrom=pengutronix.de; arc=none smtp.client-ip=185.203.201.7 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=pengutronix.de Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=pengutronix.de Received: from drehscheibe.grey.stw.pengutronix.de ([2a0a:edc0:0:c01:1d::a2]) by metis.whiteo.stw.pengutronix.de with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1uRSwt-0005eu-0H; Tue, 17 Jun 2025 11:49:47 +0200 Received: from dude04.red.stw.pengutronix.de ([2a0a:edc0:0:1101:1d::ac]) by drehscheibe.grey.stw.pengutronix.de with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1uRSws-003x4u-0d; Tue, 17 Jun 2025 11:49:46 +0200 Received: from ore by dude04.red.stw.pengutronix.de with local (Exim 4.96) (envelope-from ) id 1uRSws-00FBfk-0H; Tue, 17 Jun 2025 11:49:46 +0200 From: Oleksij Rempel To: Sebastian Reichel , Srinivas Kandagatla , Benson Leung , Tzung-Bi Shih , Daniel Lezcano Cc: Oleksij Rempel , Matti Vaittinen , Mark Brown , kernel@pengutronix.de, linux-kernel@vger.kernel.org, Liam Girdwood , "Rafael J. Wysocki" , Zhang Rui , Lukasz Luba , linux-pm@vger.kernel.org, =?utf-8?q?S=C3=B8ren_Andersen?= , Guenter Roeck , Ahmad Fatoum , Andrew Morton , chrome-platform@lists.linux.dev Subject: [PATCH v10 2/7] reboot: hw_protection_trigger: use standardized numeric shutdown/reboot reasons instead of strings Date: Tue, 17 Jun 2025 11:49:40 +0200 Message-Id: <20250617094945.3619360-3-o.rempel@pengutronix.de> X-Mailer: git-send-email 2.39.5 In-Reply-To: <20250617094945.3619360-1-o.rempel@pengutronix.de> References: <20250617094945.3619360-1-o.rempel@pengutronix.de> Precedence: bulk X-Mailing-List: linux-pm@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-SA-Exim-Connect-IP: 2a0a:edc0:0:c01:1d::a2 X-SA-Exim-Mail-From: ore@pengutronix.de X-SA-Exim-Scanned: No (on metis.whiteo.stw.pengutronix.de); SAEximRunCond expanded to false X-PTX-Original-Recipient: linux-pm@vger.kernel.org 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 : () Signed-off-by: Oleksij Rempel Reviewed-by: Matti Vaittinen Acked-by: Mark Brown Acked-by: Tzung-Bi Shih Acked-by: Daniel Lezcano --- 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 | 15 +++-- drivers/regulator/irq_helpers.c | 9 +-- drivers/thermal/thermal_core.c | 3 +- include/linux/reboot.h | 77 +++++++++++++++++++++++- kernel/reboot.c | 85 +++++++++++++++++++++++++-- 6 files changed, 169 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..2792dcf25a43 100644 --- a/drivers/regulator/core.c +++ b/drivers/regulator/core.c @@ -5324,26 +5324,25 @@ 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; + 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 #include #include +#include #include #include #include @@ -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)) From patchwork Tue Jun 17 09:49:42 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Oleksij Rempel X-Patchwork-Id: 897464 Received: from metis.whiteo.stw.pengutronix.de (metis.whiteo.stw.pengutronix.de [185.203.201.7]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id EE7952D12EB for ; Tue, 17 Jun 2025 09:50:05 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=185.203.201.7 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1750153807; cv=none; b=ZouVFxkvRJU25b5T+8ma/xh3mW7chC9cTa88H2Ed20slwZXpXQNWjpB7j+4cee0aDRTx9wALaU8huSRDoEoG3DsSViAa1Of8NIkzAFNvF2GpYKeR4SHR+xb3N2dD1+lmDFxpJFcLvZk/cmRw2xSeEydGkVccQ6TWNmCbd4a189w= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1750153807; c=relaxed/simple; bh=CSE1xGwJPwkE21FMhoW6TOA0Bu55dDPTRRyrNaMKYnQ=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=F9wuN1C2S2/dM2CXVX43oIojqVh0/ScKKVcaetBBRF6uGraKqQ6pd5HK8LzJbd+UYHb1eYKvBJrLHpMR4QkuMegnBP7qXuLoAxmDC8ebC+Ltts7OyOHOa+OsnTZPrCA7MbxEawxMuDj0ryl6ahlEbF/xEnzRYn6eGzkh2/piHaM= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=pengutronix.de; spf=pass smtp.mailfrom=pengutronix.de; arc=none smtp.client-ip=185.203.201.7 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=pengutronix.de Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=pengutronix.de Received: from drehscheibe.grey.stw.pengutronix.de ([2a0a:edc0:0:c01:1d::a2]) by metis.whiteo.stw.pengutronix.de with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1uRSwt-0005ew-0H; Tue, 17 Jun 2025 11:49:47 +0200 Received: from dude04.red.stw.pengutronix.de ([2a0a:edc0:0:1101:1d::ac]) by drehscheibe.grey.stw.pengutronix.de with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1uRSws-003x4z-0r; Tue, 17 Jun 2025 11:49:46 +0200 Received: from ore by dude04.red.stw.pengutronix.de with local (Exim 4.96) (envelope-from ) id 1uRSws-00FBgX-0Y; Tue, 17 Jun 2025 11:49:46 +0200 From: Oleksij Rempel To: Sebastian Reichel , Srinivas Kandagatla , Benson Leung , Tzung-Bi Shih , Daniel Lezcano Cc: Oleksij Rempel , Sebastian Reichel , kernel@pengutronix.de, linux-kernel@vger.kernel.org, Liam Girdwood , Mark Brown , "Rafael J. Wysocki" , Zhang Rui , Lukasz Luba , linux-pm@vger.kernel.org, =?utf-8?q?S=C3=B8ren_Andersen?= , Guenter Roeck , Matti Vaittinen , Ahmad Fatoum , Andrew Morton , chrome-platform@lists.linux.dev Subject: [PATCH v10 4/7] nvmem: provide consumer access to cell size metrics Date: Tue, 17 Jun 2025 11:49:42 +0200 Message-Id: <20250617094945.3619360-5-o.rempel@pengutronix.de> X-Mailer: git-send-email 2.39.5 In-Reply-To: <20250617094945.3619360-1-o.rempel@pengutronix.de> References: <20250617094945.3619360-1-o.rempel@pengutronix.de> Precedence: bulk X-Mailing-List: linux-pm@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-SA-Exim-Connect-IP: 2a0a:edc0:0:c01:1d::a2 X-SA-Exim-Mail-From: ore@pengutronix.de X-SA-Exim-Scanned: No (on metis.whiteo.stw.pengutronix.de); SAEximRunCond expanded to false X-PTX-Original-Recipient: linux-pm@vger.kernel.org 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 Reviewed-by: Sebastian Reichel --- 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) { From patchwork Tue Jun 17 09:49:43 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Oleksij Rempel X-Patchwork-Id: 897462 Received: from metis.whiteo.stw.pengutronix.de (metis.whiteo.stw.pengutronix.de [185.203.201.7]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 668102DF3FF for ; Tue, 17 Jun 2025 09:50:07 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=185.203.201.7 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1750153809; cv=none; b=X50JObn4hkzDQMoJTnFOXrKlvzSviDK+EUYcrhpTxt5q8skcX3PWPG/oVJY6NE82Sm5yI6tgKsqUPjTsytKOgKi1nJUw01WIMUtl2onP7hM6IZn348Oztit7oHOAY80ZaJJjyrcjL1ohPXTp1bfjAA50+kQP0DRUaM0assEkGjc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1750153809; c=relaxed/simple; bh=XGojHH9F0Yv1Zaj14e+r4bcattMMcYs/2iz2DruAfIQ=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=c02m0xjj1wvAjlkESmnmnvd33bDh2SE+rZDt+p4p2wFjs6m3bXDFiH3IPZhraR3/DJAogVFQPxBIE6DSjFrouEeFzxFhf9QUH/falNpgyQZUVyOEgrM6o75wxYOyXsFGYLy7FEje/z8K89pyObLaAYpN7Z/JeGQxTmDnLhe6YI4= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=pengutronix.de; spf=pass smtp.mailfrom=pengutronix.de; arc=none smtp.client-ip=185.203.201.7 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=pengutronix.de Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=pengutronix.de Received: from drehscheibe.grey.stw.pengutronix.de ([2a0a:edc0:0:c01:1d::a2]) by metis.whiteo.stw.pengutronix.de with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1uRSwt-0005fF-0H; Tue, 17 Jun 2025 11:49:47 +0200 Received: from dude04.red.stw.pengutronix.de ([2a0a:edc0:0:1101:1d::ac]) by drehscheibe.grey.stw.pengutronix.de with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1uRSws-003x53-19; Tue, 17 Jun 2025 11:49:46 +0200 Received: from ore by dude04.red.stw.pengutronix.de with local (Exim 4.96) (envelope-from ) id 1uRSws-00FBhE-0k; Tue, 17 Jun 2025 11:49:46 +0200 From: Oleksij Rempel To: Sebastian Reichel , Srinivas Kandagatla , Benson Leung , Tzung-Bi Shih , Daniel Lezcano Cc: Oleksij Rempel , kernel@pengutronix.de, linux-kernel@vger.kernel.org, Liam Girdwood , Mark Brown , "Rafael J. Wysocki" , Zhang Rui , Lukasz Luba , linux-pm@vger.kernel.org, =?utf-8?q?S=C3=B8ren_Andersen?= , Guenter Roeck , Matti Vaittinen , Ahmad Fatoum , Andrew Morton , chrome-platform@lists.linux.dev Subject: [PATCH v10 5/7] nvmem: add support for device and sysfs-based cell lookups Date: Tue, 17 Jun 2025 11:49:43 +0200 Message-Id: <20250617094945.3619360-6-o.rempel@pengutronix.de> X-Mailer: git-send-email 2.39.5 In-Reply-To: <20250617094945.3619360-1-o.rempel@pengutronix.de> References: <20250617094945.3619360-1-o.rempel@pengutronix.de> Precedence: bulk X-Mailing-List: linux-pm@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-SA-Exim-Connect-IP: 2a0a:edc0:0:c01:1d::a2 X-SA-Exim-Mail-From: ore@pengutronix.de X-SA-Exim-Scanned: No (on metis.whiteo.stw.pengutronix.de); SAEximRunCond expanded to false X-PTX-Original-Recipient: linux-pm@vger.kernel.org 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 --- 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 + * "@,". + * + * 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) { } From patchwork Tue Jun 17 09:49:44 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Oleksij Rempel X-Patchwork-Id: 897463 Received: from metis.whiteo.stw.pengutronix.de (metis.whiteo.stw.pengutronix.de [185.203.201.7]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 92A522DF3F3 for ; Tue, 17 Jun 2025 09:50:06 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=185.203.201.7 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1750153808; cv=none; b=RJFRLk9gObpa8b16fiJ6v/92GuuuI2FYste9P+YXrakq+vrFsUhraGoryHPCrlGnSP5nAKx/9tVB/16mKbRdHv/iaCkh6HlLt+b7JM6MUSOJaxNVclvSVThfJIt4nQEXB6pot1grIHYfVCZg7WEuXe462vSMHYCA4ZJ3v8KP4fU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1750153808; c=relaxed/simple; bh=IUqdUNekHx5Op13q9+NocfuobfQom6jZt8Bq6U04qhU=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=NVb+OWOov2UuKXZ8PUXJ8VqXqQadkGRwTTLcaWU93W11WC+jZ+kp2BqX48v/7bk49+kQrIlj3D6bzg1cGk4RzSkA1cMqe/IRt4eySKzaZ0YP6PbrVQgB7KmpNeDDEA/sY93g1ywJW8WyXLjbJXTVAaUBBsR1Ra2SsNVh/Sjo30U= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=pengutronix.de; spf=pass smtp.mailfrom=pengutronix.de; arc=none smtp.client-ip=185.203.201.7 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=pengutronix.de Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=pengutronix.de Received: from drehscheibe.grey.stw.pengutronix.de ([2a0a:edc0:0:c01:1d::a2]) by metis.whiteo.stw.pengutronix.de with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1uRSwt-0005fH-0H; Tue, 17 Jun 2025 11:49:47 +0200 Received: from dude04.red.stw.pengutronix.de ([2a0a:edc0:0:1101:1d::ac]) by drehscheibe.grey.stw.pengutronix.de with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1uRSws-003x56-1C; Tue, 17 Jun 2025 11:49:46 +0200 Received: from ore by dude04.red.stw.pengutronix.de with local (Exim 4.96) (envelope-from ) id 1uRSws-00FBhV-0r; Tue, 17 Jun 2025 11:49:46 +0200 From: Oleksij Rempel To: Sebastian Reichel , Srinivas Kandagatla , Benson Leung , Tzung-Bi Shih , Daniel Lezcano Cc: Oleksij Rempel , kernel@pengutronix.de, linux-kernel@vger.kernel.org, Liam Girdwood , Mark Brown , "Rafael J. Wysocki" , Zhang Rui , Lukasz Luba , linux-pm@vger.kernel.org, =?utf-8?q?S=C3=B8ren_Andersen?= , Guenter Roeck , Matti Vaittinen , Ahmad Fatoum , Andrew Morton , chrome-platform@lists.linux.dev Subject: [PATCH v10 6/7] power: reset: add PSCR NVMEM Driver for Recording Power State Change Reasons Date: Tue, 17 Jun 2025 11:49:44 +0200 Message-Id: <20250617094945.3619360-7-o.rempel@pengutronix.de> X-Mailer: git-send-email 2.39.5 In-Reply-To: <20250617094945.3619360-1-o.rempel@pengutronix.de> References: <20250617094945.3619360-1-o.rempel@pengutronix.de> Precedence: bulk X-Mailing-List: linux-pm@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-SA-Exim-Connect-IP: 2a0a:edc0:0:c01:1d::a2 X-SA-Exim-Mail-From: ore@pengutronix.de X-SA-Exim-Scanned: No (on metis.whiteo.stw.pengutronix.de); SAEximRunCond expanded to false X-PTX-Original-Recipient: linux-pm@vger.kernel.org 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 --- 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 +#include +#include +#include +#include +#include +#include + +/* + * 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 "); +MODULE_DESCRIPTION("PSCRR backend for storing reason code in NVMEM"); +MODULE_LICENSE("GPL");