Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

serial: 8250_dw: Rework IIR_NO_INT handling to stop interrupt storm

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>

authored by

Ilpo Järvinen and committed by
Greg Kroah-Hartman
73a4ed8f 883c5a2b

+63 -4
+63 -4
drivers/tty/serial/8250/8250_dw.c
··· 61 61 #define DW_UART_QUIRK_IS_DMA_FC BIT(3) 62 62 #define DW_UART_QUIRK_APMC0D08 BIT(4) 63 63 #define DW_UART_QUIRK_CPR_VALUE BIT(5) 64 + #define DW_UART_QUIRK_IER_KICK BIT(6) 65 + 66 + /* 67 + * Number of consecutive IIR_NO_INT interrupts required to trigger interrupt 68 + * storm prevention code. 69 + */ 70 + #define DW_UART_QUIRK_IER_KICK_THRES 4 64 71 65 72 struct dw8250_platform_data { 66 73 u8 usr_reg; ··· 89 82 90 83 unsigned int skip_autocfg:1; 91 84 unsigned int uart_16550_compatible:1; 85 + 86 + u8 no_int_count; 92 87 }; 93 88 94 89 static inline struct dw8250_data *to_dw8250_data(struct dw8250_port_data *data) ··· 317 308 return dw8250_modify_msr(p, offset, value); 318 309 } 319 310 311 + /* 312 + * INTC10EE UART can IRQ storm while reporting IIR_NO_INT. Inducing IIR value 313 + * change has been observed to break the storm. 314 + * 315 + * If Tx is empty (THRE asserted), we use here IER_THRI to cause IIR_NO_INT -> 316 + * IIR_THRI transition. 317 + */ 318 + static void dw8250_quirk_ier_kick(struct uart_port *p) 319 + { 320 + struct uart_8250_port *up = up_to_u8250p(p); 321 + u32 lsr; 322 + 323 + if (up->ier & UART_IER_THRI) 324 + return; 325 + 326 + lsr = serial_lsr_in(up); 327 + if (!(lsr & UART_LSR_THRE)) 328 + return; 329 + 330 + serial_port_out(p, UART_IER, up->ier | UART_IER_THRI); 331 + serial_port_in(p, UART_LCR); /* safe, no side-effects */ 332 + serial_port_out(p, UART_IER, up->ier); 333 + } 320 334 321 335 static int dw8250_handle_irq(struct uart_port *p) 322 336 { ··· 350 318 unsigned int quirks = d->pdata->quirks; 351 319 unsigned int status; 352 320 321 + guard(uart_port_lock_irqsave)(p); 322 + 353 323 switch (FIELD_GET(DW_UART_IIR_IID, iir)) { 354 324 case UART_IIR_NO_INT: 325 + if (d->uart_16550_compatible || up->dma) 326 + return 0; 327 + 328 + if (quirks & DW_UART_QUIRK_IER_KICK && 329 + d->no_int_count == (DW_UART_QUIRK_IER_KICK_THRES - 1)) 330 + dw8250_quirk_ier_kick(p); 331 + d->no_int_count = (d->no_int_count + 1) % DW_UART_QUIRK_IER_KICK_THRES; 332 + 355 333 return 0; 356 334 357 335 case UART_IIR_BUSY: 358 336 /* Clear the USR */ 359 337 serial_port_in(p, d->pdata->usr_reg); 360 338 339 + d->no_int_count = 0; 340 + 361 341 return 1; 362 342 } 363 343 364 - guard(uart_port_lock_irqsave)(p); 344 + d->no_int_count = 0; 365 345 366 346 /* 367 347 * There are ways to get Designware-based UARTs into a state where ··· 606 562 reset_control_assert(data); 607 563 } 608 564 565 + static void dw8250_shutdown(struct uart_port *port) 566 + { 567 + struct dw8250_data *d = to_dw8250_data(port->private_data); 568 + 569 + serial8250_do_shutdown(port); 570 + d->no_int_count = 0; 571 + } 572 + 609 573 static int dw8250_probe(struct platform_device *pdev) 610 574 { 611 575 struct uart_8250_port uart = {}, *up = &uart; ··· 741 689 dw8250_quirks(p, data); 742 690 743 691 /* If the Busy Functionality is not implemented, don't handle it */ 744 - if (data->uart_16550_compatible) 692 + if (data->uart_16550_compatible) { 745 693 p->handle_irq = NULL; 746 - else if (data->pdata) 694 + } else if (data->pdata) { 747 695 p->handle_irq = dw8250_handle_irq; 696 + p->shutdown = dw8250_shutdown; 697 + } 748 698 749 699 dw8250_setup_dma_filter(p, data); 750 700 ··· 878 824 .quirks = DW_UART_QUIRK_SKIP_SET_RATE, 879 825 }; 880 826 827 + static const struct dw8250_platform_data dw8250_intc10ee = { 828 + .usr_reg = DW_UART_USR, 829 + .quirks = DW_UART_QUIRK_IER_KICK, 830 + }; 831 + 881 832 static const struct of_device_id dw8250_of_match[] = { 882 833 { .compatible = "snps,dw-apb-uart", .data = &dw8250_dw_apb }, 883 834 { .compatible = "cavium,octeon-3860-uart", .data = &dw8250_octeon_3860_data }, ··· 912 853 { "INT33C5", (kernel_ulong_t)&dw8250_dw_apb }, 913 854 { "INT3434", (kernel_ulong_t)&dw8250_dw_apb }, 914 855 { "INT3435", (kernel_ulong_t)&dw8250_dw_apb }, 915 - { "INTC10EE", (kernel_ulong_t)&dw8250_dw_apb }, 856 + { "INTC10EE", (kernel_ulong_t)&dw8250_intc10ee }, 916 857 { }, 917 858 }; 918 859 MODULE_DEVICE_TABLE(acpi, dw8250_acpi_match);