git: b6f87b78b5bb - main - LinuxKPI: Implement kthread_worker related functions

From: Vladimir Kondratyev <wulf_at_FreeBSD.org>
Date: Tue, 17 May 2022 12:11:37 UTC
The branch main has been updated by wulf:

URL: https://cgit.FreeBSD.org/src/commit/?id=b6f87b78b5bb48e00f54b96ddea7ad5bf5e3aa1f

commit b6f87b78b5bb48e00f54b96ddea7ad5bf5e3aa1f
Author:     Vladimir Kondratyev <wulf@FreeBSD.org>
AuthorDate: 2022-05-17 12:10:20 +0000
Commit:     Vladimir Kondratyev <wulf@FreeBSD.org>
CommitDate: 2022-05-17 12:10:20 +0000

    LinuxKPI: Implement kthread_worker related functions
    
    Kthread worker is a single thread workqueue which can be used in cases
    where specific kthread association is necessary, for example, when it
    should have RT priority or be assigned to certain cgroup.
    
    This change implements Linux v4.9 interface which mostly hides kthread
    internals from users thus allowing to use ordinary taskqueue(9) KPI.
    As kthread worker prohibits enqueueing of already pending or canceling
    tasks some minimal changes to taskqueue(9) were done.
    taskqueue_enqueue_flags() was added to taskqueue KPI which accepts extra
    flags parameter. It contains one or more of the following flags:
    
    TASKQUEUE_FAIL_IF_PENDING - taskqueue_enqueue_flags() fails if the task
        is already scheduled to execution. EEXIST is returned and the
        ta_pending counter value remains unchanged.
    TASKQUEUE_FAIL_IF_CANCELING - taskqueue_enqueue_flags() fails if the
        task is in the canceling state and ECANCELED is returned.
    
    Required by:    drm-kmod 5.10
    
    MFC after:      1 week
    Reviewed by:    hselasky, Pau Amma (docs)
    Differential Revision:  https://reviews.freebsd.org/D35051
---
 share/man/man9/taskqueue.9                         | 26 +++++-
 sys/compat/linuxkpi/common/include/linux/kthread.h | 97 +++++++++++++++++++++-
 sys/compat/linuxkpi/common/src/linux_kthread.c     | 16 ++++
 sys/kern/subr_taskqueue.c                          | 74 ++++++++++++-----
 sys/sys/taskqueue.h                                |  6 ++
 5 files changed, 196 insertions(+), 23 deletions(-)

diff --git a/share/man/man9/taskqueue.9 b/share/man/man9/taskqueue.9
index 58eb6b7c5571..e13ad9498562 100644
--- a/share/man/man9/taskqueue.9
+++ b/share/man/man9/taskqueue.9
@@ -28,7 +28,7 @@
 .\"
 .\" $FreeBSD$
 .\"
-.Dd September 1, 2021
+.Dd April 25, 2022
 .Dt TASKQUEUE 9
 .Os
 .Sh NAME
@@ -85,6 +85,8 @@ struct timeout_task;
 .Ft int
 .Fn taskqueue_enqueue "struct taskqueue *queue" "struct task *task"
 .Ft int
+.Fn taskqueue_enqueue_flags "struct taskqueue *queue" "struct task *task" "int flags"
+.Ft int
 .Fn taskqueue_enqueue_timeout "struct taskqueue *queue" "struct timeout_task *timeout_task" "int ticks"
 .Ft int
 .Fn taskqueue_enqueue_timeout_sbt "struct taskqueue *queue" "struct timeout_task *timeout_task" "sbintime_t sbt" "sbintime_t pr" "int flags"
@@ -225,6 +227,28 @@ is called on the task pointer passed to
 .Fn taskqueue_enqueue .
 .Pp
 The
+.Fn taskqueue_enqueue_flags
+accepts an extra
+.Va flags
+parameter which specifies a set of optional flags to alter the behavior of
+.Fn taskqueue_enqueue .
+It contains one or more of the following flags:
+.Bl -tag -width TASKQUEUE_FAIL_IF_CANCELING
+.It Dv TASKQUEUE_FAIL_IF_PENDING
+.Fn taskqueue_enqueue_flags
+fails if the task is already scheduled for execution.
+.Er EEXIST
+is returned and the
+.Va ta_pending
+counter value remains unchanged.
+.It Dv TASKQUEUE_FAIL_IF_CANCELING
+.Fn taskqueue_enqueue_flags
+fails if the task is in the canceling state and
+.Er ECANCELED
+is returned.
+.El
+.Pp
+The
 .Fn taskqueue_enqueue_timeout
 function is used to schedule the enqueue after the specified number of
 .Va ticks .
