Skip to content
/ linux Public

Commit 5dfd8c7

Browse files
ij-intelgregkh
authored andcommitted
serial: 8250_dw: Rework IIR_NO_INT handling to stop interrupt storm
commit 73a4ed8 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: e92fad0 ("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>
1 parent 2d21617 commit 5dfd8c7

File tree

1 file changed

+63
-4
lines changed

1 file changed

+63
-4
lines changed

drivers/tty/serial/8250/8250_dw.c

Lines changed: 63 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,13 @@
6161
#define DW_UART_QUIRK_IS_DMA_FC BIT(3)
6262
#define DW_UART_QUIRK_APMC0D08 BIT(4)
6363
#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
6471

6572
struct dw8250_platform_data {
6673
u8 usr_reg;
@@ -82,6 +89,8 @@ struct dw8250_data {
8289

8390
unsigned int skip_autocfg:1;
8491
unsigned int uart_16550_compatible:1;
92+
93+
u8 no_int_count;
8594
};
8695

8796
static inline struct dw8250_data *to_dw8250_data(struct dw8250_port_data *data)
@@ -308,6 +317,29 @@ static u32 dw8250_serial_in32be(struct uart_port *p, unsigned int offset)
308317
return dw8250_modify_msr(p, offset, value);
309318
}
310319

320+
/*
321+
* INTC10EE UART can IRQ storm while reporting IIR_NO_INT. Inducing IIR value
322+
* change has been observed to break the storm.
323+
*
324+
* If Tx is empty (THRE asserted), we use here IER_THRI to cause IIR_NO_INT ->
325+
* IIR_THRI transition.
326+
*/
327+
static void dw8250_quirk_ier_kick(struct uart_port *p)
328+
{
329+
struct uart_8250_port *up = up_to_u8250p(p);
330+
u32 lsr;
331+
332+
if (up->ier & UART_IER_THRI)
333+
return;
334+
335+
lsr = serial_lsr_in(up);
336+
if (!(lsr & UART_LSR_THRE))
337+
return;
338+
339+
serial_port_out(p, UART_IER, up->ier | UART_IER_THRI);
340+
serial_port_in(p, UART_LCR); /* safe, no side-effects */
341+
serial_port_out(p, UART_IER, up->ier);
342+
}
311343

312344
static int dw8250_handle_irq(struct uart_port *p)
313345
{
@@ -318,18 +350,30 @@ static int dw8250_handle_irq(struct uart_port *p)
318350
unsigned int quirks = d->pdata->quirks;
319351
unsigned int status;
320352

353+
guard(uart_port_lock_irqsave)(p);
354+
321355
switch (FIELD_GET(DW_UART_IIR_IID, iir)) {
322356
case UART_IIR_NO_INT:
357+
if (d->uart_16550_compatible || up->dma)
358+
return 0;
359+
360+
if (quirks & DW_UART_QUIRK_IER_KICK &&
361+
d->no_int_count == (DW_UART_QUIRK_IER_KICK_THRES - 1))
362+
dw8250_quirk_ier_kick(p);
363+
d->no_int_count = (d->no_int_count + 1) % DW_UART_QUIRK_IER_KICK_THRES;
364+
323365
return 0;
324366

325367
case UART_IIR_BUSY:
326368
/* Clear the USR */
327369
serial_port_in(p, d->pdata->usr_reg);
328370

371+
d->no_int_count = 0;
372+
329373
return 1;
330374
}
331375

332-
guard(uart_port_lock_irqsave)(p);
376+
d->no_int_count = 0;
333377

334378
/*
335379
* There are ways to get Designware-based UARTs into a state where
@@ -562,6 +606,14 @@ static void dw8250_reset_control_assert(void *data)
562606
reset_control_assert(data);
563607
}
564608

609+
static void dw8250_shutdown(struct uart_port *port)
610+
{
611+
struct dw8250_data *d = to_dw8250_data(port->private_data);
612+
613+
serial8250_do_shutdown(port);
614+
d->no_int_count = 0;
615+
}
616+
565617
static int dw8250_probe(struct platform_device *pdev)
566618
{
567619
struct uart_8250_port uart = {}, *up = &uart;
@@ -685,10 +737,12 @@ static int dw8250_probe(struct platform_device *pdev)
685737
dw8250_quirks(p, data);
686738

687739
/* If the Busy Functionality is not implemented, don't handle it */
688-
if (data->uart_16550_compatible)
740+
if (data->uart_16550_compatible) {
689741
p->handle_irq = NULL;
690-
else if (data->pdata)
742+
} else if (data->pdata) {
691743
p->handle_irq = dw8250_handle_irq;
744+
p->shutdown = dw8250_shutdown;
745+
}
692746

693747
dw8250_setup_dma_filter(p, data);
694748

@@ -822,6 +876,11 @@ static const struct dw8250_platform_data dw8250_skip_set_rate_data = {
822876
.quirks = DW_UART_QUIRK_SKIP_SET_RATE,
823877
};
824878

879+
static const struct dw8250_platform_data dw8250_intc10ee = {
880+
.usr_reg = DW_UART_USR,
881+
.quirks = DW_UART_QUIRK_IER_KICK,
882+
};
883+
825884
static const struct of_device_id dw8250_of_match[] = {
826885
{ .compatible = "snps,dw-apb-uart", .data = &dw8250_dw_apb },
827886
{ .compatible = "cavium,octeon-3860-uart", .data = &dw8250_octeon_3860_data },
@@ -851,7 +910,7 @@ static const struct acpi_device_id dw8250_acpi_match[] = {
851910
{ "INT33C5", (kernel_ulong_t)&dw8250_dw_apb },
852911
{ "INT3434", (kernel_ulong_t)&dw8250_dw_apb },
853912
{ "INT3435", (kernel_ulong_t)&dw8250_dw_apb },
854-
{ "INTC10EE", (kernel_ulong_t)&dw8250_dw_apb },
913+
{ "INTC10EE", (kernel_ulong_t)&dw8250_intc10ee },
855914
{ },
856915
};
857916
MODULE_DEVICE_TABLE(acpi, dw8250_acpi_match);

0 commit comments

Comments
 (0)