Skip to content

Commit d000422

Browse files
jackzxcui1989gregkh
authored andcommitted
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>
1 parent 3f0716c commit d000422

7 files changed

Lines changed: 78 additions & 9 deletions

File tree

drivers/tty/pty.c

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,8 @@ static int pty_common_install(struct tty_driver *driver, struct tty_struct *tty,
403403
o_tty->link = tty;
404404
tty_port_init(ports[0]);
405405
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);
406408
tty_buffer_set_limit(ports[0], 8192);
407409
tty_buffer_set_limit(ports[1], 8192);
408410
o_tty->port = ports[0];
@@ -532,14 +534,16 @@ static void __init legacy_pty_init(void)
532534
pty_driver = tty_alloc_driver(legacy_count,
533535
TTY_DRIVER_RESET_TERMIOS |
534536
TTY_DRIVER_REAL_RAW |
535-
TTY_DRIVER_DYNAMIC_ALLOC);
537+
TTY_DRIVER_DYNAMIC_ALLOC |
538+
TTY_DRIVER_CUSTOM_WORKQUEUE);
536539
if (IS_ERR(pty_driver))
537540
panic("Couldn't allocate pty driver");
538541

539542
pty_slave_driver = tty_alloc_driver(legacy_count,
540543
TTY_DRIVER_RESET_TERMIOS |
541544
TTY_DRIVER_REAL_RAW |
542-
TTY_DRIVER_DYNAMIC_ALLOC);
545+
TTY_DRIVER_DYNAMIC_ALLOC |
546+
TTY_DRIVER_CUSTOM_WORKQUEUE);
543547
if (IS_ERR(pty_slave_driver))
544548
panic("Couldn't allocate pty slave driver");
545549

@@ -849,15 +853,17 @@ static void __init unix98_pty_init(void)
849853
TTY_DRIVER_REAL_RAW |
850854
TTY_DRIVER_DYNAMIC_DEV |
851855
TTY_DRIVER_DEVPTS_MEM |
852-
TTY_DRIVER_DYNAMIC_ALLOC);
856+
TTY_DRIVER_DYNAMIC_ALLOC |
857+
TTY_DRIVER_CUSTOM_WORKQUEUE);
853858
if (IS_ERR(ptm_driver))
854859
panic("Couldn't allocate Unix98 ptm driver");
855860
pts_driver = tty_alloc_driver(NR_UNIX98_PTY_MAX,
856861
TTY_DRIVER_RESET_TERMIOS |
857862
TTY_DRIVER_REAL_RAW |
858863
TTY_DRIVER_DYNAMIC_DEV |
859864
TTY_DRIVER_DEVPTS_MEM |
860-
TTY_DRIVER_DYNAMIC_ALLOC);
865+
TTY_DRIVER_DYNAMIC_ALLOC |
866+
TTY_DRIVER_CUSTOM_WORKQUEUE);
861867
if (IS_ERR(pts_driver))
862868
panic("Couldn't allocate Unix98 pts driver");
863869

drivers/tty/tty_buffer.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ void tty_buffer_unlock_exclusive(struct tty_port *port)
7676
mutex_unlock(&buf->lock);
7777

7878
if (restart)
79-
queue_work(system_dfl_wq, &buf->work);
79+
queue_work(buf->flip_wq, &buf->work);
8080
}
8181
EXPORT_SYMBOL_GPL(tty_buffer_unlock_exclusive);
8282

@@ -530,7 +530,7 @@ void tty_flip_buffer_push(struct tty_port *port)
530530
struct tty_bufhead *buf = &port->buf;
531531

532532
tty_flip_buffer_commit(buf->tail);
533-
queue_work(system_dfl_wq, &buf->work);
533+
queue_work(buf->flip_wq, &buf->work);
534534
}
535535
EXPORT_SYMBOL(tty_flip_buffer_push);
536536

@@ -560,7 +560,7 @@ int tty_insert_flip_string_and_push_buffer(struct tty_port *port,
560560
tty_flip_buffer_commit(buf->tail);
561561
spin_unlock_irqrestore(&port->lock, flags);
562562

563-
queue_work(system_dfl_wq, &buf->work);
563+
queue_work(buf->flip_wq, &buf->work);
564564

565565
return size;
566566
}
@@ -613,7 +613,7 @@ void tty_buffer_set_lock_subclass(struct tty_port *port)
613613

614614
bool tty_buffer_restart_work(struct tty_port *port)
615615
{
616-
return queue_work(system_dfl_wq, &port->buf.work);
616+
return queue_work(port->buf.flip_wq, &port->buf.work);
617617
}
618618

619619
bool tty_buffer_cancel_work(struct tty_port *port)

drivers/tty/tty_io.c

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3446,10 +3446,23 @@ int tty_register_driver(struct tty_driver *driver)
34463446
if (error < 0)
34473447
goto err;
34483448

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+
34493462
if (driver->flags & TTY_DRIVER_DYNAMIC_ALLOC) {
34503463
error = tty_cdev_add(driver, dev, 0, driver->num);
34513464
if (error)
3452-
goto err_unreg_char;
3465+
goto err_destroy_wq;
34533466
}
34543467

34553468
scoped_guard(mutex, &tty_mutex)
@@ -3475,6 +3488,10 @@ int tty_register_driver(struct tty_driver *driver)
34753488
scoped_guard(mutex, &tty_mutex)
34763489
list_del(&driver->tty_drivers);
34773490

