[PATCH v23 03/13] power: reset: reboot-mode: Add support for predefined reboot modes

From: Shivendra Pratap

Date: Tue Jul 14 2026 - 13:18:05 EST


reboot-mode based drivers can define a reboot-mode by adding it under
the reboot-mode node in device tree. This limits such drivers, to define
any predefined reboot-modes statically within the driver and creates a
dependency on device-tree.

Extend the reboot-mode framework to handle predefined modes alongside
those defined in device tree. Drivers can now define their own
reboot-modes and register them via the framework. A centralized init
call has been added to the reboot-mode framework and adopted by
existing drivers. This ensures driver state is initialized together
with predefined modes.

Signed-off-by: Shivendra Pratap <shivendra.pratap@xxxxxxxxxxxxxxxx>
---
drivers/power/reset/nvmem-reboot-mode.c | 4 +-
drivers/power/reset/qcom-pon.c | 4 +-
drivers/power/reset/reboot-mode.c | 192 ++++++++++++++++++++++++-------
drivers/power/reset/syscon-reboot-mode.c | 4 +-
include/linux/reboot-mode.h | 16 +++
5 files changed, 175 insertions(+), 45 deletions(-)

diff --git a/drivers/power/reset/nvmem-reboot-mode.c b/drivers/power/reset/nvmem-reboot-mode.c
index ac69b5f39fb4..4909c65ca02d 100644
--- a/drivers/power/reset/nvmem-reboot-mode.c
+++ b/drivers/power/reset/nvmem-reboot-mode.c
@@ -53,8 +53,8 @@ static int nvmem_reboot_mode_probe(struct platform_device *pdev)
if (!nvmem_rbm)
return -ENOMEM;

- nvmem_rbm->reboot.dev = &pdev->dev;
- nvmem_rbm->reboot.write = nvmem_reboot_mode_write;
+ reboot_mode_driver_init(&nvmem_rbm->reboot, &pdev->dev,
+ nvmem_reboot_mode_write);

