printk: nobkl: Add buffer management

In case of hostile takeovers it must be ensured that the previous
owner cannot scribble over the output buffer of the emergency/panic
context. This is achieved by:

 - Adding a global output buffer instance for early boot (pre per CPU
   data being available).

 - Allocating an output buffer per console for threaded printers once
   printer threads become available.

 - Allocating per CPU output buffers per console for printing from
   all contexts not covered by the other buffers.

 - Choosing the appropriate buffer is handled in the acquire/release
   functions.

The output buffer is wrapped into a separate data structure so other
context related fields can be added in later steps.

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>
This commit is contained in:
Thomas Gleixner
2022-09-11 00:28:04 +02:00
committed by Sebastian Andrzej Siewior
parent d387cf62ad
commit 377c6aa303
4 changed files with 137 additions and 14 deletions
+13
View File
@@ -179,6 +179,7 @@ enum cons_flags {
*
* @locked: Console is locked by a writer
* @unsafe: Console is busy in a non takeover region
* @thread: Current owner is the printk thread
* @cur_prio: The priority of the current output
* @req_prio: The priority of a handover request
* @cpu: The CPU on which the writer runs
@@ -204,6 +205,7 @@ struct cons_state {
struct {
u32 locked : 1;
u32 unsafe : 1;
u32 thread : 1;
u32 cur_prio : 2;
u32 req_prio : 2;
u32 cpu : 18;
@@ -234,6 +236,7 @@ enum cons_prio {
};
struct console;
struct printk_buffers;
/**
* struct cons_context - Context for console acquire/release
@@ -245,6 +248,8 @@ struct console;
* @req_state: The request state for spin and cleanup
* @spinwait_max_us: Limit for spinwait acquire
* @prio: Priority of the context
* @pbufs: Pointer to the text buffer for this context
* @thread: The acquire is printk thread context
* @hostile: Hostile takeover requested. Cleared on normal
* acquire or friendly handover
* @spinwait: Spinwait on acquire if possible
@@ -257,6 +262,8 @@ struct cons_context {
struct cons_state req_state;
unsigned int spinwait_max_us;
enum cons_prio prio;
struct printk_buffers *pbufs;
unsigned int thread : 1;
unsigned int hostile : 1;
unsigned int spinwait : 1;
};
@@ -275,6 +282,8 @@ struct cons_write_context {
bool unsafe;
};
struct cons_context_data;
/**
* struct console - The console descriptor structure
* @name: The name of the console driver
@@ -296,6 +305,8 @@ struct cons_write_context {
* @node: hlist node for the console list
*
* @atomic_state: State array for NOBKL consoles; real and handover
* @thread_pbufs: Pointer to thread private buffer
* @pcpu_data: Pointer to percpu context data
*/
struct console {
char name[16];
@@ -318,6 +329,8 @@ struct console {
/* NOBKL console specific members */
atomic_long_t __private atomic_state[2];
struct printk_buffers *thread_pbufs;
struct cons_context_data __percpu *pcpu_data;
};
#ifdef CONFIG_LOCKDEP
+19 -3
View File
@@ -13,8 +13,13 @@ int devkmsg_sysctl_set_loglvl(struct ctl_table *table, int write,
#define printk_sysctl_init() do { } while (0)
#endif
#ifdef CONFIG_PRINTK
#define con_printk(lvl, con, fmt, ...) \
printk(lvl pr_fmt("%s%sconsole [%s%d] " fmt), \
(con->flags & CON_NO_BKL) ? "" : "legacy ", \
(con->flags & CON_BOOT) ? "boot" : "", \
con->name, con->index, ##__VA_ARGS__)
#ifdef CONFIG_PRINTK
#ifdef CONFIG_PRINTK_CALLER
#define PRINTK_PREFIX_MAX 48
#else
@@ -64,7 +69,8 @@ u16 printk_parse_prefix(const char *text, int *level,
enum printk_info_flags *flags);
void cons_nobkl_cleanup(struct console *con);
void cons_nobkl_init(struct console *con);
bool cons_nobkl_init(struct console *con);
bool cons_alloc_percpu_data(struct console *con);
#else
@@ -81,7 +87,7 @@ void cons_nobkl_init(struct console *con);
#define printk_safe_exit_irqrestore(flags) local_irq_restore(flags)
static inline bool printk_percpu_data_ready(void) { return false; }
static inline void cons_nobkl_init(struct console *con) { }
static inline bool cons_nobkl_init(struct console *con) { return true; }
static inline void cons_nobkl_cleanup(struct console *con) { }
#endif /* CONFIG_PRINTK */
@@ -114,4 +120,14 @@ struct printk_message {
unsigned long dropped;
};
/**
* struct cons_context_data - console context data
* @pbufs: Buffer for storing the text
*
* Used for early boot and for per CPU data.
*/
struct cons_context_data {
struct printk_buffers pbufs;
};
bool other_cpu_in_panic(void);
+16 -10
View File
@@ -457,6 +457,8 @@ static bool have_bkl_console;
*/
bool have_boot_console;
static int unregister_console_locked(struct console *console);
#ifdef CONFIG_PRINTK
DECLARE_WAIT_QUEUE_HEAD(log_wait);
/* All 3 protected by @syslog_lock. */
@@ -1104,7 +1106,19 @@ static inline void log_buf_add_cpu(void) {}
static void __init set_percpu_data_ready(void)
{
struct hlist_node *tmp;
struct console *con;
console_list_lock();
hlist_for_each_entry_safe(con, tmp, &console_list, node) {
if (!cons_alloc_percpu_data(con))
unregister_console_locked(con);
}
__printk_percpu_data_ready = true;
console_list_unlock();
}
static unsigned int __init add_to_rb(struct printk_ringbuffer *rb,
@@ -3345,12 +3359,6 @@ static void try_enable_default_console(struct console *newcon)
newcon->flags |= CON_CONSDEV;
}
#define con_printk(lvl, con, fmt, ...) \
printk(lvl pr_fmt("%s%sconsole [%s%d] " fmt), \
(con->flags & CON_NO_BKL) ? "" : "legacy ", \
(con->flags & CON_BOOT) ? "boot" : "", \
con->name, con->index, ##__VA_ARGS__)
static void console_init_seq(struct console *newcon, bool bootcon_registered)
{
struct console *con;
@@ -3415,8 +3423,6 @@ static void console_init_seq(struct console *newcon, bool bootcon_registered)
#define console_first() \
hlist_entry(console_list.first, struct console, node)
static int unregister_console_locked(struct console *console);
/*
* The console driver calls this routine during kernel initialization
* to register the console printing procedure with printk() and to
@@ -3510,8 +3516,8 @@ void register_console(struct console *newcon)
if (!(newcon->flags & CON_NO_BKL))
have_bkl_console = true;
else
cons_nobkl_init(newcon);
else if (!cons_nobkl_init(newcon))
goto unlock;
if (newcon->flags & CON_BOOT)
have_boot_console = true;
+89 -1
View File
@@ -166,6 +166,47 @@ static inline bool cons_check_panic(void)
return pcpu != PANIC_CPU_INVALID && pcpu != smp_processor_id();
}
static struct cons_context_data early_cons_ctxt_data __initdata;
/**
* cons_context_set_pbufs - Set the output text buffer for the current context
* @ctxt: Pointer to the acquire context
*
* Buffer selection:
* 1) Early boot uses the global (initdata) buffer
* 2) Printer threads use the dynamically allocated per-console buffers
* 3) All other contexts use the per CPU buffers
*
* This guarantees that there is no concurrency on the output records ever.
* Early boot and per CPU nesting is not a problem. The takeover logic
* tells the interrupted context that the buffer has been overwritten.
*
* There are two critical regions that matter:
*
* 1) Context is filling the buffer with a record. After interruption
* it continues to sprintf() the record and before it goes to
* write it out, it checks the state, notices the takeover, discards
* the content and backs out.
*
* 2) Context is in a unsafe critical region in the driver. After
* interruption it might read overwritten data from the output
* buffer. When it leaves the critical region it notices and backs
* out. Hostile takeovers in driver critical regions are best effort
* and there is not much that can be done about that.
*/
static __ref void cons_context_set_pbufs(struct cons_context *ctxt)
{
struct console *con = ctxt->console;
/* Thread context or early boot? */
if (ctxt->thread)
ctxt->pbufs = con->thread_pbufs;
else if (!con->pcpu_data)
ctxt->pbufs = &early_cons_ctxt_data.pbufs;
else
ctxt->pbufs = &(this_cpu_ptr(con->pcpu_data)->pbufs);
}
/**
* cons_cleanup_handover - Cleanup a handover request
* @ctxt: Pointer to acquire context
@@ -501,6 +542,7 @@ again:
}
success:
/* Common updates on success */
cons_context_set_pbufs(ctxt);
return true;
check_hostile:
@@ -623,6 +665,9 @@ static bool cons_release(struct cons_context *ctxt)
{
bool ret = __cons_release(ctxt);
/* Invalidate the buffer pointer. It is no longer valid. */
ctxt->pbufs = NULL;
ctxt->state.atom = 0;
return ret;
}
@@ -643,16 +688,58 @@ bool console_release(struct cons_write_context *wctxt)
}
EXPORT_SYMBOL_GPL(console_release);
/**
* cons_alloc_percpu_data - Allocate percpu data for a console
* @con: Console to allocate for
*
* Returns: True on success. False otherwise and the console cannot be used.
*
* If it is not yet possible to allocate per CPU data, success is returned.
* When per CPU data becomes possible, set_percpu_data_ready() will call
* this function again for all registered consoles.
*/
bool cons_alloc_percpu_data(struct console *con)
{
if (!printk_percpu_data_ready())
return true;
con->pcpu_data = alloc_percpu(typeof(*con->pcpu_data));
if (con->pcpu_data)
return true;
con_printk(KERN_WARNING, con, "failed to allocate percpu buffers\n");
return false;
}
/**
* cons_free_percpu_data - Free percpu data of a console on unregister
* @con: Console to clean up
*/
static void cons_free_percpu_data(struct console *con)
{
if (!con->pcpu_data)
return;
free_percpu(con->pcpu_data);
con->pcpu_data = NULL;
}
/**
* cons_nobkl_init - Initialize the NOBKL console specific data
* @con: Console to initialize
*
* Returns: True on success. False otherwise and the console cannot be used.
*/
void cons_nobkl_init(struct console *con)
bool cons_nobkl_init(struct console *con)
{
struct cons_state state = { };
if (!cons_alloc_percpu_data(con))
return false;
cons_state_set(con, CON_STATE_CUR, &state);
cons_state_set(con, CON_STATE_REQ, &state);
return true;
}
/**
@@ -665,4 +752,5 @@ void cons_nobkl_cleanup(struct console *con)
cons_state_set(con, CON_STATE_CUR, &state);
cons_state_set(con, CON_STATE_REQ, &state);
cons_free_percpu_data(con);
}