serial: imx: Switch to nbcon console
authorEsben Haabendal <esben@geanix.com>
Fri, 13 Sep 2024 08:52:19 +0000 (10:52 +0200)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Fri, 11 Oct 2024 06:17:33 +0000 (08:17 +0200)
Implements the necessary callbacks to switch the imx console driver to
perform as an nbcon console.

Add implementations for the nbcon consoles (write_atomic, write_thread,
driver_enter, driver_exit) and add CON_NBCON to the initial flags.

The legacy code is kept in order to easily switch back to legacy mode
by defining CONFIG_SERIAL_IMX_LEGACY_CONSOLE.

Signed-off-by: Esben Haabendal <esben@geanix.com>
Link: https://lore.kernel.org/r/20240913-serial-imx-nbcon-v3-1-4c627302335b@geanix.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/tty/serial/imx.c

index 29a8993..5ca8069 100644 (file)
@@ -230,6 +230,8 @@ struct imx_port {
        unsigned int            saved_reg[10];
        bool                    context_saved;
 
+       bool                    last_putchar_was_newline;
+
        enum imx_tx_state       tx_state;
        struct hrtimer          trigger_start_tx;
        struct hrtimer          trigger_stop_tx;
@@ -2064,26 +2066,34 @@ static void imx_uart_console_putchar(struct uart_port *port, unsigned char ch)
                barrier();
 
        imx_uart_writel(sport, ch, URTX0);
+
+       sport->last_putchar_was_newline = (ch == '\n');
 }
 
