Re: [PATCH 1/2] serial: qcom-geni: Convert console to nbcon
Aniket RANDIVE <[email protected]> Thu, 6 Aug 2026 18:59:15 +0530
| Newsgroups | org.kernel.vger.linux-serial,org.kernel.vger.linux-arm-msm,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <[email protected]> |
On 7/30/2026 3:14 AM, Bjorn Andersson wrote: > The legacy GENI console writer serializes every message around a > synchronous polled M-side transfer. It blocks printk callers for UART > wire time and cannot provide atomic output while normal console output is > active. > > Convert the console to nbcon threaded and atomic writers. Use the UART > port lock as the device lock and bound threaded M-side commands so > urgent diagnostics can take over atomic output. The threaded writer is > batching the output in 32-source-byte commands, a value chosen to > balance the command setup overhead with atomic-handoff latency. > > Atomic output can cancel an active normal TX command. Use irq_work to > resume queued TTY output afterward, honor flow control, and prevent the > deferred restart from accessing the port during shutdown or removal. > > Assisted-by: OpenCode:GPT-5.5 > Signed-off-by: Bjorn Andersson <[email protected]> > --- > drivers/tty/serial/qcom_geni_serial.c | 166 ++++++++++++++++++++++++++++------ > 1 file changed, 140 insertions(+), 26 deletions(-) > > diff --git a/drivers/tty/serial/qcom_geni_serial.c b/drivers/tty/serial/qcom_geni_serial.c > index 67b14fda4ff9..08427390c173 100644 > --- a/drivers/tty/serial/qcom_geni_serial.c > +++ b/drivers/tty/serial/qcom_geni_serial.c > @@ -16,6 +16,7 @@ > #include <linux/io.h> > #include <linux/iopoll.h> > #include <linux/irq.h> > +#include <linux/irq_work.h> > #include <linux/module.h> > #include <linux/of.h> > #include <linux/panic_notifier.h> > @@ -89,6 +90,7 @@ > #define DEF_TX_WM 2 > #define DEF_FIFO_WIDTH_BITS 32 > #define UART_RX_WM 2 > +#define CONSOLE_TX_CHUNK_SIZE 32 I noticed that CONSOLE_TX_CHUNK_SIZE is limited to 32 bytes, effectively using only half of the 16-word TX FIFO. Was 64 bytes tested and found problematic, or is 32 bytes simply a conservative default? > > /* SE_UART_LOOPBACK_CFG */ > #define RX_TX_SORTED BIT(0) > @@ -153,7 +155,8 @@ struct qcom_geni_serial_port { > bool rx_tx_swap; > bool cts_rts_swap; > bool manual_flow; > - > + bool tx_kick_enabled; > + struct irq_work tx_kick; > struct qcom_geni_private_data private_data; > const struct qcom_geni_device_data *dev_data; > struct dev_pm_domain_list *pd_list; > @@ -168,6 +171,8 @@ static struct uart_driver qcom_geni_uart_driver; > static void __qcom_geni_serial_cancel_tx_cmd(struct uart_port *uport); > static void qcom_geni_serial_cancel_tx_cmd(struct uart_port *uport); > static int qcom_geni_serial_port_setup(struct uart_port *uport); > +static void qcom_geni_serial_start_tx_fifo(struct uart_port *uport); > +static void qcom_geni_serial_resume_tx(struct uart_port *uport); > > static inline struct qcom_geni_serial_port *to_dev_port(struct uart_port *uport) > { > @@ -539,49 +544,123 @@ __qcom_geni_serial_console_write(struct uart_port *uport, const char *s, > qcom_geni_serial_poll_tx_done(uport); > } > > -static void qcom_geni_serial_console_write(struct console *co, const char *s, > - unsigned int count) > +static void qcom_geni_serial_console_takeover(struct uart_port *uport, > + bool preserve_tx) > +{ > + if (qcom_geni_serial_main_active(uport)) { > + struct qcom_geni_serial_port *port = to_dev_port(uport); > + > + if (preserve_tx) { > + if (port->tx_remaining == 0) > + qcom_geni_serial_poll_tx_done(uport); > + else > + qcom_geni_serial_drain_fifo(uport); > + } > + > + qcom_geni_serial_cancel_tx_cmd(uport); > + } > +} > + > +static void qcom_geni_serial_console_write_thread(struct console *co, > + struct nbcon_write_context *wctxt) > { > + struct qcom_geni_serial_port *port; > struct uart_port *uport; > + unsigned int offset = 0; > + > + port = get_port_from_line(co->index, true, NULL); > + if (IS_ERR(port)) > + return; > + > + uport = &port->uport; > + if (!nbcon_enter_unsafe(wctxt)) > + return; > + > + qcom_geni_serial_console_takeover(uport, true); > + if (!nbcon_exit_unsafe(wctxt)) > + return; When nbcon_enter_unsafe()/exit_unsafe() report lost ownership, write_thread() returns after console_takeover() has cancelled the active M command, skipping resume_tx(). As far as I can tell TX still recovers: M_IRQ_EN and TX_WATERMARK_REG are untouched, so the latched watermark re-asserts on the drained FIFO and the ISR reaches handle_tx_fifo() with active=false, which re-issues setup_tx() for the remaining xmit_fifo data. Is that the intended fallback? A comment would help, since it is an implicit dependency on the !active recovery path rather than anything local to write_thread(). Thanks, Aniket > + > + while (offset < wctxt->len) { > + /* > + * Printk records can be much larger than the FIFO. Limit one > + * M-side command to 32 source bytes so atomic console output can > + * take over between commands instead of waiting for the record. > + */ > + unsigned int count = min_t(unsigned int, wctxt->len - offset, > + CONSOLE_TX_CHUNK_SIZE); > + > + if (!nbcon_enter_unsafe(wctxt)) > + return; > + > + __qcom_geni_serial_console_write(uport, wctxt->outbuf + offset, > + count); > + offset += count; > + > + if (!nbcon_exit_unsafe(wctxt)) > + return; > + } > + > + if (!nbcon_enter_unsafe(wctxt)) > + return; > + > + qcom_geni_serial_resume_tx(uport); > + nbcon_exit_unsafe(wctxt); > +} > + > +static void qcom_geni_serial_console_write_atomic(struct console *co, > + struct nbcon_write_context *wctxt) > +{ > struct qcom_geni_serial_port *port; > + struct uart_port *uport; > u32 m_irq_en, s_irq_en; > - bool locked = true; > - unsigned long flags; > - > - WARN_ON(co->index < 0 || co->index >= GENI_UART_CONS_PORTS); > > port = get_port_from_line(co->index, true, NULL); > if (IS_ERR(port)) > return; > > uport = &port->uport; > - if (oops_in_progress) > - locked = uart_port_trylock_irqsave(uport, &flags); > - else > - uart_port_lock_irqsave(uport, &flags); > + if (!nbcon_enter_unsafe(wctxt)) > + return; > > m_irq_en = readl(uport->membase + SE_GENI_M_IRQ_EN); > s_irq_en = readl(uport->membase + SE_GENI_S_IRQ_EN); > writel(0, uport->membase + SE_GENI_M_IRQ_EN); > writel(0, uport->membase + SE_GENI_S_IRQ_EN); > > - if (qcom_geni_serial_main_active(uport)) { > - /* Wait for completion or drain FIFO */ > - if (!locked || port->tx_remaining == 0) > - qcom_geni_serial_poll_tx_done(uport); > - else > - qcom_geni_serial_drain_fifo(uport); > - > - qcom_geni_serial_cancel_tx_cmd(uport); > - } > - > - __qcom_geni_serial_console_write(uport, s, count); > + qcom_geni_serial_console_takeover(uport, false); > + __qcom_geni_serial_console_write(uport, wctxt->outbuf, wctxt->len); > > writel(m_irq_en, uport->membase + SE_GENI_M_IRQ_EN); > writel(s_irq_en, uport->membase + SE_GENI_S_IRQ_EN); > + nbcon_exit_unsafe(wctxt); > > - if (locked) > - uart_port_unlock_irqrestore(uport, flags); > + /* Restart TTY data left queued when atomic output canceled M TX. */ > + if (READ_ONCE(port->tx_kick_enabled)) > + irq_work_queue(&port->tx_kick); > +} > + > +static void qcom_geni_serial_console_device_lock(struct console *co, > + unsigned long *flags) > +{ > + struct qcom_geni_serial_port *port; > + > + port = get_port_from_line(co->index, true, NULL); > + if (IS_ERR(port)) > + return; > + > + __uart_port_lock_irqsave(&port->uport, flags); > +} > + > +static void qcom_geni_serial_console_device_unlock(struct console *co, > + unsigned long flags) > +{ > + struct qcom_geni_serial_port *port; > + > + port = get_port_from_line(co->index, true, NULL); > + if (IS_ERR(port)) > + return; > + > + __uart_port_unlock_irqrestore(&port->uport, flags); > } > > static void handle_rx_console(struct uart_port *uport, u32 bytes, bool drop) > @@ -738,6 +817,29 @@ static void qcom_geni_serial_start_tx_fifo(struct uart_port *uport) > writel(irq_en, uport->membase + SE_GENI_M_IRQ_EN); > } > > +/* Caller holds the UART port lock. */ > +static void qcom_geni_serial_resume_tx(struct uart_port *uport) > +{ > + if (!uart_tx_stopped(uport) && > + !kfifo_is_empty(&uport->state->port.xmit_fifo)) > + qcom_geni_serial_start_tx_fifo(uport); > +} > + > +static void qcom_geni_serial_restart_tx(struct irq_work *work) > +{ > + struct qcom_geni_serial_port *port = container_of(work, > + struct qcom_geni_serial_port, tx_kick); > + struct uart_port *uport = &port->uport; > + > + if (!READ_ONCE(port->tx_kick_enabled) || !uport->state || uport->suspended) > + return; > + > + uart_port_lock(uport); > + if (READ_ONCE(port->tx_kick_enabled) && uport->state && !uport->suspended) > + qcom_geni_serial_resume_tx(uport); > + uart_port_unlock(uport); > +} > + > static void qcom_geni_serial_stop_tx_fifo(struct uart_port *uport) > { > u32 irq_en; > @@ -1182,6 +1284,11 @@ static int setup_fifos(struct qcom_geni_serial_port *port) > > static void qcom_geni_serial_shutdown(struct uart_port *uport) > { > + struct qcom_geni_serial_port *port = to_dev_port(uport); > + > + /* Atomic console output queues tx_kick without taking the port lock. */ > + WRITE_ONCE(port->tx_kick_enabled, false); > + irq_work_sync(&port->tx_kick); > disable_irq(uport->irq); > > uart_port_lock_irq(uport); > @@ -1281,6 +1388,7 @@ static int qcom_geni_serial_startup(struct uart_port *uport) > uart_port_unlock_irq(uport); > > enable_irq(uport->irq); > + WRITE_ONCE(port->tx_kick_enabled, true); > > return 0; > } > @@ -1627,10 +1735,13 @@ static void console_unregister(struct uart_driver *drv) > > static struct console cons_ops = { > .name = "ttyMSM", > - .write = qcom_geni_serial_console_write, > + .write_atomic = qcom_geni_serial_console_write_atomic, > + .write_thread = qcom_geni_serial_console_write_thread, > + .device_lock = qcom_geni_serial_console_device_lock, > + .device_unlock = qcom_geni_serial_console_device_unlock, > .device = uart_console_device, > .setup = qcom_geni_console_setup, > - .flags = CON_PRINTBUFFER, > + .flags = CON_PRINTBUFFER | CON_NBCON, > .index = -1, > .data = &qcom_geni_console_driver, > }; > @@ -1946,6 +2057,7 @@ static int qcom_geni_serial_probe(struct platform_device *pdev) > > port->private_data.drv = drv; > uport->private_data = &port->private_data; > + init_irq_work(&port->tx_kick, qcom_geni_serial_restart_tx); > platform_set_drvdata(pdev, port); > > irq_set_status_flags(uport->irq, IRQ_NOAUTOEN); > @@ -2000,6 +2112,8 @@ static void qcom_geni_serial_remove(struct platform_device *pdev) > > atomic_notifier_chain_unregister(&panic_notifier_list, &port->panic_nb); > > + WRITE_ONCE(port->tx_kick_enabled, false); > + irq_work_sync(&port->tx_kick); > dev_pm_clear_wake_irq(&pdev->dev); > device_init_wakeup(&pdev->dev, false); > ida_free(&port_ida, uport->line); >