Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

tty: Implement lookahead to process XON/XOFF timely

When tty is not read from, XON/XOFF may get stuck into an
intermediate buffer. As those characters are there to do software
flow-control, it is not very useful. In the case where neither end
reads from ttys, the receiving ends might not be able receive the
XOFF characters and just keep sending more data to the opposite
direction. This problem is almost guaranteed to occur with DMA
which sends data in large chunks.

If TTY is slow to process characters, that is, eats less than given
amount in receive_buf, invoke lookahead for the rest of the chars
to process potential XON/XOFF characters.

We need to keep track of how many characters have been processed by the
lookahead to avoid processing the flow control char again on the normal
path. Bookkeeping occurs parallel on two layers (tty_buffer and n_tty)
to avoid passing the lookahead_count through the whole call chain.

When a flow-control char is processed, two things must occur:
a) it must not be treated as normal char
b) if not yet processed, flow-control actions need to be taken
The return value of n_tty_receive_char_flow_ctrl() tells caller a), and
b) is kept internal to n_tty_receive_char_flow_ctrl().

If characters were previous looked ahead, __receive_buf() makes two
calls to the appropriate n_tty_receive_buf_* function. First call is
made with lookahead_done=true for the characters that were subject to
lookahead earlier and then with lookahead=false for the new characters.
Either of the calls might be skipped when it has no characters to
handle.

Reported-by: Gilles Buloz <gilles.buloz@kontron.com>
Reviewed-by: Andy Shevchenko <andy.shevchenko@gmail.com>
Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
Link: https://lore.kernel.org/r/20220606153652.63554-2-ilpo.jarvinen@linux.intel.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

authored by

Ilpo Järvinen and committed by
Greg Kroah-Hartman
6bb6fa69 84f2faa7