-/*
- * Interrupts are disabled on entering
- */
-static void
-imx_uart_console_write(struct console *co, const char *s, unsigned int count)
+static void imx_uart_console_device_lock(struct console *co, unsigned long *flags)
+{
+       struct uart_port *up = &imx_uart_ports[co->index]->port;
+
+       return __uart_port_lock_irqsave(up, flags);
+}
+
+static void imx_uart_console_device_unlock(struct console *co, unsigned long flags)
+{
+       struct uart_port *up = &imx_uart_ports[co->index]->port;
+
+       return __uart_port_unlock_irqrestore(up, flags);
+}
+
+static void imx_uart_console_write_atomic(struct console *co,
+                                         struct nbcon_write_context *wctxt)
 {
        struct imx_port *sport = imx_uart_ports[co->index];
+       struct uart_port *port = &sport->port;
        struct imx_port_ucrs old_ucr;
-       unsigned long flags;
        unsigned int ucr1, usr2;
-       int locked = 1;
 
-       if (sport->port.sysrq)
-               locked = 0;
-       else if (oops_in_progress)
-               locked = uart_port_trylock_irqsave(&sport->port, &flags);
-       else
-               uart_port_lock_irqsave(&sport->port, &flags);
+       if (!nbcon_enter_unsafe(wctxt))
+               return;
 
        /*
         *      First, save UCR1/2/3 and then disable interrupts
@@ -2097,10 +2107,12 @@ imx_uart_console_write(struct console *co, const char *s, unsigned int count)
        ucr1 &= ~(UCR1_TRDYEN | UCR1_RRDYEN | UCR1_RTSDEN);
 
        imx_uart_writel(sport, ucr1, UCR1);
-
        imx_uart_writel(sport, old_ucr.ucr2 | UCR2_TXEN, UCR2);
 
-       uart_console_write(&sport->port, s, count, imx_uart_console_putchar);
+       if (!sport->last_putchar_was_newline)
+               uart_console_write(port, "\n", 1, imx_uart_console_putchar);
+       uart_console_write(port, wctxt->outbuf, wctxt->len,
+                          imx_uart_console_putchar);
 
        /*
         *      Finally, wait for transmitter to become empty
@@ -2110,8 +2122,73 @@ imx_uart_console_write(struct console *co, const char *s, unsigned int count)
                                 0, USEC_PER_SEC, false, sport, USR2);
        imx_uart_ucrs_restore(sport, &old_ucr);
 
-       if (locked)
-               uart_port_unlock_irqrestore(&sport->port, flags);
+       nbcon_exit_unsafe(wctxt);
+}
+
+static void imx_uart_console_write_thread(struct console *co,
+                                         struct nbcon_write_context *wctxt)
+{
+       struct imx_port *sport = imx_uart_ports[co->index];
+       struct uart_port *port = &sport->port;
+       struct imx_port_ucrs old_ucr;
+       unsigned int ucr1, usr2;
+
+       if (!nbcon_enter_unsafe(wctxt))
+               return;
+
+       /*
+        *      First, save UCR1/2/3 and then disable interrupts
+        */
+       imx_uart_ucrs_save(sport, &old_ucr);
+       ucr1 = old_ucr.ucr1;
+
+       if (imx_uart_is_imx1(sport))
+               ucr1 |= IMX1_UCR1_UARTCLKEN;
+       ucr1 |= UCR1_UARTEN;
+       ucr1 &= ~(UCR1_TRDYEN | UCR1_RRDYEN | UCR1_RTSDEN);
+
+       imx_uart_writel(sport, ucr1, UCR1);
+       imx_uart_writel(sport, old_ucr.ucr2 | UCR2_TXEN, UCR2);
+
+       if (nbcon_exit_unsafe(wctxt)) {
+               int len = READ_ONCE(wctxt->len);
+               int i;
+
+               /*
+                * Write out the message. Toggle unsafe for each byte in order
+                * to give another (higher priority) context the opportunity
+                * for a friendly takeover. If such a takeover occurs, this
+                * context must reacquire ownership in order to perform final
+                * actions (such as re-enabling the interrupts).
+                *
+                * IMPORTANT: wctxt->outbuf and wctxt->len are no longer valid
+                *            after a reacquire so writing the message must be
+                *            aborted.
+                */
+               for (i = 0; i < len; i++) {
+                       if (!nbcon_enter_unsafe(wctxt))
+                               break;
+
+                       uart_console_write(port, wctxt->outbuf + i, 1,
+                                          imx_uart_console_putchar);
+
+                       if (!nbcon_exit_unsafe(wctxt))
+                               break;
+               }
+       }
+
+       while (!nbcon_enter_unsafe(wctxt))
+               nbcon_reacquire_nobuf(wctxt);
+
+       /*
+        *      Finally, wait for transmitter to become empty
+        *      and restore UCR1/2/3
+        */
+       read_poll_timeout(imx_uart_readl, usr2, usr2 & USR2_TXDC,
+                         0, USEC_PER_SEC, false, sport, USR2);
+       imx_uart_ucrs_restore(sport, &old_ucr);
+
+       nbcon_exit_unsafe(wctxt);
 }
 
 /*
@@ -2203,6 +2280,8 @@ imx_uart_console_setup(struct console *co, char *options)
        if (retval)
                goto error_console;
 
+       sport->last_putchar_was_newline = true;
+
        if (options)
                uart_parse_options(options, &baud, &parity, &bits, &flow);
        else
@@ -2239,11 +2318,14 @@ imx_uart_console_exit(struct console *co)
 static struct uart_driver imx_uart_uart_driver;
 static struct console imx_uart_console = {
        .name           = DEV_NAME,
-       .write          = imx_uart_console_write,
+       .write_atomic   = imx_uart_console_write_atomic,
+       .write_thread   = imx_uart_console_write_thread,
+       .device_lock    = imx_uart_console_device_lock,
+       .device_unlock  = imx_uart_console_device_unlock,
+       .flags          = CON_PRINTBUFFER | CON_NBCON,
        .device         = uart_console_device,
        .setup          = imx_uart_console_setup,
        .exit           = imx_uart_console_exit,
-       .flags          = CON_PRINTBUFFER,
        .index          = -1,
        .data           = &imx_uart_uart_driver,
 };