printk: Add kthread for all legacy consoles

BugLink: https://bugs.launchpad.net/bugs/2060704

The write callback of legacy consoles 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).

Since, if running from the kthread, the consoles are printing
in a task context, the legacy nbcon printing can use the
device_lock(), write_thread(), device_unlock() callbacks for
printing.

Introduce the macro force_printkthreads() to query if the
forced threading of legacy consoles is in effect.

These changes only affect CONFIG_PREEMPT_RT.

Signed-off-by: John Ogness <john.ogness@linutronix.de>
Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
Signed-off-by: Kevin Becker <kevin.becker@canonical.com>
This commit is contained in:
John Ogness
2023-09-22 17:35:04 +00:00
committed by Kevin Becker
parent f209365b54
commit 285eff7766
3 changed files with 249 additions and 69 deletions
+18 -2
View File
@@ -21,6 +21,12 @@ int devkmsg_sysctl_set_loglvl(struct ctl_table *table, int write,
(con->flags & CON_BOOT) ? "boot" : "", \
con->name, con->index, ##__VA_ARGS__)
#ifdef CONFIG_PREEMPT_RT
# define force_printkthreads() (true)
#else
# define force_printkthreads() (false)
#endif
#ifdef CONFIG_PRINTK
#ifdef CONFIG_PRINTK_CALLER
@@ -90,9 +96,10 @@ void nbcon_free(struct console *con);
enum nbcon_prio nbcon_get_default_prio(void);
void nbcon_atomic_flush_pending(void);
bool nbcon_legacy_emit_next_record(struct console *con, bool *handover,
int cookie);
int cookie, bool use_atomic);
void nbcon_kthread_create(struct console *con);
void nbcon_wake_threads(void);
void nbcon_legacy_kthread_create(void);
/*
* Check if the given console is currently capable and allowed to print
@@ -179,7 +186,7 @@ static inline void nbcon_free(struct console *con) { }
static inline enum nbcon_prio nbcon_get_default_prio(void) { return NBCON_PRIO_NONE; }
static inline void nbcon_atomic_flush_pending(void) { }
static inline bool nbcon_legacy_emit_next_record(struct console *con, bool *handover,
int cookie) { return false; }
int cookie, bool use_atomic) { return false; }
static inline bool console_is_usable(struct console *con, short flags,
bool use_atomic) { return false; }
@@ -187,6 +194,15 @@ static inline bool console_is_usable(struct console *con, short flags,
#endif /* CONFIG_PRINTK */
extern bool have_boot_console;
extern bool have_legacy_console;
/*
* Specifies if the console lock/unlock dance is needed for console
* printing. If @have_boot_console is true, the nbcon consoles will
* be printed serially along with the legacy consoles because nbcon
* consoles cannot print simultaneously with boot consoles.
*/
#define printing_via_unlock (have_legacy_console || have_boot_console)
extern struct printk_buffers printk_shared_pbufs;
+29 -15
View File
@@ -1202,9 +1202,10 @@ static __ref unsigned int *nbcon_get_cpu_emergency_nesting(void)
}
/**
* nbcon_atomic_emit_one - Print one record for an nbcon console using the
* write_atomic() callback
* nbcon_emit_one - Print one record for an nbcon console using the
* specified callback
* @wctxt: An initialized write context struct to use for this context
* @use_atomic: True if the write_atomic callback is to be used
*
* Return: False if it is known there are no more records to print,
* otherwise true.
@@ -1212,7 +1213,7 @@ static __ref unsigned int *nbcon_get_cpu_emergency_nesting(void)
* This is an internal helper to handle the locking of the console before
* calling nbcon_emit_next_record().
*/
static bool nbcon_atomic_emit_one(struct nbcon_write_context *wctxt)
static bool nbcon_emit_one(struct nbcon_write_context *wctxt, bool use_atomic)
{
struct nbcon_context *ctxt = &ACCESS_PRIVATE(wctxt, ctxt);
@@ -1224,7 +1225,7 @@ static bool nbcon_atomic_emit_one(struct nbcon_write_context *wctxt)
* handed over or taken over. In both cases the context is no
* longer valid.
*/
if (!nbcon_emit_next_record(wctxt, true))
if (!nbcon_emit_next_record(wctxt, use_atomic))
return true;
nbcon_context_release(ctxt);
@@ -1263,6 +1264,7 @@ enum nbcon_prio nbcon_get_default_prio(void)
* both the console_lock and the SRCU read lock. Otherwise it
* is set to false.
* @cookie: The cookie from the SRCU read lock.
* @use_atomic: True if the write_atomic callback is to be used
*
* Context: Any context except NMI.
* Return: False if the given console has no next record to print,
@@ -1273,7 +1275,7 @@ enum nbcon_prio nbcon_get_default_prio(void)
* Essentially it is the nbcon version of console_emit_next_record().
*/
bool nbcon_legacy_emit_next_record(struct console *con, bool *handover,
int cookie)
int cookie, bool use_atomic)
{
struct nbcon_write_context wctxt = { };
struct nbcon_context *ctxt = &ACCESS_PRIVATE(&wctxt, ctxt);
@@ -1282,19 +1284,29 @@ bool nbcon_legacy_emit_next_record(struct console *con, bool *handover,
*handover = false;
/* Use the same procedure as console_emit_next_record(). */
printk_safe_enter_irqsave(flags);
console_lock_spinning_enable();
stop_critical_timings();
ctxt->console = con;
ctxt->console = con;
ctxt->prio = nbcon_get_default_prio();
if (use_atomic) {
/* Use the same procedure as console_emit_next_record(). */
printk_safe_enter_irqsave(flags);
console_lock_spinning_enable();
stop_critical_timings();
progress = nbcon_atomic_emit_one(&wctxt);
ctxt->prio = nbcon_get_default_prio();
progress = nbcon_emit_one(&wctxt, use_atomic);
start_critical_timings();
*handover = console_lock_spinning_disable_and_check(cookie);
printk_safe_exit_irqrestore(flags);
start_critical_timings();
*handover = console_lock_spinning_disable_and_check(cookie);
printk_safe_exit_irqrestore(flags);
} else {
con->device_lock(con, &flags);
cant_migrate();
ctxt->prio = nbcon_get_default_prio();
progress = nbcon_emit_one(&wctxt, use_atomic);
con->device_unlock(con, flags);
}
return progress;
}
@@ -1536,6 +1548,8 @@ static int __init printk_setup_threads(void)
printk_threads_enabled = true;
for_each_console(con)
nbcon_kthread_create(con);
if (force_printkthreads() && printing_via_unlock)
nbcon_legacy_kthread_create();
console_list_unlock();
return 0;
}
+202 -52
View File
@@ -468,7 +468,7 @@ static DEFINE_MUTEX(syslog_lock);
* present, it is necessary to perform the console lock/unlock dance
* whenever console flushing should occur.
*/
static bool have_legacy_console;
bool have_legacy_console;
/*
* Specifies if an nbcon console is registered. If nbcon consoles are present,
@@ -485,16 +485,11 @@ static bool have_nbcon_console;
*/
bool have_boot_console;
/*
* Specifies if the console lock/unlock dance is needed for console
* printing. If @have_boot_console is true, the nbcon consoles will
* be printed serially along with the legacy consoles because nbcon
* consoles cannot print simultaneously with boot consoles.
*/
#define printing_via_unlock (have_legacy_console || have_boot_console)
#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;
@@ -2364,7 +2359,8 @@ asmlinkage int vprintk_emit(int facility, int level,
const struct dev_printk_info *dev_info,
const char *fmt, va_list args)
{
bool do_trylock_unlock = printing_via_unlock;
bool do_trylock_unlock = printing_via_unlock &&
!force_printkthreads();
int printed_len;
/* Suppress unimportant messages after panic happens */
@@ -2487,6 +2483,14 @@ EXPORT_SYMBOL(_printk);
static bool pr_flush(int timeout_ms, bool reset_on_progress);
static bool __pr_flush(struct console *con, int timeout_ms, bool reset_on_progress);
static struct task_struct *nbcon_legacy_kthread;
static inline void wake_up_legacy_kthread(void)
{
if (nbcon_legacy_kthread)
wake_up_interruptible(&legacy_wait);
}
#else /* CONFIG_PRINTK */
#define printk_time false
@@ -2500,6 +2504,8 @@ static u64 syslog_seq;
static bool pr_flush(int timeout_ms, bool reset_on_progress) { return true; }
static bool __pr_flush(struct console *con, int timeout_ms, bool reset_on_progress) { return true; }
static inline void nbcon_legacy_kthread_create(void) { }
static inline void wake_up_legacy_kthread(void) { }
#endif /* CONFIG_PRINTK */
#ifdef CONFIG_EARLY_PRINTK
@@ -2745,6 +2751,8 @@ void resume_console(void)
}
console_srcu_read_unlock(cookie);
wake_up_legacy_kthread();
pr_flush(1000, true);
}
@@ -2759,7 +2767,8 @@ void resume_console(void)
*/
static int console_cpu_notify(unsigned int cpu)
{
if (!cpuhp_tasks_frozen && printing_via_unlock) {
if (!cpuhp_tasks_frozen && printing_via_unlock &&
!force_printkthreads()) {
/* If trylock fails, someone else is doing the printing */
if (console_trylock())
console_unlock();
@@ -3019,31 +3028,43 @@ static bool console_emit_next_record(struct console *con, bool *handover, int co
con->dropped = 0;
}
/*
* While actively printing out messages, if another printk()
* were to occur on another CPU, it may wait for this one to
* finish. This task can not be preempted if there is a
* waiter waiting to take over.
*
* Interrupts are disabled because the hand over to a waiter
* must not be interrupted until the hand over is completed
* (@console_waiter is cleared).
*/
printk_safe_enter_irqsave(flags);
console_lock_spinning_enable();
/* Do not trace print latency. */
stop_critical_timings();
/* Write everything out to the hardware. */
con->write(con, outbuf, pmsg.outbuf_len);
start_critical_timings();
if (force_printkthreads()) {
/*
* With forced threading this function is either in a thread
* or panic context. So there is no need for concern about
* printk reentrance or handovers.
*/
con->seq = pmsg.seq + 1;
con->write(con, outbuf, pmsg.outbuf_len);
con->seq = pmsg.seq + 1;
} else {
/*
* While actively printing out messages, if another printk()
* were to occur on another CPU, it may wait for this one to
* finish. This task can not be preempted if there is a
* waiter waiting to take over.
*
* Interrupts are disabled because the hand over to a waiter
* must not be interrupted until the hand over is completed
* (@console_waiter is cleared).
*/
printk_safe_enter_irqsave(flags);
console_lock_spinning_enable();
*handover = console_lock_spinning_disable_and_check(cookie);
printk_safe_exit_irqrestore(flags);
/* Do not trace print latency. */
stop_critical_timings();
con->write(con, outbuf, pmsg.outbuf_len);
start_critical_timings();
con->seq = pmsg.seq + 1;
*handover = console_lock_spinning_disable_and_check(cookie);
printk_safe_exit_irqrestore(flags);
}
skip:
return true;
}
@@ -3107,12 +3128,13 @@ static bool console_flush_all(bool do_cond_resched, u64 *next_seq, bool *handove
if ((flags & CON_NBCON) && con->kthread)
continue;
if (!console_is_usable(con, flags, true))
if (!console_is_usable(con, flags, !do_cond_resched))
continue;
any_usable = true;
if (flags & CON_NBCON) {
progress = nbcon_legacy_emit_next_record(con, handover, cookie);
progress = nbcon_legacy_emit_next_record(con, handover, cookie,
!do_cond_resched);
printk_seq = nbcon_seq_read(con);
} else {
progress = console_emit_next_record(con, handover, cookie);
@@ -3151,19 +3173,7 @@ abandon:
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;
@@ -3207,6 +3217,32 @@ void console_unlock(void)
*/
} while (prb_read_valid(prb, next_seq, NULL) && console_trylock());
}
/**
* 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)
{
/*
* Forced threading relies on kthread and atomic consoles for
* printing. It never attempts to print from console_unlock().
*/
if (force_printkthreads()) {
__console_unlock();
return;
}
console_flush_and_unlock();
}
EXPORT_SYMBOL(console_unlock);
/**
@@ -3416,11 +3452,106 @@ void console_start(struct console *console)
if (flags & CON_NBCON)
nbcon_kthread_wake(console);
else
wake_up_legacy_kthread();
__pr_flush(console, 1000, true);
}
EXPORT_SYMBOL(console_start);
#ifdef CONFIG_PRINTK
static bool printer_should_wake(void)
{
bool available = false;
struct console *con;
int cookie;
if (kthread_should_stop())
return true;
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 for legacy consoles,
* unless the nbcon console has no kthread printer.
*/
if ((flags & CON_NBCON) && con->kthread)
continue;
if (!console_is_usable(con, flags, true))
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)) {
available = true;
break;
}
}
console_srcu_read_unlock(cookie);
return available;
}
static int nbcon_legacy_kthread_func(void *unused)
{
int error;
for (;;) {
error = wait_event_interruptible(legacy_wait, printer_should_wake());
if (kthread_should_stop())
break;
if (error)
continue;
console_lock();
console_flush_and_unlock();
}
return 0;
}
void nbcon_legacy_kthread_create(void)
{
struct task_struct *kt;
lockdep_assert_held(&console_mutex);
if (!force_printkthreads())
return;
if (!printk_threads_enabled || nbcon_legacy_kthread)
return;
kt = kthread_run(nbcon_legacy_kthread_func, NULL, "pr/legacy");
if (IS_ERR(kt)) {
pr_err("unable to start legacy printing thread\n");
return;
}
nbcon_legacy_kthread = kt;
/*
* It is important that console printing threads are scheduled
* shortly after a printk call and with generous runtime budgets.
*/
sched_set_normal(nbcon_legacy_kthread, -20);
}
#endif /* CONFIG_PRINTK */
static int __read_mostly keep_bootcon;
static int __init keep_bootcon_setup(char *str)
@@ -3712,6 +3843,7 @@ void register_console(struct console *newcon)
newcon->seq = 0;
} else {
have_legacy_console = true;
nbcon_legacy_kthread_create();
}
if (newcon->flags & CON_BOOT)
@@ -3867,6 +3999,13 @@ static int unregister_console_locked(struct console *console)
nbcon_kthread_create(c);
}
#ifdef CONFIG_PRINTK
if (!printing_via_unlock && nbcon_legacy_kthread) {
kthread_stop(nbcon_legacy_kthread);
nbcon_legacy_kthread = NULL;
}
#endif
return res;
}
@@ -4025,8 +4164,12 @@ static bool __pr_flush(struct console *con, int timeout_ms, bool reset_on_progre
seq = prb_next_reserve_seq(prb);
/* Flush the consoles so that records up to @seq are printed. */
if (printing_via_unlock) {
/*
* Flush the consoles so that records up to @seq are printed.
* Otherwise this function will just wait for the threaded printers
* to print up to @seq.
*/
if (printing_via_unlock && !force_printkthreads()) {
console_lock();
console_unlock();
}
@@ -4140,9 +4283,16 @@ static void wake_up_klogd_work_func(struct irq_work *irq_work)
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_printkthreads()) {
wake_up_legacy_kthread();
} else {
/*
* If trylock fails, some other context
* will do the printing.
*/
if (console_trylock())
console_unlock();
}
}
if (pending & PRINTK_PENDING_WAKEUP)