| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164 |
- // 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,
- VHOST_TASK_FLAGS_KILLED,
- };
- struct vhost_task {
- bool (*fn)(void *data);
- void (*handle_sigkill)(void *data);
- void *data;
- struct completion exited;
- unsigned long flags;
- struct task_struct *task;
- /* serialize SIGKILL and vhost_task_stop calls */
- struct mutex exit_mutex;
- };
- static int vhost_task_fn(void *data)
- {
- struct vhost_task *vtsk = data;
- for (;;) {
- bool did_work;
- if (signal_pending(current)) {
- struct ksignal ksig;
- if (get_signal(&ksig))
- break;
- }
- /* mb paired w/ vhost_task_stop */
- set_current_state(TASK_INTERRUPTIBLE);
- if (test_bit(VHOST_TASK_FLAGS_STOP, &vtsk->flags)) {
- __set_current_state(TASK_RUNNING);
- break;
- }
- did_work = vtsk->fn(vtsk->data);
- if (!did_work)
- schedule();
- }
- mutex_lock(&vtsk->exit_mutex);
- /*
- * If a vhost_task_stop and SIGKILL race, we can ignore the SIGKILL.
- * When the vhost layer has called vhost_task_stop it's already stopped
- * new work and flushed.
- */
- if (!test_bit(VHOST_TASK_FLAGS_STOP, &vtsk->flags)) {
- set_bit(VHOST_TASK_FLAGS_KILLED, &vtsk->flags);
- vtsk->handle_sigkill(vtsk->data);
- }
- mutex_unlock(&vtsk->exit_mutex);
- complete(&vtsk->exited);
- do_exit(0);
- }
- /**
- * vhost_task_wake - wakeup the vhost_task
- * @vtsk: vhost_task to wake
- *
- * wake up the vhost_task worker thread
- */
- void vhost_task_wake(struct vhost_task *vtsk)
- {
- wake_up_process(vtsk->task);
- }
- EXPORT_SYMBOL_GPL(vhost_task_wake);
- /**
- * vhost_task_stop - stop a vhost_task
- * @vtsk: vhost_task to stop
- *
- * vhost_task_fn ensures the worker thread exits after
- * VHOST_TASK_FLAGS_STOP becomes true.
- */
- void vhost_task_stop(struct vhost_task *vtsk)
- {
- mutex_lock(&vtsk->exit_mutex);
- if (!test_bit(VHOST_TASK_FLAGS_KILLED, &vtsk->flags)) {
- set_bit(VHOST_TASK_FLAGS_STOP, &vtsk->flags);
- vhost_task_wake(vtsk);
- }
- mutex_unlock(&vtsk->exit_mutex);
- /*
- * Make sure vhost_task_fn is no longer accessing the vhost_task before
- * freeing it below.
- */
- wait_for_completion(&vtsk->exited);
- kfree(vtsk);
- }
- EXPORT_SYMBOL_GPL(vhost_task_stop);
- /**
- * vhost_task_create - create a copy of a task to be used by the kernel
- * @fn: vhost worker function
- * @handle_sigkill: vhost function to handle when we are killed
- * @arg: data to be passed to fn and handled_kill
- * @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(bool (*fn)(void *),
- void (*handle_sigkill)(void *), void *arg,
- const char *name)
- {
- struct kernel_clone_args args = {
- .flags = CLONE_FS | CLONE_UNTRACED | CLONE_VM |
- CLONE_THREAD | CLONE_SIGHAND,
- .exit_signal = 0,
- .fn = vhost_task_fn,
- .name = name,
- .user_worker = 1,
- .no_files = 1,
- };
- struct vhost_task *vtsk;
- struct task_struct *tsk;
- vtsk = kzalloc(sizeof(*vtsk), GFP_KERNEL);
- if (!vtsk)
- return NULL;
- init_completion(&vtsk->exited);
- mutex_init(&vtsk->exit_mutex);
- vtsk->data = arg;
- vtsk->fn = fn;
- vtsk->handle_sigkill = handle_sigkill;
- 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);
|