printk/console: Clean up boot console handling in register_console()
The variable @bcon has two meanings. It is used several times for iterating the list of registered consoles. In the meantime, it holds the information whether a boot console is first in @console_drivers list. The information about the 1st console driver used to be important for the decision whether to install the new console by default or not. It allowed to re-evaluate the variable @need_default_console when a real console with tty binding has been unregistered in the meantime. The decision about the default console is not longer affected by @bcon variable. The current code checks whether the first driver is real and has tty binding directly. The information about the first console is still used for two more decisions: 1. It prevents duplicate output on non-boot consoles with CON_CONSDEV flag set. 2. Early/boot consoles are unregistered when a real console with CON_CONSDEV is registered and @keep_bootcon is not set. The behavior in the real life is far from obvious. @bcon is set according to the first console @console_drivers list. But the first position in the list is special: 1. Consoles with CON_CONSDEV flag are put at the beginning of the list. It is either the preferred console or any console with tty binding registered by default. 2. Another console might become the first in the list when the first console in the list is unregistered. It might happen either explicitly or automatically when boot consoles are unregistered. There is one more important rule: + Boot consoles can't be registered when any real console is already registered. It is a puzzle. The main complication is the dependency on the first position is the list and the complicated rules around it. Let's try to make it easier: 1. Add variable @bootcon_enabled and set it by iterating all registered consoles. The variable has obvious meaning and more predictable behavior. Any speed optimization and other tricks are not worth it. 2. Use a generic name for the variable that is used to iterate the list on registered console drivers. Behavior change: No, maybe surprisingly, there is _no_ behavior change! Let's provide the proof by contradiction. Both operations, duplicate output prevention and boot consoles removal, are done only when the newly added console has CON_CONSDEV flag set. The behavior would change when the new @bootcon_enabled has different value than the original @bcon. By other words, the behavior would change when the following conditions are true: + a console with CON_CONSDEV flag is added + a real (non-boot) console is the first in the list + a boot console is later in the list Now, a real console might be first in the list only when: + It was the first registered console. In this case, there can't be any boot console because any later ones were rejected. + It was put at the first position because it had CON_CONSDEV flag set. It was either the preferred console or it was a console with tty binding registered by default. We are interested only in a real consoles here. And real console with tty binding fulfills conditions of the default console. Now, there is always only one console that is either preferred or fulfills conditions of the default console. It can't be already in the list and being registered at the same time. As a result, the above three conditions could newer be "true" at the same time. Therefore the behavior can't change. Final dilemma: OK, the new code has the same behavior. But is the change in the right direction? What if the handling of @console_drivers is updated in the future? OK, let's look at it from another angle: 1. The ordering of @console_drivers list is important only in console_device() function. The first console driver with tty binding gets associated with /dev/console. 2. CON_CONSDEV flag is shown in /proc/consoles. And it should be set for the driver that is returned by console_device(). 3. A boot console is removed and the duplicated output is prevented when the real console with CON_CONSDEV flag is registered. Now, in the ideal world: + The driver associated with /dev/console should be either a console preferred via the command line, device tree, or SPCR. Or it should be the first real console with tty binding registered by default. + The code should match the related boot and real console drivers. It should unregister only the obsolete boot driver. And the duplicated output should be prevented only on the related real driver. It is clear that it is not guaranteed by the current code. Instead, the current code looks like a maze of heuristics that try to achieve the above. It is result of adding several features over last few decades. For example, a possibility to register more consoles, unregister consoles, boot consoles, consoles without tty binding, device tree, SPCR, braille consoles. Anyway, there is no reason why the decision, about removing boot consoles and preventing duplicated output, should depend on the first console in the list. The current code does the decisions primary by CON_CONSDEV flag that is used for the preferred console. It looks like a good compromise. And the change seems to be in the right direction. Signed-off-by: Petr Mladek <pmladek@suse.com> Link: https://lore.kernel.org/r/20211122132649.12737-6-pmladek@suse.com
This commit is contained in:
parent
4f54693925
commit
5e8ba485b2
|
@ -2943,31 +2943,30 @@ static void try_enable_default_console(struct console *newcon)
|
|||
*/
|
||||
void register_console(struct console *newcon)
|
||||
{
|
||||
struct console *bcon = NULL;
|
||||
struct console *con;
|
||||
bool bootcon_enabled = false;
|
||||
bool realcon_enabled = false;
|
||||
int err;
|
||||
|
||||
for_each_console(bcon) {
|
||||
if (WARN(bcon == newcon, "console '%s%d' already registered\n",
|
||||
bcon->name, bcon->index))
|
||||
for_each_console(con) {
|
||||
if (WARN(con == newcon, "console '%s%d' already registered\n",
|
||||
con->name, con->index))
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* before we register a new CON_BOOT console, make sure we don't
|
||||
* already have a valid console
|
||||
*/
|
||||
if (newcon->flags & CON_BOOT) {
|
||||
for_each_console(bcon) {
|
||||
if (!(bcon->flags & CON_BOOT)) {
|
||||
for_each_console(con) {
|
||||
if (con->flags & CON_BOOT)
|
||||
bootcon_enabled = true;
|
||||
else
|
||||
realcon_enabled = true;
|
||||
}
|
||||
|
||||
/* Do not register boot consoles when there already is a real one. */
|
||||
if (newcon->flags & CON_BOOT && realcon_enabled) {
|
||||
pr_info("Too late to register bootconsole %s%d\n",
|
||||
newcon->name, newcon->index);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (console_drivers && console_drivers->flags & CON_BOOT)
|
||||
bcon = console_drivers;
|
||||
|
||||
/*
|
||||
* See if we want to enable this console driver by default.
|
||||
|
@ -3005,8 +3004,10 @@ void register_console(struct console *newcon)
|
|||
* the real console are the same physical device, it's annoying to
|
||||
* see the beginning boot messages twice
|
||||
*/
|
||||
if (bcon && ((newcon->flags & (CON_CONSDEV | CON_BOOT)) == CON_CONSDEV))
|
||||
if (bootcon_enabled &&
|
||||
((newcon->flags & (CON_CONSDEV | CON_BOOT)) == CON_CONSDEV)) {
|
||||
newcon->flags &= ~CON_PRINTBUFFER;
|
||||
}
|
||||
|
||||
/*
|
||||
* Put this console in the list - keep the
|
||||
|
@ -3062,15 +3063,15 @@ void register_console(struct console *newcon)
|
|||
pr_info("%sconsole [%s%d] enabled\n",
|
||||
(newcon->flags & CON_BOOT) ? "boot" : "" ,
|
||||
newcon->name, newcon->index);
|
||||
if (bcon &&
|
||||
if (bootcon_enabled &&
|
||||
((newcon->flags & (CON_CONSDEV | CON_BOOT)) == CON_CONSDEV) &&
|
||||
!keep_bootcon) {
|
||||
/* We need to iterate through all boot consoles, to make
|
||||
* sure we print everything out, before we unregister them.
|
||||
*/
|
||||
for_each_console(bcon)
|
||||
if (bcon->flags & CON_BOOT)
|
||||
unregister_console(bcon);
|
||||
for_each_console(con)
|
||||
if (con->flags & CON_BOOT)
|
||||
unregister_console(con);
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL(register_console);
|
||||
|
|
Loading…
Reference in New Issue