Commit 70acca67 authored by Esben Haabendal's avatar Esben Haabendal Committed by Greg Kroah-Hartman
Browse files

serial: imx: Switch to nbcon console



Implements the necessary callbacks to switch the imx console driver to
perform as an nbcon console.

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

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

Signed-off-by: default avatarEsben Haabendal <esben@geanix.com>
Link: https://lore.kernel.org/r/20240913-serial-imx-nbcon-v3-1-4c627302335b@geanix.com


Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent fd29e1e4
Loading
Loading
Loading
Loading
+101 −19
Original line number Diff line number Diff line
@@ -230,6 +230,8 @@ struct imx_port {
	unsigned int            saved_reg[10];
	bool			context_saved;

	bool			last_putchar_was_newline;

	enum imx_tx_state	tx_state;
	struct hrtimer		trigger_start_tx;
	struct hrtimer		trigger_stop_tx;
@@ -2064,26 +2066,75 @@ static void imx_uart_console_putchar(struct uart_port *port, unsigned char ch)
		barrier();

	imx_uart_writel(sport, ch, URTX0);

	sport->last_putchar_was_newline = (ch == '\n');
}

static void imx_uart_console_device_lock(struct console *co, unsigned long *flags)
{
	struct uart_port *up = &imx_uart_ports[co->index]->port;

	return __uart_port_lock_irqsave(up, flags);
}

static void imx_uart_console_device_unlock(struct console *co, unsigned long flags)
{
	struct uart_port *up = &imx_uart_ports[co->index]->port;

	return __uart_port_unlock_irqrestore(up, flags);
}

static void imx_uart_console_write_atomic(struct console *co,
					  struct nbcon_write_context *wctxt)
{
	struct imx_port *sport = imx_uart_ports[co->index];
	struct uart_port *port = &sport->port;
	struct imx_port_ucrs old_ucr;
	unsigned int ucr1, usr2;

	if (!nbcon_enter_unsafe(wctxt))
		return;

	/*
 * Interrupts are disabled on entering
	 *	First, save UCR1/2/3 and then disable interrupts
	 */
static void
imx_uart_console_write(struct console *co, const char *s, unsigned int count)
	imx_uart_ucrs_save(sport, &old_ucr);
	ucr1 = old_ucr.ucr1;

	if (imx_uart_is_imx1(sport))
		ucr1 |= IMX1_UCR1_UARTCLKEN;
	ucr1 |= UCR1_UARTEN;
	ucr1 &= ~(UCR1_TRDYEN | UCR1_RRDYEN | UCR1_RTSDEN);

	imx_uart_writel(sport, ucr1, UCR1);
	imx_uart_writel(sport, old_ucr.ucr2 | UCR2_TXEN, UCR2);

	if (!sport->last_putchar_was_newline)
		uart_console_write(port, "\n", 1, imx_uart_console_putchar);
	uart_console_write(port, wctxt->outbuf, wctxt->len,
			   imx_uart_console_putchar);

	/*
	 *	Finally, wait for transmitter to become empty
	 *	and restore UCR1/2/3
	 */
	read_poll_timeout_atomic(imx_uart_readl, usr2, usr2 & USR2_TXDC,
				 0, USEC_PER_SEC, false, sport, USR2);
	imx_uart_ucrs_restore(sport, &old_ucr);

	nbcon_exit_unsafe(wctxt);
}

static void imx_uart_console_write_thread(struct console *co,
					  struct nbcon_write_context *wctxt)
{
	struct imx_port *sport = imx_uart_ports[co->index];
	struct uart_port *port = &sport->port;
	struct imx_port_ucrs old_ucr;
	unsigned long flags;
	unsigned int ucr1, usr2;
	int locked = 1;

	if (sport->port.sysrq)
		locked = 0;
	else if (oops_in_progress)
		locked = uart_port_trylock_irqsave(&sport->port, &flags);
	else
		uart_port_lock_irqsave(&sport->port, &flags);
	if (!nbcon_enter_unsafe(wctxt))
		return;

	/*
	 *	First, save UCR1/2/3 and then disable interrupts
@@ -2097,21 +2148,47 @@ imx_uart_console_write(struct console *co, const char *s, unsigned int count)
	ucr1 &= ~(UCR1_TRDYEN | UCR1_RRDYEN | UCR1_RTSDEN);

	imx_uart_writel(sport, ucr1, UCR1);

	imx_uart_writel(sport, old_ucr.ucr2 | UCR2_TXEN, UCR2);

	uart_console_write(&sport->port, s, count, imx_uart_console_putchar);
	if (nbcon_exit_unsafe(wctxt)) {
		int len = READ_ONCE(wctxt->len);
		int i;

		/*
		 * Write out the message. Toggle unsafe for each byte in order
		 * to give another (higher priority) context the opportunity
		 * for a friendly takeover. If such a takeover occurs, this
		 * context must reacquire ownership in order to perform final
		 * actions (such as re-enabling the interrupts).
		 *
		 * IMPORTANT: wctxt->outbuf and wctxt->len are no longer valid
		 *	      after a reacquire so writing the message must be
		 *	      aborted.
		 */
		for (i = 0; i < len; i++) {
			if (!nbcon_enter_unsafe(wctxt))
				break;

			uart_console_write(port, wctxt->outbuf + i, 1,
					   imx_uart_console_putchar);

			if (!nbcon_exit_unsafe(wctxt))
				break;
		}
	}

	while (!nbcon_enter_unsafe(wctxt))
		nbcon_reacquire_nobuf(wctxt);

	/*
	 *	Finally, wait for transmitter to become empty
	 *	and restore UCR1/2/3
	 */
	read_poll_timeout_atomic(imx_uart_readl, usr2, usr2 & USR2_TXDC,
	read_poll_timeout(imx_uart_readl, usr2, usr2 & USR2_TXDC,
			  0, USEC_PER_SEC, false, sport, USR2);
	imx_uart_ucrs_restore(sport, &old_ucr);

	if (locked)
		uart_port_unlock_irqrestore(&sport->port, flags);
	nbcon_exit_unsafe(wctxt);
}

/*
@@ -2203,6 +2280,8 @@ imx_uart_console_setup(struct console *co, char *options)
	if (retval)
		goto error_console;

	sport->last_putchar_was_newline = true;

	if (options)
		uart_parse_options(options, &baud, &parity, &bits, &flow);
	else
@@ -2239,11 +2318,14 @@ imx_uart_console_exit(struct console *co)
static struct uart_driver imx_uart_uart_driver;
static struct console imx_uart_console = {
	.name		= DEV_NAME,
	.write		= imx_uart_console_write,
	.write_atomic	= imx_uart_console_write_atomic,
	.write_thread	= imx_uart_console_write_thread,
	.device_lock	= imx_uart_console_device_lock,
	.device_unlock	= imx_uart_console_device_unlock,
	.flags		= CON_PRINTBUFFER | CON_NBCON,
	.device		= uart_console_device,
	.setup		= imx_uart_console_setup,
	.exit		= imx_uart_console_exit,
	.flags		= CON_PRINTBUFFER,
	.index		= -1,
	.data		= &imx_uart_uart_driver,
};