diff --git a/sys/compat/linuxkpi/common/include/linux/kthread.h b/sys/compat/linuxkpi/common/include/linux/kthread.h
index f4791d5f0db3..49309cd47a40 100644
--- a/sys/compat/linuxkpi/common/include/linux/kthread.h
+++ b/sys/compat/linuxkpi/common/include/linux/kthread.h
@@ -33,8 +33,29 @@
 
 #include <linux/sched.h>
 
-#include <sys/unistd.h>
+#include <sys/param.h>
+#include <sys/kernel.h>
 #include <sys/kthread.h>
+#include <sys/malloc.h>
+#include <sys/queue.h>
+#include <sys/taskqueue.h>
+#include <sys/unistd.h>
+
+struct task_struct;
+struct kthread_work;
+
+typedef void (*kthread_work_func_t)(struct kthread_work *work);
+
+struct kthread_worker {
+	struct task_struct	*task;
+	struct taskqueue	*tq;
+};
+
+struct kthread_work {
+	struct taskqueue	*tq;
+	struct task		task;
+	kthread_work_func_t	func;
+};
 
 #define	kthread_run(fn, data, fmt, ...)	({				\
 	struct task_struct *__task;					\
@@ -70,4 +91,78 @@ int linux_in_atomic(void);
 
 #define	in_atomic()			linux_in_atomic()
 
+/* Only kthread_(create|destroy)_worker interface is allowed */
+#define	kthread_init_worker(worker)	\
+	_Static_assert(false, "pre-4.9 worker interface is not supported");
+
+task_fn_t lkpi_kthread_work_fn;
+task_fn_t lkpi_kthread_worker_init_fn;
+
+#define kthread_create_worker(flags, fmt, ...) ({			\
+	struct kthread_worker *__w;					\
+	struct task __task;						\
+									\
+	__w = malloc(sizeof(*__w), M_KMALLOC, M_WAITOK | M_ZERO);	\
+	__w->tq = taskqueue_create("lkpi kthread taskq", M_WAITOK,	\
+	    taskqueue_thread_enqueue, &__w->tq);			\
+	taskqueue_start_threads(&__w->tq, 1, PWAIT, fmt, ##__VA_ARGS__);\
+	TASK_INIT(&__task, 0, lkpi_kthread_worker_init_fn, __w);	\
+	taskqueue_enqueue(__w->tq, &__task);				\
+	taskqueue_drain(__w->tq, &__task);				\
+	__w;								\
+})
+
+static inline void
+kthread_destroy_worker(struct kthread_worker *worker)
+{
+	taskqueue_drain_all(worker->tq);
+	taskqueue_free(worker->tq);
+	free(worker, M_KMALLOC);
+}
+
+static inline void
+kthread_init_work(struct kthread_work *work, kthread_work_func_t func)
+{
+	work->tq = NULL;
+	work->func = func;
+	TASK_INIT(&work->task, 0, lkpi_kthread_work_fn, work);
+}
+
+static inline bool
+kthread_queue_work(struct kthread_worker *worker, struct kthread_work *work)
+{
+	int error;
+
+	error = taskqueue_enqueue_flags(worker->tq, &work->task,
+	    TASKQUEUE_FAIL_IF_CANCELING | TASKQUEUE_FAIL_IF_PENDING);
+	if (error == 0)
+		work->tq = worker->tq;
+	return (error == 0);
+}
+
+static inline bool
+kthread_cancel_work_sync(struct kthread_work *work)
+{
+	u_int pending = 0;
+
+	if (work->tq != NULL &&
+	    taskqueue_cancel(work->tq, &work->task, &pending) != 0)
+		taskqueue_drain(work->tq, &work->task);
+
+	return (pending != 0);
+}
+
+static inline void
+kthread_flush_work(struct kthread_work *work)
+{
+	if (work->tq != NULL)
+		taskqueue_drain(work->tq, &work->task);
+}
+
+static inline void
+kthread_flush_worker(struct kthread_worker *worker)
+{
+	taskqueue_drain_all(worker->tq);
+}
+
 #endif /* _LINUXKPI_LINUX_KTHREAD_H_ */
diff --git a/sys/compat/linuxkpi/common/src/linux_kthread.c b/sys/compat/linuxkpi/common/src/linux_kthread.c
index 26afe005ea59..19afad6872c3 100644
--- a/sys/compat/linuxkpi/common/src/linux_kthread.c
+++ b/sys/compat/linuxkpi/common/src/linux_kthread.c
@@ -165,3 +165,19 @@ linux_kthread_fn(void *arg __unused)
 	}
 	kthread_exit();
 }
