diff options
author | Peter Hurley <peter@hurleysoftware.com> | 2015-01-22 12:24:30 -0500 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2015-02-02 13:11:27 -0500 |
commit | 4516d50aabedbe5ae334155193e4d35c02390d9a (patch) | |
tree | 19bcf6aba42e65701008dce683481dce7008fef9 | |
parent | 06a4c710673184f5c750bdb2f8579e0ae1cc252c (diff) |
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: Peter Hurley <peter@hurleysoftware.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
-rw-r--r-- | drivers/tty/serial/8250/8250_core.c | 35 | ||||
-rw-r--r-- | include/linux/serial_8250.h | 3 |
2 files changed, 37 insertions, 1 deletions
diff --git a/drivers/tty/serial/8250/8250_core.c b/drivers/tty/serial/8250/8250_core.c index c8ecfaf49eb4..1449c56506b7 100644 --- a/drivers/tty/serial/8250/8250_core.c +++ b/drivers/tty/serial/8250/8250_core.c | |||
@@ -3267,6 +3267,27 @@ serial8250_console_write(struct console *co, const char *s, unsigned int count) | |||
3267 | else | 3267 | else |
3268 | serial_port_out(port, UART_IER, 0); | 3268 | serial_port_out(port, UART_IER, 0); |
3269 | 3269 | ||
3270 | /* check scratch reg to see if port powered off during system sleep */ | ||
3271 | if (up->canary && (up->canary != serial_port_in(port, UART_SCR))) { | ||
3272 | struct ktermios termios; | ||
3273 | unsigned int baud, quot, frac = 0; | ||
3274 | |||
3275 | termios.c_cflag = port->cons->cflag; | ||
3276 | if (port->state->port.tty && termios.c_cflag == 0) | ||
3277 | termios.c_cflag = port->state->port.tty->termios.c_cflag; | ||
3278 | |||
3279 | baud = uart_get_baud_rate(port, &termios, NULL, | ||
3280 | port->uartclk / 16 / 0xffff, | ||
3281 | port->uartclk / 16); | ||
3282 | quot = serial8250_get_divisor(up, baud, &frac); | ||
3283 | |||
3284 | serial8250_set_divisor(port, baud, quot, frac); | ||
3285 | serial_port_out(port, UART_LCR, up->lcr); | ||
3286 | serial_port_out(port, UART_MCR, UART_MCR_DTR | UART_MCR_RTS); | ||
3287 | |||
3288 | up->canary = 0; | ||
3289 | } | ||
3290 | |||
3270 | uart_console_write(port, s, count, serial8250_console_putchar); | 3291 | uart_console_write(port, s, count, serial8250_console_putchar); |
3271 | 3292 | ||
3272 | /* | 3293 | /* |
@@ -3417,7 +3438,17 @@ int __init early_serial_setup(struct uart_port *port) | |||
3417 | */ | 3438 | */ |
3418 | void serial8250_suspend_port(int line) | 3439 | void serial8250_suspend_port(int line) |
3419 | { | 3440 | { |
3420 | uart_suspend_port(&serial8250_reg, &serial8250_ports[line].port); | 3441 | struct uart_8250_port *up = &serial8250_ports[line]; |
3442 | struct uart_port *port = &up->port; | ||
3443 | |||
3444 | if (!console_suspend_enabled && uart_console(port) && | ||
3445 | port->type != PORT_8250) { | ||
3446 | unsigned char canary = 0xa5; | ||
3447 | serial_out(up, UART_SCR, canary); | ||
3448 | up->canary = canary; | ||
3449 | } | ||
3450 | |||
3451 | uart_suspend_port(&serial8250_reg, port); | ||
3421 | } | 3452 | } |
3422 | 3453 | ||
3423 | /** | 3454 | /** |
@@ -3431,6 +3462,8 @@ void serial8250_resume_port(int line) | |||
3431 | struct uart_8250_port *up = &serial8250_ports[line]; | 3462 | struct uart_8250_port *up = &serial8250_ports[line]; |
3432 | struct uart_port *port = &up->port; | 3463 | struct uart_port *port = &up->port; |
3433 | 3464 | ||
3465 | up->canary = 0; | ||
3466 | |||
3434 | if (up->capabilities & UART_NATSEMI) { | 3467 | if (up->capabilities & UART_NATSEMI) { |
3435 | /* Ensure it's still in high speed mode */ | 3468 | /* Ensure it's still in high speed mode */ |
3436 | serial_port_out(port, UART_LCR, 0xE0); | 3469 | serial_port_out(port, UART_LCR, 0xE0); |
diff --git a/include/linux/serial_8250.h b/include/linux/serial_8250.h index 245b959f1ff6..a8efa235b7c1 100644 --- a/include/linux/serial_8250.h +++ b/include/linux/serial_8250.h | |||
@@ -85,6 +85,9 @@ struct uart_8250_port { | |||
85 | unsigned char mcr_force; /* mask of forced bits */ | 85 | unsigned char mcr_force; /* mask of forced bits */ |
86 | unsigned char cur_iotype; /* Running I/O type */ | 86 | unsigned char cur_iotype; /* Running I/O type */ |
87 | unsigned int rpm_tx_active; | 87 | unsigned int rpm_tx_active; |
88 | unsigned char canary; /* non-zero during system sleep | ||
89 | * if no_console_suspend | ||
90 | */ | ||
88 | 91 | ||
89 | /* | 92 | /* |
90 | * Some bits in registers are cleared on a read, so they must | 93 | * Some bits in registers are cleared on a read, so they must |