From 64ca752dcbc018054bfea53b784d4c85d3ec896c Mon Sep 17 00:00:00 2001 From: Sergey Senozhatsky Date: Sat, 21 Jan 2017 19:47:29 +0900 Subject: [PATCH 1/5] printk: use console_trylock() in console_cpu_notify() There is no need to always call blocking console_lock() in console_cpu_notify(), it's quite possible that console_sem can be locked by other CPU on the system, either already printing or soon to begin printing the messages. console_lock() in this case can simply block CPU hotplug for unknown period of time (console_unlock() is time unbound). Not that hotplug is very fast, but still, with other CPUs being online and doing printk() console_cpu_notify() can stuck. Use console_trylock() instead and opt-out if console_sem is already acquired from another CPU, since that CPU will do the printing for us. Link: http://lkml.kernel.org/r/20170121104729.8585-1-sergey.senozhatsky@gmail.com Cc: Steven Rostedt Cc: Andrew Morton Cc: Thomas Gleixner Cc: Sebastian Andrzej Siewior Cc: Ingo Molnar Cc: linux-kernel@vger.kernel.org Signed-off-by: Sergey Senozhatsky Signed-off-by: Petr Mladek --- kernel/printk/printk.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/kernel/printk/printk.c b/kernel/printk/printk.c index 34da86e73d00..94e2b104cdaa 100644 --- a/kernel/printk/printk.c +++ b/kernel/printk/printk.c @@ -2028,15 +2028,16 @@ void resume_console(void) * @cpu: unused * * If printk() is called from a CPU that is not online yet, the messages - * will be spooled but will not show up on the console. This function is - * called when a new CPU comes online (or fails to come up), and ensures - * that any such output gets printed. + * will be printed on the console only if there are CON_ANYTIME consoles. + * This function is called when a new CPU comes online (or fails to come + * up) or goes offline. */ static int console_cpu_notify(unsigned int cpu) { if (!cpuhp_tasks_frozen) { - console_lock(); - console_unlock(); + /* If trylock fails, someone else is doing the printing */ + if (console_trylock()) + console_unlock(); } return 0; } From 257ab443118bffc7fdcef38f49cf59be68a3e362 Mon Sep 17 00:00:00 2001 From: Petr Mladek Date: Fri, 24 Mar 2017 17:14:05 +0100 Subject: [PATCH 2/5] printk: Correctly handle preemption in console_unlock() Some console drivers code calls console_conditional_schedule() that looks at @console_may_schedule. The value must be cleared when the drivers are called from console_unlock() with interrupts disabled. But rescheduling is fine when the same code is called, for example, from tty operations where the console semaphore is taken via console_lock(). This is why @console_may_schedule is cleared before calling console drivers. The original value is stored to decide if we could sleep between lines. Now, @console_may_schedule is not cleared when we call console_trylock() and jump back to the "again" goto label. This has become a problem, since the commit 6b97a20d3a7909daa066 ("printk: set may_schedule for some of console_trylock() callers"). @console_may_schedule might get enabled now. There is also the opposite problem. console_lock() can be called only from preemptive context. It can always enable scheduling in the console code. But console_trylock() is not able to detect it when CONFIG_PREEMPT_COUNT is disabled. Therefore we should use the original @console_may_schedule value after re-acquiring the console semaphore in console_unlock(). This patch solves both problems by moving the "again" goto label. Alternative solution was to clear and restore the value around call_console_drivers(). Then console_conditional_schedule() could be used also inside console_unlock(). But there was a potential race with console_flush_on_panic() as reported by Sergey Senozhatsky. That function should be called only where there is only one CPU and with interrupts disabled. But better be on the safe side because stopping CPUs might fail. Fixes: 6b97a20d3a7909 ("printk: set may_schedule for some of console_trylock() callers") Link: http://lkml.kernel.org/r/1490372045-22288-1-git-send-email-pmladek@suse.com Suggested-by: Tetsuo Handa Cc: Steven Rostedt Cc: Peter Zijlstra Cc: Andrew Morton Cc: Greg Kroah-Hartman Cc: Jiri Slaby Cc: linux-fbdev@vger.kernel.org Cc: linux-kernel@vger.kernel.org Reviewed-by: Sergey Senozhatsky Signed-off-by: Petr Mladek --- kernel/printk/printk.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/kernel/printk/printk.c b/kernel/printk/printk.c index 94e2b104cdaa..1252068300e5 100644 --- a/kernel/printk/printk.c +++ b/kernel/printk/printk.c @@ -2159,7 +2159,7 @@ void console_unlock(void) } /* - * Console drivers are called under logbuf_lock, so + * Console drivers are called with interrupts disabled, so * @console_may_schedule should be cleared before; however, we may * end up dumping a lot of lines, for example, if called from * console registration path, and should invoke cond_resched() @@ -2167,11 +2167,15 @@ void console_unlock(void) * scheduling stall on a slow console leading to RCU stall and * softlockup warnings which exacerbate the issue with more * messages practically incapacitating the system. + * + * console_trylock() is not able to detect the preemptive + * context reliably. Therefore the value must be stored before + * and cleared after the the "again" goto label. */ do_cond_resched = console_may_schedule; +again: console_may_schedule = 0; -again: /* * We released the console_sem lock, so we need to recheck if * cpu is online and (if not) is there at least one CON_ANYTIME From b077bafa2f3848ddfcef2ef3a4a61a867f9113b5 Mon Sep 17 00:00:00 2001 From: Aleksey Makarov Date: Wed, 15 Mar 2017 13:28:50 +0300 Subject: [PATCH 3/5] printk: fix name/type/scope of preferred_console var The variable preferred_console is used only inside register_console() and its semantics is boolean. It is negative when no console has been made preferred. Make it static bool and rename to has_preferred. Renaming was suggested by Peter Hurley Link: http://lkml.kernel.org/r/20170315102854.1763-2-aleksey.makarov@linaro.org Cc: Sudeep Holla Cc: Greg Kroah-Hartman Cc: Peter Hurley Cc: Jiri Slaby Cc: Robin Murphy Cc: "Nair, Jayachandran" Cc: linux-serial@vger.kernel.org Cc: linux-kernel@vger.kernel.org Signed-off-by: Aleksey Makarov Reviewed-by: Steven Rostedt (VMware) Reviewed-by: Sergey Senozhatsky Signed-off-by: Petr Mladek --- kernel/printk/printk.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/kernel/printk/printk.c b/kernel/printk/printk.c index 1252068300e5..1afc2d69b21f 100644 --- a/kernel/printk/printk.c +++ b/kernel/printk/printk.c @@ -268,7 +268,6 @@ static struct console *exclusive_console; static struct console_cmdline console_cmdline[MAX_CMDLINECONSOLES]; static int selected_console = -1; -static int preferred_console = -1; int console_set_on_cmdline; EXPORT_SYMBOL(console_set_on_cmdline); @@ -2411,6 +2410,7 @@ void register_console(struct console *newcon) unsigned long flags; struct console *bcon = NULL; struct console_cmdline *c; + static bool has_preferred; if (console_drivers) for_each_console(bcon) @@ -2437,15 +2437,15 @@ void register_console(struct console *newcon) if (console_drivers && console_drivers->flags & CON_BOOT) bcon = console_drivers; - if (preferred_console < 0 || bcon || !console_drivers) - preferred_console = selected_console; + if (!has_preferred || bcon || !console_drivers) + has_preferred = selected_console >= 0; /* * See if we want to use this console driver. If we * didn't select a console we take the first one * that registers here. */ - if (preferred_console < 0) { + if (!has_preferred) { if (newcon->index < 0) newcon->index = 0; if (newcon->setup == NULL || @@ -2453,7 +2453,7 @@ void register_console(struct console *newcon) newcon->flags |= CON_ENABLED; if (newcon->device) { newcon->flags |= CON_CONSDEV; - preferred_console = 0; + has_preferred = true; } } } @@ -2488,7 +2488,7 @@ void register_console(struct console *newcon) newcon->flags |= CON_ENABLED; if (i == selected_console) { newcon->flags |= CON_CONSDEV; - preferred_console = selected_console; + has_preferred = true; } break; } From ad86ee2b8a47590f62a4f3bc1d25dc126d121cb9 Mon Sep 17 00:00:00 2001 From: Aleksey Makarov Date: Wed, 15 Mar 2017 13:28:51 +0300 Subject: [PATCH 4/5] printk: rename selected_console -> preferred_console The variable selected_console is set in __add_preferred_console() to point to the last console parameter that was added to the console_cmdline array. Rename it to preferred_console so that the name reflects the usage. Petr Mladek: "[..] the selected_console/preferred_console value is used to keep the console first in the console_drivers list. IMHO, the main effect is that each line will first appear on this console, see call_console_drivers(). But the message will still appear also on all other enabled consoles. From this point, the name "preferred" sounds better to me. More consoles are selected (enabled) and only one is preferred (first)." Link: http://lkml.kernel.org/r/20170315102854.1763-3-aleksey.makarov@linaro.org Cc: Sudeep Holla Cc: Greg Kroah-Hartman Cc: Jiri Slaby Cc: Robin Murphy Cc: "Nair, Jayachandran" Cc: linux-serial@vger.kernel.org Cc: linux-kernel@vger.kernel.org Signed-off-by: Aleksey Makarov Suggested-by: Peter Hurley Acked-by: Steven Rostedt (VMware) Reviewed-by: Sergey Senozhatsky Signed-off-by: Petr Mladek --- kernel/printk/printk.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/kernel/printk/printk.c b/kernel/printk/printk.c index 1afc2d69b21f..82927ca04849 100644 --- a/kernel/printk/printk.c +++ b/kernel/printk/printk.c @@ -267,7 +267,7 @@ static struct console *exclusive_console; static struct console_cmdline console_cmdline[MAX_CMDLINECONSOLES]; -static int selected_console = -1; +static int preferred_console = -1; int console_set_on_cmdline; EXPORT_SYMBOL(console_set_on_cmdline); @@ -1907,14 +1907,14 @@ static int __add_preferred_console(char *name, int idx, char *options, i++, c++) { if (strcmp(c->name, name) == 0 && c->index == idx) { if (!brl_options) - selected_console = i; + preferred_console = i; return 0; } } if (i == MAX_CMDLINECONSOLES) return -E2BIG; if (!brl_options) - selected_console = i; + preferred_console = i; strlcpy(c->name, name, sizeof(c->name)); c->options = options; braille_set_options(c, brl_options); @@ -2438,7 +2438,7 @@ void register_console(struct console *newcon) bcon = console_drivers; if (!has_preferred || bcon || !console_drivers) - has_preferred = selected_console >= 0; + has_preferred = preferred_console >= 0; /* * See if we want to use this console driver. If we @@ -2486,7 +2486,7 @@ void register_console(struct console *newcon) } newcon->flags |= CON_ENABLED; - if (i == selected_console) { + if (i == preferred_console) { newcon->flags |= CON_CONSDEV; has_preferred = true; } From cf39bf58afdaabc0b86f141630fb3fd18190294e Mon Sep 17 00:00:00 2001 From: Aleksey Makarov Date: Wed, 5 Apr 2017 23:20:00 +0300 Subject: [PATCH 5/5] printk: fix double printing with earlycon If a console was specified by ACPI SPCR table _and_ command line parameters like "console=ttyAMA0" _and_ "earlycon" were specified, then log messages appear twice. The root cause is that the code traverses the list of specified consoles (the `console_cmdline` array) and stops at the first match. But it may happen that the same console is referred by the elements of this array twice: pl011,mmio,0x87e024000000,115200 -- from SPCR ttyAMA0 -- from command line but in this case `preferred_console` points to the second entry and the flag CON_CONSDEV is not set, so bootconsole is not deregistered. To fix that, introduce an invariant "The last non-braille console is always the preferred one" on the entries of the console_cmdline array. Then traverse it in reverse order to be sure that if the console is preferred then it will be the first matching entry. Introduce variable console_cmdline_cnt that keeps the number of elements of the console_cmdline array (Petr Mladek). It helps to get rid of the loop that searches for the end of this array. Link: http://lkml.kernel.org/r/20170405202006.18234-1-aleksey.makarov@linaro.org Cc: Steven Rostedt Cc: Greg Kroah-Hartman Cc: Peter Hurley Cc: Jiri Slaby Cc: Robin Murphy Cc: "Nair, Jayachandran" Cc: linux-serial@vger.kernel.org Cc: linux-kernel@vger.kernel.org Signed-off-by: Aleksey Makarov Reported-by: Sudeep Holla Acked-by: Sergey Senozhatsky Signed-off-by: Petr Mladek --- kernel/printk/printk.c | 46 +++++++++++++++++++++++++++++++++--------- 1 file changed, 36 insertions(+), 10 deletions(-) diff --git a/kernel/printk/printk.c b/kernel/printk/printk.c index 82927ca04849..06051926dfde 100644 --- a/kernel/printk/printk.c +++ b/kernel/printk/printk.c @@ -266,6 +266,7 @@ static struct console *exclusive_console; #define MAX_CMDLINECONSOLES 8 static struct console_cmdline console_cmdline[MAX_CMDLINECONSOLES]; +static int console_cmdline_cnt; static int preferred_console = -1; int console_set_on_cmdline; @@ -1902,12 +1903,25 @@ static int __add_preferred_console(char *name, int idx, char *options, * See if this tty is not yet registered, and * if we have a slot free. */ - for (i = 0, c = console_cmdline; - i < MAX_CMDLINECONSOLES && c->name[0]; - i++, c++) { + for (i = 0, c = console_cmdline; i < console_cmdline_cnt; i++, c++) { if (strcmp(c->name, name) == 0 && c->index == idx) { - if (!brl_options) - preferred_console = i; + if (brl_options) + return 0; + + /* + * Maintain an invariant that will help to find if + * the matching console is preferred, see + * register_console(): + * + * The last non-braille console is always + * the preferred one. + */ + if (i != console_cmdline_cnt - 1) + swap(console_cmdline[i], + console_cmdline[console_cmdline_cnt - 1]); + + preferred_console = console_cmdline_cnt - 1; + return 0; } } @@ -1920,6 +1934,7 @@ static int __add_preferred_console(char *name, int idx, char *options, braille_set_options(c, brl_options); c->index = idx; + console_cmdline_cnt++; return 0; } /* @@ -2459,12 +2474,23 @@ void register_console(struct console *newcon) } /* - * See if this console matches one we selected on - * the command line. + * See if this console matches one we selected on the command line. + * + * There may be several entries in the console_cmdline array matching + * with the same console, one with newcon->match(), another by + * name/index: + * + * pl011,mmio,0x87e024000000,115200 -- added from SPCR + * ttyAMA0 -- added from command line + * + * Traverse the console_cmdline array in reverse order to be + * sure that if this console is preferred then it will be the first + * matching entry. We use the invariant that is maintained in + * __add_preferred_console(). */ - for (i = 0, c = console_cmdline; - i < MAX_CMDLINECONSOLES && c->name[0]; - i++, c++) { + for (i = console_cmdline_cnt - 1; i >= 0; i--) { + c = console_cmdline + i; + if (!newcon->match || newcon->match(newcon, c->name, c->index, c->options) != 0) { /* default matching */