Skip to content

Commit

Permalink
serial: 8250: implement write_atomic
Browse files Browse the repository at this point in the history
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: Thomas Gleixner <tglx@linutronix.de>
  • Loading branch information
jogness authored and Sebastian Andrzej Siewior committed Sep 13, 2021
1 parent 2b7d3a8 commit edee001
Show file tree
Hide file tree
Showing 7 changed files with 162 additions and 44 deletions.
47 changes: 45 additions & 2 deletions drivers/tty/serial/8250/8250.h
Original file line number Diff line number Diff line change
Expand Up @@ -132,12 +132,55 @@ static inline void serial_dl_write(struct uart_8250_port *up, int value)
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 long 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 long flags;
unsigned int prior;
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;
}

Expand All @@ -146,7 +189,7 @@ static inline bool serial8250_clear_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;
}

Expand Down
17 changes: 12 additions & 5 deletions drivers/tty/serial/8250/8250_core.c
Original file line number Diff line number Diff line change
Expand Up @@ -264,10 +264,8 @@ static void serial8250_backup_timeout(struct timer_list *t)
* 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);

Expand All @@ -290,7 +288,7 @@ static void serial8250_backup_timeout(struct timer_list *t)
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);

Expand Down Expand Up @@ -568,6 +566,14 @@ serial8250_register_ports(struct uart_driver *drv, struct device *dev)

#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)
{
Expand Down Expand Up @@ -661,6 +667,7 @@ static int univ8250_console_match(struct console *co, char *name, int idx,

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,
Expand Down
9 changes: 9 additions & 0 deletions drivers/tty/serial/8250/8250_fsl.c
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,18 @@ int fsl8250_handle_irq(struct uart_port *port)

/* Stop processing interrupts on input overrun */
if ((orig_lsr & UART_LSR_OE) && (up->overrun_backoff_time_ms > 0)) {
unsigned long flags;
unsigned long delay;
bool is_console;

is_console = uart_console(port);

if (is_console)
console_atomic_lock(flags);
up->ier = port->serial_in(port, UART_IER);
if (is_console)
console_atomic_unlock(flags);

if (up->ier & (UART_IER_RLSI | UART_IER_RDI)) {
port->ops->stop_rx(port);
} else {
Expand Down
7 changes: 7 additions & 0 deletions drivers/tty/serial/8250/8250_ingenic.c
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,8 @@ OF_EARLYCON_DECLARE(x1000_uart, "ingenic,x1000-uart",

static void ingenic_uart_serial_out(struct uart_port *p, int offset, int value)
{
unsigned long flags;
bool is_console;
int ier;

switch (offset) {
Expand All @@ -167,7 +169,12 @@ static void ingenic_uart_serial_out(struct uart_port *p, int offset, int value)
* 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;
Expand Down
29 changes: 27 additions & 2 deletions drivers/tty/serial/8250/8250_mtk.c
Original file line number Diff line number Diff line change
Expand Up @@ -218,12 +218,37 @@ static void mtk8250_shutdown(struct uart_port *port)

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 long 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 long 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)
Expand Down

0 comments on commit edee001

Please sign in to comment.