[PATCH] firmware: arm_scmi: fix deadlock between reboot notifier and forceful work
Chengyu Liu
lui2012qwq at gmail.com
Tue Nov 3 21:33:59 PST 2026
scmi_reboot_notifier() calls cancel_delayed_work_sync() while holding
sc->state_mtx, but the work it waits on - scmi_forceful_work_func() -
takes the same mutex. If a forceful transition work is in flight when
the reboot notifier fires, the notifier holds the lock and waits for
the callback while the callback waits for the lock: deadlock, and the
reboot wedges.
Moving the cancel outside the lock is not sufficient on its own, as the
callback also calls unregister_reboot_notifier() unconditionally, which
blocks on the reboot chain rwsem (down_write vs. the in-progress
chain's down_read) while the notifier waits for the callback - the
deadlock would just move to a different lock. Fix both sides:
- Notifier: compute the decision under state_mtx, drop the lock, then
cancel. A callback that slips in between sees state ==
SCMI_SYSPOWER_REBOOTING (not IN_PROGRESS), skips the forceful
request and finishes, so the cancel cannot miss it. (The callback's
own system_state early-return does not cover a work item that
started before the reboot began: kernel_restart_prepare() runs the
notifier chain before setting system_state.)
- Callback: only unregister the reboot notifier when actually forcing
the transition - that is the only case that needs it, because the
forceful kernel_restart()/kernel_power_off() would otherwise
re-enter scmi_reboot_notifier and self-deadlock on state_mtx. When
the state is not IN_PROGRESS there is nothing to force, and skipping
the unregister keeps the callback from blocking on the reboot chain
rwsem while the notifier waits for it.
Found by static analysis.
Cc: stable at vger.kernel.org
Signed-off-by: Chengyu Liu <lui2012qwq at gmail.com>
---
.../firmware/arm_scmi/scmi_power_control.c | 19 ++++++++++++++-----
1 file changed, 14 insertions(+), 5 deletions(-)
diff --git a/drivers/firmware/arm_scmi/scmi_power_control.c b/drivers/firmware/arm_scmi/scmi_power_control.c
index 9557363360..b5a0b71505 100644
--- a/drivers/firmware/arm_scmi/scmi_power_control.c
+++ b/drivers/firmware/arm_scmi/scmi_power_control.c
@@ -121,6 +121,7 @@ static int scmi_reboot_notifier(struct notifier_block *nb,
unsigned long reason, void *__unused)
{
struct scmi_syspower_conf *sc = reboot_nb_to_sconf(nb);
+ bool cancel_work;
mutex_lock(&sc->state_mtx);
switch (reason) {
@@ -138,11 +139,18 @@ static int scmi_reboot_notifier(struct notifier_block *nb,
break;
}
- if (sc->state == SCMI_SYSPOWER_REBOOTING) {
+ cancel_work = sc->state == SCMI_SYSPOWER_REBOOTING;
+ mutex_unlock(&sc->state_mtx);
+
+ if (cancel_work) {
dev_dbg(sc->dev, "Reboot in progress...cancel delayed work.\n");
+ /*
+ * cancel_delayed_work_sync() waits for a running
+ * scmi_forceful_work_func(), which takes state_mtx - it must
+ * not be called with the lock held.
+ */
cancel_delayed_work_sync(&sc->forceful_work);
}
- mutex_unlock(&sc->state_mtx);
return NOTIFY_OK;
}
@@ -190,10 +198,11 @@ static void scmi_forceful_work_func(struct work_struct *work)
dev_dbg(sc->dev, "Graceful request timed out...forcing !\n");
mutex_lock(&sc->state_mtx);
- /* avoid deadlock by unregistering reboot notifier first */
- unregister_reboot_notifier(&sc->reboot_nb);
- if (sc->state == SCMI_SYSPOWER_IN_PROGRESS)
+ if (sc->state == SCMI_SYSPOWER_IN_PROGRESS) {
+ /* avoid deadlock by unregistering reboot notifier first */
+ unregister_reboot_notifier(&sc->reboot_nb);
scmi_request_forceful_transition(sc);
+ }
mutex_unlock(&sc->state_mtx);
}
--
2.25.1
More information about the linux-arm-kernel
mailing list