[PATCH 1/2] printk: Track command line console positions to fix console order

From: Tony Lindgren
Date: Thu Jun 06 2024 - 07:42:47 EST


Recent changes to allow using DEVNAME:0.0 style console names caused a
regression to the kernel command line handling for the console options.

The last preferred console added gets used for init. This is documented
in the comments for add_preferred_console(). Now the kernel command line
options for console=ttyS0,115200 console=tty0 are wrongly handled and
cause the /dev/console to be associated with ttyS0 instead of tty0.

This happens because we are calling __add_preferred_console() later on
from serial8250_isa_init_ports() after console_setup() and the console
gets treated as the last added preferred console. As the DEVNAME:0.0 style
console device is not known at console_setup() time, and we need to call
__add_preferred_console() later.

Let's fix the issue by reserving a position in console_cmdline for a
deferred console, and then populate the reserved entry before calling
__add_preferred_console().

Note that we now need to check for valid preferred console too in
__add_preferred_console().

Reported-by: Petr Mladek <pmladek@xxxxxxxx>
Link: https://lore.kernel.org/linux-serial/ZlC6_Um4P4b-_WQE@xxxxxxxxxxxxxxx/
Fixes: f03e8c1060f8 ("printk: Save console options for add_preferred_console_match()")
Signed-off-by: Tony Lindgren <tony.lindgren@xxxxxxxxxxxxxxx>
---
kernel/printk/conopt.c | 2 +-
kernel/printk/console_cmdline.h | 6 ++-
kernel/printk/printk.c | 68 ++++++++++++++++++++++++++++++---
3 files changed, 67 insertions(+), 9 deletions(-)

diff --git a/kernel/printk/conopt.c b/kernel/printk/conopt.c
index 9d507bac3657..08dded8fbb3b 100644
--- a/kernel/printk/conopt.c
+++ b/kernel/printk/conopt.c
@@ -140,7 +140,7 @@ int add_preferred_console_match(const char *match, const char *name,
if (con->has_brl_opt)
brl_opt = con->brl_opt;

- console_opt_add_preferred_console(name, idx, con->opt, brl_opt);
+ console_opt_add_preferred_console(match, name, idx, con->opt, brl_opt);

return 0;
}
diff --git a/kernel/printk/console_cmdline.h b/kernel/printk/console_cmdline.h
index a125e0235589..e22021e3e28b 100644
--- a/kernel/printk/console_cmdline.h
+++ b/kernel/printk/console_cmdline.h
@@ -5,13 +5,15 @@
#define MAX_CMDLINECONSOLES 8

int console_opt_save(const char *str, const char *brl_opt);
-int console_opt_add_preferred_console(const char *name, const short idx,
- char *options, char *brl_options);
+int console_opt_add_preferred_console(const char *devname, const char *name,
+ const short idx, char *options,
+ char *brl_options);

struct console_cmdline
{
char name[16]; /* Name of the driver */
int index; /* Minor dev. to use */
+ char devname[32]; /* DEVNAME:0.0 style device name */
bool user_specified; /* Specified by command line vs. platform */
char *options; /* Options for the driver */
#ifdef CONFIG_A11Y_BRAILLE_CONSOLE
diff --git a/kernel/printk/printk.c b/kernel/printk/printk.c
index 420fd310129d..52bdd7dcdb6f 100644
--- a/kernel/printk/printk.c
+++ b/kernel/printk/printk.c
@@ -2426,6 +2426,24 @@ static void set_user_specified(struct console_cmdline *c, bool user_specified)
console_set_on_cmdline = 1;
}

+/* Checks if a console is the last user specified preferred console */
+static bool is_last_user_prefcon(int position)
+{
+ struct console_cmdline *c = console_cmdline;
+ int last_user_specified = -1;
+ int i;
+
+ for (i = 0; i < MAX_CMDLINECONSOLES; i++, c++) {
+ if (!c->name[0] && !c->devname[0])
+ break;
+
+ if (c->user_specified || c->devname[0])
+ last_user_specified = i;
+ }
+
+ return position == last_user_specified;
+}
+
static int __add_preferred_console(const char *name, const short idx, char *options,
char *brl_options, bool user_specified)
{
@@ -2445,10 +2463,10 @@ static int __add_preferred_console(const char *name, const short idx, char *opti
* if we have a slot free.
*/
for (i = 0, c = console_cmdline;
- i < MAX_CMDLINECONSOLES && c->name[0];
+ i < MAX_CMDLINECONSOLES && (c->name[0] || c->devname[0]);
i++, c++) {
if (strcmp(c->name, name) == 0 && c->index == idx) {
- if (!brl_options)
+ if (!brl_options && (!user_specified || is_last_user_prefcon(i)))
preferred_console = i;
set_user_specified(c, user_specified);
return 0;
@@ -2467,6 +2485,25 @@ static int __add_preferred_console(const char *name, const short idx, char *opti
return 0;
}

+/* Reserves a console_cmdline position for a deferred devname console */
+static void reserve_deferred_console(const char *str)
+{
+ struct console_cmdline *c = console_cmdline;
+ size_t namelen;
+ int i;
+
+ namelen = strcspn(str, ",");
+ if (namelen == 0 || namelen >= sizeof(c->devname))
+ return;
+
+ for (i = 0; i < MAX_CMDLINECONSOLES; i++, c++) {
+ if (c->name[0] || c->devname[0])
+ continue;
+ strscpy(c->devname, str, namelen + 1);
+ return;
+ }
+}
+
static int __init console_msg_format_setup(char *str)
{
if (!strcmp(str, "syslog"))
@@ -2508,8 +2545,10 @@ static int __init console_setup(char *str)
console_set_on_cmdline = 1;

/* Don't attempt to parse a DEVNAME:0.0 style console */
- if (strchr(str, ':'))
+ if (strchr(str, ':')) {
+ reserve_deferred_console(str);
return 1;
+ }

/*
* Decode str into name, index, options.
@@ -2542,9 +2581,26 @@ static int __init console_setup(char *str)
__setup("console=", console_setup);

/* Only called from add_preferred_console_match() */
-int console_opt_add_preferred_console(const char *name, const short idx,
- char *options, char *brl_options)
+int console_opt_add_preferred_console(const char *devname, const char *name,
+ const short idx, char *options,
+ char *brl_options)
{
+ struct console_cmdline *c = console_cmdline;
+ int i;
+
+ /* Populate a reserved console based on devname */
+ for (i = 0; i < MAX_CMDLINECONSOLES; i++, c++) {
+ if (!c->name[0] && !strcmp(c->devname, devname)) {
+ strscpy(c->name, name);
+ c->index = idx;
+ c->options = options;
+#ifdef CONFIG_A11Y_BRAILLE_CONSOLE
+ c->brl_options = brl_options;
+#endif
+ break;
+ }
+ }
+
return __add_preferred_console(name, idx, options, brl_options, true);
}

@@ -3333,7 +3389,7 @@ static int try_enable_preferred_console(struct console *newcon,
int i, err;

for (i = 0, c = console_cmdline;
- i < MAX_CMDLINECONSOLES && c->name[0];
+ i < MAX_CMDLINECONSOLES && (c->name[0] || c->devname[0]);
i++, c++) {
if (c->user_specified != user_specified)
continue;
--
2.45.2