From: Thomas Gleixner <tglx@linutronix.de> Date: Tue, 26 Sep 2023 13:03:52 +0000 Subject: [PATCH 36/48] printk: nbcon: Add printer thread wakeups Add a function to wakeup the printer threads. Use the new function when: - records are added to the printk ringbuffer - consoles are resumed - triggered via printk_trigger_flush() The actual waking is performed via irq_work so that the wakeup can be triggered from any context. Co-developed-by: John Ogness <john.ogness@linutronix.de> Signed-off-by: John Ogness <john.ogness@linutronix.de> Signed-off-by: Thomas Gleixner (Intel) <tglx@linutronix.de> Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de> --- include/linux/console.h | 3 ++ kernel/printk/internal.h | 1 kernel/printk/nbcon.c | 56 +++++++++++++++++++++++++++++++++++++++++++++++ kernel/printk/printk.c | 7 +++++ 4 files changed, 67 insertions(+) Index: linux-6.8.2-rt10/include/linux/console.h =================================================================== @ linux-6.8.2-rt10/include/linux/console.h:19 @ #include <linux/atomic.h> #include <linux/bits.h> +#include <linux/irq_work.h> #include <linux/rculist.h> #include <linux/rcuwait.h> #include <linux/types.h> @ linux-6.8.2-rt10/include/linux/console.h:331 @ struct nbcon_drvdata { * @pbufs: Pointer to nbcon private buffer * @kthread: Printer kthread for this console * @rcuwait: RCU-safe wait object for @kthread waking + * @irq_work: Defer @kthread waking to IRQ work context */ struct console { char name[16]; @ linux-6.8.2-rt10/include/linux/console.h:462 @ struct console { struct printk_buffers *pbufs; struct task_struct *kthread; struct rcuwait rcuwait; + struct irq_work irq_work; }; #ifdef CONFIG_LOCKDEP Index: linux-6.8.2-rt10/kernel/printk/internal.h =================================================================== --- linux-6.8.2-rt10.orig/kernel/printk/internal.h +++ linux-6.8.2-rt10/kernel/printk/internal.h @ linux-6.8.2-rt10/include/linux/console.h:94 @ void nbcon_atomic_flush_pending(void); bool nbcon_legacy_emit_next_record(struct console *con, bool *handover, int cookie); void nbcon_kthread_create(struct console *con); +void nbcon_wake_threads(void); /* * Check if the given console is currently capable and allowed to print Index: linux-6.8.2-rt10/kernel/printk/nbcon.c =================================================================== --- linux-6.8.2-rt10.orig/kernel/printk/nbcon.c +++ linux-6.8.2-rt10/kernel/printk/nbcon.c @ linux-6.8.2-rt10/include/linux/console.h:1059 @ wait_for_event: goto wait_for_event; } +/** + * nbcon_irq_work - irq work to wake printk thread + * @irq_work: The irq work to operate on + */ +static void nbcon_irq_work(struct irq_work *irq_work) +{ + struct console *con = container_of(irq_work, struct console, irq_work); + + nbcon_kthread_wake(con); +} + +static inline bool rcuwait_has_sleeper(struct rcuwait *w) +{ + bool has_sleeper; + + rcu_read_lock(); + /* + * Guarantee any new records can be seen by tasks preparing to wait + * before this context checks if the rcuwait is empty. + * + * This full memory barrier pairs with the full memory barrier within + * set_current_state() of ___rcuwait_wait_event(), which is called + * after prepare_to_rcuwait() adds the waiter but before it has + * checked the wait condition. + * + * This pairs with nbcon_kthread_func:A. + */ + smp_mb(); /* LMM(rcuwait_has_sleeper:A) */ + has_sleeper = !!rcu_dereference(w->task); + rcu_read_unlock(); + + return has_sleeper; +} + +/** + * nbcon_wake_threads - Wake up printing threads using irq_work + */ +void nbcon_wake_threads(void) +{ + struct console *con; + int cookie; + + cookie = console_srcu_read_lock(); + for_each_console_srcu(con) { + /* + * Only schedule irq_work if the printing thread is + * actively waiting. If not waiting, the thread will + * notice by itself that it has work to do. + */ + if (con->kthread && rcuwait_has_sleeper(&con->rcuwait)) + irq_work_queue(&con->irq_work); + } + console_srcu_read_unlock(cookie); +} + /* Track the nbcon emergency nesting per CPU. */ static DEFINE_PER_CPU(unsigned int, nbcon_pcpu_emergency_nesting); static unsigned int early_nbcon_pcpu_emergency_nesting __initdata; @ linux-6.8.2-rt10/include/linux/console.h:1509 @ void nbcon_init(struct console *con) BUG_ON(!con->pbufs); rcuwait_init(&con->rcuwait); + init_irq_work(&con->irq_work, nbcon_irq_work); nbcon_seq_force(con, 0); nbcon_state_set(con, &state); } Index: linux-6.8.2-rt10/kernel/printk/printk.c =================================================================== --- linux-6.8.2-rt10.orig/kernel/printk/printk.c +++ linux-6.8.2-rt10/kernel/printk/printk.c @ linux-6.8.2-rt10/include/linux/console.h:2418 @ asmlinkage int vprintk_emit(int facility } } + nbcon_wake_threads(); + if (do_trylock_unlock) { /* * The caller may be holding system-critical or @ linux-6.8.2-rt10/include/linux/console.h:2726 @ void resume_console(void) */ synchronize_srcu(&console_srcu); + /* + * Since this runs in task context, wake the threaded printers + * directly rather than scheduling irq_work to do it. + */ cookie = console_srcu_read_lock(); for_each_console_srcu(con) { flags = console_srcu_read_flags(con); @ linux-6.8.2-rt10/include/linux/console.h:4160 @ void defer_console_output(void) void printk_trigger_flush(void) { + nbcon_wake_threads(); defer_console_output(); }