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_CUSTOM_WORKQUEUE flag.

Introduce %TTY_DRIVER_CUSTOM_WORKQUEUE flag meaning not to create the
default single tty_driver workqueue. Two reasons why need to introduce the
%TTY_DRIVER_CUSTOM_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.

Signed-off-by: Xin Zhao <jackzxcui1989@163.com>
Reviewed-by: Jiri Slaby <jirislaby@kernel.org>
Link: https://patch.msgid.link/20251223034836.2625547-1-jackzxcui1989@163.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

authored by

Xin Zhao and committed by
Greg Kroah-Hartman
d000422a 3f0716c6

+78 -9
+10 -4
drivers/tty/pty.c
··· 403 403 o_tty->link = tty; 404 404 tty_port_init(ports[0]); 405 405 tty_port_init(ports[1]); 406 + tty_port_link_wq(ports[0], system_dfl_wq); 407 + tty_port_link_wq(ports[1], system_dfl_wq); 406 408 tty_buffer_set_limit(ports[0], 8192); 407 409 tty_buffer_set_limit(ports[1], 8192); 408 410 o_tty->port = ports[0]; ··· 534 532 pty_driver = tty_alloc_driver(legacy_count, 535 533 TTY_DRIVER_RESET_TERMIOS | 536 534 TTY_DRIVER_REAL_RAW | 537 - TTY_DRIVER_DYNAMIC_ALLOC); 535 + TTY_DRIVER_DYNAMIC_ALLOC | 536 + TTY_DRIVER_CUSTOM_WORKQUEUE); 538 537 if (IS_ERR(pty_driver)) 539 538 panic("Couldn't allocate pty driver"); 540 539 541 540 pty_slave_driver = tty_alloc_driver(legacy_count, 542 541 TTY_DRIVER_RESET_TERMIOS | 543 542 TTY_DRIVER_REAL_RAW | 544 - TTY_DRIVER_DYNAMIC_ALLOC); 543 + TTY_DRIVER_DYNAMIC_ALLOC | 544 + TTY_DRIVER_CUSTOM_WORKQUEUE); 545 545 if (IS_ERR(pty_slave_driver)) 546 546 panic("Couldn't allocate pty slave driver"); 547 547 ··· 853 849 TTY_DRIVER_REAL_RAW | 854 850 TTY_DRIVER_DYNAMIC_DEV | 855 851 TTY_DRIVER_DEVPTS_MEM | 856 - TTY_DRIVER_DYNAMIC_ALLOC); 852 + TTY_DRIVER_DYNAMIC_ALLOC | 853 + TTY_DRIVER_CUSTOM_WORKQUEUE); 857 854 if (IS_ERR(ptm_driver)) 858 855 panic("Couldn't allocate Unix98 ptm driver"); 859 856 pts_driver = tty_alloc_driver(NR_UNIX98_PTY_MAX, ··· 862 857 TTY_DRIVER_REAL_RAW | 863 858 TTY_DRIVER_DYNAMIC_DEV | 864 859 TTY_DRIVER_DEVPTS_MEM | 865 - TTY_DRIVER_DYNAMIC_ALLOC); 860 + TTY_DRIVER_DYNAMIC_ALLOC | 861 + TTY_DRIVER_CUSTOM_WORKQUEUE); 866 862 if (IS_ERR(pts_driver)) 867 863 panic("Couldn't allocate Unix98 pts driver"); 868 864
+4 -4
drivers/tty/tty_buffer.c
··· 76 76 mutex_unlock(&buf->lock); 77 77 78 78 if (restart) 79 - queue_work(system_dfl_wq, &buf->work); 79 + queue_work(buf->flip_wq, &buf->work); 80 80 } 81 81 EXPORT_SYMBOL_GPL(tty_buffer_unlock_exclusive); 82 82 ··· 530 530 struct tty_bufhead *buf = &port->buf; 531 531 532 532 tty_flip_buffer_commit(buf->tail); 533 - queue_work(system_dfl_wq, &buf->work); 533 + queue_work(buf->flip_wq, &buf->work); 534 534 } 535 535 EXPORT_SYMBOL(tty_flip_buffer_push); 536 536 ··· 560 560 tty_flip_buffer_commit(buf->tail); 561 561 spin_unlock_irqrestore(&port->lock, flags); 562 562 563 - queue_work(system_dfl_wq, &buf->work); 563 + queue_work(buf->flip_wq, &buf->work); 564 564 565 565 return size; 566 566 } ··· 613 613 614 614 bool tty_buffer_restart_work(struct tty_port *port) 615 615 { 616 - return queue_work(system_dfl_wq, &port->buf.work); 616 + return queue_work(port->buf.flip_wq, &port->buf.work); 617 617 } 618 618 619 619 bool tty_buffer_cancel_work(struct tty_port *port)
+20 -1
drivers/tty/tty_io.c
··· 3446 3446 if (error < 0) 3447 3447 goto err; 3448 3448 3449 + if (!(driver->flags & TTY_DRIVER_CUSTOM_WORKQUEUE)) { 3450 + driver->flip_wq = alloc_workqueue("%s-flip-wq", WQ_UNBOUND | WQ_SYSFS, 3451 + 0, driver->name); 3452 + if (!driver->flip_wq) { 3453 + error = -ENOMEM; 3454 + goto err_unreg_char; 3455 + } 3456 + for (i = 0; i < driver->num; i++) { 3457 + if (driver->ports[i]) 3458 + tty_port_link_driver_wq(driver->ports[i], driver); 3459 + } 3460 + } 3461 + 3449 3462 if (driver->flags & TTY_DRIVER_DYNAMIC_ALLOC) { 3450 3463 error = tty_cdev_add(driver, dev, 0, driver->num); 3451 3464 if (error) 3452 - goto err_unreg_char; 3465 + goto err_destroy_wq; 3453 3466 } 3454 3467 3455 3468 scoped_guard(mutex, &tty_mutex) ··· 3488 3475 scoped_guard(mutex, &tty_mutex) 3489 3476 list_del(&driver->tty_drivers); 3490 3477 3478 + err_destroy_wq: 3479 + if (!(driver->flags & TTY_DRIVER_CUSTOM_WORKQUEUE)) 3480 + destroy_workqueue(driver->flip_wq); 3481 + 3491 3482 err_unreg_char: 3492 3483 unregister_chrdev_region(dev, driver->num); 3493 3484 err: ··· 3511 3494 driver->num); 3512 3495 scoped_guard(mutex, &tty_mutex) 3513 3496 list_del(&driver->tty_drivers); 3497 + if (!(driver->flags & TTY_DRIVER_CUSTOM_WORKQUEUE)) 3498 + destroy_workqueue(driver->flip_wq); 3514 3499 } 3515 3500 EXPORT_SYMBOL(tty_unregister_driver); 3516 3501
+23
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 + * When %TTY_DRIVER_CUSTOM_WORKQUEUE is used, every tty_port shall be linked to 108 + * a workqueue manually by this function, otherwise tty_flip_buffer_push() will 109 + * see %NULL flip_wq pointer on queue_work. 110 + * When %TTY_DRIVER_CUSTOM_WORKQUEUE is NOT used, the function can be used to 111 + * link a certain port to a specific workqueue, instead of using the workqueue 112 + * allocated in tty_register_driver(). 113 + * 114 + * Note that TTY port API will NOT destroy the workqueue. 115 + */ 116 + void tty_port_link_wq(struct tty_port *port, struct workqueue_struct *flip_wq) 117 + { 118 + port->buf.flip_wq = flip_wq; 119 + } 120 + EXPORT_SYMBOL_GPL(tty_port_link_wq); 121 + 122 + /** 103 123 * tty_port_link_device - link tty and tty_port 104 124 * @port: tty_port of the device 105 125 * @driver: tty_driver for this device ··· 177 157 const struct attribute_group **attr_grp) 178 158 { 179 159 tty_port_link_device(port, driver, index); 160 + tty_port_link_driver_wq(port, driver); 180 161 return tty_register_device_attr(driver, index, device, drvdata, 181 162 attr_grp); 182 163 } ··· 204 183 struct device *dev; 205 184 206 185 tty_port_link_device(port, driver, index); 186 + tty_port_link_driver_wq(port, driver); 207 187 208 188 dev = serdev_tty_port_register(port, host, parent, driver, index); 209 189 if (PTR_ERR(dev) != -ENODEV) { ··· 725 703 struct tty_struct *tty) 726 704 { 727 705 tty->port = port; 706 + tty_port_link_driver_wq(port, driver); 728 707 return tty_standard_install(driver, tty); 729 708 } 730 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_CUSTOM_WORKQUEUE: 74 + * Do not create workqueue when tty_register_driver(). When set, flip 75 + * buffer workqueue shall 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_CUSTOM_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_CUSTOM_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 + port->buf.flip_wq = driver->flip_wq; 167 178 } 168 179 169 180 /* If the cts flow control is enabled, return true. */