Linux 6.9-rc6
[sfrench/cifs-2.6.git] / kernel / vhost_task.c
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * Copyright (C) 2021 Oracle Corporation
4  */
5 #include <linux/slab.h>
6 #include <linux/completion.h>
7 #include <linux/sched/task.h>
8 #include <linux/sched/vhost_task.h>
9 #include <linux/sched/signal.h>
10
11 enum vhost_task_flags {
12         VHOST_TASK_FLAGS_STOP,
13 };
14
15 struct vhost_task {
16         bool (*fn)(void *data);
17         void *data;
18         struct completion exited;
19         unsigned long flags;
20         struct task_struct *task;
21 };
22
23 static int vhost_task_fn(void *data)
24 {
25         struct vhost_task *vtsk = data;
26         bool dead = false;
27
28         for (;;) {
29                 bool did_work;
30
31                 if (!dead && signal_pending(current)) {
32                         struct ksignal ksig;
33                         /*
34                          * Calling get_signal will block in SIGSTOP,
35                          * or clear fatal_signal_pending, but remember
36                          * what was set.
37                          *
38                          * This thread won't actually exit until all
39                          * of the file descriptors are closed, and
40                          * the release function is called.
41                          */
42                         dead = get_signal(&ksig);
43                         if (dead)
44                                 clear_thread_flag(TIF_SIGPENDING);
45                 }
46
47                 /* mb paired w/ vhost_task_stop */
48                 set_current_state(TASK_INTERRUPTIBLE);
49
50                 if (test_bit(VHOST_TASK_FLAGS_STOP, &vtsk->flags)) {
51                         __set_current_state(TASK_RUNNING);
52                         break;
53                 }
54
55                 did_work = vtsk->fn(vtsk->data);
56                 if (!did_work)
57                         schedule();
58         }
59
60         complete(&vtsk->exited);
61         do_exit(0);
62 }
63
64 /**
65  * vhost_task_wake - wakeup the vhost_task
66  * @vtsk: vhost_task to wake
67  *
68  * wake up the vhost_task worker thread
69  */
70 void vhost_task_wake(struct vhost_task *vtsk)
71 {
72         wake_up_process(vtsk->task);
73 }
74 EXPORT_SYMBOL_GPL(vhost_task_wake);
75
76 /**
77  * vhost_task_stop - stop a vhost_task
78  * @vtsk: vhost_task to stop
79  *
80  * vhost_task_fn ensures the worker thread exits after
81  * VHOST_TASK_FLAGS_SOP becomes true.
82  */
83 void vhost_task_stop(struct vhost_task *vtsk)
84 {
85         set_bit(VHOST_TASK_FLAGS_STOP, &vtsk->flags);
86         vhost_task_wake(vtsk);
87         /*
88          * Make sure vhost_task_fn is no longer accessing the vhost_task before
89          * freeing it below.
90          */
91         wait_for_completion(&vtsk->exited);
92         kfree(vtsk);
93 }
94 EXPORT_SYMBOL_GPL(vhost_task_stop);
95
96 /**
97  * vhost_task_create - create a copy of a task to be used by the kernel
98  * @fn: vhost worker function
99  * @arg: data to be passed to fn
100  * @name: the thread's name
101  *
102  * This returns a specialized task for use by the vhost layer or NULL on
103  * failure. The returned task is inactive, and the caller must fire it up
104  * through vhost_task_start().
105  */
106 struct vhost_task *vhost_task_create(bool (*fn)(void *), void *arg,
107                                      const char *name)
108 {
109         struct kernel_clone_args args = {
110                 .flags          = CLONE_FS | CLONE_UNTRACED | CLONE_VM |
111                                   CLONE_THREAD | CLONE_SIGHAND,
112                 .exit_signal    = 0,
113                 .fn             = vhost_task_fn,
114                 .name           = name,
115                 .user_worker    = 1,
116                 .no_files       = 1,
117         };
118         struct vhost_task *vtsk;
119         struct task_struct *tsk;
120
121         vtsk = kzalloc(sizeof(*vtsk), GFP_KERNEL);
122         if (!vtsk)
123                 return NULL;
124         init_completion(&vtsk->exited);
125         vtsk->data = arg;
126         vtsk->fn = fn;
127
128         args.fn_arg = vtsk;
129
130         tsk = copy_process(NULL, 0, NUMA_NO_NODE, &args);
131         if (IS_ERR(tsk)) {
132                 kfree(vtsk);
133                 return NULL;
134         }
135
136         vtsk->task = tsk;
137         return vtsk;
138 }
139 EXPORT_SYMBOL_GPL(vhost_task_create);
140
141 /**
142  * vhost_task_start - start a vhost_task created with vhost_task_create
143  * @vtsk: vhost_task to wake up
144  */
145 void vhost_task_start(struct vhost_task *vtsk)
146 {
147         wake_up_new_task(vtsk->task);
148 }
149 EXPORT_SYMBOL_GPL(vhost_task_start);