diff mbox series

serial: sifive: Switch to nbcon console

Message ID 20250323060603.388621-1-ryotkkr98@gmail.com (mailing list archive)
State New
Headers show
Series serial: sifive: Switch to nbcon console | expand

Checks

Context Check Description
bjorn/pre-ci_am success Success
bjorn/build-rv32-defconfig success build-rv32-defconfig
bjorn/build-rv64-clang-allmodconfig fail build-rv64-clang-allmodconfig
bjorn/build-rv64-gcc-allmodconfig fail build-rv64-gcc-allmodconfig
bjorn/build-rv64-nommu-k210-defconfig success build-rv64-nommu-k210-defconfig
bjorn/build-rv64-nommu-k210-virt success build-rv64-nommu-k210-virt
bjorn/checkpatch success checkpatch
bjorn/dtb-warn-rv64 success dtb-warn-rv64
bjorn/header-inline success header-inline
bjorn/kdoc fail kdoc
bjorn/module-param success module-param
bjorn/verify-fixes success verify-fixes
bjorn/verify-signedoff success verify-signedoff

Commit Message

Ryo Takakura March 23, 2025, 6:06 a.m. UTC
Add the necessary callbacks(write_atomic, write_thread, device_lock
and device_unlock) and CON_NBCON flag to switch the sifive console
driver to perform as nbcon console.

Both ->write_atomic() and ->write_thread() will check for console
ownership whenever they are accessing registers.

The ->device_lock()/unlock() will provide the additional serilization
necessary for ->write_thread() which is called from dedicated printing
thread.

Signed-off-by: Ryo Takakura <ryotkkr98@gmail.com>
---

Hi!

I referred to the already converted console drivers 8250 [0], imx [1]
and amba-pl011 [2]. I tested the patch for each of the NBCON priority
(PANIC, EMERGENCY and NORMAL) on riscv qemu.
Hope to get feedbacks, thanks!

Sincerely,
Ryo Takakura

[0] https://lore.kernel.org/lkml/20250107212702.169493-1-john.ogness@linutronix.de/
[1] https://lore.kernel.org/linux-arm-kernel/20240913-serial-imx-nbcon-v3-1-4c627302335b@geanix.com/
[2] https://lore.kernel.org/linux-serial/20250204044428.2191983-1-fj6611ie@aa.jp.fujitsu.com/

---
 drivers/tty/serial/sifive.c | 87 +++++++++++++++++++++++++++++++------
 1 file changed, 74 insertions(+), 13 deletions(-)

Comments

John Ogness March 24, 2025, 3:24 p.m. UTC | #1
Hi Ryo,

On 2025-03-23, Ryo Takakura <ryotkkr98@gmail.com> wrote:
> Add the necessary callbacks(write_atomic, write_thread, device_lock
> and device_unlock) and CON_NBCON flag to switch the sifive console
> driver to perform as nbcon console.
>
> Both ->write_atomic() and ->write_thread() will check for console
> ownership whenever they are accessing registers.
>
> The ->device_lock()/unlock() will provide the additional serilization
> necessary for ->write_thread() which is called from dedicated printing
> thread.
>
> Signed-off-by: Ryo Takakura <ryotkkr98@gmail.com>

This driver has the same issue that the 8250 previously had. The
->startup() and ->shutdown() callbacks are called without the port
lock. However, the sifive driver is accessing SIFIVE_SERIAL_IE_OFFS in
these callbacks and this register is also accessed by the ->write()
callback. This needs to be synchronized.

The related 8250 patches fixing this are startup [0] and shutdown [1]. I
am assuming the following change would be sufficient:

diff --git a/drivers/tty/serial/sifive.c b/drivers/tty/serial/sifive.c
index d032de6199af..1de1b2a5833d 100644
--- a/drivers/tty/serial/sifive.c
+++ b/drivers/tty/serial/sifive.c
@@ -564,8 +564,11 @@ static void sifive_serial_break_ctl(struct uart_port *port, int break_state)
 static int sifive_serial_startup(struct uart_port *port)
 {
 	struct sifive_serial_port *ssp = port_to_sifive_serial_port(port);
+	unsigned long flags;
 
+	uart_port_lock_irqsave(&ssp->port, &flags);
 	__ssp_enable_rxwm(ssp);
+	uart_port_unlock_irqrestore(&ssp->port, flags);
 
 	return 0;
 }
