printk: Add per-console suspended state
authorJohn Ogness <john.ogness@linutronix.de>
Mon, 17 Jul 2023 19:46:06 +0000 (21:52 +0206)
committerPetr Mladek <pmladek@suse.com>
Thu, 20 Jul 2023 11:06:22 +0000 (13:06 +0200)
Currently the global @console_suspended is used to determine if
consoles are in a suspended state. Its primary purpose is to allow
usage of the console_lock when suspended without causing console
printing. It is synchronized by the console_lock.

Rather than relying on the console_lock to determine suspended
state, make it an official per-console state that is set within
console->flags. This allows the state to be queried via SRCU.

Remove @console_suspended. Console printing will still be avoided
when suspended because console_is_usable() returns false when
the new suspended flag is set for that console.

Signed-off-by: John Ogness <john.ogness@linutronix.de>
Reviewed-by: Sergey Senozhatsky <senozhatsky@chromium.org>
Reviewed-by: Petr Mladek <pmladek@suse.com>
Signed-off-by: Petr Mladek <pmladek@suse.com>
Link: https://lore.kernel.org/r/20230717194607.145135-7-john.ogness@linutronix.de
include/linux/console.h
kernel/printk/printk.c

index d319566..7de11c7 100644 (file)
@@ -154,6 +154,8 @@ static inline int con_debug_leave(void)
  *                     receiving the printk spam for obvious reasons.
  * @CON_EXTENDED:      The console supports the extended output format of
  *                     /dev/kmesg which requires a larger output buffer.
+ * @CON_SUSPENDED:     Indicates if a console is suspended. If true, the
+ *                     printing callbacks must not be called.
  */
 enum cons_flags {
        CON_PRINTBUFFER         = BIT(0),
@@ -163,6 +165,7 @@ enum cons_flags {
        CON_ANYTIME             = BIT(4),
        CON_BRL                 = BIT(5),
        CON_EXTENDED            = BIT(6),
+       CON_SUSPENDED           = BIT(7),
 };
 
 /**
index 6e853a1..efe5774 100644 (file)
@@ -86,7 +86,7 @@ EXPORT_SYMBOL(oops_in_progress);
 static DEFINE_MUTEX(console_mutex);
 
 /*
- * console_sem protects updates to console->seq and console_suspended,
+ * console_sem protects updates to console->seq
  * and also provides serialization for console printing.
  */
 static DEFINE_SEMAPHORE(console_sem);
@@ -359,7 +359,7 @@ static bool panic_in_progress(void)
  * paths in the console code where we end up in places I want
  * locked without the console semaphore held).
  */
-static int console_locked, console_suspended;
+static int console_locked;
 
 /*
  *     Array of consoles built from command line options (console=)
@@ -2549,22 +2549,46 @@ MODULE_PARM_DESC(console_no_auto_verbose, "Disable console loglevel raise to hig
  */
 void suspend_console(void)
 {
+       struct console *con;
+
        if (!console_suspend_enabled)
                return;
        pr_info("Suspending console(s) (use no_console_suspend to debug)\n");
        pr_flush(1000, true);
-       console_lock();
-       console_suspended = 1;
-       up_console_sem();
+
+       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 that they are suspended so that it
+        * is guaranteed that all printing has stopped when this function
+        * completes.
+        */
+       synchronize_srcu(&console_srcu);
 }
 
 void resume_console(void)
 {
+       struct console *con;
+
        if (!console_suspend_enabled)
                return;
-       down_console_sem();
-       console_suspended = 0;
-       console_unlock();
+
+       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);
+
        pr_flush(1000, true);
 }
 
@@ -2623,8 +2647,6 @@ void console_lock(void)
                msleep(1000);
 
        down_console_sem();
-       if (console_suspended)
-               return;
        console_locked = 1;
        console_may_schedule = 1;
 }
@@ -2645,10 +2667,6 @@ int console_trylock(void)
                return 0;
        if (down_trylock_console_sem())
                return 0;
-       if (console_suspended) {
-               up_console_sem();
-               return 0;
-       }
        console_locked = 1;
        console_may_schedule = 0;
        return 1;
@@ -2674,6 +2692,9 @@ static inline bool console_is_usable(struct console *con)
        if (!(flags & CON_ENABLED))
                return false;
 
+       if ((flags & CON_SUSPENDED))
+               return false;
+
        if (!con->write)
                return false;
 
@@ -2992,11 +3013,6 @@ void console_unlock(void)
        bool flushed;
        u64 next_seq;
 
-       if (console_suspended) {
-               up_console_sem();
-               return;
-       }
-
        /*
         * Console drivers are called with interrupts disabled, so
         * @console_may_schedule should be cleared before; however, we may
@@ -3726,8 +3742,7 @@ static bool __pr_flush(struct console *con, int timeout_ms, bool reset_on_progre
 
                /*
                 * Hold the console_lock to guarantee safe access to
-                * console->seq and to prevent changes to @console_suspended
-                * until all consoles have been processed.
+                * console->seq.
                 */
                console_lock();
 
@@ -3735,6 +3750,11 @@ static bool __pr_flush(struct console *con, int timeout_ms, bool reset_on_progre
                for_each_console_srcu(c) {
                        if (con && con != c)
                                continue;
+                       /*
+                        * If consoles are not usable, it cannot be expected
+                        * that they make forward progress, so only increment
+                        * @diff for usable consoles.
+                        */
                        if (!console_is_usable(c))
                                continue;
                        printk_seq = c->seq;
@@ -3743,18 +3763,12 @@ static bool __pr_flush(struct console *con, int timeout_ms, bool reset_on_progre
                }
                console_srcu_read_unlock(cookie);
 
-               /*
-                * If consoles are suspended, it cannot be expected that they
-                * make forward progress, so timeout immediately. @diff is
-                * still used to return a valid flush status.
-                */
-               if (console_suspended)
-                       remaining = 0;
-               else if (diff != last_diff && reset_on_progress)
+               if (diff != last_diff && reset_on_progress)
                        remaining = timeout_ms;
 
                console_unlock();
 
+               /* Note: @diff is 0 if there are no usable consoles. */
                if (diff == 0 || remaining == 0)
                        break;
 
@@ -3788,7 +3802,7 @@ static bool __pr_flush(struct console *con, int timeout_ms, bool reset_on_progre
  * printer has been seen to make some forward progress.
  *
  * Context: Process context. May sleep while acquiring console lock.
- * Return: true if all enabled printers are caught up.
+ * Return: true if all usable printers are caught up.
  */
 static bool pr_flush(int timeout_ms, bool reset_on_progress)
 {