public inbox for linux-serial@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH] serial: 8250 dw: clear FIFO before writting LCR
@ 2025-08-19 15:06 Adriana Nicolae
  2025-08-19 15:28 ` Greg KH
  0 siblings, 1 reply; 14+ messages in thread
From: Adriana Nicolae @ 2025-08-19 15:06 UTC (permalink / raw)
  To: linux-serial; +Cc: ilpo.jarvinen, andriy.shevchenko, Prasad Koya

This patch is proposing a custom configuration for Synopsys DesignWare
serial to be used by products with associated compatible string in the
device tree.

The PORT_DWAPB config will be used instead of the default PORT_16550A
which does not include the UART_FCR_CLEAR_RCVR and UART_FCR_CLEAR_XMIT
bits for the FIFO configuration register. Having those flags is necessary
to clear FIFO when the serial port is reconfigured with do_set_termios.

Additionally, inside the do_set_termios we use the LCR (Line Control
Register) to enable DLAB bit in order to access DLL/DLM (Divisor Latch
Low/High) registers for baud rate setting. These 2 registers are sharing
the same address space with UART_TX/UART_RX and UART_IER. The sequence is:

(1) enable DLAB -> (2) set baud -> (3) disable DLAB -> (4) reset FCR

When there is a TX or RX flow on the serial while we attempt to set/clear
DLAB, the LCR write will be ignored and we will get a IIR_BUSY interrupt
afterwards which is cleared by only reading the USR (UART status register).

The sequence above can leave the serial in an unstable state in two cases:

 - if UART is busy while (1), then LCR is still pointing to the normal set of
registers, which means the code setting DLL/DLM is actually writing into TX or
modifying interrupts in UART_IER which may end with either a garbage char '\'
on the console or with serial interrupts disabled.

 - if UART is busy while (3), then LCR remains pointing to DLL/DLM instead of
moving back to RX/TX. The first transfer on the serial will be stuck because
the transmit/receive registers are not accessible unless the DLAB bit
is cleared.

The changes in this patch include a specific serial_out function for this UART
type similar to the one for Armada-38x devices in commit
b7639b0b15ddd1a4686b0142e70dfb122eefc88f with some changes in the tx_wait_empty
function to check the UART status by looking at the USR register and actively
try to clear FIFO to reduce time before a LCR write since the characters will
be lost otherwise after baud rate change.

The USR register may report that UART is busy even if TX/TX FIFO is already
empty so we will loop until all USR[0] (UART busy status) is cleared and USR[1]
TX FIFO is empty (RX FIFO bits should be 0 in this case).
Keeping the same timeout of 20ms as measurements with the 9600 baud when
the console was busy it took max 1.9ms to get the UART free state.

Signed-off-by: Adriana Nicolae <adriana@arista.com>
---
 drivers/tty/serial/8250/8250_dw.c   |   52 +++++++++++++++++++++++++++++++++++
 drivers/tty/serial/8250/8250_port.c |    8 +++++
 include/uapi/linux/serial_core.h    |    3 ++
 3 files changed, 63 insertions(+)

diff --git a/drivers/tty/serial/8250/8250_dw.c
b/drivers/tty/serial/8250/8250_dw.c
index ace221afe..337b9a8bf 100644
--- a/drivers/tty/serial/8250/8250_dw.c
+++ b/drivers/tty/serial/8250/8250_dw.c
@@ -33,10 +33,16 @@

 /* Offsets for the DesignWare specific registers */
 #define DW_UART_USR 0x1f /* UART Status Register */
+#define DW_UART_USR_BUSY 0x1 /* UART Busy status */
+#define DW_UART_USR_TFNF 0x2 /* UART TX FIFO not full */
+#define DW_UART_USR_TFE  0x4 /* UART TX FIFO empty */

 /* DesignWare specific register fields */
 #define DW_UART_MCR_SIRE BIT(6)

+#define DW8250_REG( p, reg ) \
+ ((void __iomem *)(p->membase + ((reg) << p->regshift)))
+
 struct dw8250_data {
  struct dw8250_port_data data;

@@ -159,6 +165,46 @@ static void dw8250_serial_out38x(struct uart_port
*p, int offset, int value)
  dw8250_check_lcr(p, value);
 }

+/* Drain FIFO and wait for USR to be not busy and TX/RX FIFO empty */
+static void dw8250_tx_wait_empty_apb(struct uart_port *p)
+{
+ unsigned int tries = 20000;
+ unsigned int delay_threshold = tries - 1000;
+ unsigned int usr;
+
+ while (tries--) {
+ usr = readl(DW8250_REG(p, DW_UART_USR));
+
+ /* Check UART free and TX/RX FIFO empty */
+ if ((usr & ~DW_UART_USR_TFNF) == DW_UART_USR_TFE)
+ break;
+
+ /* FIFO is still not empty, try to clear it */
+ if (tries < delay_threshold) {
+ writel(UART_FCR_ENABLE_FIFO, DW8250_REG(p, UART_FCR));
+ writel(UART_FCR_ENABLE_FIFO | UART_FCR_CLEAR_RCVR |
+ UART_FCR_CLEAR_XMIT, DW8250_REG(p, UART_FCR));
+ writel(0, DW8250_REG(p, UART_FCR));
+ udelay (1);
+ }
+ }
+}
+
+static void dw8250_serial_outapb(struct uart_port *p, int offset, int value)
+{
+       struct dw8250_data *d = to_dw8250_data(p->private_data);
+
+ if(offset == UART_LCR && !d->uart_16550_compatible)
+ dw8250_tx_wait_empty_apb(p);
+
+ writel(value, DW8250_REG(p, offset));
+
+ if (offset == UART_LCR && !d->uart_16550_compatible) {
+ /* Check FIFO is left enabled and LCR was written */
+ writel(UART_FCR_ENABLE_FIFO, DW8250_REG(p, UART_FCR));
+ dw8250_check_lcr(p, value);
+ }
+}

 static void dw8250_serial_out(struct uart_port *p, int offset, int value)
 {
@@ -421,6 +467,12 @@ static void dw8250_quirks(struct uart_port *p,
struct dw8250_data *data)
  }
  if (of_device_is_compatible(np, "marvell,armada-38x-uart"))
  p->serial_out = dw8250_serial_out38x;
+ if (of_device_is_compatible(np, "snps,dw-apb-uart")) {
+ p->type = PORT_DWAPB;
+ p->flags |= UPF_FIXED_TYPE;
+ p->serial_out = dw8250_serial_outapb;
+ data->skip_autocfg = true;
+ }

  } else if (acpi_dev_present("APMC0D08", NULL, -1)) {
  p->iotype = UPIO_MEM32;
diff --git a/drivers/tty/serial/8250/8250_port.c
b/drivers/tty/serial/8250/8250_port.c
index 0cc4dcf23..d627d85a0 100644
--- a/drivers/tty/serial/8250/8250_port.c
+++ b/drivers/tty/serial/8250/8250_port.c
@@ -307,6 +307,14 @@ static const struct serial8250_config uart_config[] = {
  .rxtrig_bytes = {1, 32, 64, 112},
  .flags = UART_CAP_FIFO | UART_CAP_SLEEP,
  },
+ [PORT_DWAPB] = {
+ .name = "Synopsys DesignWare",
+ .fifo_size = 16,
+ .tx_loadsz = 16,
+ .fcr = UART_FCR_ENABLE_FIFO |
+   UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT,
+ .flags = UART_CAP_FIFO | UART_CAP_AFE | UART_CAP_IRDA,
+ },
 };

 static const char *platform_list[] = {
diff --git a/include/uapi/linux/serial_core.h b/include/uapi/linux/serial_core.h
index 851b982f8..279fc4c98 100644
--- a/include/uapi/linux/serial_core.h
+++ b/include/uapi/linux/serial_core.h
@@ -279,4 +279,7 @@
 /* Freescale LINFlexD UART */
 #define PORT_LINFLEXUART 122

+/* Synopsys DesignWare */
+#define PORT_DWAPB 123
+
 #endif /* _UAPILINUX_SERIAL_CORE_H */

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

* Re: [PATCH] serial: 8250 dw: clear FIFO before writting LCR
  2025-08-19 15:06 Adriana Nicolae
@ 2025-08-19 15:28 ` Greg KH
  0 siblings, 0 replies; 14+ messages in thread
From: Greg KH @ 2025-08-19 15:28 UTC (permalink / raw)
  To: Adriana Nicolae
  Cc: linux-serial, ilpo.jarvinen, andriy.shevchenko, Prasad Koya

On Tue, Aug 19, 2025 at 06:06:55PM +0300, Adriana Nicolae wrote:
> This patch is proposing a custom configuration for Synopsys DesignWare
> serial to be used by products with associated compatible string in the
> device tree.
> 
> The PORT_DWAPB config will be used instead of the default PORT_16550A
> which does not include the UART_FCR_CLEAR_RCVR and UART_FCR_CLEAR_XMIT
> bits for the FIFO configuration register. Having those flags is necessary
> to clear FIFO when the serial port is reconfigured with do_set_termios.
> 
> Additionally, inside the do_set_termios we use the LCR (Line Control
> Register) to enable DLAB bit in order to access DLL/DLM (Divisor Latch
> Low/High) registers for baud rate setting. These 2 registers are sharing
> the same address space with UART_TX/UART_RX and UART_IER. The sequence is:
> 
> (1) enable DLAB -> (2) set baud -> (3) disable DLAB -> (4) reset FCR
> 
> When there is a TX or RX flow on the serial while we attempt to set/clear
> DLAB, the LCR write will be ignored and we will get a IIR_BUSY interrupt
> afterwards which is cleared by only reading the USR (UART status register).
> 
> The sequence above can leave the serial in an unstable state in two cases:
> 
>  - if UART is busy while (1), then LCR is still pointing to the normal set of
> registers, which means the code setting DLL/DLM is actually writing into TX or
> modifying interrupts in UART_IER which may end with either a garbage char '\'
> on the console or with serial interrupts disabled.
> 
>  - if UART is busy while (3), then LCR remains pointing to DLL/DLM instead of
> moving back to RX/TX. The first transfer on the serial will be stuck because
> the transmit/receive registers are not accessible unless the DLAB bit
> is cleared.
> 
> The changes in this patch include a specific serial_out function for this UART
> type similar to the one for Armada-38x devices in commit
> b7639b0b15ddd1a4686b0142e70dfb122eefc88f with some changes in the tx_wait_empty
> function to check the UART status by looking at the USR register and actively
> try to clear FIFO to reduce time before a LCR write since the characters will
> be lost otherwise after baud rate change.
> 
> The USR register may report that UART is busy even if TX/TX FIFO is already
> empty so we will loop until all USR[0] (UART busy status) is cleared and USR[1]
> TX FIFO is empty (RX FIFO bits should be 0 in this case).
> Keeping the same timeout of 20ms as measurements with the 9600 baud when
> the console was busy it took max 1.9ms to get the UART free state.
> 
> Signed-off-by: Adriana Nicolae <adriana@arista.com>
> ---
>  drivers/tty/serial/8250/8250_dw.c   |   52 +++++++++++++++++++++++++++++++++++
>  drivers/tty/serial/8250/8250_port.c |    8 +++++
>  include/uapi/linux/serial_core.h    |    3 ++
>  3 files changed, 63 insertions(+)
> 
> diff --git a/drivers/tty/serial/8250/8250_dw.c
> b/drivers/tty/serial/8250/8250_dw.c
> index ace221afe..337b9a8bf 100644
> --- a/drivers/tty/serial/8250/8250_dw.c
> +++ b/drivers/tty/serial/8250/8250_dw.c
> @@ -33,10 +33,16 @@
> 
>  /* Offsets for the DesignWare specific registers */
>  #define DW_UART_USR 0x1f /* UART Status Register */
> +#define DW_UART_USR_BUSY 0x1 /* UART Busy status */
> +#define DW_UART_USR_TFNF 0x2 /* UART TX FIFO not full */
> +#define DW_UART_USR_TFE  0x4 /* UART TX FIFO empty */
> 
>  /* DesignWare specific register fields */
>  #define DW_UART_MCR_SIRE BIT(6)
> 
> +#define DW8250_REG( p, reg ) \
> + ((void __iomem *)(p->membase + ((reg) << p->regshift)))
> +

Hi,

This is the friendly patch-bot of Greg Kroah-Hartman.  You have sent him
a patch that has triggered this response.  He used to manually respond
to these common problems, but in order to save his sanity (he kept
writing the same thing over and over, yet to different people), I was
created.  Hopefully you will not take offence and will fix the problem
in your patch and resubmit it so that it can be accepted into the Linux
kernel tree.

You are receiving this message because of the following common error(s)
as indicated below:

- Your patch is malformed (tabs converted to spaces, linewrapped, etc.)
  and can not be applied.  Please read the file,
  Documentation/process/email-clients.rst in order to fix this.

If you wish to discuss this problem further, or you have questions about
how to resolve this issue, please feel free to respond to this email and
Greg will reply once he has dug out from the pending patches received
from other developers.

thanks,

greg k-h's patch email bot

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

* [PATCH] serial: 8250 dw: clear FIFO before writting LCR
@ 2025-08-19 18:23 adriana
  2025-08-19 18:31 ` Greg KH
                   ` (2 more replies)
  0 siblings, 3 replies; 14+ messages in thread
From: adriana @ 2025-08-19 18:23 UTC (permalink / raw)
  To: linux-serial; +Cc: ilpo.jarvinen, andriy.shevchenko, prasad, Adriana Nicolae

This patch is proposing a custom configuration for Synopsys DesignWare
serial to be used by products with associated compatible string in the
device tree.

The PORT_DWAPB config will be used instead of the default PORT_16550A
which does not include the UART_FCR_CLEAR_RCVR and UART_FCR_CLEAR_XMIT
bits for the FIFO configuration register. Having those flags is necessary
to clear FIFO when the serial port is reconfigured with do_set_termios.

Additionally, inside the do_set_termios we use the LCR (Line Control
Register) to enable DLAB bit in order to access DLL/DLM (Divisor Latch
Low/High) registers for baud rate setting. These 2 registers are sharing
the same address space with UART_TX/UART_RX and UART_IER. The sequence is:

(1) enable DLAB -> (2) set baud -> (3) disable DLAB -> (4) reset FCR

When there is a TX or RX flow on the serial while we attempt to set/clear
DLAB, the LCR write will be ignored and we will get a IIR_BUSY interrupt
afterwards which is cleared by only reading the USR (UART status register).

The sequence above can leave the serial in an unstable state in two cases:

- if UART is busy while (1), then LCR is still pointing to the normal set of
registers, which means the code setting DLL/DLM is actually writing into TX or
modifying interrupts in UART_IER which may end with either a garbage character
on the console or with serial interrupts disabled.

- if UART is busy while (3), then LCR remains pointing to DLL/DLM instead of
moving back to RX/TX. The first transfer on the serial will be stuck because
the transmit/receive registers are not accessible unless the DLAB bit
is cleared.

The changes in this patch include a specific serial_out function for this UART
type similar to the one for Armada-38x devices in commit
b7639b0b15ddd1a4686b0142e70dfb122eefc88f with some changes in the tx_wait_empty
function to check the UART status by looking at the USR register and actively
try to clear FIFO to reduce time before a LCR write since the characters will
be lost otherwise after baud rate change.

The USR register may report that UART is busy even if TX/TX FIFO is already
empty so we will loop until all USR[0] (UART busy status) is cleared and USR[1]
TX FIFO is empty (RX FIFO bits should be 0 in this case).
Keeping the same timeout of 20ms as measurements with the 9600 baud when
the console was busy it took max 1.9ms to get the UART free state.

Signed-off-by: Adriana Nicolae <adriana@arista.com>
---
 drivers/tty/serial/8250/8250_dw.c   |   52 +++++++++++++++++++++++++++++++++++
 drivers/tty/serial/8250/8250_port.c |    8 +++++
 include/uapi/linux/serial_core.h    |    3 ++
 3 files changed, 63 insertions(+)

diff --git a/drivers/tty/serial/8250/8250_dw.c b/drivers/tty/serial/8250/8250_dw.c
index a53ba04d9770..985a2650f3f3 100644
--- a/drivers/tty/serial/8250/8250_dw.c
+++ b/drivers/tty/serial/8250/8250_dw.c
@@ -33,6 +33,9 @@
 /* Offsets for the DesignWare specific registers */
 #define DW_UART_USR	0x1f /* UART Status Register */
 #define DW_UART_DMASA	0xa8 /* DMA Software Ack */
+#define DW_UART_USR_BUSY 0x1 /* UART Busy status */
+#define DW_UART_USR_TFNF 0x2 /* UART TX FIFO not full */
+#define DW_UART_USR_TFE  0x4 /* UART TX FIFO empty */
 
 #define OCTEON_UART_USR	0x27 /* UART Status Register */
 
@@ -56,6 +59,10 @@
 #define DW_UART_QUIRK_IS_DMA_FC		BIT(3)
 #define DW_UART_QUIRK_APMC0D08		BIT(4)
 #define DW_UART_QUIRK_CPR_VALUE		BIT(5)
+#define DW_UART_QUIRK_APB		BIT(6)
+
+#define DW8250_REG( p, reg ) \
+	((void __iomem *)(p->membase + ((reg) << p->regshift)))
 
 struct dw8250_platform_data {
 	u8 usr_reg;
@@ -220,6 +227,47 @@ static void dw8250_serial_out38x(struct uart_port *p, unsigned int offset, u32 v
 	dw8250_serial_out(p, offset, value);
 }
 
+/* Drain FIFO and wait for USR to be not busy and TX/RX FIFO empty */
+static void dw8250_tx_wait_empty_apb(struct uart_port *p)
+{
+	unsigned int tries = 20000;
+	unsigned int delay_threshold = tries - 1000;
+	unsigned int usr;
+
+	while (tries--) {
+		usr = readl(DW8250_REG(p, DW_UART_USR));
+
+		/* Check UART free and TX/RX FIFO empty */
+		if ((usr & ~DW_UART_USR_TFNF) == DW_UART_USR_TFE)
+			break;
+
+		/* FIFO is still not empty, try to clear it */
+		if (tries < delay_threshold) {
+			writel(UART_FCR_ENABLE_FIFO, DW8250_REG(p, UART_FCR));
+			writel(UART_FCR_ENABLE_FIFO | UART_FCR_CLEAR_RCVR |
+			UART_FCR_CLEAR_XMIT, DW8250_REG(p, UART_FCR));
+			writel(0, DW8250_REG(p, UART_FCR));
+			udelay (1);
+		}
+	}
+}
+
+static void dw8250_serial_outapb(struct uart_port *p, int offset, int value)
+{
+       struct dw8250_data *d = to_dw8250_data(p->private_data);
+
+	if(offset == UART_LCR && !d->uart_16550_compatible)
+		dw8250_tx_wait_empty_apb(p);
+
+	writel(value, DW8250_REG(p, offset));
+
+	if (offset == UART_LCR && !d->uart_16550_compatible) {
+		/* Check FIFO is left enabled and LCR was written */
+		writel(UART_FCR_ENABLE_FIFO, DW8250_REG(p, UART_FCR));
+		dw8250_check_lcr(p, value);
+	}
+}
+
 static u32 dw8250_serial_in(struct uart_port *p, unsigned int offset)
 {
 	u32 value = readb(p->membase + (offset << p->regshift));
@@ -520,6 +568,12 @@ static void dw8250_quirks(struct uart_port *p, struct dw8250_data *data)
 		p->serial_in = dw8250_serial_in32;
 		data->uart_16550_compatible = true;
 	}
+	if (quirks & DW_UART_QUIRK_DWAPB) {
+		p->type = PORT_DWAPB;
+		p->flags |= UPF_FIXED_TYPE;
+		p->serial_out = dw8250_serial_outapb;
+		data->skip_autocfg = true;
+	}
 }
 
 static void dw8250_reset_control_assert(void *data)
