* [PATCH tty v11 0/2] Convert 8250 to NBCON, take 2
@ 2026-07-29 12:04 John Ogness
2026-07-29 12:04 ` [PATCH tty v11 1/2] serial: 8250: Switch to nbcon console, " John Ogness
2026-07-29 12:04 ` [PATCH tty v11 2/2] Revert "serial: 8250: drop lockdep annotation from serial8250_clear_IER()" John Ogness
0 siblings, 2 replies; 5+ messages in thread
From: John Ogness @ 2026-07-29 12:04 UTC (permalink / raw)
To: Greg Kroah-Hartman, Jiri Slaby
Cc: Andy Shevchenko, Petr Mladek, Sebastian Andrzej Siewior,
linux-kernel, Ilpo Järvinen, Andy Shevchenko,
Hugo Villeneuve, Kees Cook, Stepan Ionichev, Xin Zhao,
Osama Abdelkader, Fushuai Wang, Marco Felsch, linux-serial
Hi,
This is v11 of a series to convert the 8250 driver to an NBCON
console, providing both threaded and atomic printing
implementations. v10 of this series is here [0].
Note that v5 was pulled into the 6.14 merge window, but was
then reverted before the 6.14-rc1 release due to kernel test
robot problems. It turned out that the problems were related
to general 8250 driver issues. Those issues have now been
addressed mainline, paving the way for a second take at
converting the 8250 driver to NBCON.
The changes since v10:
- Change the main write functions to be helper functions:
serial8250_console_fifo_write() -> __serial8250_console_fifo_write()
serial8250_console_byte_write() -> __serial8250_console_byte_write()
These functions take additional char array and size arguments for
text to write. This allows the functions to be used for special
text. In particular, writing the extra '\n' after a
handover/takeover.
- Add new serial8250_console_fifo_write() and
serial8250_console_byte_write() functions that are simple wrappers
for the respective helpers.
- Add a __serial8250_console_write() function to handle calling the
appropriate write functions for the various use_fifo/!use_fifo and
console_line_ended/!console_line_ended scenarios. This not only
fixes a possible FIFO overrun pointed out by Sashiko for the
"use_fifo && !console_line_ended" case, but also also consolidates
all the actual writing code into a single helper.
- Toggle unsafe after 2 potentially large delays in the post-write
part of serial8250_console_write(). This reduces the likelihood of
corruption in panic output by providing more points to recognize
ownership transfer.
John Ogness
[0] https://lore.kernel.org/lkml/20260727145332.231949-1-john.ogness@linutronix.de
John Ogness (2):
serial: 8250: Switch to nbcon console, take 2
Revert "serial: 8250: drop lockdep annotation from
serial8250_clear_IER()"
drivers/tty/serial/8250/8250.h | 4 +-
drivers/tty/serial/8250/8250_core.c | 65 ++++++--
drivers/tty/serial/8250/8250_dw.c | 2 +-
drivers/tty/serial/8250/8250_port.c | 245 +++++++++++++++++++++++-----
include/linux/serial_8250.h | 16 +-
5 files changed, 277 insertions(+), 55 deletions(-)
base-commit: a5339effdd523b4b3502fba5358d44e83c76a565
--
2.47.3
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH tty v11 1/2] serial: 8250: Switch to nbcon console, take 2
2026-07-29 12:04 [PATCH tty v11 0/2] Convert 8250 to NBCON, take 2 John Ogness
@ 2026-07-29 12:04 ` John Ogness
2026-07-29 12:44 ` John Ogness
2026-07-30 14:24 ` Petr Mladek
2026-07-29 12:04 ` [PATCH tty v11 2/2] Revert "serial: 8250: drop lockdep annotation from serial8250_clear_IER()" John Ogness
1 sibling, 2 replies; 5+ messages in thread
From: John Ogness @ 2026-07-29 12:04 UTC (permalink / raw)
To: Greg Kroah-Hartman, Jiri Slaby
Cc: Andy Shevchenko, Petr Mladek, Sebastian Andrzej Siewior,
linux-kernel, Ilpo Järvinen, Andy Shevchenko,
Hugo Villeneuve, Kees Cook, Stepan Ionichev, Xin Zhao,
Osama Abdelkader, Fushuai Wang, Marco Felsch, linux-serial
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 hardware access in the callbacks is 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. During suspend/resume the irq_work is not
used as this has been shown to cause suspend problems for some
hardware. Upon resume, any pending modem control is performed.
Note: A new __serial8250_clear_IER() is introduced for direct
clearing of UART_IER during console writing (which will not be
holding the port lock for atomic printing or KDB/KGDB). This
allows restoring a lockdep check to serial8250_clear_IER() in
a follow-up commit.
Signed-off-by: John Ogness <john.ogness@linutronix.de>
---
drivers/tty/serial/8250/8250.h | 4 +-
drivers/tty/serial/8250/8250_core.c | 65 ++++++--
drivers/tty/serial/8250/8250_dw.c | 2 +-
drivers/tty/serial/8250/8250_port.c | 242 +++++++++++++++++++++++-----
include/linux/serial_8250.h | 16 +-
5 files changed, 274 insertions(+), 55 deletions(-)
diff --git a/drivers/tty/serial/8250/8250.h b/drivers/tty/serial/8250/8250.h
index b62f88eec881f..d672b014ade02 100644
--- a/drivers/tty/serial/8250/8250.h
+++ b/drivers/tty/serial/8250/8250.h
@@ -178,7 +178,9 @@ static unsigned int __maybe_unused serial_icr_read(struct uart_8250_port *up,
void serial8250_clear_fifos(struct uart_8250_port *p);
void serial8250_clear_and_reinit_fifos(struct uart_8250_port *p);
-void serial8250_fifo_wait_for_lsr_thre(struct uart_8250_port *up, unsigned int count);
+void serial8250_fifo_wait_for_lsr_thre(struct uart_8250_port *up,
+ struct nbcon_write_context *wctxt,
+ unsigned int count);
void serial8250_rpm_get(struct uart_8250_port *p);
void serial8250_rpm_put(struct uart_8250_port *p);
diff --git a/drivers/tty/serial/8250/8250_core.c b/drivers/tty/serial/8250/8250_core.c
index c0e8a4efbdcc8..5042a8e8c6fb1 100644
--- a/drivers/tty/serial/8250/8250_core.c
+++ b/drivers/tty/serial/8250/8250_core.c
@@ -390,12 +390,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)
@@ -496,12 +518,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,
};
@@ -584,13 +609,19 @@ void serial8250_suspend_port(int line)
struct uart_8250_port *up = &serial8250_ports[line];
struct uart_port *port = &up->port;
- if (!console_suspend_enabled && uart_console(port) &&
- port->type != PORT_8250) {
- unsigned char canary = 0xa5;
+ if (uart_console(port)) {
+ /* No irq_work may be queued when suspending */
+ scoped_guard(uart_port_lock_irq, port)
+ up->console_msr_work_allow = false;
+ irq_work_sync(&up->console_msr_work);
- serial_out(up, UART_SCR, canary);
- if (serial_in(up, UART_SCR) == canary)
- up->canary = canary;
+ if (!console_suspend_enabled && port->type != PORT_8250) {
+ unsigned char canary = 0xa5;
+
+ serial_out(up, UART_SCR, canary);
+ if (serial_in(up, UART_SCR) == canary)
+ up->canary = canary;
+ }
}
uart_suspend_port(&serial8250_reg, port);
@@ -620,6 +651,18 @@ void serial8250_resume_port(int line)
port->uartclk = 921600*16;
}
uart_resume_port(&serial8250_reg, port);
+
+ if (uart_console(port)) {
+
+ guard(uart_port_lock_irq)(port);
+
+ /* irq_work allowed again */
+ up->console_msr_work_allow = true;
+
+ /* Handle any pending MSR changes */
+ if (up->msr_saved_flags)
+ serial8250_modem_status(up);
+ }
}
EXPORT_SYMBOL(serial8250_resume_port);
diff --git a/drivers/tty/serial/8250/8250_dw.c b/drivers/tty/serial/8250/8250_dw.c
index 5fba913f33010..51d026f20825a 100644
--- a/drivers/tty/serial/8250/8250_dw.c
+++ b/drivers/tty/serial/8250/8250_dw.c
@@ -156,7 +156,7 @@ static int dw8250_idle_enter(struct uart_port *p)
*
* FIXME: frame_time delay is too long with very low baudrates.
*/
- serial8250_fifo_wait_for_lsr_thre(up, p->fifosize);
+ serial8250_fifo_wait_for_lsr_thre(up, NULL, p->fifosize);
ndelay(p->frame_time);
serial_port_out(p, UART_MCR, up->mcr | UART_MCR_LOOP);
diff --git a/drivers/tty/serial/8250/8250_port.c b/drivers/tty/serial/8250/8250_port.c
index 8c241ec7f4f29..b9ea4f898474e 100644
--- a/drivers/tty/serial/8250/8250_port.c
+++ b/drivers/tty/serial/8250/8250_port.c
@@ -704,7 +704,12 @@ static void serial8250_clear_interrupts(struct uart_port *port)
serial_port_in(port, UART_MSR);
}
-static void serial8250_clear_IER(struct uart_8250_port *up)
+/*
+ * Only to be directly used by serial8250_console_write() and
+ * serial8250_put_poll_char(), which do 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);
@@ -712,6 +717,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);
+}
+
/*
* This is a quickie test to see how big the FIFO is.
* It doesn't work at all the time, more's the pity.
@@ -1284,9 +1294,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
@@ -1302,6 +1309,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);
}
@@ -2053,10 +2070,13 @@ static void serial8250_put_poll_char(struct uart_port *port,
guard(serial8250_rpm)(up);
/*
- * 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 KDB/KGDB printing
+ * is synchronized via CPU quiescence without holding the port
+ * lock.
*/
ier = serial_port_in(port, UART_IER);
- serial8250_clear_IER(up);
+ __serial8250_clear_IER(up);
wait_for_xmitr(up, UART_LSR_BOTH_EMPTY);
/*
@@ -3198,13 +3218,32 @@ void serial8250_set_defaults(struct uart_8250_port *up)
}
EXPORT_SYMBOL_GPL(serial8250_set_defaults);
-void serial8250_fifo_wait_for_lsr_thre(struct uart_8250_port *up, unsigned int count)
+void serial8250_fifo_wait_for_lsr_thre(struct uart_8250_port *up,
+ struct nbcon_write_context *wctxt,
+ unsigned int count)
{
unsigned int i;
+ /*
+ * For console writing, enter/exit an unsafe section for each byte
+ * in order to pass the ownership as quickly as possible if a higher
+ * priority context wants ownership. Otherwise, an attempt to take
+ * over the ownership might timeout. The new owner will wait for
+ * UART_LSR_THRE before reusing the fifo.
+ */
for (i = 0; i < count; i++) {
- if (wait_for_lsr(up, UART_LSR_THRE))
+ bool tx_ready;
+
+ if (wctxt && !nbcon_enter_unsafe(wctxt))
return;
+
+ tx_ready = wait_for_lsr(up, UART_LSR_THRE);
+
+ if (wctxt)
+ nbcon_exit_unsafe(wctxt);
+
+ if (tx_ready)
+ break;
}
}
EXPORT_SYMBOL_NS_GPL(serial8250_fifo_wait_for_lsr_thre, "SERIAL_8250");
@@ -3213,7 +3252,11 @@ EXPORT_SYMBOL_NS_GPL(serial8250_fifo_wait_for_lsr_thre, "SERIAL_8250");
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)
@@ -3256,8 +3299,9 @@ static void serial8250_console_restore(struct uart_8250_port *up)
* It sends fifosize bytes and then waits for the fifo
* to get empty.
*/
-static void serial8250_console_fifo_write(struct uart_8250_port *up,
- const char *s, unsigned int count)
+static void __serial8250_console_fifo_write(struct uart_8250_port *up,
+ struct nbcon_write_context *wctxt,
+ const char *s, unsigned int count)
{
const char *end = s + count;
unsigned int fifosize = up->tx_loadsz;
@@ -3268,9 +3312,16 @@ static void serial8250_console_fifo_write(struct uart_8250_port *up,
while (s != end) {
/* Allow timeout for each byte of a possibly full FIFO */
- serial8250_fifo_wait_for_lsr_thre(up, fifosize);
+ serial8250_fifo_wait_for_lsr_thre(up, wctxt, fifosize);
+ /*
+ * Fill the FIFO. If a handover or takeover occurs, writing
+ * must be aborted since the string data is 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;
@@ -3278,6 +3329,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;
}
@@ -3286,39 +3339,92 @@ 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
*/
- serial8250_fifo_wait_for_lsr_thre(up, tx_count);
+ serial8250_fifo_wait_for_lsr_thre(up, wctxt, tx_count);
+}
+
+static void serial8250_console_fifo_write(struct uart_8250_port *up,
+ struct nbcon_write_context *wctxt)
+{
+ __serial8250_console_fifo_write(up, wctxt, wctxt->outbuf, wctxt->len);
+}
+
+static void __serial8250_console_byte_write(struct uart_8250_port *up,
+ struct nbcon_write_context *wctxt,
+ const char *s, unsigned int count)
+{
+ struct uart_port *port = &up->port;
+ const char *end = s + count;
+
+ /*
+ * Write out the message. If a handover or takeover occurs, writing
+ * must be aborted since the string data is 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);
+ }
+}
+
+static void serial8250_console_byte_write(struct uart_8250_port *up,
+ struct nbcon_write_context *wctxt)
+{
+ __serial8250_console_byte_write(up, wctxt, wctxt->outbuf, wctxt->len);
+}
+
+/*
+ * Print the console line using the appropriate variant. If ownership is lost
+ * at any time during printing, the printing is aborted.
+ */
+static void __serial8250_console_write(struct uart_8250_port *up,
+ struct nbcon_write_context *wctxt,
+ bool use_fifo)
+{
+ /*
+ * If the 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) {
+ if (use_fifo)
+ __serial8250_console_fifo_write(up, wctxt, "\n", 1);
+ else
+ __serial8250_console_byte_write(up, wctxt, "\n", 1);
+ }
+
+ if (use_fifo)
+ serial8250_console_fifo_write(up, wctxt);
+ else
+ serial8250_console_byte_write(up, 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.
- *
- * Doing runtime PM is really a bad idea for the kernel console.
- * Thus, we assume the function is called when device is powered up.
+ * Print a string to the serial port trying not to disturb
+ * any possible real use of the port...
*/
-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 emergency and panic
+ * printing is synchronized only by nbcon ownership 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))) {
@@ -3352,10 +3458,17 @@ void serial8250_console_write(struct uart_8250_port *up, const char *s,
*/
!uart_console_hwflow_active(&up->port);
- if (likely(use_fifo))
- serial8250_console_fifo_write(up, s, count);
- else
- uart_console_write(port, s, count, serial8250_console_wait_putchar);
+ nbcon_exit_unsafe(wctxt);
+
+ __serial8250_console_write(up, wctxt, use_fifo);
+
+ /*
+ * Re-enter an unsafe section in order to perform final actions
+ * (such as re-enabling interrupts). If ownership was lost, this
+ * context must reacquire ownership.
+ */
+ while (!nbcon_enter_unsafe(wctxt))
+ nbcon_reacquire_nobuf(wctxt);
/*
* Finally, wait for transmitter to become empty
@@ -3365,10 +3478,21 @@ void serial8250_console_write(struct uart_8250_port *up, const char *s,
if (em485) {
mdelay(port->rs485.delay_rts_after_send);
+
+ /* Toggle unsafe after possibly long delay */
+ nbcon_exit_unsafe(wctxt);
+ while (!nbcon_enter_unsafe(wctxt))
+ nbcon_reacquire_nobuf(wctxt);
+
if (em485->tx_stopped)
up->rs485_stop_tx(up, false);
}
+ /* Toggle unsafe after possibly long delay */
+ nbcon_exit_unsafe(wctxt);
+ while (!nbcon_enter_unsafe(wctxt))
+ nbcon_reacquire_nobuf(wctxt);
+
serial_port_out(port, UART_IER, ier);
/*
@@ -3378,11 +3502,25 @@ 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) {
+ if (is_atomic) {
+ /*
+ * For atomic, MSR handling must be deferred to
+ * irq_work because this may be a context that does
+ * not permit waking up tasks.
+ *
+ * But no irq_work may be queued when suspending.
+ * In that case, the MSR handling will occur during
+ * resume in serial8250_resume_port().
+ */
+ if (up->console_msr_work_allow)
+ irq_work_queue(&up->console_msr_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)
@@ -3400,8 +3538,24 @@ static unsigned int probe_baud(struct uart_port *port)
return (port->uartclk / 16) / quot;
}
+/*
+ * irq_work handler to perform modem control during console output.
+ * Only triggered via ->write_atomic() callback because it may be
+ * in a scheduler or NMI context, unable to wake tasks.
+ */
+static void console_msr_handler(struct irq_work *iwp)
+{
+ struct uart_8250_port *up = container_of(iwp, struct uart_8250_port, console_msr_work);
+ struct uart_port *port = &up->port;
+
+ guard(uart_port_lock)(port);
+
+ serial8250_modem_status(up);
+}
+
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';
@@ -3411,6 +3565,10 @@ int serial8250_console_setup(struct uart_port *port, char *options, bool probe)
if (!port->iobase && !port->membase)
return -ENODEV;
+ up->console_line_ended = true;
+ up->console_msr_work_allow = true;
+ init_irq_work(&up->console_msr_work, console_msr_handler);
+
if (options)
uart_parse_options(options, &baud, &parity, &bits, &flow);
else if (probe)
@@ -3431,6 +3589,10 @@ int serial8250_console_setup(struct uart_port *port, char *options, bool probe)
int serial8250_console_exit(struct uart_port *port)
{
+ struct uart_8250_port *up = up_to_u8250p(port);
+
+ irq_work_sync(&up->console_msr_work);
+
if (port->dev)
pm_runtime_put_sync(port->dev);
diff --git a/include/linux/serial_8250.h b/include/linux/serial_8250.h
index a95b2d143d248..eba36710bc476 100644
--- a/include/linux/serial_8250.h
+++ b/include/linux/serial_8250.h
@@ -150,8 +150,20 @@ 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;
+
+ /* Allow queuing irq_work for MSR handling */
+ bool console_msr_work_allow;
+
#define MSR_SAVE_FLAGS UART_MSR_ANY_DELTA
unsigned char msr_saved_flags;
+ struct irq_work console_msr_work;
struct uart_8250_dma *dma;
const struct uart_8250_ops *ops;
@@ -203,8 +215,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);
--
2.47.3
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH tty v11 2/2] Revert "serial: 8250: drop lockdep annotation from serial8250_clear_IER()"
2026-07-29 12:04 [PATCH tty v11 0/2] Convert 8250 to NBCON, take 2 John Ogness
2026-07-29 12:04 ` [PATCH tty v11 1/2] serial: 8250: Switch to nbcon console, " John Ogness
@ 2026-07-29 12:04 ` John Ogness
1 sibling, 0 replies; 5+ messages in thread
From: John Ogness @ 2026-07-29 12:04 UTC (permalink / raw)
To: Greg Kroah-Hartman, Jiri Slaby
Cc: Andy Shevchenko, Petr Mladek, Sebastian Andrzej Siewior,
linux-kernel, Ilpo Järvinen, Andy Shevchenko, Marco Felsch,
Hugo Villeneuve, linux-serial
This reverts commit 3d9e6f556e235ddcdc9f73600fdd46fe1736b090.
The 8250 driver no longer depends on @oops_in_progress and
will no longer violate the port->lock locking constraints.
Signed-off-by: John Ogness <john.ogness@linutronix.de>
Reviewed-by: Petr Mladek <pmladek@suse.com>
---
drivers/tty/serial/8250/8250_port.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/drivers/tty/serial/8250/8250_port.c b/drivers/tty/serial/8250/8250_port.c
index b9ea4f898474e..6c10bff109701 100644
--- a/drivers/tty/serial/8250/8250_port.c
+++ b/drivers/tty/serial/8250/8250_port.c
@@ -719,6 +719,9 @@ static void __serial8250_clear_IER(struct uart_8250_port *up)
static inline void serial8250_clear_IER(struct uart_8250_port *up)
{
+ /* Port locked to synchronize UART_IER access against the console */
+ lockdep_assert_held_once(&up->port.lock);
+
__serial8250_clear_IER(up);
}
--
2.47.3
^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH tty v11 1/2] serial: 8250: Switch to nbcon console, take 2
2026-07-29 12:04 ` [PATCH tty v11 1/2] serial: 8250: Switch to nbcon console, " John Ogness
@ 2026-07-29 12:44 ` John Ogness
2026-07-30 14:24 ` Petr Mladek
1 sibling, 0 replies; 5+ messages in thread
From: John Ogness @ 2026-07-29 12:44 UTC (permalink / raw)
To: Greg Kroah-Hartman, Jiri Slaby
Cc: Andy Shevchenko, Petr Mladek, Sebastian Andrzej Siewior,
linux-kernel, Ilpo Järvinen, Andy Shevchenko,
Hugo Villeneuve, Kees Cook, Stepan Ionichev, Xin Zhao,
Osama Abdelkader, Fushuai Wang, Marco Felsch, linux-serial
Hi,
Sashiko did not find anything legitimate with this version. The two
issues are incorrect (I explain why below).
If anyone has been waiting for Sashiko to "finish", this is the version
that is ready for human review.
John
On 2026-07-29, Sashiko wrote:
> > diff --git a/drivers/tty/serial/8250/8250_port.c b/drivers/tty/serial/8250/8250_port.c
> > index 8c241ec7f4f29..b9ea4f898474e 100644
> > --- a/drivers/tty/serial/8250/8250_port.c
> > +++ b/drivers/tty/serial/8250/8250_port.c
> > @@ -3286,39 +3339,92 @@ static void serial8250_console_fifo_write(struct uart_8250_port *up,
> [ ... ]
> > /*
> > - * 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.
> > - *
> > - * Doing runtime PM is really a bad idea for the kernel console.
> > - * Thus, we assume the function is called when device is powered up.
> > + * Print a string to the serial port trying not to disturb
> > + * any possible real use of the port...
> > */
> > -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 emergency and panic
> > + * printing is synchronized only by nbcon ownership without
> > + * holding the port lock.
> > */
> > ier = serial_port_in(port, UART_IER);
> > - serial8250_clear_IER(up);
> > + __serial8250_clear_IER(up);
>
> Will this race with normal driver paths since the port lock is no longer
> acquired for atomic printing?
No. This is synchronized by nbcon ownership, which normal driver paths
also acquire via the port->lock locking wrappers. It is even within an
unsafe section.
> > @@ -3378,11 +3502,25 @@ 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) {
> > + if (is_atomic) {
> > + /*
> > + * For atomic, MSR handling must be deferred to
> > + * irq_work because this may be a context that does
> > + * not permit waking up tasks.
> > + *
> > + * But no irq_work may be queued when suspending.
> > + * In that case, the MSR handling will occur during
> > + * resume in serial8250_resume_port().
> > + */
> > + if (up->console_msr_work_allow)
> > + irq_work_queue(&up->console_msr_work);
>
> Is there a race condition here between checking the flag and queuing the
> work?
Ditto.
John
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH tty v11 1/2] serial: 8250: Switch to nbcon console, take 2
2026-07-29 12:04 ` [PATCH tty v11 1/2] serial: 8250: Switch to nbcon console, " John Ogness
2026-07-29 12:44 ` John Ogness
@ 2026-07-30 14:24 ` Petr Mladek
1 sibling, 0 replies; 5+ messages in thread
From: Petr Mladek @ 2026-07-30 14:24 UTC (permalink / raw)
To: John Ogness
Cc: Greg Kroah-Hartman, Jiri Slaby, Andy Shevchenko,
Sebastian Andrzej Siewior, linux-kernel, Ilpo Järvinen,
Andy Shevchenko, Hugo Villeneuve, Kees Cook, Stepan Ionichev,
Xin Zhao, Osama Abdelkader, Fushuai Wang, Marco Felsch,
linux-serial
On Wed 2026-07-29 14:10:33, John Ogness wrote:
> 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 hardware access in the callbacks is 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. During suspend/resume the irq_work is not
> used as this has been shown to cause suspend problems for some
> hardware. Upon resume, any pending modem control is performed.
>
> Note: A new __serial8250_clear_IER() is introduced for direct
> clearing of UART_IER during console writing (which will not be
> holding the port lock for atomic printing or KDB/KGDB). This
> allows restoring a lockdep check to serial8250_clear_IER() in
> a follow-up commit.
>
> Signed-off-by: John Ogness <john.ogness@linutronix.de>
> ---
> drivers/tty/serial/8250/8250.h | 4 +-
> drivers/tty/serial/8250/8250_core.c | 65 ++++++--
> drivers/tty/serial/8250/8250_dw.c | 2 +-
> drivers/tty/serial/8250/8250_port.c | 242 +++++++++++++++++++++++-----
> include/linux/serial_8250.h | 16 +-
> 5 files changed, 274 insertions(+), 55 deletions(-)
>
> diff --git a/drivers/tty/serial/8250/8250.h b/drivers/tty/serial/8250/8250.h
> index b62f88eec881f..d672b014ade02 100644
> --- a/drivers/tty/serial/8250/8250.h
> +++ b/drivers/tty/serial/8250/8250.h
> @@ -178,7 +178,9 @@ static unsigned int __maybe_unused serial_icr_read(struct uart_8250_port *up,
>
> void serial8250_clear_fifos(struct uart_8250_port *p);
> void serial8250_clear_and_reinit_fifos(struct uart_8250_port *p);
> -void serial8250_fifo_wait_for_lsr_thre(struct uart_8250_port *up, unsigned int count);
> +void serial8250_fifo_wait_for_lsr_thre(struct uart_8250_port *up,
> + struct nbcon_write_context *wctxt,
> + unsigned int count);
>
> void serial8250_rpm_get(struct uart_8250_port *p);
> void serial8250_rpm_put(struct uart_8250_port *p);
> diff --git a/drivers/tty/serial/8250/8250_core.c b/drivers/tty/serial/8250/8250_core.c
> index c0e8a4efbdcc8..5042a8e8c6fb1 100644
> --- a/drivers/tty/serial/8250/8250_core.c
> +++ b/drivers/tty/serial/8250/8250_core.c
> @@ -390,12 +390,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)
> @@ -496,12 +518,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,
> };
> @@ -584,13 +609,19 @@ void serial8250_suspend_port(int line)
> struct uart_8250_port *up = &serial8250_ports[line];
> struct uart_port *port = &up->port;
>
> - if (!console_suspend_enabled && uart_console(port) &&
> - port->type != PORT_8250) {
> - unsigned char canary = 0xa5;
> + if (uart_console(port)) {
> + /* No irq_work may be queued when suspending */
> + scoped_guard(uart_port_lock_irq, port)
> + up->console_msr_work_allow = false;
> + irq_work_sync(&up->console_msr_work);
>
> - serial_out(up, UART_SCR, canary);
> - if (serial_in(up, UART_SCR) == canary)
> - up->canary = canary;
> + if (!console_suspend_enabled && port->type != PORT_8250) {
> + unsigned char canary = 0xa5;
> +
> + serial_out(up, UART_SCR, canary);
> + if (serial_in(up, UART_SCR) == canary)
> + up->canary = canary;
> + }
> }
>
> uart_suspend_port(&serial8250_reg, port);
> @@ -620,6 +651,18 @@ void serial8250_resume_port(int line)
> port->uartclk = 921600*16;
> }
> uart_resume_port(&serial8250_reg, port);
> +
> + if (uart_console(port)) {
> +
> + guard(uart_port_lock_irq)(port);
> +
> + /* irq_work allowed again */
> + up->console_msr_work_allow = true;
> +
> + /* Handle any pending MSR changes */
> + if (up->msr_saved_flags)
> + serial8250_modem_status(up);
> + }
> }
> EXPORT_SYMBOL(serial8250_resume_port);
>
> diff --git a/drivers/tty/serial/8250/8250_dw.c b/drivers/tty/serial/8250/8250_dw.c
> index 5fba913f33010..51d026f20825a 100644
> --- a/drivers/tty/serial/8250/8250_dw.c
> +++ b/drivers/tty/serial/8250/8250_dw.c
> @@ -156,7 +156,7 @@ static int dw8250_idle_enter(struct uart_port *p)
> *
> * FIXME: frame_time delay is too long with very low baudrates.
> */
> - serial8250_fifo_wait_for_lsr_thre(up, p->fifosize);
> + serial8250_fifo_wait_for_lsr_thre(up, NULL, p->fifosize);
> ndelay(p->frame_time);
>
> serial_port_out(p, UART_MCR, up->mcr | UART_MCR_LOOP);
> diff --git a/drivers/tty/serial/8250/8250_port.c b/drivers/tty/serial/8250/8250_port.c
> index 8c241ec7f4f29..b9ea4f898474e 100644
> --- a/drivers/tty/serial/8250/8250_port.c
> +++ b/drivers/tty/serial/8250/8250_port.c
> @@ -704,7 +704,12 @@ static void serial8250_clear_interrupts(struct uart_port *port)
> serial_port_in(port, UART_MSR);
> }
>
> -static void serial8250_clear_IER(struct uart_8250_port *up)
> +/*
> + * Only to be directly used by serial8250_console_write() and
> + * serial8250_put_poll_char(), which do 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);
> @@ -712,6 +717,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);
> +}
> +
> /*
> * This is a quickie test to see how big the FIFO is.
> * It doesn't work at all the time, more's the pity.
> @@ -1284,9 +1294,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
> @@ -1302,6 +1309,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);
> }
> @@ -2053,10 +2070,13 @@ static void serial8250_put_poll_char(struct uart_port *port,
>
> guard(serial8250_rpm)(up);
> /*
> - * 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 KDB/KGDB printing
> + * is synchronized via CPU quiescence without holding the port
> + * lock.
> */
> ier = serial_port_in(port, UART_IER);
> - serial8250_clear_IER(up);
> + __serial8250_clear_IER(up);
>
> wait_for_xmitr(up, UART_LSR_BOTH_EMPTY);
> /*
> @@ -3198,13 +3218,32 @@ void serial8250_set_defaults(struct uart_8250_port *up)
> }
> EXPORT_SYMBOL_GPL(serial8250_set_defaults);
>
> -void serial8250_fifo_wait_for_lsr_thre(struct uart_8250_port *up, unsigned int count)
> +void serial8250_fifo_wait_for_lsr_thre(struct uart_8250_port *up,
> + struct nbcon_write_context *wctxt,
> + unsigned int count)
> {
> unsigned int i;
>
> + /*
> + * For console writing, enter/exit an unsafe section for each byte
> + * in order to pass the ownership as quickly as possible if a higher
> + * priority context wants ownership. Otherwise, an attempt to take
> + * over the ownership might timeout. The new owner will wait for
> + * UART_LSR_THRE before reusing the fifo.
> + */
> for (i = 0; i < count; i++) {
> - if (wait_for_lsr(up, UART_LSR_THRE))
> + bool tx_ready;
> +
> + if (wctxt && !nbcon_enter_unsafe(wctxt))
> return;
> +
> + tx_ready = wait_for_lsr(up, UART_LSR_THRE);
> +
> + if (wctxt)
> + nbcon_exit_unsafe(wctxt);
> +
> + if (tx_ready)
> + break;
> }
> }
> EXPORT_SYMBOL_NS_GPL(serial8250_fifo_wait_for_lsr_thre, "SERIAL_8250");
> @@ -3213,7 +3252,11 @@ EXPORT_SYMBOL_NS_GPL(serial8250_fifo_wait_for_lsr_thre, "SERIAL_8250");
>
> 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)
> @@ -3256,8 +3299,9 @@ static void serial8250_console_restore(struct uart_8250_port *up)
> * It sends fifosize bytes and then waits for the fifo
> * to get empty.
> */
> -static void serial8250_console_fifo_write(struct uart_8250_port *up,
> - const char *s, unsigned int count)
> +static void __serial8250_console_fifo_write(struct uart_8250_port *up,
> + struct nbcon_write_context *wctxt,
> + const char *s, unsigned int count)
> {
> const char *end = s + count;
> unsigned int fifosize = up->tx_loadsz;
> @@ -3268,9 +3312,16 @@ static void serial8250_console_fifo_write(struct uart_8250_port *up,
>
> while (s != end) {
> /* Allow timeout for each byte of a possibly full FIFO */
> - serial8250_fifo_wait_for_lsr_thre(up, fifosize);
> + serial8250_fifo_wait_for_lsr_thre(up, wctxt, fifosize);
>
> + /*
> + * Fill the FIFO. If a handover or takeover occurs, writing
> + * must be aborted since the string data is 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;
> @@ -3278,6 +3329,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;
> }
> @@ -3286,39 +3339,92 @@ 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
> */
> - serial8250_fifo_wait_for_lsr_thre(up, tx_count);
> + serial8250_fifo_wait_for_lsr_thre(up, wctxt, tx_count);
> +}
> +
> +static void serial8250_console_fifo_write(struct uart_8250_port *up,
> + struct nbcon_write_context *wctxt)
> +{
> + __serial8250_console_fifo_write(up, wctxt, wctxt->outbuf, wctxt->len);
> +}
> +
> +static void __serial8250_console_byte_write(struct uart_8250_port *up,
> + struct nbcon_write_context *wctxt,
> + const char *s, unsigned int count)
> +{
> + struct uart_port *port = &up->port;
> + const char *end = s + count;
> +
> + /*
> + * Write out the message. If a handover or takeover occurs, writing
> + * must be aborted since the string data is 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);
> + }
> +}
> +
> +static void serial8250_console_byte_write(struct uart_8250_port *up,
> + struct nbcon_write_context *wctxt)
> +{
> + __serial8250_console_byte_write(up, wctxt, wctxt->outbuf, wctxt->len);
> +}
> +
> +/*
> + * Print the console line using the appropriate variant. If ownership is lost
> + * at any time during printing, the printing is aborted.
> + */
> +static void __serial8250_console_write(struct uart_8250_port *up,
> + struct nbcon_write_context *wctxt,
> + bool use_fifo)
> +{
> + /*
> + * If the 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) {
> + if (use_fifo)
> + __serial8250_console_fifo_write(up, wctxt, "\n", 1);
> + else
> + __serial8250_console_byte_write(up, wctxt, "\n", 1);
> + }
> +
> + if (use_fifo)
> + serial8250_console_fifo_write(up, wctxt);
> + else
> + serial8250_console_byte_write(up, 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.
> - *
> - * Doing runtime PM is really a bad idea for the kernel console.
> - * Thus, we assume the function is called when device is powered up.
> + * Print a string to the serial port trying not to disturb
> + * any possible real use of the port...
> */
> -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 emergency and panic
> + * printing is synchronized only by nbcon ownership 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))) {
> @@ -3352,10 +3458,17 @@ void serial8250_console_write(struct uart_8250_port *up, const char *s,
> */
> !uart_console_hwflow_active(&up->port);
>
> - if (likely(use_fifo))
> - serial8250_console_fifo_write(up, s, count);
> - else
> - uart_console_write(port, s, count, serial8250_console_wait_putchar);
> + nbcon_exit_unsafe(wctxt);
> +
> + __serial8250_console_write(up, wctxt, use_fifo);
> +
> + /*
> + * Re-enter an unsafe section in order to perform final actions
> + * (such as re-enabling interrupts). If ownership was lost, this
> + * context must reacquire ownership.
> + */
> + while (!nbcon_enter_unsafe(wctxt))
> + nbcon_reacquire_nobuf(wctxt);
If we are going to use this on more locations than I would add
a new API, e.g.
/**
* nbcon_enter_unsafe_reacquire - Enter an unsafe region in the driver,
* reacquire when needed.
* @wctxt: The write context that was handed to the write function
*
*
* The function is allowed to reacquire the console ownership to make
* sure that the caller can enter an unsafe region in the driver.
*
* Warning: The caller must not longer access the text buffer. It is
* lost when the reacquire was needed. The function is intended
* for cleanup operations after emitting the message, for example,
* re-enabling interrupts on a used serial port.
*/
void nbcon_enter_unsafe_reacquire(struct nbcon_write_context *wctxt)
{
while (!nbcon_enter_unsafe(wctxt))
nbcon_reacquire_nobuf(wctxt);
}
>
> /*
> * Finally, wait for transmitter to become empty
> @@ -3365,10 +3478,21 @@ void serial8250_console_write(struct uart_8250_port *up, const char *s,
>
> if (em485) {
> mdelay(port->rs485.delay_rts_after_send);
> +
> + /* Toggle unsafe after possibly long delay */
> + nbcon_exit_unsafe(wctxt);
> + while (!nbcon_enter_unsafe(wctxt))
> + nbcon_reacquire_nobuf(wctxt);
The comment describes what the code does but it does not explain why.
My first idea was that it would allow to takeover the ownership
as soon as possible in an emergency context.
But the motivation seems to be to reduce the race with a possible
unsafe takeover in the final panic flush, see
https://lore.kernel.org/all/87ecgntf01.fsf@jogness.linutronix.de/
It opens some questions:
First, is it really necessary to finish the clean up when
this CPU is supposed to be stopped and the panic CPU
is about to enter the infinite loop?
Second, it is actually safe in the emergency context?
For example, the mdelay() is there for a reason. Maybe,
we should repeat it when the ownership has been lost here?
Third, is it necessary to call the mdelay() in the unsafe context?
OK, if it is safe in emergency then it does not harm in panic.
And it might be safe in emergency. We could assume that
the nested owner did the delay as well.
See below.
> if (em485->tx_stopped)
> up->rs485_stop_tx(up, false);
> }
>
> + /* Toggle unsafe after possibly long delay */
> + nbcon_exit_unsafe(wctxt);
> + while (!nbcon_enter_unsafe(wctxt))
> + nbcon_reacquire_nobuf(wctxt);
> serial_port_out(port, UART_IER, ier);
>
> /*
> @@ -3378,11 +3502,25 @@ 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) {
> + if (is_atomic) {
> + /*
> + * For atomic, MSR handling must be deferred to
> + * irq_work because this may be a context that does
> + * not permit waking up tasks.
> + *
> + * But no irq_work may be queued when suspending.
> + * In that case, the MSR handling will occur during
> + * resume in serial8250_resume_port().
> + */
> + if (up->console_msr_work_allow)
> + irq_work_queue(&up->console_msr_work);
> + } else {
> + serial8250_modem_status(up);
> + }
> + }
>
> - if (locked)
> - uart_port_unlock_irqrestore(port, flags);
> + nbcon_exit_unsafe(wctxt);
> }
I think about entering unsafe only when really needed.
Instead of releasing it and taking again immediately.
Something like:
/*
* Re-enter an unsafe section in order to perform final actions
* (such as re-enabling interrupts). If ownership was lost, this
* context must reacquire ownership.
*/
nbcon_enter_unsafe_reacquire(wctxt);
/*
* Finally, wait for transmitter to become empty
* and restore the IER
*/
wait_for_xmitr(up, UART_LSR_BOTH_EMPTY);
/*
* Exit unsafe section after a potentially long wait. It allows
* a safe takeover before another potentially long way. Also
* it reduces the race window when a possible unsafe takeover
* happened unnoticed.
*/
nbcon_exit_unsafe()
if (em485) {
u32 delay;
nbcon_enter_unsafe_reacquire(wctxt);
delay = port->rs485.delay_rts_after_send;
nbcon_exit_unsafe(wctxt);
mdelay(port->rs485.delay_rts_after_send);
nbcon_enter_unsafe_reacquire(wctxt);
if (em485->tx_stopped)
up->rs485_stop_tx(up, false);
nbcon_exit_unsafe(wctxt);
}
nbcon_enter_unsafe_reacquire(wctxt);
serial_port_out(port, UART_IER, ier);
/*
* The receive handling will happen properly because the
* receive ready bit will still be set; it is not cleared
* on read. However, modem control will not, we must
* call it if we have saved something in the saved flags
* while processing with interrupts off.
*/
if (up->msr_saved_flags) {
if (is_atomic) {
/*
* For atomic, MSR handling must be deferred to
* irq_work because this may be a context that does
* not permit waking up tasks.
*
* But no irq_work may be queued when suspending.
* In that case, the MSR handling will occur during
* resume in serial8250_resume_port().
*/
if (up->console_msr_work_allow)
irq_work_queue(&up->console_msr_work);
} else {
serial8250_modem_status(up);
}
}
nbcon_exit_unsafe(wctxt);
}
But I have to say that both approaches are quite hairy. I am not sure
if it is worth it.
> static unsigned int probe_baud(struct uart_port *port)
Otherwise, I do not see any real problems in the code.
Best Regards,
Petr
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-07-30 14:24 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-29 12:04 [PATCH tty v11 0/2] Convert 8250 to NBCON, take 2 John Ogness
2026-07-29 12:04 ` [PATCH tty v11 1/2] serial: 8250: Switch to nbcon console, " John Ogness
2026-07-29 12:44 ` John Ogness
2026-07-30 14:24 ` Petr Mladek
2026-07-29 12:04 ` [PATCH tty v11 2/2] Revert "serial: 8250: drop lockdep annotation from serial8250_clear_IER()" John Ogness
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox