Merge tag 'printk-for-6.9' of git://git.kernel.org/pub/scm/linux/kernel/git/printk...
authorLinus Torvalds <torvalds@linux-foundation.org>
Wed, 13 Mar 2024 03:54:50 +0000 (20:54 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Wed, 13 Mar 2024 03:54:50 +0000 (20:54 -0700)
Pull printk updates from Petr Mladek:
 "Improve the behavior during panic. The issues were found when testing
  the ongoing changes introducing atomic consoles and printk kthreads:

   - pr_flush() has to wait for the last reserved record instead of the
     last finalized one. Note that records are finalized in random order
     when generated by more CPUs in parallel.

   - Ignore non-finalized records during panic(). Messages printed on
     panic-CPU are always finalized. Messages printed by other CPUs
     might never be finalized when the CPUs get stopped.

   - Block new printk() calls on non-panic CPUs completely. Backtraces
     are printed before entering the panic mode. Later messages would
     just mess information printed by the panic CPU.

   - Do not take console_lock in console_flush_on_panic() at all. The
     original code did try_lock()/console_unlock(). The unlock part
     might cause a deadlock when panic() happened in a scheduler code.

   - Fix conversion of 64-bit sequence number for 32-bit atomic
     operations"

* tag 'printk-for-6.9' of git://git.kernel.org/pub/scm/linux/kernel/git/printk/linux:
  dump_stack: Do not get cpu_sync for panic CPU
  panic: Flush kernel log buffer at the end
  printk: Avoid non-panic CPUs writing to ringbuffer
  printk: Disable passing console lock owner completely during panic()
  printk: ringbuffer: Skip non-finalized records in panic
  printk: Wait for all reserved records with pr_flush()
  printk: ringbuffer: Cleanup reader terminology
  printk: Add this_cpu_in_panic()
  printk: For @suppress_panic_printk check for other CPU in panic
  printk: ringbuffer: Clarify special lpos values
  printk: ringbuffer: Do not skip non-finalized records with prb_next_seq()
  printk: Use prb_first_seq() as base for 32bit seq macros
  printk: Adjust mapping for 32bit seq macros
  printk: nbcon: Relocate 32bit seq macros

1  2 
kernel/printk/printk.c

diff --combined kernel/printk/printk.c
@@@ -347,6 -347,29 +347,29 @@@ static bool panic_in_progress(void
        return unlikely(atomic_read(&panic_cpu) != PANIC_CPU_INVALID);
  }
  
+ /* Return true if a panic is in progress on the current CPU. */
+ bool this_cpu_in_panic(void)
+ {
+       /*
+        * We can use raw_smp_processor_id() here because it is impossible for
+        * the task to be migrated to the panic_cpu, or away from it. If
+        * panic_cpu has already been set, and we're not currently executing on
+        * that CPU, then we never will be.
+        */
+       return unlikely(atomic_read(&panic_cpu) == raw_smp_processor_id());
+ }
+ /*
+  * Return true if a panic is in progress on a remote CPU.
+  *
+  * On true, the local CPU should immediately release any printing resources
+  * that may be needed by the panic CPU.
+  */
+ bool other_cpu_in_panic(void)
+ {
+       return (panic_in_progress() && !this_cpu_in_panic());
+ }
  /*
   * This is used for debugging the mess that is the VT code by
   * keeping track if we have the console semaphore held. It's
@@@ -439,12 -462,6 +462,6 @@@ static int console_msg_format = MSG_FOR
  static DEFINE_MUTEX(syslog_lock);
  
  #ifdef CONFIG_PRINTK
- /*
-  * During panic, heavy printk by other CPUs can delay the
-  * panic and risk deadlock on console resources.
-  */
- static int __read_mostly suppress_panic_printk;
  DECLARE_WAIT_QUEUE_HEAD(log_wait);
  /* All 3 protected by @syslog_lock. */
  /* the next printk record to read by syslog(READ) or /proc/kmsg */
@@@ -598,6 -615,17 +615,6 @@@ static int check_syslog_permissions(in
        if (syslog_action_restricted(type)) {
                if (capable(CAP_SYSLOG))
                        goto ok;
 -              /*
 -               * For historical reasons, accept CAP_SYS_ADMIN too, with
 -               * a warning.
 -               */
 -              if (capable(CAP_SYS_ADMIN)) {
 -                      pr_warn_once("%s (%d): Attempt to access syslog with "
 -                                   "CAP_SYS_ADMIN but no CAP_SYSLOG "
 -                                   "(deprecated).\n",
 -                               current->comm, task_pid_nr(current));
 -                      goto ok;
 -              }
                return -EPERM;
        }
  ok:
@@@ -1835,10 -1863,23 +1852,23 @@@ static bool console_waiter
   */
  static void console_lock_spinning_enable(void)
  {
+       /*
+        * Do not use spinning in panic(). The panic CPU wants to keep the lock.
+        * Non-panic CPUs abandon the flush anyway.
+        *
+        * Just keep the lockdep annotation. The panic-CPU should avoid
+        * taking console_owner_lock because it might cause a deadlock.
+        * This looks like the easiest way how to prevent false lockdep
+        * reports without handling races a lockless way.
+        */
+       if (panic_in_progress())
+               goto lockdep;
        raw_spin_lock(&console_owner_lock);
        console_owner = current;
        raw_spin_unlock(&console_owner_lock);
  
+ lockdep:
        /* The waiter may spin on us after setting console_owner */
        spin_acquire(&console_owner_dep_map, 0, 0, _THIS_IP_);
  }
@@@ -1863,6 -1904,22 +1893,22 @@@ static int console_lock_spinning_disabl
  {
        int waiter;
  
+       /*
+        * Ignore spinning waiters during panic() because they might get stopped
+        * or blocked at any time,
+        *
+        * It is safe because nobody is allowed to start spinning during panic
+        * in the first place. If there has been a waiter then non panic CPUs
+        * might stay spinning. They would get stopped anyway. The panic context
+        * will never start spinning and an interrupted spin on panic CPU will
+        * never continue.
+        */
+       if (panic_in_progress()) {
+               /* Keep lockdep happy. */
+               spin_release(&console_owner_dep_map, _THIS_IP_);
+               return 0;
+       }
        raw_spin_lock(&console_owner_lock);
        waiter = READ_ONCE(console_waiter);
        console_owner = NULL;
@@@ -2259,8 -2316,12 +2305,12 @@@ asmlinkage int vprintk_emit(int facilit
        if (unlikely(suppress_printk))
                return 0;
  
-       if (unlikely(suppress_panic_printk) &&
-           atomic_read(&panic_cpu) != raw_smp_processor_id())
+       /*
+        * The messages on the panic CPU are the most important. If
+        * non-panic CPUs are generating any messages, they will be
+        * silently dropped.
+        */
+       if (other_cpu_in_panic())
                return 0;
  
        if (level == LOGLEVEL_SCHED) {
@@@ -2373,20 -2434,12 +2423,20 @@@ static void set_user_specified(struct c
        console_set_on_cmdline = 1;
  }
  
 -static int __add_preferred_console(char *name, int idx, char *options,
 +static int __add_preferred_console(const char *name, const short idx, char *options,
                                   char *brl_options, bool user_specified)
  {
        struct console_cmdline *c;
        int i;
  
 +      /*
 +       * We use a signed short index for struct console for device drivers to
 +       * indicate a not yet assigned index or port. However, a negative index
 +       * value is not valid for preferred console.
 +       */
 +      if (idx < 0)
 +              return -EINVAL;
 +
        /*
         *      See if this tty is not yet registered, and
         *      if we have a slot free.
@@@ -2490,7 -2543,7 +2540,7 @@@ __setup("console=", console_setup)
   * commonly to provide a default console (ie from PROM variables) when
   * the user has not supplied one.
   */
 -int add_preferred_console(char *name, int idx, char *options)
 +int add_preferred_console(const char *name, const short idx, char *options)
  {
        return __add_preferred_console(name, idx, options, NULL, false);
  }
@@@ -2590,26 -2643,6 +2640,6 @@@ static int console_cpu_notify(unsigned 
        return 0;
  }
  
- /*
-  * Return true if a panic is in progress on a remote CPU.
-  *
-  * On true, the local CPU should immediately release any printing resources
-  * that may be needed by the panic CPU.
-  */
- bool other_cpu_in_panic(void)
- {
-       if (!panic_in_progress())
-               return false;
-       /*
-        * We can use raw_smp_processor_id() here because it is impossible for
-        * the task to be migrated to the panic_cpu, or away from it. If
-        * panic_cpu has already been set, and we're not currently executing on
-        * that CPU, then we never will be.
-        */
-       return atomic_read(&panic_cpu) != raw_smp_processor_id();
- }
  /**
   * console_lock - block the console subsystem from printing
   *
@@@ -2765,8 -2798,6 +2795,6 @@@ void console_prepend_dropped(struct pri
  bool printk_get_next_message(struct printk_message *pmsg, u64 seq,
                             bool is_extended, bool may_suppress)
  {
-       static int panic_console_dropped;
        struct printk_buffers *pbufs = pmsg->pbufs;
        const size_t scratchbuf_sz = sizeof(pbufs->scratchbuf);
        const size_t outbuf_sz = sizeof(pbufs->outbuf);
        pmsg->seq = r.info->seq;
        pmsg->dropped = r.info->seq - seq;
  
-       /*
-        * Check for dropped messages in panic here so that printk
-        * suppression can occur as early as possible if necessary.
-        */
-       if (pmsg->dropped &&
-           panic_in_progress() &&
-           panic_console_dropped++ > 10) {
-               suppress_panic_printk = 1;
-               pr_warn_once("Too many dropped messages. Suppress messages on non-panic CPUs to prevent livelock.\n");
-       }
        /* Skip record that has level above the console loglevel. */
        if (may_suppress && suppress_message_printing(r.info->level))
                goto out;
@@@ -3750,7 -3770,7 +3767,7 @@@ static bool __pr_flush(struct console *
  
        might_sleep();
  
-       seq = prb_next_seq(prb);
+       seq = prb_next_reserve_seq(prb);
  
        /* Flush the consoles so that records up to @seq are printed. */
        console_lock();