@@ -573,9 +576,12 @@ static int sifive_serial_startup(struct uart_port *port)
 static void sifive_serial_shutdown(struct uart_port *port)
 {
 	struct sifive_serial_port *ssp = port_to_sifive_serial_port(port);
+	unsigned long flags;
 
+	uart_port_lock_irqsave(&ssp->port, &flags);
 	__ssp_disable_rxwm(ssp);
 	__ssp_disable_txwm(ssp);
+	uart_port_unlock_irqrestore(&ssp->port, flags);
 }
 
 /**

The fix should be applied first (and likely Cc stable) since it is
fixing an existing mainline problem.

Your patch also needs the synchronization. The ->write_atomic() callback
does not use the port lock. However, the uart_port_*() functions also
take the nbcon console ownership, so they synchronize against
->write_atomic() callbacks.

Otherwise, this patch looks good. If the ->startup() and ->shutdown()
callbacks are fixed in a previous patch, feel free to add:

Reviewed-by: John Ogness <john.ogness@linutronix.de>

to this patch.

John Ogness

[0] https://lore.kernel.org/lkml/20230525093159.223817-2-john.ogness@linutronix.de
[1] https://lore.kernel.org/lkml/20230525093159.223817-9-john.ogness@linutronix.de
Ryo Takakura March 25, 2025, 11:01 a.m. UTC | #2
Hi John,

On Mon, 24 Mar 2025 16:30:20 +0106, John Ogness wrote:
>On 2025-03-23, Ryo Takakura <ryotkkr98@gmail.com> wrote:
>> Add the necessary callbacks(write_atomic, write_thread, device_lock
>> and device_unlock) and CON_NBCON flag to switch the sifive console
>> driver to perform as nbcon console.
>>
>> Both ->write_atomic() and ->write_thread() will check for console
>> ownership whenever they are accessing registers.
>>
>> The ->device_lock()/unlock() will provide the additional serilization
>> necessary for ->write_thread() which is called from dedicated printing
>> thread.
>>
>> Signed-off-by: Ryo Takakura <ryotkkr98@gmail.com>
>
>This driver has the same issue that the 8250 previously had. The
>->startup() and ->shutdown() callbacks are called without the port
>lock. However, the sifive driver is accessing SIFIVE_SERIAL_IE_OFFS in
>these callbacks and this register is also accessed by the ->write()
>callback. This needs to be synchronized.

I see. Thanks for pointing out.
(I didn't know about console_on_rootfs() and what it does. Interesting!)

>The related 8250 patches fixing this are startup [0] and shutdown [1]. I
>am assuming the following change would be sufficient:
>
>diff --git a/drivers/tty/serial/sifive.c b/drivers/tty/serial/sifive.c
>index d032de6199af..1de1b2a5833d 100644
>--- a/drivers/tty/serial/sifive.c
>+++ b/drivers/tty/serial/sifive.c
>@@ -564,8 +564,11 @@ static void sifive_serial_break_ctl(struct uart_port *port, int break_state)
> static int sifive_serial_startup(struct uart_port *port)
> {
>       struct sifive_serial_port *ssp = port_to_sifive_serial_port(port);
>+      unsigned long flags;
>
>+      uart_port_lock_irqsave(&ssp->port, &flags);
>       __ssp_enable_rxwm(ssp);
>+      uart_port_unlock_irqrestore(&ssp->port, flags);
>
>       return 0;
> }
>@@ -573,9 +576,12 @@ static int sifive_serial_startup(struct uart_port *port)
> static void sifive_serial_shutdown(struct uart_port *port)
> {
>       struct sifive_serial_port *ssp = port_to_sifive_serial_port(port);
>+      unsigned long flags;
>
>+      uart_port_lock_irqsave(&ssp->port, &flags);
>       __ssp_disable_rxwm(ssp);
>       __ssp_disable_txwm(ssp);
>+      uart_port_unlock_irqrestore(&ssp->port, flags);
> }
>
> /**
>
>The fix should be applied first (and likely Cc stable) since it is
>fixing an existing mainline problem.

Ok, I will add a patch in the next version and cc stable.

>Your patch also needs the synchronization. The ->write_atomic() callback
>does not use the port lock. However, the uart_port_*() functions also
>take the nbcon console ownership, so they synchronize against
>->write_atomic() callbacks.

I see, nice!

>Otherwise, this patch looks good. If the ->startup() and ->shutdown()
>callbacks are fixed in a previous patch, feel free to add:
>
>Reviewed-by: John Ogness <john.ogness@linutronix.de>
>
>to this patch.

I'll add in the next verison.
Thanks for reviewing!

Sincerely,
Ryo Takakura

>John Ogness
>
>[0] https://lore.kernel.org/lkml/20230525093159.223817-2-john.ogness@linutronix.de
>[1] https://lore.kernel.org/lkml/20230525093159.223817-9-john.ogness@linutronix.de
diff mbox series

Patch

diff --git a/drivers/tty/serial/sifive.c b/drivers/tty/serial/sifive.c
index 5904a2d4c..d032de619 100644
--- a/drivers/tty/serial/sifive.c
+++ b/drivers/tty/serial/sifive.c
@@ -151,6 +151,7 @@  struct sifive_serial_port {
 	unsigned long		baud_rate;
 	struct clk		*clk;
 	struct notifier_block	clk_notifier;
+	bool			console_line_ended;
 };
 
 /*
@@ -779,33 +780,88 @@  static void sifive_serial_console_putchar(struct uart_port *port, unsigned char
 
 	__ssp_wait_for_xmitr(ssp);
 	__ssp_transmit_char(ssp, ch);
+
+	ssp->console_line_ended = (ch == '\n');
+}
+
+static void sifive_serial_device_lock(struct console *co, unsigned long *flags)
+{
+	struct uart_port *up = &sifive_serial_console_ports[co->index]->port;
+
+	return __uart_port_lock_irqsave(up, flags);
+}
+
+static void sifive_serial_device_unlock(struct console *co, unsigned long flags)
+{
+	struct uart_port *up = &sifive_serial_console_ports[co->index]->port;
+
+	return __uart_port_unlock_irqrestore(up, flags);
 }
 
-static void sifive_serial_console_write(struct console *co, const char *s,
-					unsigned int count)
+static void sifive_serial_console_write_atomic(struct console *co,
+					       struct nbcon_write_context *wctxt)
 {
 	struct sifive_serial_port *ssp = sifive_serial_console_ports[co->index];
-	unsigned long flags;
+	struct uart_port *port = &ssp->port;
 	unsigned int ier;
-	int locked = 1;
 
 	if (!ssp)
 		return;
 
-	if (oops_in_progress)
-		locked = uart_port_trylock_irqsave(&ssp->port, &flags);
-	else
-		uart_port_lock_irqsave(&ssp->port, &flags);
+	if (!nbcon_enter_unsafe(wctxt))
+		return;
 
 	ier = __ssp_readl(ssp, SIFIVE_SERIAL_IE_OFFS);
 	__ssp_writel(0, SIFIVE_SERIAL_IE_OFFS, ssp);
 
-	uart_console_write(&ssp->port, s, count, sifive_serial_console_putchar);
+	if (!ssp->console_line_ended)
+		uart_console_write(port, "\n", 1, sifive_serial_console_putchar);
+	uart_console_write(port, wctxt->outbuf, wctxt->len,
+			   sifive_serial_console_putchar);
 
 	__ssp_writel(ier, SIFIVE_SERIAL_IE_OFFS, ssp);
 
-	if (locked)
-		uart_port_unlock_irqrestore(&ssp->port, flags);
+	nbcon_exit_unsafe(wctxt);
+}
+
+static void sifive_serial_console_write_thread(struct console *co,
+					       struct nbcon_write_context *wctxt)
+{
+	struct sifive_serial_port *ssp = sifive_serial_console_ports[co->index];
+	struct uart_port *port = &ssp->port;
+	unsigned int ier;
+
+	if (!ssp)
+		return;
+
+	if (!nbcon_enter_unsafe(wctxt))
+		return;
+
+	ier = __ssp_readl(ssp, SIFIVE_SERIAL_IE_OFFS);
+	__ssp_writel(0, SIFIVE_SERIAL_IE_OFFS, ssp);
+
+	if (nbcon_exit_unsafe(wctxt)) {
+		int len = READ_ONCE(wctxt->len);
+		int i;
+
+		for (i = 0; i < len; i++) {
+			if (!nbcon_enter_unsafe(wctxt))
+				break;
+
+			uart_console_write(port, wctxt->outbuf + i, 1,
+					   sifive_serial_console_putchar);
+
+			if (!nbcon_exit_unsafe(wctxt))
+				break;
+		}
+	}
+
+	while (!nbcon_enter_unsafe(wctxt))
+		nbcon_reacquire_nobuf(wctxt);
+
+	__ssp_writel(ier, SIFIVE_SERIAL_IE_OFFS, ssp);
+
+	nbcon_exit_unsafe(wctxt);
 }
 
 static int sifive_serial_console_setup(struct console *co, char *options)
@@ -823,6 +879,8 @@  static int sifive_serial_console_setup(struct console *co, char *options)
 	if (!ssp)
 		return -ENODEV;
 
+	ssp->console_line_ended = true;
+
 	if (options)
 		uart_parse_options(options, &baud, &parity, &bits, &flow);
 
@@ -833,10 +891,13 @@  static struct uart_driver sifive_serial_uart_driver;
 
 static struct console sifive_serial_console = {
 	.name		= SIFIVE_TTY_PREFIX,
-	.write		= sifive_serial_console_write,
+	.write_atomic	= sifive_serial_console_write_atomic,
+	.write_thread	= sifive_serial_console_write_thread,
+	.device_lock	= sifive_serial_device_lock,
+	.device_unlock	= sifive_serial_device_unlock,
 	.device		= uart_console_device,
 	.setup		= sifive_serial_console_setup,
-	.flags		= CON_PRINTBUFFER,
+	.flags		= CON_PRINTBUFFER | CON_NBCON,
 	.index		= -1,
 	.data		= &sifive_serial_uart_driver,
 };