xref: /linux/kernel/vhost_task.c (revision 36ec807b627b4c0a0a382f0ae48eac7187d14b2b)
1e297cd54SMike Christie // SPDX-License-Identifier: GPL-2.0-only
2e297cd54SMike Christie /*
3e297cd54SMike Christie  * Copyright (C) 2021 Oracle Corporation
4e297cd54SMike Christie  */
5e297cd54SMike Christie #include <linux/slab.h>
6e297cd54SMike Christie #include <linux/completion.h>
7e297cd54SMike Christie #include <linux/sched/task.h>
8e297cd54SMike Christie #include <linux/sched/vhost_task.h>
9e297cd54SMike Christie #include <linux/sched/signal.h>
10e297cd54SMike Christie 
11e297cd54SMike Christie enum vhost_task_flags {
12e297cd54SMike Christie 	VHOST_TASK_FLAGS_STOP,
13*db5247d9SMike Christie 	VHOST_TASK_FLAGS_KILLED,
14e297cd54SMike Christie };
15e297cd54SMike Christie 
16f9010dbdSMike Christie struct vhost_task {
17f9010dbdSMike Christie 	bool (*fn)(void *data);
18*db5247d9SMike Christie 	void (*handle_sigkill)(void *data);
19f9010dbdSMike Christie 	void *data;
20f9010dbdSMike Christie 	struct completion exited;
21f9010dbdSMike Christie 	unsigned long flags;
22f9010dbdSMike Christie 	struct task_struct *task;
23*db5247d9SMike Christie 	/* serialize SIGKILL and vhost_task_stop calls */
24*db5247d9SMike Christie 	struct mutex exit_mutex;
25f9010dbdSMike Christie };
26f9010dbdSMike Christie 
27e297cd54SMike Christie static int vhost_task_fn(void *data)
28e297cd54SMike Christie {
29e297cd54SMike Christie 	struct vhost_task *vtsk = data;
30e297cd54SMike Christie 
31f9010dbdSMike Christie 	for (;;) {
32f9010dbdSMike Christie 		bool did_work;
33f9010dbdSMike Christie 
34*db5247d9SMike Christie 		if (signal_pending(current)) {
35f9010dbdSMike Christie 			struct ksignal ksig;
36*db5247d9SMike Christie 
37*db5247d9SMike Christie 			if (get_signal(&ksig))
38*db5247d9SMike Christie 				break;
39e297cd54SMike Christie 		}
40e297cd54SMike Christie 
414b13cbefSMike Christie 		/* mb paired w/ vhost_task_stop */
42f9010dbdSMike Christie 		set_current_state(TASK_INTERRUPTIBLE);
434b13cbefSMike Christie 
444b13cbefSMike Christie 		if (test_bit(VHOST_TASK_FLAGS_STOP, &vtsk->flags)) {
454b13cbefSMike Christie 			__set_current_state(TASK_RUNNING);
464b13cbefSMike Christie 			break;
47f9010dbdSMike Christie 		}
484b13cbefSMike Christie 
494b13cbefSMike Christie 		did_work = vtsk->fn(vtsk->data);
504b13cbefSMike Christie 		if (!did_work)
514b13cbefSMike Christie 			schedule();
52f9010dbdSMike Christie 	}
53f9010dbdSMike Christie 
54*db5247d9SMike Christie 	mutex_lock(&vtsk->exit_mutex);
55*db5247d9SMike Christie 	/*
56*db5247d9SMike Christie 	 * If a vhost_task_stop and SIGKILL race, we can ignore the SIGKILL.
57*db5247d9SMike Christie 	 * When the vhost layer has called vhost_task_stop it's already stopped
58*db5247d9SMike Christie 	 * new work and flushed.
59*db5247d9SMike Christie 	 */
60*db5247d9SMike Christie 	if (!test_bit(VHOST_TASK_FLAGS_STOP, &vtsk->flags)) {
61*db5247d9SMike Christie 		set_bit(VHOST_TASK_FLAGS_KILLED, &vtsk->flags);
62*db5247d9SMike Christie 		vtsk->handle_sigkill(vtsk->data);
63*db5247d9SMike Christie 	}
64*db5247d9SMike Christie 	mutex_unlock(&vtsk->exit_mutex);
65f9010dbdSMike Christie 	complete(&vtsk->exited);
66*db5247d9SMike Christie 
67f9010dbdSMike Christie 	do_exit(0);
68f9010dbdSMike Christie }
69f9010dbdSMike Christie 
70f9010dbdSMike Christie /**
71f9010dbdSMike Christie  * vhost_task_wake - wakeup the vhost_task
72f9010dbdSMike Christie  * @vtsk: vhost_task to wake
73f9010dbdSMike Christie  *
74f9010dbdSMike Christie  * wake up the vhost_task worker thread
75f9010dbdSMike Christie  */
76f9010dbdSMike Christie void vhost_task_wake(struct vhost_task *vtsk)
77f9010dbdSMike Christie {
78f9010dbdSMike Christie 	wake_up_process(vtsk->task);
79f9010dbdSMike Christie }
80f9010dbdSMike Christie EXPORT_SYMBOL_GPL(vhost_task_wake);
81f9010dbdSMike Christie 
82e297cd54SMike Christie /**
83e297cd54SMike Christie  * vhost_task_stop - stop a vhost_task
84e297cd54SMike Christie  * @vtsk: vhost_task to stop
85e297cd54SMike Christie  *
86f9010dbdSMike Christie  * vhost_task_fn ensures the worker thread exits after
87*db5247d9SMike Christie  * VHOST_TASK_FLAGS_STOP becomes true.
88e297cd54SMike Christie  */
89e297cd54SMike Christie void vhost_task_stop(struct vhost_task *vtsk)
90e297cd54SMike Christie {
91*db5247d9SMike Christie 	mutex_lock(&vtsk->exit_mutex);
92*db5247d9SMike Christie 	if (!test_bit(VHOST_TASK_FLAGS_KILLED, &vtsk->flags)) {
93e297cd54SMike Christie 		set_bit(VHOST_TASK_FLAGS_STOP, &vtsk->flags);
94f9010dbdSMike Christie 		vhost_task_wake(vtsk);
95*db5247d9SMike Christie 	}
96*db5247d9SMike Christie 	mutex_unlock(&vtsk->exit_mutex);
97*db5247d9SMike Christie 
98e297cd54SMike Christie 	/*
99e297cd54SMike Christie 	 * Make sure vhost_task_fn is no longer accessing the vhost_task before
100f9010dbdSMike Christie 	 * freeing it below.
101e297cd54SMike Christie 	 */
102e297cd54SMike Christie 	wait_for_completion(&vtsk->exited);
103e297cd54SMike Christie 	kfree(vtsk);
104e297cd54SMike Christie }
105e297cd54SMike Christie EXPORT_SYMBOL_GPL(vhost_task_stop);
106e297cd54SMike Christie 
107e297cd54SMike Christie /**
108f9010dbdSMike Christie  * vhost_task_create - create a copy of a task to be used by the kernel
109f9010dbdSMike Christie  * @fn: vhost worker function
110*db5247d9SMike Christie  * @handle_sigkill: vhost function to handle when we are killed
111*db5247d9SMike Christie  * @arg: data to be passed to fn and handled_kill
112e297cd54SMike Christie  * @name: the thread's name
113e297cd54SMike Christie  *
114e297cd54SMike Christie  * This returns a specialized task for use by the vhost layer or NULL on
115e297cd54SMike Christie  * failure. The returned task is inactive, and the caller must fire it up
116e297cd54SMike Christie  * through vhost_task_start().
117e297cd54SMike Christie  */
118*db5247d9SMike Christie struct vhost_task *vhost_task_create(bool (*fn)(void *),
119*db5247d9SMike Christie 				     void (*handle_sigkill)(void *), void *arg,
120e297cd54SMike Christie 				     const char *name)
121e297cd54SMike Christie {
122e297cd54SMike Christie 	struct kernel_clone_args args = {
123f9010dbdSMike Christie 		.flags		= CLONE_FS | CLONE_UNTRACED | CLONE_VM |
124f9010dbdSMike Christie 				  CLONE_THREAD | CLONE_SIGHAND,
125e297cd54SMike Christie 		.exit_signal	= 0,
126e297cd54SMike Christie 		.fn		= vhost_task_fn,
127e297cd54SMike Christie 		.name		= name,
128e297cd54SMike Christie 		.user_worker	= 1,
129e297cd54SMike Christie 		.no_files	= 1,
130e297cd54SMike Christie 	};
131e297cd54SMike Christie 	struct vhost_task *vtsk;
132e297cd54SMike Christie 	struct task_struct *tsk;
133e297cd54SMike Christie 
134e297cd54SMike Christie 	vtsk = kzalloc(sizeof(*vtsk), GFP_KERNEL);
135e297cd54SMike Christie 	if (!vtsk)
136e297cd54SMike Christie 		return NULL;
137e297cd54SMike Christie 	init_completion(&vtsk->exited);
138*db5247d9SMike Christie 	mutex_init(&vtsk->exit_mutex);
139e297cd54SMike Christie 	vtsk->data = arg;
140e297cd54SMike Christie 	vtsk->fn = fn;
141*db5247d9SMike Christie 	vtsk->handle_sigkill = handle_sigkill;
142e297cd54SMike Christie 
143e297cd54SMike Christie 	args.fn_arg = vtsk;
144e297cd54SMike Christie 
145e297cd54SMike Christie 	tsk = copy_process(NULL, 0, NUMA_NO_NODE, &args);
146e297cd54SMike Christie 	if (IS_ERR(tsk)) {
147e297cd54SMike Christie 		kfree(vtsk);
148e297cd54SMike Christie 		return NULL;
149e297cd54SMike Christie 	}
150e297cd54SMike Christie 
151e297cd54SMike Christie 	vtsk->task = tsk;
152e297cd54SMike Christie 	return vtsk;
153e297cd54SMike Christie }
154e297cd54SMike Christie EXPORT_SYMBOL_GPL(vhost_task_create);
155e297cd54SMike Christie 
156e297cd54SMike Christie /**
157e297cd54SMike Christie  * vhost_task_start - start a vhost_task created with vhost_task_create
158e297cd54SMike Christie  * @vtsk: vhost_task to wake up
159e297cd54SMike Christie  */
160e297cd54SMike Christie void vhost_task_start(struct vhost_task *vtsk)
161e297cd54SMike Christie {
162e297cd54SMike Christie 	wake_up_new_task(vtsk->task);
163e297cd54SMike Christie }
164e297cd54SMike Christie EXPORT_SYMBOL_GPL(vhost_task_start);
165