serial: amba-pl011: Implement nbcon console

Implement the callbacks required for an NBCON console [0] on the
amba-pl011 console driver.

Referred to the NBCON implementation work for 8250 [1] and imx [2].

The normal-priority write_thread checks for console ownership
each time a character is printed.
write_atomic holds the console ownership until the entire string
is printed.

UART register operations are protected from other contexts by
uart_port_lock, except for a final flush(nbcon_atomic_flush_unsafe)
on panic.

The patch has been verified to correctly handle the output and
competition of messages with different priorities and flushing
panic message to console after nmi panic using ARM64 QEMU and
a physical machine(A64FX).

Stress testing was conducted on a physical machine(A64FX).
The results are as follows:

- The output speed and volume of messages using the NBCON console were
  comparable to the legacy console (data suggests a slight improvement).

- When inducing a panic (sysrq-triggered or NMI) under heavy contention
  on the serial console output,
  the legacy console resulted in the loss of some or all crash messages.
  However, using the NBCON console, no message loss occurred.

This testing referenced the NBCON console work for 8250 [3].

[0] https://lore.kernel.org/all/ZuRRTbapH0DCj334@pathway.suse.cz/
[1] https://lore.kernel.org/all/20240913140538.221708-1-john.ogness@linutronix.de/T/
[2] https://lore.kernel.org/linux-arm-kernel/20240913-serial-imx-nbcon-v3-1-4c627302335b@geanix.com/T/
[3] https://lore.kernel.org/lkml/ZsdoD6PomBRsB-ow@debarbos-thinkpadt14sgen2i.remote.csb/#t

Signed-off-by: Toshiyuki Sato <fj6611ie@aa.jp.fujitsu.com>
Link: https://lore.kernel.org/r/20250204044428.2191983-1-fj6611ie@aa.jp.fujitsu.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
Toshiyuki Sato 2025-02-04 04:44:28 +00:00 committed by Greg Kroah-Hartman
parent bfd3d4a40f
commit 2eb2608618

View File

