| From: Sebastian Andrzej Siewior <bigeasy@linutronix.de> |
| Date: Tue, 4 Feb 2025 12:30:26 +0100 |
| Subject: [PATCH] Reapply "serial: 8250: Switch to nbcon console" |
| |
| This reverts commit f79b163c42314a1f46f4bcc40a19c8a75cf1e7a3. |
| |
| Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de> |
| --- |
| drivers/tty/serial/8250/8250_core.c | 35 ++++++- |
| drivers/tty/serial/8250/8250_port.c | 180 +++++++++++++++++++++++++++++------- |
| include/linux/serial_8250.h | 13 ++ |
| 3 files changed, 187 insertions(+), 41 deletions(-) |
| |
| --- a/drivers/tty/serial/8250/8250_core.c |
| +++ b/drivers/tty/serial/8250/8250_core.c |
| @@ -391,12 +391,34 @@ void __init serial8250_register_ports(st |
| |
| #ifdef CONFIG_SERIAL_8250_CONSOLE |
| |
| -static void univ8250_console_write(struct console *co, const char *s, |
| - unsigned int count) |
| +static void univ8250_console_write_atomic(struct console *co, |
| + struct nbcon_write_context *wctxt) |
| { |
| struct uart_8250_port *up = &serial8250_ports[co->index]; |
| |
| - serial8250_console_write(up, s, count); |
| + serial8250_console_write(up, wctxt, true); |
| +} |
| + |
| +static void univ8250_console_write_thread(struct console *co, |
| + struct nbcon_write_context *wctxt) |
| +{ |
| + struct uart_8250_port *up = &serial8250_ports[co->index]; |
| + |
| + serial8250_console_write(up, wctxt, false); |
| +} |
| + |
| +static void univ8250_console_device_lock(struct console *co, unsigned long *flags) |
| +{ |
| + struct uart_port *up = &serial8250_ports[co->index].port; |
| + |
| + __uart_port_lock_irqsave(up, flags); |
| +} |
| + |
| +static void univ8250_console_device_unlock(struct console *co, unsigned long flags) |
| +{ |
| + struct uart_port *up = &serial8250_ports[co->index].port; |
| + |
| + __uart_port_unlock_irqrestore(up, flags); |
| } |
| |
| static int univ8250_console_setup(struct console *co, char *options) |
| @@ -497,12 +519,15 @@ static int univ8250_console_match(struct |
| |
| static struct console univ8250_console = { |
| .name = "ttyS", |
| - .write = univ8250_console_write, |
| + .write_atomic = univ8250_console_write_atomic, |
| + .write_thread = univ8250_console_write_thread, |
| + .device_lock = univ8250_console_device_lock, |
| + .device_unlock = univ8250_console_device_unlock, |
| .device = uart_console_device, |
| .setup = univ8250_console_setup, |
| .exit = univ8250_console_exit, |
| .match = univ8250_console_match, |
| - .flags = CON_PRINTBUFFER | CON_ANYTIME, |
| + .flags = CON_PRINTBUFFER | CON_ANYTIME | CON_NBCON, |
| .index = -1, |
| .data = &serial8250_reg, |
| }; |
| --- a/drivers/tty/serial/8250/8250_port.c |
| +++ b/drivers/tty/serial/8250/8250_port.c |
| @@ -705,7 +705,12 @@ static void serial8250_clear_interrupts( |
| serial_port_in(port, UART_MSR); |
| } |
| |
| -static void serial8250_clear_IER(struct uart_8250_port *up) |
| +/* |
| + * Only to be used directly by the callback helper serial8250_console_write(), |
| + * which may not require the port lock. Use serial8250_clear_IER() instead for |
| + * all other cases. |
| + */ |
| +static void __serial8250_clear_IER(struct uart_8250_port *up) |
| { |
| if (up->capabilities & UART_CAP_UUE) |
| serial_out(up, UART_IER, UART_IER_UUE); |
| @@ -713,6 +718,11 @@ static void serial8250_clear_IER(struct |
| serial_out(up, UART_IER, 0); |
| } |
| |
| +static inline void serial8250_clear_IER(struct uart_8250_port *up) |
| +{ |
| + __serial8250_clear_IER(up); |
| +} |
| + |
| /* |
| * This is a quickie test to see how big the FIFO is. |
| * It doesn't work at all the time, more's the pity. |
| @@ -1290,9 +1300,6 @@ void serial8250_em485_stop_tx(struct uar |
| { |
| unsigned char mcr = serial8250_in_MCR(p); |
| |
| - /* Port locked to synchronize UART_IER access against the console. */ |
| - lockdep_assert_held_once(&p->port.lock); |
| - |
| if (p->port.rs485.flags & SER_RS485_RTS_AFTER_SEND) |
| mcr |= UART_MCR_RTS; |
| else |
| @@ -1308,6 +1315,16 @@ void serial8250_em485_stop_tx(struct uar |
| serial8250_clear_and_reinit_fifos(p); |
| |
| if (toggle_ier) { |
| + /* |
| + * Port locked to synchronize UART_IER access against |
| + * the console. The lockdep_assert must be restricted |
| + * to this condition because only here is it |
| + * guaranteed that the port lock is held. The other |
| + * hardware access in this function is synchronized |
| + * by console ownership. |
| + */ |
| + lockdep_assert_held_once(&p->port.lock); |
| + |
| p->ier |= UART_IER_RLSI | UART_IER_RDI; |
| serial_port_out(&p->port, UART_IER, p->ier); |
| } |
| @@ -3243,7 +3260,11 @@ EXPORT_SYMBOL_GPL(serial8250_set_default |
| |
| static void serial8250_console_putchar(struct uart_port *port, unsigned char ch) |
| { |
| + struct uart_8250_port *up = up_to_u8250p(port); |
| + |
| serial_port_out(port, UART_TX, ch); |
| + |
| + up->console_line_ended = (ch == '\n'); |
| } |
| |
| static void serial8250_console_wait_putchar(struct uart_port *port, unsigned char ch) |
| @@ -3280,11 +3301,22 @@ static void serial8250_console_restore(s |
| serial8250_out_MCR(up, up->mcr | UART_MCR_DTR | UART_MCR_RTS); |
| } |
| |
| -static void fifo_wait_for_lsr(struct uart_8250_port *up, unsigned int count) |
| +static void fifo_wait_for_lsr(struct uart_8250_port *up, |
| + struct nbcon_write_context *wctxt, |
| + unsigned int count) |
| { |
| unsigned int i; |
| |
| for (i = 0; i < count; i++) { |
| + /* |
| + * Pass the ownership as quickly as possible to a higher |
| + * priority context. Otherwise, its attempt to take over |
| + * the ownership might timeout. The new owner will wait |
| + * for UART_LSR_THRE before reusing the fifo. |
| + */ |
| + if (!nbcon_can_proceed(wctxt)) |
| + return; |
| + |
| if (wait_for_lsr(up, UART_LSR_THRE)) |
| return; |
| } |
| @@ -3297,20 +3329,29 @@ static void fifo_wait_for_lsr(struct uar |
| * to get empty. |
| */ |
| static void serial8250_console_fifo_write(struct uart_8250_port *up, |
| - const char *s, unsigned int count) |
| + struct nbcon_write_context *wctxt) |
| { |
| - const char *end = s + count; |
| unsigned int fifosize = up->tx_loadsz; |
| struct uart_port *port = &up->port; |
| + const char *s = wctxt->outbuf; |
| + const char *end = s + wctxt->len; |
| unsigned int tx_count = 0; |
| bool cr_sent = false; |
| unsigned int i; |
| |
| while (s != end) { |
| /* Allow timeout for each byte of a possibly full FIFO */ |
| - fifo_wait_for_lsr(up, fifosize); |
| + fifo_wait_for_lsr(up, wctxt, fifosize); |
| |
| + /* |
| + * Fill the FIFO. If a handover or takeover occurs, writing |
| + * must be aborted since wctxt->outbuf and wctxt->len are no |
| + * longer valid. |
| + */ |
| for (i = 0; i < fifosize && s != end; ++i) { |
| + if (!nbcon_enter_unsafe(wctxt)) |
| + return; |
| + |
| if (*s == '\n' && !cr_sent) { |
| serial8250_console_putchar(port, '\r'); |
| cr_sent = true; |
| @@ -3318,6 +3359,8 @@ static void serial8250_console_fifo_writ |
| serial8250_console_putchar(port, *s++); |
| cr_sent = false; |
| } |
| + |
| + nbcon_exit_unsafe(wctxt); |
| } |
| tx_count = i; |
| } |
| @@ -3326,39 +3369,57 @@ static void serial8250_console_fifo_writ |
| * Allow timeout for each byte written since the caller will only wait |
| * for UART_LSR_BOTH_EMPTY using the timeout of a single character |
| */ |
| - fifo_wait_for_lsr(up, tx_count); |
| + fifo_wait_for_lsr(up, wctxt, tx_count); |
| +} |
| + |
| +static void serial8250_console_byte_write(struct uart_8250_port *up, |
| + struct nbcon_write_context *wctxt) |
| +{ |
| + struct uart_port *port = &up->port; |
| + const char *s = wctxt->outbuf; |
| + const char *end = s + wctxt->len; |
| + |
| + /* |
| + * Write out the message. If a handover or takeover occurs, writing |
| + * must be aborted since wctxt->outbuf and wctxt->len are no longer |
| + * valid. |
| + */ |
| + while (s != end) { |
| + if (!nbcon_enter_unsafe(wctxt)) |
| + return; |
| + |
| + uart_console_write(port, s++, 1, serial8250_console_wait_putchar); |
| + |
| + nbcon_exit_unsafe(wctxt); |
| + } |
| } |
| |
| /* |
| - * Print a string to the serial port trying not to disturb |
| - * any possible real use of the port... |
| - * |
| - * The console_lock must be held when we get here. |
| + * Print a string to the serial port trying not to disturb |
| + * any possible real use of the port... |
| * |
| - * Doing runtime PM is really a bad idea for the kernel console. |
| - * Thus, we assume the function is called when device is powered up. |
| + * Doing runtime PM is really a bad idea for the kernel console. |
| + * Thus, assume it is called when device is powered up. |
| */ |
| -void serial8250_console_write(struct uart_8250_port *up, const char *s, |
| - unsigned int count) |
| +void serial8250_console_write(struct uart_8250_port *up, |
| + struct nbcon_write_context *wctxt, |
| + bool is_atomic) |
| { |
| struct uart_8250_em485 *em485 = up->em485; |
| struct uart_port *port = &up->port; |
| - unsigned long flags; |
| - unsigned int ier, use_fifo; |
| - int locked = 1; |
| - |
| - touch_nmi_watchdog(); |
| + unsigned int ier; |
| + bool use_fifo; |
| |
| - if (oops_in_progress) |
| - locked = uart_port_trylock_irqsave(port, &flags); |
| - else |
| - uart_port_lock_irqsave(port, &flags); |
| + if (!nbcon_enter_unsafe(wctxt)) |
| + return; |
| |
| /* |
| - * First save the IER then disable the interrupts |
| + * First, save the IER, then disable the interrupts. The special |
| + * variant to clear the IER is used because console printing may |
| + * occur without holding the port lock. |
| */ |
| ier = serial_port_in(port, UART_IER); |
| - serial8250_clear_IER(up); |
| + __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))) { |
| @@ -3372,6 +3433,18 @@ void serial8250_console_write(struct uar |
| mdelay(port->rs485.delay_rts_before_send); |
| } |
| |
| + /* If ownership was lost, no writing is allowed */ |
| + if (!nbcon_can_proceed(wctxt)) |
| + goto skip_write; |
| + |
| + /* |
| + * If console printer did not fully output the previous line, it must |
| + * have been handed or taken over. Insert a newline in order to |
| + * maintain clean output. |
| + */ |
| + if (!up->console_line_ended) |
| + uart_console_write(port, "\n", 1, serial8250_console_wait_putchar); |
| + |
| use_fifo = (up->capabilities & UART_CAP_FIFO) && |
| /* |
| * BCM283x requires to check the fifo |
| @@ -3392,10 +3465,23 @@ void serial8250_console_write(struct uar |
| */ |
| !(up->port.flags & UPF_CONS_FLOW); |
| |
| + nbcon_exit_unsafe(wctxt); |
| + |
| if (likely(use_fifo)) |
| - serial8250_console_fifo_write(up, s, count); |
| + serial8250_console_fifo_write(up, wctxt); |
| else |
| - uart_console_write(port, s, count, serial8250_console_wait_putchar); |
| + serial8250_console_byte_write(up, wctxt); |
| +skip_write: |
| + /* |
| + * If ownership was lost, this context must reacquire ownership and |
| + * re-enter the unsafe section in order to perform final actions |
| + * (such as re-enabling interrupts). |
| + */ |
| + if (!nbcon_can_proceed(wctxt)) { |
| + do { |
| + nbcon_reacquire_nobuf(wctxt); |
| + } while (!nbcon_enter_unsafe(wctxt)); |
| + } |
| |
| /* |
| * Finally, wait for transmitter to become empty |
| @@ -3418,11 +3504,18 @@ void serial8250_console_write(struct uar |
| * call it if we have saved something in the saved flags |
| * while processing with interrupts off. |
| */ |
| - if (up->msr_saved_flags) |
| - serial8250_modem_status(up); |
| + if (up->msr_saved_flags) { |
| + /* |
| + * For atomic, it must be deferred to irq_work because this |
| + * may be a context that does not permit waking up tasks. |
| + */ |
| + if (is_atomic) |
| + irq_work_queue(&up->modem_status_work); |
| + else |
| + serial8250_modem_status(up); |
| + } |
| |
| - if (locked) |
| - uart_port_unlock_irqrestore(port, flags); |
| + nbcon_exit_unsafe(wctxt); |
| } |
| |
| static unsigned int probe_baud(struct uart_port *port) |
| @@ -3440,8 +3533,24 @@ static unsigned int probe_baud(struct ua |
| return (port->uartclk / 16) / quot; |
| } |
| |
| +/* |
| + * irq_work handler to perform modem control. Only triggered via |
| + * ->write_atomic() callback because it may be in a scheduler or |
| + * NMI context, unable to wake tasks. |
| + */ |
| +static void modem_status_handler(struct irq_work *iwp) |
| +{ |
| + struct uart_8250_port *up = container_of(iwp, struct uart_8250_port, modem_status_work); |
| + struct uart_port *port = &up->port; |
| + |
| + uart_port_lock(port); |
| + serial8250_modem_status(up); |
| + uart_port_unlock(port); |
| +} |
| + |
| 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'; |
| @@ -3451,6 +3560,9 @@ int serial8250_console_setup(struct uart |
| if (!port->iobase && !port->membase) |
| return -ENODEV; |
| |
| + up->console_line_ended = true; |
| + init_irq_work(&up->modem_status_work, modem_status_handler); |
| + |
| 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 |
| @@ -150,8 +150,17 @@ struct uart_8250_port { |
| #define LSR_SAVE_FLAGS UART_LSR_BRK_ERROR_BITS |
| u16 lsr_saved_flags; |
| u16 lsr_save_mask; |
| + |
| + /* |
| + * Track when a console line has been fully written to the |
| + * hardware, i.e. true when the most recent byte written to |
| + * UART_TX by the console was '\n'. |
| + */ |
| + bool console_line_ended; |
| + |
| #define MSR_SAVE_FLAGS UART_MSR_ANY_DELTA |
| unsigned char msr_saved_flags; |
| + struct irq_work modem_status_work; |
| |
| struct uart_8250_dma *dma; |
| const struct uart_8250_ops *ops; |
| @@ -202,8 +211,8 @@ void serial8250_tx_chars(struct uart_825 |
| unsigned int serial8250_modem_status(struct uart_8250_port *up); |
| void serial8250_init_port(struct uart_8250_port *up); |
| 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(struct uart_8250_port *up, |
| + struct nbcon_write_context *wctxt, bool in_atomic); |
| int serial8250_console_setup(struct uart_port *port, char *options, bool probe); |
| int serial8250_console_exit(struct uart_port *port); |
| |