@@ -755,6 +809,7 @@ static const struct dev_pm_ops dw8250_pm_ops = {
 
 static const struct dw8250_platform_data dw8250_dw_apb = {
 	.usr_reg = DW_UART_USR,
+	.quirks = DW_UART_QUIRK_APB,
 };
 
 static const struct dw8250_platform_data dw8250_octeon_3860_data = {
diff --git a/drivers/tty/serial/8250/8250_port.c b/drivers/tty/serial/8250/8250_port.c
index 2da9db960d09..3882a71920f6 100644
--- a/drivers/tty/serial/8250/8250_port.c
+++ b/drivers/tty/serial/8250/8250_port.c
@@ -310,6 +310,14 @@ static const struct serial8250_config uart_config[] = {
 		.rxtrig_bytes	= {1, 8, 16, 30},
 		.flags		= UART_CAP_FIFO | UART_CAP_AFE,
 	},
+	[PORT_DWAPB] = {
+		.name		= "Synopsys DesignWare",
+		.fifo_size	= 16,
+		.tx_loadsz	= 16,
+		.fcr		= UART_FCR_ENABLE_FIFO |
+				  UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT,
+		.flags		= UART_CAP_FIFO | UART_CAP_AFE | UART_CAP_IRDA,
+	},
 };
 
 /* Uart divisor latch read */
diff --git a/include/uapi/linux/serial_core.h b/include/uapi/linux/serial_core.h
index 9c007a106330..8386436b813f 100644
--- a/include/uapi/linux/serial_core.h
+++ b/include/uapi/linux/serial_core.h
@@ -231,6 +231,9 @@
 /* Sunplus UART */
 #define PORT_SUNPLUS	123
 
+/* Synopsys DesignWare */
+#define PORT_DWAPB		124
+
 /* Generic type identifier for ports which type is not important to userspace. */
 #define PORT_GENERIC	(-1)
 

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

* Re: [PATCH] serial: 8250 dw: clear FIFO before writting LCR
  2025-08-19 18:23 [PATCH] serial: 8250 dw: clear FIFO before writting LCR adriana
@ 2025-08-19 18:31 ` Greg KH
  2025-08-19 18:32 ` Greg KH
  2025-08-20 10:02 ` Ilpo Järvinen
  2 siblings, 0 replies; 14+ messages in thread
From: Greg KH @ 2025-08-19 18:31 UTC (permalink / raw)
  To: adriana; +Cc: linux-serial, ilpo.jarvinen, andriy.shevchenko, prasad

On Tue, Aug 19, 2025 at 11:23:22AM -0700, adriana@arista.com wrote:
> This patch is proposing a custom configuration for Synopsys DesignWare
> serial to be used by products with associated compatible string in the
> device tree.
> 
> The PORT_DWAPB config will be used instead of the default PORT_16550A
> which does not include the UART_FCR_CLEAR_RCVR and UART_FCR_CLEAR_XMIT
> bits for the FIFO configuration register. Having those flags is necessary
> to clear FIFO when the serial port is reconfigured with do_set_termios.
> 
> Additionally, inside the do_set_termios we use the LCR (Line Control
> Register) to enable DLAB bit in order to access DLL/DLM (Divisor Latch
> Low/High) registers for baud rate setting. These 2 registers are sharing
> the same address space with UART_TX/UART_RX and UART_IER. The sequence is:
> 
> (1) enable DLAB -> (2) set baud -> (3) disable DLAB -> (4) reset FCR
> 
> When there is a TX or RX flow on the serial while we attempt to set/clear
> DLAB, the LCR write will be ignored and we will get a IIR_BUSY interrupt
> afterwards which is cleared by only reading the USR (UART status register).
> 
> The sequence above can leave the serial in an unstable state in two cases:
> 
> - if UART is busy while (1), then LCR is still pointing to the normal set of
> registers, which means the code setting DLL/DLM is actually writing into TX or
> modifying interrupts in UART_IER which may end with either a garbage character
> on the console or with serial interrupts disabled.
> 
> - if UART is busy while (3), then LCR remains pointing to DLL/DLM instead of
> moving back to RX/TX. The first transfer on the serial will be stuck because
> the transmit/receive registers are not accessible unless the DLAB bit
> is cleared.
> 
> The changes in this patch include a specific serial_out function for this UART
> type similar to the one for Armada-38x devices in commit
> b7639b0b15ddd1a4686b0142e70dfb122eefc88f with some changes in the tx_wait_empty
> function to check the UART status by looking at the USR register and actively
> try to clear FIFO to reduce time before a LCR write since the characters will
> be lost otherwise after baud rate change.
> 
> The USR register may report that UART is busy even if TX/TX FIFO is already
> empty so we will loop until all USR[0] (UART busy status) is cleared and USR[1]
> TX FIFO is empty (RX FIFO bits should be 0 in this case).
> Keeping the same timeout of 20ms as measurements with the 9600 baud when
> the console was busy it took max 1.9ms to get the UART free state.
> 
> Signed-off-by: Adriana Nicolae <adriana@arista.com>
> ---
>  drivers/tty/serial/8250/8250_dw.c   |   52 +++++++++++++++++++++++++++++++++++
>  drivers/tty/serial/8250/8250_port.c |    8 +++++
>  include/uapi/linux/serial_core.h    |    3 ++
>  3 files changed, 63 insertions(+)
> 
> diff --git a/drivers/tty/serial/8250/8250_dw.c b/drivers/tty/serial/8250/8250_dw.c
> index a53ba04d9770..985a2650f3f3 100644
> --- a/drivers/tty/serial/8250/8250_dw.c
> +++ b/drivers/tty/serial/8250/8250_dw.c
> @@ -33,6 +33,9 @@
>  /* Offsets for the DesignWare specific registers */
>  #define DW_UART_USR	0x1f /* UART Status Register */
>  #define DW_UART_DMASA	0xa8 /* DMA Software Ack */
> +#define DW_UART_USR_BUSY 0x1 /* UART Busy status */
> +#define DW_UART_USR_TFNF 0x2 /* UART TX FIFO not full */
> +#define DW_UART_USR_TFE  0x4 /* UART TX FIFO empty */
>  
>  #define OCTEON_UART_USR	0x27 /* UART Status Register */
>  
> @@ -56,6 +59,10 @@
>  #define DW_UART_QUIRK_IS_DMA_FC		BIT(3)
>  #define DW_UART_QUIRK_APMC0D08		BIT(4)
>  #define DW_UART_QUIRK_CPR_VALUE		BIT(5)
> +#define DW_UART_QUIRK_APB		BIT(6)
> +
> +#define DW8250_REG( p, reg ) \
> +	((void __iomem *)(p->membase + ((reg) << p->regshift)))
>  
>  struct dw8250_platform_data {
>  	u8 usr_reg;
> @@ -220,6 +227,47 @@ static void dw8250_serial_out38x(struct uart_port *p, unsigned int offset, u32 v
>  	dw8250_serial_out(p, offset, value);
>  }
>  
> +/* Drain FIFO and wait for USR to be not busy and TX/RX FIFO empty */
> +static void dw8250_tx_wait_empty_apb(struct uart_port *p)
> +{
> +	unsigned int tries = 20000;
> +	unsigned int delay_threshold = tries - 1000;
> +	unsigned int usr;
> +
> +	while (tries--) {
> +		usr = readl(DW8250_REG(p, DW_UART_USR));
> +
> +		/* Check UART free and TX/RX FIFO empty */
> +		if ((usr & ~DW_UART_USR_TFNF) == DW_UART_USR_TFE)
> +			break;
> +
> +		/* FIFO is still not empty, try to clear it */
> +		if (tries < delay_threshold) {
> +			writel(UART_FCR_ENABLE_FIFO, DW8250_REG(p, UART_FCR));
> +			writel(UART_FCR_ENABLE_FIFO | UART_FCR_CLEAR_RCVR |
> +			UART_FCR_CLEAR_XMIT, DW8250_REG(p, UART_FCR));
> +			writel(0, DW8250_REG(p, UART_FCR));
> +			udelay (1);
> +		}
> +	}
> +}
> +
> +static void dw8250_serial_outapb(struct uart_port *p, int offset, int value)
> +{
> +       struct dw8250_data *d = to_dw8250_data(p->private_data);
> +
> +	if(offset == UART_LCR && !d->uart_16550_compatible)
> +		dw8250_tx_wait_empty_apb(p);
> +
> +	writel(value, DW8250_REG(p, offset));
> +
> +	if (offset == UART_LCR && !d->uart_16550_compatible) {
> +		/* Check FIFO is left enabled and LCR was written */
> +		writel(UART_FCR_ENABLE_FIFO, DW8250_REG(p, UART_FCR));
> +		dw8250_check_lcr(p, value);
> +	}
> +}
> +
>  static u32 dw8250_serial_in(struct uart_port *p, unsigned int offset)
>  {
>  	u32 value = readb(p->membase + (offset << p->regshift));
> @@ -520,6 +568,12 @@ static void dw8250_quirks(struct uart_port *p, struct dw8250_data *data)
>  		p->serial_in = dw8250_serial_in32;
>  		data->uart_16550_compatible = true;
>  	}
> +	if (quirks & DW_UART_QUIRK_DWAPB) {
> +		p->type = PORT_DWAPB;
> +		p->flags |= UPF_FIXED_TYPE;
> +		p->serial_out = dw8250_serial_outapb;
> +		data->skip_autocfg = true;
> +	}
>  }
>  
>  static void dw8250_reset_control_assert(void *data)
> @@ -755,6 +809,7 @@ static const struct dev_pm_ops dw8250_pm_ops = {
>  
>  static const struct dw8250_platform_data dw8250_dw_apb = {
>  	.usr_reg = DW_UART_USR,
> +	.quirks = DW_UART_QUIRK_APB,
>  };
>  
>  static const struct dw8250_platform_data dw8250_octeon_3860_data = {
> diff --git a/drivers/tty/serial/8250/8250_port.c b/drivers/tty/serial/8250/8250_port.c
> index 2da9db960d09..3882a71920f6 100644
> --- a/drivers/tty/serial/8250/8250_port.c
> +++ b/drivers/tty/serial/8250/8250_port.c
> @@ -310,6 +310,14 @@ static const struct serial8250_config uart_config[] = {
>  		.rxtrig_bytes	= {1, 8, 16, 30},
>  		.flags		= UART_CAP_FIFO | UART_CAP_AFE,
>  	},
> +	[PORT_DWAPB] = {
> +		.name		= "Synopsys DesignWare",
> +		.fifo_size	= 16,
> +		.tx_loadsz	= 16,
> +		.fcr		= UART_FCR_ENABLE_FIFO |
> +				  UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT,
> +		.flags		= UART_CAP_FIFO | UART_CAP_AFE | UART_CAP_IRDA,
> +	},
>  };
>  
>  /* Uart divisor latch read */
> diff --git a/include/uapi/linux/serial_core.h b/include/uapi/linux/serial_core.h
> index 9c007a106330..8386436b813f 100644
> --- a/include/uapi/linux/serial_core.h
> +++ b/include/uapi/linux/serial_core.h
> @@ -231,6 +231,9 @@
>  /* Sunplus UART */
>  #define PORT_SUNPLUS	123
>  
> +/* Synopsys DesignWare */
> +#define PORT_DWAPB		124
> +
>  /* Generic type identifier for ports which type is not important to userspace. */
>  #define PORT_GENERIC	(-1)
>  
> 

Hi,

This is the friendly patch-bot of Greg Kroah-Hartman.  You have sent him
a patch that has triggered this response.  He used to manually respond
to these common problems, but in order to save his sanity (he kept
writing the same thing over and over, yet to different people), I was
created.  Hopefully you will not take offence and will fix the problem
in your patch and resubmit it so that it can be accepted into the Linux
kernel tree.

You are receiving this message because of the following common error(s)
as indicated below:

- This looks like a new version of a previously submitted patch, but you
  did not list below the --- line any changes from the previous version.
  Please read the section entitled "The canonical patch format" in the
  kernel file, Documentation/process/submitting-patches.rst for what
  needs to be done here to properly describe this.

If you wish to discuss this problem further, or you have questions about
how to resolve this issue, please feel free to respond to this email and
Greg will reply once he has dug out from the pending patches received
from other developers.

thanks,

greg k-h's patch email bot

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

* Re: [PATCH] serial: 8250 dw: clear FIFO before writting LCR
  2025-08-19 18:23 [PATCH] serial: 8250 dw: clear FIFO before writting LCR adriana
  2025-08-19 18:31 ` Greg KH
@ 2025-08-19 18:32 ` Greg KH
  2025-08-20 10:02 ` Ilpo Järvinen
  2 siblings, 0 replies; 14+ messages in thread
From: Greg KH @ 2025-08-19 18:32 UTC (permalink / raw)
  To: adriana; +Cc: linux-serial, ilpo.jarvinen, andriy.shevchenko, prasad

On Tue, Aug 19, 2025 at 11:23:22AM -0700, adriana@arista.com wrote:
> This patch is proposing a custom configuration for Synopsys DesignWare
> serial to be used by products with associated compatible string in the
> device tree.

Any reason why you are ignoring the maintainer list that
scripts/get_maintainer.pl spits out?

thanks,

greg k-h

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

* Re: [PATCH] serial: 8250 dw: clear FIFO before writting LCR
  2025-08-19 18:23 [PATCH] serial: 8250 dw: clear FIFO before writting LCR adriana
  2025-08-19 18:31 ` Greg KH
  2025-08-19 18:32 ` Greg KH
@ 2025-08-20 10:02 ` Ilpo Järvinen
  2025-08-22  8:09   ` Adriana Nicolae
  2 siblings, 1 reply; 14+ messages in thread
From: Ilpo Järvinen @ 2025-08-20 10:02 UTC (permalink / raw)
  To: Adriana Nicolae; +Cc: linux-serial, Andy Shevchenko, prasad

On Tue, 19 Aug 2025, adriana@arista.com wrote:

> This patch is proposing a custom configuration for Synopsys DesignWare

Please try to avoid starting sentences in the changelog with "This patch" 
or other wordings with similar meaning. Write imperatively instead.

Preferrable, describe the problem first, then the solution.

> serial to be used by products with associated compatible string in the
> device tree.
> 
> The PORT_DWAPB config will be used instead of the default PORT_16550A
> which does not include the UART_FCR_CLEAR_RCVR and UART_FCR_CLEAR_XMIT
> bits for the FIFO configuration register. Having those flags is necessary
> to clear FIFO when the serial port is reconfigured with do_set_termios.
> 
> Additionally, inside the do_set_termios we use the LCR (Line Control
> Register) to enable DLAB bit in order to access DLL/DLM (Divisor Latch
> Low/High) registers for baud rate setting. These 2 registers are sharing
> the same address space with UART_TX/UART_RX and UART_IER. The sequence is:
> 
> (1) enable DLAB -> (2) set baud -> (3) disable DLAB -> (4) reset FCR
> 
> When there is a TX or RX flow on the serial while we attempt to set/clear
> DLAB, the LCR write will be ignored and we will get a IIR_BUSY interrupt
> afterwards which is cleared by only reading the USR (UART status register).
> 
> The sequence above can leave the serial in an unstable state in two cases:
> 
> - if UART is busy while (1), then LCR is still pointing to the normal set of
> registers, which means the code setting DLL/DLM is actually writing into TX or
> modifying interrupts in UART_IER which may end with either a garbage character
> on the console or with serial interrupts disabled.
> 
> - if UART is busy while (3), then LCR remains pointing to DLL/DLM instead of
> moving back to RX/TX. The first transfer on the serial will be stuck because
> the transmit/receive registers are not accessible unless the DLAB bit
> is cleared.
> 
> The changes in this patch include a specific serial_out function for this UART
> type similar to the one for Armada-38x devices in commit
> b7639b0b15ddd1a4686b0142e70dfb122eefc88f with some changes in the tx_wait_empty
> function to check the UART status by looking at the USR register and actively
> try to clear FIFO to reduce time before a LCR write since the characters will
> be lost otherwise after baud rate change.
> 
> The USR register may report that UART is busy even if TX/TX FIFO is already
> empty so we will loop until all USR[0] (UART busy status) is cleared and USR[1]
> TX FIFO is empty (RX FIFO bits should be 0 in this case).
> Keeping the same timeout of 20ms as measurements with the 9600 baud when
> the console was busy it took max 1.9ms to get the UART free state.
> 
> Signed-off-by: Adriana Nicolae <adriana@arista.com>
> ---
>  drivers/tty/serial/8250/8250_dw.c   |   52 +++++++++++++++++++++++++++++++++++
>  drivers/tty/serial/8250/8250_port.c |    8 +++++
>  include/uapi/linux/serial_core.h    |    3 ++
>  3 files changed, 63 insertions(+)
> 
> diff --git a/drivers/tty/serial/8250/8250_dw.c b/drivers/tty/serial/8250/8250_dw.c
> index a53ba04d9770..985a2650f3f3 100644
> --- a/drivers/tty/serial/8250/8250_dw.c
> +++ b/drivers/tty/serial/8250/8250_dw.c
> @@ -33,6 +33,9 @@
>  /* Offsets for the DesignWare specific registers */
>  #define DW_UART_USR	0x1f /* UART Status Register */
>  #define DW_UART_DMASA	0xa8 /* DMA Software Ack */
> +#define DW_UART_USR_BUSY 0x1 /* UART Busy status */
> +#define DW_UART_USR_TFNF 0x2 /* UART TX FIFO not full */
> +#define DW_UART_USR_TFE  0x4 /* UART TX FIFO empty */
>  
>  #define OCTEON_UART_USR	0x27 /* UART Status Register */
>  
> @@ -56,6 +59,10 @@
>  #define DW_UART_QUIRK_IS_DMA_FC		BIT(3)
>  #define DW_UART_QUIRK_APMC0D08		BIT(4)
>  #define DW_UART_QUIRK_CPR_VALUE		BIT(5)
> +#define DW_UART_QUIRK_APB		BIT(6)
> +
> +#define DW8250_REG( p, reg ) \
> +	((void __iomem *)(p->membase + ((reg) << p->regshift)))

What's wrong with dw8250_readl_ext() and dw8250_writel_ext()?

>  struct dw8250_platform_data {
>  	u8 usr_reg;
> @@ -220,6 +227,47 @@ static void dw8250_serial_out38x(struct uart_port *p, unsigned int offset, u32 v
>  	dw8250_serial_out(p, offset, value);
>  }
>  
> +/* Drain FIFO and wait for USR to be not busy and TX/RX FIFO empty */
> +static void dw8250_tx_wait_empty_apb(struct uart_port *p)
> +{
> +	unsigned int tries = 20000;
> +	unsigned int delay_threshold = tries - 1000;
> +	unsigned int usr;
> +
> +	while (tries--) {
> +		usr = readl(DW8250_REG(p, DW_UART_USR));
> +
> +		/* Check UART free and TX/RX FIFO empty */
> +		if ((usr & ~DW_UART_USR_TFNF) == DW_UART_USR_TFE)
> +			break;
> +
> +		/* FIFO is still not empty, try to clear it */
> +		if (tries < delay_threshold) {
> +			writel(UART_FCR_ENABLE_FIFO, DW8250_REG(p, UART_FCR));
> +			writel(UART_FCR_ENABLE_FIFO | UART_FCR_CLEAR_RCVR |
> +			UART_FCR_CLEAR_XMIT, DW8250_REG(p, UART_FCR));

Please indent any continuation lines properly, in this case to the char 
after the opening parenthesis.

> +			writel(0, DW8250_REG(p, UART_FCR));
> +			udelay (1);
> +		}
> +	}

This seems to be just rehashing the same non-robust algorithm. There's no 
way to ensure UART wouldn't become BUSY again because of Rx at any time. 
Thus, any amount of clearing FIFOs is just never going be fully safe.

Long time ago, I attempted to create a more robust solution to this BUSY
problem by temporarily enabling loopback which should prevent any new Rx 
from reaching the UART.

Could you please try my patch that is attached to:

https://lore.kernel.org/linux-serial/079c8fe6-9ce4-fa59-4b44-93e27dd376d6@linux.intel.com/

(I haven't found a way to reproduce this myself and so far all the 
reporters of this problem have gone oddly quiet when asked to test this 
patch so it's hasn't moved forward for fea years.)

There are small Tx DMA related bits to add to the patch from robustness 
perspective (but a sane communication pattern shouldn't need those 
anyway, ie., no application should be sending something while trying to 
change these registers).

> +}
> +
> +static void dw8250_serial_outapb(struct uart_port *p, int offset, int value)
> +{
> +       struct dw8250_data *d = to_dw8250_data(p->private_data);
> +
> +	if(offset == UART_LCR && !d->uart_16550_compatible)
> +		dw8250_tx_wait_empty_apb(p);
> +
> +	writel(value, DW8250_REG(p, offset));
> +
> +	if (offset == UART_LCR && !d->uart_16550_compatible) {
> +		/* Check FIFO is left enabled and LCR was written */
> +		writel(UART_FCR_ENABLE_FIFO, DW8250_REG(p, UART_FCR));
> +		dw8250_check_lcr(p, value);
> +	}
> +}
> +
>  static u32 dw8250_serial_in(struct uart_port *p, unsigned int offset)
>  {
>  	u32 value = readb(p->membase + (offset << p->regshift));
> @@ -520,6 +568,12 @@ static void dw8250_quirks(struct uart_port *p, struct dw8250_data *data)
>  		p->serial_in = dw8250_serial_in32;
>  		data->uart_16550_compatible = true;
>  	}
> +	if (quirks & DW_UART_QUIRK_DWAPB) {
> +		p->type = PORT_DWAPB;
> +		p->flags |= UPF_FIXED_TYPE;
> +		p->serial_out = dw8250_serial_outapb;
> +		data->skip_autocfg = true;
> +	}
>  }
>  
>  static void dw8250_reset_control_assert(void *data)
> @@ -755,6 +809,7 @@ static const struct dev_pm_ops dw8250_pm_ops = {
>  
>  static const struct dw8250_platform_data dw8250_dw_apb = {
>  	.usr_reg = DW_UART_USR,
> +	.quirks = DW_UART_QUIRK_APB,
>  };
>  
>  static const struct dw8250_platform_data dw8250_octeon_3860_data = {
> diff --git a/drivers/tty/serial/8250/8250_port.c b/drivers/tty/serial/8250/8250_port.c
> index 2da9db960d09..3882a71920f6 100644
> --- a/drivers/tty/serial/8250/8250_port.c
> +++ b/drivers/tty/serial/8250/8250_port.c
> @@ -310,6 +310,14 @@ static const struct serial8250_config uart_config[] = {
>  		.rxtrig_bytes	= {1, 8, 16, 30},
>  		.flags		= UART_CAP_FIFO | UART_CAP_AFE,
>  	},
> +	[PORT_DWAPB] = {
> +		.name		= "Synopsys DesignWare",
> +		.fifo_size	= 16,
> +		.tx_loadsz	= 16,
> +		.fcr		= UART_FCR_ENABLE_FIFO |
> +				  UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT,
> +		.flags		= UART_CAP_FIFO | UART_CAP_AFE | UART_CAP_IRDA,
> +	},
>  };
>  
>  /* Uart divisor latch read */
> diff --git a/include/uapi/linux/serial_core.h b/include/uapi/linux/serial_core.h
> index 9c007a106330..8386436b813f 100644
> --- a/include/uapi/linux/serial_core.h
> +++ b/include/uapi/linux/serial_core.h
> @@ -231,6 +231,9 @@
>  /* Sunplus UART */
>  #define PORT_SUNPLUS	123
>  
> +/* Synopsys DesignWare */
> +#define PORT_DWAPB		124
> +
>  /* Generic type identifier for ports which type is not important to userspace. */
>  #define PORT_GENERIC	(-1)
>  
> 

-- 
 i.


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

* Re: [PATCH] serial: 8250 dw: clear FIFO before writting LCR
  2025-08-20 10:02 ` Ilpo Järvinen
@ 2025-08-22  8:09   ` Adriana Nicolae
  2025-08-22  9:06     ` Ilpo Järvinen
  2025-10-08 10:39     ` Ilpo Järvinen
  0 siblings, 2 replies; 14+ messages in thread
From: Adriana Nicolae @ 2025-08-22  8:09 UTC (permalink / raw)
  To: Ilpo Järvinen; +Cc: linux-serial, Andy Shevchenko, prasad

On Wed, Aug 20, 2025 at 1:02 PM Ilpo Järvinen
<ilpo.jarvinen@linux.intel.com> wrote:
>
> On Tue, 19 Aug 2025, adriana@arista.com wrote:
>
> > This patch is proposing a custom configuration for Synopsys DesignWare
>
> Please try to avoid starting sentences in the changelog with "This patch"
> or other wordings with similar meaning. Write imperatively instead.
>
> Preferrable, describe the problem first, then the solution.
>
> > serial to be used by products with associated compatible string in the
> > device tree.
> >
> > The PORT_DWAPB config will be used instead of the default PORT_16550A
> > which does not include the UART_FCR_CLEAR_RCVR and UART_FCR_CLEAR_XMIT
> > bits for the FIFO configuration register. Having those flags is necessary
> > to clear FIFO when the serial port is reconfigured with do_set_termios.
> >
> > Additionally, inside the do_set_termios we use the LCR (Line Control
> > Register) to enable DLAB bit in order to access DLL/DLM (Divisor Latch
> > Low/High) registers for baud rate setting. These 2 registers are sharing
> > the same address space with UART_TX/UART_RX and UART_IER. The sequence is:
> >
> > (1) enable DLAB -> (2) set baud -> (3) disable DLAB -> (4) reset FCR
> >
> > When there is a TX or RX flow on the serial while we attempt to set/clear
> > DLAB, the LCR write will be ignored and we will get a IIR_BUSY interrupt
> > afterwards which is cleared by only reading the USR (UART status register).
> >
> > The sequence above can leave the serial in an unstable state in two cases:
> >
> > - if UART is busy while (1), then LCR is still pointing to the normal set of
> > registers, which means the code setting DLL/DLM is actually writing into TX or
> > modifying interrupts in UART_IER which may end with either a garbage character
> > on the console or with serial interrupts disabled.
> >
> > - if UART is busy while (3), then LCR remains pointing to DLL/DLM instead of
> > moving back to RX/TX. The first transfer on the serial will be stuck because
> > the transmit/receive registers are not accessible unless the DLAB bit
> > is cleared.
> >
> > The changes in this patch include a specific serial_out function for this UART
> > type similar to the one for Armada-38x devices in commit
> > b7639b0b15ddd1a4686b0142e70dfb122eefc88f with some changes in the tx_wait_empty
> > function to check the UART status by looking at the USR register and actively
> > try to clear FIFO to reduce time before a LCR write since the characters will
> > be lost otherwise after baud rate change.
> >
> > The USR register may report that UART is busy even if TX/TX FIFO is already
> > empty so we will loop until all USR[0] (UART busy status) is cleared and USR[1]
> > TX FIFO is empty (RX FIFO bits should be 0 in this case).
> > Keeping the same timeout of 20ms as measurements with the 9600 baud when
> > the console was busy it took max 1.9ms to get the UART free state.
> >
> > Signed-off-by: Adriana Nicolae <adriana@arista.com>
> > ---
> >  drivers/tty/serial/8250/8250_dw.c   |   52 +++++++++++++++++++++++++++++++++++
> >  drivers/tty/serial/8250/8250_port.c |    8 +++++
> >  include/uapi/linux/serial_core.h    |    3 ++
> >  3 files changed, 63 insertions(+)
> >
> > diff --git a/drivers/tty/serial/8250/8250_dw.c b/drivers/tty/serial/8250/8250_dw.c
> > index a53ba04d9770..985a2650f3f3 100644
> > --- a/drivers/tty/serial/8250/8250_dw.c
> > +++ b/drivers/tty/serial/8250/8250_dw.c
> > @@ -33,6 +33,9 @@
> >  /* Offsets for the DesignWare specific registers */
> >  #define DW_UART_USR  0x1f /* UART Status Register */
> >  #define DW_UART_DMASA        0xa8 /* DMA Software Ack */
> > +#define DW_UART_USR_BUSY 0x1 /* UART Busy status */
> > +#define DW_UART_USR_TFNF 0x2 /* UART TX FIFO not full */
> > +#define DW_UART_USR_TFE  0x4 /* UART TX FIFO empty */
> >
> >  #define OCTEON_UART_USR      0x27 /* UART Status Register */
> >
> > @@ -56,6 +59,10 @@
> >  #define DW_UART_QUIRK_IS_DMA_FC              BIT(3)
> >  #define DW_UART_QUIRK_APMC0D08               BIT(4)
> >  #define DW_UART_QUIRK_CPR_VALUE              BIT(5)
> > +#define DW_UART_QUIRK_APB            BIT(6)
> > +
> > +#define DW8250_REG( p, reg ) \
> > +     ((void __iomem *)(p->membase + ((reg) << p->regshift)))
>
> What's wrong with dw8250_readl_ext() and dw8250_writel_ext()?
>
> >  struct dw8250_platform_data {
> >       u8 usr_reg;
> > @@ -220,6 +227,47 @@ static void dw8250_serial_out38x(struct uart_port *p, unsigned int offset, u32 v
> >       dw8250_serial_out(p, offset, value);
> >  }
> >
> > +/* Drain FIFO and wait for USR to be not busy and TX/RX FIFO empty */
> > +static void dw8250_tx_wait_empty_apb(struct uart_port *p)
> > +{
> > +     unsigned int tries = 20000;
> > +     unsigned int delay_threshold = tries - 1000;
> > +     unsigned int usr;
> > +
> > +     while (tries--) {
> > +             usr = readl(DW8250_REG(p, DW_UART_USR));
> > +
> > +             /* Check UART free and TX/RX FIFO empty */
> > +             if ((usr & ~DW_UART_USR_TFNF) == DW_UART_USR_TFE)
> > +                     break;
> > +
> > +             /* FIFO is still not empty, try to clear it */
> > +             if (tries < delay_threshold) {
> > +                     writel(UART_FCR_ENABLE_FIFO, DW8250_REG(p, UART_FCR));
> > +                     writel(UART_FCR_ENABLE_FIFO | UART_FCR_CLEAR_RCVR |
> > +                     UART_FCR_CLEAR_XMIT, DW8250_REG(p, UART_FCR));
>
> Please indent any continuation lines properly, in this case to the char
> after the opening parenthesis.
>
> > +                     writel(0, DW8250_REG(p, UART_FCR));
> > +                     udelay (1);
> > +             }
> > +     }
>
> This seems to be just rehashing the same non-robust algorithm. There's no
> way to ensure UART wouldn't become BUSY again because of Rx at any time.
> Thus, any amount of clearing FIFOs is just never going be fully safe.
>
> Long time ago, I attempted to create a more robust solution to this BUSY
> problem by temporarily enabling loopback which should prevent any new Rx
> from reaching the UART.
>
> Could you please try my patch that is attached to:
>
> https://lore.kernel.org/linux-serial/079c8fe6-9ce4-fa59-4b44-93e27dd376d6@linux.intel.com/
Thanks! I've applied this patch on the device I'm using and ran the
test that attempts
to write a large buffer while do_set_termios is called. I don't see
any softlockup
anymore, but during one test iteration the console hangs.

I could ssh to the host, `stty -F /dev/ttyS0 sane` hangs and same if I try to
write anything `echo test > /dev/ttyS0`. There are no errors in dmesg, and
tty interrupts are not triggered anymore. I have kernel watchdog enabled but
it didn't trigger.  I'm going to add some traces in dw8250_idle_enter and
dw8250_idle_exit and update this thread. It takes a while to reproduce,
sorry for the delayed response.

Thanks,
Adriana


>
> (I haven't found a way to reproduce this myself and so far all the
> reporters of this problem have gone oddly quiet when asked to test this
> patch so it's hasn't moved forward for fea years.)
>
> There are small Tx DMA related bits to add to the patch from robustness
> perspective (but a sane communication pattern shouldn't need those
> anyway, ie., no application should be sending something while trying to
> change these registers).
>
> > +}
> > +
> > +static void dw8250_serial_outapb(struct uart_port *p, int offset, int value)
> > +{
> > +       struct dw8250_data *d = to_dw8250_data(p->private_data);
> > +
> > +     if(offset == UART_LCR && !d->uart_16550_compatible)
> > +             dw8250_tx_wait_empty_apb(p);
> > +
> > +     writel(value, DW8250_REG(p, offset));
> > +
> > +     if (offset == UART_LCR && !d->uart_16550_compatible) {
> > +             /* Check FIFO is left enabled and LCR was written */
> > +             writel(UART_FCR_ENABLE_FIFO, DW8250_REG(p, UART_FCR));
> > +             dw8250_check_lcr(p, value);
> > +     }
> > +}
> > +
> >  static u32 dw8250_serial_in(struct uart_port *p, unsigned int offset)
> >  {
> >       u32 value = readb(p->membase + (offset << p->regshift));
> > @@ -520,6 +568,12 @@ static void dw8250_quirks(struct uart_port *p, struct dw8250_data *data)
> >               p->serial_in = dw8250_serial_in32;
> >               data->uart_16550_compatible = true;
> >       }
> > +     if (quirks & DW_UART_QUIRK_DWAPB) {
> > +             p->type = PORT_DWAPB;
> > +             p->flags |= UPF_FIXED_TYPE;
> > +             p->serial_out = dw8250_serial_outapb;
> > +             data->skip_autocfg = true;
> > +     }
> >  }
> >
> >  static void dw8250_reset_control_assert(void *data)
> > @@ -755,6 +809,7 @@ static const struct dev_pm_ops dw8250_pm_ops = {
> >
> >  static const struct dw8250_platform_data dw8250_dw_apb = {
> >       .usr_reg = DW_UART_USR,
> > +     .quirks = DW_UART_QUIRK_APB,
> >  };
> >
> >  static const struct dw8250_platform_data dw8250_octeon_3860_data = {
> > diff --git a/drivers/tty/serial/8250/8250_port.c b/drivers/tty/serial/8250/8250_port.c
> > index 2da9db960d09..3882a71920f6 100644
> > --- a/drivers/tty/serial/8250/8250_port.c
> > +++ b/drivers/tty/serial/8250/8250_port.c
> > @@ -310,6 +310,14 @@ static const struct serial8250_config uart_config[] = {
> >               .rxtrig_bytes   = {1, 8, 16, 30},
> >               .flags          = UART_CAP_FIFO | UART_CAP_AFE,
> >       },
> > +     [PORT_DWAPB] = {
> > +             .name           = "Synopsys DesignWare",
> > +             .fifo_size      = 16,
> > +             .tx_loadsz      = 16,
> > +             .fcr            = UART_FCR_ENABLE_FIFO |
> > +                               UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT,
> > +             .flags          = UART_CAP_FIFO | UART_CAP_AFE | UART_CAP_IRDA,
> > +     },
> >  };
> >
> >  /* Uart divisor latch read */
> > diff --git a/include/uapi/linux/serial_core.h b/include/uapi/linux/serial_core.h
> > index 9c007a106330..8386436b813f 100644
> > --- a/include/uapi/linux/serial_core.h
> > +++ b/include/uapi/linux/serial_core.h
> > @@ -231,6 +231,9 @@
> >  /* Sunplus UART */
> >  #define PORT_SUNPLUS 123
> >
> > +/* Synopsys DesignWare */
> > +#define PORT_DWAPB           124
> > +
> >  /* Generic type identifier for ports which type is not important to userspace. */
> >  #define PORT_GENERIC (-1)
> >
> >
>
> --
>  i.
>

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

* Re: [PATCH] serial: 8250 dw: clear FIFO before writting LCR
  2025-08-22  8:09   ` Adriana Nicolae
@ 2025-08-22  9:06     ` Ilpo Järvinen
  2025-10-08 10:39     ` Ilpo Järvinen
  1 sibling, 0 replies; 14+ messages in thread
From: Ilpo Järvinen @ 2025-08-22  9:06 UTC (permalink / raw)
  To: Adriana Nicolae; +Cc: linux-serial, Andy Shevchenko, prasad

[-- Attachment #1: Type: text/plain, Size: 12214 bytes --]

On Fri, 22 Aug 2025, Adriana Nicolae wrote:

> On Wed, Aug 20, 2025 at 1:02 PM Ilpo Järvinen
> <ilpo.jarvinen@linux.intel.com> wrote:
> >
> > On Tue, 19 Aug 2025, adriana@arista.com wrote:
> >
> > > This patch is proposing a custom configuration for Synopsys DesignWare
> >
> > Please try to avoid starting sentences in the changelog with "This patch"
> > or other wordings with similar meaning. Write imperatively instead.
> >
> > Preferrable, describe the problem first, then the solution.
> >
> > > serial to be used by products with associated compatible string in the
> > > device tree.
> > >
> > > The PORT_DWAPB config will be used instead of the default PORT_16550A
> > > which does not include the UART_FCR_CLEAR_RCVR and UART_FCR_CLEAR_XMIT
> > > bits for the FIFO configuration register. Having those flags is necessary
> > > to clear FIFO when the serial port is reconfigured with do_set_termios.
> > >
> > > Additionally, inside the do_set_termios we use the LCR (Line Control
> > > Register) to enable DLAB bit in order to access DLL/DLM (Divisor Latch
> > > Low/High) registers for baud rate setting. These 2 registers are sharing
> > > the same address space with UART_TX/UART_RX and UART_IER. The sequence is:
> > >
> > > (1) enable DLAB -> (2) set baud -> (3) disable DLAB -> (4) reset FCR
> > >
> > > When there is a TX or RX flow on the serial while we attempt to set/clear
> > > DLAB, the LCR write will be ignored and we will get a IIR_BUSY interrupt
> > > afterwards which is cleared by only reading the USR (UART status register).
> > >
> > > The sequence above can leave the serial in an unstable state in two cases:
> > >
> > > - if UART is busy while (1), then LCR is still pointing to the normal set of
> > > registers, which means the code setting DLL/DLM is actually writing into TX or
> > > modifying interrupts in UART_IER which may end with either a garbage character
> > > on the console or with serial interrupts disabled.
> > >
> > > - if UART is busy while (3), then LCR remains pointing to DLL/DLM instead of
> > > moving back to RX/TX. The first transfer on the serial will be stuck because
> > > the transmit/receive registers are not accessible unless the DLAB bit
> > > is cleared.
> > >
> > > The changes in this patch include a specific serial_out function for this UART
> > > type similar to the one for Armada-38x devices in commit
> > > b7639b0b15ddd1a4686b0142e70dfb122eefc88f with some changes in the tx_wait_empty
> > > function to check the UART status by looking at the USR register and actively
> > > try to clear FIFO to reduce time before a LCR write since the characters will
> > > be lost otherwise after baud rate change.
> > >
> > > The USR register may report that UART is busy even if TX/TX FIFO is already
> > > empty so we will loop until all USR[0] (UART busy status) is cleared and USR[1]
> > > TX FIFO is empty (RX FIFO bits should be 0 in this case).
> > > Keeping the same timeout of 20ms as measurements with the 9600 baud when
> > > the console was busy it took max 1.9ms to get the UART free state.
> > >
> > > Signed-off-by: Adriana Nicolae <adriana@arista.com>
> > > ---
> > >  drivers/tty/serial/8250/8250_dw.c   |   52 +++++++++++++++++++++++++++++++++++
> > >  drivers/tty/serial/8250/8250_port.c |    8 +++++
> > >  include/uapi/linux/serial_core.h    |    3 ++
> > >  3 files changed, 63 insertions(+)
> > >
> > > diff --git a/drivers/tty/serial/8250/8250_dw.c b/drivers/tty/serial/8250/8250_dw.c
> > > index a53ba04d9770..985a2650f3f3 100644
> > > --- a/drivers/tty/serial/8250/8250_dw.c
> > > +++ b/drivers/tty/serial/8250/8250_dw.c
> > > @@ -33,6 +33,9 @@
> > >  /* Offsets for the DesignWare specific registers */
> > >  #define DW_UART_USR  0x1f /* UART Status Register */
> > >  #define DW_UART_DMASA        0xa8 /* DMA Software Ack */
> > > +#define DW_UART_USR_BUSY 0x1 /* UART Busy status */
> > > +#define DW_UART_USR_TFNF 0x2 /* UART TX FIFO not full */
> > > +#define DW_UART_USR_TFE  0x4 /* UART TX FIFO empty */
> > >
> > >  #define OCTEON_UART_USR      0x27 /* UART Status Register */
> > >
> > > @@ -56,6 +59,10 @@
> > >  #define DW_UART_QUIRK_IS_DMA_FC              BIT(3)
> > >  #define DW_UART_QUIRK_APMC0D08               BIT(4)
> > >  #define DW_UART_QUIRK_CPR_VALUE              BIT(5)
> > > +#define DW_UART_QUIRK_APB            BIT(6)
> > > +
> > > +#define DW8250_REG( p, reg ) \
> > > +     ((void __iomem *)(p->membase + ((reg) << p->regshift)))
> >
> > What's wrong with dw8250_readl_ext() and dw8250_writel_ext()?
> >
> > >  struct dw8250_platform_data {
> > >       u8 usr_reg;
> > > @@ -220,6 +227,47 @@ static void dw8250_serial_out38x(struct uart_port *p, unsigned int offset, u32 v
> > >       dw8250_serial_out(p, offset, value);
> > >  }
> > >
> > > +/* Drain FIFO and wait for USR to be not busy and TX/RX FIFO empty */
> > > +static void dw8250_tx_wait_empty_apb(struct uart_port *p)
> > > +{
> > > +     unsigned int tries = 20000;
> > > +     unsigned int delay_threshold = tries - 1000;
> > > +     unsigned int usr;
> > > +
> > > +     while (tries--) {
> > > +             usr = readl(DW8250_REG(p, DW_UART_USR));
> > > +
> > > +             /* Check UART free and TX/RX FIFO empty */
> > > +             if ((usr & ~DW_UART_USR_TFNF) == DW_UART_USR_TFE)
> > > +                     break;
> > > +
> > > +             /* FIFO is still not empty, try to clear it */
> > > +             if (tries < delay_threshold) {
> > > +                     writel(UART_FCR_ENABLE_FIFO, DW8250_REG(p, UART_FCR));
> > > +                     writel(UART_FCR_ENABLE_FIFO | UART_FCR_CLEAR_RCVR |
> > > +                     UART_FCR_CLEAR_XMIT, DW8250_REG(p, UART_FCR));
> >
> > Please indent any continuation lines properly, in this case to the char
> > after the opening parenthesis.
> >
> > > +                     writel(0, DW8250_REG(p, UART_FCR));
> > > +                     udelay (1);
> > > +             }
> > > +     }
> >
> > This seems to be just rehashing the same non-robust algorithm. There's no
> > way to ensure UART wouldn't become BUSY again because of Rx at any time.
> > Thus, any amount of clearing FIFOs is just never going be fully safe.
> >
> > Long time ago, I attempted to create a more robust solution to this BUSY
> > problem by temporarily enabling loopback which should prevent any new Rx
> > from reaching the UART.
> >
> > Could you please try my patch that is attached to:
> >
> > https://lore.kernel.org/linux-serial/079c8fe6-9ce4-fa59-4b44-93e27dd376d6@linux.intel.com/
>
> Thanks! I've applied this patch on the device I'm using and ran the
> test that attempts
> to write a large buffer while do_set_termios is called. I don't see
> any softlockup
> anymore, but during one test iteration the console hangs.

Can't think of anything obvious, idle enter and exit seem to always pair 
and idle exit should restore everything, AFAICT.

Maybe add an asserts using lockdep to ensure that the port lock is held.
While it looks ->in_idle should never remain set forever in case of a 
race, I'm not sure how DW UART takes two racing idle enter and/or exit 
calls.

With console, DMA shouldn't be relevant as DMA is not used for consoles.
If there are non-console UARTs involved, then Tx DMA might come into the 
picture if something is written to UART. Pausing DMA Tx is bit problematic 
though as (IIRC) pausing DMA was not always supported so the entire Tx 
would have to be terminated which obviously is going to lose characters 
(potentially quite many).

> I could ssh to the host, `stty -F /dev/ttyS0 sane` hangs and same if I try to
> write anything `echo test > /dev/ttyS0`. There are no errors in dmesg, and
> tty interrupts are not triggered anymore. I have kernel watchdog enabled but
> it didn't trigger.  I'm going to add some traces in dw8250_idle_enter and
> dw8250_idle_exit and update this thread.

If IER doesn't get messed up by something it looks a bit odd, checking LSR 
in dw8250_idle_exit() might provide some clues though that feels long 
shot to me. If DR is set, maybe try to read one char inside 
dw8250_idle_exit(), I'd think the best place for that is before IER is 
written but I'm not sure.

> It takes a while to reproduce, sorry for the delayed response.

Don't worry :-), your help is really appreciated.

-- 
 i.

> Thanks,
> Adriana
> 
> 
> >
> > (I haven't found a way to reproduce this myself and so far all the
> > reporters of this problem have gone oddly quiet when asked to test this
> > patch so it's hasn't moved forward for fea years.)
> >
> > There are small Tx DMA related bits to add to the patch from robustness
> > perspective (but a sane communication pattern shouldn't need those
> > anyway, ie., no application should be sending something while trying to
> > change these registers).
> >
> > > +}
> > > +
> > > +static void dw8250_serial_outapb(struct uart_port *p, int offset, int value)
> > > +{
> > > +       struct dw8250_data *d = to_dw8250_data(p->private_data);
> > > +
> > > +     if(offset == UART_LCR && !d->uart_16550_compatible)
> > > +             dw8250_tx_wait_empty_apb(p);
> > > +
> > > +     writel(value, DW8250_REG(p, offset));
> > > +
> > > +     if (offset == UART_LCR && !d->uart_16550_compatible) {
> > > +             /* Check FIFO is left enabled and LCR was written */
> > > +             writel(UART_FCR_ENABLE_FIFO, DW8250_REG(p, UART_FCR));
> > > +             dw8250_check_lcr(p, value);
> > > +     }
> > > +}
> > > +
> > >  static u32 dw8250_serial_in(struct uart_port *p, unsigned int offset)
> > >  {
> > >       u32 value = readb(p->membase + (offset << p->regshift));
> > > @@ -520,6 +568,12 @@ static void dw8250_quirks(struct uart_port *p, struct dw8250_data *data)
> > >               p->serial_in = dw8250_serial_in32;
> > >               data->uart_16550_compatible = true;
> > >       }
> > > +     if (quirks & DW_UART_QUIRK_DWAPB) {
> > > +             p->type = PORT_DWAPB;
> > > +             p->flags |= UPF_FIXED_TYPE;
> > > +             p->serial_out = dw8250_serial_outapb;
> > > +             data->skip_autocfg = true;
> > > +     }
> > >  }
> > >
> > >  static void dw8250_reset_control_assert(void *data)
> > > @@ -755,6 +809,7 @@ static const struct dev_pm_ops dw8250_pm_ops = {
> > >
> > >  static const struct dw8250_platform_data dw8250_dw_apb = {
> > >       .usr_reg = DW_UART_USR,
> > > +     .quirks = DW_UART_QUIRK_APB,
> > >  };
> > >
> > >  static const struct dw8250_platform_data dw8250_octeon_3860_data = {
> > > diff --git a/drivers/tty/serial/8250/8250_port.c b/drivers/tty/serial/8250/8250_port.c
> > > index 2da9db960d09..3882a71920f6 100644
> > > --- a/drivers/tty/serial/8250/8250_port.c
> > > +++ b/drivers/tty/serial/8250/8250_port.c
> > > @@ -310,6 +310,14 @@ static const struct serial8250_config uart_config[] = {
> > >               .rxtrig_bytes   = {1, 8, 16, 30},
> > >               .flags          = UART_CAP_FIFO | UART_CAP_AFE,
> > >       },
> > > +     [PORT_DWAPB] = {
> > > +             .name           = "Synopsys DesignWare",
> > > +             .fifo_size      = 16,
> > > +             .tx_loadsz      = 16,
> > > +             .fcr            = UART_FCR_ENABLE_FIFO |
> > > +                               UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT,
> > > +             .flags          = UART_CAP_FIFO | UART_CAP_AFE | UART_CAP_IRDA,
> > > +     },
> > >  };
> > >
> > >  /* Uart divisor latch read */
> > > diff --git a/include/uapi/linux/serial_core.h b/include/uapi/linux/serial_core.h
> > > index 9c007a106330..8386436b813f 100644
> > > --- a/include/uapi/linux/serial_core.h
> > > +++ b/include/uapi/linux/serial_core.h
> > > @@ -231,6 +231,9 @@
> > >  /* Sunplus UART */
> > >  #define PORT_SUNPLUS 123
> > >
> > > +/* Synopsys DesignWare */
> > > +#define PORT_DWAPB           124
> > > +
> > >  /* Generic type identifier for ports which type is not important to userspace. */
> > >  #define PORT_GENERIC (-1)
> > >
> > >
> >
> > --
> >  i.
> >
> 

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

* Re: [PATCH] serial: 8250 dw: clear FIFO before writting LCR
  2025-08-22  8:09   ` Adriana Nicolae
  2025-08-22  9:06     ` Ilpo Järvinen
@ 2025-10-08 10:39     ` Ilpo Järvinen
       [not found]       ` <CAERbo5xOg4OegoWYid3ZBCX1Fi+MBUMb59EtaQLrYkZy9MzSJA@mail.gmail.com>
  1 sibling, 1 reply; 14+ messages in thread
From: Ilpo Järvinen @ 2025-10-08 10:39 UTC (permalink / raw)
  To: Adriana Nicolae; +Cc: linux-serial, Andy Shevchenko, prasad

[-- Attachment #1: Type: text/plain, Size: 7436 bytes --]

On Fri, 22 Aug 2025, Adriana Nicolae wrote:

> On Wed, Aug 20, 2025 at 1:02 PM Ilpo Järvinen
> <ilpo.jarvinen@linux.intel.com> wrote:
> >
> > On Tue, 19 Aug 2025, adriana@arista.com wrote:
> >
> > > This patch is proposing a custom configuration for Synopsys DesignWare
> >
> > Please try to avoid starting sentences in the changelog with "This patch"
> > or other wordings with similar meaning. Write imperatively instead.
> >
> > Preferrable, describe the problem first, then the solution.
> >
> > > serial to be used by products with associated compatible string in the
> > > device tree.
> > >
> > > The PORT_DWAPB config will be used instead of the default PORT_16550A
> > > which does not include the UART_FCR_CLEAR_RCVR and UART_FCR_CLEAR_XMIT
> > > bits for the FIFO configuration register. Having those flags is necessary
> > > to clear FIFO when the serial port is reconfigured with do_set_termios.
> > >
> > > Additionally, inside the do_set_termios we use the LCR (Line Control
> > > Register) to enable DLAB bit in order to access DLL/DLM (Divisor Latch
> > > Low/High) registers for baud rate setting. These 2 registers are sharing
> > > the same address space with UART_TX/UART_RX and UART_IER. The sequence is:
> > >
> > > (1) enable DLAB -> (2) set baud -> (3) disable DLAB -> (4) reset FCR
> > >
> > > When there is a TX or RX flow on the serial while we attempt to set/clear
> > > DLAB, the LCR write will be ignored and we will get a IIR_BUSY interrupt
> > > afterwards which is cleared by only reading the USR (UART status register).
> > >
> > > The sequence above can leave the serial in an unstable state in two cases:
> > >
> > > - if UART is busy while (1), then LCR is still pointing to the normal set of
> > > registers, which means the code setting DLL/DLM is actually writing into TX or
> > > modifying interrupts in UART_IER which may end with either a garbage character
> > > on the console or with serial interrupts disabled.
> > >
> > > - if UART is busy while (3), then LCR remains pointing to DLL/DLM instead of
> > > moving back to RX/TX. The first transfer on the serial will be stuck because
> > > the transmit/receive registers are not accessible unless the DLAB bit
> > > is cleared.
> > >
> > > The changes in this patch include a specific serial_out function for this UART
> > > type similar to the one for Armada-38x devices in commit
> > > b7639b0b15ddd1a4686b0142e70dfb122eefc88f with some changes in the tx_wait_empty
> > > function to check the UART status by looking at the USR register and actively
> > > try to clear FIFO to reduce time before a LCR write since the characters will
> > > be lost otherwise after baud rate change.
> > >
> > > The USR register may report that UART is busy even if TX/TX FIFO is already
> > > empty so we will loop until all USR[0] (UART busy status) is cleared and USR[1]
> > > TX FIFO is empty (RX FIFO bits should be 0 in this case).
> > > Keeping the same timeout of 20ms as measurements with the 9600 baud when
> > > the console was busy it took max 1.9ms to get the UART free state.
> > >
> > > Signed-off-by: Adriana Nicolae <adriana@arista.com>
> > > ---
> > >  drivers/tty/serial/8250/8250_dw.c   |   52 +++++++++++++++++++++++++++++++++++
> > >  drivers/tty/serial/8250/8250_port.c |    8 +++++
> > >  include/uapi/linux/serial_core.h    |    3 ++
> > >  3 files changed, 63 insertions(+)
> > >
> > > diff --git a/drivers/tty/serial/8250/8250_dw.c b/drivers/tty/serial/8250/8250_dw.c
> > > index a53ba04d9770..985a2650f3f3 100644
> > > --- a/drivers/tty/serial/8250/8250_dw.c
> > > +++ b/drivers/tty/serial/8250/8250_dw.c
> > > @@ -33,6 +33,9 @@
> > >  /* Offsets for the DesignWare specific registers */
> > >  #define DW_UART_USR  0x1f /* UART Status Register */
> > >  #define DW_UART_DMASA        0xa8 /* DMA Software Ack */
> > > +#define DW_UART_USR_BUSY 0x1 /* UART Busy status */
> > > +#define DW_UART_USR_TFNF 0x2 /* UART TX FIFO not full */
> > > +#define DW_UART_USR_TFE  0x4 /* UART TX FIFO empty */
> > >
> > >  #define OCTEON_UART_USR      0x27 /* UART Status Register */
> > >
> > > @@ -56,6 +59,10 @@
> > >  #define DW_UART_QUIRK_IS_DMA_FC              BIT(3)
> > >  #define DW_UART_QUIRK_APMC0D08               BIT(4)
> > >  #define DW_UART_QUIRK_CPR_VALUE              BIT(5)
> > > +#define DW_UART_QUIRK_APB            BIT(6)
> > > +
> > > +#define DW8250_REG( p, reg ) \
> > > +     ((void __iomem *)(p->membase + ((reg) << p->regshift)))
> >
> > What's wrong with dw8250_readl_ext() and dw8250_writel_ext()?
> >
> > >  struct dw8250_platform_data {
> > >       u8 usr_reg;
> > > @@ -220,6 +227,47 @@ static void dw8250_serial_out38x(struct uart_port *p, unsigned int offset, u32 v
> > >       dw8250_serial_out(p, offset, value);
> > >  }
> > >
> > > +/* Drain FIFO and wait for USR to be not busy and TX/RX FIFO empty */
> > > +static void dw8250_tx_wait_empty_apb(struct uart_port *p)
> > > +{
> > > +     unsigned int tries = 20000;
> > > +     unsigned int delay_threshold = tries - 1000;
> > > +     unsigned int usr;
> > > +
> > > +     while (tries--) {
> > > +             usr = readl(DW8250_REG(p, DW_UART_USR));
> > > +
> > > +             /* Check UART free and TX/RX FIFO empty */
> > > +             if ((usr & ~DW_UART_USR_TFNF) == DW_UART_USR_TFE)
> > > +                     break;
> > > +
> > > +             /* FIFO is still not empty, try to clear it */
> > > +             if (tries < delay_threshold) {
> > > +                     writel(UART_FCR_ENABLE_FIFO, DW8250_REG(p, UART_FCR));
> > > +                     writel(UART_FCR_ENABLE_FIFO | UART_FCR_CLEAR_RCVR |
> > > +                     UART_FCR_CLEAR_XMIT, DW8250_REG(p, UART_FCR));
> >
> > Please indent any continuation lines properly, in this case to the char
> > after the opening parenthesis.
> >
> > > +                     writel(0, DW8250_REG(p, UART_FCR));
> > > +                     udelay (1);
> > > +             }
> > > +     }
> >
> > This seems to be just rehashing the same non-robust algorithm. There's no
> > way to ensure UART wouldn't become BUSY again because of Rx at any time.
> > Thus, any amount of clearing FIFOs is just never going be fully safe.
> >
> > Long time ago, I attempted to create a more robust solution to this BUSY
> > problem by temporarily enabling loopback which should prevent any new Rx
> > from reaching the UART.
> >
> > Could you please try my patch that is attached to:
> >
> > https://lore.kernel.org/linux-serial/079c8fe6-9ce4-fa59-4b44-93e27dd376d6@linux.intel.com/
> Thanks! I've applied this patch on the device I'm using and ran the
> test that attempts
> to write a large buffer while do_set_termios is called. I don't see
> any softlockup
> anymore, but during one test iteration the console hangs.
> 
> I could ssh to the host, `stty -F /dev/ttyS0 sane` hangs and same if I try to
> write anything `echo test > /dev/ttyS0`. There are no errors in dmesg, and
> tty interrupts are not triggered anymore. I have kernel watchdog enabled but
> it didn't trigger.  I'm going to add some traces in dw8250_idle_enter and
> dw8250_idle_exit and update this thread. It takes a while to reproduce,
> sorry for the delayed response.

Hi Adriana,

Did you ever manage to figure out what goes wrong with the idle enter/exit 
patch?

-- 
 i.

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

* Re: [PATCH] serial: 8250 dw: clear FIFO before writting LCR
       [not found]       ` <CAERbo5xOg4OegoWYid3ZBCX1Fi+MBUMb59EtaQLrYkZy9MzSJA@mail.gmail.com>
@ 2025-10-08 13:11         ` Ilpo Järvinen
  2025-10-31 19:32           ` Adriana Nicolae
  0 siblings, 1 reply; 14+ messages in thread
From: Ilpo Järvinen @ 2025-10-08 13:11 UTC (permalink / raw)
  To: Adriana Nicolae; +Cc: linux-serial, Andy Shevchenko, prasad

[-- Attachment #1: Type: text/plain, Size: 11395 bytes --]

On Wed, 8 Oct 2025, Adriana Nicolae wrote:
> Hello, Sorry for missing an update, the exact root cause is not clear, currently the
> brute force method of draining FIFO right before setting or clearing DLAB was stable
> during tests.
> 
> The serial stuck seems to be a failed attempt to clear the DLAB.
> This operation fails because the USR indicates the hardware is
> still busy, even though the UART is in loopback mode and should
> be inactive.
> 
> To isolate the issue, I tried the following without success:
> - Added delays: I inserted 100 repeated ndelay(p->frame_time)
> calls before and after enabling loopback mode to allow the FIFO
> to clear.
> - Cleared FIFO: I explicitly cleared the FIFO in addition to
> adding the delay.
> - Checked status: I printed the LSR just before the DLAB clear
> attempt and checked the USB busy bit.

Okay, so the BUSY must be stuck asserted.

Another idea, maybe test tx + rx over loopback to see if that manages to 
de-freeze the BUSY bit. A patch to that effect below.

(I've only added the new block into dw8250_idle_enter() compared with the 
old patch but rebasing to tty-next resulted in some other changes due to 
conflicts.)


Only thing besides BUSY being stuck asserted is something Tx'ing after the 
idle enter sequence. I think we could trap/check that too in 
dw8250_serial_out() by using something along these lines:

	if (d->in_idle && offset == UART_TX) {
		WARN_ON_ONCE(1);
		return;
	}

(I think that should catch even console writes but I'm not 100% sure of 
that and it will should get us where the rogue Tx originates from).

> The critical finding was that immediately before the DLAB clear
> operation (p->serial_out(p, UART_LCR, up->lcr);), the LSR value
> was 0x6a and the USR busy bit [0] was set. This confirms the UART
> is busy, which blocks the DLAB modification.
> 
> This is occurring on a device with a single UART console. The setup
> does not use DMA or modem control; only the Rx/Tx lines are connected.
> 
> The trace below, from a single process, shows one successful DLAB
> clear followed by a failing one. The second attempt repeatedly logs
> "USR still busy" before eventually succeeding. This can lead to
> subsequent failures in dw8250_check_lcr: dw8250_idle_entered.
> 
> Trace Log:
> 
> <...>-15440  8583.592533: dw8250_idle_enter: in_idle = 1
> login-15440  8583.713817: dw8250_idle_enter: in loopback mode
> login-15440  8583.835099: dw8250_idle_enter: LSR in loopback mode is 0x63
> login-15440  8583.835103: dw8250_set_divisor: UART_LCR_DLAB cleared 13
> login-15440  8583.835104: dw8250_idle_exit: idle exit
> login-15440  8583.835105: dw8250_idle_exit: out of loopback mode
> login-15440  8583.835105: dw8250_idle_exit: in_idle = 0
> login-15440  8583.835352: dw8250_idle_enter: in_idle = 1
> login-15440  8583.956633: dw8250_idle_enter: in loopback mode
> login-15440  8583.956638: dw8250_idle_enter: LSR in loopback mode is 0x6a
> login-15440  8583.963918: dw8250_set_divisor: USR still busy dl_write      
> login-15440  8584.000332: dw8250_set_divisor: USR still busy dl_write
> login-15440  8584.040385: dw8250_set_divisor: USR still busy dl_write
> login-15440  8584.078012: dw8250_set_divisor: UART_LCR_DLAB cleared 93
> login-15440  8584.078013: dw8250_idle_exit: idle exit
> login-15440  8584.078014: dw8250_idle_exit: out of loopback mode
> login-15440  8584.078015: dw8250_idle_exit: in_idle = 0



--
From 01df58736a10f7f34aca895ef08e5519953f8572 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ilpo=20J=C3=A4rvinen?= <ilpo.jarvinen@linux.intel.com>
Date: Wed, 8 Oct 2025 15:40:19 +0300
Subject: [PATCH 1/1] serial: 8250_dw: Ensure BUSY is deasserted
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

DW UART cannot write to LCR, DLL, and DLH while BUSY is asserted.
Existance of BUSY depends on uart_16550_compatible, if UART HW is
configured with 16550 compatible those registers can always be
written.

There currently is dw8250_force_idle() which attempts to archive
non-BUSY state by disabling FIFO, however, the solution is unreliable
when Rx keeps getting more and more characters.

Create a sequence of operations to enforce that ensures UART cannot
keep BUSY asserted indefinitely. The new sequence relies on enabling
loopback mode temporarily to prevent incoming Rx characters keeping
UART BUSY.

Use the new dw8250_idle_enter/exit() to do divisor writes and LCR
writes.

This issue was reported by qianfan Zhao who put lots of debugging
effort into understanding the solution space.

Reported-by: qianfan Zhao <qianfanguijin@163.com>
Reported-by: Adriana Nicolae <adriana@arista.com>
Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
---
 drivers/tty/serial/8250/8250_dw.c | 159 +++++++++++++++++++++---------
 1 file changed, 115 insertions(+), 44 deletions(-)

diff --git a/drivers/tty/serial/8250/8250_dw.c b/drivers/tty/serial/8250/8250_dw.c
index a53ba04d9770..8e25dfe8e653 100644
--- a/drivers/tty/serial/8250/8250_dw.c
+++ b/drivers/tty/serial/8250/8250_dw.c
@@ -42,6 +42,8 @@
 /* DesignWare specific register fields */
 #define DW_UART_MCR_SIRE		BIT(6)
 
+#define DW_UART_USR_BUSY		BIT(0)
+
 /* Renesas specific register fields */
 #define RZN1_UART_xDMACR_DMA_EN		BIT(0)
 #define RZN1_UART_xDMACR_1_WORD_BURST	(0 << 1)
@@ -77,6 +79,7 @@ struct dw8250_data {
 
 	unsigned int		skip_autocfg:1;
 	unsigned int		uart_16550_compatible:1;
+	unsigned int		in_idle:1;
 };
 
 static inline struct dw8250_data *to_dw8250_data(struct dw8250_port_data *data)
@@ -108,36 +111,103 @@ static inline u32 dw8250_modify_msr(struct uart_port *p, unsigned int offset, u3
 }
 
 /*
- * This function is being called as part of the uart_port::serial_out()
- * routine. Hence, it must not call serial_port_out() or serial_out()
- * against the modified registers here, i.e. LCR.
+ * Ensure BUSY is not asserted. If DW UART is configured with
+ * !uart_16550_compatible, the writes to LCR, DLL, and DLH fail while
+ * BUSY is asserted.
+ *
+ * Context: port's lock must be held
  */
-static void dw8250_force_idle(struct uart_port *p)
+static int dw8250_idle_enter(struct uart_port *p)
 {
+	struct dw8250_data *d = to_dw8250_data(p->private_data);
 	struct uart_8250_port *up = up_to_u8250p(p);
-	unsigned int lsr;
+	u32 lsr;
 
-	/*
-	 * The following call currently performs serial_out()
-	 * against the FCR register. Because it differs to LCR
-	 * there will be no infinite loop, but if it ever gets
-	 * modified, we might need a new custom version of it
-	 * that avoids infinite recursion.
-	 */
-	serial8250_clear_and_reinit_fifos(up);
+	if (d->uart_16550_compatible)
+		return 0;
+
+	d->in_idle = 1;
+
+	/* Prevent triggering interrupt from RBR filling */
+	p->serial_out(p, UART_IER, 0);
+
+	serial8250_rx_dma_flush(up);
+	// What about Tx DMA? Should probably pause that too and resume
+	// afterwards.
+
+	p->serial_out(p, UART_MCR, up->mcr | UART_MCR_LOOP);
+	if (up->capabilities & UART_CAP_FIFO)
+		p->serial_out(p, UART_FCR, 0);
+
+	if (p->serial_in(p, d->pdata->usr_reg) & DW_UART_USR_BUSY)
+		ndelay(p->frame_time);
+
+	lsr = serial_lsr_in(up);
+	if (lsr & UART_LSR_DR) {
+		p->serial_in(p, UART_RX);
+		up->lsr_saved_flags = 0;
+	}
 
 	/*
-	 * With PSLVERR_RESP_EN parameter set to 1, the device generates an
-	 * error response when an attempt to read an empty RBR with FIFO
-	 * enabled.
+	 * BUSY might still be frozen to asserted, try to de-freeze it by
+	 * sending a character over the loopback and receiving it.
 	 */
-	if (up->fcr & UART_FCR_ENABLE_FIFO) {
-		lsr = serial_port_in(p, UART_LSR);
-		if (!(lsr & UART_LSR_DR))
-			return;
+	if (p->serial_in(p, d->pdata->usr_reg) & DW_UART_USR_BUSY) {
+		p->serial_out(p, UART_TX, 0);
+		ndelay(p->frame_time);
+		lsr = serial_lsr_in(up);
+		if (lsr & UART_LSR_DR) {
+			p->serial_in(p, UART_RX);
+			up->lsr_saved_flags = 0;
+		}
 	}
 
-	serial_port_in(p, UART_RX);
+	/* Now guaranteed to have BUSY deasserted? Just sanity check */
+	if (p->serial_in(p, d->pdata->usr_reg) & DW_UART_USR_BUSY)
+		return -EBUSY;
+
+	return 0;
+}
+
+static void dw8250_idle_exit(struct uart_port *p)
+{
+	struct dw8250_data *d = to_dw8250_data(p->private_data);
+	struct uart_8250_port *up = up_to_u8250p(p);
+
+	if (d->uart_16550_compatible)
+		return;
+
+	if (up->capabilities & UART_CAP_FIFO)
+		p->serial_out(p, UART_FCR, up->fcr);
+	p->serial_out(p, UART_MCR, up->mcr);
+	p->serial_out(p, UART_IER, up->ier);
+
+	// Maybe move the DMA Rx restart check in dma_rx_complete() to own
+	// function (serial8250_rx_dma_restart()) and call it from here.
+	// DMA Tx resume
+
+	d->in_idle = 0;
+}
+
+static void dw8250_set_divisor(struct uart_port *p, unsigned int baud,
+			       unsigned int quot, unsigned int quot_frac)
+{
+	struct uart_8250_port *up = up_to_u8250p(p);
+	int ret;
+
+	ret = dw8250_idle_enter(p);
+	if (ret < 0)
+		goto idle_failed;
+
+	p->serial_out(p, UART_LCR, up->lcr | UART_LCR_DLAB);
+	if (!(p->serial_in(p, UART_LCR) & UART_LCR_DLAB))
+		goto idle_failed;
+
+	serial_dl_write(up, quot);
+	p->serial_out(p, UART_LCR, up->lcr);
+
+idle_failed:
+	dw8250_idle_exit(p);
 }
 
 /*
@@ -148,37 +218,37 @@ static void dw8250_force_idle(struct uart_port *p)
 static void dw8250_check_lcr(struct uart_port *p, unsigned int offset, u32 value)
 {
 	struct dw8250_data *d = to_dw8250_data(p->private_data);
-	void __iomem *addr = p->membase + (offset << p->regshift);
-	int tries = 1000;
+	u32 lcr = p->serial_in(p, UART_LCR);
+	int ret;
 
 	if (offset != UART_LCR || d->uart_16550_compatible)
 		return;
 
 	/* Make sure LCR write wasn't ignored */
-	while (tries--) {
-		u32 lcr = serial_port_in(p, offset);
-
-		if ((value & ~UART_LCR_SPAR) == (lcr & ~UART_LCR_SPAR))
-			return;
+	if ((value & ~UART_LCR_SPAR) == (lcr & ~UART_LCR_SPAR))
+		return;
 
-		dw8250_force_idle(p);
+	if (d->in_idle) {
+		/*
+		 * FIXME: this deadlocks if port->lock is already held
+		 * dev_err(p->dev, "Couldn't set LCR to %d\n", value);
+		 */
+		return;
+	}
 
-#ifdef CONFIG_64BIT
-		if (p->type == PORT_OCTEON)
-			__raw_writeq(value & 0xff, addr);
-		else
-#endif
-		if (p->iotype == UPIO_MEM32)
-			writel(value, addr);
-		else if (p->iotype == UPIO_MEM32BE)
-			iowrite32be(value, addr);
-		else
-			writeb(value, addr);
+	ret = dw8250_idle_enter(p);
+	if (ret < 0) {
+		/*
+		 * FIXME: this deadlocks if port->lock is already held
+		 * dev_err(p->dev, "Couldn't set LCR to %d\n", value);
+		 */
+		goto idle_failed;
 	}
-	/*
-	 * FIXME: this deadlocks if port->lock is already held
-	 * dev_err(p->dev, "Couldn't set LCR to %d\n", value);
-	 */
+
+	p->serial_out(p, UART_LCR, value);
+
+idle_failed:
+	dw8250_idle_exit(p);
 }
 
 /* Returns once the transmitter is empty or we run out of retries */
@@ -547,6 +617,7 @@ static int dw8250_probe(struct platform_device *pdev)
 	p->dev		= dev;
 	p->set_ldisc	= dw8250_set_ldisc;
 	p->set_termios	= dw8250_set_termios;
+	p->set_divisor	= dw8250_set_divisor;
 
 	data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
 	if (!data)

base-commit: d21b26cad33250be758ea9d860ff9d5c3992c459
-- 
2.39.5

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

* Re: [PATCH] serial: 8250 dw: clear FIFO before writting LCR
  2025-10-08 13:11         ` Ilpo Järvinen
@ 2025-10-31 19:32           ` Adriana Nicolae
  2026-01-21 14:31             ` Ilpo Järvinen
  0 siblings, 1 reply; 14+ messages in thread
From: Adriana Nicolae @ 2025-10-31 19:32 UTC (permalink / raw)
  To: Ilpo Järvinen; +Cc: linux-serial, Andy Shevchenko, prasad

On Wed, Oct 8, 2025 at 4:12 PM Ilpo Järvinen
<ilpo.jarvinen@linux.intel.com> wrote:
>
> On Wed, 8 Oct 2025, Adriana Nicolae wrote:
> > Hello, Sorry for missing an update, the exact root cause is not clear, currently the
> > brute force method of draining FIFO right before setting or clearing DLAB was stable
> > during tests.
> >
> > The serial stuck seems to be a failed attempt to clear the DLAB.
> > This operation fails because the USR indicates the hardware is
> > still busy, even though the UART is in loopback mode and should
> > be inactive.
> >
> > To isolate the issue, I tried the following without success:
> > - Added delays: I inserted 100 repeated ndelay(p->frame_time)
> > calls before and after enabling loopback mode to allow the FIFO
> > to clear.
> > - Cleared FIFO: I explicitly cleared the FIFO in addition to
> > adding the delay.
> > - Checked status: I printed the LSR just before the DLAB clear
> > attempt and checked the USB busy bit.
>
> Okay, so the BUSY must be stuck asserted.
>
> Another idea, maybe test tx + rx over loopback to see if that manages to
> de-freeze the BUSY bit. A patch to that effect below.
>
> (I've only added the new block into dw8250_idle_enter() compared with the
> old patch but rebasing to tty-next resulted in some other changes due to
> conflicts.)
>
I've tested the new dw8250_idle_enter() sequence, and you're right,
the BUSY bit remains set after entering loopback mode.

However, the sequence in the patch (including the single loopback
tx/rx) wasn't quite enough. I didn't see any kernel panics or console
stuck anymore, but I've monitored the traces and there were cases when
the trace after "p->serial_out(p, UART_LCR, up->lcr);" showed both
BUSY bit set and DLAB bit still enabled.

>
> Only thing besides BUSY being stuck asserted is something Tx'ing after the
> idle enter sequence. I think we could trap/check that too in
> dw8250_serial_out() by using something along these lines:
>
>         if (d->in_idle && offset == UART_TX) {
>                 WARN_ON_ONCE(1);
>                 return;
>         }
>
> (I think that should catch even console writes but I'm not 100% sure of
> that and it will should get us where the rogue Tx originates from).

I also added the WARN_ON_ONCE check you suggested in
dw8250_serial_out(). The warning has not triggered, so it seems we
don't have a rogue Tx firing while in_idle is set.

>
> > The critical finding was that immediately before the DLAB clear
> > operation (p->serial_out(p, UART_LCR, up->lcr);), the LSR value
> > was 0x6a and the USR busy bit [0] was set. This confirms the UART
> > is busy, which blocks the DLAB modification.
> >
> > This is occurring on a device with a single UART console. The setup
> > does not use DMA or modem control; only the Rx/Tx lines are connected.
> >
> > The trace below, from a single process, shows one successful DLAB
> > clear followed by a failing one. The second attempt repeatedly logs
> > "USR still busy" before eventually succeeding. This can lead to
> > subsequent failures in dw8250_check_lcr: dw8250_idle_entered.
> >
> > Trace Log:
> >
> > <...>-15440  8583.592533: dw8250_idle_enter: in_idle = 1
> > login-15440  8583.713817: dw8250_idle_enter: in loopback mode
> > login-15440  8583.835099: dw8250_idle_enter: LSR in loopback mode is 0x63
> > login-15440  8583.835103: dw8250_set_divisor: UART_LCR_DLAB cleared 13
> > login-15440  8583.835104: dw8250_idle_exit: idle exit
> > login-15440  8583.835105: dw8250_idle_exit: out of loopback mode
> > login-15440  8583.835105: dw8250_idle_exit: in_idle = 0
> > login-15440  8583.835352: dw8250_idle_enter: in_idle = 1
> > login-15440  8583.956633: dw8250_idle_enter: in loopback mode
> > login-15440  8583.956638: dw8250_idle_enter: LSR in loopback mode is 0x6a
> > login-15440  8583.963918: dw8250_set_divisor: USR still busy dl_write
> > login-15440  8584.000332: dw8250_set_divisor: USR still busy dl_write
> > login-15440  8584.040385: dw8250_set_divisor: USR still busy dl_write
> > login-15440  8584.078012: dw8250_set_divisor: UART_LCR_DLAB cleared 93
> > login-15440  8584.078013: dw8250_idle_exit: idle exit
> > login-15440  8584.078014: dw8250_idle_exit: out of loopback mode
> > login-15440  8584.078015: dw8250_idle_exit: in_idle = 0
>
>
>
> --
> From 01df58736a10f7f34aca895ef08e5519953f8572 Mon Sep 17 00:00:00 2001
> From: =?UTF-8?q?Ilpo=20J=C3=A4rvinen?= <ilpo.jarvinen@linux.intel.com>
> Date: Wed, 8 Oct 2025 15:40:19 +0300
> Subject: [PATCH 1/1] serial: 8250_dw: Ensure BUSY is deasserted
> MIME-Version: 1.0
> Content-Type: text/plain; charset=UTF-8
> Content-Transfer-Encoding: 8bit
>
> DW UART cannot write to LCR, DLL, and DLH while BUSY is asserted.
> Existance of BUSY depends on uart_16550_compatible, if UART HW is
> configured with 16550 compatible those registers can always be
> written.
>
> There currently is dw8250_force_idle() which attempts to archive
> non-BUSY state by disabling FIFO, however, the solution is unreliable
> when Rx keeps getting more and more characters.
>
> Create a sequence of operations to enforce that ensures UART cannot
> keep BUSY asserted indefinitely. The new sequence relies on enabling
> loopback mode temporarily to prevent incoming Rx characters keeping
> UART BUSY.
>
> Use the new dw8250_idle_enter/exit() to do divisor writes and LCR
> writes.
>
> This issue was reported by qianfan Zhao who put lots of debugging
> effort into understanding the solution space.
>
> Reported-by: qianfan Zhao <qianfanguijin@163.com>
> Reported-by: Adriana Nicolae <adriana@arista.com>
> Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
> ---
>  drivers/tty/serial/8250/8250_dw.c | 159 +++++++++++++++++++++---------
>  1 file changed, 115 insertions(+), 44 deletions(-)
>
> diff --git a/drivers/tty/serial/8250/8250_dw.c b/drivers/tty/serial/8250/8250_dw.c
> index a53ba04d9770..8e25dfe8e653 100644
> --- a/drivers/tty/serial/8250/8250_dw.c
> +++ b/drivers/tty/serial/8250/8250_dw.c
> @@ -42,6 +42,8 @@
>  /* DesignWare specific register fields */
>  #define DW_UART_MCR_SIRE               BIT(6)
>
> +#define DW_UART_USR_BUSY               BIT(0)
> +
>  /* Renesas specific register fields */
>  #define RZN1_UART_xDMACR_DMA_EN                BIT(0)
>  #define RZN1_UART_xDMACR_1_WORD_BURST  (0 << 1)
> @@ -77,6 +79,7 @@ struct dw8250_data {
>
>         unsigned int            skip_autocfg:1;
>         unsigned int            uart_16550_compatible:1;
> +       unsigned int            in_idle:1;
>  };
>
>  static inline struct dw8250_data *to_dw8250_data(struct dw8250_port_data *data)
> @@ -108,36 +111,103 @@ static inline u32 dw8250_modify_msr(struct uart_port *p, unsigned int offset, u3
>  }
>
>  /*
> - * This function is being called as part of the uart_port::serial_out()
> - * routine. Hence, it must not call serial_port_out() or serial_out()
> - * against the modified registers here, i.e. LCR.
> + * Ensure BUSY is not asserted. If DW UART is configured with
> + * !uart_16550_compatible, the writes to LCR, DLL, and DLH fail while
> + * BUSY is asserted.
> + *
> + * Context: port's lock must be held
>   */
> -static void dw8250_force_idle(struct uart_port *p)
> +static int dw8250_idle_enter(struct uart_port *p)
>  {
> +       struct dw8250_data *d = to_dw8250_data(p->private_data);
>         struct uart_8250_port *up = up_to_u8250p(p);
> -       unsigned int lsr;
> +       u32 lsr;
>
> -       /*
> -        * The following call currently performs serial_out()
> -        * against the FCR register. Because it differs to LCR
> -        * there will be no infinite loop, but if it ever gets
> -        * modified, we might need a new custom version of it
> -        * that avoids infinite recursion.
> -        */
> -       serial8250_clear_and_reinit_fifos(up);
> +       if (d->uart_16550_compatible)
> +               return 0;
> +
> +       d->in_idle = 1;
> +
> +       /* Prevent triggering interrupt from RBR filling */
> +       p->serial_out(p, UART_IER, 0);
> +
> +       serial8250_rx_dma_flush(up);
> +       // What about Tx DMA? Should probably pause that too and resume
> +       // afterwards.
> +
> +       p->serial_out(p, UART_MCR, up->mcr | UART_MCR_LOOP);
> +       if (up->capabilities & UART_CAP_FIFO)
> +               p->serial_out(p, UART_FCR, 0);

Changing this to repeatedly clear the FIFO turned out to reliably
clear the BUSY bit , also no kernel panic or device stuck in busy
mode.

On the device I tested the first clear is not always enough, under
high load I saw it cleared on the second iteration. I'm thinking it
might be some particular issue with the device I'm using where the
first FIFO clear might fail. I never encountered more than 2
iterations with a "ndelay(p->frame_time);" in between here to get out
of BUSY state.

> +
> +       if (p->serial_in(p, d->pdata->usr_reg) & DW_UART_USR_BUSY)
> +               ndelay(p->frame_time);
> +
> +       lsr = serial_lsr_in(up);
> +       if (lsr & UART_LSR_DR) {
> +               p->serial_in(p, UART_RX);
> +               up->lsr_saved_flags = 0;
> +       }
>
>         /*
> -        * With PSLVERR_RESP_EN parameter set to 1, the device generates an
> -        * error response when an attempt to read an empty RBR with FIFO
> -        * enabled.
> +        * BUSY might still be frozen to asserted, try to de-freeze it by
> +        * sending a character over the loopback and receiving it.
>          */
> -       if (up->fcr & UART_FCR_ENABLE_FIFO) {
> -               lsr = serial_port_in(p, UART_LSR);
> -               if (!(lsr & UART_LSR_DR))
> -                       return;
> +       if (p->serial_in(p, d->pdata->usr_reg) & DW_UART_USR_BUSY) {
> +               p->serial_out(p, UART_TX, 0);
> +               ndelay(p->frame_time);
> +               lsr = serial_lsr_in(up);
> +               if (lsr & UART_LSR_DR) {
> +                       p->serial_in(p, UART_RX);
> +                       up->lsr_saved_flags = 0;
> +               }
>         }
>
> -       serial_port_in(p, UART_RX);
> +       /* Now guaranteed to have BUSY deasserted? Just sanity check */
> +       if (p->serial_in(p, d->pdata->usr_reg) & DW_UART_USR_BUSY)
> +               return -EBUSY;
> +
> +       return 0;
> +}
> +
> +static void dw8250_idle_exit(struct uart_port *p)
> +{
> +       struct dw8250_data *d = to_dw8250_data(p->private_data);
> +       struct uart_8250_port *up = up_to_u8250p(p);
> +
> +       if (d->uart_16550_compatible)
> +               return;
> +
> +       if (up->capabilities & UART_CAP_FIFO)
> +               p->serial_out(p, UART_FCR, up->fcr);
> +       p->serial_out(p, UART_MCR, up->mcr);
> +       p->serial_out(p, UART_IER, up->ier);
> +
> +       // Maybe move the DMA Rx restart check in dma_rx_complete() to own
> +       // function (serial8250_rx_dma_restart()) and call it from here.
> +       // DMA Tx resume
> +
> +       d->in_idle = 0;
> +}
> +
> +static void dw8250_set_divisor(struct uart_port *p, unsigned int baud,
> +                              unsigned int quot, unsigned int quot_frac)
> +{
> +       struct uart_8250_port *up = up_to_u8250p(p);
> +       int ret;
> +
> +       ret = dw8250_idle_enter(p);
> +       if (ret < 0)
> +               goto idle_failed;
> +
> +       p->serial_out(p, UART_LCR, up->lcr | UART_LCR_DLAB);
> +       if (!(p->serial_in(p, UART_LCR) & UART_LCR_DLAB))
> +               goto idle_failed;
> +
> +       serial_dl_write(up, quot);
> +       p->serial_out(p, UART_LCR, up->lcr);
> +
> +idle_failed:
> +       dw8250_idle_exit(p);
>  }
>
>  /*
> @@ -148,37 +218,37 @@ static void dw8250_force_idle(struct uart_port *p)
>  static void dw8250_check_lcr(struct uart_port *p, unsigned int offset, u32 value)
>  {
>         struct dw8250_data *d = to_dw8250_data(p->private_data);
> -       void __iomem *addr = p->membase + (offset << p->regshift);
> -       int tries = 1000;
> +       u32 lcr = p->serial_in(p, UART_LCR);
> +       int ret;
>
>         if (offset != UART_LCR || d->uart_16550_compatible)
>                 return;
>
>         /* Make sure LCR write wasn't ignored */
> -       while (tries--) {
> -               u32 lcr = serial_port_in(p, offset);
> -
> -               if ((value & ~UART_LCR_SPAR) == (lcr & ~UART_LCR_SPAR))
> -                       return;
> +       if ((value & ~UART_LCR_SPAR) == (lcr & ~UART_LCR_SPAR))
> +               return;
>
> -               dw8250_force_idle(p);
> +       if (d->in_idle) {
> +               /*
> +                * FIXME: this deadlocks if port->lock is already held
> +                * dev_err(p->dev, "Couldn't set LCR to %d\n", value);
> +                */
> +               return;
> +       }
>
> -#ifdef CONFIG_64BIT
> -               if (p->type == PORT_OCTEON)
> -                       __raw_writeq(value & 0xff, addr);
> -               else
> -#endif
> -               if (p->iotype == UPIO_MEM32)
> -                       writel(value, addr);
> -               else if (p->iotype == UPIO_MEM32BE)
> -                       iowrite32be(value, addr);
> -               else
> -                       writeb(value, addr);
> +       ret = dw8250_idle_enter(p);
> +       if (ret < 0) {
> +               /*
> +                * FIXME: this deadlocks if port->lock is already held
> +                * dev_err(p->dev, "Couldn't set LCR to %d\n", value);
> +                */
> +               goto idle_failed;
>         }
> -       /*
> -        * FIXME: this deadlocks if port->lock is already held
> -        * dev_err(p->dev, "Couldn't set LCR to %d\n", value);
> -        */
> +
> +       p->serial_out(p, UART_LCR, value);
> +
> +idle_failed:
> +       dw8250_idle_exit(p);
>  }
>
>  /* Returns once the transmitter is empty or we run out of retries */
> @@ -547,6 +617,7 @@ static int dw8250_probe(struct platform_device *pdev)
>         p->dev          = dev;
>         p->set_ldisc    = dw8250_set_ldisc;
>         p->set_termios  = dw8250_set_termios;
> +       p->set_divisor  = dw8250_set_divisor;
>
>         data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
>         if (!data)
>
> base-commit: d21b26cad33250be758ea9d860ff9d5c3992c459
> --
> 2.39.5

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

* Re: [PATCH] serial: 8250 dw: clear FIFO before writting LCR
  2025-10-31 19:32           ` Adriana Nicolae
@ 2026-01-21 14:31             ` Ilpo Järvinen
  2026-01-21 16:55               ` Adriana Nicolae
  0 siblings, 1 reply; 14+ messages in thread
From: Ilpo Järvinen @ 2026-01-21 14:31 UTC (permalink / raw)
  To: Adriana Nicolae; +Cc: linux-serial, Andy Shevchenko, prasad

[-- Attachment #1: Type: text/plain, Size: 16100 bytes --]

On Fri, 31 Oct 2025, Adriana Nicolae wrote:

> On Wed, Oct 8, 2025 at 4:12 PM Ilpo Järvinen
> <ilpo.jarvinen@linux.intel.com> wrote:
> >
> > On Wed, 8 Oct 2025, Adriana Nicolae wrote:
> > > Hello, Sorry for missing an update, the exact root cause is not clear, currently the
> > > brute force method of draining FIFO right before setting or clearing DLAB was stable
> > > during tests.
> > >
> > > The serial stuck seems to be a failed attempt to clear the DLAB.
> > > This operation fails because the USR indicates the hardware is
> > > still busy, even though the UART is in loopback mode and should
> > > be inactive.
> > >
> > > To isolate the issue, I tried the following without success:
> > > - Added delays: I inserted 100 repeated ndelay(p->frame_time)
> > > calls before and after enabling loopback mode to allow the FIFO
> > > to clear.
> > > - Cleared FIFO: I explicitly cleared the FIFO in addition to
> > > adding the delay.
> > > - Checked status: I printed the LSR just before the DLAB clear
> > > attempt and checked the USB busy bit.
> >
> > Okay, so the BUSY must be stuck asserted.
> >
> > Another idea, maybe test tx + rx over loopback to see if that manages to
> > de-freeze the BUSY bit. A patch to that effect below.
> >
> > (I've only added the new block into dw8250_idle_enter() compared with the
> > old patch but rebasing to tty-next resulted in some other changes due to
> > conflicts.)
>
> I've tested the new dw8250_idle_enter() sequence, and you're right,
> the BUSY bit remains set after entering loopback mode.
> 
> However, the sequence in the patch (including the single loopback
> tx/rx) wasn't quite enough. I didn't see any kernel panics or console
> stuck anymore, but I've monitored the traces and there were cases when
> the trace after "p->serial_out(p, UART_LCR, up->lcr);" showed both
> BUSY bit set and DLAB bit still enabled.
> 
> >
> > Only thing besides BUSY being stuck asserted is something Tx'ing after the
> > idle enter sequence. I think we could trap/check that too in
> > dw8250_serial_out() by using something along these lines:
> >
> >         if (d->in_idle && offset == UART_TX) {
> >                 WARN_ON_ONCE(1);
> >                 return;
> >         }
> >
> > (I think that should catch even console writes but I'm not 100% sure of
> > that and it will should get us where the rogue Tx originates from).
> 
> I also added the WARN_ON_ONCE check you suggested in
> dw8250_serial_out(). The warning has not triggered, so it seems we
> don't have a rogue Tx firing while in_idle is set.
> 
> >
> > > The critical finding was that immediately before the DLAB clear
> > > operation (p->serial_out(p, UART_LCR, up->lcr);), the LSR value
> > > was 0x6a and the USR busy bit [0] was set. This confirms the UART
> > > is busy, which blocks the DLAB modification.
> > >
> > > This is occurring on a device with a single UART console. The setup
> > > does not use DMA or modem control; only the Rx/Tx lines are connected.
> > >
> > > The trace below, from a single process, shows one successful DLAB
> > > clear followed by a failing one. The second attempt repeatedly logs
> > > "USR still busy" before eventually succeeding. This can lead to
> > > subsequent failures in dw8250_check_lcr: dw8250_idle_entered.
> > >
> > > Trace Log:
> > >
> > > <...>-15440  8583.592533: dw8250_idle_enter: in_idle = 1
> > > login-15440  8583.713817: dw8250_idle_enter: in loopback mode
> > > login-15440  8583.835099: dw8250_idle_enter: LSR in loopback mode is 0x63
> > > login-15440  8583.835103: dw8250_set_divisor: UART_LCR_DLAB cleared 13
> > > login-15440  8583.835104: dw8250_idle_exit: idle exit
> > > login-15440  8583.835105: dw8250_idle_exit: out of loopback mode
> > > login-15440  8583.835105: dw8250_idle_exit: in_idle = 0
> > > login-15440  8583.835352: dw8250_idle_enter: in_idle = 1
> > > login-15440  8583.956633: dw8250_idle_enter: in loopback mode
> > > login-15440  8583.956638: dw8250_idle_enter: LSR in loopback mode is 0x6a
> > > login-15440  8583.963918: dw8250_set_divisor: USR still busy dl_write
> > > login-15440  8584.000332: dw8250_set_divisor: USR still busy dl_write
> > > login-15440  8584.040385: dw8250_set_divisor: USR still busy dl_write
> > > login-15440  8584.078012: dw8250_set_divisor: UART_LCR_DLAB cleared 93
> > > login-15440  8584.078013: dw8250_idle_exit: idle exit
> > > login-15440  8584.078014: dw8250_idle_exit: out of loopback mode
> > > login-15440  8584.078015: dw8250_idle_exit: in_idle = 0
> >
> >
> >
> > --
> > From 01df58736a10f7f34aca895ef08e5519953f8572 Mon Sep 17 00:00:00 2001
> > From: =?UTF-8?q?Ilpo=20J=C3=A4rvinen?= <ilpo.jarvinen@linux.intel.com>
> > Date: Wed, 8 Oct 2025 15:40:19 +0300
> > Subject: [PATCH 1/1] serial: 8250_dw: Ensure BUSY is deasserted
> > MIME-Version: 1.0
> > Content-Type: text/plain; charset=UTF-8
> > Content-Transfer-Encoding: 8bit
> >
> > DW UART cannot write to LCR, DLL, and DLH while BUSY is asserted.
> > Existance of BUSY depends on uart_16550_compatible, if UART HW is
> > configured with 16550 compatible those registers can always be
> > written.
> >
> > There currently is dw8250_force_idle() which attempts to archive
> > non-BUSY state by disabling FIFO, however, the solution is unreliable
> > when Rx keeps getting more and more characters.
> >
> > Create a sequence of operations to enforce that ensures UART cannot
> > keep BUSY asserted indefinitely. The new sequence relies on enabling
> > loopback mode temporarily to prevent incoming Rx characters keeping
> > UART BUSY.
> >
> > Use the new dw8250_idle_enter/exit() to do divisor writes and LCR
> > writes.
> >
> > This issue was reported by qianfan Zhao who put lots of debugging
> > effort into understanding the solution space.
> >
> > Reported-by: qianfan Zhao <qianfanguijin@163.com>
> > Reported-by: Adriana Nicolae <adriana@arista.com>
> > Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
> > ---
> >  drivers/tty/serial/8250/8250_dw.c | 159 +++++++++++++++++++++---------
> >  1 file changed, 115 insertions(+), 44 deletions(-)
> >
> > diff --git a/drivers/tty/serial/8250/8250_dw.c b/drivers/tty/serial/8250/8250_dw.c
> > index a53ba04d9770..8e25dfe8e653 100644
> > --- a/drivers/tty/serial/8250/8250_dw.c
> > +++ b/drivers/tty/serial/8250/8250_dw.c
> > @@ -42,6 +42,8 @@
> >  /* DesignWare specific register fields */
> >  #define DW_UART_MCR_SIRE               BIT(6)
> >
> > +#define DW_UART_USR_BUSY               BIT(0)
> > +
> >  /* Renesas specific register fields */
> >  #define RZN1_UART_xDMACR_DMA_EN                BIT(0)
> >  #define RZN1_UART_xDMACR_1_WORD_BURST  (0 << 1)
> > @@ -77,6 +79,7 @@ struct dw8250_data {
> >
> >         unsigned int            skip_autocfg:1;
> >         unsigned int            uart_16550_compatible:1;
> > +       unsigned int            in_idle:1;
> >  };
> >
> >  static inline struct dw8250_data *to_dw8250_data(struct dw8250_port_data *data)
> > @@ -108,36 +111,103 @@ static inline u32 dw8250_modify_msr(struct uart_port *p, unsigned int offset, u3
> >  }
> >
> >  /*
> > - * This function is being called as part of the uart_port::serial_out()
> > - * routine. Hence, it must not call serial_port_out() or serial_out()
> > - * against the modified registers here, i.e. LCR.
> > + * Ensure BUSY is not asserted. If DW UART is configured with
> > + * !uart_16550_compatible, the writes to LCR, DLL, and DLH fail while
> > + * BUSY is asserted.
> > + *
> > + * Context: port's lock must be held
> >   */
> > -static void dw8250_force_idle(struct uart_port *p)
> > +static int dw8250_idle_enter(struct uart_port *p)
> >  {
> > +       struct dw8250_data *d = to_dw8250_data(p->private_data);
> >         struct uart_8250_port *up = up_to_u8250p(p);
> > -       unsigned int lsr;
> > +       u32 lsr;
> >
> > -       /*
> > -        * The following call currently performs serial_out()
> > -        * against the FCR register. Because it differs to LCR
> > -        * there will be no infinite loop, but if it ever gets
> > -        * modified, we might need a new custom version of it
> > -        * that avoids infinite recursion.
> > -        */
> > -       serial8250_clear_and_reinit_fifos(up);
> > +       if (d->uart_16550_compatible)
> > +               return 0;
> > +
> > +       d->in_idle = 1;
> > +
> > +       /* Prevent triggering interrupt from RBR filling */
> > +       p->serial_out(p, UART_IER, 0);
> > +
> > +       serial8250_rx_dma_flush(up);
> > +       // What about Tx DMA? Should probably pause that too and resume
> > +       // afterwards.
> > +
> > +       p->serial_out(p, UART_MCR, up->mcr | UART_MCR_LOOP);
> > +       if (up->capabilities & UART_CAP_FIFO)
> > +               p->serial_out(p, UART_FCR, 0);
> 
> Changing this to repeatedly clear the FIFO turned out to reliably
> clear the BUSY bit , also no kernel panic or device stuck in busy
> mode.
> 
> On the device I tested the first clear is not always enough, under
> high load I saw it cleared on the second iteration. I'm thinking it
> might be some particular issue with the device I'm using where the
> first FIFO clear might fail. I never encountered more than 2
> iterations with a "ndelay(p->frame_time);" in between here to get out
> of BUSY state.

Hi,

I seem to have missed this email until now (I'm sorry about that, though 
to my defence, IIRC, I was quite sick around that timeframe it was sent 
and clear the email backlog isn't ever fun and may end up missing 
something).

Do you mean changing this to a simple loop or writing something else than 
just 0 to FCR (or perhaps calling serial8250_clear_fifos())?

What is the exact code that you found working?

So when you fixed this FIFO clearing thing, everything seemed to work okay 
after that?

In the meantime, this issue has once again been reported to me by somebody 
else, and I've done improvements to shutdown code as well to address a 
few BUSY related problems (I'll be posting a series that solved that 
case soon but I suppose this patch needs amendments based on input from
your case).

-- 
 i.

> > +       if (p->serial_in(p, d->pdata->usr_reg) & DW_UART_USR_BUSY)
> > +               ndelay(p->frame_time);
> > +
> > +       lsr = serial_lsr_in(up);
> > +       if (lsr & UART_LSR_DR) {
> > +               p->serial_in(p, UART_RX);
> > +               up->lsr_saved_flags = 0;
> > +       }
> >
> >         /*
> > -        * With PSLVERR_RESP_EN parameter set to 1, the device generates an
> > -        * error response when an attempt to read an empty RBR with FIFO
> > -        * enabled.
> > +        * BUSY might still be frozen to asserted, try to de-freeze it by
> > +        * sending a character over the loopback and receiving it.
> >          */
> > -       if (up->fcr & UART_FCR_ENABLE_FIFO) {
> > -               lsr = serial_port_in(p, UART_LSR);
> > -               if (!(lsr & UART_LSR_DR))
> > -                       return;
> > +       if (p->serial_in(p, d->pdata->usr_reg) & DW_UART_USR_BUSY) {
> > +               p->serial_out(p, UART_TX, 0);
> > +               ndelay(p->frame_time);
> > +               lsr = serial_lsr_in(up);
> > +               if (lsr & UART_LSR_DR) {
> > +                       p->serial_in(p, UART_RX);
> > +                       up->lsr_saved_flags = 0;
> > +               }
> >         }
> >
> > -       serial_port_in(p, UART_RX);
> > +       /* Now guaranteed to have BUSY deasserted? Just sanity check */
> > +       if (p->serial_in(p, d->pdata->usr_reg) & DW_UART_USR_BUSY)
> > +               return -EBUSY;
> > +
> > +       return 0;
> > +}
> > +
> > +static void dw8250_idle_exit(struct uart_port *p)
> > +{
> > +       struct dw8250_data *d = to_dw8250_data(p->private_data);
> > +       struct uart_8250_port *up = up_to_u8250p(p);
> > +
> > +       if (d->uart_16550_compatible)
> > +               return;
> > +
> > +       if (up->capabilities & UART_CAP_FIFO)
> > +               p->serial_out(p, UART_FCR, up->fcr);
> > +       p->serial_out(p, UART_MCR, up->mcr);
> > +       p->serial_out(p, UART_IER, up->ier);
> > +
> > +       // Maybe move the DMA Rx restart check in dma_rx_complete() to own
> > +       // function (serial8250_rx_dma_restart()) and call it from here.
> > +       // DMA Tx resume
> > +
> > +       d->in_idle = 0;
> > +}
> > +
> > +static void dw8250_set_divisor(struct uart_port *p, unsigned int baud,
> > +                              unsigned int quot, unsigned int quot_frac)
> > +{
> > +       struct uart_8250_port *up = up_to_u8250p(p);
> > +       int ret;
> > +
> > +       ret = dw8250_idle_enter(p);
> > +       if (ret < 0)
> > +               goto idle_failed;
> > +
> > +       p->serial_out(p, UART_LCR, up->lcr | UART_LCR_DLAB);
> > +       if (!(p->serial_in(p, UART_LCR) & UART_LCR_DLAB))
> > +               goto idle_failed;
> > +
> > +       serial_dl_write(up, quot);
> > +       p->serial_out(p, UART_LCR, up->lcr);
> > +
> > +idle_failed:
> > +       dw8250_idle_exit(p);
> >  }
> >
> >  /*
> > @@ -148,37 +218,37 @@ static void dw8250_force_idle(struct uart_port *p)
> >  static void dw8250_check_lcr(struct uart_port *p, unsigned int offset, u32 value)
> >  {
> >         struct dw8250_data *d = to_dw8250_data(p->private_data);
> > -       void __iomem *addr = p->membase + (offset << p->regshift);
> > -       int tries = 1000;
> > +       u32 lcr = p->serial_in(p, UART_LCR);
> > +       int ret;
> >
> >         if (offset != UART_LCR || d->uart_16550_compatible)
> >                 return;
> >
> >         /* Make sure LCR write wasn't ignored */
> > -       while (tries--) {
> > -               u32 lcr = serial_port_in(p, offset);
> > -
> > -               if ((value & ~UART_LCR_SPAR) == (lcr & ~UART_LCR_SPAR))
> > -                       return;
> > +       if ((value & ~UART_LCR_SPAR) == (lcr & ~UART_LCR_SPAR))
> > +               return;
> >
> > -               dw8250_force_idle(p);
> > +       if (d->in_idle) {
> > +               /*
> > +                * FIXME: this deadlocks if port->lock is already held
> > +                * dev_err(p->dev, "Couldn't set LCR to %d\n", value);
> > +                */
> > +               return;
> > +       }
> >
> > -#ifdef CONFIG_64BIT
> > -               if (p->type == PORT_OCTEON)
> > -                       __raw_writeq(value & 0xff, addr);
> > -               else
> > -#endif
> > -               if (p->iotype == UPIO_MEM32)
> > -                       writel(value, addr);
> > -               else if (p->iotype == UPIO_MEM32BE)
> > -                       iowrite32be(value, addr);
> > -               else
> > -                       writeb(value, addr);
> > +       ret = dw8250_idle_enter(p);
> > +       if (ret < 0) {
> > +               /*
> > +                * FIXME: this deadlocks if port->lock is already held
> > +                * dev_err(p->dev, "Couldn't set LCR to %d\n", value);
> > +                */
> > +               goto idle_failed;
> >         }
> > -       /*
> > -        * FIXME: this deadlocks if port->lock is already held
> > -        * dev_err(p->dev, "Couldn't set LCR to %d\n", value);
> > -        */
> > +
> > +       p->serial_out(p, UART_LCR, value);
> > +
> > +idle_failed:
> > +       dw8250_idle_exit(p);
> >  }
> >
> >  /* Returns once the transmitter is empty or we run out of retries */
> > @@ -547,6 +617,7 @@ static int dw8250_probe(struct platform_device *pdev)
> >         p->dev          = dev;
> >         p->set_ldisc    = dw8250_set_ldisc;
> >         p->set_termios  = dw8250_set_termios;
> > +       p->set_divisor  = dw8250_set_divisor;
> >
> >         data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
> >         if (!data)
> >
> > base-commit: d21b26cad33250be758ea9d860ff9d5c3992c459
> > --
> > 2.39.5
> 

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

* Re: [PATCH] serial: 8250 dw: clear FIFO before writting LCR
  2026-01-21 14:31             ` Ilpo Järvinen
@ 2026-01-21 16:55               ` Adriana Nicolae
  2026-01-22 13:18                 ` Ilpo Järvinen
  0 siblings, 1 reply; 14+ messages in thread
From: Adriana Nicolae @ 2026-01-21 16:55 UTC (permalink / raw)
  To: Ilpo Järvinen; +Cc: linux-serial, Andy Shevchenko, prasad

On Wed, Jan 21, 2026 at 4:31 PM Ilpo Järvinen
<ilpo.jarvinen@linux.intel.com> wrote:
>
> On Fri, 31 Oct 2025, Adriana Nicolae wrote:
>
> > On Wed, Oct 8, 2025 at 4:12 PM Ilpo Järvinen
> > <ilpo.jarvinen@linux.intel.com> wrote:
> > >
> > > On Wed, 8 Oct 2025, Adriana Nicolae wrote:
> > > > Hello, Sorry for missing an update, the exact root cause is not clear, currently the
> > > > brute force method of draining FIFO right before setting or clearing DLAB was stable
> > > > during tests.
> > > >
> > > > The serial stuck seems to be a failed attempt to clear the DLAB.
> > > > This operation fails because the USR indicates the hardware is
> > > > still busy, even though the UART is in loopback mode and should
> > > > be inactive.
> > > >
> > > > To isolate the issue, I tried the following without success:
> > > > - Added delays: I inserted 100 repeated ndelay(p->frame_time)
> > > > calls before and after enabling loopback mode to allow the FIFO
> > > > to clear.
> > > > - Cleared FIFO: I explicitly cleared the FIFO in addition to
> > > > adding the delay.
> > > > - Checked status: I printed the LSR just before the DLAB clear
> > > > attempt and checked the USB busy bit.
> > >
> > > Okay, so the BUSY must be stuck asserted.
> > >
> > > Another idea, maybe test tx + rx over loopback to see if that manages to
> > > de-freeze the BUSY bit. A patch to that effect below.
> > >
> > > (I've only added the new block into dw8250_idle_enter() compared with the
> > > old patch but rebasing to tty-next resulted in some other changes due to
> > > conflicts.)
> >
> > I've tested the new dw8250_idle_enter() sequence, and you're right,
> > the BUSY bit remains set after entering loopback mode.
> >
> > However, the sequence in the patch (including the single loopback
> > tx/rx) wasn't quite enough. I didn't see any kernel panics or console
> > stuck anymore, but I've monitored the traces and there were cases when
> > the trace after "p->serial_out(p, UART_LCR, up->lcr);" showed both
> > BUSY bit set and DLAB bit still enabled.
> >
> > >
> > > Only thing besides BUSY being stuck asserted is something Tx'ing after the
> > > idle enter sequence. I think we could trap/check that too in
> > > dw8250_serial_out() by using something along these lines:
> > >
> > >         if (d->in_idle && offset == UART_TX) {
> > >                 WARN_ON_ONCE(1);
> > >                 return;
> > >         }
> > >
> > > (I think that should catch even console writes but I'm not 100% sure of
> > > that and it will should get us where the rogue Tx originates from).
> >
> > I also added the WARN_ON_ONCE check you suggested in
> > dw8250_serial_out(). The warning has not triggered, so it seems we
> > don't have a rogue Tx firing while in_idle is set.
> >
> > >
> > > > The critical finding was that immediately before the DLAB clear
> > > > operation (p->serial_out(p, UART_LCR, up->lcr);), the LSR value
> > > > was 0x6a and the USR busy bit [0] was set. This confirms the UART
> > > > is busy, which blocks the DLAB modification.
> > > >
> > > > This is occurring on a device with a single UART console. The setup
> > > > does not use DMA or modem control; only the Rx/Tx lines are connected.
> > > >
> > > > The trace below, from a single process, shows one successful DLAB
> > > > clear followed by a failing one. The second attempt repeatedly logs
> > > > "USR still busy" before eventually succeeding. This can lead to
> > > > subsequent failures in dw8250_check_lcr: dw8250_idle_entered.
> > > >
> > > > Trace Log:
> > > >
> > > > <...>-15440  8583.592533: dw8250_idle_enter: in_idle = 1
> > > > login-15440  8583.713817: dw8250_idle_enter: in loopback mode
> > > > login-15440  8583.835099: dw8250_idle_enter: LSR in loopback mode is 0x63
> > > > login-15440  8583.835103: dw8250_set_divisor: UART_LCR_DLAB cleared 13
> > > > login-15440  8583.835104: dw8250_idle_exit: idle exit
> > > > login-15440  8583.835105: dw8250_idle_exit: out of loopback mode
> > > > login-15440  8583.835105: dw8250_idle_exit: in_idle = 0
> > > > login-15440  8583.835352: dw8250_idle_enter: in_idle = 1
> > > > login-15440  8583.956633: dw8250_idle_enter: in loopback mode
> > > > login-15440  8583.956638: dw8250_idle_enter: LSR in loopback mode is 0x6a
> > > > login-15440  8583.963918: dw8250_set_divisor: USR still busy dl_write
> > > > login-15440  8584.000332: dw8250_set_divisor: USR still busy dl_write
> > > > login-15440  8584.040385: dw8250_set_divisor: USR still busy dl_write
> > > > login-15440  8584.078012: dw8250_set_divisor: UART_LCR_DLAB cleared 93
> > > > login-15440  8584.078013: dw8250_idle_exit: idle exit
> > > > login-15440  8584.078014: dw8250_idle_exit: out of loopback mode
> > > > login-15440  8584.078015: dw8250_idle_exit: in_idle = 0
> > >
> > >
> > >
> > > --
> > > From 01df58736a10f7f34aca895ef08e5519953f8572 Mon Sep 17 00:00:00 2001
> > > From: =?UTF-8?q?Ilpo=20J=C3=A4rvinen?= <ilpo.jarvinen@linux.intel.com>
> > > Date: Wed, 8 Oct 2025 15:40:19 +0300
> > > Subject: [PATCH 1/1] serial: 8250_dw: Ensure BUSY is deasserted
> > > MIME-Version: 1.0
> > > Content-Type: text/plain; charset=UTF-8
> > > Content-Transfer-Encoding: 8bit
> > >
> > > DW UART cannot write to LCR, DLL, and DLH while BUSY is asserted.
> > > Existance of BUSY depends on uart_16550_compatible, if UART HW is
> > > configured with 16550 compatible those registers can always be
> > > written.
> > >
> > > There currently is dw8250_force_idle() which attempts to archive
> > > non-BUSY state by disabling FIFO, however, the solution is unreliable
> > > when Rx keeps getting more and more characters.
> > >
> > > Create a sequence of operations to enforce that ensures UART cannot
> > > keep BUSY asserted indefinitely. The new sequence relies on enabling
> > > loopback mode temporarily to prevent incoming Rx characters keeping
> > > UART BUSY.
> > >
> > > Use the new dw8250_idle_enter/exit() to do divisor writes and LCR
> > > writes.
> > >
> > > This issue was reported by qianfan Zhao who put lots of debugging
> > > effort into understanding the solution space.
> > >
> > > Reported-by: qianfan Zhao <qianfanguijin@163.com>
> > > Reported-by: Adriana Nicolae <adriana@arista.com>
> > > Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
> > > ---
> > >  drivers/tty/serial/8250/8250_dw.c | 159 +++++++++++++++++++++---------
> > >  1 file changed, 115 insertions(+), 44 deletions(-)
> > >
> > > diff --git a/drivers/tty/serial/8250/8250_dw.c b/drivers/tty/serial/8250/8250_dw.c
> > > index a53ba04d9770..8e25dfe8e653 100644
> > > --- a/drivers/tty/serial/8250/8250_dw.c
> > > +++ b/drivers/tty/serial/8250/8250_dw.c
> > > @@ -42,6 +42,8 @@
> > >  /* DesignWare specific register fields */
> > >  #define DW_UART_MCR_SIRE               BIT(6)
> > >
> > > +#define DW_UART_USR_BUSY               BIT(0)
> > > +
> > >  /* Renesas specific register fields */
> > >  #define RZN1_UART_xDMACR_DMA_EN                BIT(0)
> > >  #define RZN1_UART_xDMACR_1_WORD_BURST  (0 << 1)
> > > @@ -77,6 +79,7 @@ struct dw8250_data {
> > >
> > >         unsigned int            skip_autocfg:1;
> > >         unsigned int            uart_16550_compatible:1;
> > > +       unsigned int            in_idle:1;
> > >  };
> > >
> > >  static inline struct dw8250_data *to_dw8250_data(struct dw8250_port_data *data)
> > > @@ -108,36 +111,103 @@ static inline u32 dw8250_modify_msr(struct uart_port *p, unsigned int offset, u3
> > >  }
> > >
> > >  /*
> > > - * This function is being called as part of the uart_port::serial_out()
> > > - * routine. Hence, it must not call serial_port_out() or serial_out()
> > > - * against the modified registers here, i.e. LCR.
> > > + * Ensure BUSY is not asserted. If DW UART is configured with
> > > + * !uart_16550_compatible, the writes to LCR, DLL, and DLH fail while
> > > + * BUSY is asserted.
> > > + *
> > > + * Context: port's lock must be held
> > >   */
> > > -static void dw8250_force_idle(struct uart_port *p)
> > > +static int dw8250_idle_enter(struct uart_port *p)
> > >  {
> > > +       struct dw8250_data *d = to_dw8250_data(p->private_data);
> > >         struct uart_8250_port *up = up_to_u8250p(p);
> > > -       unsigned int lsr;
> > > +       u32 lsr;
> > >
> > > -       /*
> > > -        * The following call currently performs serial_out()
> > > -        * against the FCR register. Because it differs to LCR
> > > -        * there will be no infinite loop, but if it ever gets
> > > -        * modified, we might need a new custom version of it
> > > -        * that avoids infinite recursion.
> > > -        */
> > > -       serial8250_clear_and_reinit_fifos(up);
> > > +       if (d->uart_16550_compatible)
> > > +               return 0;
> > > +
> > > +       d->in_idle = 1;
> > > +
> > > +       /* Prevent triggering interrupt from RBR filling */
> > > +       p->serial_out(p, UART_IER, 0);
> > > +
> > > +       serial8250_rx_dma_flush(up);
> > > +       // What about Tx DMA? Should probably pause that too and resume
> > > +       // afterwards.
> > > +
> > > +       p->serial_out(p, UART_MCR, up->mcr | UART_MCR_LOOP);
> > > +       if (up->capabilities & UART_CAP_FIFO)
> > > +               p->serial_out(p, UART_FCR, 0);
> >
> > Changing this to repeatedly clear the FIFO turned out to reliably
> > clear the BUSY bit , also no kernel panic or device stuck in busy
> > mode.
> >
> > On the device I tested the first clear is not always enough, under
> > high load I saw it cleared on the second iteration. I'm thinking it
> > might be some particular issue with the device I'm using where the
> > first FIFO clear might fail. I never encountered more than 2
> > iterations with a "ndelay(p->frame_time);" in between here to get out
> > of BUSY state.
>
> Hi,
>
> I seem to have missed this email until now (I'm sorry about that, though
> to my defence, IIRC, I was quite sick around that timeframe it was sent
> and clear the email backlog isn't ever fun and may end up missing
> something).
>
> Do you mean changing this to a simple loop or writing something else than
> just 0 to FCR (or perhaps calling serial8250_clear_fifos())?
>
> What is the exact code that you found working?
>
Yes, everything worked ok for me after changing the dw8250_idle_enter
function with the one below. From traces added in the function, it
sometimes reported iterations_in_busy = 2 but never higher than that.
The function only has the prepended "while(p->serial_in(p,
d->pdata->usr_reg) & DW_UART_USR_BUSY) {" to iterate forever, although
it was at most 2 iterations when serial was stressed:
static int dw8250_idle_enter(struct uart_port *p)
{
    struct dw8250_data *d = to_dw8250_data(p->private_data);
    struct uart_8250_port *up = up_to_u8250p(p);
    u32 lsr, iterations_in_busy = 0;

    if (d->uart_16550_compatible)
        return 0;

    d->in_idle = 1;

    /* Prevent triggering interrupt from RBR filling */
    p->serial_out(p, UART_IER, 0);

    serial8250_rx_dma_flush(up);
    // What about Tx DMA? Should probably pause that too and resume
    // afterwards.
    p->serial_out(p, UART_MCR, up->mcr | UART_MCR_LOOP);

    while(p->serial_in(p, d->pdata->usr_reg) & DW_UART_USR_BUSY) {
        if (up->capabilities & UART_CAP_FIFO) {
            p->serial_out(p, UART_FCR, UART_FCR_ENABLE_FIFO);
            p->serial_out(p, UART_FCR, UART_FCR_ENABLE_FIFO |
                UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT);
            p->serial_out(p, UART_FCR, 0);
        }
        ndelay(p->frame_time);
        iterations_in_busy++;
    }

    trace_printk("Not busy got after %d\n", iterations_in_busy);
    lsr = serial_lsr_in(up);
    if (lsr & UART_LSR_DR) {
        p->serial_in(p, UART_RX);
        up->lsr_saved_flags = 0;
    }

        /*
     * BUSY might still be frozen to asserted, try to de-freeze it by
     * sending a character over the loopback and receiving it.
         */
    if (p->serial_in(p, d->pdata->usr_reg) & DW_UART_USR_BUSY) {
        trace_printk("Serial USR still busy\n");
        p->serial_out(p, UART_TX, 0);
        ndelay(1000);
        lsr = serial_lsr_in(up);

        if (lsr & UART_LSR_DR) {
            p->serial_in(p, UART_RX);
            up->lsr_saved_flags = 0;
        }
        }

     /* Now guaranteed to have BUSY deasserted? Just sanity check */
    if (p->serial_in(p, d->pdata->usr_reg) & DW_UART_USR_BUSY) {
        trace_printk("BUSY\n");
        return -EBUSY;
    }

    return 0;
}
> So when you fixed this FIFO clearing thing, everything seemed to work okay
> after that?
>
> In the meantime, this issue has once again been reported to me by somebody
> else, and I've done improvements to shutdown code as well to address a
> few BUSY related problems (I'll be posting a series that solved that
> case soon but I suppose this patch needs amendments based on input from
> your case).
>
> --
>  i.
>
> > > +       if (p->serial_in(p, d->pdata->usr_reg) & DW_UART_USR_BUSY)
> > > +               ndelay(p->frame_time);
> > > +
> > > +       lsr = serial_lsr_in(up);
> > > +       if (lsr & UART_LSR_DR) {
> > > +               p->serial_in(p, UART_RX);
> > > +               up->lsr_saved_flags = 0;
> > > +       }
> > >
> > >         /*
> > > -        * With PSLVERR_RESP_EN parameter set to 1, the device generates an
> > > -        * error response when an attempt to read an empty RBR with FIFO
> > > -        * enabled.
> > > +        * BUSY might still be frozen to asserted, try to de-freeze it by
> > > +        * sending a character over the loopback and receiving it.
> > >          */
> > > -       if (up->fcr & UART_FCR_ENABLE_FIFO) {
> > > -               lsr = serial_port_in(p, UART_LSR);
> > > -               if (!(lsr & UART_LSR_DR))
> > > -                       return;
> > > +       if (p->serial_in(p, d->pdata->usr_reg) & DW_UART_USR_BUSY) {
> > > +               p->serial_out(p, UART_TX, 0);
> > > +               ndelay(p->frame_time);
> > > +               lsr = serial_lsr_in(up);
> > > +               if (lsr & UART_LSR_DR) {
> > > +                       p->serial_in(p, UART_RX);
> > > +                       up->lsr_saved_flags = 0;
> > > +               }
> > >         }
> > >
> > > -       serial_port_in(p, UART_RX);
> > > +       /* Now guaranteed to have BUSY deasserted? Just sanity check */
> > > +       if (p->serial_in(p, d->pdata->usr_reg) & DW_UART_USR_BUSY)
> > > +               return -EBUSY;
> > > +
> > > +       return 0;
> > > +}
> > > +
> > > +static void dw8250_idle_exit(struct uart_port *p)
> > > +{
> > > +       struct dw8250_data *d = to_dw8250_data(p->private_data);
> > > +       struct uart_8250_port *up = up_to_u8250p(p);
> > > +
> > > +       if (d->uart_16550_compatible)
> > > +               return;
> > > +
> > > +       if (up->capabilities & UART_CAP_FIFO)
> > > +               p->serial_out(p, UART_FCR, up->fcr);
> > > +       p->serial_out(p, UART_MCR, up->mcr);
> > > +       p->serial_out(p, UART_IER, up->ier);
> > > +
> > > +       // Maybe move the DMA Rx restart check in dma_rx_complete() to own
> > > +       // function (serial8250_rx_dma_restart()) and call it from here.
> > > +       // DMA Tx resume
> > > +
> > > +       d->in_idle = 0;
> > > +}
> > > +
> > > +static void dw8250_set_divisor(struct uart_port *p, unsigned int baud,
> > > +                              unsigned int quot, unsigned int quot_frac)
> > > +{
> > > +       struct uart_8250_port *up = up_to_u8250p(p);
> > > +       int ret;
> > > +
> > > +       ret = dw8250_idle_enter(p);
> > > +       if (ret < 0)
> > > +               goto idle_failed;
> > > +
> > > +       p->serial_out(p, UART_LCR, up->lcr | UART_LCR_DLAB);
> > > +       if (!(p->serial_in(p, UART_LCR) & UART_LCR_DLAB))
> > > +               goto idle_failed;
> > > +
> > > +       serial_dl_write(up, quot);
> > > +       p->serial_out(p, UART_LCR, up->lcr);
> > > +
> > > +idle_failed:
> > > +       dw8250_idle_exit(p);
> > >  }
> > >
> > >  /*
> > > @@ -148,37 +218,37 @@ static void dw8250_force_idle(struct uart_port *p)
> > >  static void dw8250_check_lcr(struct uart_port *p, unsigned int offset, u32 value)
> > >  {
> > >         struct dw8250_data *d = to_dw8250_data(p->private_data);
> > > -       void __iomem *addr = p->membase + (offset << p->regshift);
> > > -       int tries = 1000;
> > > +       u32 lcr = p->serial_in(p, UART_LCR);
> > > +       int ret;
> > >
> > >         if (offset != UART_LCR || d->uart_16550_compatible)
> > >                 return;
> > >
> > >         /* Make sure LCR write wasn't ignored */
> > > -       while (tries--) {
> > > -               u32 lcr = serial_port_in(p, offset);
> > > -
> > > -               if ((value & ~UART_LCR_SPAR) == (lcr & ~UART_LCR_SPAR))
> > > -                       return;
> > > +       if ((value & ~UART_LCR_SPAR) == (lcr & ~UART_LCR_SPAR))
> > > +               return;
> > >
> > > -               dw8250_force_idle(p);
> > > +       if (d->in_idle) {
> > > +               /*
> > > +                * FIXME: this deadlocks if port->lock is already held
> > > +                * dev_err(p->dev, "Couldn't set LCR to %d\n", value);
> > > +                */
> > > +               return;
> > > +       }
> > >
> > > -#ifdef CONFIG_64BIT
> > > -               if (p->type == PORT_OCTEON)
> > > -                       __raw_writeq(value & 0xff, addr);
> > > -               else
> > > -#endif
> > > -               if (p->iotype == UPIO_MEM32)
> > > -                       writel(value, addr);
> > > -               else if (p->iotype == UPIO_MEM32BE)
> > > -                       iowrite32be(value, addr);
> > > -               else
> > > -                       writeb(value, addr);
> > > +       ret = dw8250_idle_enter(p);
> > > +       if (ret < 0) {
> > > +               /*
> > > +                * FIXME: this deadlocks if port->lock is already held
> > > +                * dev_err(p->dev, "Couldn't set LCR to %d\n", value);
> > > +                */
> > > +               goto idle_failed;
> > >         }
> > > -       /*
> > > -        * FIXME: this deadlocks if port->lock is already held
> > > -        * dev_err(p->dev, "Couldn't set LCR to %d\n", value);
> > > -        */
> > > +
> > > +       p->serial_out(p, UART_LCR, value);
> > > +
> > > +idle_failed:
> > > +       dw8250_idle_exit(p);
> > >  }
> > >
> > >  /* Returns once the transmitter is empty or we run out of retries */
> > > @@ -547,6 +617,7 @@ static int dw8250_probe(struct platform_device *pdev)
> > >         p->dev          = dev;
> > >         p->set_ldisc    = dw8250_set_ldisc;
> > >         p->set_termios  = dw8250_set_termios;
> > > +       p->set_divisor  = dw8250_set_divisor;
> > >
> > >         data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
> > >         if (!data)
> > >
> > > base-commit: d21b26cad33250be758ea9d860ff9d5c3992c459
> > > --
> > > 2.39.5
> >

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

* Re: [PATCH] serial: 8250 dw: clear FIFO before writting LCR
  2026-01-21 16:55               ` Adriana Nicolae
@ 2026-01-22 13:18                 ` Ilpo Järvinen
  0 siblings, 0 replies; 14+ messages in thread
From: Ilpo Järvinen @ 2026-01-22 13:18 UTC (permalink / raw)
  To: Adriana Nicolae; +Cc: linux-serial, Andy Shevchenko, prasad

[-- Attachment #1: Type: text/plain, Size: 15950 bytes --]

On Wed, 21 Jan 2026, Adriana Nicolae wrote:
> On Wed, Jan 21, 2026 at 4:31 PM Ilpo Järvinen
> <ilpo.jarvinen@linux.intel.com> wrote:
> > On Fri, 31 Oct 2025, Adriana Nicolae wrote:
> > > On Wed, Oct 8, 2025 at 4:12 PM Ilpo Järvinen
> > > <ilpo.jarvinen@linux.intel.com> wrote:
> > > > On Wed, 8 Oct 2025, Adriana Nicolae wrote:
> > > > > Hello, Sorry for missing an update, the exact root cause is not clear, currently the
> > > > > brute force method of draining FIFO right before setting or clearing DLAB was stable
> > > > > during tests.
> > > > >
> > > > > The serial stuck seems to be a failed attempt to clear the DLAB.
> > > > > This operation fails because the USR indicates the hardware is
> > > > > still busy, even though the UART is in loopback mode and should
> > > > > be inactive.
> > > > >
> > > > > To isolate the issue, I tried the following without success:
> > > > > - Added delays: I inserted 100 repeated ndelay(p->frame_time)
> > > > > calls before and after enabling loopback mode to allow the FIFO
> > > > > to clear.
> > > > > - Cleared FIFO: I explicitly cleared the FIFO in addition to
> > > > > adding the delay.
> > > > > - Checked status: I printed the LSR just before the DLAB clear
> > > > > attempt and checked the USB busy bit.
> > > >
> > > > Okay, so the BUSY must be stuck asserted.
> > > >
> > > > Another idea, maybe test tx + rx over loopback to see if that manages to
> > > > de-freeze the BUSY bit. A patch to that effect below.
> > > >
> > > > (I've only added the new block into dw8250_idle_enter() compared with the
> > > > old patch but rebasing to tty-next resulted in some other changes due to
> > > > conflicts.)
> > >
> > > I've tested the new dw8250_idle_enter() sequence, and you're right,
> > > the BUSY bit remains set after entering loopback mode.
> > >
> > > However, the sequence in the patch (including the single loopback
> > > tx/rx) wasn't quite enough. I didn't see any kernel panics or console
> > > stuck anymore, but I've monitored the traces and there were cases when
> > > the trace after "p->serial_out(p, UART_LCR, up->lcr);" showed both
> > > BUSY bit set and DLAB bit still enabled.
> > >
> > > >
> > > > Only thing besides BUSY being stuck asserted is something Tx'ing after the
> > > > idle enter sequence. I think we could trap/check that too in
> > > > dw8250_serial_out() by using something along these lines:
> > > >
> > > >         if (d->in_idle && offset == UART_TX) {
> > > >                 WARN_ON_ONCE(1);
> > > >                 return;
> > > >         }
> > > >
> > > > (I think that should catch even console writes but I'm not 100% sure of
> > > > that and it will should get us where the rogue Tx originates from).
> > >
> > > I also added the WARN_ON_ONCE check you suggested in
> > > dw8250_serial_out(). The warning has not triggered, so it seems we
> > > don't have a rogue Tx firing while in_idle is set.
> > >
> > > >
> > > > > The critical finding was that immediately before the DLAB clear
> > > > > operation (p->serial_out(p, UART_LCR, up->lcr);), the LSR value
> > > > > was 0x6a and the USR busy bit [0] was set. This confirms the UART
> > > > > is busy, which blocks the DLAB modification.
> > > > >
> > > > > This is occurring on a device with a single UART console. The setup
> > > > > does not use DMA or modem control; only the Rx/Tx lines are connected.
> > > > >
> > > > > The trace below, from a single process, shows one successful DLAB
> > > > > clear followed by a failing one. The second attempt repeatedly logs
> > > > > "USR still busy" before eventually succeeding. This can lead to
> > > > > subsequent failures in dw8250_check_lcr: dw8250_idle_entered.
> > > > >
> > > > > Trace Log:
> > > > >
> > > > > <...>-15440  8583.592533: dw8250_idle_enter: in_idle = 1
> > > > > login-15440  8583.713817: dw8250_idle_enter: in loopback mode
> > > > > login-15440  8583.835099: dw8250_idle_enter: LSR in loopback mode is 0x63
> > > > > login-15440  8583.835103: dw8250_set_divisor: UART_LCR_DLAB cleared 13
> > > > > login-15440  8583.835104: dw8250_idle_exit: idle exit
> > > > > login-15440  8583.835105: dw8250_idle_exit: out of loopback mode
> > > > > login-15440  8583.835105: dw8250_idle_exit: in_idle = 0
> > > > > login-15440  8583.835352: dw8250_idle_enter: in_idle = 1
> > > > > login-15440  8583.956633: dw8250_idle_enter: in loopback mode
> > > > > login-15440  8583.956638: dw8250_idle_enter: LSR in loopback mode is 0x6a
> > > > > login-15440  8583.963918: dw8250_set_divisor: USR still busy dl_write
> > > > > login-15440  8584.000332: dw8250_set_divisor: USR still busy dl_write
> > > > > login-15440  8584.040385: dw8250_set_divisor: USR still busy dl_write
> > > > > login-15440  8584.078012: dw8250_set_divisor: UART_LCR_DLAB cleared 93
> > > > > login-15440  8584.078013: dw8250_idle_exit: idle exit
> > > > > login-15440  8584.078014: dw8250_idle_exit: out of loopback mode
> > > > > login-15440  8584.078015: dw8250_idle_exit: in_idle = 0
> > > >
> > > >
> > > >
> > > > --
> > > > From 01df58736a10f7f34aca895ef08e5519953f8572 Mon Sep 17 00:00:00 2001
> > > > From: =?UTF-8?q?Ilpo=20J=C3=A4rvinen?= <ilpo.jarvinen@linux.intel.com>
> > > > Date: Wed, 8 Oct 2025 15:40:19 +0300
> > > > Subject: [PATCH 1/1] serial: 8250_dw: Ensure BUSY is deasserted
> > > > MIME-Version: 1.0
> > > > Content-Type: text/plain; charset=UTF-8
> > > > Content-Transfer-Encoding: 8bit
> > > >
> > > > DW UART cannot write to LCR, DLL, and DLH while BUSY is asserted.
> > > > Existance of BUSY depends on uart_16550_compatible, if UART HW is
> > > > configured with 16550 compatible those registers can always be
> > > > written.
> > > >
> > > > There currently is dw8250_force_idle() which attempts to archive
> > > > non-BUSY state by disabling FIFO, however, the solution is unreliable
> > > > when Rx keeps getting more and more characters.
> > > >
> > > > Create a sequence of operations to enforce that ensures UART cannot
> > > > keep BUSY asserted indefinitely. The new sequence relies on enabling
> > > > loopback mode temporarily to prevent incoming Rx characters keeping
> > > > UART BUSY.
> > > >
> > > > Use the new dw8250_idle_enter/exit() to do divisor writes and LCR
> > > > writes.
> > > >
> > > > This issue was reported by qianfan Zhao who put lots of debugging
> > > > effort into understanding the solution space.
> > > >
> > > > Reported-by: qianfan Zhao <qianfanguijin@163.com>
> > > > Reported-by: Adriana Nicolae <adriana@arista.com>
> > > > Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
> > > > ---
> > > >  drivers/tty/serial/8250/8250_dw.c | 159 +++++++++++++++++++++---------
> > > >  1 file changed, 115 insertions(+), 44 deletions(-)
> > > >
> > > > diff --git a/drivers/tty/serial/8250/8250_dw.c b/drivers/tty/serial/8250/8250_dw.c
> > > > index a53ba04d9770..8e25dfe8e653 100644
> > > > --- a/drivers/tty/serial/8250/8250_dw.c
> > > > +++ b/drivers/tty/serial/8250/8250_dw.c
> > > > @@ -42,6 +42,8 @@
> > > >  /* DesignWare specific register fields */
> > > >  #define DW_UART_MCR_SIRE               BIT(6)
> > > >
> > > > +#define DW_UART_USR_BUSY               BIT(0)
> > > > +
> > > >  /* Renesas specific register fields */
> > > >  #define RZN1_UART_xDMACR_DMA_EN                BIT(0)
> > > >  #define RZN1_UART_xDMACR_1_WORD_BURST  (0 << 1)
> > > > @@ -77,6 +79,7 @@ struct dw8250_data {
> > > >
> > > >         unsigned int            skip_autocfg:1;
> > > >         unsigned int            uart_16550_compatible:1;
> > > > +       unsigned int            in_idle:1;
> > > >  };
> > > >
> > > >  static inline struct dw8250_data *to_dw8250_data(struct dw8250_port_data *data)
> > > > @@ -108,36 +111,103 @@ static inline u32 dw8250_modify_msr(struct uart_port *p, unsigned int offset, u3
> > > >  }
> > > >
> > > >  /*
> > > > - * This function is being called as part of the uart_port::serial_out()
> > > > - * routine. Hence, it must not call serial_port_out() or serial_out()
> > > > - * against the modified registers here, i.e. LCR.
> > > > + * Ensure BUSY is not asserted. If DW UART is configured with
> > > > + * !uart_16550_compatible, the writes to LCR, DLL, and DLH fail while
> > > > + * BUSY is asserted.
> > > > + *
> > > > + * Context: port's lock must be held
> > > >   */
> > > > -static void dw8250_force_idle(struct uart_port *p)
> > > > +static int dw8250_idle_enter(struct uart_port *p)
> > > >  {
> > > > +       struct dw8250_data *d = to_dw8250_data(p->private_data);
> > > >         struct uart_8250_port *up = up_to_u8250p(p);
> > > > -       unsigned int lsr;
> > > > +       u32 lsr;
> > > >
> > > > -       /*
> > > > -        * The following call currently performs serial_out()
> > > > -        * against the FCR register. Because it differs to LCR
> > > > -        * there will be no infinite loop, but if it ever gets
> > > > -        * modified, we might need a new custom version of it
> > > > -        * that avoids infinite recursion.
> > > > -        */
> > > > -       serial8250_clear_and_reinit_fifos(up);
> > > > +       if (d->uart_16550_compatible)
> > > > +               return 0;
> > > > +
> > > > +       d->in_idle = 1;
> > > > +
> > > > +       /* Prevent triggering interrupt from RBR filling */
> > > > +       p->serial_out(p, UART_IER, 0);
> > > > +
> > > > +       serial8250_rx_dma_flush(up);
> > > > +       // What about Tx DMA? Should probably pause that too and resume
> > > > +       // afterwards.
> > > > +
> > > > +       p->serial_out(p, UART_MCR, up->mcr | UART_MCR_LOOP);
> > > > +       if (up->capabilities & UART_CAP_FIFO)
> > > > +               p->serial_out(p, UART_FCR, 0);
> > >
> > > Changing this to repeatedly clear the FIFO turned out to reliably
> > > clear the BUSY bit , also no kernel panic or device stuck in busy
> > > mode.
> > >
> > > On the device I tested the first clear is not always enough, under
> > > high load I saw it cleared on the second iteration. I'm thinking it
> > > might be some particular issue with the device I'm using where the
> > > first FIFO clear might fail. I never encountered more than 2
> > > iterations with a "ndelay(p->frame_time);" in between here to get out
> > > of BUSY state.
> >
> > Hi,
> >
> > I seem to have missed this email until now (I'm sorry about that, though
> > to my defence, IIRC, I was quite sick around that timeframe it was sent
> > and clear the email backlog isn't ever fun and may end up missing
> > something).
> >
> > Do you mean changing this to a simple loop or writing something else than
> > just 0 to FCR (or perhaps calling serial8250_clear_fifos())?
> >
> > What is the exact code that you found working?
> >
> Yes, everything worked ok for me after changing the dw8250_idle_enter
> function with the one below. From traces added in the function, it
> sometimes reported iterations_in_busy = 2 but never higher than that.
> The function only has the prepended "while(p->serial_in(p,
> d->pdata->usr_reg) & DW_UART_USR_BUSY) {" to iterate forever, although
> it was at most 2 iterations when serial was stressed:
> static int dw8250_idle_enter(struct uart_port *p)
> {
>     struct dw8250_data *d = to_dw8250_data(p->private_data);
>     struct uart_8250_port *up = up_to_u8250p(p);
>     u32 lsr, iterations_in_busy = 0;
> 
>     if (d->uart_16550_compatible)
>         return 0;
> 
>     d->in_idle = 1;
> 
>     /* Prevent triggering interrupt from RBR filling */
>     p->serial_out(p, UART_IER, 0);
> 
>     serial8250_rx_dma_flush(up);
>     // What about Tx DMA? Should probably pause that too and resume
>     // afterwards.
>     p->serial_out(p, UART_MCR, up->mcr | UART_MCR_LOOP);
> 
>     while(p->serial_in(p, d->pdata->usr_reg) & DW_UART_USR_BUSY) {
>         if (up->capabilities & UART_CAP_FIFO) {
>             p->serial_out(p, UART_FCR, UART_FCR_ENABLE_FIFO);
>             p->serial_out(p, UART_FCR, UART_FCR_ENABLE_FIFO |
>                 UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT);
>             p->serial_out(p, UART_FCR, 0);

Thanks for the information!

Okay, so this is same as calling serial8250_clear_fifos() which is what 
I'll do here.

>         }
>         ndelay(p->frame_time);
>         iterations_in_busy++;
>     }
> 
>     trace_printk("Not busy got after %d\n", iterations_in_busy);
>     lsr = serial_lsr_in(up);
>     if (lsr & UART_LSR_DR) {
>         p->serial_in(p, UART_RX);
>         up->lsr_saved_flags = 0;
>     }
> 
>         /*
>      * BUSY might still be frozen to asserted, try to de-freeze it by
>      * sending a character over the loopback and receiving it.
>          */
>     if (p->serial_in(p, d->pdata->usr_reg) & DW_UART_USR_BUSY) {
>         trace_printk("Serial USR still busy\n");
>         p->serial_out(p, UART_TX, 0);
>         ndelay(1000);
>         lsr = serial_lsr_in(up);
> 
>         if (lsr & UART_LSR_DR) {
>             p->serial_in(p, UART_RX);
>             up->lsr_saved_flags = 0;
>         }
>         }

So I think this entire tx+rx de-freezing wasn't required at all? Adding 
this was based on my guess how the driver could try to force BUSY 
deassertion but if the FIFOs were the real culprit to the BUSY remaining 
asserted, I'd prefer to remove this block entirely to not add random 
complexity just for the sake of doing everything imaginable.

>      /* Now guaranteed to have BUSY deasserted? Just sanity check */
>     if (p->serial_in(p, d->pdata->usr_reg) & DW_UART_USR_BUSY) {
>         trace_printk("BUSY\n");
>         return -EBUSY;
>     }
> 
>     return 0;
> }
> > So when you fixed this FIFO clearing thing, everything seemed to work okay
> > after that?
> >
> > In the meantime, this issue has once again been reported to me by somebody
> > else, and I've done improvements to shutdown code as well to address a
> > few BUSY related problems (I'll be posting a series that solved that
> > case soon but I suppose this patch needs amendments based on input from
> > your case).
> >
> > --
> >  i.
> >
> > > > +       if (p->serial_in(p, d->pdata->usr_reg) & DW_UART_USR_BUSY)
> > > > +               ndelay(p->frame_time);
> > > > +
> > > > +       lsr = serial_lsr_in(up);
> > > > +       if (lsr & UART_LSR_DR) {
> > > > +               p->serial_in(p, UART_RX);
> > > > +               up->lsr_saved_flags = 0;
> > > > +       }
> > > >
> > > >         /*
> > > > -        * With PSLVERR_RESP_EN parameter set to 1, the device generates an
> > > > -        * error response when an attempt to read an empty RBR with FIFO
> > > > -        * enabled.
> > > > +        * BUSY might still be frozen to asserted, try to de-freeze it by
> > > > +        * sending a character over the loopback and receiving it.
> > > >          */
> > > > -       if (up->fcr & UART_FCR_ENABLE_FIFO) {
> > > > -               lsr = serial_port_in(p, UART_LSR);
> > > > -               if (!(lsr & UART_LSR_DR))
> > > > -                       return;
> > > > +       if (p->serial_in(p, d->pdata->usr_reg) & DW_UART_USR_BUSY) {
> > > > +               p->serial_out(p, UART_TX, 0);
> > > > +               ndelay(p->frame_time);
> > > > +               lsr = serial_lsr_in(up);
> > > > +               if (lsr & UART_LSR_DR) {
> > > > +                       p->serial_in(p, UART_RX);
> > > > +                       up->lsr_saved_flags = 0;
> > > > +               }
> > > >         }
> > > >
> > > > -       serial_port_in(p, UART_RX);
> > > > +       /* Now guaranteed to have BUSY deasserted? Just sanity check */
> > > > +       if (p->serial_in(p, d->pdata->usr_reg) & DW_UART_USR_BUSY)
> > > > +               return -EBUSY;
> > > > +
> > > > +       return 0;
> > > > +}


-- 
 i.

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

end of thread, other threads:[~2026-01-22 13:18 UTC | newest]

Thread overview: 14+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-08-19 18:23 [PATCH] serial: 8250 dw: clear FIFO before writting LCR adriana
2025-08-19 18:31 ` Greg KH
2025-08-19 18:32 ` Greg KH
2025-08-20 10:02 ` Ilpo Järvinen
2025-08-22  8:09   ` Adriana Nicolae
2025-08-22  9:06     ` Ilpo Järvinen
2025-10-08 10:39     ` Ilpo Järvinen
     [not found]       ` <CAERbo5xOg4OegoWYid3ZBCX1Fi+MBUMb59EtaQLrYkZy9MzSJA@mail.gmail.com>
2025-10-08 13:11         ` Ilpo Järvinen
2025-10-31 19:32           ` Adriana Nicolae
2026-01-21 14:31             ` Ilpo Järvinen
2026-01-21 16:55               ` Adriana Nicolae
2026-01-22 13:18                 ` Ilpo Järvinen
  -- strict thread matches above, loose matches on Subject: below --
2025-08-19 15:06 Adriana Nicolae
2025-08-19 15:28 ` Greg KH

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