mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH v3 0/1] braille: nbcon: Fix Braille console for NBCON API
@ 2026-10-01  9:39 Petr Mladek
  2026-10-01  9:39 ` [PATCH v3 1/1] braille: nbcon: Allow to use a serial console with NBCON API as Braille console Petr Mladek
  0 siblings, 1 reply; 4+ messages in thread
From: Petr Mladek @ 2026-10-01  9:39 UTC (permalink / raw)
  To: John Ogness
  Cc: Sergey Senozhatsky, Steven Rostedt, Marcos Paulo de Souza,
	Samuel Thibault, Greg Kroah-Hartman, Jiri Slaby,
	Ilpo Järvinen, Hugo Villeneuve, Fushuai Wang, Kees Cook,
	Stepan Ionichev, linux-serial, Manuel Lauss, linux-kernel,
	Petr Mladek

Hi,

this is v3 patch which tries to fix Braille console driver to work
with serial console drivers ported to NBCON API.

I played with the locking. And I came up with another look.
I tried to simulate the legacy locking using the NBCON API.

IMPORTANT: I believe that it should work even in PREEMPT_RT
  because it worked with the legacy serial console drivers
  there. I think that it works because:

    + con->device_lock() is using normal spin_lock, aka rt_spin_lock().
      It should be safe in the VT context even in IRQ context.

    + The VT code can be called from the legacy loop when the related
      ttyX is registered as a console. But the legacy loop is offloaded
      to a dedicated kthread in PREEMT_RT. So, it will never be called
      in a truly atomic context there, except for panic().


Changes against v2:

  + Use con->device_lock() for non-panic case. Try even an unsafe
    acquire in panic(). This simulates the legacy locking using
    NBCON API.

  + Return when the nbcon context can't be acquired in panic().
    The NBCON API is not ready to ignore the lock. Well, it
    should never happen in practice because the unsafe acquire...

  + Avoid the newline prepend logic in all serial consoles
    ported to NBCON API which have it. Add two APIs for
    checking whether either "con" or "wctxt" is in Braille
    mode.

  + Update commit message and comments in the code.


Changes against v1:

  + Merged into one patch. It is hard to explain nbcon_braille*()
    API without the user.

  + Reworked the locking, see the commit message for more details.

  + Fixed the check of CON_NBCON_ATOMIC_UNSAFE [Sashiko]

IMPORTANT: The solution is not safe in PREEMPT_RT. But it is better
	then nothing. We could either solve it in v3. Or we could
	fix it later.

[v1] https://lore.kernel.org/r/20260922072558.98854-1-pmladek@suse.com
[v2] https://lore.kernel.org/r/20260925141729.173943-2-pmladek@suse.com

Petr Mladek (1):
  braille: nbcon: Allow to use a serial console with NBCON API as
    Braille console

 .../accessibility/braille/braille_console.c   | 64 ++++++++++++++-
 drivers/tty/serial/8250/8250_port.c           |  5 +-
 drivers/tty/serial/amba-pl011.c               |  2 +-
 drivers/tty/serial/imx.c                      |  2 +-
 drivers/tty/serial/sifive.c                   |  2 +-
 include/linux/console.h                       | 15 ++++
 kernel/printk/nbcon.c                         | 78 +++++++++++++++++++
 7 files changed, 162 insertions(+), 6 deletions(-)

-- 
2.55.0


^ permalink raw reply	[flat|nested] 4+ messages in thread

* [PATCH v3 1/1] braille: nbcon: Allow to use a serial console with NBCON API as Braille console
  2026-10-01  9:39 [PATCH v3 0/1] braille: nbcon: Fix Braille console for NBCON API Petr Mladek
@ 2026-10-01  9:39 ` Petr Mladek
  2026-10-01 10:48   ` John Ogness
  0 siblings, 1 reply; 4+ messages in thread
From: Petr Mladek @ 2026-10-01  9:39 UTC (permalink / raw)
  To: John Ogness
  Cc: Sergey Senozhatsky, Steven Rostedt, Marcos Paulo de Souza,
	Samuel Thibault, Greg Kroah-Hartman, Jiri Slaby,
	Ilpo Järvinen, Hugo Villeneuve, Fushuai Wang, Kees Cook,
	Stepan Ionichev, linux-serial, Manuel Lauss, linux-kernel,
	Petr Mladek

The Braille console is not registered in console_list. Instead, it is
integrated with the virtual terminal (VT) and shows what is displayed
on the terminal. It writes the data using con->write*() callback
of the associated serial console driver.

braille_write() is called from the VT code under console_lock().
The associated serial console driver can be converted to the NBCON API
though. The situation is similar to flushing nbcon consoles in the legacy
loop when some boot consoles are still registered, see
nbcon_legacy_emit_next_record().

But there is a big difference thouhg. braille_write() is not directly
called from the code paths flushing registered consoles. The VT code
expects that braille_write() succeeds. It does not replay the message
when it can't acquire the ownership. As a result, braille_write():

  + must try harder to get the ownership.

  + has to be synchronized only against non-printk serial console
    which depend nbcon_device_try_acquire() using NBCON_PRIO_NORMAL.

Let's look at it from another side and try to simulate the original
locking using the NBCON API:

  1. Take con->device_lock(), aka the port->lock in the legacy serial
     console driver.

  2. Acquire nbcon context to provide some synchronization for a panic()
     context. Use NBCON_PRIO_NORMAL because it fightling only against
     nbcon_device_try_acquire() users. Do it in a busy loop. It should
     always succeed when con->device_lock() succeeded because all other
     users do the same. The only exception is when the context get acquired
     by a CPU handling panic.

  3. In panic, disable interrupts and try to acquire the nbcon context.
     Use NBCON_PRIO_PANIC. And try even an unsafe takeover because
     otherwise the Braille console won't see the text shown during panic().
     It is similar to the oops_in_progress/trylock handling in the legacy
     serial console driver.

Finally, avoid the newline prepending logic in the existing serial console
drivers when they are used as a Braille console. As explained above,
the Braille console shows the last modified line on the terminal (VT).
braille_write() is called when single characters are added. Most
messages are not ended by newline. Anyway, the VT code does not have
logic to reply partially printed messages.

Fixes: 13189fa73afa ("printk: nbcon: Rely on kthreads for normal operation")
Signed-off-by: Petr Mladek <pmladek@suse.com>
---
 .../accessibility/braille/braille_console.c   | 64 ++++++++++++++-
 drivers/tty/serial/8250/8250_port.c           |  5 +-
 drivers/tty/serial/amba-pl011.c               |  2 +-
 drivers/tty/serial/imx.c                      |  2 +-
 drivers/tty/serial/sifive.c                   |  2 +-
 include/linux/console.h                       | 15 ++++
 kernel/printk/nbcon.c                         | 78 +++++++++++++++++++
 7 files changed, 162 insertions(+), 6 deletions(-)

diff --git a/drivers/accessibility/braille/braille_console.c b/drivers/accessibility/braille/braille_console.c
index 06b43b678d6e..78611019b671 100644
--- a/drivers/accessibility/braille/braille_console.c
+++ b/drivers/accessibility/braille/braille_console.c
@@ -62,14 +62,45 @@ static void braille_write(u16 *buf)
 {
 	static u16 lastwrite[WIDTH];
 	unsigned char data[1 + 1 + 2*WIDTH + 2 + 1], csum = 0, *c;
+	struct nbcon_write_context wctxt = { };
+	unsigned long flags;
+	bool locked;
 	u16 out;
 	int i;
 
 	if (!braille_co)
 		return;
 
+	/*
+	 * Braille console is not registered in console_list. Instead, it
+	 * is integrated with VT and shows what appears on the graphical
+	 * console under console_lock(). From this POV it is a legacy
+	 * console. But is calls serial console driver which might be
+	 * converted to the NBCON API. It is similar to
+	 * nbcon_legacy_emit_next_record() except that we should try
+	 * harder to get the lock. Othewise, the Braille device won't show
+	 * everything what is displayed on the terminal.
+	 *
+	 * In short, simulate the original locking using NBCON API.
+	 */
+	if (braille_co->flags & CON_NBCON) {
+		if (panic_on_this_cpu()) {
+			local_irq_save(flags);
+			locked = nbcon_braille_try_acquire(braille_co, &wctxt);
+			/* NBCON API strictly requires the ownership. */
+			if (!locked) {
+				local_irq_restore(flags);
+				return;
+			}
+		} else {
+			braille_co->device_lock(braille_co, &flags);
+			while (!nbcon_braille_try_acquire(braille_co, &wctxt))
+				cpu_relax();
+		}
+	}
+
 	if (!memcmp(lastwrite, buf, WIDTH * sizeof(*buf)))
-		return;
+		goto unlock_nbcon;
 	memcpy(lastwrite, buf, WIDTH * sizeof(*buf));
 
 #define SOH 1
@@ -102,7 +133,27 @@ static void braille_write(u16 *buf)
 	*c++ = csum;
 	*c++ = ETX;
 
-	braille_co->write(braille_co, data, c - data);
+	if (braille_co->flags & CON_NBCON) {
+		nbcon_write_context_set_buf(&wctxt, (char *)data, c - data);
+		if (panic_on_this_cpu())
+			braille_co->write_atomic(braille_co, &wctxt);
+		else
+			braille_co->write_thread(braille_co, &wctxt);
+	} else {
+		braille_co->write(braille_co, data, c - data);
+	}
+
+unlock_nbcon:
+	if (braille_co->flags & CON_NBCON) {
+		if (panic_on_this_cpu()) {
+			if (locked)
+				nbcon_braille_release(&wctxt);
+			local_irq_restore(flags);
+		} else {
+			nbcon_braille_release(&wctxt);
+			braille_co->device_unlock(braille_co, flags);
+		}
+	}
 }
 
 /* Follow the VC cursor*/
@@ -353,13 +404,22 @@ int braille_register_console(struct console *console, int index,
 	if (!console_options)
 		/* Only support VisioBraille for now */
 		console_options = "57600o8";
+
 	if (braille_co)
 		return -ENODEV;
+
+	if (console->flags & CON_NBCON &&
+	    (!console->write_atomic || console->flags & CON_NBCON_ATOMIC_UNSAFE)) {
+		pr_err("Braille console requires a safe braille_co->write_atomic callback\n");
+		return -EINVAL;
+	}
+
 	if (console->setup) {
 		ret = console->setup(console, console_options);
 		if (ret != 0)
 			return ret;
 	}
+
 	console->flags |= CON_ENABLED;
 	console->index = index;
 	braille_co = console;
diff --git a/drivers/tty/serial/8250/8250_port.c b/drivers/tty/serial/8250/8250_port.c
index 38fa45e74a37..548fd68ad1fa 100644
--- a/drivers/tty/serial/8250/8250_port.c
+++ b/drivers/tty/serial/8250/8250_port.c
@@ -3417,8 +3417,11 @@ static void __serial8250_console_write(struct uart_8250_port *up,
 	 * 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.
+	 *
+	 * Braille consoles are an exception. The serial port is not used
+	 * for printk(). The driver is supposed to write exactly what it gets.
 	 */
-	if (!up->console_line_ended) {
+	if (unlikely(!up->console_line_ended && !nbcon_write_context_is_braille(wctxt))) {
 		if (use_fifo)
 			__serial8250_console_fifo_write(up, wctxt, "\n", 1);
 		else
diff --git a/drivers/tty/serial/amba-pl011.c b/drivers/tty/serial/amba-pl011.c
index c4824c201e1c..e67f2ac454b6 100644
--- a/drivers/tty/serial/amba-pl011.c
+++ b/drivers/tty/serial/amba-pl011.c
@@ -2656,7 +2656,7 @@ pl011_console_write_atomic(struct console *co, struct nbcon_write_context *wctxt
 				uap, REG_CR);
 	}
 
-	if (!uap->console_line_ended)
+	if (unlikely(!uap->console_line_ended && !nbcon_is_braille(co)))
 		uart_console_write(&uap->port, "\n", 1, pl011_console_putchar);
 	uart_console_write(&uap->port, wctxt->outbuf, wctxt->len, pl011_console_putchar);
 
diff --git a/drivers/tty/serial/imx.c b/drivers/tty/serial/imx.c
index 513dbe95f7e9..986fefb45c63 100644
--- a/drivers/tty/serial/imx.c
+++ b/drivers/tty/serial/imx.c
@@ -2136,7 +2136,7 @@ static void imx_uart_console_write_atomic(struct console *co,
 	imx_uart_writel(sport, ucr1, UCR1);
 	imx_uart_writel(sport, old_ucr.ucr2 | UCR2_TXEN, UCR2);
 
-	if (!sport->last_putchar_was_newline)
+	if (unlikely(!sport->last_putchar_was_newline && !nbcon_is_braille(co)))
 		uart_console_write(port, "\n", 1, imx_uart_console_putchar);
 	uart_console_write(port, wctxt->outbuf, wctxt->len,
 			   imx_uart_console_putchar);
diff --git a/drivers/tty/serial/sifive.c b/drivers/tty/serial/sifive.c
index 110d67613192..c44183cbdd8b 100644
--- a/drivers/tty/serial/sifive.c
+++ b/drivers/tty/serial/sifive.c
@@ -821,7 +821,7 @@ static void sifive_serial_console_write_atomic(struct console *co,
 	ier = __ssp_readl(ssp, SIFIVE_SERIAL_IE_OFFS);
 	__ssp_writel(0, SIFIVE_SERIAL_IE_OFFS, ssp);
 
-	if (!ssp->console_line_ended)
+	if (unlinely(!ssp->console_line_ended && !nbcon_is_braille(co)))
 		uart_console_write(port, "\n", 1, sifive_serial_console_putchar);
 	uart_console_write(port, wctxt->outbuf, wctxt->len,
 			   sifive_serial_console_putchar);
diff --git a/include/linux/console.h b/include/linux/console.h
index 502d1abe3f50..94b926ad8c9c 100644
--- a/include/linux/console.h
+++ b/include/linux/console.h
@@ -615,6 +615,15 @@ extern bool nbcon_allow_unsafe_takeover(void);
 extern bool nbcon_kdb_try_acquire(struct console *con,
 				  struct nbcon_write_context *wctxt);
 extern void nbcon_kdb_release(struct nbcon_write_context *wctxt);
+extern bool nbcon_write_context_is_braille(struct nbcon_write_context *wctxt);
+extern bool nbcon_braille_try_acquire(struct console *con,
+				      struct nbcon_write_context *wctxt);
+extern void nbcon_braille_release(struct nbcon_write_context *wctxt);
+
+static inline bool nbcon_is_braille(struct console *con)
+{
+	return con && con->flags & CON_BRL;
+}
 
 /*
  * Check if the given console is currently capable and allowed to print
@@ -678,8 +687,14 @@ static inline void nbcon_reacquire_nobuf(struct nbcon_write_context *wctxt) { }
 static inline bool nbcon_kdb_try_acquire(struct console *con,
 					 struct nbcon_write_context *wctxt) { return false; }
 static inline void nbcon_kdb_release(struct nbcon_write_context *wctxt) { }
+static inline
+bool nbcon_write_context_is_braille(struct nbcon_write_context *wctxt) { return false; }
 static inline bool console_is_usable(struct console *con, short flags,
 				     bool use_atomic) { return false; }
+static inline bool nbcon_braille_try_acquire(struct console *con,
+					     struct nbcon_write_context *wctxt) { return false; }
+static inline void nbcon_braille_release(struct nbcon_write_context *wctxt) { }
+static inline bool nbcon_is_braille(struct console *con) { return false; }
 #endif
 
 extern int console_set_on_cmdline;
diff --git a/kernel/printk/nbcon.c b/kernel/printk/nbcon.c
index d17704fe93ae..228c75f90662 100644
--- a/kernel/printk/nbcon.c
+++ b/kernel/printk/nbcon.c
@@ -1887,6 +1887,7 @@ bool nbcon_device_try_acquire(struct console *con)
 
 	memset(ctxt, 0, sizeof(*ctxt));
 	ctxt->console	= con;
+	/* Keep in sync with nbcon_braille_try_acquire(). */
 	ctxt->prio	= NBCON_PRIO_NORMAL;
 
 	if (!nbcon_context_try_acquire(ctxt, false))
@@ -2002,3 +2003,80 @@ void nbcon_kdb_release(struct nbcon_write_context *wctxt)
 	 */
 	__nbcon_atomic_flush_pending_con(ctxt->console, prb_next_reserve_seq(prb));
 }
+
+/**
+ * nbcon_is_braille - Checks whether the nbcon write context is using Braille console
+ *
+ * @wctxt:	checked nbcon write context
+ *
+ * Return: True when the write context is associated with a Braille console.
+ *	   Othrewise, return false.
+ *
+ * Context: Can be called in any context but only when Braille console is
+ *	registered and the struct console could not disappear.
+ */
+bool nbcon_write_context_is_braille(struct nbcon_write_context *wctxt)
+{
+	struct nbcon_context *ctxt = &ACCESS_PRIVATE(wctxt, ctxt);
+	struct console *con = ctxt->console;
+
+	return con && con->flags & CON_BRL;
+}
+
+/**
+ * nbcon_braille_try_acquire - Try to acquire nbcon console for braille_write()
+ *
+ * @con:	The nbcon console to acquire
+ * @wctxt:	The nbcon write context to be used on success
+ *
+ * Context:	braille_write() for emitting a single buffer on Braille console.
+ *
+ * Return:	True if the console was acquired. False otherwise.
+ *
+ * Braille console is not registered as a proper printk consoles. Instead,
+ * it is integrated with the graphical virtual terminal.
+ *
+ * This function is going to synchronize the Braille write against other
+ * operations on the used serial port. The port can be used also for a user
+ * input but printk() won't emit the messages there directly. It means
+ * the other operations will get synchronized using nbcon_device_try_acquire().
+ */
+bool nbcon_braille_try_acquire(struct console *con,
+			   struct nbcon_write_context *wctxt)
+{
+	struct nbcon_context *ctxt = &ACCESS_PRIVATE(wctxt, ctxt);
+	bool success;
+
+	memset(ctxt, 0, sizeof(*ctxt));
+	ctxt->console = con;
+	/* Keep in sync with nbcon_device_try_acquire(). */
+	if (panic_on_this_cpu())
+		ctxt->prio = NBCON_PRIO_PANIC;
+	else
+		ctxt->prio = NBCON_PRIO_NORMAL;
+
+	success = nbcon_context_try_acquire(ctxt, false);
+	/*
+	 * Try hard in panic, othrewise the Braille console would
+	 * miss a text on the related VT.
+	 */
+	if (panic_on_this_cpu() && !success) {
+		ctxt->allow_unsafe_takeover = true;
+		success = nbcon_context_try_acquire(ctxt, false);
+	}
+
+	return success;
+}
+
+/**
+ * nbcon_braille_release - Release the nbcon console
+ *
+ * @wctxt:	The nbcon write context initialized by a successful
+ *		nbcon_braille_try_acquire()
+ */
+void nbcon_braille_release(struct nbcon_write_context *wctxt)
+{
+	struct nbcon_context *ctxt = &ACCESS_PRIVATE(wctxt, ctxt);
+
+	nbcon_context_release(ctxt);
+}
-- 
2.55.0


^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [PATCH v3 1/1] braille: nbcon: Allow to use a serial console with NBCON API as Braille console
  2026-10-01  9:39 ` [PATCH v3 1/1] braille: nbcon: Allow to use a serial console with NBCON API as Braille console Petr Mladek
