Skip to content

Commit 5f53ca3

Browse files
jognesspmladek
authored andcommitted
printk: Implement legacy printer kthread for PREEMPT_RT
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>
1 parent def84b4 commit 5f53ca3

File tree

3 files changed

+159
-18
lines changed

3 files changed

+159
-18
lines changed

kernel/printk/internal.h

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,19 @@ int devkmsg_sysctl_set_loglvl(const struct ctl_table *table, int write,
2121
(con->flags & CON_BOOT) ? "boot" : "", \
2222
con->name, con->index, ##__VA_ARGS__)
2323

24+
/*
25+
* Identify if legacy printing is forced in a dedicated kthread. If
26+
* true, all printing via console lock occurs within a dedicated
27+
* legacy printer thread. The only exception is on panic, after the
28+
* nbcon consoles have had their chance to print the panic messages
29+
* first.
30+
*/
31+
#ifdef CONFIG_PREEMPT_RT
32+
# define force_legacy_kthread() (true)
33+
#else
34+
# define force_legacy_kthread() (false)
35+
#endif
36+
2437
#ifdef CONFIG_PRINTK
2538

2639
#ifdef CONFIG_PRINTK_CALLER
@@ -173,6 +186,7 @@ static inline void nbcon_kthread_wake(struct console *con)
173186
#define printk_safe_exit_irqrestore(flags) local_irq_restore(flags)
174187

175188
static inline bool printk_percpu_data_ready(void) { return false; }
189+
static inline void defer_console_output(void) { }
176190
static inline bool is_printk_legacy_deferred(void) { return false; }
177191
static inline u64 nbcon_seq_read(struct console *con) { return 0; }
178192
static inline void nbcon_seq_force(struct console *con, u64 seq) { }
@@ -200,7 +214,7 @@ extern bool legacy_allow_panic_sync;
200214
* @nbcon_atomic: Flush directly using nbcon_atomic() callback
201215
* @nbcon_offload: Offload flush to printer thread
202216
* @legacy_direct: Call the legacy loop in this context
203-
* @legacy_offload: Offload the legacy loop into IRQ
217+
* @legacy_offload: Offload the legacy loop into IRQ or legacy thread
204218
*
205219
* Note that the legacy loop also flushes the nbcon consoles.
206220
*/

kernel/printk/printk.c

Lines changed: 141 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -491,6 +491,7 @@ bool legacy_allow_panic_sync;
491491

492492
#ifdef CONFIG_PRINTK
493493
DECLARE_WAIT_QUEUE_HEAD(log_wait);
494+
static DECLARE_WAIT_QUEUE_HEAD(legacy_wait);
494495
/* All 3 protected by @syslog_lock. */
495496
/* the next printk record to read by syslog(READ) or /proc/kmsg */
496497
static u64 syslog_seq;
@@ -2756,6 +2757,8 @@ void resume_console(void)
27562757
printk_get_console_flush_type(&ft);
27572758
if (ft.nbcon_offload)
27582759
nbcon_kthreads_wake();
2760+
if (ft.legacy_offload)
2761+
defer_console_output();
27592762

27602763
pr_flush(1000, true);
27612764
}
@@ -3166,19 +3169,7 @@ static bool console_flush_all(bool do_cond_resched, u64 *next_seq, bool *handove
31663169
return false;
31673170
}
31683171

3169-
/**
3170-
* console_unlock - unblock the console subsystem from printing
3171-
*
3172-
* Releases the console_lock which the caller holds to block printing of
3173-
* the console subsystem.
3174-
*
3175-
* While the console_lock was held, console output may have been buffered
3176-
* by printk(). If this is the case, console_unlock(); emits
3177-
* the output prior to releasing the lock.
3178-
*
3179-
* console_unlock(); may be called from any context.
3180-
*/
3181-
void console_unlock(void)
3172+
static void __console_flush_and_unlock(void)
31823173
{
31833174
bool do_cond_resched;
31843175
bool handover;
@@ -3222,6 +3213,29 @@ void console_unlock(void)
32223213
*/
32233214
} while (prb_read_valid(prb, next_seq, NULL) && console_trylock());
32243215
}
3216+
3217+
/**
3218+
* console_unlock - unblock the legacy console subsystem from printing
3219+
*
3220+
* Releases the console_lock which the caller holds to block printing of
3221+
* the legacy console subsystem.
3222+
*
3223+
* While the console_lock was held, console output may have been buffered
3224+
* by printk(). If this is the case, console_unlock() emits the output on
3225+
* legacy consoles prior to releasing the lock.
3226+
*
3227+
* console_unlock(); may be called from any context.
3228+
*/
3229+
void console_unlock(void)
3230+
{
3231+
struct console_flush_type ft;
3232+
3233+
printk_get_console_flush_type(&ft);
3234+
if (ft.legacy_direct)
3235+
__console_flush_and_unlock();
3236+
else
3237+
__console_unlock();
3238+
}
32253239
EXPORT_SYMBOL(console_unlock);
32263240

32273241
/**
@@ -3449,6 +3463,8 @@ void console_start(struct console *console)
34493463
printk_get_console_flush_type(&ft);
34503464
if (is_nbcon && ft.nbcon_offload)
34513465
nbcon_kthread_wake(console);
3466+
else if (ft.legacy_offload)
3467+
defer_console_output();
34523468

34533469
__pr_flush(console, 1000, true);
34543470
}
@@ -3460,6 +3476,88 @@ static int unregister_console_locked(struct console *console);
34603476
/* True when system boot is far enough to create printer threads. */
34613477
static bool printk_kthreads_ready __ro_after_init;
34623478

