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

Configure Feed

Select the types of activity you want to include in your feed.

tty: tty_port: add workqueue to flip TTY buffer

On the embedded platform, certain critical data, such as IMU data, is
transmitted through UART. The tty_flip_buffer_push() interface in the TTY
layer uses system_dfl_wq to handle the flipping of the TTY buffer.
Although the unbound workqueue can create new threads on demand and wake
up the kworker thread on an idle CPU, it may be preempted by real-time
tasks or other high-prio tasks.

flush_to_ldisc() needs to wake up the relevant data handle thread. When
executing __wake_up_common_lock(), it calls spin_lock_irqsave(), which
does not disable preemption but disables migration in RT-Linux. This
prevents the kworker thread from being migrated to other cores by CPU's
balancing logic, resulting in long delays. The call trace is as follows:
__wake_up_common_lock
__wake_up
ep_poll_callback
__wake_up_common
__wake_up_common_lock
__wake_up
n_tty_receive_buf_common
n_tty_receive_buf2
tty_ldisc_receive_buf
tty_port_default_receive_buf
flush_to_ldisc

In our system, the processing interval for each frame of IMU data
transmitted via UART can experience significant jitter due to this issue.
Instead of the expected 10 to 15 ms frame processing interval, we see
spikes up to 30 to 35 ms. Moreover, in just one or two hours, there can
be 2 to 3 occurrences of such high jitter, which is quite frequent. This
jitter exceeds the software's tolerable limit of 20 ms.

Introduce flip_wq in tty_port which can be set by tty_port_link_wq() or as
default linked to default workqueue allocated when tty_register_driver().
The default workqueue is allocated with flag WQ_SYSFS, so that cpumask and
nice can be set dynamically. The execution timing of tty_port_link_wq() is
not clearly restricted. The newly added function tty_port_link_driver_wq()
checks whether the flip_wq of the tty_port has already been assigned when
linking the default tty_driver's workqueue to the port. After the user has
set a custom workqueue for a certain tty_port using tty_port_link_wq(), the
system will only use this custom workqueue, even if tty_driver does not
have %TTY_DRIVER_NO_WORKQUEUE flag. When tty_port register device, flip_wq
link operation is done by tty_port_link_driver_wq(), but for in-memory
devices the link operation cannot cover all the cases. Although
tty_port_install() is dedicated for in-memory devices lik PTY to link port
allocated on demand, the logic of tty_port_install() is so simple that
people may not call it, vc_cons[0].d->port is one such case. We check the
buf.flip_wq when flip TTY buffer, if buf.flip_wq of TTY port is NULL, use
system_dfl_wq as a backup.

To avoid naming conflict of the default tty_driver's workqueue, using
'"%s-%s", driver->name, driver->driver_name' as the workqueue name. In
cases where driver_name is not specified and therefore is NULL, the
workqueue is not created. Drivers that do not define driver_name are
potentially in-memory devices like vty, which generally do not require
special workqueue settings. Even with the combination of name and
driver_name, the workqueue names can still be duplicated, as many tty
serial drivers use "ttyS" as dev_name and "serial" as driver_name. I
modified the conflicting driver_name of these drivers by appending a
suffix of _xx based on the corresponding .c file. If this modification is
not made, it could not only lead to duplicate workqueue names but also
result in duplicate entries for the /proc/tty/driver/<driver_name> nodes.

Introduce %TTY_DRIVER_NO_WORKQUEUE flag meaning not to create the
default single tty_driver workqueue. Two reasons why need to introduce the
%TTY_DRIVER_NO_WORKQUEUE flag:
1. If the WQ_SYSFS parameter is enabled, workqueue_sysfs_register() will
fail when trying to create a workqueue with the same name. The pty is an
example of this; if both CONFIG_LEGACY_PTYS and CONFIG_UNIX98_PTYS are
enabled, the call to tty_register_driver() in unix98_pty_init() will fail.
2. Different TTY ports may be used for different tasks, which may require
separate core binding control via workqueues. In this case, the workqueue
created by default in the TTY driver is unnecessary. Enabling this flag
prevents the creation of this redundant workqueue.

After applying this patch, we can set the related UART TTY flip buffer
workqueue by sysfs. We set the cpumask to CPU cores associated with the
IMU tasks, and set the nice to -20. Testing has shown significant
improvement in the previously described issue, with almost no stuttering
occurring anymore.

Tested-by: Tommaso Merciai <tommaso.merciai.xr@bp.renesas.com>
Tested-by: Marek Szyprowski <m.szyprowski@samsung.com>
Signed-off-by: Xin Zhao <jackzxcui1989@163.com>
Link: https://patch.msgid.link/20260213085039.3274704-1-jackzxcui1989@163.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

