| /* |
| * 2007+ Copyright (c) Evgeniy Polyakov <zbr@ioremap.net> |
| * All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| */ |
| |
| #include <linux/kernel.h> |
| #include <linux/dst.h> |
| #include <linux/kthread.h> |
| #include <linux/slab.h> |
| |
| /* |
| * Thread pool abstraction allows to schedule a work to be performed |
| * on behalf of kernel thread. One does not operate with threads itself, |
| * instead user provides setup and cleanup callbacks for thread pool itself, |
| * and action and cleanup callbacks for each submitted work. |
| * |
| * Each worker has private data initialized at creation time and data, |
| * provided by user at scheduling time. |
| * |
| * When action is being performed, thread can not be used by other users, |
| * instead they will sleep until there is free thread to pick their work. |
| */ |
| struct thread_pool_worker |
| { |
| struct list_head worker_entry; |
| |
| struct task_struct *thread; |
| |
| struct thread_pool *pool; |
| |
| int error; |
| int has_data; |
| int need_exit; |
| unsigned int id; |
| |
| wait_queue_head_t wait; |
| |
| void *private; |
| void *schedule_data; |
| |
| int (* action)(void *private, void *schedule_data); |
| void (* cleanup)(void *private); |
| }; |
| |
| static void thread_pool_exit_worker(struct thread_pool_worker *w) |
| { |
| kthread_stop(w->thread); |
| |
| w->cleanup(w->private); |
| kfree(w); |
| } |
| |
| /* |
| * Called to mark thread as ready and allow users to schedule new work. |
| */ |
| static void thread_pool_worker_make_ready(struct thread_pool_worker *w) |
| { |
| struct thread_pool *p = w->pool; |
| |
| mutex_lock(&p->thread_lock); |
| |
| if (!w->need_exit) { |
| list_move_tail(&w->worker_entry, &p->ready_list); |
| w->has_data = 0; |
| mutex_unlock(&p->thread_lock); |
| |
| wake_up(&p->wait); |
| } else { |
| p->thread_num--; |
| list_del(&w->worker_entry); |
| mutex_unlock(&p->thread_lock); |
| |
| thread_pool_exit_worker(w); |
| } |
| } |
| |
| /* |
| * Thread action loop: waits until there is new work. |
| */ |
| static int thread_pool_worker_func(void *data) |
| { |
| struct thread_pool_worker *w = data; |
| |
| while (!kthread_should_stop()) { |
| wait_event_interruptible(w->wait, |
| kthread_should_stop() || w->has_data); |
| |
| if (kthread_should_stop()) |
| break; |
| |
| if (!w->has_data) |
| continue; |
| |
| w->action(w->private, w->schedule_data); |
| thread_pool_worker_make_ready(w); |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * Remove single worker without specifying which one. |
| */ |
| void thread_pool_del_worker(struct thread_pool *p) |
| { |
| struct thread_pool_worker *w = NULL; |
| |
| while (!w && p->thread_num) { |
| wait_event(p->wait, !list_empty(&p->ready_list) || !p->thread_num); |
| |
| dprintk("%s: locking list_empty: %d, thread_num: %d.\n", |
| __func__, list_empty(&p->ready_list), p->thread_num); |
| |
| mutex_lock(&p->thread_lock); |
| if (!list_empty(&p->ready_list)) { |
| w = list_first_entry(&p->ready_list, |
| struct thread_pool_worker, |
| worker_entry); |
| |
| dprintk("%s: deleting w: %p, thread_num: %d, list: %p [%p.%p].\n", |
| __func__, w, p->thread_num, &p->ready_list, |
| p->ready_list.prev, p->ready_list.next); |
| |
| p->thread_num--; |
| list_del(&w->worker_entry); |
| } |
| mutex_unlock(&p->thread_lock); |
| } |
| |
| if (w) |
| thread_pool_exit_worker(w); |
| dprintk("%s: deleted w: %p, thread_num: %d.\n", |
| __func__, w, p->thread_num); |
| } |
| |
| /* |
| * Remove a worker with given ID. |
| */ |
| void thread_pool_del_worker_id(struct thread_pool *p, unsigned int id) |
| { |
| struct thread_pool_worker *w; |
| int found = 0; |
| |
| mutex_lock(&p->thread_lock); |
| list_for_each_entry(w, &p->ready_list, worker_entry) { |
| if (w->id == id) { |
| found = 1; |
| p->thread_num--; |
| list_del(&w->worker_entry); |
| break; |
| } |
| } |
| |
| if (!found) { |
| list_for_each_entry(w, &p->active_list, worker_entry) { |
| if (w->id == id) { |
| w->need_exit = 1; |
| break; |
| } |
| } |
| } |
| mutex_unlock(&p->thread_lock); |
| |
| if (found) |
| thread_pool_exit_worker(w); |
| } |
| |
| /* |
| * Add new worker thread with given parameters. |
| * If initialization callback fails, return error. |
| */ |
| int thread_pool_add_worker(struct thread_pool *p, |
| char *name, |
| unsigned int id, |
| void *(* init)(void *private), |
| void (* cleanup)(void *private), |
| void *private) |
| { |
| struct thread_pool_worker *w; |
| int err = -ENOMEM; |
| |
| w = kzalloc(sizeof(struct thread_pool_worker), GFP_KERNEL); |
| if (!w) |
| goto err_out_exit; |
| |
| w->pool = p; |
| init_waitqueue_head(&w->wait); |
| w->cleanup = cleanup; |
| w->id = id; |
| |
| w->thread = kthread_run(thread_pool_worker_func, w, "%s", name); |
| if (IS_ERR(w->thread)) { |
| err = PTR_ERR(w->thread); |
| goto err_out_free; |
| } |
| |
| w->private = init(private); |
| if (IS_ERR(w->private)) { |
| err = PTR_ERR(w->private); |
| goto err_out_stop_thread; |
| } |
| |
| mutex_lock(&p->thread_lock); |
| list_add_tail(&w->worker_entry, &p->ready_list); |
| p->thread_num++; |
| mutex_unlock(&p->thread_lock); |
| |
| return 0; |
| |
| err_out_stop_thread: |
| kthread_stop(w->thread); |
| err_out_free: |
| kfree(w); |
| err_out_exit: |
| return err; |
| } |
| |
| /* |
| * Destroy the whole pool. |
| */ |
| void thread_pool_destroy(struct thread_pool *p) |
| { |
| while (p->thread_num) { |
| dprintk("%s: num: %d.\n", __func__, p->thread_num); |
| thread_pool_del_worker(p); |
| } |
| |
| kfree(p); |
| } |
| |
| /* |
| * Create a pool with given number of threads. |
| * They will have sequential IDs started from zero. |
| */ |
| struct thread_pool *thread_pool_create(int num, char *name, |
| void *(* init)(void *private), |
| void (* cleanup)(void *private), |
| void *private) |
| { |
| struct thread_pool_worker *w, *tmp; |
| struct thread_pool *p; |
| int err = -ENOMEM; |
| int i; |
| |
| p = kzalloc(sizeof(struct thread_pool), GFP_KERNEL); |
| if (!p) |
| goto err_out_exit; |
| |
| init_waitqueue_head(&p->wait); |
| mutex_init(&p->thread_lock); |
| INIT_LIST_HEAD(&p->ready_list); |
| INIT_LIST_HEAD(&p->active_list); |
| p->thread_num = 0; |
| |
| for (i=0; i<num; ++i) { |
| err = thread_pool_add_worker(p, name, i, init, |
| cleanup, private); |
| if (err) |
| goto err_out_free_all; |
| } |
| |
| return p; |
| |
| err_out_free_all: |
| list_for_each_entry_safe(w, tmp, &p->ready_list, worker_entry) { |
| list_del(&w->worker_entry); |
| thread_pool_exit_worker(w); |
| } |
| kfree(p); |
| err_out_exit: |
| return ERR_PTR(err); |
| } |
| |
| /* |
| * Schedule execution of the action on a given thread, |
| * provided ID pointer has to match previously stored |
| * private data. |
| */ |
| int thread_pool_schedule_private(struct thread_pool *p, |
| int (* setup)(void *private, void *data), |
| int (* action)(void *private, void *data), |
| void *data, long timeout, void *id) |
| { |
| struct thread_pool_worker *w, *tmp, *worker = NULL; |
| int err = 0; |
| |
| while (!worker && !err) { |
| timeout = wait_event_interruptible_timeout(p->wait, |
| !list_empty(&p->ready_list), |
| timeout); |
| |
| if (!timeout) { |
| err = -ETIMEDOUT; |
| break; |
| } |
| |
| worker = NULL; |
| mutex_lock(&p->thread_lock); |
| list_for_each_entry_safe(w, tmp, &p->ready_list, worker_entry) { |
| if (id && id != w->private) |
| continue; |
| |
| worker = w; |
| |
| list_move_tail(&w->worker_entry, &p->active_list); |
| |
| err = setup(w->private, data); |
| if (!err) { |
| w->schedule_data = data; |
| w->action = action; |
| w->has_data = 1; |
| wake_up(&w->wait); |
| } else { |
| list_move_tail(&w->worker_entry, &p->ready_list); |
| } |
| |
| break; |
| } |
| mutex_unlock(&p->thread_lock); |
| } |
| |
| return err; |
| } |
| |
| /* |
| * Schedule execution on arbitrary thread from the pool. |
| */ |
| int thread_pool_schedule(struct thread_pool *p, |
| int (* setup)(void *private, void *data), |
| int (* action)(void *private, void *data), |
| void *data, long timeout) |
| { |
| return thread_pool_schedule_private(p, setup, |
| action, data, timeout, NULL); |
| } |