From: Greg Kroah-Hartman Date: Sat, 21 Mar 2026 06:02:50 +0000 (+0100) Subject: 6.18-stable patches X-Git-Tag: v6.1.167~72 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=bf45f6a3e5b32d0fa088975d1be0e16ee6496014;p=thirdparty%2Fkernel%2Fstable-queue.git 6.18-stable patches 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 --- 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 index 0000000000..2c3db7a5cd --- /dev/null +++ b/queue-6.18/serial-8250-add-late-synchronize_irq-to-shutdown-to-handle-dw-uart-busy.patch @@ -0,0 +1,64 @@ +From e0a368ae79531ff92105a2692f10d83052055856 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ilpo=20J=C3=A4rvinen?= +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 + +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 +Reported-by: Bandal, Shankar +Tested-by: Bandal, Shankar +Tested-by: Murthy, Shanth +Reviewed-by: Andy Shevchenko +Signed-off-by: Ilpo Järvinen +Link: https://patch.msgid.link/20260203171049.4353-7-ilpo.jarvinen@linux.intel.com +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..ecf4a1bbca --- /dev/null +++ b/queue-6.18/serial-8250-add-serial8250_handle_irq_locked.patch @@ -0,0 +1,101 @@ +From 8324a54f604da18f21070702a8ad82ab2062787b Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ilpo=20J=C3=A4rvinen?= +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 + +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 +Tested-by: Murthy, Shanth +Cc: stable +Reviewed-by: Andy Shevchenko +Signed-off-by: Ilpo Järvinen +Link: https://patch.msgid.link/20260203171049.4353-4-ilpo.jarvinen@linux.intel.com +Signed-off-by: Greg Kroah-Hartman +--- + 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 + #include + #include ++#include + #include + #include + #include +@@ -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 index 0000000000..8dfa90aff8 --- /dev/null +++ b/queue-6.18/serial-8250-always-disable-irq-during-thre-test.patch @@ -0,0 +1,54 @@ +From 24b98e8664e157aff0814a0f49895ee8223f382f Mon Sep 17 00:00:00 2001 +From: Peng Zhang +Date: Tue, 24 Feb 2026 13:16:39 +0100 +Subject: serial: 8250: always disable IRQ during THRE test + +From: Peng Zhang + +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 +Signed-off-by: Peng Zhang +Reviewed-by: Muchun Song +Signed-off-by: Alban Bedel +Tested-by: Maximilian Lueer +Link: https://patch.msgid.link/20260224121639.579404-1-alban.bedel@lht.dlh.de +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..e52ac32a16 --- /dev/null +++ b/queue-6.18/serial-8250-fix-tx-deadlock-when-using-dma.patch @@ -0,0 +1,54 @@ +From a424a34b8faddf97b5af41689087e7a230f79ba7 Mon Sep 17 00:00:00 2001 +From: Raul E Rangel +Date: Mon, 9 Feb 2026 13:58:18 -0700 +Subject: serial: 8250: Fix TX deadlock when using DMA + +From: Raul E Rangel + +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 +Signed-off-by: Raul E Rangel +Link: https://patch.msgid.link/20260209135815.1.I16366ecb0f62f3c96fe3dd5763fcf6f3c2b4d8cd@changeid +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..9948d6352a --- /dev/null +++ b/queue-6.18/serial-8250-protect-lcr-write-in-shutdown.patch @@ -0,0 +1,61 @@ +From 59a33d83bbe6d73d2071d7ae21590b29faed0503 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ilpo=20J=C3=A4rvinen?= +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 + +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 +Tested-by: Bandal, Shankar +Tested-by: Murthy, Shanth +Cc: stable +Reviewed-by: Andy Shevchenko +Signed-off-by: Ilpo Järvinen +Link: https://patch.msgid.link/20260203171049.4353-2-ilpo.jarvinen@linux.intel.com +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..947f642406 --- /dev/null +++ b/queue-6.18/serial-8250_dw-avoid-unnecessary-lcr-writes.patch @@ -0,0 +1,107 @@ +From 8002d6d6d0d8a36a7d6ca523b17a51cb0fa7c3c3 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ilpo=20J=C3=A4rvinen?= +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 + +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 +Tested-by: Bandal, Shankar +Tested-by: Murthy, Shanth +Cc: stable +Reviewed-by: Andy Shevchenko +Signed-off-by: Ilpo Järvinen +Link: https://patch.msgid.link/20260203171049.4353-3-ilpo.jarvinen@linux.intel.com +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..ce44274f42 --- /dev/null +++ b/queue-6.18/serial-8250_dw-ensure-busy-is-deasserted.patch @@ -0,0 +1,420 @@ +From a7b9ce39fbe4ae2919fe4f7ac16c293cb6632d30 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ilpo=20J=C3=A4rvinen?= +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 + +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 , +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 +Reported-by: qianfan Zhao +Link: https://lore.kernel.org/linux-serial/289bb78a-7509-1c5c-2923-a04ed3b6487d@163.com/ +Reported-by: Adriana Nicolae +Link: https://lore.kernel.org/linux-serial/20250819182322.3451959-1-adriana@arista.com/ +Reported-by: Bandal, Shankar +Tested-by: Bandal, Shankar +Tested-by: Murthy, Shanth +Reviewed-by: Andy Shevchenko +Signed-off-by: Ilpo Järvinen +Link: https://patch.msgid.link/20260203171049.4353-8-ilpo.jarvinen@linux.intel.com +Signed-off-by: Greg Kroah-Hartman +--- + 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 + #include + #include ++#include + #include + #include + #include +@@ -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 index 0000000000..26e3def172 --- /dev/null +++ b/queue-6.18/serial-8250_dw-rework-dw8250_handle_irq-locking-and-iir-handling.patch @@ -0,0 +1,134 @@ +From 883c5a2bc934c165c4491d1ef7da0ac4e9765077 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ilpo=20J=C3=A4rvinen?= +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 + +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 +Tested-by: Murthy, Shanth +Cc: stable +Reviewed-by: Andy Shevchenko +Signed-off-by: Ilpo Järvinen +Link: https://patch.msgid.link/20260203171049.4353-5-ilpo.jarvinen@linux.intel.com +Signed-off-by: Greg Kroah-Hartman +--- + 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 ++#include ++#include + #include + #include + #include +@@ -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 index 0000000000..d46b338784 --- /dev/null +++ b/queue-6.18/serial-8250_dw-rework-iir_no_int-handling-to-stop-interrupt-storm.patch @@ -0,0 +1,191 @@ +From 73a4ed8f9efaaaf8207614ccc1c9d5ca1888f23a Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ilpo=20J=C3=A4rvinen?= +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 + +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 +Reported-by: Bandal, Shankar +Tested-by: Bandal, Shankar +Tested-by: Murthy, Shanth +Reviewed-by: Andy Shevchenko +Signed-off-by: Ilpo Järvinen +Link: https://patch.msgid.link/20260203171049.4353-6-ilpo.jarvinen@linux.intel.com +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..aeed11e729 --- /dev/null +++ b/queue-6.18/serial-8250_pci-add-support-for-the-ax99100.patch @@ -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?= + +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) + +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) +Cc: stable +Link: https://patch.msgid.link/20260309-8250_pci_ax99100-v1-1-3328bdfd8e94@mupuf.org +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..be7f63d171 --- /dev/null +++ b/queue-6.18/serial-core-fix-infinite-loop-in-handle_tx-for-port_unknown.patch @@ -0,0 +1,54 @@ +From 455ce986fa356ff43a43c0d363ba95fa152f21d5 Mon Sep 17 00:00:00 2001 +From: Jiayuan Chen +Date: Wed, 4 Feb 2026 15:43:20 +0800 +Subject: serial: core: fix infinite loop in handle_tx() for PORT_UNKNOWN + +From: Jiayuan Chen + +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 +Cc: stable +Link: https://patch.msgid.link/20260204074327.226165-1-jiayuan.chen@linux.dev +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..113b40450e --- /dev/null +++ b/queue-6.18/serial-uartlite-fix-pm-runtime-usage-count-underflow-on-probe.patch @@ -0,0 +1,53 @@ +From d54801cd509515f674a5aac1d3ea1401d2a05863 Mon Sep 17 00:00:00 2001 +From: Maciej Andrzejewski ICEYE +Date: Thu, 5 Mar 2026 13:37:51 +0100 +Subject: serial: uartlite: fix PM runtime usage count underflow on probe + +From: Maciej Andrzejewski ICEYE + +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 +Signed-off-by: Maciej Andrzejewski ICEYE +Link: https://patch.msgid.link/20260305123746.4152800-1-maciej.andrzejewski@m-works.net +Signed-off-by: Greg Kroah-Hartman +--- + 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); diff --git a/queue-6.18/series b/queue-6.18/series index 0f2b50e07f..0cc97bfb96 100644 --- a/queue-6.18/series +++ b/queue-6.18/series @@ -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 index 0000000000..165d3ef443 --- /dev/null +++ b/queue-6.18/vt-save-restore-unicode-screen-buffer-for-alternate-screen.patch @@ -0,0 +1,76 @@ +From 5eb608319bb56464674a71b4a66ea65c6c435d64 Mon Sep 17 00:00:00 2001 +From: Nicolas Pitre +Date: Tue, 27 Jan 2026 17:56:01 -0500 +Subject: vt: save/restore unicode screen buffer for alternate screen + +From: Nicolas Pitre + +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 +Signed-off-by: Nicolas Pitre +Link: https://patch.msgid.link/5o2p6qp3-91pq-0p17-or02-1oors4417ns7@onlyvoer.pbz +Signed-off-by: Greg Kroah-Hartman +--- + 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 */