Skip to content

Commit 5909c0b

Browse files
jk-ozlabsgregkh
authored andcommitted
serial/aspeed-vuart: Implement quick throttle mechanism
Although we populate the ->throttle and ->unthrottle UART operations, these may not be called until the ldisc has had a chance to schedule and check buffer space. This means that we may overflow the flip buffers without ever hitting the ldisc's throttle threshold. This change implements an interrupt-based throttle, where we check for space in the flip buffers before reading characters from the UART's FIFO. If there's no space available, we disable the RX interrupt and schedule a timer to check for space later. For this, we need an unlocked version of the set_throttle function to be able to change throttle state from the irq_handler, which already holds port->lock. This prevents a case where we drop characters under heavy RX load. Signed-off-by: Jeremy Kerr <jk@ozlabs.org> Tested-by: Eddie James <eajames@linux.vnet.ibm.com> Tested-by: Joel Stanley <joel@jms.id.au> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
1 parent 989983e commit 5909c0b

File tree

1 file changed

+101
-4
lines changed

1 file changed

+101
-4
lines changed

drivers/tty/serial/8250/8250_aspeed_vuart.c

Lines changed: 101 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
#include <linux/of_address.h>
1111
#include <linux/of_irq.h>
1212
#include <linux/of_platform.h>
13+
#include <linux/tty.h>
14+
#include <linux/tty_flip.h>
1315
#include <linux/clk.h>
1416

1517
#include "8250.h"
@@ -28,8 +30,17 @@ struct aspeed_vuart {
2830
void __iomem *regs;
2931
struct clk *clk;
3032
int line;
33+
struct timer_list unthrottle_timer;
34+
struct uart_8250_port *port;
3135
};
3236

