]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
printk: Avoid scheduling irq_work on suspend
authorJohn Ogness <john.ogness@linutronix.de>
Thu, 13 Nov 2025 16:03:48 +0000 (17:09 +0106)
committerPetr Mladek <pmladek@suse.com>
Wed, 19 Nov 2025 15:01:31 +0000 (16:01 +0100)
Allowing irq_work to be scheduled while trying to suspend has shown
to cause problems as some architectures interpret the pending
interrupts as a reason to not suspend. This became a problem for
printk() with the introduction of NBCON consoles. With every
printk() call, NBCON console printing kthreads are woken by queueing
irq_work. This means that irq_work continues to be queued due to
printk() calls late in the suspend procedure.

Avoid this problem by preventing printk() from queueing irq_work
once console suspending has begun. This applies to triggering NBCON
and legacy deferred printing as well as klogd waiters.

Since triggering of NBCON threaded printing relies on irq_work, the
pr_flush() within console_suspend_all() is used to perform the final
flushing before suspending consoles and blocking irq_work queueing.
NBCON consoles that are not suspended (due to the usage of the
"no_console_suspend" boot argument) transition to atomic flushing.

Introduce a new global variable @console_irqwork_blocked to flag
when irq_work queueing is to be avoided. The flag is used by
printk_get_console_flush_type() to avoid allowing deferred printing
and switch NBCON consoles to atomic flushing. It is also used by
vprintk_emit() to avoid klogd waking.

Add WARN_ON_ONCE(console_irqwork_blocked) to the irq_work queuing
functions to catch any code that attempts to queue printk irq_work
during the suspending/resuming procedure.

Cc: stable@vger.kernel.org # 6.13.x because no drivers in 6.12.x
Fixes: 6b93bb41f6ea ("printk: Add non-BKL (nbcon) console basic infrastructure")
Closes: https://lore.kernel.org/lkml/DB9PR04MB8429E7DDF2D93C2695DE401D92C4A@DB9PR04MB8429.eurprd04.prod.outlook.com
Signed-off-by: John Ogness <john.ogness@linutronix.de>
Reviewed-by: Petr Mladek <pmladek@suse.com>
Tested-by: Sherry Sun <sherry.sun@nxp.com>
Link: https://patch.msgid.link/20251113160351.113031-3-john.ogness@linutronix.de
Signed-off-by: Petr Mladek <pmladek@suse.com>
kernel/printk/internal.h
kernel/printk/nbcon.c
kernel/printk/printk.c

index f72bbfa266d6c9bbc533661c40386aa5f0df6c8f..b20929b7d71f596eda5ea884cdb6c800b758dde5 100644 (file)
@@ -230,6 +230,8 @@ struct console_flush_type {
        bool    legacy_offload;
 };
 
