[PATCH v24 03/12] firmware: psci: Introduce command-based resets
From: Shivendra Pratap <hidden>
Date: 2026-08-03 09:46:24
Also in:
linux-arm-msm, linux-devicetree, linux-pm, lkml
Subsystem:
power state coordination interface (psci), the rest · Maintainers:
Mark Rutland, Lorenzo Pieralisi, Linus Torvalds
PSCI currently supports only two resets - SYSTEM_RESET and SYSTEM_RESET2
ARCH WARM reset. The reset patch is selected based on the Linux
reboot_mode variable. The PSCI specification now includes SYSTEM_RESET2
for vendor-specific resets but there's no mechanism to issue these
through psci_sys_reset().
Add a command-based reset mechanism that allows reboot-mode drivers to set
the PSCI reset command by passing a reset_type and a cookie.
Add support for the following reset commands:
- SYSTEM_RESET2 vendor-specific resets.
- SYSTEM_RESET2 ARCH WARM reset and SYSTEM_RESET (reset_type = 0 and
cookie one of psci_standard_resets).
Unsupported commands fall back to the regular PSCI reset path.
Default to the existing reboot_mode-based reset flow unless a reset
command is configured. If a kernel panic() or a emergency_restart()
occurs after a reset command is set but before the final PSCI reset is
issued, ignore the reset command and follow the normal reboot_mode-based
reset path.
Signed-off-by: Shivendra Pratap <redacted>
---
drivers/firmware/psci/psci.c | 112 ++++++++++++++++++++++++++++++++++++++++++-
include/linux/psci.h | 15 ++++++
2 files changed, 125 insertions(+), 2 deletions(-)
diff --git a/drivers/firmware/psci/psci.c b/drivers/firmware/psci/psci.c
index e73bae6cb23a..094303ef7c03 100644
--- a/drivers/firmware/psci/psci.c
+++ b/drivers/firmware/psci/psci.c@@ -12,7 +12,9 @@ #include <linux/debugfs.h> #include <linux/errno.h> #include <linux/linkage.h> +#include <linux/mutex.h> #include <linux/of.h> +#include <linux/panic.h> #include <linux/pm.h> #include <linux/printk.h> #include <linux/psci.h>
@@ -22,6 +24,7 @@ #include <uapi/linux/psci.h> +#include <asm/barrier.h> #include <asm/cpuidle.h> #include <asm/cputype.h> #include <asm/hypervisor.h>
@@ -51,6 +54,15 @@ static int resident_cpu = -1; struct psci_operations psci_ops; static enum arm_smccc_conduit psci_conduit = SMCCC_CONDUIT_NONE; +struct psci_system_reset_cmd { + u32 reset_type; + u64 cookie; +}; + +static struct psci_system_reset_cmd reset_cmd_data; +static struct psci_system_reset_cmd *reset_cmd; +static DEFINE_MUTEX(reset_cmd_mutex); + bool psci_tos_resident_on(int cpu) { return cpu == resident_cpu;
@@ -80,6 +92,61 @@ static u32 psci_cpu_suspend_feature; static bool psci_system_reset2_supported; static bool psci_system_off2_hibernate_supported; +static u32 psci_get_sys_reset_fn(const struct psci_system_reset_cmd *cmd) +{ + switch (cmd->cookie) { + case PSCI_SYSTEM_RESET2_ARCH_WARM_RESET: + if (psci_system_reset2_supported) + return PSCI_FN_NATIVE(1_1, SYSTEM_RESET2); + return 0; + case PSCI_SYSTEM_RESET_COLD_RESET: + return PSCI_0_2_FN_SYSTEM_RESET; + default: + return 0; + } +} + +/** + * psci_set_reset_cmd() - Configure the PSCI reset command + * @reset_type: SYSTEM_RESET2 reset type, or 0 for a standard reset + * @cookie: Vendor-defined SYSTEM_RESET2 cookie, or a value from + * enum psci_standard_resets when @reset_type is 0 + * + * For vendor-specific SYSTEM_RESET2 resets, @reset_type and @cookie + * must contain platform-defined values. + * + * For standard resets, @reset_type must be 0 and @cookie must be a + * value from enum psci_standard_resets. + * + * The reset command may be configured only once per boot cycle. + */ +int psci_set_reset_cmd(u32 reset_type, u64 cookie) +{ + if (!reset_type && !cookie) + return -EINVAL; + + scoped_guard(mutex, &reset_cmd_mutex) { + if (reset_cmd) + return -EBUSY; + + reset_cmd_data.reset_type = reset_type; + reset_cmd_data.cookie = cookie; + /* + * Publish the command only after both fields are fully initialized. + * Readers run from the atomic restart notifier path and must not block. + */ + smp_store_release(&reset_cmd, &reset_cmd_data); + } + + return 0; +} + +bool psci_has_system_reset2_support(void) +{ + return psci_system_reset2_supported; +} +EXPORT_SYMBOL_NS_GPL(psci_has_system_reset2_support, "PSCI"); + static inline bool psci_has_ext_power_state(void) { return psci_cpu_suspend_feature &
@@ -306,8 +373,25 @@ static int get_set_conduit_method(const struct device_node *np) return 0; } -static int psci_sys_reset(struct notifier_block *nb, unsigned long action, - void *data) +static void psci_handle_reset_cmd(const struct psci_system_reset_cmd *cmd) +{ + u32 psci_sys_reset_fn; + + /* PSCI_1_1_RESET_TYPE_VENDOR_START identifies vendor reset types. */ + if ((cmd->reset_type & PSCI_1_1_RESET_TYPE_VENDOR_START) && + psci_system_reset2_supported) { + /* PSCI SYSTEM_RESET2 Vendor-specific reset */ + invoke_psci_fn(PSCI_FN_NATIVE(1_1, SYSTEM_RESET2), + cmd->reset_type, cmd->cookie, 0); + } else { + /* Retrieve the psci reset function from reset_cmd */ + psci_sys_reset_fn = psci_get_sys_reset_fn(cmd); + if (!cmd->reset_type && psci_sys_reset_fn) + invoke_psci_fn(psci_sys_reset_fn, 0, 0, 0); + } +} + +static void psci_handle_reboot_mode(void) { if ((reboot_mode == REBOOT_WARM || reboot_mode == REBOOT_SOFT) && psci_system_reset2_supported) {
@@ -320,6 +404,30 @@ static int psci_sys_reset(struct notifier_block *nb, unsigned long action, } else { invoke_psci_fn(PSCI_0_2_FN_SYSTEM_RESET, 0, 0, 0); } +} + +static int psci_sys_reset(struct notifier_block *nb, unsigned long action, + void *data) +{ + const struct psci_system_reset_cmd *cmd; + + /* The function psci_handle_reboot_mode follows reboot_mode based + * reset flow and psci_handle_reset_cmd uses reset_cmd based reset flow. + * + * The reset_cmd is configured at the reboot_notifier phase. + * If panic() or emergency_restart() occurs between the reboot_notifier + * and this final reset, skip command-based reset and let reboot_mode drive + * the reset flow. + * + * The function psci_handle_reset_cmd invokes non-returning PSCI SYSTEM_RESET + * calls to reset the device. If it returns, either the reset failed, or the + * command was unsupported. Fallback to reboot_mode based reset flow. + */ + cmd = smp_load_acquire(&reset_cmd); + if (data && cmd && !panic_in_progress()) + psci_handle_reset_cmd(cmd); + + psci_handle_reboot_mode(); return NOTIFY_DONE; }
diff --git a/include/linux/psci.h b/include/linux/psci.h
index 4ca0060a3fc4..e81b1cdeca51 100644
--- a/include/linux/psci.h
+++ b/include/linux/psci.h@@ -8,6 +8,7 @@ #define __LINUX_PSCI_H #include <linux/arm-smccc.h> +#include <linux/errno.h> #include <linux/init.h> #include <linux/types.h>
@@ -21,6 +22,16 @@ bool psci_power_state_is_valid(u32 state); int psci_set_osi_mode(bool enable); bool psci_has_osi_support(void); +/** + * enum psci_standard_resets - Standard reset selectors for PSCI reset + * @PSCI_SYSTEM_RESET_COLD_RESET: Standard SYSTEM_RESET command. + * @PSCI_SYSTEM_RESET2_ARCH_WARM_RESET: SYSTEM_RESET2 architectural warm reset. + */ +enum psci_standard_resets { + PSCI_SYSTEM_RESET_COLD_RESET = 1, + PSCI_SYSTEM_RESET2_ARCH_WARM_RESET, +}; + struct psci_operations { u32 (*get_version)(void); int (*cpu_suspend)(u32 state, unsigned long entry_point);
@@ -45,8 +56,12 @@ struct psci_0_1_function_ids get_psci_0_1_function_ids(void); #if defined(CONFIG_ARM_PSCI_FW) int __init psci_dt_init(void); +int psci_set_reset_cmd(u32 reset_type, u64 cookie); +bool psci_has_system_reset2_support(void); #else static inline int psci_dt_init(void) { return 0; } +static inline int psci_set_reset_cmd(u32 reset_type, u64 cookie) { return -EOPNOTSUPP; } +static inline bool psci_has_system_reset2_support(void) { return false; } #endif #if defined(CONFIG_ARM_PSCI_FW) && defined(CONFIG_ACPI)
--
2.34.1