+
+void
+lkpi_kthread_work_fn(void *context, int pending __unused)
+{
+	struct kthread_work *work = context;
+
+	work->func(work);
+}
+
+void
+lkpi_kthread_worker_init_fn(void *context, int pending __unused)
+{
+	struct kthread_worker *worker = context;
+
+	worker->task = current;
+}
diff --git a/sys/kern/subr_taskqueue.c b/sys/kern/subr_taskqueue.c
index e43b09010761..7ad7c210ceff 100644
--- a/sys/kern/subr_taskqueue.c
+++ b/sys/kern/subr_taskqueue.c
@@ -59,6 +59,7 @@ static void	 taskqueue_swi_giant_enqueue(void *);
 struct taskqueue_busy {
 	struct task		*tb_running;
 	u_int			 tb_seq;
+	bool			 tb_canceling;
 	LIST_ENTRY(taskqueue_busy) tb_link;
 };
 
@@ -125,6 +126,19 @@ TQ_SLEEP(struct taskqueue *tq, void *p, const char *wm)
 	return (msleep(p, &tq->tq_mutex, 0, wm, 0));
 }
 
+static struct taskqueue_busy *
+task_get_busy(struct taskqueue *queue, struct task *task)
+{
+	struct taskqueue_busy *tb;
+
+	TQ_ASSERT_LOCKED(queue);
+	LIST_FOREACH(tb, &queue->tq_active, tb_link) {
+		if (tb->tb_running == task)
+			return (tb);
+	}
+	return (NULL);
+}
+
 static struct taskqueue *
 _taskqueue_create(const char *name, int mflags,
 		 taskqueue_enqueue_fn enqueue, void *context,
@@ -217,16 +231,32 @@ taskqueue_free(struct taskqueue *queue)
 }
 
 static int
