summaryrefslogtreecommitdiffstats
path: root/drivers/tty
diff options
context:
space:
mode:
authorJeremy Kerr <jk@ozlabs.org>2018-03-26 23:48:27 -0400
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2018-04-23 04:16:50 -0400
commit5909c0bf9c7a17c52cf357bf5e752a76b8d72568 (patch)
tree3ccbc3fa01f81d6d08d55eee2a6cb578fefcd24c /drivers/tty
parent989983ea849d9421e8b9cd11f18e072fe00ea0d7 (diff)
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>
Diffstat (limited to 'drivers/tty')
-rw-r--r--drivers/tty/serial/8250/8250_aspeed_vuart.c105
1 files changed, 101 insertions, 4 deletions
diff --git a/drivers/tty/serial/8250/8250_aspeed_vuart.c b/drivers/tty/serial/8250/8250_aspeed_vuart.c
index cd1bb49dadfe..023db3266757 100644
--- a/drivers/tty/serial/8250/8250_aspeed_vuart.c
+++ b/drivers/tty/serial/8250/8250_aspeed_vuart.c
@@ -10,6 +10,8 @@
10#include <linux/of_address.h> 10#include <linux/of_address.h>
11#include <linux/of_irq.h> 11#include <linux/of_irq.h>
12#include <linux/of_platform.h> 12#include <linux/of_platform.h>
13#include <linux/tty.h>
14#include <linux/tty_flip.h>
13#include <linux/clk.h> 15#include <linux/clk.h>
14 16
15#include "8250.h" 17#include "8250.h"
@@ -28,9 +30,18 @@ struct aspeed_vuart {
28 void __iomem *regs; 30 void __iomem *regs;
29 struct clk *clk; 31 struct clk *clk;
30 int line; 32 int line;
33 struct timer_list unthrottle_timer;
34 struct uart_8250_port *port;
31}; 35};
32 36
33/* 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 */
42static const int unthrottle_timeout = HZ/10;
43
44/*
34 * The VUART is basically two UART 'front ends' connected by their FIFO 45 * The VUART is basically two UART 'front ends' connected by their FIFO
35 * (no actual serial line in between). One is on the BMC side (management 46 * (no actual serial line in between). One is on the BMC side (management
36 * controller) and one is on the host CPU side. 47 * controller) and one is on the host CPU side.
@@ -179,17 +190,23 @@ static void aspeed_vuart_shutdown(struct uart_port *uart_port)
179 serial8250_do_shutdown(uart_port); 190 serial8250_do_shutdown(uart_port);
180} 191}
181 192
182static void aspeed_vuart_set_throttle(struct uart_port *port, bool throttle) 193static void __aspeed_vuart_set_throttle(struct uart_8250_port *up,
194 bool throttle)
183{ 195{
184 unsigned char irqs = UART_IER_RLSI | UART_IER_RDI; 196 unsigned char irqs = UART_IER_RLSI | UART_IER_RDI;
185 struct uart_8250_port *up = up_to_u8250p(port);
186 unsigned long flags;
187 197
188 spin_lock_irqsave(&port->lock, flags);
189 up->ier &= ~irqs; 198 up->ier &= ~irqs;
190 if (!throttle) 199 if (!throttle)
191 up->ier |= irqs; 200 up->ier |= irqs;
192 serial_out(up, UART_IER, up->ier); 201 serial_out(up, UART_IER, up->ier);
202}
203static 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);
193 spin_unlock_irqrestore(&port->lock, flags); 210 spin_unlock_irqrestore(&port->lock, flags);
194} 211}
195 212
@@ -203,6 +220,83 @@ static void aspeed_vuart_unthrottle(struct uart_port *port)
203 aspeed_vuart_set_throttle(port, false); 220 aspeed_vuart_set_throttle(port, false);
204} 221}
205 222
223static 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 */
247static 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
206static int aspeed_vuart_probe(struct platform_device *pdev) 300static int aspeed_vuart_probe(struct platform_device *pdev)
207{ 301{
208 struct uart_8250_port port; 302 struct uart_8250_port port;
@@ -219,6 +313,7 @@ static int aspeed_vuart_probe(struct platform_device *pdev)
219 return -ENOMEM; 313 return -ENOMEM;
220 314
221 vuart->dev = &pdev->dev; 315 vuart->dev = &pdev->dev;
316 timer_setup(&vuart->unthrottle_timer, aspeed_vuart_unthrottle_exp, 0);
222 317
223 res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 318 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
224 vuart->regs = devm_ioremap_resource(&pdev->dev, res); 319 vuart->regs = devm_ioremap_resource(&pdev->dev, res);
@@ -280,6 +375,7 @@ static int aspeed_vuart_probe(struct platform_device *pdev)
280 375
281 port.port.irq = irq_of_parse_and_map(np, 0); 376 port.port.irq = irq_of_parse_and_map(np, 0);
282 port.port.irqflags = IRQF_SHARED; 377 port.port.irqflags = IRQF_SHARED;
378 port.port.handle_irq = aspeed_vuart_handle_irq;
283 port.port.iotype = UPIO_MEM; 379 port.port.iotype = UPIO_MEM;
284 port.port.type = PORT_16550A; 380 port.port.type = PORT_16550A;
285 port.port.uartclk = clk; 381 port.port.uartclk = clk;
@@ -319,6 +415,7 @@ static int aspeed_vuart_remove(struct platform_device *pdev)
319{ 415{
320 struct aspeed_vuart *vuart = platform_get_drvdata(pdev); 416 struct aspeed_vuart *vuart = platform_get_drvdata(pdev);
321 417
418 del_timer_sync(&vuart->unthrottle_timer);
322 aspeed_vuart_set_enabled(vuart, false); 419 aspeed_vuart_set_enabled(vuart, false);
323 serial8250_unregister_port(vuart->line); 420 serial8250_unregister_port(vuart->line);
324 sysfs_remove_group(&vuart->dev->kobj, &aspeed_vuart_attr_group); 421 sysfs_remove_group(&vuart->dev->kobj, &aspeed_vuart_attr_group);