[PATCH v25 06/10] power: reset: Add psci-reboot-mode driver
From: Shivendra Pratap
Date: Mon Sep 14 2026 - 11:12:35 EST
PSCI supports different types of resets like SYSTEM_RESET, SYSTEM_RESET2
ARCH WARM reset and SYSTEM_RESET2 vendor-specific resets. Currently
there is no common driver that handles all supported psci resets at one
place. Additionally, there is no common mechanism to issue the supported
psci resets from userspace.
Add psci-reboot-mode as an auxiliary device created by the psci-devices
driver. Define two types of PSCI resets, predefined-resets and
vendor-specific resets. Predefined-resets are defined by psci driver
and vendor-specific resets are defined by SoC vendors, under the
psci:reboot-mode node of SoC device tree.
Register the driver with the reboot-mode framework to interface these
resets to userspace. When userspace initiates a supported command, pass
the reset arguments to the PSCI driver to enable command-based reset.
This change allows userspace to issue supported PSCI reset commands
using the standard reboot system calls while enabling SoC vendors to
define their specific resets for PSCI.
Suggested-by: Ulf Hansson <ulf.hansson@xxxxxxxxxxxxxxxx>
Suggested-by: Lee Jones <lee@xxxxxxxxxx>
Suggested-by: Bartosz Golaszewski <bartosz.golaszewski@xxxxxxxxxxxxxxxx>
Signed-off-by: Shivendra Pratap <shivendra.pratap@xxxxxxxxxxxxxxxx>
---
MAINTAINERS | 1 +
drivers/firmware/psci/psci-devices.c | 71 +++++++++++++++++++++-
drivers/power/reset/Kconfig | 10 ++++
drivers/power/reset/Makefile | 1 +
drivers/power/reset/psci-reboot-mode.c | 104 +++++++++++++++++++++++++++++++++
5 files changed, 185 insertions(+), 2 deletions(-)
diff --git a/MAINTAINERS b/MAINTAINERS
index 9c0334ee2ed2..1c0f111ba197 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -21933,6 +21933,7 @@ S: Maintained
F: Documentation/devicetree/bindings/arm/psci.yaml
F: drivers/firmware/psci/
F: drivers/firmware/psci/psci-devices.c
+F: drivers/power/reset/psci-reboot-mode.c
F: include/linux/psci.h
F: include/uapi/linux/psci.h
diff --git a/drivers/firmware/psci/psci-devices.c b/drivers/firmware/psci/psci-devices.c
index f1eca61e3269..43e6743202f9 100644
--- a/drivers/firmware/psci/psci-devices.c
+++ b/drivers/firmware/psci/psci-devices.c
@@ -4,19 +4,86 @@
*/
#include <linux/auxiliary_bus.h>
+#include <linux/device.h>
#include <linux/init.h>
#include <linux/of.h>
#include <linux/platform_device.h>
+#include <linux/psci.h>
+#include <linux/slab.h>
+
+static void arm_psci_auxiliary_device_release(struct device *dev)
+{
+ struct auxiliary_device *auxdev = to_auxiliary_dev(dev);
+
+ of_node_put(dev->of_node);
+ kfree(auxdev);
+}
+
+static struct auxiliary_device *
+arm_psci_auxiliary_device_create(struct device *dev, const char *devname,
+ struct device_node *np)
+{
+ struct auxiliary_device *auxdev;
+ int ret;
+
+ auxdev = kzalloc_obj(*auxdev);
+ if (!auxdev)
+ return NULL;
+
+ auxdev->id = 0;
+ auxdev->name = devname;
+ auxdev->dev.parent = dev;
+ auxdev->dev.release = arm_psci_auxiliary_device_release;
+
+ if (np)
+ device_set_node(&auxdev->dev, of_fwnode_handle(of_node_get(np)));
+
+ ret = auxiliary_device_init(auxdev);
+ if (ret) {
+ of_node_put(auxdev->dev.of_node);
+ kfree(auxdev);
+ return NULL;
+ }
+
+ ret = __auxiliary_device_add(auxdev, "arm-psci");
+ if (ret) {
+ auxiliary_device_uninit(auxdev);
+ return NULL;
+ }
+
+ ret = devm_add_action_or_reset(dev, auxiliary_device_destroy, auxdev);
+ if (ret)
+ return NULL;
+
+ return auxdev;
+}
static int arm_psci_probe(struct platform_device *pdev)
{
struct auxiliary_device *auxdev;
+#ifdef CONFIG_PSCI_REBOOT_MODE
+ struct device_node *reboot_mode_np = NULL;
+#endif
- auxdev = __devm_auxiliary_device_create(&pdev->dev, "arm-psci",
- "psci-cpuidle-domain", NULL, 0);
+ auxdev = arm_psci_auxiliary_device_create(&pdev->dev,
+ "psci-cpuidle-domain",
+ pdev->dev.of_node);
if (!auxdev)
return -ENOMEM;
+#ifdef CONFIG_PSCI_REBOOT_MODE
+ if (psci_has_system_reset2_support())
+ reboot_mode_np = of_get_child_by_name(pdev->dev.of_node,
+ "reboot-mode");
+
+ auxdev = arm_psci_auxiliary_device_create(&pdev->dev,
+ "psci-reboot-mode",
+ reboot_mode_np);
+ of_node_put(reboot_mode_np);
+ if (!auxdev)
+ dev_warn(&pdev->dev, "failed to create PSCI reboot mode device\n");
+#endif
+
return 0;
}
diff --git a/drivers/power/reset/Kconfig b/drivers/power/reset/Kconfig
index bce996bbef28..5c349f41e097 100644
--- a/drivers/power/reset/Kconfig
+++ b/drivers/power/reset/Kconfig
@@ -360,6 +360,16 @@ config NVMEM_REBOOT_MODE
then the bootloader can read it and take different
action according to the mode.
+config PSCI_REBOOT_MODE
+ bool "PSCI reboot mode driver"
+ depends on ARM_PSCI_DEVICES
+ select REBOOT_MODE
+ help
+ Say y here to enable the PSCI reboot mode driver. The driver
+ registers with the reboot-mode framework to configure PSCI
+ reset commands, which are executed by the PSCI driver during
+ psci_sys_reset().
+
config POWER_MLXBF
tristate "Mellanox BlueField power handling driver"
depends on (GPIO_MLXBF2 || GPIO_MLXBF3) && ACPI
diff --git a/drivers/power/reset/Makefile b/drivers/power/reset/Makefile
index e31cab4ba78e..45d8aaaffaa1 100644
--- a/drivers/power/reset/Makefile
+++ b/drivers/power/reset/Makefile
@@ -41,5 +41,6 @@ obj-$(CONFIG_REBOOT_MODE) += reboot-mode.o
obj-$(CONFIG_SYSCON_REBOOT_MODE) += syscon-reboot-mode.o
obj-$(CONFIG_POWER_RESET_SC27XX) += sc27xx-poweroff.o
obj-$(CONFIG_NVMEM_REBOOT_MODE) += nvmem-reboot-mode.o
+obj-$(CONFIG_PSCI_REBOOT_MODE) += psci-reboot-mode.o
obj-$(CONFIG_POWER_MLXBF) += pwr-mlxbf.o
obj-$(CONFIG_POWER_RESET_QEMU_VIRT_CTRL) += qemu-virt-ctrl.o
diff --git a/drivers/power/reset/psci-reboot-mode.c b/drivers/power/reset/psci-reboot-mode.c
new file mode 100644
index 000000000000..bf2dd6626feb
--- /dev/null
+++ b/drivers/power/reset/psci-reboot-mode.c
@@ -0,0 +1,104 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#include <linux/array_size.h>
+#include <linux/auxiliary_bus.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/kconfig.h>
+#include <linux/module.h>
+#include <linux/psci.h>
+#include <linux/reboot-mode.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+
+/*
+ * Predefined modes use two arguments. arg1/magic[0] is always zero and
+ * arg2/magic[1] is one of the values defined by enum psci_standard_resets.
+ */
+static const struct reboot_mode_entry psci_resets[] = {
+ {
+ .name = "psci-system-reset",
+ .magic = { 0, PSCI_SYSTEM_RESET_COLD_RESET },
+ .count = 2,
+ },
+ {
+ .name = "psci-system-reset2-arch-warm-reset",
+ .magic = { 0, PSCI_SYSTEM_RESET2_ARCH_WARM_RESET },
+ .count = 2,
+ },
+};
+
+static u64 psci_reboot_mode_get_cookie(const u32 *magic, int count)
+{
+ u64 cookie = 0;
+ int i;
+
+ /*
+ * For a 3-cell magic, arg2 is the high 32 bits and arg3 is the low 32 bits.
+ * For a 2-cell magic, arg2 is the low 32 bits.
+ */
+ for (i = 1; i < count; i++)
+ cookie = (cookie << 32) | magic[i];
+
+ return cookie;
+}
+
+static int psci_reboot_mode_write(struct reboot_mode_driver *reboot,
+ const u32 *magic, u32 count)
+{
+ if (count < 2 || count > 3)
+ return -EINVAL;
+
+ /* Unexpected value at magic[1]/arg2 on a 32-bit system. */
+ if (!IS_ENABLED(CONFIG_64BIT) && count == 3 && magic[1])
+ return -EINVAL;
+
+ return psci_set_reset_cmd(magic[0],
+ psci_reboot_mode_get_cookie(magic, count));
+}
+
+static int psci_reboot_mode_probe(struct auxiliary_device *auxdev,
+ const struct auxiliary_device_id *id)
+{
+ struct reboot_mode_driver *reboot;
+ struct device *dev = &auxdev->dev;
+ size_t count;
+ int ret;
+
+ reboot = devm_kzalloc(dev, sizeof(*reboot), GFP_KERNEL);
+ if (!reboot)
+ return -ENOMEM;
+
+ /* Skip PSCI SYSTEM_RESET2 modes if unsupported. */
+ count = psci_has_system_reset2_support() ? ARRAY_SIZE(psci_resets) : 1;
+
+ ret = reboot_mode_driver_init(reboot, dev, NULL,
+ psci_reboot_mode_write, psci_resets,
+ count);
+ if (ret)
+ return ret;
+
+ return devm_reboot_mode_register(dev, reboot);
+}
+
+static const struct auxiliary_device_id psci_reboot_mode_id_table[] = {
+ { .name = "arm-psci.psci-reboot-mode" },
+ { }
+};
+MODULE_DEVICE_TABLE(auxiliary, psci_reboot_mode_id_table);
+
+static struct auxiliary_driver psci_reboot_mode_driver = {
+ .probe = psci_reboot_mode_probe,
+ .id_table = psci_reboot_mode_id_table,
+};
+
+static int __init psci_reboot_mode_init(void)
+{
+ return __auxiliary_driver_register(&psci_reboot_mode_driver, THIS_MODULE,
+ "psci-reboot-mode");
+}
+subsys_initcall(psci_reboot_mode_init);
--
2.34.1