37+
/*
38+
* If we fill the tty flip buffers, we throttle the data ready interrupt
39+
* to prevent dropped characters. This timeout defines how long we wait
40+
* to (conditionally, depending on buffer state) unthrottle.
41+
*/
42+
static const int unthrottle_timeout = HZ/10;
43+
3344
/*
3445
* The VUART is basically two UART 'front ends' connected by their FIFO
3546
* (no actual serial line in between). One is on the BMC side (management
@@ -179,17 +190,23 @@ static void aspeed_vuart_shutdown(struct uart_port *uart_port)
179190
serial8250_do_shutdown(uart_port);
180191
}
181192

182-
static void aspeed_vuart_set_throttle(struct uart_port *port, bool throttle)
193+
static void __aspeed_vuart_set_throttle(struct uart_8250_port *up,
194+
bool throttle)
183195
{
184196
unsigned char irqs = UART_IER_RLSI | UART_IER_RDI;
185-
struct uart_8250_port *up = up_to_u8250p(port);
186-
unsigned long flags;
187197

188-
spin_lock_irqsave(&port->lock, flags);
189198
up->ier &= ~irqs;
190199
if (!throttle)
191200
up->ier |= irqs;
192201
serial_out(up, UART_IER, up->ier);
202+
}
203+
static void aspeed_vuart_set_throttle(struct uart_port *port, bool throttle)
204+
{
205+
struct uart_8250_port *up = up_to_u8250p(port);
206+
unsigned long flags;
207+
208+
spin_lock_irqsave(&port->lock, flags);
209+
__aspeed_vuart_set_throttle(up, throttle);
193210
spin_unlock_irqrestore(&port->lock, flags);
194211
}
195212

@@ -203,6 +220,83 @@ static void aspeed_vuart_unthrottle(struct uart_port *port)
203220
aspeed_vuart_set_throttle(port, false);
204221
}
205222

223+
static void aspeed_vuart_unthrottle_exp(struct timer_list *timer)
224+
{
225+
struct aspeed_vuart *vuart = from_timer(vuart, timer, unthrottle_timer);
226+
struct uart_8250_port *up = vuart->port;
227+
228+
if (!tty_buffer_space_avail(&up->port.state->port)) {
229+
mod_timer(&vuart->unthrottle_timer, unthrottle_timeout);
230+
return;
231+
}
232+
233+
aspeed_vuart_unthrottle(&up->port);
234+
}
235+
236+
/*
237+
* Custom interrupt handler to manage finer-grained flow control. Although we
238+
* have throttle/unthrottle callbacks, we've seen that the VUART device can
239+
* deliver characters faster than the ldisc has a chance to check buffer space
240+
* against the throttle threshold. This results in dropped characters before
241+
* the throttle.
242+
*
243+
* We do this by checking for flip buffer space before RX. If we have no space,
244+
* throttle now and schedule an unthrottle for later, once the ldisc has had
245+
* a chance to drain the buffers.
246+
*/
247+
static int aspeed_vuart_handle_irq(struct uart_port *port)
248+
{
249+
struct uart_8250_port *up = up_to_u8250p(port);
250+
unsigned int iir, lsr;
251+
unsigned long flags;
252+
int space, count;
253+
254+
iir = serial_port_in(port, UART_IIR);
255+
256+
if (iir & UART_IIR_NO_INT)
257+
return 0;
258+
259+
spin_lock_irqsave(&port->lock, flags);
260+
261+
lsr = serial_port_in(port, UART_LSR);
262+
263+
if (lsr & (UART_LSR_DR | UART_LSR_BI)) {
264+
space = tty_buffer_space_avail(&port->state->port);
265+
266+
if (!space) {
267+
/* throttle and schedule an unthrottle later */
268+
struct aspeed_vuart *vuart = port->private_data;
269+
__aspeed_vuart_set_throttle(up, true);
270+
271+
if (!timer_pending(&vuart->unthrottle_timer)) {
272+
vuart->port = up;
273+
mod_timer(&vuart->unthrottle_timer,
274+
unthrottle_timeout);
275+
}
276+
277+
} else {
278+
count = min(space, 256);
279+
280+
do {
281+
serial8250_read_char(up, lsr);
282+
lsr = serial_in(up, UART_LSR);
283+
if (--count == 0)
284+
break;
285+
} while (lsr & (UART_LSR_DR | UART_LSR_BI));
286+
287+
tty_flip_buffer_push(&port->state->port);
288+
}
289+
}
290+
291+
serial8250_modem_status(up);
292+
if (lsr & UART_LSR_THRE)
293+
serial8250_tx_chars(up);
294+
295+
spin_unlock_irqrestore(&port->lock, flags);
296+
297+
return 1;
298+
}
299+
206300
static int aspeed_vuart_probe(struct platform_device *pdev)
207301
{
208302
struct uart_8250_port port;
@@ -219,6 +313,7 @@ static int aspeed_vuart_probe(struct platform_device *pdev)
219313
return -ENOMEM;
220314

221315
vuart->dev = &pdev->dev;
316+
timer_setup(&vuart->unthrottle_timer, aspeed_vuart_unthrottle_exp, 0);
222317

223318
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
224319
vuart->regs = devm_ioremap_resource(&pdev->dev, res);
@@ -280,6 +375,7 @@ static int aspeed_vuart_probe(struct platform_device *pdev)
280375

281376
port.port.irq = irq_of_parse_and_map(np, 0);
282377
port.port.irqflags = IRQF_SHARED;
378+
port.port.handle_irq = aspeed_vuart_handle_irq;
283379
port.port.iotype = UPIO_MEM;
284380
port.port.type = PORT_16550A;
285381
port.port.uartclk = clk;
@@ -319,6 +415,7 @@ static int aspeed_vuart_remove(struct platform_device *pdev)
319415
{
320416
struct aspeed_vuart *vuart = platform_get_drvdata(pdev);
321417

418+
del_timer_sync(&vuart->unthrottle_timer);
322419
aspeed_vuart_set_enabled(vuart, false);
323420
serial8250_unregister_port(vuart->line);
324421
sysfs_remove_group(&vuart->dev->kobj, &aspeed_vuart_attr_group);

0 commit comments

Comments
 (0)