serial: 8250: Rate limit serial port rx interrupts during input overruns
authorDarwin Dingel <darwin.dingel@alliedtelesis.co.nz>
Sun, 9 Dec 2018 22:29:09 +0000 (11:29 +1300)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 5 Dec 2019 14:37:26 +0000 (15:37 +0100)
[ Upstream commit 6d7f677a2afa1c82d7fc7af7f9159cbffd5dc010 ]

When a serial port gets faulty or gets flooded with inputs, its interrupt
handler starts to work double time to get the characters to the workqueue
for the tty layer to handle them. When this busy time on the serial/tty
subsystem happens during boot, where it is also busy on the userspace
trying to initialise, some processes can continuously get preempted
and will be on hold until the interrupts subside.

The fix is to backoff on processing received characters for a specified
amount of time when an input overrun is seen (received a new character
before the previous one is processed). This only stops receive and will
continue to transmit characters to serial port. After the backoff period
is done, it receive will be re-enabled. This is optional and will only
be enabled by setting 'overrun-throttle-ms' in the dts.

Signed-off-by: Darwin Dingel <darwin.dingel@alliedtelesis.co.nz>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Signed-off-by: Sasha Levin <sashal@kernel.org>
drivers/tty/serial/8250/8250_core.c
drivers/tty/serial/8250/8250_fsl.c
drivers/tty/serial/8250/8250_of.c
include/linux/serial_8250.h

index d29b512a7d9fac1b182d1f63a0a34d3c181fc46d..ceeea4b159c4b80685d43cd6e41c7b0cd0810d50 100644 (file)
@@ -953,6 +953,21 @@ static struct uart_8250_port *serial8250_find_match_or_unused(struct uart_port *
        return NULL;
 }
 
+static void serial_8250_overrun_backoff_work(struct work_struct *work)
+{
+       struct uart_8250_port *up =
+           container_of(to_delayed_work(work), struct uart_8250_port,
+                        overrun_backoff);
+       struct uart_port *port = &up->port;
+       unsigned long flags;
+
+       spin_lock_irqsave(&port->lock, flags);
+       up->ier |= UART_IER_RLSI | UART_IER_RDI;
+       up->port.read_status_mask |= UART_LSR_DR;
+       serial_out(up, UART_IER, up->ier);
+       spin_unlock_irqrestore(&port->lock, flags);
+}
+
 /**
  *     serial8250_register_8250_port - register a serial port
  *     @up: serial port template
@@ -1063,6 +1078,16 @@ int serial8250_register_8250_port(struct uart_8250_port *up)
                        ret = 0;
                }
        }
+
+       /* Initialise interrupt backoff work if required */
+       if (up->overrun_backoff_time_ms > 0) {
+               uart->overrun_backoff_time_ms = up->overrun_backoff_time_ms;
+               INIT_DELAYED_WORK(&uart->overrun_backoff,
+                                 serial_8250_overrun_backoff_work);
+       } else {
+               uart->overrun_backoff_time_ms = 0;
+       }
+
        mutex_unlock(&serial_mutex);
 
        return ret;
index 910bfee5a88b7f07f5a4c00aad47a45361a1d19b..cc138c24ae889a28abc48e8a2d3c58434a747080 100644 (file)
@@ -48,8 +48,29 @@ int fsl8250_handle_irq(struct uart_port *port)
 
        lsr = orig_lsr = up->port.serial_in(&up->port, UART_LSR);
 
-       if (lsr & (UART_LSR_DR | UART_LSR_BI))
+       /* Process incoming characters first */
+       if ((lsr & (UART_LSR_DR | UART_LSR_BI)) &&
+           (up->ier & (UART_IER_RLSI | UART_IER_RDI))) {
                lsr = serial8250_rx_chars(up, lsr);
+       }
+
+       /* Stop processing interrupts on input overrun */
+       if ((orig_lsr & UART_LSR_OE) && (up->overrun_backoff_time_ms > 0)) {
+               unsigned long delay;
+
+               up->ier = port->serial_in(port, UART_IER);
+               if (up->ier & (UART_IER_RLSI | UART_IER_RDI)) {
+                       port->ops->stop_rx(port);
+               } else {
+                       /* Keep restarting the timer until
+                        * the input overrun subsides.
+                        */
+                       cancel_delayed_work(&up->overrun_backoff);
+               }
+
+               delay = msecs_to_jiffies(up->overrun_backoff_time_ms);
+               schedule_delayed_work(&up->overrun_backoff, delay);
+       }
 
        serial8250_modem_status(up);
 
index ec510e342e06cf86eb5ebb118263cac5a0c58de2..c51044ba503c393460c1adc27becf19eedbdef00 100644 (file)
@@ -232,6 +232,11 @@ static int of_platform_serial_probe(struct platform_device *ofdev)
        if (of_property_read_bool(ofdev->dev.of_node, "auto-flow-control"))
                port8250.capabilities |= UART_CAP_AFE;
 
+       if (of_property_read_u32(ofdev->dev.of_node,
+                       "overrun-throttle-ms",
+                       &port8250.overrun_backoff_time_ms) != 0)
+               port8250.overrun_backoff_time_ms = 0;
+
        ret = serial8250_register_8250_port(&port8250);
        if (ret < 0)
                goto err_dispose;
index a27ef5f56431783f516e039944b8cf4d75c898e3..791a6be0e3949000a196566b507c85be0670ff03 100644 (file)
@@ -134,6 +134,10 @@ struct uart_8250_port {
        void                    (*dl_write)(struct uart_8250_port *, int);
 
        struct uart_8250_em485 *em485;
+
+       /* Serial port overrun backoff */
+       struct delayed_work overrun_backoff;
+       u32 overrun_backoff_time_ms;
 };
 
 static inline struct uart_8250_port *up_to_u8250p(struct uart_port *up)