diff options
Diffstat (limited to 'kernel/task_work.c')
-rw-r--r-- | kernel/task_work.c | 80 |
1 files changed, 80 insertions, 0 deletions
diff --git a/kernel/task_work.c b/kernel/task_work.c new file mode 100644 index 00000000000..91d4e1742a0 --- /dev/null +++ b/kernel/task_work.c @@ -0,0 +1,80 @@ +#include <linux/spinlock.h> +#include <linux/task_work.h> +#include <linux/tracehook.h> + +int +task_work_add(struct task_struct *task, struct callback_head *twork, bool notify) +{ + struct callback_head *last, *first; + unsigned long flags; + + /* + * Not inserting the new work if the task has already passed + * exit_task_work() is the responisbility of callers. + */ + raw_spin_lock_irqsave(&task->pi_lock, flags); + last = task->task_works; + first = last ? last->next : twork; + twork->next = first; + if (last) + last->next = twork; + task->task_works = twork; + raw_spin_unlock_irqrestore(&task->pi_lock, flags); + + /* test_and_set_bit() implies mb(), see tracehook_notify_resume(). */ + if (notify) + set_notify_resume(task); + return 0; +} + +struct callback_head * +task_work_cancel(struct task_struct *task, task_work_func_t func) +{ + unsigned long flags; + struct callback_head *last, *res = NULL; + + raw_spin_lock_irqsave(&task->pi_lock, flags); + last = task->task_works; + if (last) { + struct callback_head *q = last, *p = q->next; + while (1) { + if (p->func == func) { + q->next = p->next; + if (p == last) + task->task_works = q == p ? NULL : q; + res = p; + break; + } + if (p == last) + break; + q = p; + p = q->next; + } + } + raw_spin_unlock_irqrestore(&task->pi_lock, flags); + return res; +} + +void task_work_run(void) +{ + struct task_struct *task = current; + struct callback_head *p, *q; + + while (1) { + raw_spin_lock_irq(&task->pi_lock); + p = task->task_works; + task->task_works = NULL; + raw_spin_unlock_irq(&task->pi_lock); + + if (unlikely(!p)) + return; + + q = p->next; /* head */ + p->next = NULL; /* cut it */ + while (q) { + p = q->next; + q->func(q); + q = p; + } + } +} |