drivers/firmware/stratix10-svc.c | 88 ++++++++++++++++++++++++++++++-- 1 file changed, 85 insertions(+), 3 deletions(-)
On Agilex7 and Stratix10 SoCs, secondary CPUs must be offlined before
a warm reboot to ensure a clean PSCI state. Agilex5 warm reset is a
SDM-owned HPS reset that holds secondaries in the new reset-release until
ATF releases them, so the secondary cores dont have to be put offline
first. Register a reboot notifier that calls remove_cpu() on each
secondary CPU when SYS_RESTART is triggered in REBOOT_WARM mode.
A snapshot of cpu_online_mask is taken before the loop since remove_cpu()
modifies the mask as CPUs go down. CPU 0 is excluded as it is typically
not hotpluggable. Failures are logged and the loop continues so the
reboot is not blocked by a single CPU that refuses to offline.
The of_device_id table carries a per-compatible pdata flag to enable the
notifier only on platforms that need it; Agilex5 leaves the flag unset.
The notifier is unregistered at the start of remove(), before async
channel teardown, to prevent a reboot notification from racing with
driver shutdown.
Signed-off-by: Adrian Ng Ho Yin <adrian.ho.yin.ng@altera.com>
---
changelog:
v2 -> v3:
- Use ctrl in psci_offline_secondary_cpus() via dev_err(ctrl->dev, ...)
instead of pr_err(). The controller pointer was unused; this both
consumes it and attributes the failure to the svc device.
- Keep of_device_id.data / stratix10_svc_pdata rather than dropping it
because both intel,stratix10-svc and intel,agilex-svc happen to share
the same notifier path. A separate patch adds intel,agilex5-svc without
.data so Agilex5 does not run this sequence. Agilex5 warm reset is an
SDM-owned HPS reset (REBOOT_HPS) that holds secondaries in the
reset-release / P-channel block until ATF CPURSTRELEASE; Linux must
not PSCI-offline them first.
v1 -> v2:
- Drop workqueue fan-out: remove_cpu() serialises on device_hotplug_lock
internally so concurrent workers provided no parallelism. Replace with
a single sequential loop over a cpumask snapshot.
- Fix CPU identity bug: get_cpu()/put_cpu() did not prevent task migration
and the notifier can run on any CPU before migrate_to_reboot_cpu().
Exclude CPU 0 (typically not hotpluggable) and offline everything else.
- Fix pre-count race: the two-loop atomic pre-count had a window where a
concurrent hotplug event could cause dec_and_test() to fire prematurely.
Eliminated entirely along with the completion and timeout.
- Replace of_device_is_compatible() checks with a stratix10_svc_pdata
struct carried in of_device_id.data, retrieved via of_device_get_match_data().
- Move psci_cpu_off_teardown() to the top of stratix10_svc_drv_remove(),
before async channel teardown, to close the race where a warm-reboot
notification could fire while the driver is being dismantled.
---
drivers/firmware/stratix10-svc.c | 88 ++++++++++++++++++++++++++++++--
1 file changed, 85 insertions(+), 3 deletions(-)
diff --git a/drivers/firmware/stratix10-svc.c b/drivers/firmware/stratix10-svc.c
index f8c2da207cb4..722d478b57ac 100644
--- a/drivers/firmware/stratix10-svc.c
+++ b/drivers/firmware/stratix10-svc.c
@@ -24,6 +24,10 @@
#include <linux/firmware/intel/stratix10-smc.h>
#include <linux/firmware/intel/stratix10-svc-client.h>
#include <linux/types.h>
+#include <linux/cpu.h>
+#include <linux/cpumask.h>
+#include <linux/of_device.h>
+#include <linux/reboot.h>
/**
* SVC_NUM_DATA_IN_FIFO - number of struct stratix10_svc_data in the FIFO
@@ -97,6 +101,14 @@
#define STRATIX10_GET_SDM_STATUS_CODE(status) \
(FIELD_GET(STRATIX10_SDM_STATUS_MASK, status))
+struct stratix10_svc_pdata {
+ bool needs_psci_cpu_off;
+};
+
+static const struct stratix10_svc_pdata psci_cpu_off_pdata = {
+ .needs_psci_cpu_off = true,
+};
+
typedef void (svc_invoke_fn)(unsigned long, unsigned long, unsigned long,
unsigned long, unsigned long, unsigned long,
unsigned long, unsigned long,
@@ -283,6 +295,7 @@ struct stratix10_svc_chan {
* @svc: manages the list of client svc drivers
* @sdm_lock: only allows a single command single response to SDM
* @actrl: async control structure
+ * @psci_reboot_nb: reboot notifier for PSCI secondary CPU offlining
* @chans: array of service channels
*
* This struct is used to create communication channels for service clients, to
@@ -299,6 +312,7 @@ struct stratix10_svc_controller {
struct stratix10_svc *svc;
struct mutex sdm_lock;
struct stratix10_async_ctrl actrl;
+ struct notifier_block psci_reboot_nb;
struct stratix10_svc_chan chans[] __counted_by(num_chans);
};
@@ -1999,9 +2013,60 @@ void stratix10_svc_free_memory(struct stratix10_svc_chan *chan, void *kaddr)
}
EXPORT_SYMBOL_GPL(stratix10_svc_free_memory);
+static void psci_offline_secondary_cpus(struct stratix10_svc_controller *ctrl)
+{
+ cpumask_var_t mask;
+ int cpu, ret;
+
+ if (!alloc_cpumask_var(&mask, GFP_KERNEL))
+ return;
+
+ /*
+ * Snapshot cpu_online_mask before the loop; remove_cpu() modifies it
+ * as each CPU is brought down. Always preserve CPU 0 (boot CPU) to
+ * run the reboot.
+ */
+ cpumask_copy(mask, cpu_online_mask);
+ cpumask_clear_cpu(0, mask);
+
+ /*
+ * Offlining is best-effort: if a CPU refuses to go down we log the
+ * error and continue so the remaining secondaries are still attempted
+ * and the warm reboot can proceed.
+ */
+ for_each_cpu(cpu, mask) {
+ ret = remove_cpu(cpu);
+ if (ret)
+ dev_err(ctrl->dev,
+ "psci_cpu_off: failed to offline CPU%d: %d\n",
+ cpu, ret);
+ }
+
+ free_cpumask_var(mask);
+}
+
+static int psci_cpu_off_reboot_notifier(struct notifier_block *nb,
+ unsigned long action, void *data)
+{
+ struct stratix10_svc_controller *ctrl =
+ container_of(nb, struct stratix10_svc_controller, psci_reboot_nb);
+
+ if (reboot_mode != REBOOT_WARM)
+ return NOTIFY_DONE;
+
+ if (action == SYS_RESTART)
+ psci_offline_secondary_cpus(ctrl);
+
+ return NOTIFY_OK;
+}
+
+static void psci_cpu_off_teardown(struct stratix10_svc_controller *ctrl)
+{
+ unregister_reboot_notifier(&ctrl->psci_reboot_nb);
+}
+
static const struct of_device_id stratix10_svc_drv_match[] = {
- {.compatible = "intel,stratix10-svc"},
- {.compatible = "intel,agilex-svc"},
+ { .compatible = "intel,stratix10-svc", .data = &psci_cpu_off_pdata },
+ { .compatible = "intel,agilex-svc", .data = &psci_cpu_off_pdata },
{},
};
@@ -2019,6 +2084,7 @@ static int stratix10_svc_drv_probe(struct platform_device *pdev)
struct gen_pool *genpool;
struct stratix10_svc_sh_memory *sh_memory;
struct stratix10_svc *svc = NULL;
+ const struct stratix10_svc_pdata *pdata = of_device_get_match_data(dev);
svc_invoke_fn *invoke_fn;
size_t fifo_size;
@@ -2058,13 +2124,25 @@ static int stratix10_svc_drv_probe(struct platform_device *pdev)
INIT_LIST_HEAD(&controller->node);
init_completion(&controller->complete_status);
+ if (pdata && pdata->needs_psci_cpu_off) {
+ controller->psci_reboot_nb.notifier_call =
+ psci_cpu_off_reboot_notifier;
+ controller->psci_reboot_nb.priority = INT_MAX;
+
+ ret = register_reboot_notifier(&controller->psci_reboot_nb);
+ if (ret) {
+ dev_err(dev, "failed to register reboot notifier: %d\n", ret);
+ goto err_destroy_pool;
+ }
+ }
+
ret = stratix10_svc_async_init(controller);
if (ret == -EOPNOTSUPP) {
dev_info(dev, "Intel Service Layer Driver Initialized (sync-only mode)\n");
} else if (ret) {
dev_dbg(dev, "Intel Service Layer Driver: Error on stratix10_svc_async_init %d\n",
ret);
- goto err_destroy_pool;
+ goto err_free_notifier;
} else {
dev_info(dev, "Intel Service Layer Driver Initialized\n");
}
@@ -2150,6 +2228,8 @@ static int stratix10_svc_drv_probe(struct platform_device *pdev)
while (i--)
kfifo_free(&controller->chans[i].svc_fifo);
stratix10_svc_async_exit(controller);
+err_free_notifier:
+ psci_cpu_off_teardown(controller);
err_destroy_pool:
gen_pool_destroy(genpool);
@@ -2166,6 +2246,8 @@ static void stratix10_svc_drv_remove(struct platform_device *pdev)
if (svc->stratix10_svc_hwmon)
platform_device_unregister(svc->stratix10_svc_hwmon);
+ psci_cpu_off_teardown(ctrl);
+
stratix10_svc_async_exit(ctrl);
of_platform_depopulate(ctrl->dev);
--
2.49.GIT
On 9/4/26 00:52, Adrian Ng Ho Yin wrote: > On Agilex7 and Stratix10 SoCs, secondary CPUs must be offlined before > a warm reboot to ensure a clean PSCI state. Agilex5 warm reset is a > SDM-owned HPS reset that holds secondaries in the new reset-release until > ATF releases them, so the secondary cores dont have to be put offline > first. Register a reboot notifier that calls remove_cpu() on each > secondary CPU when SYS_RESTART is triggered in REBOOT_WARM mode. > > A snapshot of cpu_online_mask is taken before the loop since remove_cpu() > modifies the mask as CPUs go down. CPU 0 is excluded as it is typically > not hotpluggable. Failures are logged and the loop continues so the > reboot is not blocked by a single CPU that refuses to offline. > The of_device_id table carries a per-compatible pdata flag to enable the > notifier only on platforms that need it; Agilex5 leaves the flag unset. > > The notifier is unregistered at the start of remove(), before async > channel teardown, to prevent a reboot notification from racing with > driver shutdown. > > Signed-off-by: Adrian Ng Ho Yin <adrian.ho.yin.ng@altera.com> > --- > changelog: > v2 -> v3: > - Use ctrl in psci_offline_secondary_cpus() via dev_err(ctrl->dev, ...) > instead of pr_err(). The controller pointer was unused; this both > consumes it and attributes the failure to the svc device. > - Keep of_device_id.data / stratix10_svc_pdata rather than dropping it > because both intel,stratix10-svc and intel,agilex-svc happen to share > the same notifier path. A separate patch adds intel,agilex5-svc without > .data so Agilex5 does not run this sequence. Agilex5 warm reset is an > SDM-owned HPS reset (REBOOT_HPS) that holds secondaries in the > reset-release / P-channel block until ATF CPURSTRELEASE; Linux must > not PSCI-offline them first. > > v1 -> v2: > - Drop workqueue fan-out: remove_cpu() serialises on device_hotplug_lock > internally so concurrent workers provided no parallelism. Replace with > a single sequential loop over a cpumask snapshot. > - Fix CPU identity bug: get_cpu()/put_cpu() did not prevent task migration > and the notifier can run on any CPU before migrate_to_reboot_cpu(). > Exclude CPU 0 (typically not hotpluggable) and offline everything else. > - Fix pre-count race: the two-loop atomic pre-count had a window where a > concurrent hotplug event could cause dec_and_test() to fire prematurely. > Eliminated entirely along with the completion and timeout. > - Replace of_device_is_compatible() checks with a stratix10_svc_pdata > struct carried in of_device_id.data, retrieved via of_device_get_match_data(). > - Move psci_cpu_off_teardown() to the top of stratix10_svc_drv_remove(), > before async channel teardown, to close the race where a warm-reboot > notification could fire while the driver is being dismantled. > > --- > drivers/firmware/stratix10-svc.c | 88 ++++++++++++++++++++++++++++++-- > 1 file changed, 85 insertions(+), 3 deletions(-) > I had some erros apply this patch, but was able to finally do it. Please double-check the applied patch on my branch: socfpga_firmware_for_v7.4 Dinh
On 9/5/2026 12:55 AM, Dinh Nguyen wrote: > > > On 9/4/26 00:52, Adrian Ng Ho Yin wrote: >> On Agilex7 and Stratix10 SoCs, secondary CPUs must be offlined before >> a warm reboot to ensure a clean PSCI state. Agilex5 warm reset is a >> SDM-owned HPS reset that holds secondaries in the new reset-release >> until >> ATF releases them, so the secondary cores dont have to be put offline >> first. Register a reboot notifier that calls remove_cpu() on each >> secondary CPU when SYS_RESTART is triggered in REBOOT_WARM mode. >> >> A snapshot of cpu_online_mask is taken before the loop since >> remove_cpu() >> modifies the mask as CPUs go down. CPU 0 is excluded as it is typically >> not hotpluggable. Failures are logged and the loop continues so the >> reboot is not blocked by a single CPU that refuses to offline. >> The of_device_id table carries a per-compatible pdata flag to enable the >> notifier only on platforms that need it; Agilex5 leaves the flag unset. >> >> The notifier is unregistered at the start of remove(), before async >> channel teardown, to prevent a reboot notification from racing with >> driver shutdown. >> >> Signed-off-by: Adrian Ng Ho Yin <adrian.ho.yin.ng@altera.com> >> --- >> changelog: >> v2 -> v3: >> - Use ctrl in psci_offline_secondary_cpus() via dev_err(ctrl->dev, ...) >> instead of pr_err(). The controller pointer was unused; this both >> consumes it and attributes the failure to the svc device. >> - Keep of_device_id.data / stratix10_svc_pdata rather than dropping it >> because both intel,stratix10-svc and intel,agilex-svc happen to share >> the same notifier path. A separate patch adds intel,agilex5-svc >> without >> .data so Agilex5 does not run this sequence. Agilex5 warm reset is an >> SDM-owned HPS reset (REBOOT_HPS) that holds secondaries in the >> reset-release / P-channel block until ATF CPURSTRELEASE; Linux must >> not PSCI-offline them first. >> >> v1 -> v2: >> - Drop workqueue fan-out: remove_cpu() serialises on device_hotplug_lock >> internally so concurrent workers provided no parallelism. Replace >> with >> a single sequential loop over a cpumask snapshot. >> - Fix CPU identity bug: get_cpu()/put_cpu() did not prevent task >> migration >> and the notifier can run on any CPU before migrate_to_reboot_cpu(). >> Exclude CPU 0 (typically not hotpluggable) and offline everything >> else. >> - Fix pre-count race: the two-loop atomic pre-count had a window where a >> concurrent hotplug event could cause dec_and_test() to fire >> prematurely. >> Eliminated entirely along with the completion and timeout. >> - Replace of_device_is_compatible() checks with a stratix10_svc_pdata >> struct carried in of_device_id.data, retrieved via >> of_device_get_match_data(). >> - Move psci_cpu_off_teardown() to the top of stratix10_svc_drv_remove(), >> before async channel teardown, to close the race where a warm-reboot >> notification could fire while the driver is being dismantled. >> >> --- >> drivers/firmware/stratix10-svc.c | 88 ++++++++++++++++++++++++++++++-- >> 1 file changed, 85 insertions(+), 3 deletions(-) >> > I had some erros apply this patch, but was able to finally do it. > Please double-check the applied patch on my branch: > socfpga_firmware_for_v7.4 Hi Dinh, The patch looks good on socfpga_firmware_for_v7.4. Thank You Adrian
© 2016 - 2026 Red Hat, Inc.