3491+
err_destroy_wq:
3492+
if (!(driver->flags & TTY_DRIVER_CUSTOM_WORKQUEUE))
3493+
destroy_workqueue(driver->flip_wq);
3494+
34783495
err_unreg_char:
34793496
unregister_chrdev_region(dev, driver->num);
34803497
err:
@@ -3494,6 +3511,8 @@ void tty_unregister_driver(struct tty_driver *driver)
34943511
driver->num);
34953512
scoped_guard(mutex, &tty_mutex)
34963513
list_del(&driver->tty_drivers);
3514+
if (!(driver->flags & TTY_DRIVER_CUSTOM_WORKQUEUE))
3515+
destroy_workqueue(driver->flip_wq);
34973516
}
34983517
EXPORT_SYMBOL(tty_unregister_driver);
34993518

drivers/tty/tty_port.c

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,26 @@ void tty_port_init(struct tty_port *port)
9999
}
100100
EXPORT_SYMBOL(tty_port_init);
101101

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+
102122
/**
103123
* tty_port_link_device - link tty and tty_port
104124
* @port: tty_port of the device
@@ -157,6 +177,7 @@ struct device *tty_port_register_device_attr(struct tty_port *port,
157177
const struct attribute_group **attr_grp)
158178
{
159179
tty_port_link_device(port, driver, index);
180+
tty_port_link_driver_wq(port, driver);
160181
return tty_register_device_attr(driver, index, device, drvdata,
161182
attr_grp);
162183
}
@@ -183,6 +204,7 @@ struct device *tty_port_register_device_attr_serdev(struct tty_port *port,
183204
struct device *dev;
184205

185206
tty_port_link_device(port, driver, index);
207+
tty_port_link_driver_wq(port, driver);
186208

187209
dev = serdev_tty_port_register(port, host, parent, driver, index);
188210
if (PTR_ERR(dev) != -ENODEV) {
@@ -703,6 +725,7 @@ int tty_port_install(struct tty_port *port, struct tty_driver *driver,
703725
struct tty_struct *tty)
704726
{
705727
tty->port = port;
728+
tty_port_link_driver_wq(port, driver);
706729
return tty_standard_install(driver, tty);
707730
}
708731
EXPORT_SYMBOL_GPL(tty_port_install);

include/linux/tty_buffer.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ static inline u8 *flag_buf_ptr(struct tty_buffer *b, unsigned int ofs)
3434

3535
struct tty_bufhead {
3636
struct tty_buffer *head; /* Queue head */
37+
struct workqueue_struct *flip_wq;
3738
struct work_struct work;
3839
struct mutex lock;
3940
atomic_t priority;

include/linux/tty_driver.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ struct serial_struct;
6969
* Do not create numbered ``/dev`` nodes. For example, create
7070
* ``/dev/ttyprintk`` and not ``/dev/ttyprintk0``. Applicable only when a
7171
* 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.
7276
*/
7377
enum tty_driver_flag {
7478
TTY_DRIVER_INSTALLED = BIT(0),
@@ -79,6 +83,7 @@ enum tty_driver_flag {
7983
TTY_DRIVER_HARDWARE_BREAK = BIT(5),
8084
TTY_DRIVER_DYNAMIC_ALLOC = BIT(6),
8185
TTY_DRIVER_UNNUMBERED_NODE = BIT(7),
86+
TTY_DRIVER_CUSTOM_WORKQUEUE = BIT(8),
8287
};
8388

8489
enum tty_driver_type {
@@ -506,6 +511,7 @@ struct tty_operations {
506511
* @flags: tty driver flags (%TTY_DRIVER_)
507512
* @proc_entry: proc fs entry, used internally
508513
* @other: driver of the linked tty; only used for the PTY driver
514+
* @flip_wq: workqueue to queue flip buffer work on
509515
* @ttys: array of active &struct tty_struct, set by tty_standard_install()
510516
* @ports: array of &struct tty_port; can be set during initialization by
511517
* tty_port_link_device() and similar
@@ -539,6 +545,7 @@ struct tty_driver {
539545
unsigned long flags;
540546
struct proc_dir_entry *proc_entry;
541547
struct tty_driver *other;
548+
struct workqueue_struct *flip_wq;
542549

543550
/*
544551
* Pointer to the tty data structures

include/linux/tty_port.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ struct tty_port {
138138
kernel */
139139

140140
void tty_port_init(struct tty_port *port);
141+
void tty_port_link_wq(struct tty_port *port, struct workqueue_struct *flip_wq);
141142
void tty_port_link_device(struct tty_port *port, struct tty_driver *driver,
142143
unsigned index);
143144
struct device *tty_port_register_device(struct tty_port *port,
@@ -165,6 +166,18 @@ static inline struct tty_port *tty_port_get(struct tty_port *port)
165166
return NULL;
166167
}
167168

169+
/*
170+
* Never overwrite the workqueue set by tty_port_link_wq().
171+
* No effect when %TTY_DRIVER_CUSTOM_WORKQUEUE is set, as driver->flip_wq is
172+
* %NULL.
173+
*/
174+
static inline void tty_port_link_driver_wq(struct tty_port *port,
175+
struct tty_driver *driver)
176+
{
177+
if (!port->buf.flip_wq)
178+
port->buf.flip_wq = driver->flip_wq;
179+
}
180+
168181
/* If the cts flow control is enabled, return true. */
169182
static inline bool tty_port_cts_enabled(const struct tty_port *port)
170183
{

0 commit comments

Comments
 (0)