@ -272,6 +272,7 @@ struct uart_amba_port {
enum pl011_rs485_tx_state rs485_tx_state;
struct hrtimer trigger_start_tx;
struct hrtimer trigger_stop_tx;
bool console_line_ended;
#ifdef CONFIG_DMA_ENGINE
/* DMA stuff */
unsigned int dmacr; /* dma control reg */
@ -2366,50 +2367,7 @@ static void pl011_console_putchar(struct uart_port *port, unsigned char ch)
while (pl011_read(uap, REG_FR) & UART01x_FR_TXFF)
cpu_relax();
pl011_write(ch, uap, REG_DR);
}
static void
pl011_console_write(struct console *co, const char *s, unsigned int count)
{
struct uart_amba_port *uap = amba_ports[co->index];
unsigned int old_cr = 0, new_cr;
unsigned long flags;
int locked = 1;
clk_enable(uap->clk);
if (oops_in_progress)
locked = uart_port_trylock_irqsave(&uap->port, &flags);
else
uart_port_lock_irqsave(&uap->port, &flags);
/*
* First save the CR then disable the interrupts
*/
if (!uap->vendor->always_enabled) {
old_cr = pl011_read(uap, REG_CR);
new_cr = old_cr & ~UART011_CR_CTSEN;
new_cr |= UART01x_CR_UARTEN | UART011_CR_TXE;
pl011_write(new_cr, uap, REG_CR);
}
uart_console_write(&uap->port, s, count, pl011_console_putchar);
/*
* Finally, wait for transmitter to become empty and restore the
* TCR. Allow feature register bits to be inverted to work around
* errata.
*/
while ((pl011_read(uap, REG_FR) ^ uap->vendor->inv_fr)
& uap->vendor->fr_busy)
cpu_relax();
if (!uap->vendor->always_enabled)
pl011_write(old_cr, uap, REG_CR);
if (locked)
uart_port_unlock_irqrestore(&uap->port, flags);
clk_disable(uap->clk);
uap->console_line_ended = (ch == '\n');
}
static void pl011_console_get_options(struct uart_amba_port *uap, int *baud,
@ -2472,6 +2430,8 @@ static int pl011_console_setup(struct console *co, char *options)
if (ret)
return ret;
uap->console_line_ended = true;
if (dev_get_platdata(uap->port.dev)) {
struct amba_pl011_data *plat;
@ -2555,14 +2515,105 @@ static int pl011_console_match(struct console *co, char *name, int idx,
return -ENODEV;
}
static void
pl011_console_write_atomic(struct console *co, struct nbcon_write_context *wctxt)
{
struct uart_amba_port *uap = amba_ports[co->index];
unsigned int old_cr = 0;
if (!nbcon_enter_unsafe(wctxt))
return;
clk_enable(uap->clk);
if (!uap->vendor->always_enabled) {
old_cr = pl011_read(uap, REG_CR);
pl011_write((old_cr & ~UART011_CR_CTSEN) | (UART01x_CR_UARTEN | UART011_CR_TXE),
uap, REG_CR);
}
if (!uap->console_line_ended)
uart_console_write(&uap->port, "\n", 1, pl011_console_putchar);
uart_console_write(&uap->port, wctxt->outbuf, wctxt->len, pl011_console_putchar);
while ((pl011_read(uap, REG_FR) ^ uap->vendor->inv_fr) & uap->vendor->fr_busy)
cpu_relax();
if (!uap->vendor->always_enabled)
pl011_write(old_cr, uap, REG_CR);
clk_disable(uap->clk);
nbcon_exit_unsafe(wctxt);
}
static void
pl011_console_write_thread(struct console *co, struct nbcon_write_context *wctxt)
{
struct uart_amba_port *uap = amba_ports[co->index];
unsigned int old_cr = 0;
if (!nbcon_enter_unsafe(wctxt))
return;
clk_enable(uap->clk);
if (!uap->vendor->always_enabled) {
old_cr = pl011_read(uap, REG_CR);
pl011_write((old_cr & ~UART011_CR_CTSEN) | (UART01x_CR_UARTEN | UART011_CR_TXE),
uap, REG_CR);
}
if (nbcon_exit_unsafe(wctxt)) {
int i;
unsigned int len = READ_ONCE(wctxt->len);
for (i = 0; i < len; i++) {
if (!nbcon_enter_unsafe(wctxt))
break;
uart_console_write(&uap->port, wctxt->outbuf + i, 1, pl011_console_putchar);
if (!nbcon_exit_unsafe(wctxt))
break;
}
}
while (!nbcon_enter_unsafe(wctxt))
nbcon_reacquire_nobuf(wctxt);
while ((pl011_read(uap, REG_FR) ^ uap->vendor->inv_fr) & uap->vendor->fr_busy)
cpu_relax();
if (!uap->vendor->always_enabled)
pl011_write(old_cr, uap, REG_CR);
clk_disable(uap->clk);
nbcon_exit_unsafe(wctxt);
}
static void
pl011_console_device_lock(struct console *co, unsigned long *flags)
{
__uart_port_lock_irqsave(&amba_ports[co->index]->port, flags);
}
static void
pl011_console_device_unlock(struct console *co, unsigned long flags)
{
__uart_port_unlock_irqrestore(&amba_ports[co->index]->port, flags);
}
static struct uart_driver amba_reg;
static struct console amba_console = {
.name = "ttyAMA",
.write = pl011_console_write,
.device = uart_console_device,
.setup = pl011_console_setup,
.match = pl011_console_match,
.flags = CON_PRINTBUFFER | CON_ANYTIME,
.write_atomic = pl011_console_write_atomic,
.write_thread = pl011_console_write_thread,
.device_lock = pl011_console_device_lock,
.device_unlock = pl011_console_device_unlock,
.flags = CON_PRINTBUFFER | CON_ANYTIME | CON_NBCON,
.index = -1,
.data = &amba_reg,
};