linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH] serial: Lock console when calling into driver before registration
@ 2024-02-22 19:23 Peter Collingbourne
  2024-02-26 11:36 ` John Ogness
  0 siblings, 1 reply; 2+ messages in thread
From: Peter Collingbourne @ 2024-02-22 19:23 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Jiri Slaby, Petr Mladek, Steven Rostedt,
	John Ogness, Sergey Senozhatsky
  Cc: Peter Collingbourne, linux-kernel, linux-serial, stable

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>
Link: https://linux-review.googlesource.com/id/I7cf8124dcebf8618e6b2ee543fa5b25532de55d8
Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Cc: stable@vger.kernel.org
---
 drivers/tty/serial/8250/8250_port.c |  6 ------
 drivers/tty/serial/serial_core.c    | 10 ++++++++++
 kernel/printk/printk.c              | 20 +++++++++++++++++---
 3 files changed, 27 insertions(+), 9 deletions(-)

diff --git a/drivers/tty/serial/8250/8250_port.c b/drivers/tty/serial/8250/8250_port.c
index 8ca061d3bbb9..1d65055dde27 100644
--- a/drivers/tty/serial/8250/8250_port.c
+++ b/drivers/tty/serial/8250/8250_port.c
@@ -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);
@@ -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;
 }
 
diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c
index d6a58a9e072a..128aa0e0ae24 100644
--- a/drivers/tty/serial/serial_core.c
+++ b/drivers/tty/serial/serial_core.c
@@ -2608,7 +2608,11 @@ uart_configure_port(struct uart_driver *drv, struct uart_state *state,
 			port->type = PORT_UNKNOWN;
 			flags |= UART_CONFIG_TYPE;
 		}
+		if (uart_console(port))
+			console_lock();
 		port->ops->config_port(port, flags);
+		if (uart_console(port))
+			console_unlock();
 	}
 
 	if (port->type != PORT_UNKNOWN) {
@@ -2616,6 +2620,9 @@ uart_configure_port(struct uart_driver *drv, struct uart_state *state,
 
 		uart_report_port(drv, port);
 
+		if (uart_console(port))
+			console_lock();
+
 		/* Power up port for set_mctrl() */
 		uart_change_pm(state, UART_PM_STATE_ON);
 
@@ -2632,6 +2639,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.
diff --git a/kernel/printk/printk.c b/kernel/printk/printk.c
index f2444b581e16..db69545e6250 100644
--- a/kernel/printk/printk.c
+++ b/kernel/printk/printk.c
@@ -3263,6 +3263,20 @@ 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;
+
+	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
@@ -3298,8 +3312,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 != 0)
 				return err;
 		}
 		newcon->flags |= CON_ENABLED;
@@ -3325,7 +3339,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;
-- 
2.44.0.rc1.240.g4c46232300-goog


^ permalink raw reply related	[flat|nested] 2+ messages in thread

* Re: [PATCH] serial: Lock console when calling into driver before registration
  2024-02-22 19:23 [PATCH] serial: Lock console when calling into driver before registration Peter Collingbourne
@ 2024-02-26 11:36 ` John Ogness
  0 siblings, 0 replies; 2+ messages in thread
From: John Ogness @ 2024-02-26 11:36 UTC (permalink / raw)
  To: Peter Collingbourne, Greg Kroah-Hartman, Jiri Slaby, Petr Mladek,
	Steven Rostedt, Sergey Senozhatsky
  Cc: Peter Collingbourne, linux-kernel, linux-serial, stable

On 2024-02-22, Peter Collingbourne <pcc@google.com> wrote:
> 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.

Even after the current atomic/threaded print rework is completed, the
console lock will be used to synchronize boot consoles with non-boot
consoles. I am fine with this solution.

Comments below...

> 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>
> Link: https://linux-review.googlesource.com/id/I7cf8124dcebf8618e6b2ee543fa5b25532de55d8
> Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
> Cc: stable@vger.kernel.org
> ---
>  drivers/tty/serial/8250/8250_port.c |  6 ------
>  drivers/tty/serial/serial_core.c    | 10 ++++++++++
>  kernel/printk/printk.c              | 20 +++++++++++++++++---
>  3 files changed, 27 insertions(+), 9 deletions(-)
>
> diff --git a/drivers/tty/serial/8250/8250_port.c b/drivers/tty/serial/8250/8250_port.c
> index 8ca061d3bbb9..1d65055dde27 100644
> --- a/drivers/tty/serial/8250/8250_port.c
> +++ b/drivers/tty/serial/8250/8250_port.c
> @@ -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);
> @@ -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;
>  }
>  
> diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c
> index d6a58a9e072a..128aa0e0ae24 100644
> --- a/drivers/tty/serial/serial_core.c
> +++ b/drivers/tty/serial/serial_core.c
> @@ -2608,7 +2608,11 @@ uart_configure_port(struct uart_driver *drv, struct uart_state *state,
>  			port->type = PORT_UNKNOWN;
>  			flags |= UART_CONFIG_TYPE;
>  		}

It would be nice to add a comment here mentioning why the console_lock
is taken. Even if it is something brief like:

     /* Sychronize 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) {
> @@ -2616,6 +2620,9 @@ uart_configure_port(struct uart_driver *drv, struct uart_state *state,
>  
>  		uart_report_port(drv, port);
>  

Also, here a brief comment.

> +		if (uart_console(port))
> +			console_lock();
> +
>  		/* Power up port for set_mctrl() */
>  		uart_change_pm(state, UART_PM_STATE_ON);
>  
> @@ -2632,6 +2639,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.
> diff --git a/kernel/printk/printk.c b/kernel/printk/printk.c
> index f2444b581e16..db69545e6250 100644
> --- a/kernel/printk/printk.c
> +++ b/kernel/printk/printk.c
> @@ -3263,6 +3263,20 @@ static int __init keep_bootcon_setup(char *str)
>  
>  early_param("keep_bootcon", keep_bootcon_setup);
>  

And here. Maybe slightly more verbose since there is an entire wrapper
function created for the purpose.

> +static int console_call_setup(struct console *newcon, char *options)
> +{
> +	int err;
> +
> +	if (!newcon->setup)
> +		return 0;
> +
> +	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
> @@ -3298,8 +3312,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 != 0)
>  				return err;
>  		}
>  		newcon->flags |= CON_ENABLED;
> @@ -3325,7 +3339,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;
> -- 
> 2.44.0.rc1.240.g4c46232300-goog

With comments added:

Reviewed-by: John Ogness <john.ogness@linutronix.de>

^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2024-02-26 11:37 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2024-02-22 19:23 [PATCH] serial: Lock console when calling into driver before registration Peter Collingbourne
2024-02-26 11:36 ` John Ogness

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).