authored by

Xin Zhao and committed by
Greg Kroah-Hartman
eb3b0d92 fa4268cf

+91 -14
+8 -4
drivers/tty/pty.c
··· 532 532 pty_driver = tty_alloc_driver(legacy_count, 533 533 TTY_DRIVER_RESET_TERMIOS | 534 534 TTY_DRIVER_REAL_RAW | 535 - TTY_DRIVER_DYNAMIC_ALLOC); 535 + TTY_DRIVER_DYNAMIC_ALLOC | 536 + TTY_DRIVER_NO_WORKQUEUE); 536 537 if (IS_ERR(pty_driver)) 537 538 panic("Couldn't allocate pty driver"); 538 539 539 540 pty_slave_driver = tty_alloc_driver(legacy_count, 540 541 TTY_DRIVER_RESET_TERMIOS | 541 542 TTY_DRIVER_REAL_RAW | 542 - TTY_DRIVER_DYNAMIC_ALLOC); 543 + TTY_DRIVER_DYNAMIC_ALLOC | 544 + TTY_DRIVER_NO_WORKQUEUE); 543 545 if (IS_ERR(pty_slave_driver)) 544 546 panic("Couldn't allocate pty slave driver"); 545 547 ··· 851 849 TTY_DRIVER_REAL_RAW | 852 850 TTY_DRIVER_DYNAMIC_DEV | 853 851 TTY_DRIVER_DEVPTS_MEM | 854 - TTY_DRIVER_DYNAMIC_ALLOC); 852 + TTY_DRIVER_DYNAMIC_ALLOC | 853 + TTY_DRIVER_NO_WORKQUEUE); 855 854 if (IS_ERR(ptm_driver)) 856 855 panic("Couldn't allocate Unix98 ptm driver"); 857 856 pts_driver = tty_alloc_driver(NR_UNIX98_PTY_MAX, ··· 860 857 TTY_DRIVER_REAL_RAW | 861 858 TTY_DRIVER_DYNAMIC_DEV | 862 859 TTY_DRIVER_DEVPTS_MEM | 863 - TTY_DRIVER_DYNAMIC_ALLOC); 860 + TTY_DRIVER_DYNAMIC_ALLOC | 861 + TTY_DRIVER_NO_WORKQUEUE); 864 862 if (IS_ERR(pts_driver)) 865 863 panic("Couldn't allocate Unix98 pts driver"); 866 864
+1 -1
drivers/tty/serial/8250/8250_core.c
··· 524 524 525 525 struct uart_driver serial8250_reg = { 526 526 .owner = THIS_MODULE, 527 - .driver_name = "serial", 527 + .driver_name = "serial_8250", 528 528 .dev_name = "ttyS", 529 529 .major = TTY_MAJOR, 530 530 .minor = 64,
+1 -1
drivers/tty/serial/apbuart.c
··· 505 505 506 506 static struct uart_driver grlib_apbuart_driver = { 507 507 .owner = THIS_MODULE, 508 - .driver_name = "serial", 508 + .driver_name = "serial_apbuart", 509 509 .dev_name = "ttyS", 510 510 .major = SERIAL_APBUART_MAJOR, 511 511 .minor = SERIAL_APBUART_MINOR,
+1 -1
drivers/tty/serial/dz.c
··· 914 914 915 915 static struct uart_driver dz_reg = { 916 916 .owner = THIS_MODULE, 917 - .driver_name = "serial", 917 + .driver_name = "serial_dz", 918 918 .dev_name = "ttyS", 919 919 .major = TTY_MAJOR, 920 920 .minor = 64,
+1 -1
drivers/tty/serial/ip22zilog.c
··· 1015 1015 1016 1016 static struct uart_driver ip22zilog_reg = { 1017 1017 .owner = THIS_MODULE, 1018 - .driver_name = "serial", 1018 + .driver_name = "serial_ip22zilog", 1019 1019 .dev_name = "ttyS", 1020 1020 .major = TTY_MAJOR, 1021 1021 .minor = 64,
+1 -1
drivers/tty/serial/zs.c
··· 1252 1252 1253 1253 static struct uart_driver zs_reg = { 1254 1254 .owner = THIS_MODULE, 1255 - .driver_name = "serial", 1255 + .driver_name = "serial_zs", 1256 1256 .dev_name = "ttyS", 1257 1257 .major = TTY_MAJOR, 1258 1258 .minor = 64,
+11 -4
drivers/tty/tty_buffer.c
··· 59 59 } 60 60 EXPORT_SYMBOL_GPL(tty_buffer_lock_exclusive); 61 61 62 + static bool tty_buffer_queue_work(struct tty_bufhead *buf) 63 + { 64 + struct workqueue_struct *flip_wq = READ_ONCE(buf->flip_wq); 65 + 66 + return queue_work(flip_wq ?: system_dfl_wq, &buf->work); 67 + } 68 + 62 69 /** 63 70 * tty_buffer_unlock_exclusive - release exclusive access 64 71 * @port: tty port owning the flip buffer ··· 83 76 mutex_unlock(&buf->lock); 84 77 85 78 if (restart) 86 - queue_work(system_dfl_wq, &buf->work); 79 + tty_buffer_queue_work(buf); 87 80 } 88 81 EXPORT_SYMBOL_GPL(tty_buffer_unlock_exclusive); 89 82 ··· 537 530 struct tty_bufhead *buf = &port->buf; 538 531 539 532 tty_flip_buffer_commit(buf->tail); 540 - queue_work(system_dfl_wq, &buf->work); 533 + tty_buffer_queue_work(buf); 541 534 } 542 535 EXPORT_SYMBOL(tty_flip_buffer_push); 543 536 ··· 567 560 tty_flip_buffer_commit(buf->tail); 568 561 spin_unlock_irqrestore(&port->lock, flags); 569 562 570 - queue_work(system_dfl_wq, &buf->work); 563 + tty_buffer_queue_work(buf); 571 564 572 565 return size; 573 566 } ··· 620 613 621 614 bool tty_buffer_restart_work(struct tty_port *port) 622 615 { 623 - return queue_work(system_dfl_wq, &port->buf.work); 616 + return tty_buffer_queue_work(&port->buf); 624 617 } 625 618 626 619 bool tty_buffer_cancel_work(struct tty_port *port)
+24 -1
drivers/tty/tty_io.c
··· 3443 3443 if (error < 0) 3444 3444 goto err; 3445 3445 3446 + /* 3447 + * Drivers that do not define driver_name are potentially in-memory devices 3448 + * like vty, which generally do not require special workqueue settings. 3449 + */ 3450 + if (!(driver->flags & TTY_DRIVER_NO_WORKQUEUE) && driver->driver_name) { 3451 + driver->flip_wq = alloc_workqueue("%s-%s", WQ_UNBOUND | WQ_SYSFS, 3452 + 0, driver->name, driver->driver_name); 3453 + if (!driver->flip_wq) { 3454 + error = -ENOMEM; 3455 + goto err_unreg_char; 3456 + } 3457 + for (i = 0; i < driver->num; i++) { 3458 + if (driver->ports[i]) 3459 + tty_port_link_driver_wq(driver->ports[i], driver); 3460 + } 3461 + } 3462 + 3446 3463 if (driver->flags & TTY_DRIVER_DYNAMIC_ALLOC) { 3447 3464 error = tty_cdev_add(driver, dev, 0, driver->num); 3448 3465 if (error) 3449 - goto err_unreg_char; 3466 + goto err_destroy_wq; 3450 3467 } 3451 3468 3452 3469 scoped_guard(mutex, &tty_mutex) ··· 3489 3472 scoped_guard(mutex, &tty_mutex) 3490 3473 list_del(&driver->tty_drivers); 3491 3474 3475 + err_destroy_wq: 3476 + if (driver->flip_wq) 3477 + destroy_workqueue(driver->flip_wq); 3478 + 3492 3479 err_unreg_char: 3493 3480 unregister_chrdev_region(dev, driver->num); 3494 3481 err: ··· 3512 3491 driver->num); 3513 3492 scoped_guard(mutex, &tty_mutex) 3514 3493 list_del(&driver->tty_drivers); 3494 + if (driver->flip_wq) 3495 + destroy_workqueue(driver->flip_wq); 3515 3496 } 3516 3497 EXPORT_SYMBOL(tty_unregister_driver); 3517 3498
+22
drivers/tty/tty_port.c
··· 100 100 EXPORT_SYMBOL(tty_port_init); 101 101 102 102 /** 103 + * tty_port_link_wq - link tty_port and flip workqueue 104 + * @port: tty_port of the device 105 + * @flip_wq: workqueue to queue flip buffer work on 106 + * 107 + * Whenever %TTY_DRIVER_NO_WORKQUEUE is used, every tty_port can be linked to 108 + * a workqueue manually by this function. 109 + * tty_port will use system_dfl_wq when buf.flip_wq is NULL. 110 + * 111 + * Note that tty_port API will NOT destroy the workqueue. 112 + */ 113 + void tty_port_link_wq(struct tty_port *port, struct workqueue_struct *flip_wq) 114 + { 115 + port->buf.flip_wq = flip_wq; 116 + } 117 + EXPORT_SYMBOL_GPL(tty_port_link_wq); 118 + 119 + /** 103 120 * tty_port_link_device - link tty and tty_port 104 121 * @port: tty_port of the device 105 122 * @driver: tty_driver for this device ··· 174 157 const struct attribute_group **attr_grp) 175 158 { 176 159 tty_port_link_device(port, driver, index); 160 + tty_port_link_driver_wq(port, driver); 177 161 return tty_register_device_attr(driver, index, device, drvdata, 178 162 attr_grp); 179 163 } ··· 201 183 struct device *dev; 202 184 203 185 tty_port_link_device(port, driver, index); 186 + tty_port_link_driver_wq(port, driver); 204 187 205 188 dev = serdev_tty_port_register(port, host, parent, driver, index); 206 189 if (PTR_ERR(dev) != -ENODEV) { ··· 229 210 { 230 211 int ret; 231 212 213 + WRITE_ONCE(port->buf.flip_wq, NULL); 232 214 ret = serdev_tty_port_unregister(port); 233 215 if (ret == 0) 234 216 return; ··· 277 257 { 278 258 tty_buffer_cancel_work(port); 279 259 tty_buffer_free_all(port); 260 + WRITE_ONCE(port->buf.flip_wq, NULL); 280 261 } 281 262 EXPORT_SYMBOL(tty_port_destroy); 282 263 ··· 724 703 struct tty_struct *tty) 725 704 { 726 705 tty->port = port; 706 + tty_port_link_driver_wq(port, driver); 727 707 return tty_standard_install(driver, tty); 728 708 } 729 709 EXPORT_SYMBOL_GPL(tty_port_install);
+1
include/linux/tty_buffer.h
··· 34 34 35 35 struct tty_bufhead { 36 36 struct tty_buffer *head; /* Queue head */ 37 + struct workqueue_struct *flip_wq; 37 38 struct work_struct work; 38 39 struct mutex lock; 39 40 atomic_t priority;
+7
include/linux/tty_driver.h
··· 69 69 * Do not create numbered ``/dev`` nodes. For example, create 70 70 * ``/dev/ttyprintk`` and not ``/dev/ttyprintk0``. Applicable only when a 71 71 * driver for a single tty device is being allocated. 72 + * 73 + * @TTY_DRIVER_NO_WORKQUEUE: 74 + * Do not create workqueue when tty_register_driver(). Whenever set, flip 75 + * buffer workqueue can be set by tty_port_link_wq() for every port. 72 76 */ 73 77 enum tty_driver_flag { 74 78 TTY_DRIVER_INSTALLED = BIT(0), ··· 83 79 TTY_DRIVER_HARDWARE_BREAK = BIT(5), 84 80 TTY_DRIVER_DYNAMIC_ALLOC = BIT(6), 85 81 TTY_DRIVER_UNNUMBERED_NODE = BIT(7), 82 + TTY_DRIVER_NO_WORKQUEUE = BIT(8), 86 83 }; 87 84 88 85 enum tty_driver_type { ··· 511 506 * @flags: tty driver flags (%TTY_DRIVER_) 512 507 * @proc_entry: proc fs entry, used internally 513 508 * @other: driver of the linked tty; only used for the PTY driver 509 + * @flip_wq: workqueue to queue flip buffer work on 514 510 * @ttys: array of active &struct tty_struct, set by tty_standard_install() 515 511 * @ports: array of &struct tty_port; can be set during initialization by 516 512 * tty_port_link_device() and similar ··· 545 539 unsigned long flags; 546 540 struct proc_dir_entry *proc_entry; 547 541 struct tty_driver *other; 542 + struct workqueue_struct *flip_wq; 548 543 549 544 /* 550 545 * Pointer to the tty data structures
+13
include/linux/tty_port.h
··· 138 138 kernel */ 139 139 140 140 void tty_port_init(struct tty_port *port); 141 + void tty_port_link_wq(struct tty_port *port, struct workqueue_struct *flip_wq); 141 142 void tty_port_link_device(struct tty_port *port, struct tty_driver *driver, 142 143 unsigned index); 143 144 struct device *tty_port_register_device(struct tty_port *port, ··· 164 163 if (port && kref_get_unless_zero(&port->kref)) 165 164 return port; 166 165 return NULL; 166 + } 167 + 168 + /* 169 + * Never overwrite the workqueue set by tty_port_link_wq(). 170 + * No effect when %TTY_DRIVER_NO_WORKQUEUE is set, as driver->flip_wq is 171 + * %NULL. 172 + */ 173 + static inline void tty_port_link_driver_wq(struct tty_port *port, 174 + struct tty_driver *driver) 175 + { 176 + if (!port->buf.flip_wq) 177 + tty_port_link_wq(port, driver->flip_wq); 167 178 } 168 179 169 180 /* If the cts flow control is enabled, return true. */