Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4516d50a authored by Peter Hurley's avatar Peter Hurley Committed by Greg Kroah-Hartman
Browse files

serial: 8250: Use canary to restart console after suspend



When using no_console_suspend, the serial console may be powered off
anyway during system sleep. Upon resume, the port may be in its default
power-on state, but is expected to continue console i/o before the device
has received its pm callback. The resultant garbage i/o can cause all
kinds of havoc on the remote end.

Use the scratch register as a canary to discover if the console
has been powered-off. Write a non-zero value to the scratch register
at port suspend and reprogram the port before any console i/o if the
scratch register != canary before port resume.

This workaround is disabled for omap_8250 (which uses different divisor
programming).

Credit to Doug Anderson <dianders@chromium.org> for the idea of using
the scratch register canary to discover port power-down.

Cc: Doug Anderson <dianders@chromium.org>
Signed-off-by: default avatarPeter Hurley <peter@hurleysoftware.com>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 06a4c710
Loading
Loading
Loading
Loading
+34 −1
Original line number Diff line number Diff line
@@ -3267,6 +3267,27 @@ serial8250_console_write(struct console *co, const char *s, unsigned int count)
	else
		serial_port_out(port, UART_IER, 0);

	/* check scratch reg to see if port powered off during system sleep */
	if (up->canary && (up->canary != serial_port_in(port, UART_SCR))) {
		struct ktermios termios;
		unsigned int baud, quot, frac = 0;

		termios.c_cflag = port->cons->cflag;
		if (port->state->port.tty && termios.c_cflag == 0)
			termios.c_cflag = port->state->port.tty->termios.c_cflag;

		baud = uart_get_baud_rate(port, &termios, NULL,
					  port->uartclk / 16 / 0xffff,
					  port->uartclk / 16);
		quot = serial8250_get_divisor(up, baud, &frac);

		serial8250_set_divisor(port, baud, quot, frac);
		serial_port_out(port, UART_LCR, up->lcr);
		serial_port_out(port, UART_MCR, UART_MCR_DTR | UART_MCR_RTS);

		up->canary = 0;
	}

	uart_console_write(port, s, count, serial8250_console_putchar);

	/*
@@ -3417,7 +3438,17 @@ int __init early_serial_setup(struct uart_port *port)
 */
void serial8250_suspend_port(int line)
{
	uart_suspend_port(&serial8250_reg, &serial8250_ports[line].port);
	struct uart_8250_port *up = &serial8250_ports[line];
	struct uart_port *port = &up->port;

	if (!console_suspend_enabled && uart_console(port) &&
	    port->type != PORT_8250) {
		unsigned char canary = 0xa5;
		serial_out(up, UART_SCR, canary);
		up->canary = canary;
	}

	uart_suspend_port(&serial8250_reg, port);
}

/**
@@ -3431,6 +3462,8 @@ void serial8250_resume_port(int line)
	struct uart_8250_port *up = &serial8250_ports[line];
	struct uart_port *port = &up->port;

	up->canary = 0;

	if (up->capabilities & UART_NATSEMI) {
		/* Ensure it's still in high speed mode */
		serial_port_out(port, UART_LCR, 0xE0);
+3 −0
Original line number Diff line number Diff line
@@ -85,6 +85,9 @@ struct uart_8250_port {
	unsigned char		mcr_force;	/* mask of forced bits */
	unsigned char		cur_iotype;	/* Running I/O type */
	unsigned int		rpm_tx_active;
	unsigned char		canary;		/* non-zero during system sleep
						 *   if no_console_suspend
						 */

	/*
	 * Some bits in registers are cleared on a read, so they must