[PATCH v23 03/13] power: reset: reboot-mode: Add support for predefined reboot modes
From: Shivendra Pratap <hidden>
Date: 2026-07-14 17:17:39
Also in:
linux-arm-msm, linux-devicetree, linux-pm, lkml
Subsystem:
arm/qualcomm mailing list, system reset/shutdown drivers, the rest · Maintainers:
Sebastian Reichel, Linus Torvalds
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 <redacted> --- 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