[PATCH] printk: Prevent softlockup when resuming console

From: Petr Mladek
Date: Thu May 06 2021 - 06:40:56 EST


Many printk messages might get accumulated when consoles were suspended.
They are proceed when console_unlock() is called in resume_console().

The possibility to pass the console lock owner was added to reduce the risk
of softlockup when too many messages were handled in an atomic context.

Now, resume_console() is always in a preemptible context that is safe
to handle all accumulated messages. The possibility to pass the console
lock owner actually makes things worse. The new owner might be in an atomic
context and might cause softlockup when processing all messages accumulated
when the console was suspended.

Create new console_unlock_preemptible() that will not allow to pass
the console lock owner. As a result, all accumulated messages will
be proceed in the safe preemptible process.

Use it in resume_console(). But it might be used also on other locations
where console lock was used in preemptible context and many messages
might get accumulated when the process was sleeping.

Reported-by: Luo Jiaxing <luojiaxing@xxxxxxxxxx>
Suggested-by: Luo Jiaxing <luojiaxing@xxxxxxxxxx>
Signed-off-by: Petr Mladek <pmladek@xxxxxxxx>
---
include/linux/console.h | 1 +
kernel/printk/printk.c | 39 +++++++++++++++++++++++++++++++++------
2 files changed, 34 insertions(+), 6 deletions(-)

diff --git a/include/linux/console.h b/include/linux/console.h
index 20874db50bc8..0c444c6448e8 100644
--- a/include/linux/console.h
+++ b/include/linux/console.h
@@ -174,6 +174,7 @@ extern struct console *console_drivers;
extern void console_lock(void);
extern int console_trylock(void);
extern void console_unlock(void);
+extern void console_unlock_preemptible(void);
extern void console_conditional_schedule(void);
extern void console_unblank(void);
extern void console_flush_on_panic(enum con_flush_mode mode);
diff --git a/kernel/printk/printk.c b/kernel/printk/printk.c
index 421c35571797..a7e94c898646 100644
--- a/kernel/printk/printk.c
+++ b/kernel/printk/printk.c
@@ -2425,7 +2425,7 @@ void resume_console(void)
return;
down_console_sem();
console_suspended = 0;
- console_unlock();
+ console_unlock_preemptible();
}

/**
@@ -2534,10 +2534,8 @@ static inline int can_use_console(void)
* the output prior to releasing the lock.
*
* If there is output waiting, we wake /dev/kmsg and syslog() users.
- *
- * console_unlock(); may be called from any context.
*/
-void console_unlock(void)
+void __console_unlock(bool spinning_enabled)
{
static char ext_text[CONSOLE_EXT_LOG_MAX];
static char text[CONSOLE_LOG_MAX];
@@ -2546,6 +2544,7 @@ void console_unlock(void)
struct printk_info info;
struct printk_record r;

+
if (console_suspended) {
up_console_sem();
return;
@@ -2637,13 +2636,15 @@ void console_unlock(void)
* finish. This task can not be preempted if there is a
* waiter waiting to take over.
*/
- console_lock_spinning_enable();
+ if (spinning_enabled)
+ console_lock_spinning_enable();

stop_critical_timings(); /* don't trace print latency */
call_console_drivers(ext_text, ext_len, text, len);
start_critical_timings();

- if (console_lock_spinning_disable_and_check()) {
+ if (spinning_enabled &&
+ console_lock_spinning_disable_and_check()) {
printk_safe_exit_irqrestore(flags);
return;
}
@@ -2670,8 +2671,34 @@ void console_unlock(void)
if (retry && console_trylock())
goto again;
}
+
+/*
+ * Classic console_unlock() that might be called in any context.
+ *
+ * It allows to pass the console lock owner when processing the buffered
+ * messages. It helps to prevent soft lockups in an atomic context.
+ */
+void console_unlock()
+{
+ __console_unlock(true);
+}
EXPORT_SYMBOL(console_unlock);

+/*
+ * Variant of the console unlock that can be called only in preemptible
+ * context.
+ *
+ * All messages are processed in this safe context. It helps to prevent
+ * softlockups when the console lock owner was passed to an atomic context.
+ */
+void console_unlock_preemptible()
+{
+ lockdep_assert_preemption_enabled();
+
+ __console_unlock(false);
+}
+EXPORT_SYMBOL(console_unlock_preemptible);
+
/**
* console_conditional_schedule - yield the CPU if required
*
--
2.26.2