@ 2026-10-01 10:48   ` John Ogness
  2026-10-01 12:01     ` Petr Mladek
  0 siblings, 1 reply; 4+ messages in thread
From: John Ogness @ 2026-10-01 10:48 UTC (permalink / raw)
  To: Petr Mladek
  Cc: Sergey Senozhatsky, Steven Rostedt, Marcos Paulo de Souza,
	Samuel Thibault, Greg Kroah-Hartman, Jiri Slaby,
	Ilpo Järvinen, Hugo Villeneuve, Fushuai Wang, Kees Cook,
	Stepan Ionichev, linux-serial, Manuel Lauss, linux-kernel,
	Petr Mladek

On 2026-10-01, Petr Mladek <pmladek@suse.com> wrote:
> The Braille console is not registered in console_list. Instead, it is
> integrated with the virtual terminal (VT) and shows what is displayed
> on the terminal. It writes the data using con->write*() callback
> of the associated serial console driver.
>
> braille_write() is called from the VT code under console_lock().
> The associated serial console driver can be converted to the NBCON API
> though. The situation is similar to flushing nbcon consoles in the legacy
> loop when some boot consoles are still registered, see
> nbcon_legacy_emit_next_record().
>
> But there is a big difference thouhg. braille_write() is not directly

                         though ^^^^^^

