325 lines
9.8 KiB
Diff
325 lines
9.8 KiB
Diff
|
From: John Ogness <john.ogness@linutronix.de>
|
||
|
Date: Wed, 4 Sep 2024 14:11:34 +0206
|
||
|
Subject: [PATCH 52/54] printk: Implement legacy printer kthread for PREEMPT_RT
|
||
|
Origin: https://www.kernel.org/pub/linux/kernel/projects/rt/6.11/older/patches-6.11-rt7.tar.xz
|
||
|
|
||
|
The write() callback of legacy consoles usually makes use of
|
||
|
spinlocks. This is not permitted with PREEMPT_RT in atomic
|
||
|
contexts.
|
||
|
|
||
|
For PREEMPT_RT, create a new kthread to handle printing of all
|
||
|
the legacy consoles (and nbcon consoles if boot consoles are
|
||
|
registered). This allows legacy consoles to work on PREEMPT_RT
|
||
|
without requiring modification. (However they will not have
|
||
|
the reliability properties guaranteed by nbcon atomic
|
||
|
consoles.)
|
||
|
|
||
|
Use the existing printk_kthreads_check_locked() to start/stop
|
||
|
the legacy kthread as needed.
|
||
|
|
||
|
Introduce the macro force_legacy_kthread() to query if the
|
||
|
forced threading of legacy consoles is in effect. Although
|
||
|
currently only enabled for PREEMPT_RT, this acts as a simple
|
||
|
mechanism for the future to allow other preemption models to
|
||
|
easily take advantage of the non-interference property provided
|
||
|
by the legacy kthread.
|
||
|
|
||
|
When force_legacy_kthread() is true, the legacy kthread
|
||
|
fulfills the role of the console_flush_type @legacy_offload by
|
||
|
waking the legacy kthread instead of printing via the
|
||
|
console_lock in the irq_work. If the legacy kthread is not
|
||
|
yet available, no legacy printing takes place (unless in
|
||
|
panic).
|
||
|
|
||
|
If for some reason the legacy kthread fails to create, any
|
||
|
legacy consoles are unregistered. With force_legacy_kthread(),
|
||
|
the legacy kthread is a critical component for legacy consoles.
|
||
|
|
||
|
These changes only affect CONFIG_PREEMPT_RT.
|
||
|
|
||
|
Signed-off-by: John Ogness <john.ogness@linutronix.de>
|
||
|
Reviewed-by: Petr Mladek <pmladek@suse.com>
|
||
|
Link: https://lore.kernel.org/r/20240904120536.115780-16-john.ogness@linutronix.de
|
||
|
Signed-off-by: Petr Mladek <pmladek@suse.com>
|
||
|
Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
|
||
|
---
|
||
|
kernel/printk/internal.h | 16 ++++
|
||
|
kernel/printk/printk.c | 157 +++++++++++++++++++++++++++++++++++++++-----
|
||
|
kernel/printk/printk_safe.c | 4 -
|
||
|
3 files changed, 159 insertions(+), 18 deletions(-)
|
||
|
|
||
|
--- a/kernel/printk/internal.h
|
||
|
+++ b/kernel/printk/internal.h
|
||
|
@@ -21,6 +21,19 @@ int devkmsg_sysctl_set_loglvl(const stru
|
||
|
(con->flags & CON_BOOT) ? "boot" : "", \
|
||
|
con->name, con->index, ##__VA_ARGS__)
|
||
|
|
||
|
+/*
|
||
|
+ * Identify if legacy printing is forced in a dedicated kthread. If
|
||
|
+ * true, all printing via console lock occurs within a dedicated
|
||
|
+ * legacy printer thread. The only exception is on panic, after the
|
||
|
+ * nbcon consoles have had their chance to print the panic messages
|
||
|
+ * first.
|
||
|
+ */
|
||
|
+#ifdef CONFIG_PREEMPT_RT
|
||
|
+# define force_legacy_kthread() (true)
|
||
|
+#else
|
||
|
+# define force_legacy_kthread() (false)
|
||
|
+#endif
|
||
|
+
|
||
|
#ifdef CONFIG_PRINTK
|
||
|
|
||
|
#ifdef CONFIG_PRINTK_CALLER
|
||
|
@@ -173,6 +186,7 @@ static inline void nbcon_kthread_wake(st
|
||
|
#define printk_safe_exit_irqrestore(flags) local_irq_restore(flags)
|
||
|
|
||
|
static inline bool printk_percpu_data_ready(void) { return false; }
|
||
|
+static inline void defer_console_output(void) { }
|
||
|
static inline bool is_printk_legacy_deferred(void) { return false; }
|
||
|
static inline u64 nbcon_seq_read(struct console *con) { return 0; }
|
||
|
static inline void nbcon_seq_force(struct console *con, u64 seq) { }
|
||
|
@@ -200,7 +214,7 @@ extern bool legacy_allow_panic_sync;
|
||
|
* @nbcon_atomic: Flush directly using nbcon_atomic() callback
|
||
|
* @nbcon_offload: Offload flush to printer thread
|
||
|
* @legacy_direct: Call the legacy loop in this context
|
||
|
- * @legacy_offload: Offload the legacy loop into IRQ
|
||
|
+ * @legacy_offload: Offload the legacy loop into IRQ or legacy thread
|
||
|
*
|
||
|
* Note that the legacy loop also flushes the nbcon consoles.
|
||
|
*/
|
||
|
--- a/kernel/printk/printk.c
|
||
|
+++ b/kernel/printk/printk.c
|
||
|
@@ -491,6 +491,7 @@ bool legacy_allow_panic_sync;
|
||
|
|
||
|
#ifdef CONFIG_PRINTK
|
||
|
DECLARE_WAIT_QUEUE_HEAD(log_wait);
|
||
|
+static DECLARE_WAIT_QUEUE_HEAD(legacy_wait);
|
||
|
/* All 3 protected by @syslog_lock. */
|
||
|
/* the next printk record to read by syslog(READ) or /proc/kmsg */
|
||
|
static u64 syslog_seq;
|
||
|
@@ -2757,6 +2758,8 @@ void resume_console(void)
|
||
|
printk_get_console_flush_type(&ft);
|
||
|
if (ft.nbcon_offload)
|
||
|
nbcon_kthreads_wake();
|
||
|
+ if (ft.legacy_offload)
|
||
|
+ defer_console_output();
|
||
|
|
||
|
pr_flush(1000, true);
|
||
|
}
|
||
|
@@ -3167,19 +3170,7 @@ static bool console_flush_all(bool do_co
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
-/**
|
||
|
- * console_unlock - unblock the console subsystem from printing
|
||
|
- *
|
||
|
- * Releases the console_lock which the caller holds to block printing of
|
||
|
- * the console subsystem.
|
||
|
- *
|
||
|
- * While the console_lock was held, console output may have been buffered
|
||
|
- * by printk(). If this is the case, console_unlock(); emits
|
||
|
- * the output prior to releasing the lock.
|
||
|
- *
|
||
|
- * console_unlock(); may be called from any context.
|
||
|
- */
|
||
|
-void console_unlock(void)
|
||
|
+static void __console_flush_and_unlock(void)
|
||
|
{
|
||
|
bool do_cond_resched;
|
||
|
bool handover;
|
||
|
@@ -3223,6 +3214,29 @@ void console_unlock(void)
|
||
|
*/
|
||
|
} while (prb_read_valid(prb, next_seq, NULL) && console_trylock());
|
||
|
}
|
||
|
+
|
||
|
+/**
|
||
|
+ * console_unlock - unblock the legacy console subsystem from printing
|
||
|
+ *
|
||
|
+ * Releases the console_lock which the caller holds to block printing of
|
||
|
+ * the legacy console subsystem.
|
||
|
+ *
|
||
|
+ * While the console_lock was held, console output may have been buffered
|
||
|
+ * by printk(). If this is the case, console_unlock() emits the output on
|
||
|
+ * legacy consoles prior to releasing the lock.
|
||
|
+ *
|
||
|
+ * console_unlock(); may be called from any context.
|
||
|
+ */
|
||
|
+void console_unlock(void)
|
||
|
+{
|
||
|
+ struct console_flush_type ft;
|
||
|
+
|
||
|
+ printk_get_console_flush_type(&ft);
|
||
|
+ if (ft.legacy_direct)
|
||
|
+ __console_flush_and_unlock();
|
||
|
+ else
|
||
|
+ __console_unlock();
|
||
|
+}
|
||
|
EXPORT_SYMBOL(console_unlock);
|
||
|
|
||
|
/**
|
||
|
@@ -3450,6 +3464,8 @@ void console_start(struct console *conso
|
||
|
printk_get_console_flush_type(&ft);
|
||
|
if (is_nbcon && ft.nbcon_offload)
|
||
|
nbcon_kthread_wake(console);
|
||
|
+ else if (ft.legacy_offload)
|
||
|
+ defer_console_output();
|
||
|
|
||
|
__pr_flush(console, 1000, true);
|
||
|
}
|
||
|
@@ -3461,6 +3477,88 @@ static int unregister_console_locked(str
|
||
|
/* True when system boot is far enough to create printer threads. */
|
||
|
static bool printk_kthreads_ready __ro_after_init;
|
||
|
|
||
|
+static struct task_struct *printk_legacy_kthread;
|
||
|
+
|
||
|
+static bool legacy_kthread_should_wakeup(void)
|
||
|
+{
|
||
|
+ struct console_flush_type ft;
|
||
|
+ struct console *con;
|
||
|
+ bool ret = false;
|
||
|
+ int cookie;
|
||
|
+
|
||
|
+ if (kthread_should_stop())
|
||
|
+ return true;
|
||
|
+
|
||
|
+ printk_get_console_flush_type(&ft);
|
||
|
+
|
||
|
+ cookie = console_srcu_read_lock();
|
||
|
+ for_each_console_srcu(con) {
|
||
|
+ short flags = console_srcu_read_flags(con);
|
||
|
+ u64 printk_seq;
|
||
|
+
|
||
|
+ /*
|
||
|
+ * The legacy printer thread is only responsible for nbcon
|
||
|
+ * consoles when the nbcon consoles cannot print via their
|
||
|
+ * atomic or threaded flushing.
|
||
|
+ */
|
||
|
+ if ((flags & CON_NBCON) && (ft.nbcon_atomic || ft.nbcon_offload))
|
||
|
+ continue;
|
||
|
+
|
||
|
+ if (!console_is_usable(con, flags, false))
|
||
|
+ continue;
|
||
|
+
|
||
|
+ if (flags & CON_NBCON) {
|
||
|
+ printk_seq = nbcon_seq_read(con);
|
||
|
+ } else {
|
||
|
+ /*
|
||
|
+ * It is safe to read @seq because only this
|
||
|
+ * thread context updates @seq.
|
||
|
+ */
|
||
|
+ printk_seq = con->seq;
|
||
|
+ }
|
||
|
+
|
||
|
+ if (prb_read_valid(prb, printk_seq, NULL)) {
|
||
|
+ ret = true;
|
||
|
+ break;
|
||
|
+ }
|
||
|
+ }
|
||
|
+ console_srcu_read_unlock(cookie);
|
||
|
+
|
||
|
+ return ret;
|
||
|
+}
|
||
|
+
|
||
|
+static int legacy_kthread_func(void *unused)
|
||
|
+{
|
||
|
+ for (;;) {
|
||
|
+ wait_event_interruptible(legacy_wait, legacy_kthread_should_wakeup());
|
||
|
+
|
||
|
+ if (kthread_should_stop())
|
||
|
+ break;
|
||
|
+
|
||
|
+ console_lock();
|
||
|
+ __console_flush_and_unlock();
|
||
|
+ }
|
||
|
+
|
||
|
+ return 0;
|
||
|
+}
|
||
|
+
|
||
|
+static bool legacy_kthread_create(void)
|
||
|
+{
|
||
|
+ struct task_struct *kt;
|
||
|
+
|
||
|
+ lockdep_assert_console_list_lock_held();
|
||
|
+
|
||
|
+ kt = kthread_run(legacy_kthread_func, NULL, "pr/legacy");
|
||
|
+ if (WARN_ON(IS_ERR(kt))) {
|
||
|
+ pr_err("failed to start legacy printing thread\n");
|
||
|
+ return false;
|
||
|
+ }
|
||
|
+
|
||
|
+ printk_legacy_kthread = kt;
|
||
|
+
|
||
|
+ return true;
|
||
|
+}
|
||
|
+
|
||
|
/**
|
||
|
* printk_kthreads_shutdown - shutdown all threaded printers
|
||
|
*
|
||
|
@@ -3510,6 +3608,27 @@ static void printk_kthreads_check_locked
|
||
|
if (!printk_kthreads_ready)
|
||
|
return;
|
||
|
|
||
|
+ if (have_legacy_console || have_boot_console) {
|
||
|
+ if (!printk_legacy_kthread &&
|
||
|
+ force_legacy_kthread() &&
|
||
|
+ !legacy_kthread_create()) {
|
||
|
+ /*
|
||
|
+ * All legacy consoles must be unregistered. If there
|
||
|
+ * are any nbcon consoles, they will set up their own
|
||
|
+ * kthread.
|
||
|
+ */
|
||
|
+ hlist_for_each_entry_safe(con, tmp, &console_list, node) {
|
||
|
+ if (con->flags & CON_NBCON)
|
||
|
+ continue;
|
||
|
+
|
||
|
+ unregister_console_locked(con);
|
||
|
+ }
|
||
|
+ }
|
||
|
+ } else if (printk_legacy_kthread) {
|
||
|
+ kthread_stop(printk_legacy_kthread);
|
||
|
+ printk_legacy_kthread = NULL;
|
||
|
+ }
|
||
|
+
|
||
|
/*
|
||
|
* Printer threads cannot be started as long as any boot console is
|
||
|
* registered because there is no way to synchronize the hardware
|
||
|
@@ -4286,9 +4405,13 @@ static void wake_up_klogd_work_func(stru
|
||
|
int pending = this_cpu_xchg(printk_pending, 0);
|
||
|
|
||
|
if (pending & PRINTK_PENDING_OUTPUT) {
|
||
|
- /* If trylock fails, someone else is doing the printing */
|
||
|
- if (console_trylock())
|
||
|
- console_unlock();
|
||
|
+ if (force_legacy_kthread()) {
|
||
|
+ if (printk_legacy_kthread)
|
||
|
+ wake_up_interruptible(&legacy_wait);
|
||
|
+ } else {
|
||
|
+ if (console_trylock())
|
||
|
+ console_unlock();
|
||
|
+ }
|
||
|
}
|
||
|
|
||
|
if (pending & PRINTK_PENDING_WAKEUP)
|
||
|
@@ -4703,6 +4826,8 @@ void console_try_replay_all(void)
|
||
|
nbcon_atomic_flush_pending();
|
||
|
if (ft.nbcon_offload)
|
||
|
nbcon_kthreads_wake();
|
||
|
+ if (ft.legacy_offload)
|
||
|
+ defer_console_output();
|
||
|
/* Consoles are flushed as part of console_unlock(). */
|
||
|
console_unlock();
|
||
|
}
|
||
|
--- a/kernel/printk/printk_safe.c
|
||
|
+++ b/kernel/printk/printk_safe.c
|
||
|
@@ -44,7 +44,9 @@ bool is_printk_legacy_deferred(void)
|
||
|
* The per-CPU variable @printk_context can be read safely in any
|
||
|
* context. CPU migration is always disabled when set.
|
||
|
*/
|
||
|
- return (this_cpu_read(printk_context) || in_nmi());
|
||
|
+ return (force_legacy_kthread() ||
|
||
|
+ this_cpu_read(printk_context) ||
|
||
|
+ in_nmi());
|
||
|
}
|
||
|
|
||
|
asmlinkage int vprintk(const char *fmt, va_list args)
|