+163 -25
+75 -16
drivers/tty/n_tty.c
··· 118 118 size_t read_tail; 119 119 size_t line_start; 120 120 121 + /* # of chars looked ahead (to find software flow control chars) */ 122 + size_t lookahead_count; 123 + 121 124 /* protected by output lock */ 122 125 unsigned int column; 123 126 unsigned int canon_column; ··· 336 333 ldata->erasing = 0; 337 334 bitmap_zero(ldata->read_flags, N_TTY_BUF_SIZE); 338 335 ldata->push = 0; 336 + 337 + ldata->lookahead_count = 0; 339 338 } 340 339 341 340 static void n_tty_packet_mode_flush(struct tty_struct *tty) ··· 1230 1225 return c == START_CHAR(tty) || c == STOP_CHAR(tty); 1231 1226 } 1232 1227 1233 - /* Returns true if c is consumed as flow-control character */ 1234 - static bool n_tty_receive_char_flow_ctrl(struct tty_struct *tty, unsigned char c) 1228 + /** 1229 + * n_tty_receive_char_flow_ctrl - receive flow control chars 1230 + * @tty: terminal device 1231 + * @c: character 1232 + * @lookahead_done: lookahead has processed this character already 1233 + * 1234 + * Receive and process flow control character actions. 1235 + * 1236 + * In case lookahead for flow control chars already handled the character in 1237 + * advance to the normal receive, the actions are skipped during normal 1238 + * receive. 1239 + * 1240 + * Returns true if @c is consumed as flow-control character, the character 1241 + * must not be treated as normal character. 1242 + */ 1243 + static bool n_tty_receive_char_flow_ctrl(struct tty_struct *tty, unsigned char c, 1244 + bool lookahead_done) 1235 1245 { 1236 1246 if (!n_tty_is_char_flow_ctrl(tty, c)) 1237 1247 return false; 1248 + 1249 + if (lookahead_done) 1250 + return true; 1238 1251 1239 1252 if (c == START_CHAR(tty)) { 1240 1253 start_tty(tty); ··· 1265 1242 return true; 1266 1243 } 1267 1244 1268 - static void n_tty_receive_char_special(struct tty_struct *tty, unsigned char c) 1245 + static void n_tty_receive_char_special(struct tty_struct *tty, unsigned char c, 1246 + bool lookahead_done) 1269 1247 { 1270 1248 struct n_tty_data *ldata = tty->disc_data; 1271 1249 1272 - if (I_IXON(tty) && n_tty_receive_char_flow_ctrl(tty, c)) 1250 + if (I_IXON(tty) && n_tty_receive_char_flow_ctrl(tty, c, lookahead_done)) 1273 1251 return; 1274 1252 1275 1253 if (L_ISIG(tty)) { ··· 1425 1401 put_tty_queue(c, ldata); 1426 1402 } 1427 1403 1428 - static void n_tty_receive_char_closing(struct tty_struct *tty, unsigned char c) 1404 + static void n_tty_receive_char_closing(struct tty_struct *tty, unsigned char c, 1405 + bool lookahead_done) 1429 1406 { 1430 1407 if (I_ISTRIP(tty)) 1431 1408 c &= 0x7f; ··· 1434 1409 c = tolower(c); 1435 1410 1436 1411 if (I_IXON(tty)) { 1437 - if (c == STOP_CHAR(tty)) 1438 - stop_tty(tty); 1439 - else if (c == START_CHAR(tty) || 1412 + if (c == STOP_CHAR(tty)) { 1413 + if (!lookahead_done) 1414 + stop_tty(tty); 1415 + } else if (c == START_CHAR(tty) && lookahead_done) { 1416 + return; 1417 + } else if (c == START_CHAR(tty) || 1440 1418 (tty->flow.stopped && !tty->flow.tco_stopped && I_IXANY(tty) && 1441 1419 c != INTR_CHAR(tty) && c != QUIT_CHAR(tty) && 1442 1420 c != SUSP_CHAR(tty))) { ··· 1485 1457 n_tty_receive_char_flagged(tty, c, flag); 1486 1458 } 1487 1459 1460 + /* Caller must ensure count > 0 */ 1461 + static void n_tty_lookahead_flow_ctrl(struct tty_struct *tty, const unsigned char *cp, 1462 + const unsigned char *fp, unsigned int count) 1463 + { 1464 + struct n_tty_data *ldata = tty->disc_data; 1465 + unsigned char flag = TTY_NORMAL; 1466 + 1467 + ldata->lookahead_count += count; 1468 + 1469 + if (!I_IXON(tty)) 1470 + return; 1471 + 1472 + while (count--) { 1473 + if (fp) 1474 + flag = *fp++; 1475 + if (likely(flag == TTY_NORMAL)) 1476 + n_tty_receive_char_flow_ctrl(tty, *cp, false); 1477 + cp++; 1478 + } 1479 + } 1480 + 1488 1481 static void 1489 1482 n_tty_receive_buf_real_raw(struct tty_struct *tty, const unsigned char *cp, 1490 1483 const char *fp, int count) ··· 1545 1496 1546 1497 static void 1547 1498 n_tty_receive_buf_closing(struct tty_struct *tty, const unsigned char *cp, 1548 - const char *fp, int count) 1499 + const char *fp, int count, bool lookahead_done) 1549 1500 { 1550 1501 char flag = TTY_NORMAL; 1551 1502 ··· 1553 1504 if (fp) 1554 1505 flag = *fp++; 1555 1506 if (likely(flag == TTY_NORMAL)) 1556 - n_tty_receive_char_closing(tty, *cp++); 1507 + n_tty_receive_char_closing(tty, *cp++, lookahead_done); 1557 1508 } 1558 1509 } 1559 1510 1560 1511 static void n_tty_receive_buf_standard(struct tty_struct *tty, 1561 - const unsigned char *cp, const char *fp, int count) 1512 + const unsigned char *cp, const char *fp, int count, bool lookahead_done) 1562 1513 { 1563 1514 struct n_tty_data *ldata = tty->disc_data; 1564 1515 char flag = TTY_NORMAL; ··· 1589 1540 } 1590 1541 1591 1542 if (test_bit(c, ldata->char_map)) 1592 - n_tty_receive_char_special(tty, c); 1543 + n_tty_receive_char_special(tty, c, lookahead_done); 1593 1544 else 1594 1545 n_tty_receive_char(tty, c); 1595 1546 } ··· 1600 1551 { 1601 1552 struct n_tty_data *ldata = tty->disc_data; 1602 1553 bool preops = I_ISTRIP(tty) || (I_IUCLC(tty) && L_IEXTEN(tty)); 1554 + size_t la_count = min_t(size_t, ldata->lookahead_count, count); 1603 1555 1604 1556 if (ldata->real_raw) 1605 1557 n_tty_receive_buf_real_raw(tty, cp, fp, count); 1606 1558 else if (ldata->raw || (L_EXTPROC(tty) && !preops)) 1607 1559 n_tty_receive_buf_raw(tty, cp, fp, count); 1608 - else if (tty->closing && !L_EXTPROC(tty)) 1609 - n_tty_receive_buf_closing(tty, cp, fp, count); 1610 - else { 1611 - n_tty_receive_buf_standard(tty, cp, fp, count); 1560 + else if (tty->closing && !L_EXTPROC(tty)) { 1561 + if (la_count > 0) 1562 + n_tty_receive_buf_closing(tty, cp, fp, la_count, true); 1563 + if (count > la_count) 1564 + n_tty_receive_buf_closing(tty, cp, fp, count - la_count, false); 1565 + } else { 1566 + if (la_count > 0) 1567 + n_tty_receive_buf_standard(tty, cp, fp, la_count, true); 1568 + if (count > la_count) 1569 + n_tty_receive_buf_standard(tty, cp, fp, count - la_count, false); 1612 1570 1613 1571 flush_echoes(tty); 1614 1572 if (tty->ops->flush_chars) 1615 1573 tty->ops->flush_chars(tty); 1616 1574 } 1575 + 1576 + ldata->lookahead_count -= la_count; 1617 1577 1618 1578 if (ldata->icanon && !L_EXTPROC(tty)) 1619 1579 return; ··· 2504 2446 .receive_buf = n_tty_receive_buf, 2505 2447 .write_wakeup = n_tty_write_wakeup, 2506 2448 .receive_buf2 = n_tty_receive_buf2, 2449 + .lookahead_buf = n_tty_lookahead_flow_ctrl, 2507 2450 }; 2508 2451 2509 2452 /**
+50 -9
drivers/tty/tty_buffer.c
··· 5 5 6 6 #include <linux/types.h> 7 7 #include <linux/errno.h> 8 + #include <linux/minmax.h> 8 9 #include <linux/tty.h> 9 10 #include <linux/tty_driver.h> 10 11 #include <linux/tty_flip.h> ··· 105 104 p->size = size; 106 105 p->next = NULL; 107 106 p->commit = 0; 107 + p->lookahead = 0; 108 108 p->read = 0; 109 109 p->flags = 0; 110 110 } ··· 236 234 buf->head = next; 237 235 } 238 236 buf->head->read = buf->head->commit; 237 + buf->head->lookahead = buf->head->read; 239 238 240 239 if (ld && ld->ops->flush_buffer) 241 240 ld->ops->flush_buffer(tty); ··· 279 276 if (n != NULL) { 280 277 n->flags = flags; 281 278 buf->tail = n; 282 - /* paired w/ acquire in flush_to_ldisc(); ensures 283 - * flush_to_ldisc() sees buffer data. 279 + /* 280 + * Paired w/ acquire in flush_to_ldisc() and lookahead_bufs() 281 + * ensures they see all buffer data. 284 282 */ 285 283 smp_store_release(&b->commit, b->used); 286 - /* paired w/ acquire in flush_to_ldisc(); ensures the 287 - * latest commit value can be read before the head is 288 - * advanced to the next buffer 284 + /* 285 + * Paired w/ acquire in flush_to_ldisc() and lookahead_bufs() 286 + * ensures the latest commit value can be read before the head 287 + * is advanced to the next buffer. 289 288 */ 290 289 smp_store_release(&b->next, n); 291 290 } else if (change) ··· 464 459 } 465 460 EXPORT_SYMBOL_GPL(tty_ldisc_receive_buf); 466 461 462 + static void lookahead_bufs(struct tty_port *port, struct tty_buffer *head) 463 + { 464 + head->lookahead = max(head->lookahead, head->read); 465 + 466 + while (head) { 467 + struct tty_buffer *next; 468 + unsigned char *p, *f = NULL; 469 + unsigned int count; 470 + 471 + /* 472 + * Paired w/ release in __tty_buffer_request_room(); 473 + * ensures commit value read is not stale if the head 474 + * is advancing to the next buffer. 475 + */ 476 + next = smp_load_acquire(&head->next); 477 + /* 478 + * Paired w/ release in __tty_buffer_request_room() or in 479 + * tty_buffer_flush(); ensures we see the committed buffer data. 480 + */ 481 + count = smp_load_acquire(&head->commit) - head->lookahead; 482 + if (!count) { 483 + head = next; 484 + continue; 485 + } 486 + 487 + p = char_buf_ptr(head, head->lookahead); 488 + if (~head->flags & TTYB_NORMAL) 489 + f = flag_buf_ptr(head, head->lookahead); 490 + 491 + port->client_ops->lookahead_buf(port, p, f, count); 492 + head->lookahead += count; 493 + } 494 + } 495 + 467 496 static int 468 497 receive_buf(struct tty_port *port, struct tty_buffer *head, int count) 469 498 { ··· 535 496 while (1) { 536 497 struct tty_buffer *head = buf->head; 537 498 struct tty_buffer *next; 538 - int count; 499 + int count, rcvd; 539 500 540 501 /* Ldisc or user is trying to gain exclusive access */ 541 502 if (atomic_read(&buf->priority)) ··· 558 519 continue; 559 520 } 560 521 561 - count = receive_buf(port, head, count); 562 - if (!count) 522 + rcvd = receive_buf(port, head, count); 523 + head->read += rcvd; 524 + if (rcvd < count) 525 + lookahead_bufs(port, head); 526 + if (!rcvd) 563 527 break; 564 - head->read += count; 565 528 566 529 if (need_resched()) 567 530 cond_resched();
+21
drivers/tty/tty_port.c
··· 43 43 return ret; 44 44 } 45 45 46 + static void tty_port_default_lookahead_buf(struct tty_port *port, const unsigned char *p, 47 + const unsigned char *f, unsigned int count) 48 + { 49 + struct tty_struct *tty; 50 + struct tty_ldisc *disc; 51 + 52 + tty = READ_ONCE(port->itty); 53 + if (!tty) 54 + return; 55 + 56 + disc = tty_ldisc_ref(tty); 57 + if (!disc) 58 + return; 59 + 60 + if (disc->ops->lookahead_buf) 61 + disc->ops->lookahead_buf(disc->tty, p, f, count); 62 + 63 + tty_ldisc_deref(disc); 64 + } 65 + 46 66 static void tty_port_default_wakeup(struct tty_port *port) 47 67 { 48 68 struct tty_struct *tty = tty_port_tty_get(port); ··· 75 55 76 56 const struct tty_port_client_operations tty_port_default_client_ops = { 77 57 .receive_buf = tty_port_default_receive_buf, 58 + .lookahead_buf = tty_port_default_lookahead_buf, 78 59 .write_wakeup = tty_port_default_wakeup, 79 60 }; 80 61 EXPORT_SYMBOL_GPL(tty_port_default_client_ops);
+1
include/linux/tty_buffer.h
··· 15 15 int used; 16 16 int size; 17 17 int commit; 18 + int lookahead; /* Lazy update on recv, can become less than "read" */ 18 19 int read; 19 20 int flags; 20 21 /* Data points here */
+14
include/linux/tty_ldisc.h
··· 186 186 * indicate all data received is %TTY_NORMAL. If assigned, prefer this 187 187 * function for automatic flow control. 188 188 * 189 + * @lookahead_buf: [DRV] ``void ()(struct tty_struct *tty, 190 + * const unsigned char *cp, const char *fp, int count) 191 + * 192 + * This function is called by the low-level tty driver for characters 193 + * not eaten by ->receive_buf() or ->receive_buf2(). It is useful for 194 + * processing high-priority characters such as software flow-control 195 + * characters that could otherwise get stuck into the intermediate 196 + * buffer until tty has room to receive them. Ldisc must be able to 197 + * handle later a ->receive_buf() or ->receive_buf2() call for the 198 + * same characters (e.g. by skipping the actions for high-priority 199 + * characters already handled by ->lookahead_buf()). 200 + * 189 201 * @owner: module containting this ldisc (for reference counting) 190 202 * 191 203 * This structure defines the interface between the tty line discipline ··· 241 229 void (*dcd_change)(struct tty_struct *tty, unsigned int status); 242 230 int (*receive_buf2)(struct tty_struct *tty, const unsigned char *cp, 243 231 const char *fp, int count); 232 + void (*lookahead_buf)(struct tty_struct *tty, const unsigned char *cp, 233 + const unsigned char *fp, unsigned int count); 244 234 245 235 struct module *owner; 246 236 };
+2
include/linux/tty_port.h
··· 40 40 41 41 struct tty_port_client_operations { 42 42 int (*receive_buf)(struct tty_port *port, const unsigned char *, const unsigned char *, size_t); 43 + void (*lookahead_buf)(struct tty_port *port, const unsigned char *cp, 44 + const unsigned char *fp, unsigned int count); 43 45 void (*write_wakeup)(struct tty_port *port); 44 46 }; 45 47