3479+
static struct task_struct *printk_legacy_kthread;
3480+
3481+
static bool legacy_kthread_should_wakeup(void)
3482+
{
3483+
struct console_flush_type ft;
3484+
struct console *con;
3485+
bool ret = false;
3486+
int cookie;
3487+
3488+
if (kthread_should_stop())
3489+
return true;
3490+
3491+
printk_get_console_flush_type(&ft);
3492+
3493+
cookie = console_srcu_read_lock();
3494+
for_each_console_srcu(con) {
3495+
short flags = console_srcu_read_flags(con);
3496+
u64 printk_seq;
3497+
3498+
/*
3499+
* The legacy printer thread is only responsible for nbcon
3500+
* consoles when the nbcon consoles cannot print via their
3501+
* atomic or threaded flushing.
3502+
*/
3503+
if ((flags & CON_NBCON) && (ft.nbcon_atomic || ft.nbcon_offload))
3504+
continue;
3505+
3506+
if (!console_is_usable(con, flags, false))
3507+
continue;
3508+
3509+
if (flags & CON_NBCON) {
3510+
printk_seq = nbcon_seq_read(con);
3511+
} else {
3512+
/*
3513+
* It is safe to read @seq because only this
3514+
* thread context updates @seq.
3515+
*/
3516+
printk_seq = con->seq;
3517+
}
3518+
3519+
if (prb_read_valid(prb, printk_seq, NULL)) {
3520+
ret = true;
3521+
break;
3522+
}
3523+
}
3524+
console_srcu_read_unlock(cookie);
3525+
3526+
return ret;
3527+
}
3528+
3529+
static int legacy_kthread_func(void *unused)
3530+
{
3531+
for (;;) {
3532+
wait_event_interruptible(legacy_wait, legacy_kthread_should_wakeup());
3533+
3534+
if (kthread_should_stop())
3535+
break;
3536+
3537+
console_lock();
3538+
__console_flush_and_unlock();
3539+
}
3540+
3541+
return 0;
3542+
}
3543+
3544+
static bool legacy_kthread_create(void)
3545+
{
3546+
struct task_struct *kt;
3547+
3548+
lockdep_assert_console_list_lock_held();
3549+
3550+
kt = kthread_run(legacy_kthread_func, NULL, "pr/legacy");
3551+
if (WARN_ON(IS_ERR(kt))) {
3552+
pr_err("failed to start legacy printing thread\n");
3553+
return false;
3554+
}
3555+
3556+
printk_legacy_kthread = kt;
3557+
3558+
return true;
3559+
}
3560+
34633561
/**
34643562
* printk_kthreads_shutdown - shutdown all threaded printers
34653563
*
@@ -3509,6 +3607,27 @@ static void printk_kthreads_check_locked(void)
35093607
if (!printk_kthreads_ready)
35103608
return;
35113609

3610+
if (have_legacy_console || have_boot_console) {
3611+
if (!printk_legacy_kthread &&
3612+
force_legacy_kthread() &&
3613+
!legacy_kthread_create()) {
3614+
/*
3615+
* All legacy consoles must be unregistered. If there
3616+
* are any nbcon consoles, they will set up their own
3617+
* kthread.
3618+
*/
3619+
hlist_for_each_entry_safe(con, tmp, &console_list, node) {
3620+
if (con->flags & CON_NBCON)
3621+
continue;
3622+
3623+
unregister_console_locked(con);
3624+
}
3625+
}
3626+
} else if (printk_legacy_kthread) {
3627+
kthread_stop(printk_legacy_kthread);
3628+
printk_legacy_kthread = NULL;
3629+
}
3630+
35123631
/*
35133632
* Printer threads cannot be started as long as any boot console is
35143633
* registered because there is no way to synchronize the hardware
@@ -4285,9 +4404,13 @@ static void wake_up_klogd_work_func(struct irq_work *irq_work)
42854404
int pending = this_cpu_xchg(printk_pending, 0);
42864405

42874406
if (pending & PRINTK_PENDING_OUTPUT) {
4288-
/* If trylock fails, someone else is doing the printing */
4289-
if (console_trylock())
4290-
console_unlock();
4407+
if (force_legacy_kthread()) {
4408+
if (printk_legacy_kthread)
4409+
wake_up_interruptible(&legacy_wait);
4410+
} else {
4411+
if (console_trylock())
4412+
console_unlock();
4413+
}
42914414
}
42924415

42934416
if (pending & PRINTK_PENDING_WAKEUP)
@@ -4702,6 +4825,8 @@ void console_try_replay_all(void)
47024825
nbcon_atomic_flush_pending();
47034826
if (ft.nbcon_offload)
47044827
nbcon_kthreads_wake();
4828+
if (ft.legacy_offload)
4829+
defer_console_output();
47054830
/* Consoles are flushed as part of console_unlock(). */
47064831
console_unlock();
47074832
}

kernel/printk/printk_safe.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,9 @@ bool is_printk_legacy_deferred(void)
4444
* The per-CPU variable @printk_context can be read safely in any
4545
* context. CPU migration is always disabled when set.
4646
*/
47-
return (this_cpu_read(printk_context) || in_nmi());
47+
return (force_legacy_kthread() ||
48+
this_cpu_read(printk_context) ||
49+
in_nmi());
4850
}
4951

5052
asmlinkage int vprintk(const char *fmt, va_list args)

0 commit comments

Comments
 (0)