diff options
author | Jeremy Kerr <jk@ozlabs.org> | 2018-03-26 23:48:27 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2018-04-23 04:16:50 -0400 |
commit | 5909c0bf9c7a17c52cf357bf5e752a76b8d72568 (patch) | |
tree | 3ccbc3fa01f81d6d08d55eee2a6cb578fefcd24c /drivers/tty | |
parent | 989983ea849d9421e8b9cd11f18e072fe00ea0d7 (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.c | 105 |
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 | */ | ||
42 | static 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 | ||
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) | ||
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 | } | ||
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); | ||
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 | ||
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 | |||
206 | static int aspeed_vuart_probe(struct platform_device *pdev) | 300 | static 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); |