From 7328c38c773f3dd4bc0dc582fd2b2ddeb0066656 Mon Sep 17 00:00:00 2001 From: Shivendra Pratap Date: Sun, 9 Nov 2025 20:07:14 +0530 Subject: [PATCH 01/10] power: reset: reboot-mode: Remove devres based allocations Devres APIs are intended for use in drivers, and they should be avoided in shared subsystem code which is being used by multiple drivers. Avoid using devres based allocations in the reboot-mode subsystem and manually free the resources. Replace devm_kzalloc with kzalloc and handle memory cleanup explicitly. Fixes: 4fcd504edbf7 ("power: reset: add reboot mode driver") Signed-off-by: Shivendra Pratap Link: https://lore.kernel.org/r/20251109-arm-psci-system_reset2-vendor-reboots-v17-1-46e085bca4cc@oss.qualcomm.com --- drivers/power/reset/reboot-mode.c | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/drivers/power/reset/reboot-mode.c b/drivers/power/reset/reboot-mode.c index fba53f638da04..ac4223794083f 100644 --- a/drivers/power/reset/reboot-mode.c +++ b/drivers/power/reset/reboot-mode.c @@ -3,6 +3,8 @@ * Copyright (c) 2016, Fuzhou Rockchip Electronics Co., Ltd */ +#define pr_fmt(fmt) "reboot-mode: " fmt + #include #include #include @@ -71,6 +73,7 @@ static int reboot_mode_notify(struct notifier_block *this, int reboot_mode_register(struct reboot_mode_driver *reboot) { struct mode_info *info; + struct mode_info *next; struct property *prop; struct device_node *np = reboot->dev->of_node; size_t len = strlen(PREFIX); @@ -82,29 +85,27 @@ int reboot_mode_register(struct reboot_mode_driver *reboot) if (strncmp(prop->name, PREFIX, len)) continue; - info = devm_kzalloc(reboot->dev, sizeof(*info), GFP_KERNEL); + info = kzalloc(sizeof(*info), GFP_KERNEL); if (!info) { ret = -ENOMEM; goto error; } if (of_property_read_u32(np, prop->name, &info->magic)) { - dev_err(reboot->dev, "reboot mode %s without magic number\n", - info->mode); - devm_kfree(reboot->dev, info); + pr_err("reboot mode %s without magic number\n", info->mode); + kfree(info); continue; } info->mode = kstrdup_const(prop->name + len, GFP_KERNEL); if (!info->mode) { ret = -ENOMEM; - goto error; + goto err_info; } 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; + pr_err("invalid mode name(%s): too short!\n", prop->name); + goto err_info; } list_add_tail(&info->list, &reboot->head); @@ -115,9 +116,14 @@ int reboot_mode_register(struct reboot_mode_driver *reboot) return 0; +err_info: + kfree(info); error: - list_for_each_entry(info, &reboot->head, list) + list_for_each_entry_safe(info, next, &reboot->head, list) { + list_del(&info->list); kfree_const(info->mode); + kfree(info); + } return ret; } @@ -130,11 +136,15 @@ EXPORT_SYMBOL_GPL(reboot_mode_register); int reboot_mode_unregister(struct reboot_mode_driver *reboot) { struct mode_info *info; + struct mode_info *next; unregister_reboot_notifier(&reboot->reboot_notifier); - list_for_each_entry(info, &reboot->head, list) + list_for_each_entry_safe(info, next, &reboot->head, list) { + list_del(&info->list); kfree_const(info->mode); + kfree(info); + } return 0; } From 0d0a4a5fb9982199eaf0c9b6678349a19827aacc Mon Sep 17 00:00:00 2001 From: Shivendra Pratap Date: Sun, 9 Nov 2025 20:07:15 +0530 Subject: [PATCH 02/10] power: reset: reboot-mode: Add firmware node based registration The reboot-mode driver does not have a strict requirement for device-based registration. It primarily uses the device's of_node to read mode- properties. Remove the dependency on struct device and introduce support for firmware node (fwnode) based registration. This enables drivers that are not associated with a struct device to leverage the reboot-mode framework. Signed-off-by: Shivendra Pratap Link: https://lore.kernel.org/r/20251109-arm-psci-system_reset2-vendor-reboots-v17-2-46e085bca4cc@oss.qualcomm.com --- drivers/power/reset/reboot-mode.c | 19 ++++++++++++++++--- include/linux/reboot-mode.h | 4 +++- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/drivers/power/reset/reboot-mode.c b/drivers/power/reset/reboot-mode.c index ac4223794083f..eff60d6e04df2 100644 --- a/drivers/power/reset/reboot-mode.c +++ b/drivers/power/reset/reboot-mode.c @@ -8,10 +8,12 @@ #include #include #include +#include #include #include #include #include +#include #define PREFIX "mode-" @@ -67,18 +69,26 @@ static int reboot_mode_notify(struct notifier_block *this, /** * reboot_mode_register - register a reboot mode driver * @reboot: reboot mode driver + * @fwnode: Firmware node with reboot-mode configuration * * Returns: 0 on success or a negative error code on failure. */ -int reboot_mode_register(struct reboot_mode_driver *reboot) +int reboot_mode_register(struct reboot_mode_driver *reboot, struct fwnode_handle *fwnode) { struct mode_info *info; struct mode_info *next; + struct device_node *np; struct property *prop; - struct device_node *np = reboot->dev->of_node; size_t len = strlen(PREFIX); int ret; + if (!fwnode) + return -EINVAL; + + np = to_of_node(fwnode); + if (!np) + return -EINVAL; + INIT_LIST_HEAD(&reboot->head); for_each_property_of_node(np, prop) { @@ -168,11 +178,14 @@ int devm_reboot_mode_register(struct device *dev, struct reboot_mode_driver **dr; int rc; + if (!reboot->dev || !reboot->dev->of_node) + return -EINVAL; + dr = devres_alloc(devm_reboot_mode_release, sizeof(*dr), GFP_KERNEL); if (!dr) return -ENOMEM; - rc = reboot_mode_register(reboot); + rc = reboot_mode_register(reboot, of_fwnode_handle(reboot->dev->of_node)); if (rc) { devres_free(dr); return rc; diff --git a/include/linux/reboot-mode.h b/include/linux/reboot-mode.h index 4a2abb38d1d61..22f707ade4ba9 100644 --- a/include/linux/reboot-mode.h +++ b/include/linux/reboot-mode.h @@ -2,6 +2,8 @@ #ifndef __REBOOT_MODE_H__ #define __REBOOT_MODE_H__ +#include + struct reboot_mode_driver { struct device *dev; struct list_head head; @@ -9,7 +11,7 @@ struct reboot_mode_driver { struct notifier_block reboot_notifier; }; -int reboot_mode_register(struct reboot_mode_driver *reboot); +int reboot_mode_register(struct reboot_mode_driver *reboot, struct fwnode_handle *fwnode); int reboot_mode_unregister(struct reboot_mode_driver *reboot); int devm_reboot_mode_register(struct device *dev, struct reboot_mode_driver *reboot); From fd5cf646eefbc7e518a356582708f4651abca446 Mon Sep 17 00:00:00 2001 From: Shivendra Pratap Date: Sun, 9 Nov 2025 20:07:16 +0530 Subject: [PATCH 03/10] power: reset: reboot-mode: Add support for 64 bit magic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Current reboot-mode supports a single 32-bit argument for any supported mode. Some reboot-mode based drivers may require passing two independent 32-bit arguments during a reboot sequence, for uses-cases, where a mode requires an additional argument. Such drivers may not be able to use the reboot-mode driver. For example, ARM PSCI vendor-specific resets, need two arguments for its operation – reset_type and cookie, to complete the reset operation. If a driver wants to implement this firmware-based reset, it cannot use reboot-mode framework. Introduce 64-bit magic values in reboot-mode driver to accommodate dual 32-bit arguments when specified via device tree. In cases, where no second argument is passed from device tree, keep the upper 32-bit of magic un-changed(0) to maintain backward compatibility. Update the current drivers using reboot-mode for a 64-bit magic value. Reviewed-by: Umang Chheda Reviewed-by: Nirmesh Kumar Singh Signed-off-by: Shivendra Pratap Link: https://lore.kernel.org/r/20251109-arm-psci-system_reset2-vendor-reboots-v17-3-46e085bca4cc@oss.qualcomm.com --- drivers/power/reset/nvmem-reboot-mode.c | 11 ++++++++--- drivers/power/reset/qcom-pon.c | 11 ++++++++--- drivers/power/reset/reboot-mode.c | 19 +++++++++++++------ drivers/power/reset/syscon-reboot-mode.c | 11 ++++++++--- include/linux/reboot-mode.h | 3 ++- 5 files changed, 39 insertions(+), 16 deletions(-) diff --git a/drivers/power/reset/nvmem-reboot-mode.c b/drivers/power/reset/nvmem-reboot-mode.c index d260715fccf67..4479c874077c7 100644 --- a/drivers/power/reset/nvmem-reboot-mode.c +++ b/drivers/power/reset/nvmem-reboot-mode.c @@ -17,14 +17,19 @@ struct nvmem_reboot_mode { struct nvmem_cell *cell; }; -static int nvmem_reboot_mode_write(struct reboot_mode_driver *reboot, - unsigned int magic) +static int nvmem_reboot_mode_write(struct reboot_mode_driver *reboot, u64 magic) { struct nvmem_reboot_mode *nvmem_rbm; size_t buf_len; + u32 magic_32; void *buf; int ret; + if (magic > U32_MAX) + return -EINVAL; + + magic_32 = magic; + nvmem_rbm = container_of(reboot, struct nvmem_reboot_mode, reboot); buf = nvmem_cell_read(nvmem_rbm->cell, &buf_len); @@ -35,7 +40,7 @@ static int nvmem_reboot_mode_write(struct reboot_mode_driver *reboot, if (buf_len > sizeof(magic)) return -EINVAL; - ret = nvmem_cell_write(nvmem_rbm->cell, &magic, buf_len); + ret = nvmem_cell_write(nvmem_rbm->cell, &magic_32, sizeof(magic_32)); if (ret < 0) dev_err(reboot->dev, "update reboot mode bits failed\n"); diff --git a/drivers/power/reset/qcom-pon.c b/drivers/power/reset/qcom-pon.c index 7e108982a582e..d0ed9431a0231 100644 --- a/drivers/power/reset/qcom-pon.c +++ b/drivers/power/reset/qcom-pon.c @@ -27,17 +27,22 @@ struct qcom_pon { long reason_shift; }; -static int qcom_pon_reboot_mode_write(struct reboot_mode_driver *reboot, - unsigned int magic) +static int qcom_pon_reboot_mode_write(struct reboot_mode_driver *reboot, u64 magic) { struct qcom_pon *pon = container_of (reboot, struct qcom_pon, reboot_mode); + u32 magic_32; int ret; + if (magic > U32_MAX || (magic << pon->reason_shift) > U32_MAX) + return -EINVAL; + + magic_32 = magic << pon->reason_shift; + ret = regmap_update_bits(pon->regmap, pon->baseaddr + PON_SOFT_RB_SPARE, GENMASK(7, pon->reason_shift), - magic << pon->reason_shift); + magic_32); if (ret < 0) dev_err(pon->dev, "update reboot mode bits failed\n"); diff --git a/drivers/power/reset/reboot-mode.c b/drivers/power/reset/reboot-mode.c index eff60d6e04df2..873ac45cd7659 100644 --- a/drivers/power/reset/reboot-mode.c +++ b/drivers/power/reset/reboot-mode.c @@ -19,12 +19,11 @@ struct mode_info { const char *mode; - u32 magic; + u64 magic; struct list_head list; }; -static unsigned int get_reboot_mode_magic(struct reboot_mode_driver *reboot, - const char *cmd) +static u64 get_reboot_mode_magic(struct reboot_mode_driver *reboot, const char *cmd) { const char *normal = "normal"; struct mode_info *info; @@ -56,7 +55,7 @@ static int reboot_mode_notify(struct notifier_block *this, unsigned long mode, void *cmd) { struct reboot_mode_driver *reboot; - unsigned int magic; + u64 magic; reboot = container_of(this, struct reboot_mode_driver, reboot_notifier); magic = get_reboot_mode_magic(reboot, cmd); @@ -80,6 +79,8 @@ int reboot_mode_register(struct reboot_mode_driver *reboot, struct fwnode_handle struct device_node *np; struct property *prop; size_t len = strlen(PREFIX); + u32 magic_arg1; + u32 magic_arg2; int ret; if (!fwnode) @@ -101,12 +102,18 @@ int reboot_mode_register(struct reboot_mode_driver *reboot, struct fwnode_handle goto error; } - if (of_property_read_u32(np, prop->name, &info->magic)) { - pr_err("reboot mode %s without magic number\n", info->mode); + if (of_property_read_u32(np, prop->name, &magic_arg1)) { + pr_err("reboot mode without magic number\n"); kfree(info); continue; } + if (of_property_read_u32_index(np, prop->name, 1, &magic_arg2)) + magic_arg2 = 0; + + info->magic = magic_arg2; + info->magic = (info->magic << 32) | magic_arg1; + info->mode = kstrdup_const(prop->name + len, GFP_KERNEL); if (!info->mode) { ret = -ENOMEM; diff --git a/drivers/power/reset/syscon-reboot-mode.c b/drivers/power/reset/syscon-reboot-mode.c index e0772c9f70f7a..3cbd000c51223 100644 --- a/drivers/power/reset/syscon-reboot-mode.c +++ b/drivers/power/reset/syscon-reboot-mode.c @@ -20,16 +20,21 @@ struct syscon_reboot_mode { u32 mask; }; -static int syscon_reboot_mode_write(struct reboot_mode_driver *reboot, - unsigned int magic) +static int syscon_reboot_mode_write(struct reboot_mode_driver *reboot, u64 magic) { struct syscon_reboot_mode *syscon_rbm; + u32 magic_32; int ret; + if (magic > U32_MAX) + return -EINVAL; + + magic_32 = magic; + syscon_rbm = container_of(reboot, struct syscon_reboot_mode, reboot); ret = regmap_update_bits(syscon_rbm->map, syscon_rbm->offset, - syscon_rbm->mask, magic); + syscon_rbm->mask, magic_32); if (ret < 0) dev_err(reboot->dev, "update reboot mode bits failed\n"); diff --git a/include/linux/reboot-mode.h b/include/linux/reboot-mode.h index 22f707ade4ba9..e0d3e8a54050a 100644 --- a/include/linux/reboot-mode.h +++ b/include/linux/reboot-mode.h @@ -3,11 +3,12 @@ #define __REBOOT_MODE_H__ #include +#include struct reboot_mode_driver { struct device *dev; struct list_head head; - int (*write)(struct reboot_mode_driver *reboot, unsigned int magic); + int (*write)(struct reboot_mode_driver *reboot, u64 magic); struct notifier_block reboot_notifier; }; From 163a841094b592946d4ac107ee9ca2d4143ef95e Mon Sep 17 00:00:00 2001 From: Shivendra Pratap Date: Sun, 9 Nov 2025 20:07:17 +0530 Subject: [PATCH 04/10] Documentation: ABI: Add sysfs-class-reboot-mode-reboot_modes Add ABI documentation for /sys/class/reboot-mode/*/reboot_modes, a read-only sysfs attribute exposing the list of supported reboot-mode arguments. This file is created by reboot-mode framework and provides a user-readable interface to query available reboot-mode arguments. Reviewed-by: Sebastian Reichel Signed-off-by: Shivendra Pratap Link: https://lore.kernel.org/r/20251109-arm-psci-system_reset2-vendor-reboots-v17-4-46e085bca4cc@oss.qualcomm.com --- .../sysfs-class-reboot-mode-reboot_modes | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-class-reboot-mode-reboot_modes diff --git a/Documentation/ABI/testing/sysfs-class-reboot-mode-reboot_modes b/Documentation/ABI/testing/sysfs-class-reboot-mode-reboot_modes new file mode 100644 index 0000000000000..6a3fc379afae3 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-class-reboot-mode-reboot_modes @@ -0,0 +1,39 @@ +What: /sys/class/reboot-mode//reboot_modes +Date: August 2025 +KernelVersion: 6.17.0-rc1 +Contact: linux-pm@vger.kernel.org + Description: + This interface exposes the reboot-mode arguments + registered with the reboot-mode framework. It is + a read-only interface and provides a space + separated list of reboot-mode arguments supported + on the current platform. + Example: + recovery fastboot bootloader + + The exact sysfs path may vary depending on the + name of the driver that registers the arguments. + Example: + /sys/class/reboot-mode/nvmem-reboot-mode/reboot_modes + /sys/class/reboot-mode/syscon-reboot-mode/reboot_modes + /sys/class/reboot-mode/qcom-pon/reboot_modes + + The supported arguments can be used by userspace + to invoke device reset using the reboot() system + call, with the "argument" as string to "*arg" + parameter along with LINUX_REBOOT_CMD_RESTART2. + Example: + reboot(LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, + LINUX_REBOOT_CMD_RESTART2, "bootloader"); + + A driver can expose the supported arguments by + registering them with the reboot-mode framework + using the property names that follow the + mode- format. + Example: + mode-bootloader, mode-recovery. + + This attribute is useful for scripts or initramfs + logic that need to programmatically determine + which reboot-mode arguments are valid before + triggering a reboot. From cebbc21c32958633878c005bd1d892bd47c6b178 Mon Sep 17 00:00:00 2001 From: Shivendra Pratap Date: Sun, 9 Nov 2025 20:07:18 +0530 Subject: [PATCH 05/10] power: reset: reboot-mode: Expose sysfs for registered reboot_modes Currently, there is no standardized mechanism for userspace to discover which reboot-modes are supported on a given platform. This limitation forces tools and scripts to rely on hardcoded assumptions about the supported reboot-modes. Create a class 'reboot-mode' and a device under it to expose a sysfs interface to show the available reboot mode arguments to userspace. Use the driver_name field of the struct reboot_mode_driver to create the device. For device-based drivers, configure the device driver name as driver_name. This results in the creation of: /sys/class/reboot-mode//reboot_modes This read-only sysfs file will exposes the list of supported reboot modes arguments provided by the driver, enabling userspace to query the list of arguments. Signed-off-by: Shivendra Pratap Link: https://lore.kernel.org/r/20251109-arm-psci-system_reset2-vendor-reboots-v17-5-46e085bca4cc@oss.qualcomm.com --- drivers/power/reset/reboot-mode.c | 62 ++++++++++++++++++++++++++++++- include/linux/reboot-mode.h | 2 + 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/drivers/power/reset/reboot-mode.c b/drivers/power/reset/reboot-mode.c index 873ac45cd7659..582aa7f8ed7fa 100644 --- a/drivers/power/reset/reboot-mode.c +++ b/drivers/power/reset/reboot-mode.c @@ -6,6 +6,7 @@ #define pr_fmt(fmt) "reboot-mode: " fmt #include +#include #include #include #include @@ -23,6 +24,8 @@ struct mode_info { struct list_head list; }; +static struct class *rb_class; + static u64 get_reboot_mode_magic(struct reboot_mode_driver *reboot, const char *cmd) { const char *normal = "normal"; @@ -65,6 +68,51 @@ static int reboot_mode_notify(struct notifier_block *this, return NOTIFY_DONE; } +static ssize_t reboot_modes_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct reboot_mode_driver *reboot; + struct mode_info *info; + ssize_t size = 0; + + reboot = (struct reboot_mode_driver *)dev_get_drvdata(dev); + if (!reboot) + return -ENODATA; + + list_for_each_entry(info, &reboot->head, list) + size += sysfs_emit_at(buf, size, "%s ", info->mode); + + if (size) { + size += sysfs_emit_at(buf, size - 1, "\n"); + return size; + } + + return -ENODATA; +} +static DEVICE_ATTR_RO(reboot_modes); + +static int create_reboot_mode_device(struct reboot_mode_driver *reboot) +{ + int ret = 0; + + if (!rb_class) { + rb_class = class_create("reboot-mode"); + if (IS_ERR(rb_class)) + return PTR_ERR(rb_class); + } + + reboot->reboot_dev = device_create(rb_class, NULL, 0, (void *)reboot, reboot->driver_name); + if (IS_ERR(reboot->reboot_dev)) + return PTR_ERR(reboot->reboot_dev); + + ret = device_create_file(reboot->reboot_dev, &dev_attr_reboot_modes); + if (ret) { + device_unregister(reboot->reboot_dev); + return ret; + } + + return ret; +} + /** * reboot_mode_register - register a reboot mode driver * @reboot: reboot mode driver @@ -83,13 +131,17 @@ int reboot_mode_register(struct reboot_mode_driver *reboot, struct fwnode_handle u32 magic_arg2; int ret; - if (!fwnode) + if (!fwnode || !reboot->driver_name) return -EINVAL; np = to_of_node(fwnode); if (!np) return -EINVAL; + ret = create_reboot_mode_device(reboot); + if (ret) + return ret; + INIT_LIST_HEAD(&reboot->head); for_each_property_of_node(np, prop) { @@ -142,6 +194,8 @@ int reboot_mode_register(struct reboot_mode_driver *reboot, struct fwnode_handle kfree(info); } + device_remove_file(reboot->reboot_dev, &dev_attr_reboot_modes); + device_unregister(reboot->reboot_dev); return ret; } EXPORT_SYMBOL_GPL(reboot_mode_register); @@ -155,6 +209,9 @@ int reboot_mode_unregister(struct reboot_mode_driver *reboot) struct mode_info *info; struct mode_info *next; + if (!reboot->reboot_dev) + return -EINVAL; + unregister_reboot_notifier(&reboot->reboot_notifier); list_for_each_entry_safe(info, next, &reboot->head, list) { @@ -163,6 +220,8 @@ int reboot_mode_unregister(struct reboot_mode_driver *reboot) kfree(info); } + device_remove_file(reboot->reboot_dev, &dev_attr_reboot_modes); + device_unregister(reboot->reboot_dev); return 0; } EXPORT_SYMBOL_GPL(reboot_mode_unregister); @@ -192,6 +251,7 @@ int devm_reboot_mode_register(struct device *dev, if (!dr) return -ENOMEM; + reboot->driver_name = reboot->dev->driver->name; rc = reboot_mode_register(reboot, of_fwnode_handle(reboot->dev->of_node)); if (rc) { devres_free(dr); diff --git a/include/linux/reboot-mode.h b/include/linux/reboot-mode.h index e0d3e8a54050a..81c149edf40fb 100644 --- a/include/linux/reboot-mode.h +++ b/include/linux/reboot-mode.h @@ -7,6 +7,8 @@ struct reboot_mode_driver { struct device *dev; + struct device *reboot_dev; + const char *driver_name; struct list_head head; int (*write)(struct reboot_mode_driver *reboot, u64 magic); struct notifier_block reboot_notifier; From fe6575ed08cb354030bc2e85d873ec05913aa4e1 Mon Sep 17 00:00:00 2001 From: Shivendra Pratap Date: Sun, 9 Nov 2025 20:07:20 +0530 Subject: [PATCH 06/10] firmware: psci: Implement vendor-specific resets as reboot-mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SoC vendors have different types of resets which are controlled through various hardware registers. For instance, Qualcomm SoC may have a requirement that reboot with “bootloader” command should reboot the device to bootloader flashing mode and reboot with “edl” should reboot the device into Emergency flashing mode. Setting up such reboots on Qualcomm devices can be inconsistent across SoC platforms and may require setting different HW registers, where some of these registers may not be accessible to HLOS. These knobs evolve over product generations and require more drivers. PSCI spec defines, SYSTEM_RESET2, vendor-specific reset which can help align this requirement. Add support for PSCI SYSTEM_RESET2, vendor-specific resets and align the implementation to allow user-space initiated reboots to trigger these resets. Implement the PSCI vendor-specific resets by registering to the reboot-mode framework. As psci init is done at early kernel init, reboot-mode registration cannot be done at the time of psci init. This is because reboot-mode creates a “reboot-mode” class for exposing sysfs, which can fail at early kernel init. To overcome this, introduce a late_initcall to register PSCI vendor-specific resets as reboot modes. Implement a reboot-mode write function that sets reset_type and cookie values during the reboot notifier callback. Introduce a firmware-based call for SYSTEM_RESET2 vendor-specific reset in the psci_sys_reset path, using reset_type and cookie if supported by secure firmware. Register a panic notifier and clear vendor_reset valid status during panic. This is needed for any kernel panic that occurs post reboot_notifiers. By using the above implementation, userspace will be able to issue such resets using the reboot() system call with the "*arg" parameter as a string based command. The commands can be defined in PSCI device tree node under “reboot-mode” and are based on the reboot-mode based commands. Reviewed-by: Umang Chheda Reviewed-by: Kathiravan Thirumoorthy Reviewed-by: Nirmesh Kumar Singh Signed-off-by: Shivendra Pratap Link: https://lore.kernel.org/r/20251109-arm-psci-system_reset2-vendor-reboots-v17-7-46e085bca4cc@oss.qualcomm.com --- drivers/firmware/psci/Kconfig | 2 + drivers/firmware/psci/psci.c | 92 ++++++++++++++++++++++++++++++++++- 2 files changed, 93 insertions(+), 1 deletion(-) diff --git a/drivers/firmware/psci/Kconfig b/drivers/firmware/psci/Kconfig index 97944168b5e66..93ff7b071a0c3 100644 --- a/drivers/firmware/psci/Kconfig +++ b/drivers/firmware/psci/Kconfig @@ -1,6 +1,8 @@ # SPDX-License-Identifier: GPL-2.0-only config ARM_PSCI_FW bool + select POWER_RESET + select REBOOT_MODE config ARM_PSCI_CHECKER bool "ARM PSCI checker" diff --git a/drivers/firmware/psci/psci.c b/drivers/firmware/psci/psci.c index 38ca190d4a22d..ff82e7f4c27d1 100644 --- a/drivers/firmware/psci/psci.c +++ b/drivers/firmware/psci/psci.c @@ -8,15 +8,18 @@ #include #include +#include #include #include #include #include #include +#include #include #include #include #include +#include #include #include @@ -51,6 +54,24 @@ static int resident_cpu = -1; struct psci_operations psci_ops; static enum arm_smccc_conduit psci_conduit = SMCCC_CONDUIT_NONE; +struct psci_vendor_sysreset2 { + u32 reset_type; + u32 cookie; + bool valid; +}; + +static struct psci_vendor_sysreset2 vendor_reset; + +static int psci_panic_event(struct notifier_block *nb, unsigned long v, void *p) +{ + vendor_reset.valid = false; + return NOTIFY_DONE; +} + +static struct notifier_block psci_panic_block = { + .notifier_call = psci_panic_event +}; + bool psci_tos_resident_on(int cpu) { return cpu == resident_cpu; @@ -309,7 +330,10 @@ static int get_set_conduit_method(const struct device_node *np) static int psci_sys_reset(struct notifier_block *nb, unsigned long action, void *data) { - if ((reboot_mode == REBOOT_WARM || reboot_mode == REBOOT_SOFT) && + if (vendor_reset.valid && psci_system_reset2_supported) { + invoke_psci_fn(PSCI_FN_NATIVE(1_1, SYSTEM_RESET2), vendor_reset.reset_type, + vendor_reset.cookie, 0); + } else if ((reboot_mode == REBOOT_WARM || reboot_mode == REBOOT_SOFT) && psci_system_reset2_supported) { /* * reset_type[31] = 0 (architectural) @@ -547,6 +571,72 @@ static const struct platform_suspend_ops psci_suspend_ops = { .enter = psci_system_suspend_enter, }; +static int psci_set_vendor_sys_reset2(struct reboot_mode_driver *reboot, u64 magic) +{ + u32 magic_32; + + if (psci_system_reset2_supported) { + magic_32 = magic & GENMASK(31, 0); + vendor_reset.reset_type = PSCI_1_1_RESET_TYPE_VENDOR_START | magic_32; + vendor_reset.cookie = (magic >> 32) & GENMASK(31, 0); + vendor_reset.valid = true; + } + + return NOTIFY_DONE; +} + +static int __init psci_init_vendor_reset(void) +{ + struct reboot_mode_driver *reboot; + struct device_node *psci_np; + struct device_node *np; + int ret; + + if (!psci_system_reset2_supported) + return -EINVAL; + + psci_np = of_find_compatible_node(NULL, NULL, "arm,psci-1.0"); + if (!psci_np) + return -ENODEV; + + np = of_find_node_by_name(psci_np, "reboot-mode"); + if (!np) { + of_node_put(psci_np); + return -ENODEV; + } + + ret = atomic_notifier_chain_register(&panic_notifier_list, &psci_panic_block); + if (ret) + goto err_notifier; + + reboot = kzalloc(sizeof(*reboot), GFP_KERNEL); + if (!reboot) { + ret = -ENOMEM; + goto err_kzalloc; + } + + reboot->write = psci_set_vendor_sys_reset2; + reboot->driver_name = "psci"; + + ret = reboot_mode_register(reboot, of_fwnode_handle(np)); + if (ret) + goto err_register; + + of_node_put(psci_np); + of_node_put(np); + return 0; + +err_register: + kfree(reboot); +err_kzalloc: + atomic_notifier_chain_unregister(&panic_notifier_list, &psci_panic_block); +err_notifier: + of_node_put(psci_np); + of_node_put(np); + return ret; +} +late_initcall(psci_init_vendor_reset) + static void __init psci_init_system_reset2(void) { int ret; From acfe9f04482fd1d8e3a5b9b5084b61dce019b2d2 Mon Sep 17 00:00:00 2001 From: Maulik Shah Date: Thu, 12 Mar 2026 21:26:36 +0530 Subject: [PATCH 07/10] FROMLIST: dt-bindings: interrupt-controller: qcom,pdc: Document reg and QMP Document PDC reg to configure pass through or secondary controller mode for GPIO IRQs. Document QMP handle for action concerning global resources. Link: https://lore.kernel.org/r/20260312-hamoa_pdc-v1-2-760c8593ce50@oss.qualcomm.com Signed-off-by: Maulik Shah Signed-off-by: Sneh Mankad --- .../devicetree/bindings/interrupt-controller/qcom,pdc.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Documentation/devicetree/bindings/interrupt-controller/qcom,pdc.yaml b/Documentation/devicetree/bindings/interrupt-controller/qcom,pdc.yaml index f9321366cae45..9e07a9b5f456d 100644 --- a/Documentation/devicetree/bindings/interrupt-controller/qcom,pdc.yaml +++ b/Documentation/devicetree/bindings/interrupt-controller/qcom,pdc.yaml @@ -61,6 +61,7 @@ properties: items: - description: PDC base register region - description: Edge or Level config register for SPI interrupts + - description: PDC config for pass through or secondary IRQ mode for GPIOs '#interrupt-cells': const: 2 @@ -81,6 +82,10 @@ properties: The tuples indicates the valid mapping of valid PDC ports and their hwirq mapping. + qcom,qmp: + $ref: /schemas/types.yaml#/definitions/phandle + description: Reference to the AOSS side-channel message RAM. + required: - compatible - reg From ff9d2eecd015d9ec94048fe8dace491db1965739 Mon Sep 17 00:00:00 2001 From: Maulik Shah Date: Thu, 12 Mar 2026 21:26:37 +0530 Subject: [PATCH 08/10] FROMLIST: irqchip/qcom-pdc: Configure PDC to pass through mode There are two modes PDC irqchip supports pass through mode and secondary controller mode. All PDC irqchip supports pass through mode in which both Direct SPIs and GPIO IRQs (as SPIs) are sent to GIC without latching at PDC. Newer PDCs (v3.0 onwards) also support additional secondary controller mode where PDC latches GPIO IRQs and sends to GIC as level type IRQ. Direct SPIs still works same as pass through mode without latching at PDC even in secondary controller mode. All the SoCs so far default uses pass through mode with the exception of x1e. x1e PDC may be set to secondary controller mode for builds on CRD boards whereas it may be set to pass through mode for IoT-EVK. There is no way to read which current mode it is set to and make PDC work in respective mode as the read access is not opened up for non secure world. There is though write access opened up via SCM write API to set the mode. Configure PDC mode to pass through mode for all x1e based boards via SCM write. Link: https://lore.kernel.org/r/20260312-hamoa_pdc-v1-3-760c8593ce50@oss.qualcomm.com Co-developed-by: Sneh Mankad Signed-off-by: Sneh Mankad Signed-off-by: Maulik Shah --- drivers/irqchip/Kconfig | 1 + drivers/irqchip/qcom-pdc.c | 119 ++++++++++++++++++++++++++++++++++--- 2 files changed, 111 insertions(+), 9 deletions(-) diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig index f07b00d7fef90..1d4ed503b2625 100644 --- a/drivers/irqchip/Kconfig +++ b/drivers/irqchip/Kconfig @@ -508,6 +508,7 @@ config GOLDFISH_PIC config QCOM_PDC tristate "QCOM PDC" depends on ARCH_QCOM + depends on QCOM_AOSS_QMP select IRQ_DOMAIN_HIERARCHY help Power Domain Controller driver to manage and configure wakeup diff --git a/drivers/irqchip/qcom-pdc.c b/drivers/irqchip/qcom-pdc.c index 32b77fa93f730..051700d672471 100644 --- a/drivers/irqchip/qcom-pdc.c +++ b/drivers/irqchip/qcom-pdc.c @@ -19,6 +19,8 @@ #include #include #include +#include +#include #define PDC_MAX_GPIO_IRQS 256 #define PDC_DRV_OFFSET 0x10000 @@ -26,9 +28,11 @@ /* Valid only on HW version < 3.2 */ #define IRQ_ENABLE_BANK 0x10 #define IRQ_ENABLE_BANK_MAX (IRQ_ENABLE_BANK + BITS_TO_BYTES(PDC_MAX_GPIO_IRQS)) +#define IRQ_i_CFG_IRQ_MASK_3_0 3 #define IRQ_i_CFG 0x110 /* Valid only on HW version >= 3.2 */ +#define IRQ_i_CFG_IRQ_MASK_3_2 4 #define IRQ_i_CFG_IRQ_ENABLE 3 #define IRQ_i_CFG_TYPE_MASK GENMASK(2, 0) @@ -36,8 +40,11 @@ #define PDC_VERSION_REG 0x1000 /* Notable PDC versions */ +#define PDC_VERSION_3_0 0x30000 #define PDC_VERSION_3_2 0x30200 +#define PDC_PASS_THROUGH_MODE 0 + struct pdc_pin_region { u32 pin_base; u32 parent_base; @@ -97,6 +104,33 @@ static void pdc_x1e_irq_enable_write(u32 bank, u32 enable) pdc_base_reg_write(base, IRQ_ENABLE_BANK, bank, enable); } +/* + * The new mask bit controls whether the interrupt is to be forwarded to the + * parent GIC in secondary controller mode. Writing the mask is do not care + * when the PDC is set to pass through mode. + * + * As linux only makes so far make use of pass through mode set all IRQs + * masked during probe. + */ +static void __pdc_mask_intr(int pin_out, bool mask) +{ + unsigned long irq_cfg; + int mask_bit; + + /* Mask bit available from v3.0 */ + if (pdc_version < PDC_VERSION_3_0) + return; + + if (pdc_version < PDC_VERSION_3_2) + mask_bit = IRQ_i_CFG_IRQ_MASK_3_0; + else + mask_bit = IRQ_i_CFG_IRQ_MASK_3_2; + + irq_cfg = pdc_reg_read(IRQ_i_CFG, pin_out); + __assign_bit(mask_bit, &irq_cfg, mask); + pdc_reg_write(IRQ_i_CFG, pin_out, irq_cfg); +} + static void __pdc_enable_intr(int pin_out, bool on) { unsigned long enable; @@ -312,7 +346,6 @@ static const struct irq_domain_ops qcom_pdc_ops = { static int pdc_setup_pin_mapping(struct device_node *np) { int ret, n, i; - n = of_property_count_elems_of_size(np, "qcom,pdc-ranges", sizeof(u32)); if (n <= 0 || n % 3) return -EINVAL; @@ -341,8 +374,10 @@ static int pdc_setup_pin_mapping(struct device_node *np) if (ret) return ret; - for (i = 0; i < pdc_region[n].cnt; i++) + for (i = 0; i < pdc_region[n].cnt; i++) { __pdc_enable_intr(i + pdc_region[n].pin_base, 0); + __pdc_mask_intr(i + pdc_region[n].pin_base, true); + } } return 0; @@ -352,10 +387,13 @@ static int pdc_setup_pin_mapping(struct device_node *np) static int qcom_pdc_probe(struct platform_device *pdev, struct device_node *parent) { + static const char buf[64] = "{class: cx_mol, res: cx, val: mol}"; + unsigned int domain_flag = IRQ_DOMAIN_FLAG_QCOM_PDC_WAKEUP; struct irq_domain *parent_domain, *pdc_domain; struct device_node *node = pdev->dev.of_node; resource_size_t res_size; struct resource res; + struct qmp *pdc_qmp; int ret; /* compat with old sm8150 DT which had very small region for PDC */ @@ -366,6 +404,13 @@ static int qcom_pdc_probe(struct platform_device *pdev, struct device_node *pare if (res_size > resource_size(&res)) pr_warn("%pOF: invalid reg size, please fix DT\n", node); + pdc_base = ioremap(res.start, res_size); + if (!pdc_base) { + pr_err("%pOF: unable to map PDC registers\n", node); + ret = -ENXIO; + goto fail; + } + /* * PDC has multiple DRV regions, each one provides the same set of * registers for a particular client in the system. Due to a hardware @@ -382,15 +427,71 @@ static int qcom_pdc_probe(struct platform_device *pdev, struct device_node *pare } pdc_x1e_quirk = true; - } - pdc_base = ioremap(res.start, res_size); - if (!pdc_base) { - pr_err("%pOF: unable to map PDC registers\n", node); - ret = -ENXIO; - goto fail; + /* + * There are two modes PDC irqchip can work in + * - pass through mode + * - secondary controller mode + * + * All PDC irqchip supports pass through mode in which both + * Direct SPIs and GPIO IRQs (as SPIs) are sent to GIC + * without latching at PDC. + * + * Newer PDCs (v3.0 onwards) also support additional + * secondary controller mode where PDC latches GPIO IRQs + * and sends to GIC as level type IRQ. Direct SPIs still + * works same as pass through mode without latching at PDC + * even in secondary controller mode. + * + * All the SoCs so far default uses pass through mode with + * the exception of x1e. + * + * x1e modes: + * + * x1e PDC may be set to secondary controller mode for + * builds on CRD boards whereas it may be set to pass + * through mode for IoT-EVK boards. + * + * There is no way to read which current mode it is set to + * and make PDC work in respective mode as the read access + * is not opened up for non secure world. There is though + * write access opened up via SCM write API to set the mode. + * + * Configure PDC mode to pass through mode for all x1e based + * boards. + * + * For successful write: + * - Nothing more to be done + * + * For unsuccessful write: + * - Inform TLMM to monitor GPIO IRQs (same as MPM) + * - Prevent SoC low power mode (CxPC) as PDC is not + * monitoring GPIO IRQs which may be needed to wake + * the SoC from low power mode. + */ + ret = of_address_to_resource(node, 2, &res); + if (ret) { + domain_flag = IRQ_DOMAIN_FLAG_QCOM_MPM_WAKEUP; + goto skip_scm_write; + } + + ret = qcom_scm_io_writel(res.start, PDC_PASS_THROUGH_MODE); + if (ret) { + pdc_qmp = qmp_get(&pdev->dev); + if (IS_ERR(pdc_qmp)) { + ret = PTR_ERR(pdc_qmp); + goto fail; + } else { + ret = qmp_send(pdc_qmp, buf, sizeof(buf)); + qmp_put(pdc_qmp); + if (ret) + goto fail; + } + domain_flag = IRQ_DOMAIN_FLAG_QCOM_MPM_WAKEUP; + } } +skip_scm_write: pdc_version = pdc_reg_read(PDC_VERSION_REG, 0); parent_domain = irq_find_host(parent); @@ -407,7 +508,7 @@ static int qcom_pdc_probe(struct platform_device *pdev, struct device_node *pare } pdc_domain = irq_domain_create_hierarchy(parent_domain, - IRQ_DOMAIN_FLAG_QCOM_PDC_WAKEUP, + domain_flag, PDC_MAX_GPIO_IRQS, of_fwnode_handle(node), &qcom_pdc_ops, NULL); From 25fc1b70fb8b9e86cabdb3f633590a5469e72756 Mon Sep 17 00:00:00 2001 From: Maulik Shah Date: Wed, 31 Dec 2025 15:48:45 +0530 Subject: [PATCH 09/10] FROMLIST: dt-bindings: interrupt-controller: qcom,pdc: Document x1p42100 PDC Purwa shares the Hamoa (X1E80100) PDC device, but the hardware register bug addressed in commit e9a48ea4d90b ("irqchip/qcom-pdc: Workaround hardware register bug on X1E80100") is already fixed in Purwa silicon. Hamoa compatible forces the software workaround. Add PDC compatible for purwa as "qcom,x1p42100-pdc" to remove the workaround from Purwa. Fixes: f08edb529916 ("arm64: dts: qcom: Add X1P42100 SoC and CRD") Link: https://lore.kernel.org/r/20251231-purwa_pdc-v1-1-2b4979dd88ad@oss.qualcomm.com Signed-off-by: Maulik Shah Signed-off-by: Sneh Mankad --- .../devicetree/bindings/interrupt-controller/qcom,pdc.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/interrupt-controller/qcom,pdc.yaml b/Documentation/devicetree/bindings/interrupt-controller/qcom,pdc.yaml index 9e07a9b5f456d..5d1cdd322c9a8 100644 --- a/Documentation/devicetree/bindings/interrupt-controller/qcom,pdc.yaml +++ b/Documentation/devicetree/bindings/interrupt-controller/qcom,pdc.yaml @@ -54,6 +54,7 @@ properties: - qcom,sm8650-pdc - qcom,sm8750-pdc - qcom,x1e80100-pdc + - qcom,x1p42100-pdc - const: qcom,pdc reg: From af3deb7e38b09ef3273e41748d14e5a9f0c8b1e5 Mon Sep 17 00:00:00 2001 From: Maulik Shah Date: Fri, 3 Apr 2026 09:38:36 +0530 Subject: [PATCH 10/10] FROMLIST: cpuidle: Deny idle entry when CPU already have IPI interrupt pending CPU can get IPI interrupt from another CPU while it is executing cpuidle_select() or about to execute same. The selection do not account for pending interrupts and may continue to enter selected idle state only to exit immediately. Example trace collected when there is cross CPU IPI. [000] 154.892148: sched_waking: comm=sugov:4 pid=491 prio=-1 target_cpu=007 [000] 154.892148: ipi_raise: target_mask=00000000,00000080 (Function call interrupts) [007] 154.892162: cpu_idle: state=2 cpu_id=7 [007] 154.892208: cpu_idle: state=4294967295 cpu_id=7 [007] 154.892211: irq_handler_entry: irq=2 name=IPI [007] 154.892211: ipi_entry: (Function call interrupts) [007] 154.892213: sched_wakeup: comm=sugov:4 pid=491 prio=-1 target_cpu=007 [007] 154.892214: ipi_exit: (Function call interrupts) This impacts performance and the above count increments. commit ccde6525183c ("smp: Introduce a helper function to check for pending IPIs") already introduced a helper function to check the pending IPIs and it is used in pmdomain governor to deny the cluster level idle state when there is a pending IPI on any of cluster CPUs. This however does not stop CPU to enter CPU level idle state. Make use of same at CPUidle to deny the idle entry when there is already IPI pending. With change observing glmark2 [1] off screen scores improving in the range of 25% to 30% on Qualcomm lemans-evk board which is arm64 based having two clusters each with 4 CPUs. [1] https://github.com/glmark2/glmark2 Link: https://lore.kernel.org/r/20260403-cpuidle_ipi-v2-1-b3e44b032e2c@oss.qualcomm.com Signed-off-by: Maulik Shah Signed-off-by: Sneh Mankad --- drivers/cpuidle/cpuidle.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/cpuidle/cpuidle.c b/drivers/cpuidle/cpuidle.c index 65fbb8e807b97..976c8c226aaea 100644 --- a/drivers/cpuidle/cpuidle.c +++ b/drivers/cpuidle/cpuidle.c @@ -224,6 +224,9 @@ noinstr int cpuidle_enter_state(struct cpuidle_device *dev, bool broadcast = !!(target_state->flags & CPUIDLE_FLAG_TIMER_STOP); ktime_t time_start, time_end; + if (cpus_peek_for_pending_ipi(cpumask_of(dev->cpu))) + return -EBUSY; + instrumentation_begin(); /*