M. Feser KBS GmbH
Date: 10.01.2025

This patch modifies the TTY implementation to make use of a kthread for
low latency configuration

Modifications:
reintroduced low_latency flag which is missing in newer kernel versions
adjusted line numbers
adjusted prio

Based on the following patch series from Steven Walter (08.06.2015):
drivers/tty: refactor functions for flushing/queuing work
drivers/tty: convert tty_port to use kthread_worker
drivers/tty/tty_buffer.c: use a separate kthread for low_latency
-------------------------------------------------------------------------
diff -ruPN a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c
--- a/drivers/tty/serial/serial_core.c	2025-01-09 13:28:51.000000000 +0100
+++ b/drivers/tty/serial/serial_core.c	2025-01-10 14:31:12.665776943 +0100
@@ -974,6 +974,7 @@
 	port->closing_wait    = closing_wait;
 	if (new_info->xmit_fifo_size)
 		uport->fifosize = new_info->xmit_fifo_size;
+	port->low_latency = (uport->flags & UPF_LOW_LATENCY) ? 1 : 0;
 
  check_and_exit:
 	retval = 0;
diff -ruPN a/drivers/tty/tty_buffer.c b/drivers/tty/tty_buffer.c
--- a/drivers/tty/tty_buffer.c	2025-01-09 13:28:51.000000000 +0100
+++ b/drivers/tty/tty_buffer.c	2025-01-10 14:35:59.971952999 +0100
@@ -5,6 +5,7 @@
 
 #include <linux/types.h>
 #include <linux/errno.h>
+#include <linux/kthread.h>
 #include <linux/tty.h>
 #include <linux/tty_driver.h>
 #include <linux/tty_flip.h>
@@ -12,6 +13,8 @@
 #include <linux/string.h>
 #include <linux/slab.h>
 #include <linux/sched.h>
+#include <linux/sched/prio.h>
+#include <uapi/linux/sched/types.h>
 #include <linux/wait.h>
 #include <linux/bitops.h>
 #include <linux/delay.h>
@@ -72,7 +75,7 @@
 	atomic_dec(&buf->priority);
 	mutex_unlock(&buf->lock);
 	if (restart)
-		queue_work(system_unbound_wq, &buf->work);
+		tty_buffer_restart_work(port);
 }
 EXPORT_SYMBOL_GPL(tty_buffer_unlock_exclusive);
 
@@ -487,7 +490,7 @@
  *		 'consumer'
  */
 
-static void flush_to_ldisc(struct work_struct *work)
+static void flush_to_ldisc(struct kthread_work *work)
 {
 	struct tty_port *port = container_of(work, struct tty_port, buf.work);
 	struct tty_bufhead *buf = &port->buf;
@@ -558,7 +561,7 @@
 	struct tty_bufhead *buf = &port->buf;
 
 	tty_flip_buffer_commit(buf->tail);
-	queue_work(system_unbound_wq, &buf->work);
+	tty_buffer_restart_work(port);
 }
 EXPORT_SYMBOL(tty_flip_buffer_push);
 
@@ -588,11 +591,25 @@
 		tty_flip_buffer_commit(buf->tail);
 	spin_unlock_irqrestore(&port->lock, flags);
 
-	queue_work(system_unbound_wq, &buf->work);
+	tty_buffer_restart_work(port);
 
 	return size;
 }
 