+extern bool console_irqwork_blocked;
+
 /*
  * Identify which console flushing methods should be used in the context of
  * the caller.
@@ -241,7 +243,7 @@ static inline void printk_get_console_flush_type(struct console_flush_type *ft)
        switch (nbcon_get_default_prio()) {
        case NBCON_PRIO_NORMAL:
                if (have_nbcon_console && !have_boot_console) {
-                       if (printk_kthreads_running)
+                       if (printk_kthreads_running && !console_irqwork_blocked)
                                ft->nbcon_offload = true;
                        else
                                ft->nbcon_atomic = true;
@@ -251,7 +253,7 @@ static inline void printk_get_console_flush_type(struct console_flush_type *ft)
                if (have_legacy_console || have_boot_console) {
                        if (!is_printk_legacy_deferred())
                                ft->legacy_direct = true;
-                       else
+                       else if (!console_irqwork_blocked)
                                ft->legacy_offload = true;
                }
                break;
@@ -264,7 +266,7 @@ static inline void printk_get_console_flush_type(struct console_flush_type *ft)
                if (have_legacy_console || have_boot_console) {
                        if (!is_printk_legacy_deferred())
                                ft->legacy_direct = true;
-                       else
+                       else if (!console_irqwork_blocked)
                                ft->legacy_offload = true;
                }
                break;
index 73f315fd97a3e3e2fd2c03f41bd086d5ec6c1561..730d14f6cbc5820da839a17c2b5e9b95cc6e8d49 100644 (file)
@@ -1276,6 +1276,13 @@ void nbcon_kthreads_wake(void)
        if (!printk_kthreads_running)
                return;
 
+       /*
+        * It is not allowed to call this function when console irq_work
+        * is blocked.
+        */
+       if (WARN_ON_ONCE(console_irqwork_blocked))
+               return;
+
        cookie = console_srcu_read_lock();
        for_each_console_srcu(con) {
                if (!(console_srcu_read_flags(con) & CON_NBCON))
index dc89239cf1b58cd0ef937b130ca37fff8b1a3cbe..b1c0d35cf3caa30c774d7718e448c85223c49ed7 100644 (file)
@@ -462,6 +462,9 @@ bool have_boot_console;
 /* See printk_legacy_allow_panic_sync() for details. */
 bool legacy_allow_panic_sync;
 
+/* Avoid using irq_work when suspending. */
+bool console_irqwork_blocked;
+
 #ifdef CONFIG_PRINTK
 DECLARE_WAIT_QUEUE_HEAD(log_wait);
 static DECLARE_WAIT_QUEUE_HEAD(legacy_wait);
@@ -2426,7 +2429,7 @@ asmlinkage int vprintk_emit(int facility, int level,
 
        if (ft.legacy_offload)
                defer_console_output();
-       else
+       else if (!console_irqwork_blocked)
                wake_up_klogd();
 
        return printed_len;
@@ -2730,10 +2733,20 @@ void console_suspend_all(void)
 {
        struct console *con;
 
+       if (console_suspend_enabled)
+               pr_info("Suspending console(s) (use no_console_suspend to debug)\n");
+
+       /*
+        * Flush any console backlog and then avoid queueing irq_work until
+        * console_resume_all(). Until then deferred printing is no longer
+        * triggered, NBCON consoles transition to atomic flushing, and
+        * any klogd waiters are not triggered.
+        */
+       pr_flush(1000, true);
+       console_irqwork_blocked = true;
+
        if (!console_suspend_enabled)
                return;
-       pr_info("Suspending console(s) (use no_console_suspend to debug)\n");
-       pr_flush(1000, true);
 
        console_list_lock();
        for_each_console(con)
@@ -2754,26 +2767,34 @@ void console_resume_all(void)
        struct console_flush_type ft;
        struct console *con;
 
-       if (!console_suspend_enabled)
-               return;
-
-       console_list_lock();
-       for_each_console(con)
-               console_srcu_write_flags(con, con->flags & ~CON_SUSPENDED);
-       console_list_unlock();
-
        /*
-        * Ensure that all SRCU list walks have completed. All printing
-        * contexts must be able to see they are no longer suspended so
-        * that they are guaranteed to wake up and resume printing.
+        * Allow queueing irq_work. After restoring console state, deferred
+        * printing and any klogd waiters need to be triggered in case there
+        * is now a console backlog.
         */
-       synchronize_srcu(&console_srcu);
+       console_irqwork_blocked = false;
+
+       if (console_suspend_enabled) {
+               console_list_lock();
+               for_each_console(con)
+                       console_srcu_write_flags(con, con->flags & ~CON_SUSPENDED);
+               console_list_unlock();
+
+               /*
+                * Ensure that all SRCU list walks have completed. All printing
+                * contexts must be able to see they are no longer suspended so
+                * that they are guaranteed to wake up and resume printing.
+                */
+               synchronize_srcu(&console_srcu);
+       }
 
        printk_get_console_flush_type(&ft);
        if (ft.nbcon_offload)
                nbcon_kthreads_wake();
        if (ft.legacy_offload)
                defer_console_output();
+       else
+               wake_up_klogd();
 
        pr_flush(1000, true);
 }
@@ -4511,6 +4532,13 @@ static void __wake_up_klogd(int val)
        if (!printk_percpu_data_ready())
                return;
 
+       /*
+        * It is not allowed to call this function when console irq_work
+        * is blocked.
+        */
+       if (WARN_ON_ONCE(console_irqwork_blocked))
+               return;
+
        preempt_disable();
        /*
         * Guarantee any new records can be seen by tasks preparing to wait