]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
serial: 8250: Switch to nbcon console
authorJohn Ogness <john.ogness@linutronix.de>
Tue, 7 Jan 2025 21:27:01 +0000 (22:33 +0106)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Fri, 10 Jan 2025 15:08:25 +0000 (16:08 +0100)
Implement the necessary callbacks to switch the 8250 console driver
to perform as an nbcon console.

Add implementations for the nbcon console callbacks:

  ->write_atomic()
  ->write_thread()
  ->device_lock()
  ->device_unlock()

and add CON_NBCON to the initial @flags.

All register access in the callbacks are within unsafe sections.
The ->write_atomic() and ->write_thread() callbacks allow safe
handover/takeover per byte and add a preceding newline if they
take over from another context mid-line.

For the ->write_atomic() callback, a new irq_work is used to defer
modem control since it may be called from a context that does not
allow waking up tasks.

Note: A new __serial8250_clear_IER() is introduced for direct
clearing of UART_IER. This will allow to restore the lockdep
check to serial8250_clear_IER() in a follow-up commit.

Signed-off-by: John Ogness <john.ogness@linutronix.de>
Reviewed-by: Petr Mladek <pmladek@suse.com>
Tested-by: Petr Mladek <pmladek@suse.com>
Link: https://lore.kernel.org/r/20250107212702.169493-6-john.ogness@linutronix.de
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/tty/serial/8250/8250_core.c
drivers/tty/serial/8250/8250_port.c
include/linux/serial_8250.h

index 2b70e82dffeb889c7f2b27abc238b3c95911676c..3d5a2183ff13dc230ae89ff50865ed449de17f8f 100644 (file)
@@ -388,12 +388,34 @@ void __init serial8250_register_ports(struct uart_driver *drv, struct device *de
 
 #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)
@@ -494,12 +516,15 @@ static int univ8250_console_match(struct console *co, char *name, int idx,
 
 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,
 };
index d7976a21cca9ce50557ca5f13bb01448ced0728b..08466cf10d737ed38e0fa7ba4354950fd1899c86 100644 (file)
@@ -711,7 +711,12 @@ static void serial8250_set_sleep(struct uart_8250_port *p, int sleep)
        serial8250_rpm_put(p);
 }
 
-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);
@@ -719,6 +724,11 @@ static void serial8250_clear_IER(struct uart_8250_port *up)
                serial_out(up, UART_IER, 0);
 }
 
+static inline void serial8250_clear_IER(struct uart_8250_port *up)
+{
+       __serial8250_clear_IER(up);
+}
+
 #ifdef CONFIG_SERIAL_8250_RSA
 /*
  * Attempts to turn on the RSA FIFO.  Returns zero on failure.
@@ -1406,9 +1416,6 @@ void serial8250_em485_stop_tx(struct uart_8250_port *p, bool toggle_ier)
 {
        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
@@ -1424,6 +1431,16 @@ void serial8250_em485_stop_tx(struct uart_8250_port *p, bool toggle_ier)
                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);
                }
@@ -3303,7 +3320,11 @@ EXPORT_SYMBOL_GPL(serial8250_set_defaults);
 
 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)
@@ -3340,11 +3361,22 @@ static void serial8250_console_restore(struct uart_8250_port *up)
        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;
        }
@@ -3357,20 +3389,29 @@ static void fifo_wait_for_lsr(struct uart_8250_port *up, unsigned int count)
  * 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;
@@ -3378,6 +3419,8 @@ static void serial8250_console_fifo_write(struct uart_8250_port *up,
                                serial8250_console_putchar(port, *s++);
                                cr_sent = false;
                        }
+
+                       nbcon_exit_unsafe(wctxt);
                }
                tx_count = i;
        }
@@ -3386,39 +3429,57 @@ static void serial8250_console_fifo_write(struct uart_8250_port *up,
         * 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))) {
@@ -3432,6 +3493,18 @@ void serial8250_console_write(struct uart_8250_port *up, const char *s,
                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
@@ -3452,10 +3525,23 @@ void serial8250_console_write(struct uart_8250_port *up, const char *s,
                 */
                !(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
@@ -3478,11 +3564,18 @@ void serial8250_console_write(struct uart_8250_port *up, const char *s,
         *      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)
@@ -3500,8 +3593,24 @@ static unsigned int probe_baud(struct uart_port *port)
        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';
@@ -3511,6 +3620,9 @@ int serial8250_console_setup(struct uart_port *port, char *options, bool probe)
        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)
index 144de7a7948de30e97df7a1577aecb56fe4ccac6..57875c37023af718beed5cc4211bfca0d3309c90 100644 (file)
@@ -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_8250_port *up);
 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);