+static DEFINE_KTHREAD_WORKER(tty_buffer_worker);
+static DEFINE_KTHREAD_WORKER(tty_buffer_worker_ll);
+
+void tty_buffer_init_kthread()
+{
+	struct task_struct *task;
+	struct sched_param param = { .sched_priority = MAX_RT_PRIO/4 };
+
+ 	kthread_run(kthread_worker_fn, &tty_buffer_worker, "tty");
+	task = kthread_run(kthread_worker_fn, &tty_buffer_worker_ll,
+			   "tty-low-latency");
+	sched_setscheduler(task, SCHED_FIFO, &param);
+}
+
 /**
  *	tty_buffer_init		-	prepare a tty buffer structure
  *	@port: tty port to initialise
@@ -612,7 +629,7 @@
 	init_llist_head(&buf->free);
 	atomic_set(&buf->mem_used, 0);
 	atomic_set(&buf->priority, 0);
-	INIT_WORK(&buf->work, flush_to_ldisc);
+	kthread_init_work(&buf->work, flush_to_ldisc);
 	buf->mem_limit = TTYB_DEFAULT_MEM_LIMIT;
 }
 
@@ -642,15 +659,18 @@
 
 bool tty_buffer_restart_work(struct tty_port *port)
 {
-	return queue_work(system_unbound_wq, &port->buf.work);
+	if (port->low_latency)
+		return kthread_queue_work(&tty_buffer_worker_ll, &port->buf.work);
+	else
+		return kthread_queue_work(&tty_buffer_worker, &port->buf.work);
 }
 
 bool tty_buffer_cancel_work(struct tty_port *port)
 {
-	return cancel_work_sync(&port->buf.work);
+	return kthread_cancel_work_sync(&port->buf.work);
 }
 
 void tty_buffer_flush_work(struct tty_port *port)
 {
-	flush_work(&port->buf.work);
+	kthread_flush_work(&port->buf.work);
 }
diff -ruPN a/drivers/tty/tty.h b/drivers/tty/tty.h
--- a/drivers/tty/tty.h	2025-01-09 13:28:51.000000000 +0100
+++ b/drivers/tty/tty.h	2025-01-10 13:23:26.000000000 +0100
@@ -71,6 +71,7 @@
 void tty_buffer_free_all(struct tty_port *port);
 void tty_buffer_flush(struct tty_struct *tty, struct tty_ldisc *ld);
 void tty_buffer_init(struct tty_port *port);
+void tty_buffer_init_kthread(void);
 void tty_buffer_set_lock_subclass(struct tty_port *port);
 bool tty_buffer_restart_work(struct tty_port *port);
 bool tty_buffer_cancel_work(struct tty_port *port);
diff -ruPN a/drivers/tty/tty_io.c b/drivers/tty/tty_io.c
--- a/drivers/tty/tty_io.c	2025-01-09 13:28:51.000000000 +0100
+++ b/drivers/tty/tty_io.c	2025-01-10 13:23:26.000000000 +0100
@@ -3614,6 +3614,7 @@
 int __init tty_init(void)
 {
 	tty_sysctl_init();
+	tty_buffer_init_kthread();
 	cdev_init(&tty_cdev, &tty_fops);
 	if (cdev_add(&tty_cdev, MKDEV(TTYAUX_MAJOR, 0), 1) ||
 	    register_chrdev_region(MKDEV(TTYAUX_MAJOR, 0), 1, "/dev/tty") < 0)
diff -ruPN a/include/linux/tty_buffer.h b/include/linux/tty_buffer.h
--- a/include/linux/tty_buffer.h	2025-01-09 13:28:51.000000000 +0100
+++ b/include/linux/tty_buffer.h	2025-01-10 13:23:26.000000000 +0100
@@ -3,9 +3,9 @@
 #define _LINUX_TTY_BUFFER_H
 
 #include <linux/atomic.h>
+#include <linux/kthread.h>
 #include <linux/llist.h>
 #include <linux/mutex.h>
-#include <linux/workqueue.h>
 
 struct tty_buffer {
 	union {
@@ -36,7 +36,7 @@
 
 struct tty_bufhead {
 	struct tty_buffer *head;	/* Queue head */
-	struct work_struct work;
+	struct kthread_work work;
 	struct mutex	   lock;
 	atomic_t	   priority;
 	struct tty_buffer sentinel;
diff -ruPN a/include/linux/tty_port.h b/include/linux/tty_port.h
--- a/include/linux/tty_port.h	2025-01-09 13:28:51.000000000 +0100
+++ b/include/linux/tty_port.h	2025-01-10 14:43:23.909345579 +0100
@@ -61,7 +61,8 @@
 	wait_queue_head_t	delta_msr_wait;	/* Modem status change */
 	unsigned long		flags;		/* User TTY flags ASYNC_ */
 	unsigned long		iflags;		/* Internal flags TTY_PORT_ */
-	unsigned char		console:1;	/* port is a console */
+	unsigned char		console:1,	/* port is a console */
+									low_latency:1;	/* optional: tune for latency */
 	struct mutex		mutex;		/* Locking */
 	struct mutex		buf_mutex;	/* Buffer alloc lock */
 	unsigned char		*xmit_buf;	/* Optional buffer */
