]> git.ipfire.org Git - thirdparty/kernel/stable-queue.git/commitdiff
6.18-stable patches
authorGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Sat, 21 Mar 2026 06:02:50 +0000 (07:02 +0100)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Sat, 21 Mar 2026 06:02:50 +0000 (07:02 +0100)
added patches:
serial-8250-add-late-synchronize_irq-to-shutdown-to-handle-dw-uart-busy.patch
serial-8250-add-serial8250_handle_irq_locked.patch
serial-8250-always-disable-irq-during-thre-test.patch
serial-8250-fix-tx-deadlock-when-using-dma.patch
serial-8250-protect-lcr-write-in-shutdown.patch
serial-8250_dw-avoid-unnecessary-lcr-writes.patch
serial-8250_dw-ensure-busy-is-deasserted.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_pci-add-support-for-the-ax99100.patch
serial-core-fix-infinite-loop-in-handle_tx-for-port_unknown.patch
serial-uartlite-fix-pm-runtime-usage-count-underflow-on-probe.patch
vt-save-restore-unicode-screen-buffer-for-alternate-screen.patch

14 files changed:
queue-6.18/serial-8250-add-late-synchronize_irq-to-shutdown-to-handle-dw-uart-busy.patch [new file with mode: 0644]
queue-6.18/serial-8250-add-serial8250_handle_irq_locked.patch [new file with mode: 0644]
queue-6.18/serial-8250-always-disable-irq-during-thre-test.patch [new file with mode: 0644]
queue-6.18/serial-8250-fix-tx-deadlock-when-using-dma.patch [new file with mode: 0644]
queue-6.18/serial-8250-protect-lcr-write-in-shutdown.patch [new file with mode: 0644]
queue-6.18/serial-8250_dw-avoid-unnecessary-lcr-writes.patch [new file with mode: 0644]
queue-6.18/serial-8250_dw-ensure-busy-is-deasserted.patch [new file with mode: 0644]
queue-6.18/serial-8250_dw-rework-dw8250_handle_irq-locking-and-iir-handling.patch [new file with mode: 0644]
queue-6.18/serial-8250_dw-rework-iir_no_int-handling-to-stop-interrupt-storm.patch [new file with mode: 0644]
queue-6.18/serial-8250_pci-add-support-for-the-ax99100.patch [new file with mode: 0644]
queue-6.18/serial-core-fix-infinite-loop-in-handle_tx-for-port_unknown.patch [new file with mode: 0644]
queue-6.18/serial-uartlite-fix-pm-runtime-usage-count-underflow-on-probe.patch [new file with mode: 0644]
queue-6.18/series
queue-6.18/vt-save-restore-unicode-screen-buffer-for-alternate-screen.patch [new file with mode: 0644]

diff --git a/queue-6.18/serial-8250-add-late-synchronize_irq-to-shutdown-to-handle-dw-uart-busy.patch b/queue-6.18/serial-8250-add-late-synchronize_irq-to-shutdown-to-handle-dw-uart-busy.patch
new file mode 100644 (file)
index 0000000..2c3db7a
--- /dev/null
@@ -0,0 +1,64 @@
+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);
diff --git a/queue-6.18/serial-8250-add-serial8250_handle_irq_locked.patch b/queue-6.18/serial-8250-add-serial8250_handle_irq_locked.patch
new file mode 100644 (file)
index 0000000..ecf4a1b
--- /dev/null
@@ -0,0 +1,101 @@
+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);
diff --git a/queue-6.18/serial-8250-always-disable-irq-during-thre-test.patch b/queue-6.18/serial-8250-always-disable-irq-during-thre-test.patch
new file mode 100644 (file)
index 0000000..8dfa90a
--- /dev/null
@@ -0,0 +1,54 @@
+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
diff --git a/queue-6.18/serial-8250-fix-tx-deadlock-when-using-dma.patch b/queue-6.18/serial-8250-fix-tx-deadlock-when-using-dma.patch
new file mode 100644 (file)
index 0000000..e52ac32
--- /dev/null
@@ -0,0 +1,54 @@
+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)
diff --git a/queue-6.18/serial-8250-protect-lcr-write-in-shutdown.patch b/queue-6.18/serial-8250-protect-lcr-write-in-shutdown.patch
new file mode 100644 (file)
index 0000000..9948d63
--- /dev/null
@@ -0,0 +1,61 @@
+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);
diff --git a/queue-6.18/serial-8250_dw-avoid-unnecessary-lcr-writes.patch b/queue-6.18/serial-8250_dw-avoid-unnecessary-lcr-writes.patch
new file mode 100644 (file)
index 0000000..947f642
--- /dev/null
@@ -0,0 +1,107 @@
+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);
+ }
diff --git a/queue-6.18/serial-8250_dw-ensure-busy-is-deasserted.patch b/queue-6.18/serial-8250_dw-ensure-busy-is-deasserted.patch
new file mode 100644 (file)
index 0000000..ce44274
--- /dev/null
@@ -0,0 +1,420 @@
+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);
+ }
+ /*
diff --git a/queue-6.18/serial-8250_dw-rework-dw8250_handle_irq-locking-and-iir-handling.patch b/queue-6.18/serial-8250_dw-rework-dw8250_handle_irq-locking-and-iir-handling.patch
new file mode 100644 (file)
index 0000000..26e3def
--- /dev/null
@@ -0,0 +1,134 @@
+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");
diff --git a/queue-6.18/serial-8250_dw-rework-iir_no_int-handling-to-stop-interrupt-storm.patch b/queue-6.18/serial-8250_dw-rework-iir_no_int-handling-to-stop-interrupt-storm.patch
new file mode 100644 (file)
index 0000000..d46b338
--- /dev/null
@@ -0,0 +1,191 @@
+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);
diff --git a/queue-6.18/serial-8250_pci-add-support-for-the-ax99100.patch b/queue-6.18/serial-8250_pci-add-support-for-the-ax99100.patch
new file mode 100644 (file)
index 0000000..aeed11e
--- /dev/null
@@ -0,0 +1,80 @@
+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
+        */
diff --git a/queue-6.18/serial-core-fix-infinite-loop-in-handle_tx-for-port_unknown.patch b/queue-6.18/serial-core-fix-infinite-loop-in-handle_tx-for-port_unknown.patch
new file mode 100644 (file)
index 0000000..be7f63d
--- /dev/null
@@ -0,0 +1,54 @@
+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;
+ }
diff --git a/queue-6.18/serial-uartlite-fix-pm-runtime-usage-count-underflow-on-probe.patch b/queue-6.18/serial-uartlite-fix-pm-runtime-usage-count-underflow-on-probe.patch
new file mode 100644 (file)
index 0000000..113b404
--- /dev/null
@@ -0,0 +1,53 @@
+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);
index 0f2b50e07f232696226f76a5d222f14e0343b7bb..0cc97bfb960a9d677fb906a9a1614cb1033a4fec 100644 (file)
@@ -63,3 +63,16 @@ iommu-vt-d-only-handle-iopf-for-sva-when-pri-is-supported.patch
 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
diff --git a/queue-6.18/vt-save-restore-unicode-screen-buffer-for-alternate-screen.patch b/queue-6.18/vt-save-restore-unicode-screen-buffer-for-alternate-screen.patch
new file mode 100644 (file)
index 0000000..165d3ef
--- /dev/null
@@ -0,0 +1,76 @@
+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 */