[PATCH v24 02/12] power: reset: reboot-mode: Add support for predefined reboot modes
Shivendra Pratap <[email protected]> Mon, 03 Aug 2026 15:13:33 +0530
| Newsgroups | org.kernel.vger.linux-pm,dev.linux.lists.mfd,org.infradead.lists.linux-arm-kernel,org.kernel.vger.linux-arm-msm,org.kernel.vger.linux-devicetree,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <20260803-arm-psci-system_reset2-vendor-reboots-v24-2-889281373870@oss.qualcomm.com> |
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 support driver-defined predefined reboot modes. Add a centralized initcall to initialize driver state along with predefined modes. Signed-off-by: Shivendra Pratap <[email protected]> --- drivers/power/reset/nvmem-reboot-mode.c | 7 +- drivers/power/reset/qcom-pon.c | 7 +- drivers/power/reset/reboot-mode.c | 192 ++++++++++++++++++++++--------- drivers/power/reset/syscon-reboot-mode.c | 7 +- include/linux/reboot-mode.h | 26 ++++- 5 files changed, 175 insertions(+), 64 deletions(-) diff --git a/drivers/power/reset/nvmem-reboot-mode.c b/drivers/power/reset/nvmem-reboot-mode.c index d260715fccf6..700732ed0f5b 100644 --- a/drivers/power/reset/nvmem-reboot-mode.c +++ b/drivers/power/reset/nvmem-reboot-mode.c @@ -51,8 +51,11 @@ 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; + ret = reboot_mode_driver_init(&nvmem_rbm->reboot, &pdev->dev, + nvmem_reboot_mode_write, NULL, + NULL, 0); + if (ret) + return ret; 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 7e108982a582..f63996994a25 100644 --- a/drivers/power/reset/qcom-pon.c +++ b/drivers/power/reset/qcom-pon.c @@ -70,9 +70,12 @@ 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; + error = reboot_mode_driver_init(&pon->reboot_mode, &pdev->dev, + qcom_pon_reboot_mode_write, NULL, + NULL, 0); + if (error) + return error; 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 c7e9285c64e9..cf8978c0ce22 100644 --- a/drivers/power/reset/reboot-mode.c +++ b/drivers/power/reset/reboot-mode.c @@ -30,12 +30,12 @@ struct reboot_mode_sysfs_data { struct list_head head; }; -static inline void reboot_mode_release_list(struct reboot_mode_sysfs_data *priv) +static 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); @@ -119,6 +119,80 @@ 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 to program a single magic value + * @write_array: write callback to program multiple magic values + * @predefined_modes: optional predefined reboot-mode table + * @predefined_mode_count: number of entries in @predefined_modes + * + * Exactly one write callback must be provided by the driver. + * + * This function must be called with a valid @dev before calling + * reboot_mode_register(). + * + * Returns: 0 on success or -EINVAL if callback/predefined mode configuration + * is invalid. + */ +int reboot_mode_driver_init(struct reboot_mode_driver *reboot, + struct device *dev, + int (*write)(struct reboot_mode_driver *reboot, unsigned int magic), + int (*write_array)(struct reboot_mode_driver *reboot, + const u32 *magic, u32 count), + const struct reboot_mode_entry *predefined_modes, + size_t predefined_mode_count) +{ + if (!reboot || !dev) + return -EINVAL; + + if (!write == !write_array) + return -EINVAL; + + memset(reboot, 0, sizeof(*reboot)); + reboot->dev = dev; + reboot->write = write; + reboot->write_array = write_array; + reboot->predefined_modes = predefined_modes; + reboot->predefined_mode_count = predefined_mode_count; + INIT_LIST_HEAD(&reboot->head); + + return 0; +} +EXPORT_SYMBOL_GPL(reboot_mode_driver_init); + +static struct mode_info *reboot_mode_create_info(struct device *dev, const char *mode, + const u32 *magic, int count) +{ + struct mode_info *info; + + if (!mode || mode[0] == '\0') { + dev_err(dev, "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))) { + dev_dbg(dev, "reboot mode %s with zero magic values\n", mode); + info->count = 0; + } 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; @@ -160,11 +234,38 @@ 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; } +static int reboot_mode_add_predefined_modes(struct reboot_mode_driver *reboot) +{ + const struct reboot_mode_entry *modes = reboot->predefined_modes; + struct mode_info *info; + size_t i; + + if (!modes) + return 0; + + for (i = 0; i < reboot->predefined_mode_count; i++) { + if (modes[i].name && strpbrk(modes[i].name, "\n ,/")) + return -EINVAL; + + if (!modes[i].count || modes[i].count > ARRAY_SIZE(modes[i].magic)) + return -EINVAL; + + info = reboot_mode_create_info(reboot->dev, modes[i].name, + modes[i].magic, modes[i].count); + if (IS_ERR(info)) + return PTR_ERR(info); + + list_add_tail(&info->list, &reboot->head); + } + + return 0; +} + /** * reboot_mode_register - register a reboot mode driver * @reboot: reboot mode driver @@ -173,7 +274,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); @@ -183,52 +284,36 @@ int reboot_mode_register(struct reboot_mode_driver *reboot) INIT_LIST_HEAD(&reboot->head); - for_each_property_of_node(np, prop) { - memset(magic, 0, sizeof(magic)); - - if (strncmp(prop->name, PREFIX, len)) - continue; - - count = device_property_count_u32(reboot->dev, prop->name); - - if (count <= 0 || count > ARRAY_SIZE(magic) || - device_property_read_u32_array(reboot->dev, prop->name, magic, count)) { - dev_dbg(reboot->dev, "reboot mode %s without magic number\n", - prop->name); - continue; - } - - info = kzalloc_obj(*info, GFP_KERNEL); - if (!info) { - ret = -ENOMEM; - goto error; - } - - if (!memchr_inv(magic, 0, count * sizeof(u32))) { - dev_dbg(reboot->dev, "reboot mode %s with zero magic values\n", - prop->name); - info->count = 0; - } 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; - dev_err(reboot->dev, "invalid mode name(%s): too short!\n", - prop->name); - goto error; + if (np) { + for_each_property_of_node(np, prop) { + memset(magic, 0, sizeof(magic)); + if (strncmp(prop->name, PREFIX, len)) + continue; + + count = device_property_count_u32(reboot->dev, prop->name); + + if (count <= 0 || count > ARRAY_SIZE(magic) || + device_property_read_u32_array(reboot->dev, prop->name, magic, count)) { + dev_dbg(reboot->dev, "reboot mode %s without magic number\n", + prop->name); + continue; + } + + info = reboot_mode_create_info(reboot->dev, prop->name + len, + magic, count); + if (IS_ERR(info)) { + ret = PTR_ERR(info); + goto error; + } + + list_add_tail(&info->list, &reboot->head); } - - list_add_tail(&info->list, &reboot->head); - info = NULL; } + ret = reboot_mode_add_predefined_modes(reboot); + if (ret) + goto error; + reboot->reboot_notifier.notifier_call = reboot_mode_notify; register_reboot_notifier(&reboot->reboot_notifier); @@ -239,7 +324,6 @@ int reboot_mode_register(struct reboot_mode_driver *reboot) return 0; error: - kfree(info); reboot_mode_unregister(reboot); return ret; } @@ -272,7 +356,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); } @@ -282,17 +366,11 @@ 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); return 0; } diff --git a/drivers/power/reset/syscon-reboot-mode.c b/drivers/power/reset/syscon-reboot-mode.c index e0772c9f70f7..7d3a95e8dfd7 100644 --- a/drivers/power/reset/syscon-reboot-mode.c +++ b/drivers/power/reset/syscon-reboot-mode.c @@ -45,8 +45,11 @@ 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; + ret = reboot_mode_driver_init(&syscon_rbm->reboot, &pdev->dev, + syscon_reboot_mode_write, NULL, + NULL, 0); + if (ret) + return ret; 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 29ae39935d69..560d8b4d67ac 100644 --- a/include/linux/reboot-mode.h +++ b/include/linux/reboot-mode.h @@ -4,14 +4,38 @@ #include <linux/types.h> +struct reboot_mode_entry { + const char *name; + u32 magic[3]; + u32 count; +}; + struct reboot_mode_driver { struct device *dev; struct list_head head; + /* + * Optional predefined reboot-mode table. + * + * Must be initialized before reboot_mode_register() and remain valid + * until reboot_mode_unregister(). + * + * If a predefined mode name duplicates a DT-defined mode name, + * the DT-defined mode takes precedence. + */ + const struct reboot_mode_entry *predefined_modes; + size_t predefined_mode_count; + int (*write_array)(struct reboot_mode_driver *reboot, const u32 *magic, u32 count); int (*write)(struct reboot_mode_driver *reboot, unsigned int magic); - int (*write_array)(struct reboot_mode_driver *reboot, const u32 *magic, int count); struct notifier_block reboot_notifier; }; +int reboot_mode_driver_init(struct reboot_mode_driver *reboot, + struct device *dev, + int (*write)(struct reboot_mode_driver *reboot, unsigned int magic), + int (*write_array)(struct reboot_mode_driver *reboot, + const u32 *magic, u32 count), + const struct reboot_mode_entry *predefined_modes, + size_t predefined_mode_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, -- 2.34.1