* [PATCH 6.12.y 1/8] serial: 8250: use serial_port_in/out() helpers
2026-05-10 13:40 [PATCH 6.12.y 0/8] serial: 8250_dw: backport BUSY deassert series Ionut Nechita (Wind River)
@ 2026-05-10 13:40 ` Ionut Nechita (Wind River)
2026-05-10 13:40 ` [PATCH 6.12.y 2/8] serial: 8250_dw: Comment possible corner cases in serial_out() implementation Ionut Nechita (Wind River)
` (7 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Ionut Nechita (Wind River) @ 2026-05-10 13:40 UTC (permalink / raw)
To: ilpo.jarvinen, gregkh
Cc: andriy.shevchenko, linux-serial, Jiri Slaby (SUSE), Ionut Nechita
From: "Jiri Slaby (SUSE)" <jirislaby@kernel.org>
commit dbd26a886e94deb7fda9050f9195ccb41f9a5d93 upstream.
There are serial_port_in/out() helpers to be used instead of direct
p->serial_in/out(). Use them in various 8250 drivers.
Signed-off-by: Jiri Slaby (SUSE) <jirislaby@kernel.org>
Cc: "Ilpo Järvinen" <ilpo.jarvinen@linux.intel.com>
Cc: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
--
[v2]
* Use serial_port_in/out() and not serial_in/out() [Andy]
Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com> # 8250_dw
Link: https://lore.kernel.org/r/20250317070046.24386-28-jirislaby@kernel.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Signed-off-by: Ionut Nechita <ionut.nechita@windriver.com>
---
drivers/tty/serial/8250/8250_dw.c | 16 ++++++++--------
drivers/tty/serial/8250/8250_fsl.c | 8 ++++----
drivers/tty/serial/8250/8250_omap.c | 2 +-
3 files changed, 13 insertions(+), 13 deletions(-)
diff --git a/drivers/tty/serial/8250/8250_dw.c b/drivers/tty/serial/8250/8250_dw.c
index f17dc3de020c..3225011fd772 100644
--- a/drivers/tty/serial/8250/8250_dw.c
+++ b/drivers/tty/serial/8250/8250_dw.c
@@ -120,12 +120,12 @@ static void dw8250_force_idle(struct uart_port *p)
* enabled.
*/
if (up->fcr & UART_FCR_ENABLE_FIFO) {
- lsr = p->serial_in(p, UART_LSR);
+ lsr = serial_port_in(p, UART_LSR);
if (!(lsr & UART_LSR_DR))
return;
}
- (void)p->serial_in(p, UART_RX);
+ serial_port_in(p, UART_RX);
}
static void dw8250_check_lcr(struct uart_port *p, int offset, int value)
@@ -139,7 +139,7 @@ static void dw8250_check_lcr(struct uart_port *p, int offset, int value)
/* Make sure LCR write wasn't ignored */
while (tries--) {
- unsigned int lcr = p->serial_in(p, offset);
+ unsigned int lcr = serial_port_in(p, offset);
if ((value & ~UART_LCR_SPAR) == (lcr & ~UART_LCR_SPAR))
return;
@@ -260,7 +260,7 @@ static int dw8250_handle_irq(struct uart_port *p)
{
struct uart_8250_port *up = up_to_u8250p(p);
struct dw8250_data *d = to_dw8250_data(p->private_data);
- unsigned int iir = p->serial_in(p, UART_IIR);
+ unsigned int iir = serial_port_in(p, UART_IIR);
bool rx_timeout = (iir & 0x3f) == UART_IIR_RX_TIMEOUT;
unsigned int quirks = d->pdata->quirks;
unsigned int status;
@@ -281,7 +281,7 @@ static int dw8250_handle_irq(struct uart_port *p)
status = serial_lsr_in(up);
if (!(status & (UART_LSR_DR | UART_LSR_BI)))
- (void) p->serial_in(p, UART_RX);
+ serial_port_in(p, UART_RX);
uart_port_unlock_irqrestore(p, flags);
}
@@ -303,7 +303,7 @@ static int dw8250_handle_irq(struct uart_port *p)
if ((iir & UART_IIR_BUSY) == UART_IIR_BUSY) {
/* Clear the USR */
- (void)p->serial_in(p, d->pdata->usr_reg);
+ serial_port_in(p, d->pdata->usr_reg);
return 1;
}
@@ -390,7 +390,7 @@ static void dw8250_set_termios(struct uart_port *p, struct ktermios *termios,
static void dw8250_set_ldisc(struct uart_port *p, struct ktermios *termios)
{
struct uart_8250_port *up = up_to_u8250p(p);
- unsigned int mcr = p->serial_in(p, UART_MCR);
+ unsigned int mcr = serial_port_in(p, UART_MCR);
if (up->capabilities & UART_CAP_IRDA) {
if (termios->c_line == N_IRDA)
@@ -398,7 +398,7 @@ static void dw8250_set_ldisc(struct uart_port *p, struct ktermios *termios)
else
mcr &= ~DW_UART_MCR_SIRE;
- p->serial_out(p, UART_MCR, mcr);
+ serial_port_out(p, UART_MCR, mcr);
}
serial8250_do_set_ldisc(p, termios);
}
diff --git a/drivers/tty/serial/8250/8250_fsl.c b/drivers/tty/serial/8250/8250_fsl.c
index b4ed442082a8..59d3d2801c2e 100644
--- a/drivers/tty/serial/8250/8250_fsl.c
+++ b/drivers/tty/serial/8250/8250_fsl.c
@@ -32,7 +32,7 @@ int fsl8250_handle_irq(struct uart_port *port)
uart_port_lock_irqsave(&up->port, &flags);
- iir = port->serial_in(port, UART_IIR);
+ iir = serial_port_in(port, UART_IIR);
if (iir & UART_IIR_NO_INT) {
uart_port_unlock_irqrestore(&up->port, flags);
return 0;
@@ -54,12 +54,12 @@ int fsl8250_handle_irq(struct uart_port *port)
if (unlikely((iir & UART_IIR_ID) == UART_IIR_RLSI &&
(up->lsr_saved_flags & UART_LSR_BI))) {
up->lsr_saved_flags &= ~UART_LSR_BI;
- port->serial_in(port, UART_RX);
+ serial_port_in(port, UART_RX);
uart_port_unlock_irqrestore(&up->port, flags);
return 1;
}
- lsr = orig_lsr = up->port.serial_in(&up->port, UART_LSR);
+ lsr = orig_lsr = serial_port_in(port, UART_LSR);
/* Process incoming characters first */
if ((lsr & (UART_LSR_DR | UART_LSR_BI)) &&
@@ -71,7 +71,7 @@ int fsl8250_handle_irq(struct uart_port *port)
if ((orig_lsr & UART_LSR_OE) && (up->overrun_backoff_time_ms > 0)) {
unsigned long delay;
- up->ier = port->serial_in(port, UART_IER);
+ up->ier = serial_port_in(port, UART_IER);
if (up->ier & (UART_IER_RLSI | UART_IER_RDI)) {
port->ops->stop_rx(port);
} else {
diff --git a/drivers/tty/serial/8250/8250_omap.c b/drivers/tty/serial/8250/8250_omap.c
index 0f4ce0c69114..dc9e3e25d55f 100644
--- a/drivers/tty/serial/8250/8250_omap.c
+++ b/drivers/tty/serial/8250/8250_omap.c
@@ -688,7 +688,7 @@ static irqreturn_t omap8250_irq(int irq, void *dev_id)
/* Synchronize UART_IER access against the console. */
uart_port_lock(port);
- up->ier = port->serial_in(port, UART_IER);
+ up->ier = serial_port_in(port, UART_IER);
if (up->ier & (UART_IER_RLSI | UART_IER_RDI)) {
port->ops->stop_rx(port);
} else {
--
2.54.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH 6.12.y 2/8] serial: 8250_dw: Comment possible corner cases in serial_out() implementation
2026-05-10 13:40 [PATCH 6.12.y 0/8] serial: 8250_dw: backport BUSY deassert series Ionut Nechita (Wind River)
2026-05-10 13:40 ` [PATCH 6.12.y 1/8] serial: 8250: use serial_port_in/out() helpers Ionut Nechita (Wind River)
@ 2026-05-10 13:40 ` Ionut Nechita (Wind River)
2026-05-10 13:40 ` [PATCH 6.12.y 3/8] serial: 8250: Protect LCR write in shutdown Ionut Nechita (Wind River)
` (6 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Ionut Nechita (Wind River) @ 2026-05-10 13:40 UTC (permalink / raw)
To: ilpo.jarvinen, gregkh; +Cc: andriy.shevchenko, linux-serial, Ionut Nechita
From: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
commit bd8cad85561b8de36f099404c332bf3e3dbe90f5 upstream.
8250 DesignWare driver uses a few custom implementations of the serial_out().
These implementations are carefully made to avoid infinite loops. But this is
not obvious from looking at the code. Comment the possible corner cases in
the respective functions.
Signed-off-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Link: https://lore.kernel.org/r/20250317094021.1201512-1-andriy.shevchenko@linux.intel.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Signed-off-by: Ionut Nechita <ionut.nechita@windriver.com>
---
drivers/tty/serial/8250/8250_dw.c | 17 +++++++++++++++++
1 file changed, 17 insertions(+)
diff --git a/drivers/tty/serial/8250/8250_dw.c b/drivers/tty/serial/8250/8250_dw.c
index 3225011fd772..88c55336d50f 100644
--- a/drivers/tty/serial/8250/8250_dw.c
+++ b/drivers/tty/serial/8250/8250_dw.c
@@ -107,11 +107,23 @@ static inline int dw8250_modify_msr(struct uart_port *p, int offset, int value)
return value;
}
+/*
+ * 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.
+ */
static void dw8250_force_idle(struct uart_port *p)
{
struct uart_8250_port *up = up_to_u8250p(p);
unsigned int 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);
/*
@@ -128,6 +140,11 @@ static void dw8250_force_idle(struct uart_port *p)
serial_port_in(p, UART_RX);
}
+/*
+ * 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.
+ */
static void dw8250_check_lcr(struct uart_port *p, int offset, int value)
{
struct dw8250_data *d = to_dw8250_data(p->private_data);
--
2.54.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH 6.12.y 3/8] serial: 8250: Protect LCR write in shutdown
2026-05-10 13:40 [PATCH 6.12.y 0/8] serial: 8250_dw: backport BUSY deassert series Ionut Nechita (Wind River)
2026-05-10 13:40 ` [PATCH 6.12.y 1/8] serial: 8250: use serial_port_in/out() helpers Ionut Nechita (Wind River)
2026-05-10 13:40 ` [PATCH 6.12.y 2/8] serial: 8250_dw: Comment possible corner cases in serial_out() implementation Ionut Nechita (Wind River)
@ 2026-05-10 13:40 ` Ionut Nechita (Wind River)
2026-05-10 13:40 ` [PATCH 6.12.y 4/8] serial: 8250_dw: Avoid unnecessary LCR writes Ionut Nechita (Wind River)
` (5 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Ionut Nechita (Wind River) @ 2026-05-10 13:40 UTC (permalink / raw)
To: ilpo.jarvinen, gregkh
Cc: andriy.shevchenko, linux-serial, Bandal, Shankar, Murthy, Shanth,
stable, Ionut Nechita
From: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
commit 59a33d83bbe6d73d2071d7ae21590b29faed0503 upstream.
The 8250_dw driver needs to potentially perform very complex operations
during LCR writes because its BUSY handling prevents updates to LCR
while UART is BUSY (which is not fully under our control without those
complex operations). Thus, LCR writes should occur under port's lock.
Move LCR write under port's lock in serial8250_do_shutdown(). Also
split the LCR RMW so that the logic is on a separate line for clarity.
Reported-by: Bandal, Shankar <shankar.bandal@intel.com>
Tested-by: Bandal, Shankar <shankar.bandal@intel.com>
Tested-by: Murthy, Shanth <shanth.murthy@intel.com>
Cc: stable <stable@kernel.org>
Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
Link: https://patch.msgid.link/20260203171049.4353-2-ilpo.jarvinen@linux.intel.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
[Ionut: adapt to 6.12.y - keep both 'unsigned long flags' and the
new 'u32 lcr' declaration; place the LCR RMW inside the existing
explicit uart_port_lock_irqsave/unlock_irqrestore region rather
than the scoped_guard() block used upstream (cleanup-based
uart_port_lock_irqsave guard class is not present in 6.12.y).]
Signed-off-by: Ionut Nechita <ionut.nechita@windriver.com>
---
drivers/tty/serial/8250/8250_port.c | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/drivers/tty/serial/8250/8250_port.c b/drivers/tty/serial/8250/8250_port.c
index b4c8388ea6fc..d0f2e634ac30 100644
--- a/drivers/tty/serial/8250/8250_port.c
+++ b/drivers/tty/serial/8250/8250_port.c
@@ -2462,6 +2462,7 @@ void serial8250_do_shutdown(struct uart_port *port)
{
struct uart_8250_port *up = up_to_u8250p(port);
unsigned long flags;
+ u32 lcr;
serial8250_rpm_get(up);
/*
@@ -2488,13 +2489,13 @@ void serial8250_do_shutdown(struct uart_port *port)
port->mctrl &= ~TIOCM_OUT2;
serial8250_set_mctrl(port, port->mctrl);
+
+ /* Disable break condition */
+ lcr = serial_port_in(port, UART_LCR);
+ lcr &= ~UART_LCR_SBC;
+ serial_port_out(port, UART_LCR, lcr);
uart_port_unlock_irqrestore(port, flags);
- /*
- * Disable break condition and FIFOs
- */
- serial_port_out(port, UART_LCR,
- serial_port_in(port, UART_LCR) & ~UART_LCR_SBC);
serial8250_clear_fifos(up);
#ifdef CONFIG_SERIAL_8250_RSA
--
2.54.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH 6.12.y 4/8] serial: 8250_dw: Avoid unnecessary LCR writes
2026-05-10 13:40 [PATCH 6.12.y 0/8] serial: 8250_dw: backport BUSY deassert series Ionut Nechita (Wind River)
` (2 preceding siblings ...)
2026-05-10 13:40 ` [PATCH 6.12.y 3/8] serial: 8250: Protect LCR write in shutdown Ionut Nechita (Wind River)
@ 2026-05-10 13:40 ` Ionut Nechita (Wind River)
2026-05-10 13:40 ` [PATCH 6.12.y 5/8] serial: 8250: Add serial8250_handle_irq_locked() Ionut Nechita (Wind River)
` (4 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Ionut Nechita (Wind River) @ 2026-05-10 13:40 UTC (permalink / raw)
To: ilpo.jarvinen, gregkh
Cc: andriy.shevchenko, linux-serial, Bandal, Shankar, Murthy, Shanth,
stable, Ionut Nechita
From: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
commit 8002d6d6d0d8a36a7d6ca523b17a51cb0fa7c3c3 upstream.
When DW UART is configured with BUSY flag, LCR writes may not always
succeed which can make any LCR write complex and very expensive.
Performing write directly can trigger IRQ and the driver has to perform
complex and distruptive sequence while retrying the write.
Therefore, it's better to avoid doing LCR write that would not change
the value of the LCR register. Add LCR write avoidance code into the
8250_dw driver's .serial_out() functions.
Reported-by: Bandal, Shankar <shankar.bandal@intel.com>
Tested-by: Bandal, Shankar <shankar.bandal@intel.com>
Tested-by: Murthy, Shanth <shanth.murthy@intel.com>
Cc: stable <stable@kernel.org>
Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
Link: https://patch.msgid.link/20260203171049.4353-3-ilpo.jarvinen@linux.intel.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Signed-off-by: Ionut Nechita <ionut.nechita@windriver.com>
---
drivers/tty/serial/8250/8250_dw.c | 31 +++++++++++++++++++++++++++++++
1 file changed, 31 insertions(+)
diff --git a/drivers/tty/serial/8250/8250_dw.c b/drivers/tty/serial/8250/8250_dw.c
index 88c55336d50f..05e45b63e5f5 100644
--- a/drivers/tty/serial/8250/8250_dw.c
+++ b/drivers/tty/serial/8250/8250_dw.c
@@ -181,6 +181,22 @@ static void dw8250_check_lcr(struct uart_port *p, int offset, int value)
*/
}
+/*
+ * With BUSY, LCR writes can be very expensive (IRQ + complex retry logic).
+ * If the write does not change the value of the LCR register, skip it entirely.
+ */
+static bool dw8250_can_skip_reg_write(struct uart_port *p, unsigned int offset, u32 value)
+{
+ struct dw8250_data *d = to_dw8250_data(p->private_data);
+ u32 lcr;
+
+ if (offset != UART_LCR || d->uart_16550_compatible)
+ return false;
+
+ lcr = serial_port_in(p, offset);
+ return lcr == value;
+}
+
/* Returns once the transmitter is empty or we run out of retries */
static void dw8250_tx_wait_empty(struct uart_port *p)
{
@@ -207,12 +223,18 @@ static void dw8250_tx_wait_empty(struct uart_port *p)
static void dw8250_serial_out(struct uart_port *p, int offset, int value)
{
+ if (dw8250_can_skip_reg_write(p, offset, value))
+ return;
+
writeb(value, p->membase + (offset << p->regshift));
dw8250_check_lcr(p, offset, value);
}
static void dw8250_serial_out38x(struct uart_port *p, int offset, int value)
{
+ if (dw8250_can_skip_reg_write(p, offset, value))
+ return;
+
/* Allow the TX to drain before we reconfigure */
if (offset == UART_LCR)
dw8250_tx_wait_empty(p);
@@ -237,6 +259,9 @@ static unsigned int dw8250_serial_inq(struct uart_port *p, int offset)
static void dw8250_serial_outq(struct uart_port *p, int offset, int value)
{
+ if (dw8250_can_skip_reg_write(p, offset, value))
+ return;
+
value &= 0xff;
__raw_writeq(value, p->membase + (offset << p->regshift));
/* Read back to ensure register write ordering. */
@@ -248,6 +273,9 @@ static void dw8250_serial_outq(struct uart_port *p, int offset, int value)
static void dw8250_serial_out32(struct uart_port *p, int offset, int value)
{
+ if (dw8250_can_skip_reg_write(p, offset, value))
+ return;
+
writel(value, p->membase + (offset << p->regshift));
dw8250_check_lcr(p, offset, value);
}
@@ -261,6 +289,9 @@ static unsigned int dw8250_serial_in32(struct uart_port *p, int offset)
static void dw8250_serial_out32be(struct uart_port *p, int offset, int value)
{
+ if (dw8250_can_skip_reg_write(p, offset, value))
+ return;
+
iowrite32be(value, p->membase + (offset << p->regshift));
dw8250_check_lcr(p, offset, value);
}
--
2.54.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH 6.12.y 5/8] serial: 8250: Add serial8250_handle_irq_locked()
2026-05-10 13:40 [PATCH 6.12.y 0/8] serial: 8250_dw: backport BUSY deassert series Ionut Nechita (Wind River)
` (3 preceding siblings ...)
2026-05-10 13:40 ` [PATCH 6.12.y 4/8] serial: 8250_dw: Avoid unnecessary LCR writes Ionut Nechita (Wind River)
@ 2026-05-10 13:40 ` Ionut Nechita (Wind River)
2026-05-10 13:40 ` [PATCH 6.12.y 6/8] serial: 8250_dw: Rework dw8250_handle_irq() locking and IIR handling Ionut Nechita (Wind River)
` (3 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Ionut Nechita (Wind River) @ 2026-05-10 13:40 UTC (permalink / raw)
To: ilpo.jarvinen, gregkh
Cc: andriy.shevchenko, linux-serial, Bandal, Shankar, Murthy, Shanth,
stable, Ionut Nechita
From: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
commit 8324a54f604da18f21070702a8ad82ab2062787b upstream.
8250_port exports serial8250_handle_irq() to HW specific 8250 drivers.
It takes port's lock within but a HW specific 8250 driver may want to
take port's lock itself, do something, and then call the generic
handler in 8250_port but to do that, the caller has to release port's
lock for no good reason.
Introduce serial8250_handle_irq_locked() which a HW specific driver can
call while already holding port's lock.
As this is new export, put it straight into a namespace (where all 8250
exports should eventually be moved).
Tested-by: Bandal, Shankar <shankar.bandal@intel.com>
Tested-by: Murthy, Shanth <shanth.murthy@intel.com>
Cc: stable <stable@kernel.org>
Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
Link: https://patch.msgid.link/20260203171049.4353-4-ilpo.jarvinen@linux.intel.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
[Ionut: adapt to 6.12.y -
- replace guard(uart_port_lock_irqsave)(port) in
serial8250_handle_irq() with explicit uart_port_lock_irqsave/
unlock_irqrestore; the cleanup-based uart_port_lock_irqsave
guard class is not present in 6.12.y;
- use the bare-identifier form EXPORT_SYMBOL_NS_GPL(...,
SERIAL_8250) rather than the string form ("SERIAL_8250");
in 6.12.y the macro stringifies the namespace argument via
__stringify(ns), so it must be an unquoted identifier.]
Signed-off-by: Ionut Nechita <ionut.nechita@windriver.com>
---
drivers/tty/serial/8250/8250_port.c | 27 +++++++++++++++++++--------
include/linux/serial_8250.h | 1 +
2 files changed, 20 insertions(+), 8 deletions(-)
diff --git a/drivers/tty/serial/8250/8250_port.c b/drivers/tty/serial/8250/8250_port.c
index d0f2e634ac30..1e6dca739eab 100644
--- a/drivers/tty/serial/8250/8250_port.c
+++ b/drivers/tty/serial/8250/8250_port.c
@@ -18,6 +18,7 @@
#include <linux/irq.h>
#include <linux/console.h>
#include <linux/gpio/consumer.h>
+#include <linux/lockdep.h>
#include <linux/sysrq.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
@@ -1884,20 +1885,16 @@ static bool handle_rx_dma(struct uart_8250_port *up, unsigned int iir)
}
/*
- * This handles the interrupt from one port.
+ * Context: port's lock must be held by the caller.
*/
-int serial8250_handle_irq(struct uart_port *port, unsigned int iir)
+void serial8250_handle_irq_locked(struct uart_port *port, unsigned int iir)
{
struct uart_8250_port *up = up_to_u8250p(port);
struct tty_port *tport = &port->state->port;
bool skip_rx = false;
- unsigned long flags;
u16 status;
- if (iir & UART_IIR_NO_INT)
- return 0;
-
- uart_port_lock_irqsave(port, &flags);
+ lockdep_assert_held_once(&port->lock);
status = serial_lsr_in(up);
@@ -1930,8 +1927,22 @@ int serial8250_handle_irq(struct uart_port *port, unsigned int iir)
else if (!up->dma->tx_running)
__stop_tx(up);
}
+}
+EXPORT_SYMBOL_NS_GPL(serial8250_handle_irq_locked, SERIAL_8250);
- uart_unlock_and_check_sysrq_irqrestore(port, flags);
+/*
+ * This handles the interrupt from one port.
+ */
+int serial8250_handle_irq(struct uart_port *port, unsigned int iir)
+{
+ unsigned long flags;
+
+ if (iir & UART_IIR_NO_INT)
+ return 0;
+
+ uart_port_lock_irqsave(port, &flags);
+ serial8250_handle_irq_locked(port, iir);
+ uart_port_unlock_irqrestore(port, flags);
return 1;
}
diff --git a/include/linux/serial_8250.h b/include/linux/serial_8250.h
index e0717c8393d7..7e0439ecd496 100644
--- a/include/linux/serial_8250.h
+++ b/include/linux/serial_8250.h
@@ -195,6 +195,7 @@ void serial8250_do_set_mctrl(struct uart_port *port, unsigned int mctrl);
void serial8250_do_set_divisor(struct uart_port *port, unsigned int baud,
unsigned int quot);
int fsl8250_handle_irq(struct uart_port *port);
+void serial8250_handle_irq_locked(struct uart_port *port, unsigned int iir);
int serial8250_handle_irq(struct uart_port *port, unsigned int iir);
u16 serial8250_rx_chars(struct uart_8250_port *up, u16 lsr);
void serial8250_read_char(struct uart_8250_port *up, u16 lsr);
--
2.54.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH 6.12.y 6/8] serial: 8250_dw: Rework dw8250_handle_irq() locking and IIR handling
2026-05-10 13:40 [PATCH 6.12.y 0/8] serial: 8250_dw: backport BUSY deassert series Ionut Nechita (Wind River)
` (4 preceding siblings ...)
2026-05-10 13:40 ` [PATCH 6.12.y 5/8] serial: 8250: Add serial8250_handle_irq_locked() Ionut Nechita (Wind River)
@ 2026-05-10 13:40 ` Ionut Nechita (Wind River)
2026-05-10 13:40 ` [PATCH 6.12.y 7/8] serial: 8250_dw: Rework IIR_NO_INT handling to stop interrupt storm Ionut Nechita (Wind River)
` (2 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Ionut Nechita (Wind River) @ 2026-05-10 13:40 UTC (permalink / raw)
To: ilpo.jarvinen, gregkh
Cc: andriy.shevchenko, linux-serial, Bandal, Shankar, Murthy, Shanth,
stable, Ionut Nechita
From: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
commit 883c5a2bc934c165c4491d1ef7da0ac4e9765077 upstream.
dw8250_handle_irq() takes port's lock multiple times with no good
reason to release it in between and calls serial8250_handle_irq()
that also takes port's lock.
Take port's lock only once in dw8250_handle_irq() and use
serial8250_handle_irq_locked() to avoid releasing port's lock in
between.
As IIR_NO_INT check in serial8250_handle_irq() was outside of port's
lock, it has to be done already in dw8250_handle_irq().
DW UART can, in addition to IIR_NO_INT, report BUSY_DETECT (0x7) which
collided with the IIR_NO_INT (0x1) check in serial8250_handle_irq()
(because & is used instead of ==) meaning that no other work is done by
serial8250_handle_irq() during an BUSY_DETECT interrupt.
This allows reorganizing code in dw8250_handle_irq() to do both
IIR_NO_INT and BUSY_DETECT handling right at the start simplifying
the logic.
Tested-by: Bandal, Shankar <shankar.bandal@intel.com>
Tested-by: Murthy, Shanth <shanth.murthy@intel.com>
Cc: stable <stable@kernel.org>
Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
Link: https://patch.msgid.link/20260203171049.4353-5-ilpo.jarvinen@linux.intel.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
[Ionut: adapt to 6.12.y - replace guard(uart_port_lock_irqsave)(p)
in dw8250_handle_irq() with explicit uart_port_lock_irqsave/
unlock_irqrestore around the post-switch body; the cleanup-based
uart_port_lock_irqsave guard class is not present in 6.12.y.]
Signed-off-by: Ionut Nechita <ionut.nechita@windriver.com>
---
drivers/tty/serial/8250/8250_dw.c | 36 +++++++++++++++++++------------
1 file changed, 22 insertions(+), 14 deletions(-)
diff --git a/drivers/tty/serial/8250/8250_dw.c b/drivers/tty/serial/8250/8250_dw.c
index 05e45b63e5f5..1076b72c120b 100644
--- a/drivers/tty/serial/8250/8250_dw.c
+++ b/drivers/tty/serial/8250/8250_dw.c
@@ -9,6 +9,9 @@
* LCR is written whilst busy. If it is, then a busy detect interrupt is
* raised, the LCR needs to be rewritten and the uart status register read.
*/
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/cleanup.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/device.h>
@@ -40,6 +43,8 @@
#define RZN1_UART_RDMACR 0x110 /* DMA Control Register Receive Mode */
/* DesignWare specific register fields */
+#define DW_UART_IIR_IID GENMASK(3, 0)
+
#define DW_UART_MCR_SIRE BIT(6)
/* Renesas specific register fields */
@@ -314,6 +319,19 @@ static int dw8250_handle_irq(struct uart_port *p)
unsigned int status;
unsigned long flags;
+ switch (FIELD_GET(DW_UART_IIR_IID, iir)) {
+ case UART_IIR_NO_INT:
+ return 0;
+
+ case UART_IIR_BUSY:
+ /* Clear the USR */
+ serial_port_in(p, d->pdata->usr_reg);
+
+ return 1;
+ }
+
+ uart_port_lock_irqsave(p, &flags);
+
/*
* There are ways to get Designware-based UARTs into a state where
* they are asserting UART_IIR_RX_TIMEOUT but there is no actual
@@ -325,20 +343,15 @@ static int dw8250_handle_irq(struct uart_port *p)
* so we limit the workaround only to non-DMA mode.
*/
if (!up->dma && rx_timeout) {
- uart_port_lock_irqsave(p, &flags);
status = serial_lsr_in(up);
if (!(status & (UART_LSR_DR | UART_LSR_BI)))
serial_port_in(p, UART_RX);
-
- uart_port_unlock_irqrestore(p, flags);
}
/* Manually stop the Rx DMA transfer when acting as flow controller */
if (quirks & DW_UART_QUIRK_IS_DMA_FC && up->dma && up->dma->rx_running && rx_timeout) {
- uart_port_lock_irqsave(p, &flags);
status = serial_lsr_in(up);
- uart_port_unlock_irqrestore(p, flags);
if (status & (UART_LSR_DR | UART_LSR_BI)) {
dw8250_writel_ext(p, RZN1_UART_RDMACR, 0);
@@ -346,17 +359,11 @@ static int dw8250_handle_irq(struct uart_port *p)
}
}
- if (serial8250_handle_irq(p, iir))
- return 1;
-
- if ((iir & UART_IIR_BUSY) == UART_IIR_BUSY) {
- /* Clear the USR */
- serial_port_in(p, d->pdata->usr_reg);
+ serial8250_handle_irq_locked(p, iir);
- return 1;
- }
+ uart_port_unlock_irqrestore(p, flags);
- return 0;
+ return 1;
}
static void dw8250_clk_work_cb(struct work_struct *work)
@@ -859,6 +866,7 @@ static struct platform_driver dw8250_platform_driver = {
module_platform_driver(dw8250_platform_driver);
+MODULE_IMPORT_NS("SERIAL_8250");
MODULE_AUTHOR("Jamie Iles");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Synopsys DesignWare 8250 serial port driver");
--
2.54.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH 6.12.y 7/8] serial: 8250_dw: Rework IIR_NO_INT handling to stop interrupt storm
2026-05-10 13:40 [PATCH 6.12.y 0/8] serial: 8250_dw: backport BUSY deassert series Ionut Nechita (Wind River)
` (5 preceding siblings ...)
2026-05-10 13:40 ` [PATCH 6.12.y 6/8] serial: 8250_dw: Rework dw8250_handle_irq() locking and IIR handling Ionut Nechita (Wind River)
@ 2026-05-10 13:40 ` Ionut Nechita (Wind River)
2026-05-10 13:40 ` [PATCH 6.12.y 8/8] serial: 8250_dw: Ensure BUSY is deasserted Ionut Nechita (Wind River)
2026-05-11 10:26 ` [PATCH 6.12.y 0/8] serial: 8250_dw: backport BUSY deassert series Ilpo Järvinen
8 siblings, 0 replies; 10+ messages in thread
From: Ionut Nechita (Wind River) @ 2026-05-10 13:40 UTC (permalink / raw)
To: ilpo.jarvinen, gregkh
Cc: andriy.shevchenko, linux-serial, stable, Bandal, Shankar,
Murthy, Shanth, Ionut Nechita
From: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
commit 73a4ed8f9efaaaf8207614ccc1c9d5ca1888f23a upstream.
INTC10EE UART can end up into an interrupt storm where it reports
IIR_NO_INT (0x1). If the storm happens during active UART operation, it
is promptly stopped by IIR value change due to Rx or Tx events.
However, when there is no activity, either due to idle serial line or
due to specific circumstances such as during shutdown that writes
IER=0, there is nothing to stop the storm.
During shutdown the storm is particularly problematic because
serial8250_do_shutdown() calls synchronize_irq() that will hang in
waiting for the storm to finish which never happens.
This problem can also result in triggering a warning:
irq 45: nobody cared (try booting with the "irqpoll" option)
[...snip...]
handlers:
serial8250_interrupt
Disabling IRQ #45
Normal means to reset interrupt status by reading LSR, MSR, USR, or RX
register do not result in the UART deasserting the IRQ.
Add a quirk to INTC10EE UARTs to enable Tx interrupts if UART's Tx is
currently empty and inactive. Rework IIR_NO_INT to keep track of the
number of consecutive IIR_NO_INT, and on fourth one perform the quirk.
Enabling Tx interrupts should change IIR value from IIR_NO_INT to
IIR_THRI which has been observed to stop the storm.
Fixes: e92fad024929 ("serial: 8250_dw: Add ACPI ID for Granite Rapids-D UART")
Cc: stable <stable@kernel.org>
Reported-by: Bandal, Shankar <shankar.bandal@intel.com>
Tested-by: Bandal, Shankar <shankar.bandal@intel.com>
Tested-by: Murthy, Shanth <shanth.murthy@intel.com>
Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
Link: https://patch.msgid.link/20260203171049.4353-6-ilpo.jarvinen@linux.intel.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
[Ionut: adapt to 6.12.y -
- move uart_port_lock_irqsave() before the switch (matching the
upstream guard() placement) and convert the early returns into
a goto-out pattern with uart_port_unlock_irqrestore;
- drop the dw8250_setup_dma_filter() call (helper introduced by
c213375e3283 'serial: 8250_dw: Call dw8250_quirks() conditionally'
which is not backported);
- keep only the new p->shutdown = dw8250_shutdown assignment in the
existing 'else if (data->pdata)' branch; in 6.12.y p->handle_irq
is unconditionally set earlier in dw8250_probe() so the upstream
reassignment is unnecessary here.]
Signed-off-by: Ionut Nechita <ionut.nechita@windriver.com>
---
drivers/tty/serial/8250/8250_dw.c | 75 ++++++++++++++++++++++++++++---
1 file changed, 69 insertions(+), 6 deletions(-)
diff --git a/drivers/tty/serial/8250/8250_dw.c b/drivers/tty/serial/8250/8250_dw.c
index 1076b72c120b..1650107d1cbc 100644
--- a/drivers/tty/serial/8250/8250_dw.c
+++ b/drivers/tty/serial/8250/8250_dw.c
@@ -61,6 +61,13 @@
#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_IER_KICK BIT(6)
+
+/*
+ * Number of consecutive IIR_NO_INT interrupts required to trigger interrupt
+ * storm prevention code.
+ */
+#define DW_UART_QUIRK_IER_KICK_THRES 4
struct dw8250_platform_data {
u8 usr_reg;
@@ -82,6 +89,8 @@ struct dw8250_data {
unsigned int skip_autocfg:1;
unsigned int uart_16550_compatible:1;
+
+ u8 no_int_count;
};
static inline struct dw8250_data *to_dw8250_data(struct dw8250_port_data *data)
@@ -308,6 +317,29 @@ static unsigned int dw8250_serial_in32be(struct uart_port *p, int offset)
return dw8250_modify_msr(p, offset, value);
}
+/*
+ * INTC10EE UART can IRQ storm while reporting IIR_NO_INT. Inducing IIR value
+ * change has been observed to break the storm.
+ *
+ * If Tx is empty (THRE asserted), we use here IER_THRI to cause IIR_NO_INT ->
+ * IIR_THRI transition.
+ */
+static void dw8250_quirk_ier_kick(struct uart_port *p)
+{
+ struct uart_8250_port *up = up_to_u8250p(p);
+ u32 lsr;
+
+ if (up->ier & UART_IER_THRI)
+ return;
+
+ lsr = serial_lsr_in(up);
+ if (!(lsr & UART_LSR_THRE))
+ return;
+
+ serial_port_out(p, UART_IER, up->ier | UART_IER_THRI);
+ serial_port_in(p, UART_LCR); /* safe, no side-effects */
+ serial_port_out(p, UART_IER, up->ier);
+}
static int dw8250_handle_irq(struct uart_port *p)
{
@@ -318,19 +350,35 @@ static int dw8250_handle_irq(struct uart_port *p)
unsigned int quirks = d->pdata->quirks;
unsigned int status;
unsigned long flags;
+ int ret = 1;
+
+ uart_port_lock_irqsave(p, &flags);
switch (FIELD_GET(DW_UART_IIR_IID, iir)) {
case UART_IIR_NO_INT:
- return 0;
+ if (d->uart_16550_compatible || up->dma) {
+ ret = 0;
+ goto out;
+ }
+
+ if (quirks & DW_UART_QUIRK_IER_KICK &&
+ d->no_int_count == (DW_UART_QUIRK_IER_KICK_THRES - 1))
+ dw8250_quirk_ier_kick(p);
+ d->no_int_count = (d->no_int_count + 1) % DW_UART_QUIRK_IER_KICK_THRES;
+
+ ret = 0;
+ goto out;
case UART_IIR_BUSY:
/* Clear the USR */
serial_port_in(p, d->pdata->usr_reg);
- return 1;
+ d->no_int_count = 0;
+
+ goto out;
}
- uart_port_lock_irqsave(p, &flags);
+ d->no_int_count = 0;
/*
* There are ways to get Designware-based UARTs into a state where
@@ -361,9 +409,9 @@ static int dw8250_handle_irq(struct uart_port *p)
serial8250_handle_irq_locked(p, iir);
+out:
uart_port_unlock_irqrestore(p, flags);
-
- return 1;
+ return ret;
}
static void dw8250_clk_work_cb(struct work_struct *work)
@@ -561,6 +609,14 @@ static void dw8250_reset_control_assert(void *data)
reset_control_assert(data);
}
+static void dw8250_shutdown(struct uart_port *port)
+{
+ struct dw8250_data *d = to_dw8250_data(port->private_data);
+
+ serial8250_do_shutdown(port);
+ d->no_int_count = 0;
+}
+
static int dw8250_probe(struct platform_device *pdev)
{
struct uart_8250_port uart = {}, *up = &uart;
@@ -688,6 +744,8 @@ static int dw8250_probe(struct platform_device *pdev)
/* If the Busy Functionality is not implemented, don't handle it */
if (data->uart_16550_compatible)
p->handle_irq = NULL;
+ else if (data->pdata)
+ p->shutdown = dw8250_shutdown;
if (!data->skip_autocfg)
dw8250_setup_port(p);
@@ -819,6 +877,11 @@ static const struct dw8250_platform_data dw8250_skip_set_rate_data = {
.quirks = DW_UART_QUIRK_SKIP_SET_RATE,
};
+static const struct dw8250_platform_data dw8250_intc10ee = {
+ .usr_reg = DW_UART_USR,
+ .quirks = DW_UART_QUIRK_IER_KICK,
+};
+
static const struct of_device_id dw8250_of_match[] = {
{ .compatible = "snps,dw-apb-uart", .data = &dw8250_dw_apb },
{ .compatible = "cavium,octeon-3860-uart", .data = &dw8250_octeon_3860_data },
@@ -848,7 +911,7 @@ static const struct acpi_device_id dw8250_acpi_match[] = {
{ "INT33C5", (kernel_ulong_t)&dw8250_dw_apb },
{ "INT3434", (kernel_ulong_t)&dw8250_dw_apb },
{ "INT3435", (kernel_ulong_t)&dw8250_dw_apb },
- { "INTC10EE", (kernel_ulong_t)&dw8250_dw_apb },
+ { "INTC10EE", (kernel_ulong_t)&dw8250_intc10ee },
{ },
};
MODULE_DEVICE_TABLE(acpi, dw8250_acpi_match);
--
2.54.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH 6.12.y 8/8] serial: 8250_dw: Ensure BUSY is deasserted
2026-05-10 13:40 [PATCH 6.12.y 0/8] serial: 8250_dw: backport BUSY deassert series Ionut Nechita (Wind River)
` (6 preceding siblings ...)
2026-05-10 13:40 ` [PATCH 6.12.y 7/8] serial: 8250_dw: Rework IIR_NO_INT handling to stop interrupt storm Ionut Nechita (Wind River)
@ 2026-05-10 13:40 ` Ionut Nechita (Wind River)
2026-05-11 10:26 ` [PATCH 6.12.y 0/8] serial: 8250_dw: backport BUSY deassert series Ilpo Järvinen
8 siblings, 0 replies; 10+ messages in thread
From: Ionut Nechita (Wind River) @ 2026-05-10 13:40 UTC (permalink / raw)
To: ilpo.jarvinen, gregkh
Cc: andriy.shevchenko, linux-serial, stable, qianfan Zhao,
Adriana Nicolae, Bandal, Shankar, Murthy, Shanth, Ionut Nechita
From: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
commit a7b9ce39fbe4ae2919fe4f7ac16c293cb6632d30 upstream.
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 it those registers can always be written.
There currently is dw8250_force_idle() which attempts to achieve
non-BUSY state by disabling FIFO, however, the solution is unreliable
when Rx keeps getting more and more characters.
Create a sequence of operations 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.
Ensure no Tx in ongoing while the UART is switches into the loopback
mode (requires exporting serial8250_fifo_wait_for_lsr_thre() and adding
DMA Tx pause/resume functions).
According to tests performed by Adriana Nicolae <adriana@arista.com>,
simply disabling FIFO or clearing FIFOs only once does not always
ensure BUSY is deasserted but up to two tries may be needed. This could
be related to ongoing Rx of a character (a guess, not known for sure).
Therefore, retry FIFO clearing a few times (retry limit 4 is arbitrary
number but using, e.g., p->fifosize seems overly large). Tests
performed by others did not exhibit similar challenge but it does not
seem harmful to leave the FIFO clearing loop in place for all DW UARTs
with BUSY functionality.
Use the new dw8250_idle_enter/exit() to do divisor writes and LCR
writes. In case of plain LCR writes, opportunistically try to update
LCR first and only invoke dw8250_idle_enter() if the write did not
succeed (it has been observed that in practice most LCR writes do
succeed without complications).
This issue was first reported by qianfan Zhao who put lots of debugging
effort into understanding the solution space.
Fixes: c49436b657d0 ("serial: 8250_dw: Improve unwritable LCR workaround")
Fixes: 7d4008ebb1c9 ("tty: add a DesignWare 8250 driver")
Cc: stable <stable@kernel.org>
Reported-by: qianfan Zhao <qianfanguijin@163.com>
Link: https://lore.kernel.org/linux-serial/289bb78a-7509-1c5c-2923-a04ed3b6487d@163.com/
Reported-by: Adriana Nicolae <adriana@arista.com>
Link: https://lore.kernel.org/linux-serial/20250819182322.3451959-1-adriana@arista.com/
Reported-by: Bandal, Shankar <shankar.bandal@intel.com>
Tested-by: Bandal, Shankar <shankar.bandal@intel.com>
Tested-by: Murthy, Shanth <shanth.murthy@intel.com>
Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
Link: https://patch.msgid.link/20260203171049.4353-8-ilpo.jarvinen@linux.intel.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
[Ionut: adapt to 6.12.y -
- trivial conflict in dw8250_check_lcr(): 6.12.y still has the
(int offset, int value) signature (the 'unsigned int offset,
u32 value' rework from fc9ceb501e38 is not backported), but
the conflicting tries/loop body is removed by this patch
anyway - took the new code as-is;
- use the bare-identifier form EXPORT_SYMBOL_NS_GPL(...,
SERIAL_8250) and MODULE_IMPORT_NS(SERIAL_8250) rather than
the string forms; in 6.12.y both macros stringify the
namespace argument via __stringify(ns), so it must be an
unquoted identifier.]
Signed-off-by: Ionut Nechita <ionut.nechita@windriver.com>
---
drivers/tty/serial/8250/8250.h | 25 +++++
drivers/tty/serial/8250/8250_dw.c | 165 ++++++++++++++++++++--------
drivers/tty/serial/8250/8250_port.c | 28 ++---
3 files changed, 162 insertions(+), 56 deletions(-)
diff --git a/drivers/tty/serial/8250/8250.h b/drivers/tty/serial/8250/8250.h
index 10a706fe4b24..1fe8222ffacd 100644
--- a/drivers/tty/serial/8250/8250.h
+++ b/drivers/tty/serial/8250/8250.h
@@ -184,7 +184,9 @@ static unsigned int __maybe_unused serial_icr_read(struct uart_8250_port *up,
return value;
}
+void serial8250_clear_fifos(struct uart_8250_port *p);
void serial8250_clear_and_reinit_fifos(struct uart_8250_port *p);
+void serial8250_fifo_wait_for_lsr_thre(struct uart_8250_port *up, unsigned int count);
static inline u32 serial_dl_read(struct uart_8250_port *up)
{
@@ -402,6 +404,26 @@ static inline bool serial8250_tx_dma_running(struct uart_8250_port *p)
return dma && dma->tx_running;
}
+
+static inline void serial8250_tx_dma_pause(struct uart_8250_port *p)
+{
+ struct uart_8250_dma *dma = p->dma;
+
+ if (!dma->tx_running)
+ return;
+
+ dmaengine_pause(dma->txchan);
+}
+
+static inline void serial8250_tx_dma_resume(struct uart_8250_port *p)
+{
+ struct uart_8250_dma *dma = p->dma;
+
+ if (!dma->tx_running)
+ return;
+
+ dmaengine_resume(dma->txchan);
+}
#else
static inline int serial8250_tx_dma(struct uart_8250_port *p)
{
@@ -423,6 +445,9 @@ static inline bool serial8250_tx_dma_running(struct uart_8250_port *p)
{
return false;
}
+
+static inline void serial8250_tx_dma_pause(struct uart_8250_port *p) { }
+static inline void serial8250_tx_dma_resume(struct uart_8250_port *p) { }
#endif
static inline int ns16550a_goto_highspeed(struct uart_8250_port *up)
diff --git a/drivers/tty/serial/8250/8250_dw.c b/drivers/tty/serial/8250/8250_dw.c
index 1650107d1cbc..630cc7227494 100644
--- a/drivers/tty/serial/8250/8250_dw.c
+++ b/drivers/tty/serial/8250/8250_dw.c
@@ -16,6 +16,7 @@
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/io.h>
+#include <linux/lockdep.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/notifier.h>
@@ -47,6 +48,8 @@
#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)
@@ -89,6 +92,7 @@ struct dw8250_data {
unsigned int skip_autocfg:1;
unsigned int uart_16550_compatible:1;
+ unsigned int in_idle:1;
u8 no_int_count;
};
@@ -121,78 +125,151 @@ static inline int dw8250_modify_msr(struct uart_port *p, int offset, int value)
return value;
}
+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)
+ serial_port_out(p, UART_FCR, up->fcr);
+ serial_port_out(p, UART_MCR, up->mcr);
+ serial_port_out(p, UART_IER, up->ier);
+
+ /* DMA Rx is restarted by IRQ handler as needed. */
+ if (up->dma)
+ serial8250_tx_dma_resume(up);
+
+ d->in_idle = 0;
+}
+
/*
- * 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);
+ unsigned int usr_reg = d->pdata ? d->pdata->usr_reg : DW_UART_USR;
struct uart_8250_port *up = up_to_u8250p(p);
- unsigned int lsr;
+ int retries;
+ 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);
+ lockdep_assert_held_once(&p->lock);
+
+ if (d->uart_16550_compatible)
+ return 0;
+
+ d->in_idle = 1;
+
+ /* Prevent triggering interrupt from RBR filling */
+ serial_port_out(p, UART_IER, 0);
+
+ if (up->dma) {
+ serial8250_rx_dma_flush(up);
+ if (serial8250_tx_dma_running(up))
+ serial8250_tx_dma_pause(up);
+ }
/*
- * 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.
+ * Wait until Tx becomes empty + one extra frame time to ensure all bits
+ * have been sent on the wire.
+ *
+ * FIXME: frame_time delay is too long with very low baudrates.
*/
- if (up->fcr & UART_FCR_ENABLE_FIFO) {
- lsr = serial_port_in(p, UART_LSR);
- if (!(lsr & UART_LSR_DR))
- return;
+ serial8250_fifo_wait_for_lsr_thre(up, p->fifosize);
+ ndelay(p->frame_time);
+
+ serial_port_out(p, UART_MCR, up->mcr | UART_MCR_LOOP);
+
+ retries = 4; /* Arbitrary limit, 2 was always enough in tests */
+ do {
+ serial8250_clear_fifos(up);
+ if (!(serial_port_in(p, usr_reg) & DW_UART_USR_BUSY))
+ break;
+ /* FIXME: frame_time delay is too long with very low baudrates. */
+ ndelay(p->frame_time);
+ } while (--retries);
+
+ lsr = serial_lsr_in(up);
+ if (lsr & UART_LSR_DR) {
+ serial_port_in(p, UART_RX);
+ up->lsr_saved_flags = 0;
+ }
+
+ /* Now guaranteed to have BUSY deasserted? Just sanity check */
+ if (serial_port_in(p, usr_reg) & DW_UART_USR_BUSY) {
+ dw8250_idle_exit(p);
+ return -EBUSY;
}
- serial_port_in(p, UART_RX);
+ return 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)
+ return;
+
+ serial_port_out(p, UART_LCR, up->lcr | UART_LCR_DLAB);
+ if (!(serial_port_in(p, UART_LCR) & UART_LCR_DLAB))
+ goto idle_failed;
+
+ serial_dl_write(up, quot);
+ serial_port_out(p, UART_LCR, up->lcr);
+
+idle_failed:
+ dw8250_idle_exit(p);
}
/*
* 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.
+ * routine. Hence, special care must be taken when serial_port_out() or
+ * serial_out() against the modified registers here, i.e. LCR (d->in_idle is
+ * used to break recursion loop).
*/
static void dw8250_check_lcr(struct uart_port *p, int offset, int value)
{
struct dw8250_data *d = to_dw8250_data(p->private_data);
- void __iomem *addr = p->membase + (offset << p->regshift);
- int tries = 1000;
+ u32 lcr;
+ int ret;
if (offset != UART_LCR || d->uart_16550_compatible)
return;
+ lcr = serial_port_in(p, UART_LCR);
+
/* Make sure LCR write wasn't ignored */
- while (tries--) {
- unsigned int 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;
+ if (d->in_idle)
+ goto write_err;
- dw8250_force_idle(p);
+ ret = dw8250_idle_enter(p);
+ if (ret < 0)
+ goto write_err;
-#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);
- }
+ serial_port_out(p, UART_LCR, value);
+ dw8250_idle_exit(p);
+ return;
+
+write_err:
/*
* FIXME: this deadlocks if port->lock is already held
* dev_err(p->dev, "Couldn't set LCR to %d\n", value);
*/
+ return; /* Silences "label at the end of compound statement" */
}
/*
@@ -636,8 +713,10 @@ static int dw8250_probe(struct platform_device *pdev)
p->type = PORT_8250;
p->flags = UPF_FIXED_PORT;
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)
@@ -929,7 +1008,7 @@ static struct platform_driver dw8250_platform_driver = {
module_platform_driver(dw8250_platform_driver);
-MODULE_IMPORT_NS("SERIAL_8250");
+MODULE_IMPORT_NS(SERIAL_8250);
MODULE_AUTHOR("Jamie Iles");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Synopsys DesignWare 8250 serial port driver");
diff --git a/drivers/tty/serial/8250/8250_port.c b/drivers/tty/serial/8250/8250_port.c
index 1e6dca739eab..ce26d1649ca5 100644
--- a/drivers/tty/serial/8250/8250_port.c
+++ b/drivers/tty/serial/8250/8250_port.c
@@ -478,7 +478,7 @@ serial_port_out_sync(struct uart_port *p, int offset, int value)
/*
* FIFO support.
*/
-static void serial8250_clear_fifos(struct uart_8250_port *p)
+void serial8250_clear_fifos(struct uart_8250_port *p)
{
if (p->capabilities & UART_CAP_FIFO) {
serial_out(p, UART_FCR, UART_FCR_ENABLE_FIFO);
@@ -487,6 +487,7 @@ static void serial8250_clear_fifos(struct uart_8250_port *p)
serial_out(p, UART_FCR, 0);
}
}
+EXPORT_SYMBOL_NS_GPL(serial8250_clear_fifos, SERIAL_8250);
static enum hrtimer_restart serial8250_em485_handle_start_tx(struct hrtimer *t);
static enum hrtimer_restart serial8250_em485_handle_stop_tx(struct hrtimer *t);
@@ -3288,6 +3289,17 @@ void serial8250_set_defaults(struct uart_8250_port *up)
}
EXPORT_SYMBOL_GPL(serial8250_set_defaults);
+void serial8250_fifo_wait_for_lsr_thre(struct uart_8250_port *up, unsigned int count)
+{
+ unsigned int i;
+
+ for (i = 0; i < count; i++) {
+ if (wait_for_lsr(up, UART_LSR_THRE))
+ return;
+ }
+}
+EXPORT_SYMBOL_NS_GPL(serial8250_fifo_wait_for_lsr_thre, SERIAL_8250);
+
#ifdef CONFIG_SERIAL_8250_CONSOLE
static void serial8250_console_putchar(struct uart_port *port, unsigned char ch)
@@ -3324,16 +3336,6 @@ static void serial8250_console_restore(struct uart_8250_port *up)
serial8250_out_MCR(up, up->mcr | UART_MCR_DTR | UART_MCR_RTS);
}
-static void fifo_wait_for_lsr(struct uart_8250_port *up, unsigned int count)
-{
- unsigned int i;
-
- for (i = 0; i < count; i++) {
- if (wait_for_lsr(up, UART_LSR_THRE))
- return;
- }
-}
-
/*
* Print a string to the serial port using the device FIFO
*
@@ -3351,7 +3353,7 @@ static void serial8250_console_fifo_write(struct uart_8250_port *up,
while (s != end) {
/* Allow timeout for each byte of a possibly full FIFO */
- fifo_wait_for_lsr(up, fifosize);
+ serial8250_fifo_wait_for_lsr_thre(up, fifosize);
for (i = 0; i < fifosize && s != end; ++i) {
if (*s == '\n' && !cr_sent) {
@@ -3369,7 +3371,7 @@ static void serial8250_console_fifo_write(struct uart_8250_port *up,
* Allow timeout for each byte written since the caller will only wait
* for UART_LSR_BOTH_EMPTY using the timeout of a single character
*/
- fifo_wait_for_lsr(up, tx_count);
+ serial8250_fifo_wait_for_lsr_thre(up, tx_count);
}
/*
--
2.54.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* Re: [PATCH 6.12.y 0/8] serial: 8250_dw: backport BUSY deassert series
2026-05-10 13:40 [PATCH 6.12.y 0/8] serial: 8250_dw: backport BUSY deassert series Ionut Nechita (Wind River)
` (7 preceding siblings ...)
2026-05-10 13:40 ` [PATCH 6.12.y 8/8] serial: 8250_dw: Ensure BUSY is deasserted Ionut Nechita (Wind River)
@ 2026-05-11 10:26 ` Ilpo Järvinen
8 siblings, 0 replies; 10+ messages in thread
From: Ilpo Järvinen @ 2026-05-11 10:26 UTC (permalink / raw)
To: Ionut Nechita (Wind River)
Cc: Greg Kroah-Hartman, Andy Shevchenko, linux-serial
[-- Attachment #1: Type: text/plain, Size: 6427 bytes --]
On Sun, 10 May 2026, Ionut Nechita (Wind River) wrote:
> From: Ionut Nechita <ionut.nechita@windriver.com>
>
> Hi Greg, Ilpo,
>
> This series backports the 8250_dw BUSY deassert fix to 6.12.y, per
> Ilpo's guidance from the request thread:
>
> https://lore.kernel.org/linux-serial/deb9499d-3245-7e38-9034-e533d4b5f512@linux.intel.com/
>
> Background: we ship 6.12 LTS as part of a certified production
> platform (StarlingX/Yocto) and are hitting the BUSY assertion issue
> on Intel DesignWare 8250 UARTs - LCR writes get silently ignored
> under Rx load, causing baud-rate / framing mismatches after
> set_termios. A major LTS bump is a multi-month re-qualification we
> can't justify for a single subsystem fix.
>
> The original mainline series is 7 patches by Ilpo (plus its
> dependencies). For 6.12.y, the resulting series here is 8 patches:
>
> Prerequisites (per Ilpo):
> 1/8 serial: 8250: use serial_port_in/out() helpers [dbd26a886e94]
> 2/8 serial: 8250_dw: Comment possible corner cases ... [bd8cad85561b]
>
> BUSY deassert series:
> 3/8 serial: 8250: Protect LCR write in shutdown [59a33d83bbe6]
> 4/8 serial: 8250_dw: Avoid unnecessary LCR writes [8002d6d6d0d8]
> 5/8 serial: 8250: Add serial8250_handle_irq_locked() [8324a54f604d]
> 6/8 serial: 8250_dw: Rework dw8250_handle_irq() ... [883c5a2bc934]
> 7/8 serial: 8250_dw: Rework IIR_NO_INT handling ... [73a4ed8f9efa]
> 8/8 serial: 8250_dw: Ensure BUSY is deasserted [a7b9ce39fbe4]
>
> Notes:
>
> - Patch 6/7 of the original mainline series,
> commit e0a368ae7953 ("serial: 8250: Add late synchronize_irq() to shutdown to handle DW UART BUSY"),
> is *already* in 6.12.y as
> commit 0bae1c670aa8 ("serial: 8250: Add late synchronize_irq() to shutdown to handle DW UART BUSY")
> (Ilpo, 2026-02-03), so it is not re-sent here. Functionally this
> means patches 3-8 above land on top of the existing
> late-synchronize_irq() fix; the conflict in patch 3 (LCR write
> placement around the late synchronize_irq) was resolved
> accordingly.
>
> - Ilpo's other suggested prerequisites are *not* included as prereqs:
> * commit b339809edda1 ("serial: 8250: use guard()s")
> * commit fc9ceb501e38 ("serial: 8250: sanitize uart_port::serial_{in,out}() types")
> * commit c213375e3283 ("serial: 8250_dw: Call dw8250_quirks() conditionally")
>
> Reasoning:
>
> * commit b339809edda1 ("serial: 8250: use guard()s") is a
> large refactor and only the shutdown hunk was needed;
> instead, patches 3, 5, 6, 7 here are adapted to use the
> existing explicit uart_port_lock_irqsave/unlock_irqrestore
> form rather than the cleanup-based guard() introduced by
> that commit;
Hi,
By doing this you had to add some gotos which makes code control flow
more complicated than it was in the original changes. From reviewer's
point of view, the code is more complicated without guard() than with
them.
guard() seems to be already used in 6.12 in general so I don't know why
you had to go replacing guard()s introduced in the original changes with
lock/unlock pairs.
FWIW, I scanned through the patches and didn't find anything that
interesting (other than that the approach chosen ignored my advice that
would have resulted in less changes/conflicts compared with the original
changes).
> * commit fc9ceb501e38 ("serial: 8250: sanitize
> uart_port::serial_{in,out}() types") only causes a trivial
> conflict in patch 8 in code that the BUSY change removes
> anyway (per Ilpo's note);
> * commit c213375e3283 ("serial: 8250_dw: Call dw8250_quirks()
> conditionally") only causes a conflict in patch 7 around
> the dw8250_setup_dma_filter() helper and the conditional
> p->handle_irq assignment, neither of which exist in 6.12.y
> and neither of which is needed for the BUSY fix.
>
> - Namespace export syntax: in 6.12.y both EXPORT_SYMBOL_NS_GPL()
> and MODULE_IMPORT_NS() apply __stringify(ns) to the namespace
> argument, so it must be a bare identifier. Mainline (where the
> upstream patches were written) accepts a string literal. Patches
> 5 and 8 here use the bare-identifier form (SERIAL_8250) instead
> of the upstream string form ("SERIAL_8250"); without this fix
> the .vmlinux.export.c link step fails with "expected ':' or ')'
> before 'SERIAL_8250'". This is noted in the [Ionut: ...] block
> of the affected patches.
>
> Each of patches 3, 5, 6, 7 and 8 carries an explicit
> "[Ionut: adapt to 6.12.y - ...]" note describing exactly what
> was changed relative to the upstream commit.
>
> Build:
> - Each patch builds individually on 6.12.87 to a complete vmlinux
> (bisect-safe), with CONFIG_SERIAL_8250=y, CONFIG_SERIAL_8250_DW=m
> on x86_64 defconfig.
>
> Testing plan:
> - We will test on Intel platforms with DW APB UART
> (snps,dw-apb-uart) running 6.12.57-rt / 6.12.87-rt (PREEMPT_RT)
> to confirm the original symptom (LCR writes silently ignored
> under Rx load -> baud / framing mismatch after set_termios) is
> gone. Will report Tested-by once cycles complete.
Testing with a reproduces is highly appreciated.
> Based on: linux-6.12.y at v6.12.87 (8bf2f55ef536).
>
> Andy Shevchenko (1):
> serial: 8250_dw: Comment possible corner cases in serial_out()
> implementation
>
> Ilpo Järvinen (6):
> serial: 8250: Protect LCR write in shutdown
> serial: 8250_dw: Avoid unnecessary LCR writes
> serial: 8250: Add serial8250_handle_irq_locked()
> serial: 8250_dw: Rework dw8250_handle_irq() locking and IIR handling
> serial: 8250_dw: Rework IIR_NO_INT handling to stop interrupt storm
> serial: 8250_dw: Ensure BUSY is deasserted
>
> Jiri Slaby (SUSE) (1):
> serial: 8250: use serial_port_in/out() helpers
>
> drivers/tty/serial/8250/8250.h | 25 +++
> drivers/tty/serial/8250/8250_dw.c | 298 +++++++++++++++++++++++-----
> drivers/tty/serial/8250/8250_fsl.c | 8 +-
> drivers/tty/serial/8250/8250_omap.c | 2 +-
> drivers/tty/serial/8250/8250_port.c | 66 +++---
> include/linux/serial_8250.h | 1 +
> 6 files changed, 319 insertions(+), 81 deletions(-)
>
>
--
i.
^ permalink raw reply [flat|nested] 10+ messages in thread