-taskqueue_enqueue_locked(struct taskqueue *queue, struct task *task)
+taskqueue_enqueue_locked(struct taskqueue *queue, struct task *task, int flags)
 {
 	struct task *ins;
 	struct task *prev;
+	struct taskqueue_busy *tb;
 
 	KASSERT(task->ta_func != NULL, ("enqueueing task with NULL func"));
+	/*
+	 * Ignore canceling task if requested.
+	 */
+	if (__predict_false((flags & TASKQUEUE_FAIL_IF_CANCELING) != 0)) {
+		tb = task_get_busy(queue, task);
+		if (tb != NULL && tb->tb_canceling) {
+			TQ_UNLOCK(queue);
+			return (ECANCELED);
+		}
+	}
+
 	/*
 	 * Count multiple enqueues.
 	 */
 	if (task->ta_pending) {
+		if (__predict_false((flags & TASKQUEUE_FAIL_IF_PENDING) != 0)) {
+			TQ_UNLOCK(queue);
+			return (EEXIST);
+		}
 		if (task->ta_pending < USHRT_MAX)
 			task->ta_pending++;
 		TQ_UNLOCK(queue);
@@ -274,17 +304,23 @@ taskqueue_enqueue_locked(struct taskqueue *queue, struct task *task)
 }
 
 int
-taskqueue_enqueue(struct taskqueue *queue, struct task *task)
+taskqueue_enqueue_flags(struct taskqueue *queue, struct task *task, int flags)
 {
 	int res;
 
 	TQ_LOCK(queue);
-	res = taskqueue_enqueue_locked(queue, task);
+	res = taskqueue_enqueue_locked(queue, task, flags);
 	/* The lock is released inside. */
 
 	return (res);
 }
 
+int
+taskqueue_enqueue(struct taskqueue *queue, struct task *task)
+{
+	return (taskqueue_enqueue_flags(queue, task, 0));
+}
+
 static void
 taskqueue_timeout_func(void *arg)
 {
@@ -296,7 +332,7 @@ taskqueue_timeout_func(void *arg)
 	KASSERT((timeout_task->f & DT_CALLOUT_ARMED) != 0, ("Stray timeout"));
 	timeout_task->f &= ~DT_CALLOUT_ARMED;
 	queue->tq_callouts--;
-	taskqueue_enqueue_locked(timeout_task->q, &timeout_task->t);
+	taskqueue_enqueue_locked(timeout_task->q, &timeout_task->t, 0);
 	/* The lock is released inside. */
 }
 
@@ -316,7 +352,7 @@ taskqueue_enqueue_timeout_sbt(struct taskqueue *queue,
 		TQ_UNLOCK(queue);
 		res = -1;
 	} else if (sbt == 0) {
-		taskqueue_enqueue_locked(queue, &timeout_task->t);
+		taskqueue_enqueue_locked(queue, &timeout_task->t, 0);
 		/* The lock is released inside. */
 	} else {
 		if ((timeout_task->f & DT_CALLOUT_ARMED) != 0) {
@@ -464,6 +500,7 @@ taskqueue_run_locked(struct taskqueue *queue)
 		task->ta_pending = 0;
 		tb.tb_running = task;
 		tb.tb_seq = ++queue->tq_seq;
+		tb.tb_canceling = false;
 		TQ_UNLOCK(queue);
 
 		KASSERT(task->ta_func != NULL, ("task->ta_func is NULL"));
@@ -493,19 +530,6 @@ taskqueue_run(struct taskqueue *queue)
 	TQ_UNLOCK(queue);
 }
 
-static int
-task_is_running(struct taskqueue *queue, struct task *task)
-{
-	struct taskqueue_busy *tb;
-
-	TQ_ASSERT_LOCKED(queue);
-	LIST_FOREACH(tb, &queue->tq_active, tb_link) {
-		if (tb->tb_running == task)
-			return (1);
-	}
-	return (0);
-}
-
 /*
  * Only use this function in single threaded contexts. It returns
  * non-zero if the given task is either pending or running. Else the
@@ -517,7 +541,7 @@ taskqueue_poll_is_busy(struct taskqueue *queue, struct task *task)
 	int retval;
 
 	TQ_LOCK(queue);
-	retval = task->ta_pending > 0 || task_is_running(queue, task);
+	retval = task->ta_pending > 0 || task_get_busy(queue, task) != NULL;
 	TQ_UNLOCK(queue);
 
 	return (retval);
@@ -527,6 +551,8 @@ static int
 taskqueue_cancel_locked(struct taskqueue *queue, struct task *task,
     u_int *pendp)
 {
+	struct taskqueue_busy *tb;
+	int retval = 0;
 
 	if (task->ta_pending > 0) {
 		STAILQ_REMOVE(&queue->tq_queue, task, task, ta_link);
@@ -536,7 +562,13 @@ taskqueue_cancel_locked(struct taskqueue *queue, struct task *task,
 	if (pendp != NULL)
 		*pendp = task->ta_pending;
 	task->ta_pending = 0;
-	return (task_is_running(queue, task) ? EBUSY : 0);
+	tb = task_get_busy(queue, task);
+	if (tb != NULL) {
+		tb->tb_canceling = true;
+		retval = EBUSY;
+	}
+
+	return (retval);
 }
 
 int
@@ -580,7 +612,7 @@ taskqueue_drain(struct taskqueue *queue, struct task *task)
 		WITNESS_WARN(WARN_GIANTOK | WARN_SLEEPOK, NULL, __func__);
 
 	TQ_LOCK(queue);
-	while (task->ta_pending != 0 || task_is_running(queue, task))
+	while (task->ta_pending != 0 || task_get_busy(queue, task) != NULL)
 		TQ_SLEEP(queue, task, "tq_drain");
 	TQ_UNLOCK(queue);
 }
diff --git a/sys/sys/taskqueue.h b/sys/sys/taskqueue.h
index 7e59187e0114..cc98fe5fab85 100644
--- a/sys/sys/taskqueue.h
+++ b/sys/sys/taskqueue.h
@@ -61,6 +61,10 @@ enum taskqueue_callback_type {
 #define	TASKQUEUE_NUM_CALLBACKS		TASKQUEUE_CALLBACK_TYPE_MAX + 1
 #define	TASKQUEUE_NAMELEN		32
 
+/* taskqueue_enqueue flags */
+#define	TASKQUEUE_FAIL_IF_PENDING	(1 << 0)
+#define	TASKQUEUE_FAIL_IF_CANCELING	(1 << 1)
+
 typedef void (*taskqueue_callback_fn)(void *context);
 
 /*
@@ -82,6 +86,8 @@ int	taskqueue_start_threads_in_proc(struct taskqueue **tqp, int count,
 int	taskqueue_start_threads_cpuset(struct taskqueue **tqp, int count,
 	    int pri, cpuset_t *mask, const char *name, ...) __printflike(5, 6);
 int	taskqueue_enqueue(struct taskqueue *queue, struct task *task);
+int	taskqueue_enqueue_flags(struct taskqueue *queue, struct task *task,
+	    int flags);
 int	taskqueue_enqueue_timeout(struct taskqueue *queue,
 	    struct timeout_task *timeout_task, int ticks);
 int	taskqueue_enqueue_timeout_sbt(struct taskqueue *queue,