[PATCH v1] printk: Unconditionally unregister boot consoles when any real console appears

Xiaochun Li posted 1 patch 6 days, 20 hours ago
kernel/printk/printk.c | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
[PATCH v1] printk: Unconditionally unregister boot consoles when any real console appears
Posted by Xiaochun Li 6 days, 20 hours ago
The design of kernel message printing ensures that boot consoles are
temporary and should be unregistered once a real console appears,
preventing duplicate output without any loss of kernel messages.

But currently, when multiple "console=" parameters are specified
(e.g., console=ttyS0 console=ttyS1) together with a boot console
(earlyprintk=ttyS0 and/or earlycon=uart,io,0x3f8), the boot console
remains active indefinitely. As a result, after the real console
active, each kernel message line is printed twice consecutively that
is not as expected.

Currently, register_console() unregisters boot consoles only when the
newly registered console has CON_CONSDEV (i.e., it is the preferred
console). However, when multiple "console=" arguments are given for
the same driver (e.g., 8250), the try_enable_preferred_console()
matches only the first console_cmdline entry and returns immediately.
The enabled real console (ttyS0) is therefore not the preferred one
and does not set CON_CONSDEV, because preferred_console points to the
last entry (ttyS1). Consequently, the boot console is never unregistered.

Fix this issue by removing the CON_CONSDEV requirement and
unregistering all boot consoles whenever any real console
(i.e., any console without CON_BOOT flag) is registered, unless
keep_bootcon or keep option append to earlyprintk is specified.

This simplification relies on the existing printk buffer replay
mechanism: when a real console is registered, its internal sequence
number ensures it will output all messages from the entire kernel
log, including those printed before it was enabled. Therefore,
no messages are lost even if boot consoles are removed early.

Behavior comparison:

Command line: earlyprintk=ttyS0 console=ttyS0 console=ttyS1
                (ttyS1 is preferred)

- Before fix: ttyS0 is enabled but lacks CON_CONSDEV -> the boot console
	is not unregistered.
- After fix: ttyS0 is enabled -> the boot console is unconditionally
	unregistered.

Command line: earlyprintk=ttyS0 console=ttyS1 console=ttyS0
        (ttyS0 is preferred, order swapped)

- Before fix: ttyS1 is enabled but lacks CON_CONSDEV -> the boot
	console is kept.
- After fix:  ttyS1 is enabled -> the boot console is still unregistered.
        (ttyS1 will also replay the logs on printk buffer if COM2
         was configured correctly, boot console on ttyS0 is
         no longer needed.)

Command line: earlyprintk=ttyS0 console=tty0 console=ttyS0 console=ttyS1
        (ttyS1 is the preferred console)

- Before fix: tty0 (VGA) registers first -> the boot console remains.
        ttyS0 (8250) registers later -> the boot console still remains.

- After fix: tty0 (VGA) registers first -> the boot console
	is unregistered.
        ttyS0 registers later -> CON_PRINTBUFFER is set ->
        it replays the entire ring buffer.

Command line: earlyprintk=ttyS0 keep_bootcon console=ttyS0
or Command line: earlyprintk=ttyS0,keep console=ttyS0

- Before and after: the boot consoles remain.

Signed-off-by: Xiaochun Li <lixiaochun@open-hieco.net>
---
 kernel/printk/printk.c | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/kernel/printk/printk.c b/kernel/printk/printk.c
