mirror of
https://github.com/linuxkit/linuxkit.git
synced 2025-07-20 17:49:10 +00:00
482 lines
14 KiB
Diff
482 lines
14 KiB
Diff
From: John Ogness <john.ogness@linutronix.de>
|
|
Date: Mon, 30 Nov 2020 01:42:02 +0106
|
|
Subject: [PATCH 20/28] serial: 8250: implement write_atomic
|
|
|
|
Implement a non-sleeping NMI-safe write_atomic() console function in
|
|
order to support emergency console printing.
|
|
|
|
Since interrupts need to be disabled during transmit, all usage of
|
|
the IER register is wrapped with access functions that use the
|
|
console_atomic_lock() function to synchronize register access while
|
|
tracking the state of the interrupts. This is necessary because
|
|
write_atomic() can be called from an NMI context that has preempted
|
|
write_atomic().
|
|
|
|
Signed-off-by: John Ogness <john.ogness@linutronix.de>
|
|
Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
|
|
---
|
|
drivers/tty/serial/8250/8250.h | 47 ++++++++++++++++
|
|
drivers/tty/serial/8250/8250_core.c | 17 ++++--
|
|
drivers/tty/serial/8250/8250_fsl.c | 9 +++
|
|
drivers/tty/serial/8250/8250_ingenic.c | 7 ++
|
|
drivers/tty/serial/8250/8250_mtk.c | 29 +++++++++-
|
|
drivers/tty/serial/8250/8250_port.c | 92 ++++++++++++++++++++-------------
|
|
include/linux/serial_8250.h | 5 +
|
|
7 files changed, 162 insertions(+), 44 deletions(-)
|
|
|
|
--- a/drivers/tty/serial/8250/8250.h
|
|
+++ b/drivers/tty/serial/8250/8250.h
|
|
@@ -130,12 +130,55 @@ static inline void serial_dl_write(struc
|
|
up->dl_write(up, value);
|
|
}
|
|
|
|
+static inline void serial8250_set_IER(struct uart_8250_port *up,
|
|
+ unsigned char ier)
|
|
+{
|
|
+ struct uart_port *port = &up->port;
|
|
+ unsigned int flags;
|
|
+ bool is_console;
|
|
+
|
|
+ is_console = uart_console(port);
|
|
+
|
|
+ if (is_console)
|
|
+ console_atomic_lock(&flags);
|
|
+
|
|
+ serial_out(up, UART_IER, ier);
|
|
+
|
|
+ if (is_console)
|
|
+ console_atomic_unlock(flags);
|
|
+}
|
|
+
|
|
+static inline unsigned char serial8250_clear_IER(struct uart_8250_port *up)
|
|
+{
|
|
+ struct uart_port *port = &up->port;
|
|
+ unsigned int clearval = 0;
|
|
+ unsigned int prior;
|
|
+ unsigned int flags;
|
|
+ bool is_console;
|
|
+
|
|
+ is_console = uart_console(port);
|
|
+
|
|
+ if (up->capabilities & UART_CAP_UUE)
|
|
+ clearval = UART_IER_UUE;
|
|
+
|
|
+ if (is_console)
|
|
+ console_atomic_lock(&flags);
|
|
+
|
|
+ prior = serial_port_in(port, UART_IER);
|
|
+ serial_port_out(port, UART_IER, clearval);
|
|
+
|
|
+ if (is_console)
|
|
+ console_atomic_unlock(flags);
|
|
+
|
|
+ return prior;
|
|
+}
|
|
+
|
|
static inline bool serial8250_set_THRI(struct uart_8250_port *up)
|
|
{
|
|
if (up->ier & UART_IER_THRI)
|
|
return false;
|
|
up->ier |= UART_IER_THRI;
|
|
- serial_out(up, UART_IER, up->ier);
|
|
+ serial8250_set_IER(up, up->ier);
|
|
return true;
|
|
}
|
|
|
|
@@ -144,7 +187,7 @@ static inline bool serial8250_clear_THRI
|
|
if (!(up->ier & UART_IER_THRI))
|
|
return false;
|
|
up->ier &= ~UART_IER_THRI;
|
|
- serial_out(up, UART_IER, up->ier);
|
|
+ serial8250_set_IER(up, up->ier);
|
|
return true;
|
|
}
|
|
|
|
--- a/drivers/tty/serial/8250/8250_core.c
|
|
+++ b/drivers/tty/serial/8250/8250_core.c
|
|
@@ -274,10 +274,8 @@ static void serial8250_backup_timeout(st
|
|
* Must disable interrupts or else we risk racing with the interrupt
|
|
* based handler.
|
|
*/
|
|
- if (up->port.irq) {
|
|
- ier = serial_in(up, UART_IER);
|
|
- serial_out(up, UART_IER, 0);
|
|
- }
|
|
+ if (up->port.irq)
|
|
+ ier = serial8250_clear_IER(up);
|
|
|
|
iir = serial_in(up, UART_IIR);
|
|
|
|
@@ -300,7 +298,7 @@ static void serial8250_backup_timeout(st
|
|
serial8250_tx_chars(up);
|
|
|
|
if (up->port.irq)
|
|
- serial_out(up, UART_IER, ier);
|
|
+ serial8250_set_IER(up, ier);
|
|
|
|
spin_unlock_irqrestore(&up->port.lock, flags);
|
|
|
|
@@ -578,6 +576,14 @@ serial8250_register_ports(struct uart_dr
|
|
|
|
#ifdef CONFIG_SERIAL_8250_CONSOLE
|
|
|
|
+static void univ8250_console_write_atomic(struct console *co, const char *s,
|
|
+ unsigned int count)
|
|
+{
|
|
+ struct uart_8250_port *up = &serial8250_ports[co->index];
|
|
+
|
|
+ serial8250_console_write_atomic(up, s, count);
|
|
+}
|
|
+
|
|
static void univ8250_console_write(struct console *co, const char *s,
|
|
unsigned int count)
|
|
{
|
|
@@ -671,6 +677,7 @@ static int univ8250_console_match(struct
|
|
|
|
static struct console univ8250_console = {
|
|
.name = "ttyS",
|
|
+ .write_atomic = univ8250_console_write_atomic,
|
|
.write = univ8250_console_write,
|
|
.device = uart_console_device,
|
|
.setup = univ8250_console_setup,
|
|
--- a/drivers/tty/serial/8250/8250_fsl.c
|
|
+++ b/drivers/tty/serial/8250/8250_fsl.c
|
|
@@ -60,9 +60,18 @@ int fsl8250_handle_irq(struct uart_port
|
|
|
|
/* Stop processing interrupts on input overrun */
|
|
if ((orig_lsr & UART_LSR_OE) && (up->overrun_backoff_time_ms > 0)) {
|
|
+ unsigned int ca_flags;
|
|
unsigned long delay;
|
|
+ bool is_console;
|
|
|
|
+ is_console = uart_console(port);
|
|
+
|
|
+ if (is_console)
|
|
+ console_atomic_lock(&ca_flags);
|
|
up->ier = port->serial_in(port, UART_IER);
|
|
+ if (is_console)
|
|
+ console_atomic_unlock(ca_flags);
|
|
+
|
|
if (up->ier & (UART_IER_RLSI | UART_IER_RDI)) {
|
|
port->ops->stop_rx(port);
|
|
} else {
|
|
--- a/drivers/tty/serial/8250/8250_ingenic.c
|
|
+++ b/drivers/tty/serial/8250/8250_ingenic.c
|
|
@@ -146,6 +146,8 @@ OF_EARLYCON_DECLARE(x1000_uart, "ingenic
|
|
|
|
static void ingenic_uart_serial_out(struct uart_port *p, int offset, int value)
|
|
{
|
|
+ unsigned int flags;
|
|
+ bool is_console;
|
|
int ier;
|
|
|
|
switch (offset) {
|
|
@@ -167,7 +169,12 @@ static void ingenic_uart_serial_out(stru
|
|
* If we have enabled modem status IRQs we should enable
|
|
* modem mode.
|
|
*/
|
|
+ is_console = uart_console(p);
|
|
+ if (is_console)
|
|
+ console_atomic_lock(&flags);
|
|
ier = p->serial_in(p, UART_IER);
|
|
+ if (is_console)
|
|
+ console_atomic_unlock(flags);
|
|
|
|
if (ier & UART_IER_MSI)
|
|
value |= UART_MCR_MDCE | UART_MCR_FCM;
|
|
--- a/drivers/tty/serial/8250/8250_mtk.c
|
|
+++ b/drivers/tty/serial/8250/8250_mtk.c
|
|
@@ -213,12 +213,37 @@ static void mtk8250_shutdown(struct uart
|
|
|
|
static void mtk8250_disable_intrs(struct uart_8250_port *up, int mask)
|
|
{
|
|
- serial_out(up, UART_IER, serial_in(up, UART_IER) & (~mask));
|
|
+ struct uart_port *port = &up->port;
|
|
+ unsigned int flags;
|
|
+ unsigned int ier;
|
|
+ bool is_console;
|
|
+
|
|
+ is_console = uart_console(port);
|
|
+
|
|
+ if (is_console)
|
|
+ console_atomic_lock(&flags);
|
|
+
|
|
+ ier = serial_in(up, UART_IER);
|
|
+ serial_out(up, UART_IER, ier & (~mask));
|
|
+
|
|
+ if (is_console)
|
|
+ console_atomic_unlock(flags);
|
|
}
|
|
|
|
static void mtk8250_enable_intrs(struct uart_8250_port *up, int mask)
|
|
{
|
|
- serial_out(up, UART_IER, serial_in(up, UART_IER) | mask);
|
|
+ struct uart_port *port = &up->port;
|
|
+ unsigned int flags;
|
|
+ unsigned int ier;
|
|
+
|
|
+ if (uart_console(port))
|
|
+ console_atomic_lock(&flags);
|
|
+
|
|
+ ier = serial_in(up, UART_IER);
|
|
+ serial_out(up, UART_IER, ier | mask);
|
|
+
|
|
+ if (uart_console(port))
|
|
+ console_atomic_unlock(flags);
|
|
}
|
|
|
|
static void mtk8250_set_flow_ctrl(struct uart_8250_port *up, int mode)
|
|
--- a/drivers/tty/serial/8250/8250_port.c
|
|
+++ b/drivers/tty/serial/8250/8250_port.c
|
|
@@ -757,7 +757,7 @@ static void serial8250_set_sleep(struct
|
|
serial_out(p, UART_EFR, UART_EFR_ECB);
|
|
serial_out(p, UART_LCR, 0);
|
|
}
|
|
- serial_out(p, UART_IER, sleep ? UART_IERX_SLEEP : 0);
|
|
+ serial8250_set_IER(p, sleep ? UART_IERX_SLEEP : 0);
|
|
if (p->capabilities & UART_CAP_EFR) {
|
|
serial_out(p, UART_LCR, UART_LCR_CONF_MODE_B);
|
|
serial_out(p, UART_EFR, efr);
|
|
@@ -1429,7 +1429,7 @@ static void serial8250_stop_rx(struct ua
|
|
|
|
up->ier &= ~(UART_IER_RLSI | UART_IER_RDI);
|
|
up->port.read_status_mask &= ~UART_LSR_DR;
|
|
- serial_port_out(port, UART_IER, up->ier);
|
|
+ serial8250_set_IER(up, up->ier);
|
|
|
|
serial8250_rpm_put(up);
|
|
}
|
|
@@ -1459,7 +1459,7 @@ void serial8250_em485_stop_tx(struct uar
|
|
serial8250_clear_and_reinit_fifos(p);
|
|
|
|
p->ier |= UART_IER_RLSI | UART_IER_RDI;
|
|
- serial_port_out(&p->port, UART_IER, p->ier);
|
|
+ serial8250_set_IER(p, p->ier);
|
|
}
|
|
}
|
|
EXPORT_SYMBOL_GPL(serial8250_em485_stop_tx);
|
|
@@ -1687,7 +1687,7 @@ static void serial8250_disable_ms(struct
|
|
mctrl_gpio_disable_ms(up->gpios);
|
|
|
|
up->ier &= ~UART_IER_MSI;
|
|
- serial_port_out(port, UART_IER, up->ier);
|
|
+ serial8250_set_IER(up, up->ier);
|
|
}
|
|
|
|
static void serial8250_enable_ms(struct uart_port *port)
|
|
@@ -1703,7 +1703,7 @@ static void serial8250_enable_ms(struct
|
|
up->ier |= UART_IER_MSI;
|
|
|
|
serial8250_rpm_get(up);
|
|
- serial_port_out(port, UART_IER, up->ier);
|
|
+ serial8250_set_IER(up, up->ier);
|
|
serial8250_rpm_put(up);
|
|
}
|
|
|
|
@@ -2118,14 +2118,7 @@ static void serial8250_put_poll_char(str
|
|
struct uart_8250_port *up = up_to_u8250p(port);
|
|
|
|
serial8250_rpm_get(up);
|
|
- /*
|
|
- * First save the IER then disable the interrupts
|
|
- */
|
|
- ier = serial_port_in(port, UART_IER);
|
|
- if (up->capabilities & UART_CAP_UUE)
|
|
- serial_port_out(port, UART_IER, UART_IER_UUE);
|
|
- else
|
|
- serial_port_out(port, UART_IER, 0);
|
|
+ ier = serial8250_clear_IER(up);
|
|
|
|
wait_for_xmitr(up, BOTH_EMPTY);
|
|
/*
|
|
@@ -2138,7 +2131,7 @@ static void serial8250_put_poll_char(str
|
|
* and restore the IER
|
|
*/
|
|
wait_for_xmitr(up, BOTH_EMPTY);
|
|
- serial_port_out(port, UART_IER, ier);
|
|
+ serial8250_set_IER(up, ier);
|
|
serial8250_rpm_put(up);
|
|
}
|
|
|
|
@@ -2441,7 +2434,7 @@ void serial8250_do_shutdown(struct uart_
|
|
*/
|
|
spin_lock_irqsave(&port->lock, flags);
|
|
up->ier = 0;
|
|
- serial_port_out(port, UART_IER, 0);
|
|
+ serial8250_set_IER(up, 0);
|
|
spin_unlock_irqrestore(&port->lock, flags);
|
|
|
|
synchronize_irq(port->irq);
|
|
@@ -2771,7 +2764,7 @@ serial8250_do_set_termios(struct uart_po
|
|
if (up->capabilities & UART_CAP_RTOIE)
|
|
up->ier |= UART_IER_RTOIE;
|
|
|
|
- serial_port_out(port, UART_IER, up->ier);
|
|
+ serial8250_set_IER(up, up->ier);
|
|
|
|
if (up->capabilities & UART_CAP_EFR) {
|
|
unsigned char efr = 0;
|
|
@@ -3237,7 +3230,7 @@ EXPORT_SYMBOL_GPL(serial8250_set_default
|
|
|
|
#ifdef CONFIG_SERIAL_8250_CONSOLE
|
|
|
|
-static void serial8250_console_putchar(struct uart_port *port, int ch)
|
|
+static void serial8250_console_putchar_locked(struct uart_port *port, int ch)
|
|
{
|
|
struct uart_8250_port *up = up_to_u8250p(port);
|
|
|
|
@@ -3245,6 +3238,18 @@ static void serial8250_console_putchar(s
|
|
serial_port_out(port, UART_TX, ch);
|
|
}
|
|
|
|
+static void serial8250_console_putchar(struct uart_port *port, int ch)
|
|
+{
|
|
+ struct uart_8250_port *up = up_to_u8250p(port);
|
|
+ unsigned int flags;
|
|
+
|
|
+ wait_for_xmitr(up, UART_LSR_THRE);
|
|
+
|
|
+ console_atomic_lock(&flags);
|
|
+ serial8250_console_putchar_locked(port, ch);
|
|
+ console_atomic_unlock(flags);
|
|
+}
|
|
+
|
|
/*
|
|
* Restore serial console when h/w power-off detected
|
|
*/
|
|
@@ -3266,6 +3271,32 @@ static void serial8250_console_restore(s
|
|
serial8250_out_MCR(up, UART_MCR_DTR | UART_MCR_RTS);
|
|
}
|
|
|
|
+void serial8250_console_write_atomic(struct uart_8250_port *up,
|
|
+ const char *s, unsigned int count)
|
|
+{
|
|
+ struct uart_port *port = &up->port;
|
|
+ unsigned int flags;
|
|
+ unsigned int ier;
|
|
+
|
|
+ console_atomic_lock(&flags);
|
|
+
|
|
+ touch_nmi_watchdog();
|
|
+
|
|
+ ier = serial8250_clear_IER(up);
|
|
+
|
|
+ if (atomic_fetch_inc(&up->console_printing)) {
|
|
+ uart_console_write(port, "\n", 1,
|
|
+ serial8250_console_putchar_locked);
|
|
+ }
|
|
+ uart_console_write(port, s, count, serial8250_console_putchar_locked);
|
|
+ atomic_dec(&up->console_printing);
|
|
+
|
|
+ wait_for_xmitr(up, BOTH_EMPTY);
|
|
+ serial8250_set_IER(up, ier);
|
|
+
|
|
+ console_atomic_unlock(flags);
|
|
+}
|
|
+
|
|
/*
|
|
* Print a string to the serial port trying not to disturb
|
|
* any possible real use of the port...
|
|
@@ -3282,24 +3313,12 @@ void serial8250_console_write(struct uar
|
|
struct uart_port *port = &up->port;
|
|
unsigned long flags;
|
|
unsigned int ier;
|
|
- int locked = 1;
|
|
|
|
touch_nmi_watchdog();
|
|
|
|
- if (oops_in_progress)
|
|
- locked = spin_trylock_irqsave(&port->lock, flags);
|
|
- else
|
|
- spin_lock_irqsave(&port->lock, flags);
|
|
-
|
|
- /*
|
|
- * First save the IER then disable the interrupts
|
|
- */
|
|
- ier = serial_port_in(port, UART_IER);
|
|
+ spin_lock_irqsave(&port->lock, flags);
|
|
|
|
- if (up->capabilities & UART_CAP_UUE)
|
|
- serial_port_out(port, UART_IER, UART_IER_UUE);
|
|
- else
|
|
- serial_port_out(port, UART_IER, 0);
|
|
+ ier = serial8250_clear_IER(up);
|
|
|
|
/* check scratch reg to see if port powered off during system sleep */
|
|
if (up->canary && (up->canary != serial_port_in(port, UART_SCR))) {
|
|
@@ -3313,7 +3332,9 @@ void serial8250_console_write(struct uar
|
|
mdelay(port->rs485.delay_rts_before_send);
|
|
}
|
|
|
|
+ atomic_inc(&up->console_printing);
|
|
uart_console_write(port, s, count, serial8250_console_putchar);
|
|
+ atomic_dec(&up->console_printing);
|
|
|
|
/*
|
|
* Finally, wait for transmitter to become empty
|
|
@@ -3326,8 +3347,7 @@ void serial8250_console_write(struct uar
|
|
if (em485->tx_stopped)
|
|
up->rs485_stop_tx(up);
|
|
}
|
|
-
|
|
- serial_port_out(port, UART_IER, ier);
|
|
+ serial8250_set_IER(up, ier);
|
|
|
|
/*
|
|
* The receive handling will happen properly because the
|
|
@@ -3339,8 +3359,7 @@ void serial8250_console_write(struct uar
|
|
if (up->msr_saved_flags)
|
|
serial8250_modem_status(up);
|
|
|
|
- if (locked)
|
|
- spin_unlock_irqrestore(&port->lock, flags);
|
|
+ spin_unlock_irqrestore(&port->lock, flags);
|
|
}
|
|
|
|
static unsigned int probe_baud(struct uart_port *port)
|
|
@@ -3360,6 +3379,7 @@ static unsigned int probe_baud(struct ua
|
|
|
|
int serial8250_console_setup(struct uart_port *port, char *options, bool probe)
|
|
{
|
|
+ struct uart_8250_port *up = up_to_u8250p(port);
|
|
int baud = 9600;
|
|
int bits = 8;
|
|
int parity = 'n';
|
|
@@ -3369,6 +3389,8 @@ int serial8250_console_setup(struct uart
|
|
if (!port->iobase && !port->membase)
|
|
return -ENODEV;
|
|
|
|
+ atomic_set(&up->console_printing, 0);
|
|
+
|
|
if (options)
|
|
uart_parse_options(options, &baud, &parity, &bits, &flow);
|
|
else if (probe)
|
|
--- a/include/linux/serial_8250.h
|
|
+++ b/include/linux/serial_8250.h
|
|
@@ -7,6 +7,7 @@
|
|
#ifndef _LINUX_SERIAL_8250_H
|
|
#define _LINUX_SERIAL_8250_H
|
|
|
|
+#include <linux/atomic.h>
|
|
#include <linux/serial_core.h>
|
|
#include <linux/serial_reg.h>
|
|
#include <linux/platform_device.h>
|
|
@@ -125,6 +126,8 @@ struct uart_8250_port {
|
|
#define MSR_SAVE_FLAGS UART_MSR_ANY_DELTA
|
|
unsigned char msr_saved_flags;
|
|
|
|
+ atomic_t console_printing;
|
|
+
|
|
struct uart_8250_dma *dma;
|
|
const struct uart_8250_ops *ops;
|
|
|
|
@@ -180,6 +183,8 @@ void serial8250_init_port(struct uart_82
|
|
void serial8250_set_defaults(struct uart_8250_port *up);
|
|
void serial8250_console_write(struct uart_8250_port *up, const char *s,
|
|
unsigned int count);
|
|
+void serial8250_console_write_atomic(struct uart_8250_port *up, const char *s,
|
|
+ unsigned int count);
|
|
int serial8250_console_setup(struct uart_port *port, char *options, bool probe);
|
|
int serial8250_console_exit(struct uart_port *port);
|
|
|