nvmem_rbm->cell = devm_nvmem_cell_get(&pdev->dev, "reboot-mode");
if (IS_ERR(nvmem_rbm->cell)) {
diff --git a/drivers/power/reset/qcom-pon.c b/drivers/power/reset/qcom-pon.c
index a508ab26d03f..5f3c6002ef8a 100644
--- a/drivers/power/reset/qcom-pon.c
+++ b/drivers/power/reset/qcom-pon.c
@@ -72,9 +72,9 @@ static int qcom_pon_probe(struct platform_device *pdev)
reason_shift = (long)of_device_get_match_data(&pdev->dev);

if (reason_shift != NO_REASON_SHIFT) {
- pon->reboot_mode.dev = &pdev->dev;
+ reboot_mode_driver_init(&pon->reboot_mode, &pdev->dev,
+ qcom_pon_reboot_mode_write);
pon->reason_shift = reason_shift;
- pon->reboot_mode.write = qcom_pon_reboot_mode_write;
error = devm_reboot_mode_register(&pdev->dev, &pon->reboot_mode);
if (error) {
dev_err(&pdev->dev, "can't register reboot mode\n");
diff --git a/drivers/power/reset/reboot-mode.c b/drivers/power/reset/reboot-mode.c
index db2129c72341..08d5b58fce71 100644
--- a/drivers/power/reset/reboot-mode.c
+++ b/drivers/power/reset/reboot-mode.c
@@ -32,18 +32,30 @@ struct reboot_mode_sysfs_data {
struct list_head head;
};

-static inline void reboot_mode_release_list(struct reboot_mode_sysfs_data *priv)
+static inline void reboot_mode_release_list(struct list_head *head)
{
struct mode_info *info;
struct mode_info *next;

- list_for_each_entry_safe(info, next, &priv->head, list) {
+ list_for_each_entry_safe(info, next, head, list) {
list_del(&info->list);
kfree_const(info->mode);
kfree(info);
}
}

+/**
+ * reboot_mode_reset_predefined_modes - Remove all predefined reboot modes
+ * @reboot: reboot mode driver
+ *
+ * Reset predefined reboot modes added via reboot_mode_add_predefined_modes().
+ */
+void reboot_mode_reset_predefined_modes(struct reboot_mode_driver *reboot)
+{
+ reboot_mode_release_list(&reboot->predefined_modes);
+}
+EXPORT_SYMBOL_GPL(reboot_mode_reset_predefined_modes);
+
static ssize_t reboot_modes_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct reboot_mode_sysfs_data *priv;
@@ -117,6 +129,58 @@ static int reboot_mode_notify(struct notifier_block *this,
return NOTIFY_DONE;
}

+/**
+ * reboot_mode_driver_init - Initialize reboot-mode state
+ * @reboot: reboot mode driver object to initialize
+ * @dev: backing device
+ * @write: write callback for programming magic
+ *
+ * This function must be called with a valid @dev and @write before calling
+ * reboot_mode_register(), reboot_mode_add_predefined_modes(), or any other
+ * reboot-mode framework API.
+ */
+void reboot_mode_driver_init(struct reboot_mode_driver *reboot,
+ struct device *dev,
+ int (*write)(struct reboot_mode_driver *reboot, u32 *magic, int count))
+{
+ memset(reboot, 0, sizeof(*reboot));
+ reboot->dev = dev;
+ reboot->write = write;
+ INIT_LIST_HEAD(&reboot->head);
+ INIT_LIST_HEAD(&reboot->predefined_modes);
+}
+EXPORT_SYMBOL_GPL(reboot_mode_driver_init);
+
+static struct mode_info *reboot_mode_create_info(const char *mode, const u32 *magic, int count)
+{
+ struct mode_info *info;
+
+ if (!mode || mode[0] == '\0') {
+ pr_err("invalid mode name\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ info = kzalloc_obj(*info, GFP_KERNEL);
+ if (!info)
+ return ERR_PTR(-ENOMEM);
+
+ info->mode = kstrdup_const(mode, GFP_KERNEL);
+ if (!info->mode) {
+ kfree(info);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ if (!memchr_inv(magic, 0, count * sizeof(u32))) {
+ pr_debug("reboot mode %s with zero magic values\n", mode);
+ info->count = -1;
+ } else {
+ memcpy(info->magic, magic, count * sizeof(u32));
+ info->count = count;
+ }
+
+ return info;
+}
+
static int reboot_mode_create_device(struct reboot_mode_driver *reboot)
{
struct reboot_mode_sysfs_data *priv;
@@ -158,7 +222,7 @@ static int reboot_mode_create_device(struct reboot_mode_driver *reboot)
return 0;

error:
- reboot_mode_release_list(priv);
+ reboot_mode_release_list(&priv->head);
kfree(priv);
return ret;
}
@@ -171,7 +235,7 @@ static int reboot_mode_create_device(struct reboot_mode_driver *reboot)
*/
int reboot_mode_register(struct reboot_mode_driver *reboot)
{
- struct mode_info *info = NULL;
+ struct mode_info *info;
struct property *prop;
struct device_node *np = reboot->dev->of_node;
size_t len = strlen(PREFIX);
@@ -179,11 +243,16 @@ int reboot_mode_register(struct reboot_mode_driver *reboot)
int count;
int ret;

+ if (reboot->reboot_notifier.notifier_call == reboot_mode_notify)
+ return -EBUSY;
+
INIT_LIST_HEAD(&reboot->head);

+ if (!np)
+ goto predefined_modes;
+
for_each_property_of_node(np, prop) {
memset(magic, 0, sizeof(magic));
-
if (strncmp(prop->name, PREFIX, len))
continue;

@@ -195,35 +264,17 @@ int reboot_mode_register(struct reboot_mode_driver *reboot)
continue;
}

- info = kzalloc(sizeof(*info), GFP_KERNEL);
- if (!info) {
- ret = -ENOMEM;
- goto error;
- }
-
- if (!memchr_inv(magic, 0, count * sizeof(u32))) {
- pr_debug("reboot mode %s with zero magic values\n", prop->name);
- info->count = -1;
- } else {
- memcpy(info->magic, magic, count * sizeof(u32));
- info->count = count;
- }
-
- info->mode = kstrdup_const(prop->name + len, GFP_KERNEL);
- if (!info->mode) {
- ret = -ENOMEM;
- goto error;
- } else if (info->mode[0] == '\0') {
- kfree_const(info->mode);
- ret = -EINVAL;
- pr_err("invalid mode name(%s): too short!\n", prop->name);
+ info = reboot_mode_create_info(prop->name + len, magic, count);
+ if (IS_ERR(info)) {
+ ret = PTR_ERR(info);
goto error;
}

list_add_tail(&info->list, &reboot->head);
- info = NULL;
}

+predefined_modes:
+ list_splice_tail_init(&reboot->predefined_modes, &reboot->head);
reboot->reboot_notifier.notifier_call = reboot_mode_notify;
register_reboot_notifier(&reboot->reboot_notifier);

@@ -234,7 +285,6 @@ int reboot_mode_register(struct reboot_mode_driver *reboot)
return 0;

error:
- kfree(info);
reboot_mode_unregister(reboot);
return ret;
}
@@ -267,7 +317,7 @@ static inline void reboot_mode_unregister_device(struct reboot_mode_driver *rebo
if (!priv)
return;

- reboot_mode_release_list(priv);
+ reboot_mode_release_list(&priv->head);
kfree(priv);
}

@@ -277,17 +327,12 @@ static inline void reboot_mode_unregister_device(struct reboot_mode_driver *rebo
*/
int reboot_mode_unregister(struct reboot_mode_driver *reboot)
{
- struct mode_info *info;
- struct mode_info *next;
-
unregister_reboot_notifier(&reboot->reboot_notifier);
+ reboot->reboot_notifier.notifier_call = NULL;
reboot_mode_unregister_device(reboot);

- list_for_each_entry_safe(info, next, &reboot->head, list) {
- list_del(&info->list);
- kfree_const(info->mode);
- kfree(info);
- }
+ reboot_mode_release_list(&reboot->head);
+ reboot_mode_release_list(&reboot->predefined_modes);

return 0;
}
@@ -312,8 +357,10 @@ int devm_reboot_mode_register(struct device *dev,
int rc;

dr = devres_alloc(devm_reboot_mode_release, sizeof(*dr), GFP_KERNEL);
- if (!dr)
+ if (!dr) {
+ reboot_mode_reset_predefined_modes(reboot);
return -ENOMEM;
+ }

rc = reboot_mode_register(reboot);
if (rc) {
@@ -352,6 +399,73 @@ void devm_reboot_mode_unregister(struct device *dev,
}
EXPORT_SYMBOL_GPL(devm_reboot_mode_unregister);

+/**
+ * reboot_mode_add_predefined_modes - Add predefined reboot modes
+ * @reboot: reboot mode driver
+ * @modes: array of predefined reboot mode entries
+ * @count: number of entries in @modes
+ *
+ * Add predefined reboot modes before registration.
+ *
+ * The entire list is discarded if any mode entry is invalid. An entry
+ * with a zero or negative magic count, a NULL mode string, or a mode
+ * string containing spaces or "\n" is considered invalid.
+ *
+ * Predefined modes are cleared if registration fails.
+ * Call reboot_mode_reset_predefined_modes() if registration is not
+ * performed after adding predefined modes.
+ *
+ * @reboot must be initialized with reboot_mode_driver_init() before calling
+ * this function.
+ *
+ * Returns: 0 on success,
+ * -EINVAL if invalid entry is found in list,
+ * -EBUSY if called after reboot_mode_register() or if predefined modes
+ * are already set, and, -ENOMEM on allocation failures.
+ */
+int reboot_mode_add_predefined_modes(struct reboot_mode_driver *reboot,
+ const struct reboot_mode_entry *modes,
+ size_t count)
+{
+ struct mode_info *info;
+ int ret;
+ size_t i;
+
+ if (reboot->reboot_notifier.notifier_call == reboot_mode_notify ||
+ !list_empty(&reboot->predefined_modes))
+ return -EBUSY;
+
+ if (!modes || !count)
+ return -EINVAL;
+
+ for (i = 0; i < count; i++) {
+ if (modes[i].name && strpbrk(modes[i].name, "\n ")) {
+ ret = -EINVAL;
+ goto error;
+ }
+
+ if (modes[i].count <= 0 || modes[i].count > ARRAY_SIZE(modes[i].magic)) {
+ ret = -EINVAL;
+ goto error;
+ }
+
+ info = reboot_mode_create_info(modes[i].name, modes[i].magic, modes[i].count);
+ if (IS_ERR(info)) {
+ ret = PTR_ERR(info);
+ goto error;
+ }
+
+ list_add_tail(&info->list, &reboot->predefined_modes);
+ }
+
+ return 0;
+
+error:
+ reboot_mode_release_list(&reboot->predefined_modes);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(reboot_mode_add_predefined_modes);
+
static int __init reboot_mode_init(void)
{
return class_register(&reboot_mode_class);
diff --git a/drivers/power/reset/syscon-reboot-mode.c b/drivers/power/reset/syscon-reboot-mode.c
index 983b1fbf614a..0c7e11276e6f 100644
--- a/drivers/power/reset/syscon-reboot-mode.c
+++ b/drivers/power/reset/syscon-reboot-mode.c
@@ -47,8 +47,8 @@ static int syscon_reboot_mode_probe(struct platform_device *pdev)
if (!syscon_rbm)
return -ENOMEM;

- syscon_rbm->reboot.dev = &pdev->dev;
- syscon_rbm->reboot.write = syscon_reboot_mode_write;
+ reboot_mode_driver_init(&syscon_rbm->reboot, &pdev->dev,
+ syscon_reboot_mode_write);
syscon_rbm->mask = 0xffffffff;

syscon_rbm->map = syscon_node_to_regmap(pdev->dev.parent->of_node);
diff --git a/include/linux/reboot-mode.h b/include/linux/reboot-mode.h
index 34a81e4b4f95..b216c7595f7e 100644
--- a/include/linux/reboot-mode.h
+++ b/include/linux/reboot-mode.h
@@ -4,18 +4,34 @@

#include <linux/types.h>

+struct reboot_mode_entry {
+ const char *name;
+ u32 magic[3];
+ int count;
+};
+
struct reboot_mode_driver {
struct device *dev;
struct list_head head;
+ /* List of predefined reboot-modes, populated via reboot_mode_add_predefined_modes(). */
+ struct list_head predefined_modes;
int (*write)(struct reboot_mode_driver *reboot, u32 *magic, int count);
struct notifier_block reboot_notifier;
};

+void reboot_mode_driver_init(struct reboot_mode_driver *reboot,
+ struct device *dev,
+ int (*write)(struct reboot_mode_driver *reboot, u32 *magic,
+ int count));
int reboot_mode_register(struct reboot_mode_driver *reboot);
int reboot_mode_unregister(struct reboot_mode_driver *reboot);
int devm_reboot_mode_register(struct device *dev,
struct reboot_mode_driver *reboot);
void devm_reboot_mode_unregister(struct device *dev,
struct reboot_mode_driver *reboot);
+int reboot_mode_add_predefined_modes(struct reboot_mode_driver *reboot,
+ const struct reboot_mode_entry *modes,
+ size_t count);
+void reboot_mode_reset_predefined_modes(struct reboot_mode_driver *reboot);

#endif

--
2.34.1