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:
committed by
Kevin Becker
parent
f209365b54
commit
285eff7766
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user