index 0323149548f6..920e27f63003 100644
--- a/kernel/printk/printk.c
+++ b/kernel/printk/printk.c
@@ -4207,8 +4207,7 @@ void register_console(struct console *newcon)
 	 */
 	con_printk(KERN_INFO, newcon, "enabled\n");
 	if (bootcon_registered &&
-	    ((newcon->flags & (CON_CONSDEV | CON_BOOT)) == CON_CONSDEV) &&
-	    !keep_bootcon) {
+	    !(newcon->flags & CON_BOOT) && !keep_bootcon) {
 		struct hlist_node *tmp;
 
 		hlist_for_each_entry_safe(con, tmp, &console_list, node) {
-- 
2.18.2
Re: [PATCH v1] printk: Unconditionally unregister boot consoles when any real console appears
Posted by John Ogness 6 days, 18 hours ago
Hi Xiaochun,

On 2026-05-18, Xiaochun Li <lixiaochun@open-hieco.net> wrote:
> The design of kernel message printing ensures that boot consoles are
> temporary and should be unregistered once a real console appears,
> preventing duplicate output without any loss of kernel messages.
>
> But currently, when multiple "console=" parameters are specified
> (e.g., console=ttyS0 console=ttyS1) together with a boot console
> (earlyprintk=ttyS0 and/or earlycon=uart,io,0x3f8), the boot console
> remains active indefinitely. As a result, after the real console
> active, each kernel message line is printed twice consecutively that
> is not as expected.

Although specifying multiple consoles using the same driver is improper
usage, I would still consider this a bug. Thanks for pointing this out.

(The fact that you cannot specify multiple consoles with the same driver
is a ridiculous historical artifact... but that is another topic.)

> Currently, register_console() unregisters boot consoles only when the
> newly registered console has CON_CONSDEV (i.e., it is the preferred
> console). However, when multiple "console=" arguments are given for
> the same driver (e.g., 8250), the try_enable_preferred_console()
> matches only the first console_cmdline entry and returns immediately.
> The enabled real console (ttyS0) is therefore not the preferred one
> and does not set CON_CONSDEV, because preferred_console points to the
> last entry (ttyS1). Consequently, the boot console is never unregistered.
>
> Fix this issue by removing the CON_CONSDEV requirement and
> unregistering all boot consoles whenever any real console
> (i.e., any console without CON_BOOT flag) is registered

I do not think this is the correct fix. It relies on the buggy
unregister_console_locked() code that even warns:

     * <HISTORICAL>
     * If this isn't the last console and it has CON_CONSDEV set, we
     * need to set it on the next preferred console.
     * </HISTORICAL>
     *
     * The above makes no sense as there is no guarantee that the next
     * console has any device attached. Oh well....

I expect that the correct fix would be to make sure the registered
regular console gets CON_CONSDEV if the preferred console could not be
registered.

Or maybe the unregister_console_locked() should be fixed so that there
is a proper fallback when a CON_CONSDEV console unregisters.

The preferred console code is quite tricky and is even in the process of
being cleaned up. (Although the bug is present after applying the
cleanup series as well.)

John

[0] https://lore.kernel.org/lkml/20260423130015.85175-1-pmladek@suse.com
Re: [PATCH v1] printk: Unconditionally unregister boot consoles when any real console appears
Posted by Petr Mladek 2 days, 14 hours ago
On Mon 2026-05-18 12:22:56, John Ogness wrote:
> Hi Xiaochun,
> 
> On 2026-05-18, Xiaochun Li <lixiaochun@open-hieco.net> wrote:
> > The design of kernel message printing ensures that boot consoles are
> > temporary and should be unregistered once a real console appears,
> > preventing duplicate output without any loss of kernel messages.
> >
> > But currently, when multiple "console=" parameters are specified
> > (e.g., console=ttyS0 console=ttyS1) together with a boot console
> > (earlyprintk=ttyS0 and/or earlycon=uart,io,0x3f8), the boot console
> > remains active indefinitely. As a result, after the real console
> > active, each kernel message line is printed twice consecutively that
> > is not as expected.
> 
> Although specifying multiple consoles using the same driver is improper
> usage, I would still consider this a bug. Thanks for pointing this out.
> 
> (The fact that you cannot specify multiple consoles with the same driver
> is a ridiculous historical artifact... but that is another topic.)

Yeah.

> > Currently, register_console() unregisters boot consoles only when the
> > newly registered console has CON_CONSDEV (i.e., it is the preferred
> > console). However, when multiple "console=" arguments are given for
> > the same driver (e.g., 8250), the try_enable_preferred_console()
> > matches only the first console_cmdline entry and returns immediately.
> > The enabled real console (ttyS0) is therefore not the preferred one
> > and does not set CON_CONSDEV, because preferred_console points to the
> > last entry (ttyS1). Consequently, the boot console is never unregistered.
> >
> > Fix this issue by removing the CON_CONSDEV requirement and
> > unregistering all boot consoles whenever any real console
> > (i.e., any console without CON_BOOT flag) is registered
> 
> I do not think this is the correct fix. It relies on the buggy
> unregister_console_locked() code that even warns:
> 
>      * <HISTORICAL>
>      * If this isn't the last console and it has CON_CONSDEV set, we
>      * need to set it on the next preferred console.
>      * </HISTORICAL>
>      *
>      * The above makes no sense as there is no guarantee that the next
>      * console has any device attached. Oh well....
> 
> I expect that the correct fix would be to make sure the registered
> regular console gets CON_CONSDEV if the preferred console could not be
> registered.

The boot consoles are already automatically removed in
printk_late_init(). But it removes only early consoles which
use a code in the init section.

It does not remove all boot consoles because the real console might
get registered later by a deferred probe.

Idea 1:

It would make sense to remove all boot consoles in printk_later_init()
when there is at least one real (non-boot) console registered.

We could not guarantee that all the boot consoles were replaced by
proper drivers. But we could at least guarantee that some console
will stay and the messages might be visible there.


Idea 2:

We actually know that a boot console was replaced by a proper
one when newcon->match() succeeds in try_enable_preferred_console().
It might be acceptable to remove the boot consoles in this case.

We do not know exactly which boot console was replaced but
we know that at least one was replaced.

Note that .match() callback works only for a subset of early consoles.
I see only univ8250_console_match() and pl011_console_match().
But it should catch "earlycon=uart,io,0x3f8" mentioned as an
example in the original commit message.

> Or maybe the unregister_console_locked() should be fixed so that there
> is a proper fallback when a CON_CONSDEV console unregisters.
>
> The preferred console code is quite tricky and is even in the process of
> being cleaned up. (Although the bug is present after applying the
> cleanup series as well.)

Sigh, CON_CONSDEV is another thing which would deserve a clean up.
The current code is a mess.

The flag should be set only for the driver which gets associated
with /dev/console. But it is not guaranteed.

The driver associated with /dev/console is selected
by console_device(). It is the first driver where c->device()
returns a valid tty_driver.

But CON_CONSDEV could not be assigned correctly in register_console()
because the tty_driver layer gets initialized later.

Historically, CON_CONSDEV is used to order the drivers in
console_list:

   1. CON_CONSDEV is set by try_enable_preferred_console() for
      the preferred console. As a result, it is put first in the
      list in register_console().

   2. register_console()/unregister_console() always sets CON_CONSDEV
      for the first console in the list.

   The first driver has the biggest chance to get assigned to
   /dev/console console_device() function. But it is skipped when
   c->device() either does not exist or returns NULL.

  AFAIK, early console drivers never have con->device() callback
  so they never will get assigned to /dev/console. But we still
  keep them first in the console_list() because it affects
  the order of later added drivers. And we need to keep
  the ordering right to avoid regressions.

My proposal:

We could try to implement the above mentioned "Idea 1" and "Idea 2".
They should fix most of the real life scenarios.

"Idea 1" is even independent on the currently pending cleanup [0].

In the long term, we should stop using CON_CONSDEV for the ordering
in register_console(). Instead, it should get set properly in
console_device(). But it would require another careful code
clean up.

[0] https://lore.kernel.org/lkml/20260423130015.85175-1-pmladek@suse.com

Best Regards,
Petr
Re: [PATCH v1] printk: Unconditionally unregister boot consoles when any real console appears
Posted by Xiaochun Li 3 hours ago
On 5/22/2026 10:46 PM, Petr Mladek wrote:
> On Mon 2026-05-18 12:22:56, John Ogness wrote:
>> Hi Xiaochun,
>>
>> On 2026-05-18, Xiaochun Li <lixiaochun@open-hieco.net> wrote:
>>> The design of kernel message printing ensures that boot consoles are
>>> temporary and should be unregistered once a real console appears,
>>> preventing duplicate output without any loss of kernel messages.
>>>
>>> But currently, when multiple "console=" parameters are specified
>>> (e.g., console=ttyS0 console=ttyS1) together with a boot console
>>> (earlyprintk=ttyS0 and/or earlycon=uart,io,0x3f8), the boot console
>>> remains active indefinitely. As a result, after the real console
>>> active, each kernel message line is printed twice consecutively that
>>> is not as expected.
>>
>> Although specifying multiple consoles using the same driver is improper
>> usage, I would still consider this a bug. Thanks for pointing this out.
>>
>> (The fact that you cannot specify multiple consoles with the same driver
>> is a ridiculous historical artifact... but that is another topic.)
> 
> Yeah.
> 
>>> Currently, register_console() unregisters boot consoles only when the
>>> newly registered console has CON_CONSDEV (i.e., it is the preferred
>>> console). However, when multiple "console=" arguments are given for
>>> the same driver (e.g., 8250), the try_enable_preferred_console()
>>> matches only the first console_cmdline entry and returns immediately.
>>> The enabled real console (ttyS0) is therefore not the preferred one
>>> and does not set CON_CONSDEV, because preferred_console points to the
>>> last entry (ttyS1). Consequently, the boot console is never unregistered.
>>>
>>> Fix this issue by removing the CON_CONSDEV requirement and
>>> unregistering all boot consoles whenever any real console
>>> (i.e., any console without CON_BOOT flag) is registered
>>
>> I do not think this is the correct fix. It relies on the buggy
>> unregister_console_locked() code that even warns:
>>
>>       * <HISTORICAL>
>>       * If this isn't the last console and it has CON_CONSDEV set, we
>>       * need to set it on the next preferred console.
>>       * </HISTORICAL>
>>       *
>>       * The above makes no sense as there is no guarantee that the next
>>       * console has any device attached. Oh well....
>>
>> I expect that the correct fix would be to make sure the registered
>> regular console gets CON_CONSDEV if the preferred console could not be
>> registered.
> 
> The boot consoles are already automatically removed in
> printk_late_init(). But it removes only early consoles which
> use a code in the init section.
> 
> It does not remove all boot consoles because the real console might
> get registered later by a deferred probe.
> 
> Idea 1:
> 
> It would make sense to remove all boot consoles in printk_later_init()
> when there is at least one real (non-boot) console registered.
> 
> We could not guarantee that all the boot consoles were replaced by
> proper drivers. But we could at least guarantee that some console
> will stay and the messages might be visible there.
> 
> 
> Idea 2:
> 
> We actually know that a boot console was replaced by a proper
> one when newcon->match() succeeds in try_enable_preferred_console().
> It might be acceptable to remove the boot consoles in this case.
> 
> We do not know exactly which boot console was replaced but
> we know that at least one was replaced.
> 
> Note that .match() callback works only for a subset of early consoles.
> I see only univ8250_console_match() and pl011_console_match().
> But it should catch "earlycon=uart,io,0x3f8" mentioned as an
> example in the original commit message.
> 
>> Or maybe the unregister_console_locked() should be fixed so that there
>> is a proper fallback when a CON_CONSDEV console unregisters.
>>
>> The preferred console code is quite tricky and is even in the process of
>> being cleaned up. (Although the bug is present after applying the
>> cleanup series as well.)
> 
> Sigh, CON_CONSDEV is another thing which would deserve a clean up.
> The current code is a mess.
> 
> The flag should be set only for the driver which gets associated
> with /dev/console. But it is not guaranteed.
> 
> The driver associated with /dev/console is selected
> by console_device(). It is the first driver where c->device()
> returns a valid tty_driver.
> 
> But CON_CONSDEV could not be assigned correctly in register_console()
> because the tty_driver layer gets initialized later.
> 
> Historically, CON_CONSDEV is used to order the drivers in
> console_list:
> 
>     1. CON_CONSDEV is set by try_enable_preferred_console() for
>        the preferred console. As a result, it is put first in the
>        list in register_console().
> 
>     2. register_console()/unregister_console() always sets CON_CONSDEV
>        for the first console in the list.
> 
>     The first driver has the biggest chance to get assigned to
>     /dev/console console_device() function. But it is skipped when
>     c->device() either does not exist or returns NULL.
> 
>    AFAIK, early console drivers never have con->device() callback
>    so they never will get assigned to /dev/console. But we still
>    keep them first in the console_list() because it affects
>    the order of later added drivers. And we need to keep
>    the ordering right to avoid regressions.
> 
> My proposal:
> 
> We could try to implement the above mentioned "Idea 1" and "Idea 2".
> They should fix most of the real life scenarios.
> 
> "Idea 1" is even independent on the currently pending cleanup [0].
> 
> In the long term, we should stop using CON_CONSDEV for the ordering
> in register_console(). Instead, it should get set properly in
> console_device(). But it would require another careful code
> clean up.
> 

Hi Petr,
Appreciate your clarification and fix proposals, I will make corresponding 
updates, perform system tests and let you know once the the next patch 
version is ready.

Regards,
Xiaochun Li

> [0] https://lore.kernel.org/lkml/20260423130015.85175-1-pmladek@suse.com
> 
> Best Regards,
> Petr


-- 
Best regards,
Xiaochun Li
Re: [PATCH v1] printk: Unconditionally unregister boot consoles when any real console appears
Posted by Xiaochun Li 5 days, 3 hours ago
On 5/18/2026 6:16 PM, John Ogness wrote:
> Hi Xiaochun,
> 
> On 2026-05-18, Xiaochun Li <lixiaochun@open-hieco.net> wrote:
>> The design of kernel message printing ensures that boot consoles are
>> temporary and should be unregistered once a real console appears,
>> preventing duplicate output without any loss of kernel messages.
>>
>> But currently, when multiple "console=" parameters are specified
>> (e.g., console=ttyS0 console=ttyS1) together with a boot console
>> (earlyprintk=ttyS0 and/or earlycon=uart,io,0x3f8), the boot console
>> remains active indefinitely. As a result, after the real console
>> active, each kernel message line is printed twice consecutively that
>> is not as expected.
> 
> Although specifying multiple consoles using the same driver is improper
> usage, I would still consider this a bug. Thanks for pointing this out.
> 
> (The fact that you cannot specify multiple consoles with the same driver
> is a ridiculous historical artifact... but that is another topic.)
> 
>> Currently, register_console() unregisters boot consoles only when the
>> newly registered console has CON_CONSDEV (i.e., it is the preferred
>> console). However, when multiple "console=" arguments are given for
>> the same driver (e.g., 8250), the try_enable_preferred_console()
>> matches only the first console_cmdline entry and returns immediately.
>> The enabled real console (ttyS0) is therefore not the preferred one
>> and does not set CON_CONSDEV, because preferred_console points to the
>> last entry (ttyS1). Consequently, the boot console is never unregistered.
>>
>> Fix this issue by removing the CON_CONSDEV requirement and
>> unregistering all boot consoles whenever any real console
>> (i.e., any console without CON_BOOT flag) is registered
> 
> I do not think this is the correct fix. It relies on the buggy
> unregister_console_locked() code that even warns:
> 
>       * <HISTORICAL>
>       * If this isn't the last console and it has CON_CONSDEV set, we
>       * need to set it on the next preferred console.
>       * </HISTORICAL>
>       *
>       * The above makes no sense as there is no guarantee that the next
>       * console has any device attached. Oh well....
> 
> I expect that the correct fix would be to make sure the registered
> regular console gets CON_CONSDEV if the preferred console could not be
> registered.
> 
> Or maybe the unregister_console_locked() should be fixed so that there
> is a proper fallback when a CON_CONSDEV console unregisters.
> 
> The preferred console code is quite tricky and is even in the process of
> being cleaned up. (Although the bug is present after applying the
> cleanup series as well.)
> 
> John
> 
> [0] https://lore.kernel.org/lkml/20260423130015.85175-1-pmladek@suse.com

Thanks for your prompt reply. I will follow your suggestions to work out a 
proper solution based on the cleanup patch series.

Best regards,
Xiaochun Li