Skip to content

Commit

Permalink
serial: Lock console when calling into driver before registration
Browse files Browse the repository at this point in the history
[ Upstream commit 801410b ]

During the handoff from earlycon to the real console driver, we have
two separate drivers operating on the same device concurrently. In the
case of the 8250 driver these concurrent accesses cause problems due
to the driver's use of banked registers, controlled by LCR.DLAB. It is
possible for the setup(), config_port(), pm() and set_mctrl() callbacks
to set DLAB, which can cause the earlycon code that intends to access
TX to instead access DLL, leading to missed output and corruption on
the serial line due to unintended modifications to the baud rate.

In particular, for setup() we have:

univ8250_console_setup()
-> serial8250_console_setup()
-> uart_set_options()
-> serial8250_set_termios()
-> serial8250_do_set_termios()
-> serial8250_do_set_divisor()

For config_port() we have:

serial8250_config_port()
-> autoconfig()

For pm() we have:

serial8250_pm()
-> serial8250_do_pm()
-> serial8250_set_sleep()

For set_mctrl() we have (for some devices):

serial8250_set_mctrl()
-> omap8250_set_mctrl()
-> __omap8250_set_mctrl()

To avoid such problems, let's make it so that the console is locked
during pre-registration calls to these callbacks, which will prevent
the earlycon driver from running concurrently.

Remove the partial solution to this problem in the 8250 driver
that locked the console only during autoconfig_irq(), as this would
result in a deadlock with the new approach. The console continues
to be locked during autoconfig_irq() because it can only be called
through uart_configure_port().

Although this patch introduces more locking than strictly necessary
(and in particular it also locks during the call to rs485_config()
which is not affected by this issue as far as I can tell), it follows
the principle that it is the responsibility of the generic console
code to manage the earlycon handoff by ensuring that earlycon and real
console driver code cannot run concurrently, and not the individual
drivers.

Signed-off-by: Peter Collingbourne <pcc@google.com>
Reviewed-by: John Ogness <john.ogness@linutronix.de>
Link: https://linux-review.googlesource.com/id/I7cf8124dcebf8618e6b2ee543fa5b25532de55d8
Fixes: 1da177e ("Linux-2.6.12-rc2")
Cc: stable@vger.kernel.org
Link: https://lore.kernel.org/r/20240304214350.501253-1-pcc@google.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Signed-off-by: Sasha Levin <sashal@kernel.org>
  • Loading branch information
pcc authored and gregkh committed Apr 3, 2024
1 parent 5cce6e2 commit 23174ac
Show file tree
Hide file tree
Showing 3 changed files with 30 additions and 9 deletions.
6 changes: 0 additions & 6 deletions drivers/tty/serial/8250/8250_port.c
Expand Up @@ -1329,9 +1329,6 @@ static void autoconfig_irq(struct uart_8250_port *up)
inb_p(ICP);
}

if (uart_console(port))
console_lock();

/* forget possible initially masked and pending IRQ */
probe_irq_off(probe_irq_on());
save_mcr = serial8250_in_MCR(up);
Expand Down Expand Up @@ -1371,9 +1368,6 @@ static void autoconfig_irq(struct uart_8250_port *up)
if (port->flags & UPF_FOURPORT)
outb_p(save_ICP, ICP);

if (uart_console(port))
console_unlock();

port->irq = (irq > 0) ? irq : 0;
}

Expand Down
12 changes: 12 additions & 0 deletions drivers/tty/serial/serial_core.c
Expand Up @@ -2603,14 +2603,23 @@ uart_configure_port(struct uart_driver *drv, struct uart_state *state,
port->type = PORT_UNKNOWN;
flags |= UART_CONFIG_TYPE;
}
/* Synchronize with possible boot console. */
if (uart_console(port))
console_lock();
port->ops->config_port(port, flags);
if (uart_console(port))
console_unlock();
}

if (port->type != PORT_UNKNOWN) {
unsigned long flags;

uart_report_port(drv, port);

/* Synchronize with possible boot console. */
if (uart_console(port))
console_lock();

/* Power up port for set_mctrl() */
uart_change_pm(state, UART_PM_STATE_ON);

Expand All @@ -2627,6 +2636,9 @@ uart_configure_port(struct uart_driver *drv, struct uart_state *state,

uart_rs485_config(port);

if (uart_console(port))
console_unlock();

/*
* If this driver supports console, and it hasn't been
* successfully registered yet, try to re-register it.
Expand Down
21 changes: 18 additions & 3 deletions kernel/printk/printk.c
Expand Up @@ -3295,6 +3295,21 @@ static int __init keep_bootcon_setup(char *str)

early_param("keep_bootcon", keep_bootcon_setup);

static int console_call_setup(struct console *newcon, char *options)
{
int err;

if (!newcon->setup)
return 0;

/* Synchronize with possible boot console. */
console_lock();
err = newcon->setup(newcon, options);
console_unlock();

return err;
}

/*
* This is called by register_console() to try to match
* the newly registered console with any of the ones selected
Expand Down Expand Up @@ -3330,8 +3345,8 @@ static int try_enable_preferred_console(struct console *newcon,
if (_braille_register_console(newcon, c))
return 0;

if (newcon->setup &&
(err = newcon->setup(newcon, c->options)) != 0)
err = console_call_setup(newcon, c->options);
if (err)
return err;
}
newcon->flags |= CON_ENABLED;
Expand All @@ -3357,7 +3372,7 @@ static void try_enable_default_console(struct console *newcon)
if (newcon->index < 0)
newcon->index = 0;

if (newcon->setup && newcon->setup(newcon, NULL) != 0)
if (console_call_setup(newcon, NULL) != 0)
return;

newcon->flags |= CON_ENABLED;
Expand Down

0 comments on commit 23174ac

Please sign in to comment.