--- /dev/null
+From e0a368ae79531ff92105a2692f10d83052055856 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Ilpo=20J=C3=A4rvinen?= <ilpo.jarvinen@linux.intel.com>
+Date: Tue, 3 Feb 2026 19:10:48 +0200
+Subject: serial: 8250: Add late synchronize_irq() to shutdown to handle DW UART BUSY
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+From: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
+
+commit e0a368ae79531ff92105a2692f10d83052055856 upstream.
+
+When DW UART is !uart_16550_compatible, it can indicate BUSY at any
+point (when under constant Rx pressure) unless a complex sequence of
+steps is performed. Any LCR write can run a foul with the condition
+that prevents writing LCR while the UART is BUSY, which triggers
+BUSY_DETECT interrupt that seems unmaskable using IER bits.
+
+Normal flow is that dw8250_handle_irq() handles BUSY_DETECT condition
+by reading USR register. This BUSY feature, however, breaks the
+assumptions made in serial8250_do_shutdown(), which runs
+synchronize_irq() after clearing IER and assumes no interrupts can
+occur after that point but then proceeds to update LCR, which on DW
+UART can trigger an interrupt.
+
+If serial8250_do_shutdown() releases the interrupt handler before the
+handler has run and processed the BUSY_DETECT condition by read the USR
+register, the IRQ is not deasserted resulting in interrupt storm that
+triggers "irq x: nobody cared" warning leading to disabling the IRQ.
+
+Add late synchronize_irq() into serial8250_do_shutdown() to ensure
+BUSY_DETECT from DW UART is handled before port's interrupt handler is
+released. Alternative would be to add DW UART specific shutdown
+function but it would mostly duplicate the generic code and the extra
+synchronize_irq() seems pretty harmless in serial8250_do_shutdown().
+
+Fixes: 7d4008ebb1c9 ("tty: add a DesignWare 8250 driver")
+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-7-ilpo.jarvinen@linux.intel.com
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/tty/serial/8250/8250_port.c | 6 ++++++
+ 1 file changed, 6 insertions(+)
+
+--- a/drivers/tty/serial/8250/8250_port.c
++++ b/drivers/tty/serial/8250/8250_port.c
+@@ -2399,6 +2399,12 @@ void serial8250_do_shutdown(struct uart_
+ * the IRQ chain.
+ */
+ serial_port_in(port, UART_RX);
++ /*
++ * LCR writes on DW UART can trigger late (unmaskable) IRQs.
++ * Handle them before releasing the handler.
++ */
++ synchronize_irq(port->irq);
++
+ serial8250_rpm_put(up);
+
+ up->ops->release_irq(up);
--- /dev/null
+From 8324a54f604da18f21070702a8ad82ab2062787b Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Ilpo=20J=C3=A4rvinen?= <ilpo.jarvinen@linux.intel.com>
+Date: Tue, 3 Feb 2026 19:10:45 +0200
+Subject: serial: 8250: Add serial8250_handle_irq_locked()
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+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>
+---
+ drivers/tty/serial/8250/8250_port.c | 24 ++++++++++++++++--------
+ include/linux/serial_8250.h | 1 +
+ 2 files changed, 17 insertions(+), 8 deletions(-)
+
+--- 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>
+@@ -1782,20 +1783,16 @@ static bool handle_rx_dma(struct uart_82
+ }
+
+ /*
+- * 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);
+
+@@ -1828,8 +1825,19 @@ int serial8250_handle_irq(struct uart_po
+ else if (!up->dma->tx_running)
+ __stop_tx(up);
+ }
++}
++EXPORT_SYMBOL_NS_GPL(serial8250_handle_irq_locked, "SERIAL_8250");
++
++/*
++ * This handles the interrupt from one port.
++ */
++int serial8250_handle_irq(struct uart_port *port, unsigned int iir)
++{
++ if (iir & UART_IIR_NO_INT)
++ return 0;
+
+- uart_unlock_and_check_sysrq_irqrestore(port, flags);
++ guard(uart_port_lock_irqsave)(port);
++ serial8250_handle_irq_locked(port, iir);
+
+ return 1;
+ }
+--- a/include/linux/serial_8250.h
++++ b/include/linux/serial_8250.h
+@@ -195,6 +195,7 @@ void serial8250_do_set_mctrl(struct uart
+ 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);
--- /dev/null
+From 24b98e8664e157aff0814a0f49895ee8223f382f Mon Sep 17 00:00:00 2001
+From: Peng Zhang <zhangpeng.00@bytedance.com>
+Date: Tue, 24 Feb 2026 13:16:39 +0100
+Subject: serial: 8250: always disable IRQ during THRE test
+
+From: Peng Zhang <zhangpeng.00@bytedance.com>
+
+commit 24b98e8664e157aff0814a0f49895ee8223f382f upstream.
+
+commit 039d4926379b ("serial: 8250: Toggle IER bits on only after irq
+has been set up") moved IRQ setup before the THRE test, in combination
+with commit 205d300aea75 ("serial: 8250: change lock order in
+serial8250_do_startup()") the interrupt handler can run during the
+test and race with its IIR reads. This can produce wrong THRE test
+results and cause spurious registration of the
+serial8250_backup_timeout timer. Unconditionally disable the IRQ for
+the short duration of the test and re-enable it afterwards to avoid
+the race.
+
+Fixes: 039d4926379b ("serial: 8250: Toggle IER bits on only after irq has been set up")
+Depends-on: 205d300aea75 ("serial: 8250: change lock order in serial8250_do_startup()")
+Cc: stable <stable@kernel.org>
+Signed-off-by: Peng Zhang <zhangpeng.00@bytedance.com>
+Reviewed-by: Muchun Song <songmuchun@bytedance.com>
+Signed-off-by: Alban Bedel <alban.bedel@lht.dlh.de>
+Tested-by: Maximilian Lueer <maximilian.lueer@lht.dlh.de>
+Link: https://patch.msgid.link/20260224121639.579404-1-alban.bedel@lht.dlh.de
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/tty/serial/8250/8250_port.c | 6 ++----
+ 1 file changed, 2 insertions(+), 4 deletions(-)
+
+--- a/drivers/tty/serial/8250/8250_port.c
++++ b/drivers/tty/serial/8250/8250_port.c
+@@ -2147,8 +2147,7 @@ static void serial8250_THRE_test(struct
+ if (up->port.flags & UPF_NO_THRE_TEST)
+ return;
+
+- if (port->irqflags & IRQF_SHARED)
+- disable_irq_nosync(port->irq);
++ disable_irq(port->irq);
+
+ /*
+ * Test for UARTs that do not reassert THRE when the transmitter is idle and the interrupt
+@@ -2170,8 +2169,7 @@ static void serial8250_THRE_test(struct
+ serial_port_out(port, UART_IER, 0);
+ }
+
+- if (port->irqflags & IRQF_SHARED)
+- enable_irq(port->irq);
++ enable_irq(port->irq);
+
+ /*
+ * If the interrupt is not reasserted, or we otherwise don't trust the iir, setup a timer to
--- /dev/null
+From a424a34b8faddf97b5af41689087e7a230f79ba7 Mon Sep 17 00:00:00 2001
+From: Raul E Rangel <rrangel@chromium.org>
+Date: Mon, 9 Feb 2026 13:58:18 -0700
+Subject: serial: 8250: Fix TX deadlock when using DMA
+
+From: Raul E Rangel <rrangel@chromium.org>
+
+commit a424a34b8faddf97b5af41689087e7a230f79ba7 upstream.
+
+`dmaengine_terminate_async` does not guarantee that the
+`__dma_tx_complete` callback will run. The callback is currently the
+only place where `dma->tx_running` gets cleared. If the transaction is
+canceled and the callback never runs, then `dma->tx_running` will never
+get cleared and we will never schedule new TX DMA transactions again.
+
+This change makes it so we clear `dma->tx_running` after we terminate
+the DMA transaction. This is "safe" because `serial8250_tx_dma_flush`
+is holding the UART port lock. The first thing the callback does is also
+grab the UART port lock, so access to `dma->tx_running` is serialized.
+
+Fixes: 9e512eaaf8f4 ("serial: 8250: Fix fifo underflow on flush")
+Cc: stable <stable@kernel.org>
+Signed-off-by: Raul E Rangel <rrangel@google.com>
+Link: https://patch.msgid.link/20260209135815.1.I16366ecb0f62f3c96fe3dd5763fcf6f3c2b4d8cd@changeid
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/tty/serial/8250/8250_dma.c | 15 +++++++++++++++
+ 1 file changed, 15 insertions(+)
+
+--- a/drivers/tty/serial/8250/8250_dma.c
++++ b/drivers/tty/serial/8250/8250_dma.c
+@@ -162,7 +162,22 @@ void serial8250_tx_dma_flush(struct uart
+ */
+ dma->tx_size = 0;
+
++ /*
++ * We can't use `dmaengine_terminate_sync` because `uart_flush_buffer` is
++ * holding the uart port spinlock.
++ */
+ dmaengine_terminate_async(dma->txchan);
++
++ /*
++ * The callback might or might not run. If it doesn't run, we need to ensure
++ * that `tx_running` is cleared so that we can schedule new transactions.
++ * If it does run, then the zombie callback will clear `tx_running` again
++ * and perform a no-op since `tx_size` was cleared above.
++ *
++ * In either case, we ASSUME the DMA transaction will terminate before we
++ * issue a new `serial8250_tx_dma`.
++ */
++ dma->tx_running = 0;
+ }
+
+ int serial8250_rx_dma(struct uart_8250_port *p)
--- /dev/null
+From 59a33d83bbe6d73d2071d7ae21590b29faed0503 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Ilpo=20J=C3=A4rvinen?= <ilpo.jarvinen@linux.intel.com>
+Date: Tue, 3 Feb 2026 19:10:43 +0200
+Subject: serial: 8250: Protect LCR write in shutdown
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+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>
+---
+ drivers/tty/serial/8250/8250_port.c | 11 ++++++-----
+ 1 file changed, 6 insertions(+), 5 deletions(-)
+
+--- a/drivers/tty/serial/8250/8250_port.c
++++ b/drivers/tty/serial/8250/8250_port.c
+@@ -2348,6 +2348,7 @@ static int serial8250_startup(struct uar
+ void serial8250_do_shutdown(struct uart_port *port)
+ {
+ struct uart_8250_port *up = up_to_u8250p(port);
++ u32 lcr;
+
+ serial8250_rpm_get(up);
+ /*
+@@ -2374,13 +2375,13 @@ void serial8250_do_shutdown(struct uart_
+ 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);
+ }
+
+- /*
+- * Disable break condition and FIFOs
+- */
+- serial_port_out(port, UART_LCR,
+- serial_port_in(port, UART_LCR) & ~UART_LCR_SBC);
+ serial8250_clear_fifos(up);
+
+ rsa_disable(up);
--- /dev/null
+From 8002d6d6d0d8a36a7d6ca523b17a51cb0fa7c3c3 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Ilpo=20J=C3=A4rvinen?= <ilpo.jarvinen@linux.intel.com>
+Date: Tue, 3 Feb 2026 19:10:44 +0200
+Subject: serial: 8250_dw: Avoid unnecessary LCR writes
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+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>
+---
+ drivers/tty/serial/8250/8250_dw.c | 31 +++++++++++++++++++++++++++++++
+ 1 file changed, 31 insertions(+)
+
+--- 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
+ */
+ }
+
++/*
++ * 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
+
+ static void dw8250_serial_out(struct uart_port *p, unsigned int offset, u32 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, unsigned int offset, u32 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 u32 dw8250_serial_inq(struct uart
+
+ static void dw8250_serial_outq(struct uart_port *p, unsigned int offset, u32 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 ua
+
+ static void dw8250_serial_out32(struct uart_port *p, unsigned int offset, u32 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 u32 dw8250_serial_in32(struct uar
+
+ static void dw8250_serial_out32be(struct uart_port *p, unsigned int offset, u32 value)
+ {
++ if (dw8250_can_skip_reg_write(p, offset, value))
++ return;
++
+ iowrite32be(value, p->membase + (offset << p->regshift));
+ dw8250_check_lcr(p, offset, value);
+ }
--- /dev/null
+From a7b9ce39fbe4ae2919fe4f7ac16c293cb6632d30 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Ilpo=20J=C3=A4rvinen?= <ilpo.jarvinen@linux.intel.com>
+Date: Tue, 3 Feb 2026 19:10:49 +0200
+Subject: serial: 8250_dw: Ensure BUSY is deasserted
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+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>
+---
+ drivers/tty/serial/8250/8250.h | 25 +++++
+ drivers/tty/serial/8250/8250_dw.c | 163 ++++++++++++++++++++++++++----------
+ drivers/tty/serial/8250/8250_port.c | 28 +++---
+ 3 files changed, 161 insertions(+), 55 deletions(-)
+
+--- a/drivers/tty/serial/8250/8250.h
++++ b/drivers/tty/serial/8250/8250.h
+@@ -184,7 +184,9 @@ static unsigned int __maybe_unused seria
+ 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);
+
+ void serial8250_rpm_get(struct uart_8250_port *p);
+ void serial8250_rpm_put(struct uart_8250_port *p);
+@@ -409,6 +411,26 @@ static inline bool serial8250_tx_dma_run
+
+ 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)
+ {
+@@ -430,6 +452,9 @@ static inline bool serial8250_tx_dma_run
+ {
+ 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)
+--- 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 u32 dw8250_modify_msr(stru
+ 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;
+ }
+
+- serial_port_in(p, UART_RX);
++ /* 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;
++ }
++
++ 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, 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;
++ 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--) {
+- 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;
++ if (d->in_idle)
++ goto write_err;
+
+- dw8250_force_idle(p);
++ ret = dw8250_idle_enter(p);
++ if (ret < 0)
++ goto write_err;
++
++ serial_port_out(p, UART_LCR, value);
++ dw8250_idle_exit(p);
++ 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);
+- }
++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" */
+ }
+
+ /*
+@@ -632,8 +709,10 @@ static int dw8250_probe(struct platform_
+ 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)
+--- a/drivers/tty/serial/8250/8250_port.c
++++ b/drivers/tty/serial/8250/8250_port.c
+@@ -489,7 +489,7 @@ serial_port_out_sync(struct uart_port *p
+ /*
+ * 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);
+@@ -498,6 +498,7 @@ static void serial8250_clear_fifos(struc
+ 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);
+@@ -3198,6 +3199,17 @@ void serial8250_set_defaults(struct uart
+ }
+ 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)
+@@ -3239,16 +3251,6 @@ static void serial8250_console_restore(s
+ 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
+ *
+@@ -3267,7 +3269,7 @@ static void serial8250_console_fifo_writ
+
+ 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) {
+@@ -3285,7 +3287,7 @@ static void serial8250_console_fifo_writ
+ * 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);
+ }
+
+ /*
--- /dev/null
+From 883c5a2bc934c165c4491d1ef7da0ac4e9765077 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Ilpo=20J=C3=A4rvinen?= <ilpo.jarvinen@linux.intel.com>
+Date: Tue, 3 Feb 2026 19:10:46 +0200
+Subject: serial: 8250_dw: Rework dw8250_handle_irq() locking and IIR handling
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+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>
+---
+ drivers/tty/serial/8250/8250_dw.c | 37 +++++++++++++++++++++----------------
+ 1 file changed, 21 insertions(+), 16 deletions(-)
+
+--- 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 */
+@@ -312,7 +317,19 @@ static int dw8250_handle_irq(struct uart
+ bool rx_timeout = (iir & 0x3f) == UART_IIR_RX_TIMEOUT;
+ unsigned int quirks = d->pdata->quirks;
+ 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;
++ }
++
++ guard(uart_port_lock_irqsave)(p);
+
+ /*
+ * There are ways to get Designware-based UARTs into a state where
+@@ -325,20 +342,15 @@ static int dw8250_handle_irq(struct uart
+ * 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 +358,9 @@ static int dw8250_handle_irq(struct uart
+ }
+ }
+
+- 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;
+- }
+-
+- return 0;
++ return 1;
+ }
+
+ static void dw8250_clk_work_cb(struct work_struct *work)
+@@ -865,6 +869,7 @@ static struct platform_driver dw8250_pla
+
+ 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");
--- /dev/null
+From 73a4ed8f9efaaaf8207614ccc1c9d5ca1888f23a Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Ilpo=20J=C3=A4rvinen?= <ilpo.jarvinen@linux.intel.com>
+Date: Tue, 3 Feb 2026 19:10:47 +0200
+Subject: serial: 8250_dw: Rework IIR_NO_INT handling to stop interrupt storm
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+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>
+---
+ drivers/tty/serial/8250/8250_dw.c | 67 +++++++++++++++++++++++++++++++++++---
+ 1 file changed, 63 insertions(+), 4 deletions(-)
+
+--- 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 u32 dw8250_serial_in32be(struct u
+ 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,18 +350,30 @@ static int dw8250_handle_irq(struct uart
+ unsigned int quirks = d->pdata->quirks;
+ unsigned int status;
+
++ guard(uart_port_lock_irqsave)(p);
++
+ switch (FIELD_GET(DW_UART_IIR_IID, iir)) {
+ case UART_IIR_NO_INT:
++ if (d->uart_16550_compatible || up->dma)
++ return 0;
++
++ 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;
++
+ return 0;
+
+ case UART_IIR_BUSY:
+ /* Clear the USR */
+ serial_port_in(p, d->pdata->usr_reg);
+
++ d->no_int_count = 0;
++
+ return 1;
+ }
+
+- guard(uart_port_lock_irqsave)(p);
++ d->no_int_count = 0;
+
+ /*
+ * There are ways to get Designware-based UARTs into a state where
+@@ -562,6 +606,14 @@ static void dw8250_reset_control_assert(
+ 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;
+@@ -685,10 +737,12 @@ static int dw8250_probe(struct platform_
+ dw8250_quirks(p, data);
+
+ /* If the Busy Functionality is not implemented, don't handle it */
+- if (data->uart_16550_compatible)
++ if (data->uart_16550_compatible) {
+ p->handle_irq = NULL;
+- else if (data->pdata)
++ } else if (data->pdata) {
+ p->handle_irq = dw8250_handle_irq;
++ p->shutdown = dw8250_shutdown;
++ }
+
+ dw8250_setup_dma_filter(p, data);
+
+@@ -822,6 +876,11 @@ static const struct dw8250_platform_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 },
+@@ -851,7 +910,7 @@ static const struct acpi_device_id dw825
+ { "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);
--- /dev/null
+From 9c0072bc33d349c83d223e64be30794e11938a6b Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Martin=20Roukala=20=28n=C3=A9=20Peres=29?=
+ <martin.roukala@mupuf.org>
+Date: Mon, 9 Mar 2026 15:53:10 +0200
+Subject: serial: 8250_pci: add support for the AX99100
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+From: Martin Roukala (né Peres) <martin.roukala@mupuf.org>
+
+commit 9c0072bc33d349c83d223e64be30794e11938a6b upstream.
+
+This is found in popular brands such as StarTech.com or Delock, and has
+been a source of frustration to quite a few people, if I can trust
+Amazon comments complaining about Linux support via the official
+out-of-the-tree driver.
+
+Signed-off-by: Martin Roukala (né Peres) <martin.roukala@mupuf.org>
+Cc: stable <stable@kernel.org>
+Link: https://patch.msgid.link/20260309-8250_pci_ax99100-v1-1-3328bdfd8e94@mupuf.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/tty/serial/8250/8250_pci.c | 17 +++++++++++++++++
+ 1 file changed, 17 insertions(+)
+
+--- a/drivers/tty/serial/8250/8250_pci.c
++++ b/drivers/tty/serial/8250/8250_pci.c
+@@ -137,6 +137,8 @@ struct serial_private {
+ };
+
+ #define PCI_DEVICE_ID_HPE_PCI_SERIAL 0x37e
++#define PCIE_VENDOR_ID_ASIX 0x125B
++#define PCIE_DEVICE_ID_AX99100 0x9100
+
+ static const struct pci_device_id pci_use_msi[] = {
+ { PCI_DEVICE_SUB(PCI_VENDOR_ID_NETMOS, PCI_DEVICE_ID_NETMOS_9900,
+@@ -149,6 +151,8 @@ static const struct pci_device_id pci_us
+ 0xA000, 0x1000) },
+ { PCI_DEVICE_SUB(PCI_VENDOR_ID_HP_3PAR, PCI_DEVICE_ID_HPE_PCI_SERIAL,
+ PCI_ANY_ID, PCI_ANY_ID) },
++ { PCI_DEVICE_SUB(PCIE_VENDOR_ID_ASIX, PCIE_DEVICE_ID_AX99100,
++ 0xA000, 0x1000) },
+ { }
+ };
+
+@@ -912,6 +916,7 @@ static int pci_netmos_init(struct pci_de
+ case PCI_DEVICE_ID_NETMOS_9912:
+ case PCI_DEVICE_ID_NETMOS_9922:
+ case PCI_DEVICE_ID_NETMOS_9900:
++ case PCIE_DEVICE_ID_AX99100:
+ num_serial = pci_netmos_9900_numports(dev);
+ break;
+
+@@ -2547,6 +2552,14 @@ static struct pci_serial_quirk pci_seria
+ .init = pci_netmos_init,
+ .setup = pci_netmos_9900_setup,
+ },
++ {
++ .vendor = PCIE_VENDOR_ID_ASIX,
++ .device = PCI_ANY_ID,
++ .subvendor = PCI_ANY_ID,
++ .subdevice = PCI_ANY_ID,
++ .init = pci_netmos_init,
++ .setup = pci_netmos_9900_setup,
++ },
+ /*
+ * EndRun Technologies
+ */
+@@ -6068,6 +6081,10 @@ static const struct pci_device_id serial
+ 0xA000, 0x3002,
+ 0, 0, pbn_NETMOS9900_2s_115200 },
+
++ { PCIE_VENDOR_ID_ASIX, PCIE_DEVICE_ID_AX99100,
++ 0xA000, 0x1000,
++ 0, 0, pbn_b0_1_115200 },
++
+ /*
+ * Best Connectivity and Rosewill PCI Multi I/O cards
+ */
--- /dev/null
+From 455ce986fa356ff43a43c0d363ba95fa152f21d5 Mon Sep 17 00:00:00 2001
+From: Jiayuan Chen <jiayuan.chen@shopee.com>
+Date: Wed, 4 Feb 2026 15:43:20 +0800
+Subject: serial: core: fix infinite loop in handle_tx() for PORT_UNKNOWN
+
+From: Jiayuan Chen <jiayuan.chen@shopee.com>
+
+commit 455ce986fa356ff43a43c0d363ba95fa152f21d5 upstream.
+
+uart_write_room() and uart_write() behave inconsistently when
+xmit_buf is NULL (which happens for PORT_UNKNOWN ports that were
+never properly initialized):
+
+- uart_write_room() returns kfifo_avail() which can be > 0
+- uart_write() checks xmit_buf and returns 0 if NULL
+
+This inconsistency causes an infinite loop in drivers that rely on
+tty_write_room() to determine if they can write:
+
+ while (tty_write_room(tty) > 0) {
+ written = tty->ops->write(...);
+ // written is always 0, loop never exits
+ }
+
+For example, caif_serial's handle_tx() enters an infinite loop when
+used with PORT_UNKNOWN serial ports, causing system hangs.
+
+Fix by making uart_write_room() also check xmit_buf and return 0 if
+it's NULL, consistent with uart_write().
+
+Reproducer: https://gist.github.com/mrpre/d9a694cc0e19828ee3bc3b37983fde13
+
+Signed-off-by: Jiayuan Chen <jiayuan.chen@shopee.com>
+Cc: stable <stable@kernel.org>
+Link: https://patch.msgid.link/20260204074327.226165-1-jiayuan.chen@linux.dev
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/tty/serial/serial_core.c | 5 ++++-
+ 1 file changed, 4 insertions(+), 1 deletion(-)
+
+--- a/drivers/tty/serial/serial_core.c
++++ b/drivers/tty/serial/serial_core.c
+@@ -643,7 +643,10 @@ static unsigned int uart_write_room(stru
+ unsigned int ret;
+
+ port = uart_port_ref_lock(state, &flags);
+- ret = kfifo_avail(&state->port.xmit_fifo);
++ if (!state->port.xmit_buf)
++ ret = 0;
++ else
++ ret = kfifo_avail(&state->port.xmit_fifo);
+ uart_port_unlock_deref(port, flags);
+ return ret;
+ }
--- /dev/null
+From d54801cd509515f674a5aac1d3ea1401d2a05863 Mon Sep 17 00:00:00 2001
+From: Maciej Andrzejewski ICEYE <maciej.andrzejewski@m-works.net>
+Date: Thu, 5 Mar 2026 13:37:51 +0100
+Subject: serial: uartlite: fix PM runtime usage count underflow on probe
+
+From: Maciej Andrzejewski ICEYE <maciej.andrzejewski@m-works.net>
+
+commit d54801cd509515f674a5aac1d3ea1401d2a05863 upstream.
+
+ulite_probe() calls pm_runtime_put_autosuspend() at the end of probe
+without holding a corresponding PM runtime reference for non-console
+ports.
+
+During ulite_assign(), uart_add_one_port() triggers uart_configure_port()
+which calls ulite_pm() via uart_change_pm(). For non-console ports, the
+UART core performs a balanced get/put cycle:
+
+ uart_change_pm(ON) -> ulite_pm() -> pm_runtime_get_sync() +1
+ uart_change_pm(OFF) -> ulite_pm() -> pm_runtime_put_autosuspend() -1
+
+This leaves no spare reference for the pm_runtime_put_autosuspend() at
+the end of probe. The PM runtime core prevents the count from actually
+going below zero, and instead triggers a
+"Runtime PM usage count underflow!" warning.
+
+For console ports the bug is masked: the UART core skips the
+uart_change_pm(OFF) call, so the UART core's unbalanced get happens to
+pair with probe's trailing put.
+
+Add pm_runtime_get_noresume() before pm_runtime_enable() to take an
+explicit probe-owned reference that the trailing
+pm_runtime_put_autosuspend() can release. This ensures a correct usage
+count regardless of whether the port is a console.
+
+Fixes: 5bbe10a6942d ("tty: serial: uartlite: Add runtime pm support")
+Cc: stable <stable@kernel.org>
+Signed-off-by: Maciej Andrzejewski ICEYE <maciej.andrzejewski@m-works.net>
+Link: https://patch.msgid.link/20260305123746.4152800-1-maciej.andrzejewski@m-works.net
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/tty/serial/uartlite.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+--- a/drivers/tty/serial/uartlite.c
++++ b/drivers/tty/serial/uartlite.c
+@@ -878,6 +878,7 @@ of_err:
+ pm_runtime_use_autosuspend(&pdev->dev);
+ pm_runtime_set_autosuspend_delay(&pdev->dev, UART_AUTOSUSPEND_TIMEOUT);
+ pm_runtime_set_active(&pdev->dev);
++ pm_runtime_get_noresume(&pdev->dev);
+ pm_runtime_enable(&pdev->dev);
+
+ ret = ulite_assign(&pdev->dev, id, res->start, irq, pdata);
io_uring-poll-fix-multishot-recv-missing-eof-on-wakeup-race.patch
io_uring-kbuf-fix-missing-buf_more-for-incremental-buffers-at-eof.patch
io_uring-kbuf-propagate-buf_more-through-early-buffer-commit-path.patch
+vt-save-restore-unicode-screen-buffer-for-alternate-screen.patch
+serial-8250_pci-add-support-for-the-ax99100.patch
+serial-8250-fix-tx-deadlock-when-using-dma.patch
+serial-8250-always-disable-irq-during-thre-test.patch
+serial-8250-protect-lcr-write-in-shutdown.patch
+serial-8250_dw-avoid-unnecessary-lcr-writes.patch
+serial-8250-add-serial8250_handle_irq_locked.patch
+serial-8250_dw-rework-dw8250_handle_irq-locking-and-iir-handling.patch
+serial-8250_dw-rework-iir_no_int-handling-to-stop-interrupt-storm.patch
+serial-8250-add-late-synchronize_irq-to-shutdown-to-handle-dw-uart-busy.patch
+serial-8250_dw-ensure-busy-is-deasserted.patch
+serial-core-fix-infinite-loop-in-handle_tx-for-port_unknown.patch
+serial-uartlite-fix-pm-runtime-usage-count-underflow-on-probe.patch
--- /dev/null
+From 5eb608319bb56464674a71b4a66ea65c6c435d64 Mon Sep 17 00:00:00 2001
+From: Nicolas Pitre <npitre@baylibre.com>
+Date: Tue, 27 Jan 2026 17:56:01 -0500
+Subject: vt: save/restore unicode screen buffer for alternate screen
+
+From: Nicolas Pitre <npitre@baylibre.com>
+
+commit 5eb608319bb56464674a71b4a66ea65c6c435d64 upstream.
+
+The alternate screen support added by commit 23743ba64709 ("vt: add
+support for smput/rmput escape codes") only saves and restores the
+regular screen buffer (vc_origin), but completely ignores the corresponding
+unicode screen buffer (vc_uni_lines) creating a messed-up display.
+
+Add vc_saved_uni_lines to save the unicode screen buffer when entering
+the alternate screen, and restore it when leaving. Also ensure proper
+cleanup in reset_terminal() and vc_deallocate().
+
+Fixes: 23743ba64709 ("vt: add support for smput/rmput escape codes")
+Cc: stable <stable@kernel.org>
+Signed-off-by: Nicolas Pitre <npitre@baylibre.com>
+Link: https://patch.msgid.link/5o2p6qp3-91pq-0p17-or02-1oors4417ns7@onlyvoer.pbz
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/tty/vt/vt.c | 8 ++++++++
+ include/linux/console_struct.h | 1 +
+ 2 files changed, 9 insertions(+)
+
+--- a/drivers/tty/vt/vt.c
++++ b/drivers/tty/vt/vt.c
+@@ -1345,6 +1345,8 @@ struct vc_data *vc_deallocate(unsigned i
+ kfree(vc->vc_saved_screen);
+ vc->vc_saved_screen = NULL;
+ }
++ vc_uniscr_free(vc->vc_saved_uni_lines);
++ vc->vc_saved_uni_lines = NULL;
+ }
+ return vc;
+ }
+@@ -1890,6 +1892,8 @@ static void enter_alt_screen(struct vc_d
+ vc->vc_saved_screen = kmemdup((u16 *)vc->vc_origin, size, GFP_KERNEL);
+ if (vc->vc_saved_screen == NULL)
+ return;
++ vc->vc_saved_uni_lines = vc->vc_uni_lines;
++ vc->vc_uni_lines = NULL;
+ vc->vc_saved_rows = vc->vc_rows;
+ vc->vc_saved_cols = vc->vc_cols;
+ save_cur(vc);
+@@ -1911,6 +1915,8 @@ static void leave_alt_screen(struct vc_d
+ dest = ((u16 *)vc->vc_origin) + r * vc->vc_cols;
+ memcpy(dest, src, 2 * cols);
+ }
++ vc_uniscr_set(vc, vc->vc_saved_uni_lines);
++ vc->vc_saved_uni_lines = NULL;
+ restore_cur(vc);
+ /* Update the entire screen */
+ if (con_should_update(vc))
+@@ -2233,6 +2239,8 @@ static void reset_terminal(struct vc_dat
+ if (vc->vc_saved_screen != NULL) {
+ kfree(vc->vc_saved_screen);
+ vc->vc_saved_screen = NULL;
++ vc_uniscr_free(vc->vc_saved_uni_lines);
++ vc->vc_saved_uni_lines = NULL;
+ vc->vc_saved_rows = 0;
+ vc->vc_saved_cols = 0;
+ }
+--- a/include/linux/console_struct.h
++++ b/include/linux/console_struct.h
+@@ -160,6 +160,7 @@ struct vc_data {
+ struct uni_pagedict **uni_pagedict_loc; /* [!] Location of uni_pagedict variable for this console */
+ u32 **vc_uni_lines; /* unicode screen content */
+ u16 *vc_saved_screen;
++ u32 **vc_saved_uni_lines;
+ unsigned int vc_saved_cols;
+ unsigned int vc_saved_rows;
+ /* additional information is in vt_kern.h */