> called from the code paths flushing registered consoles. The VT code
> expects that braille_write() succeeds. It does not replay the message
> when it can't acquire the ownership. As a result, braille_write():
>
>   + must try harder to get the ownership.
>
>   + has to be synchronized only against non-printk serial console
>     which depend nbcon_device_try_acquire() using NBCON_PRIO_NORMAL.
>
> Let's look at it from another side and try to simulate the original
> locking using the NBCON API:
>
>   1. Take con->device_lock(), aka the port->lock in the legacy serial
>      console driver.
>
>   2. Acquire nbcon context to provide some synchronization for a panic()
>      context. Use NBCON_PRIO_NORMAL because it fightling only against

                              contends only with ^^^^^^^^^^^^^^^^^^^^^^

>      nbcon_device_try_acquire() users. Do it in a busy loop. It should
>      always succeed when con->device_lock() succeeded because all other
>      users do the same. The only exception is when the context get acquired
>      by a CPU handling panic.
>
>   3. In panic, disable interrupts and try to acquire the nbcon context.
>      Use NBCON_PRIO_PANIC. And try even an unsafe takeover because
>      otherwise the Braille console won't see the text shown during panic().
>      It is similar to the oops_in_progress/trylock handling in the legacy
>      serial console driver.
>
> Finally, avoid the newline prepending logic in the existing serial console
> drivers when they are used as a Braille console. As explained above,
> the Braille console shows the last modified line on the terminal (VT).
> braille_write() is called when single characters are added. Most
> messages are not ended by newline. Anyway, the VT code does not have
> logic to reply partially printed messages.
>
> Fixes: 13189fa73afa ("printk: nbcon: Rely on kthreads for normal operation")
> Signed-off-by: Petr Mladek <pmladek@suse.com>
> ---
>  .../accessibility/braille/braille_console.c   | 64 ++++++++++++++-
>  drivers/tty/serial/8250/8250_port.c           |  5 +-
>  drivers/tty/serial/amba-pl011.c               |  2 +-
>  drivers/tty/serial/imx.c                      |  2 +-
>  drivers/tty/serial/sifive.c                   |  2 +-
>  include/linux/console.h                       | 15 ++++
>  kernel/printk/nbcon.c                         | 78 +++++++++++++++++++
>  7 files changed, 162 insertions(+), 6 deletions(-)
>
> diff --git a/drivers/accessibility/braille/braille_console.c b/drivers/accessibility/braille/braille_console.c
> index 06b43b678d6e..78611019b671 100644
> --- a/drivers/accessibility/braille/braille_console.c
> +++ b/drivers/accessibility/braille/braille_console.c
> @@ -62,14 +62,45 @@ static void braille_write(u16 *buf)
>  {
>  	static u16 lastwrite[WIDTH];
>  	unsigned char data[1 + 1 + 2*WIDTH + 2 + 1], csum = 0, *c;
> +	struct nbcon_write_context wctxt = { };
> +	unsigned long flags;
> +	bool locked;

There is no need for @locked because on failure, the function returns.

>  	u16 out;
>  	int i;
>  
>  	if (!braille_co)
>  		return;
>  
> +	/*
> +	 * Braille console is not registered in console_list. Instead, it
> +	 * is integrated with VT and shows what appears on the graphical
> +	 * console under console_lock(). From this POV it is a legacy
> +	 * console. But is calls serial console driver which might be

                     it ^^

> +	 * converted to the NBCON API. It is similar to
> +	 * nbcon_legacy_emit_next_record() except that we should try
> +	 * harder to get the lock. Othewise, the Braille device won't show

                         Otherwise ^^^^^^^^

> +	 * everything what is displayed on the terminal.
> +	 *
> +	 * In short, simulate the original locking using NBCON API.
> +	 */
> +	if (braille_co->flags & CON_NBCON) {
> +		if (panic_on_this_cpu()) {

How about adding here:

			if (!braille_co->write_atomic)
				return;

I see no reason to forbid Braille device usage just because it cannot
show panics.

> +			local_irq_save(flags);
> +			locked = nbcon_braille_try_acquire(braille_co, &wctxt);
> +			/* NBCON API strictly requires the ownership. */
> +			if (!locked) {
> +				local_irq_restore(flags);
> +				return;
> +			}
> +		} else {
> +			braille_co->device_lock(braille_co, &flags);
> +			while (!nbcon_braille_try_acquire(braille_co, &wctxt))
> +				cpu_relax();
> +		}
> +	}
> +
>  	if (!memcmp(lastwrite, buf, WIDTH * sizeof(*buf)))
> -		return;
> +		goto unlock_nbcon;
>  	memcpy(lastwrite, buf, WIDTH * sizeof(*buf));
>  
>  #define SOH 1
> @@ -102,7 +133,27 @@ static void braille_write(u16 *buf)
>  	*c++ = csum;
>  	*c++ = ETX;
>  
> -	braille_co->write(braille_co, data, c - data);
> +	if (braille_co->flags & CON_NBCON) {
> +		nbcon_write_context_set_buf(&wctxt, (char *)data, c - data);
> +		if (panic_on_this_cpu())
> +			braille_co->write_atomic(braille_co, &wctxt);
> +		else
> +			braille_co->write_thread(braille_co, &wctxt);
> +	} else {
> +		braille_co->write(braille_co, data, c - data);
> +	}
> +
> +unlock_nbcon:
> +	if (braille_co->flags & CON_NBCON) {
> +		if (panic_on_this_cpu()) {
> +			if (locked)
> +				nbcon_braille_release(&wctxt);

There will never be a locked=false scenario here. We already returned.

> +			local_irq_restore(flags);
> +		} else {
> +			nbcon_braille_release(&wctxt);
> +			braille_co->device_unlock(braille_co, flags);
> +		}
> +	}
>  }
>  
>  /* Follow the VC cursor*/
> @@ -353,13 +404,22 @@ int braille_register_console(struct console *console, int index,
>  	if (!console_options)
>  		/* Only support VisioBraille for now */
>  		console_options = "57600o8";
> +
>  	if (braille_co)
>  		return -ENODEV;
> +
> +	if (console->flags & CON_NBCON &&
> +	    (!console->write_atomic || console->flags & CON_NBCON_ATOMIC_UNSAFE)) {
> +		pr_err("Braille console requires a safe braille_co->write_atomic callback\n");

IMO it is not necessary to restrict to !CON_NBCON_ATOMIC_UNSAFE consoles
because if the acquire fails, an unsafe acquire is tried anyway. But as
I suggested earlier, I think even NBCON consoles without
->write_atomic() should be allowed. Just no panic message for them.

> +		return -EINVAL;
> +	}
> +
>  	if (console->setup) {
>  		ret = console->setup(console, console_options);
>  		if (ret != 0)
>  			return ret;
>  	}
> +
>  	console->flags |= CON_ENABLED;
>  	console->index = index;
>  	braille_co = console;
> diff --git a/drivers/tty/serial/8250/8250_port.c b/drivers/tty/serial/8250/8250_port.c
> index 38fa45e74a37..548fd68ad1fa 100644
> --- a/drivers/tty/serial/8250/8250_port.c
> +++ b/drivers/tty/serial/8250/8250_port.c
> @@ -3417,8 +3417,11 @@ static void __serial8250_console_write(struct uart_8250_port *up,
>  	 * 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.
> +	 *
> +	 * Braille consoles are an exception. The serial port is not used
> +	 * for printk(). The driver is supposed to write exactly what it gets.
>  	 */
> -	if (!up->console_line_ended) {
> +	if (unlikely(!up->console_line_ended && !nbcon_write_context_is_braille(wctxt))) {
>  		if (use_fifo)
>  			__serial8250_console_fifo_write(up, wctxt, "\n", 1);
>  		else
> diff --git a/drivers/tty/serial/amba-pl011.c b/drivers/tty/serial/amba-pl011.c
> index c4824c201e1c..e67f2ac454b6 100644
> --- a/drivers/tty/serial/amba-pl011.c
> +++ b/drivers/tty/serial/amba-pl011.c
> @@ -2656,7 +2656,7 @@ pl011_console_write_atomic(struct console *co, struct nbcon_write_context *wctxt
>  				uap, REG_CR);
>  	}
>  
> -	if (!uap->console_line_ended)
> +	if (unlikely(!uap->console_line_ended && !nbcon_is_braille(co)))
>  		uart_console_write(&uap->port, "\n", 1, pl011_console_putchar);
>  	uart_console_write(&uap->port, wctxt->outbuf, wctxt->len, pl011_console_putchar);
>  
> diff --git a/drivers/tty/serial/imx.c b/drivers/tty/serial/imx.c
> index 513dbe95f7e9..986fefb45c63 100644
> --- a/drivers/tty/serial/imx.c
> +++ b/drivers/tty/serial/imx.c
> @@ -2136,7 +2136,7 @@ static void imx_uart_console_write_atomic(struct console *co,
>  	imx_uart_writel(sport, ucr1, UCR1);
>  	imx_uart_writel(sport, old_ucr.ucr2 | UCR2_TXEN, UCR2);
>  
> -	if (!sport->last_putchar_was_newline)
> +	if (unlikely(!sport->last_putchar_was_newline && !nbcon_is_braille(co)))
>  		uart_console_write(port, "\n", 1, imx_uart_console_putchar);
>  	uart_console_write(port, wctxt->outbuf, wctxt->len,
>  			   imx_uart_console_putchar);
> diff --git a/drivers/tty/serial/sifive.c b/drivers/tty/serial/sifive.c
> index 110d67613192..c44183cbdd8b 100644
> --- a/drivers/tty/serial/sifive.c
> +++ b/drivers/tty/serial/sifive.c
> @@ -821,7 +821,7 @@ static void sifive_serial_console_write_atomic(struct console *co,
>  	ier = __ssp_readl(ssp, SIFIVE_SERIAL_IE_OFFS);
>  	__ssp_writel(0, SIFIVE_SERIAL_IE_OFFS, ssp);
>  
> -	if (!ssp->console_line_ended)
> +	if (unlinely(!ssp->console_line_ended && !nbcon_is_braille(co)))

   unlikely ^^^^^^^^

>  		uart_console_write(port, "\n", 1, sifive_serial_console_putchar);
>  	uart_console_write(port, wctxt->outbuf, wctxt->len,
>  			   sifive_serial_console_putchar);
> diff --git a/include/linux/console.h b/include/linux/console.h
> index 502d1abe3f50..94b926ad8c9c 100644
> --- a/include/linux/console.h
> +++ b/include/linux/console.h
> @@ -615,6 +615,15 @@ extern bool nbcon_allow_unsafe_takeover(void);
>  extern bool nbcon_kdb_try_acquire(struct console *con,
>  				  struct nbcon_write_context *wctxt);
>  extern void nbcon_kdb_release(struct nbcon_write_context *wctxt);
> +extern bool nbcon_write_context_is_braille(struct nbcon_write_context *wctxt);
> +extern bool nbcon_braille_try_acquire(struct console *con,
> +				      struct nbcon_write_context *wctxt);
> +extern void nbcon_braille_release(struct nbcon_write_context *wctxt);
> +
> +static inline bool nbcon_is_braille(struct console *con)
> +{
> +	return con && con->flags & CON_BRL;
> +}
>  
>  /*
>   * Check if the given console is currently capable and allowed to print
> @@ -678,8 +687,14 @@ static inline void nbcon_reacquire_nobuf(struct nbcon_write_context *wctxt) { }
>  static inline bool nbcon_kdb_try_acquire(struct console *con,
>  					 struct nbcon_write_context *wctxt) { return false; }
>  static inline void nbcon_kdb_release(struct nbcon_write_context *wctxt) { }
> +static inline
> +bool nbcon_write_context_is_braille(struct nbcon_write_context *wctxt) { return false; }
>  static inline bool console_is_usable(struct console *con, short flags,
>  				     bool use_atomic) { return false; }
> +static inline bool nbcon_braille_try_acquire(struct console *con,
> +					     struct nbcon_write_context *wctxt) { return false; }
> +static inline void nbcon_braille_release(struct nbcon_write_context *wctxt) { }
> +static inline bool nbcon_is_braille(struct console *con) { return false; }
>  #endif
>  
>  extern int console_set_on_cmdline;
> diff --git a/kernel/printk/nbcon.c b/kernel/printk/nbcon.c
> index d17704fe93ae..228c75f90662 100644
> --- a/kernel/printk/nbcon.c
> +++ b/kernel/printk/nbcon.c
> @@ -1887,6 +1887,7 @@ bool nbcon_device_try_acquire(struct console *con)
>  
>  	memset(ctxt, 0, sizeof(*ctxt));
>  	ctxt->console	= con;
> +	/* Keep in sync with nbcon_braille_try_acquire(). */
>  	ctxt->prio	= NBCON_PRIO_NORMAL;
>  
>  	if (!nbcon_context_try_acquire(ctxt, false))
> @@ -2002,3 +2003,80 @@ void nbcon_kdb_release(struct nbcon_write_context *wctxt)
>  	 */
>  	__nbcon_atomic_flush_pending_con(ctxt->console, prb_next_reserve_seq(prb));
>  }
> +
> +/**
> + * nbcon_is_braille - Checks whether the nbcon write context is using Braille console
> + *
> + * @wctxt:	checked nbcon write context
> + *
> + * Return: True when the write context is associated with a Braille console.
> + *	   Othrewise, return false.
> + *
> + * Context: Can be called in any context but only when Braille console is
> + *	registered and the struct console could not disappear.
> + */
> +bool nbcon_write_context_is_braille(struct nbcon_write_context *wctxt)
> +{
> +	struct nbcon_context *ctxt = &ACCESS_PRIVATE(wctxt, ctxt);
> +	struct console *con = ctxt->console;
> +
> +	return con && con->flags & CON_BRL;

I suggest parenthesis around "con->flags & CON_BRL".

> +}
> +
> +/**
> + * nbcon_braille_try_acquire - Try to acquire nbcon console for braille_write()
> + *
> + * @con:	The nbcon console to acquire
> + * @wctxt:	The nbcon write context to be used on success
> + *
> + * Context:	braille_write() for emitting a single buffer on Braille console.
> + *
> + * Return:	True if the console was acquired. False otherwise.
> + *
> + * Braille console is not registered as a proper printk consoles. Instead,
> + * it is integrated with the graphical virtual terminal.
> + *
> + * This function is going to synchronize the Braille write against other
> + * operations on the used serial port. The port can be used also for a user
> + * input but printk() won't emit the messages there directly. It means
> + * the other operations will get synchronized using nbcon_device_try_acquire().
> + */
> +bool nbcon_braille_try_acquire(struct console *con,
> +			   struct nbcon_write_context *wctxt)
> +{
> +	struct nbcon_context *ctxt = &ACCESS_PRIVATE(wctxt, ctxt);
> +	bool success;
> +
> +	memset(ctxt, 0, sizeof(*ctxt));
> +	ctxt->console = con;
> +	/* Keep in sync with nbcon_device_try_acquire(). */
> +	if (panic_on_this_cpu())
> +		ctxt->prio = NBCON_PRIO_PANIC;
> +	else
> +		ctxt->prio = NBCON_PRIO_NORMAL;
> +
> +	success = nbcon_context_try_acquire(ctxt, false);
> +	/*
> +	 * Try hard in panic, othrewise the Braille console would

                    otherwise ^^^^^^^^^
> +	 * miss a text on the related VT.
> +	 */
> +	if (panic_on_this_cpu() && !success) {
> +		ctxt->allow_unsafe_takeover = true;
> +		success = nbcon_context_try_acquire(ctxt, false);
> +	}
> +
> +	return success;
> +}
> +
> +/**
> + * nbcon_braille_release - Release the nbcon console
> + *
> + * @wctxt:	The nbcon write context initialized by a successful
> + *		nbcon_braille_try_acquire()
> + */
> +void nbcon_braille_release(struct nbcon_write_context *wctxt)
> +{
> +	struct nbcon_context *ctxt = &ACCESS_PRIVATE(wctxt, ctxt);
> +
> +	nbcon_context_release(ctxt);
> +}

The rest looks OK.

John

^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [PATCH v3 1/1] braille: nbcon: Allow to use a serial console with NBCON API as Braille console
  2026-10-01 10:48   ` John Ogness
@ 2026-10-01 12:01     ` Petr Mladek
  0 siblings, 0 replies; 4+ messages in thread
From: Petr Mladek @ 2026-10-01 12:01 UTC (permalink / raw)
  To: John Ogness
  Cc: Sergey Senozhatsky, Steven Rostedt, Marcos Paulo de Souza,
	Samuel Thibault, Greg Kroah-Hartman, Jiri Slaby,
	Ilpo Järvinen, Hugo Villeneuve, Fushuai Wang, Kees Cook,
	Stepan Ionichev, linux-serial, Manuel Lauss, linux-kernel

On Thu 2026-10-01 12:54:37, John Ogness wrote:
> On 2026-10-01, Petr Mladek <pmladek@suse.com> wrote:
> > The Braille console is not registered in console_list. Instead, it is
> > integrated with the virtual terminal (VT) and shows what is displayed
> > on the terminal. It writes the data using con->write*() callback
> > of the associated serial console driver.
> >
> > --- a/drivers/accessibility/braille/braille_console.c
> > +++ b/drivers/accessibility/braille/braille_console.c
> > @@ -62,14 +62,45 @@ static void braille_write(u16 *buf)
> >  {
> >  	static u16 lastwrite[WIDTH];
> >  	unsigned char data[1 + 1 + 2*WIDTH + 2 + 1], csum = 0, *c;
> > +	struct nbcon_write_context wctxt = { };
> > +	unsigned long flags;
> > +	bool locked;
> 
> There is no need for @locked because on failure, the function returns.

Great catch! I rewrote the the code many times and decided to send
it at some point...

> >  	u16 out;
> >  	int i;
> >  
> >  	if (!braille_co)
> >  		return;
> >  
> > +	/*
> > +	 * Braille console is not registered in console_list. Instead, it
> > +	 * is integrated with VT and shows what appears on the graphical
> > +	 * console under console_lock(). From this POV it is a legacy
> > +	 * console. But is calls serial console driver which might be
> 
>                      it ^^
> 
> > +	 * converted to the NBCON API. It is similar to
> > +	 * nbcon_legacy_emit_next_record() except that we should try
> > +	 * harder to get the lock. Othewise, the Braille device won't show
> 
>                          Otherwise ^^^^^^^^

My muscle memory is clearly wrong for this word.

> > +	 * everything what is displayed on the terminal.
> > +	 *
> > +	 * In short, simulate the original locking using NBCON API.
> > +	 */
> > +	if (braille_co->flags & CON_NBCON) {
> > +		if (panic_on_this_cpu()) {
> 
> How about adding here:
> 
> 			if (!braille_co->write_atomic)
> 				return;
>
> I see no reason to forbid Braille device usage just because it cannot
> show panics.

Fair enough. I am going to add the following in v4:

			/*
			 * This should be good enough in practice. Most/all
			 * serial console drivers have the atomic callback.
			 */
			if (!braille_co->write_atomic)
				return;

> > +			local_irq_save(flags);
> > +			locked = nbcon_braille_try_acquire(braille_co, &wctxt);
> > +			/* NBCON API strictly requires the ownership. */
> > +			if (!locked) {
> > +				local_irq_restore(flags);
> > +				return;
> > +			}
> > +		} else {
> > +			braille_co->device_lock(braille_co, &flags);
> > +			while (!nbcon_braille_try_acquire(braille_co, &wctxt))
> > +				cpu_relax();
> > +		}
> > +	}
> > +
> >  	if (!memcmp(lastwrite, buf, WIDTH * sizeof(*buf)))
> > -		return;
> > +		goto unlock_nbcon;
> >  	memcpy(lastwrite, buf, WIDTH * sizeof(*buf));
> >  
> >  #define SOH 1
> > @@ -102,7 +133,27 @@ static void braille_write(u16 *buf)
> >  	*c++ = csum;
> >  	*c++ = ETX;
> >  
> > -	braille_co->write(braille_co, data, c - data);
> > +	if (braille_co->flags & CON_NBCON) {
> > +		nbcon_write_context_set_buf(&wctxt, (char *)data, c - data);
> > +		if (panic_on_this_cpu())
> > +			braille_co->write_atomic(braille_co, &wctxt);
> > +		else
> > +			braille_co->write_thread(braille_co, &wctxt);
> > +	} else {
> > +		braille_co->write(braille_co, data, c - data);
> > +	}
> > +
> > +unlock_nbcon:
> > +	if (braille_co->flags & CON_NBCON) {
> > +		if (panic_on_this_cpu()) {
> > +			if (locked)
> > +				nbcon_braille_release(&wctxt);
> 
> There will never be a locked=false scenario here. We already returned.

Right!

> > +			local_irq_restore(flags);
> > +		} else {
> > +			nbcon_braille_release(&wctxt);
> > +			braille_co->device_unlock(braille_co, flags);
> > +		}
> > +	}
> >  }
> >  
> >  /* Follow the VC cursor*/
> > @@ -353,13 +404,22 @@ int braille_register_console(struct console *console, int index,
> >  	if (!console_options)
> >  		/* Only support VisioBraille for now */
> >  		console_options = "57600o8";
> > +
> >  	if (braille_co)
> >  		return -ENODEV;
> > +
> > +	if (console->flags & CON_NBCON &&
> > +	    (!console->write_atomic || console->flags & CON_NBCON_ATOMIC_UNSAFE)) {
> > +		pr_err("Braille console requires a safe braille_co->write_atomic callback\n");
> 
> IMO it is not necessary to restrict to !CON_NBCON_ATOMIC_UNSAFE consoles
> because if the acquire fails, an unsafe acquire is tried anyway. But as
> I suggested earlier, I think even NBCON consoles without
> ->write_atomic() should be allowed. Just no panic message for them.

I agree. I did not revisit this after I enabled the unsafe takeover
in panic(). I'll remove it in v4.

> > +		return -EINVAL;
> > +	}
> > +
> >  	if (console->setup) {
> >  		ret = console->setup(console, console_options);
> >  		if (ret != 0)
> >  			return ret;
> >  	}
> > +
> >  	console->flags |= CON_ENABLED;
> >  	console->index = index;
> >  	braille_co = console;
> > --- a/kernel/printk/nbcon.c
> > +++ b/kernel/printk/nbcon.c
> > @@ -2002,3 +2003,80 @@ void nbcon_kdb_release(struct nbcon_write_context *wctxt)
> >  	 */
> >  	__nbcon_atomic_flush_pending_con(ctxt->console, prb_next_reserve_seq(prb));
> >  }
> > +
> > +/**
> > + * nbcon_is_braille - Checks whether the nbcon write context is using Braille console
> > + *
> > + * @wctxt:	checked nbcon write context
> > + *
> > + * Return: True when the write context is associated with a Braille console.
> > + *	   Othrewise, return false.
> > + *
> > + * Context: Can be called in any context but only when Braille console is
> > + *	registered and the struct console could not disappear.
> > + */
> > +bool nbcon_write_context_is_braille(struct nbcon_write_context *wctxt)
> > +{
> > +	struct nbcon_context *ctxt = &ACCESS_PRIVATE(wctxt, ctxt);
> > +	struct console *con = ctxt->console;
> > +
> > +	return con && con->flags & CON_BRL;
> 
> I suggest parenthesis around "con->flags & CON_BRL".

Will do in v4.

> > +}
> > +

Thanks a lot for the quick review and catching so many details.

Best Regards,
Petr

^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2026-10-01 12:01 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-01  9:39 [PATCH v3 0/1] braille: nbcon: Fix Braille console for NBCON API Petr Mladek
2026-10-01  9:39 ` [PATCH v3 1/1] braille: nbcon: Allow to use a serial console with NBCON API as Braille console Petr Mladek
2026-10-01 10:48   ` John Ogness
2026-10-01 12:01     ` Petr Mladek

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®