From: Johan Hovold johan@kernel.org
mainline inclusion from mainline-v5.15-rc6 commit 57116ce17b04fde2fe30f0859df69d8dbe5809f6 category: bugfix bugzilla: https://gitee.com/openeuler/kernel/issues/I61CQ3
Reference: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?id=...
-------------------------------------------------
Console drivers often queue work while holding locks also taken in their console write paths, something which can lead to deadlocks on SMP when dumping workqueue state (e.g. sysrq-t or on suspend failures).
For serial console drivers this could look like:
CPU0 CPU1 ---- ----
show_workqueue_state(); lock(&pool->lock); <IRQ> lock(&port->lock); schedule_work(); lock(&pool->lock); printk(); lock(console_owner); lock(&port->lock);
where workqueues are, for example, used to push data to the line discipline, process break signals and handle modem-status changes. Line disciplines and serdev drivers can also queue work on write-wakeup notifications, etc.
Reworking every console driver to avoid queuing work while holding locks also taken in their write paths would complicate drivers and is neither desirable or feasible.
Instead use the deferred-printk mechanism to avoid printing while holding pool locks when dumping workqueue state.
Note that there are a few WARN_ON() assertions in the workqueue code which could potentially also trigger a deadlock. Hopefully the ongoing printk rework will provide a general solution for this eventually.
This was originally reported after a lockdep splat when executing sysrq-t with the imx serial driver.
Fixes: 3494fc30846d ("workqueue: dump workqueues on sysrq-t") Cc: stable@vger.kernel.org # 4.0 Reported-by: Fabio Estevam festevam@denx.de Tested-by: Fabio Estevam festevam@denx.de Signed-off-by: Johan Hovold johan@kernel.org Reviewed-by: John Ogness john.ogness@linutronix.de Signed-off-by: Tejun Heo tj@kernel.org Signed-off-by: Wang ShaoBo bobo.shaobowang@huawei.com Reviewed-by: Xie XiuQi xiexiuqi@huawei.com Signed-off-by: Zheng Zengkai zhengzengkai@huawei.com --- kernel/workqueue.c | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-)
diff --git a/kernel/workqueue.c b/kernel/workqueue.c index 422ee6312475..a27605c17f07 100644 --- a/kernel/workqueue.c +++ b/kernel/workqueue.c @@ -4812,8 +4812,16 @@ void show_workqueue_state(void)
for_each_pwq(pwq, wq) { raw_spin_lock_irqsave(&pwq->pool->lock, flags); - if (pwq->nr_active || !list_empty(&pwq->delayed_works)) + if (pwq->nr_active || !list_empty(&pwq->delayed_works)) { + /* + * Defer printing to avoid deadlocks in console + * drivers that queue work while holding locks + * also taken in their write paths. + */ + printk_safe_enter(); show_pwq(pwq); + printk_safe_exit(); + } raw_spin_unlock_irqrestore(&pwq->pool->lock, flags); /* * We could be printing a lot from atomic context, e.g. @@ -4831,7 +4839,12 @@ void show_workqueue_state(void) raw_spin_lock_irqsave(&pool->lock, flags); if (pool->nr_workers == pool->nr_idle) goto next_pool; - + /* + * Defer printing to avoid deadlocks in console drivers that + * queue work while holding locks also taken in their write + * paths. + */ + printk_safe_enter(); pr_info("pool %d:", pool->id); pr_cont_pool_info(pool); pr_cont(" hung=%us workers=%d", @@ -4846,6 +4859,7 @@ void show_workqueue_state(void) first = false; } pr_cont("\n"); + printk_safe_exit(); next_pool: raw_spin_unlock_irqrestore(&pool->lock, flags); /*