118 lines
2.8 KiB
C
118 lines
2.8 KiB
C
|
// SPDX-License-Identifier: GPL-2.0-only
|
||
|
/*
|
||
|
* Copyright (C) 2021 Oracle Corporation
|
||
|
*/
|
||
|
#include <linux/slab.h>
|
||
|
#include <linux/completion.h>
|
||
|
#include <linux/sched/task.h>
|
||
|
#include <linux/sched/vhost_task.h>
|
||
|
#include <linux/sched/signal.h>
|
||
|
|
||
|
enum vhost_task_flags {
|
||
|
VHOST_TASK_FLAGS_STOP,
|
||
|
};
|
||
|
|
||
|
static int vhost_task_fn(void *data)
|
||
|
{
|
||
|
struct vhost_task *vtsk = data;
|
||
|
int ret;
|
||
|
|
||
|
ret = vtsk->fn(vtsk->data);
|
||
|
complete(&vtsk->exited);
|
||
|
do_exit(ret);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* vhost_task_stop - stop a vhost_task
|
||
|
* @vtsk: vhost_task to stop
|
||
|
*
|
||
|
* Callers must call vhost_task_should_stop and return from their worker
|
||
|
* function when it returns true;
|
||
|
*/
|
||
|
void vhost_task_stop(struct vhost_task *vtsk)
|
||
|
{
|
||
|
pid_t pid = vtsk->task->pid;
|
||
|
|
||
|
set_bit(VHOST_TASK_FLAGS_STOP, &vtsk->flags);
|
||
|
wake_up_process(vtsk->task);
|
||
|
/*
|
||
|
* Make sure vhost_task_fn is no longer accessing the vhost_task before
|
||
|
* freeing it below. If userspace crashed or exited without closing,
|
||
|
* then the vhost_task->task could already be marked dead so
|
||
|
* kernel_wait will return early.
|
||
|
*/
|
||
|
wait_for_completion(&vtsk->exited);
|
||
|
/*
|
||
|
* If we are just closing/removing a device and the parent process is
|
||
|
* not exiting then reap the task.
|
||
|
*/
|
||
|
kernel_wait4(pid, NULL, __WCLONE, NULL);
|
||
|
kfree(vtsk);
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(vhost_task_stop);
|
||
|
|
||
|
/**
|
||
|
* vhost_task_should_stop - should the vhost task return from the work function
|
||
|
* @vtsk: vhost_task to stop
|
||
|
*/
|
||
|
bool vhost_task_should_stop(struct vhost_task *vtsk)
|
||
|
{
|
||
|
return test_bit(VHOST_TASK_FLAGS_STOP, &vtsk->flags);
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(vhost_task_should_stop);
|
||
|
|
||
|
/**
|
||
|
* vhost_task_create - create a copy of a process to be used by the kernel
|
||
|
* @fn: thread stack
|
||
|
* @arg: data to be passed to fn
|
||
|
* @name: the thread's name
|
||
|
*
|
||
|
* This returns a specialized task for use by the vhost layer or NULL on
|
||
|
* failure. The returned task is inactive, and the caller must fire it up
|
||
|
* through vhost_task_start().
|
||
|
*/
|
||
|
struct vhost_task *vhost_task_create(int (*fn)(void *), void *arg,
|
||
|
const char *name)
|
||
|
{
|
||
|
struct kernel_clone_args args = {
|
||
|
.flags = CLONE_FS | CLONE_UNTRACED | CLONE_VM,
|
||
|
.exit_signal = 0,
|
||
|
.fn = vhost_task_fn,
|
||
|
.name = name,
|
||
|
.user_worker = 1,
|
||
|
.no_files = 1,
|
||
|
.ignore_signals = 1,
|
||
|
};
|
||
|
struct vhost_task *vtsk;
|
||
|
struct task_struct *tsk;
|
||
|
|
||
|
vtsk = kzalloc(sizeof(*vtsk), GFP_KERNEL);
|
||
|
if (!vtsk)
|
||
|
return NULL;
|
||
|
init_completion(&vtsk->exited);
|
||
|
vtsk->data = arg;
|
||
|
vtsk->fn = fn;
|
||
|
|
||
|
args.fn_arg = vtsk;
|
||
|
|
||
|
tsk = copy_process(NULL, 0, NUMA_NO_NODE, &args);
|
||
|
if (IS_ERR(tsk)) {
|
||
|
kfree(vtsk);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
vtsk->task = tsk;
|
||
|
return vtsk;
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(vhost_task_create);
|
||
|
|
||
|
/**
|
||
|
* vhost_task_start - start a vhost_task created with vhost_task_create
|
||
|
* @vtsk: vhost_task to wake up
|
||
|
*/
|
||
|
void vhost_task_start(struct vhost_task *vtsk)
|
||
|
{
|
||
|
wake_up_new_task(vtsk->task);
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(vhost_task_start);
|