xref: /openbmc/linux/kernel/vhost_task.c (revision dce54888)
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 		/* mb paired w/ vhost_task_stop */
32 		if (test_bit(VHOST_TASK_FLAGS_STOP, &vtsk->flags))
33 			break;
34 
35 		if (!dead && signal_pending(current)) {
36 			struct ksignal ksig;
37 			/*
38 			 * Calling get_signal will block in SIGSTOP,
39 			 * or clear fatal_signal_pending, but remember
40 			 * what was set.
41 			 *
42 			 * This thread won't actually exit until all
43 			 * of the file descriptors are closed, and
44 			 * the release function is called.
45 			 */
46 			dead = get_signal(&ksig);
47 			if (dead)
48 				clear_thread_flag(TIF_SIGPENDING);
49 		}
50 
51 		did_work = vtsk->fn(vtsk->data);
52 		if (!did_work) {
53 			set_current_state(TASK_INTERRUPTIBLE);
54 			schedule();
55 		}
56 	}
57 
58 	complete(&vtsk->exited);
59 	do_exit(0);
60 }
61 
62 /**
63  * vhost_task_wake - wakeup the vhost_task
64  * @vtsk: vhost_task to wake
65  *
66  * wake up the vhost_task worker thread
67  */
68 void vhost_task_wake(struct vhost_task *vtsk)
69 {
70 	wake_up_process(vtsk->task);
71 }
72 EXPORT_SYMBOL_GPL(vhost_task_wake);
73 
74 /**
75  * vhost_task_stop - stop a vhost_task
76  * @vtsk: vhost_task to stop
77  *
78  * vhost_task_fn ensures the worker thread exits after
79  * VHOST_TASK_FLAGS_SOP becomes true.
80  */
81 void vhost_task_stop(struct vhost_task *vtsk)
82 {
83 	set_bit(VHOST_TASK_FLAGS_STOP, &vtsk->flags);
84 	vhost_task_wake(vtsk);
85 	/*
86 	 * Make sure vhost_task_fn is no longer accessing the vhost_task before
87 	 * freeing it below.
88 	 */
89 	wait_for_completion(&vtsk->exited);
90 	kfree(vtsk);
91 }
92 EXPORT_SYMBOL_GPL(vhost_task_stop);
93 
94 /**
95  * vhost_task_create - create a copy of a task to be used by the kernel
96  * @fn: vhost worker function
97  * @arg: data to be passed to fn
98  * @name: the thread's name
99  *
100  * This returns a specialized task for use by the vhost layer or NULL on
101  * failure. The returned task is inactive, and the caller must fire it up
102  * through vhost_task_start().
103  */
104 struct vhost_task *vhost_task_create(bool (*fn)(void *), void *arg,
105 				     const char *name)
106 {
107 	struct kernel_clone_args args = {
108 		.flags		= CLONE_FS | CLONE_UNTRACED | CLONE_VM |
109 				  CLONE_THREAD | CLONE_SIGHAND,
110 		.exit_signal	= 0,
111 		.fn		= vhost_task_fn,
112 		.name		= name,
113 		.user_worker	= 1,
114 		.no_files	= 1,
115 	};
116 	struct vhost_task *vtsk;
117 	struct task_struct *tsk;
118 
119 	vtsk = kzalloc(sizeof(*vtsk), GFP_KERNEL);
120 	if (!vtsk)
121 		return NULL;
122 	init_completion(&vtsk->exited);
123 	vtsk->data = arg;
124 	vtsk->fn = fn;
125 
126 	args.fn_arg = vtsk;
127 
128 	tsk = copy_process(NULL, 0, NUMA_NO_NODE, &args);
129 	if (IS_ERR(tsk)) {
130 		kfree(vtsk);
131 		return NULL;
132 	}
133 
134 	vtsk->task = tsk;
135 	return vtsk;
136 }
137 EXPORT_SYMBOL_GPL(vhost_task_create);
138 
139 /**
140  * vhost_task_start - start a vhost_task created with vhost_task_create
141  * @vtsk: vhost_task to wake up
142  */
143 void vhost_task_start(struct vhost_task *vtsk)
144 {
145 	wake_up_new_task(vtsk->task);
146 }
147 EXPORT_SYMBOL_GPL(vhost_task_start);
148