Skip to content

Commit

Permalink
serial: 8250: Rate limit serial port rx interrupts during input overruns
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Darwin Dingel authored and Greg Kroah-Hartman committed Dec 17, 2018
1 parent bdb48e4 commit 6d7f677
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 1 deletion.
25 changes: 25 additions & 0 deletions drivers/tty/serial/8250/8250_core.c
Original file line number Diff line number Diff line change
Expand Up @@ -942,6 +942,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
Expand Down Expand Up @@ -1056,6 +1071,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;
Expand Down
23 changes: 22 additions & 1 deletion drivers/tty/serial/8250/8250_fsl.c
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,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);

Expand Down
5 changes: 5 additions & 0 deletions drivers/tty/serial/8250/8250_of.c
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,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;
Expand Down
4 changes: 4 additions & 0 deletions include/linux/serial_8250.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit 6d7f677

Please sign in to comment.