summaryrefslogtreecommitdiff
path: root/kernel
diff options
context:
space:
mode:
authorIngo Molnar <mingo@elte.hu>2002-09-30 22:17:42 -0700
committerIngo Molnar <mingo@elte.hu>2002-09-30 22:17:42 -0700
commit6ed12ff83c765aeda7d38d3bf9df7d46d24bfb11 (patch)
treed2dd4a9cefd38743d3e51fbbab3d79920bb19ae1 /kernel
parent7570df54ef8cc5b42500d26562ff50fcbe265aa2 (diff)
[PATCH] Workqueue Abstraction
This is the next iteration of the workqueue abstraction. The framework includes: - per-CPU queueing support. on SMP there is a per-CPU worker thread (bound to its CPU) and per-CPU work queues - this feature is completely transparent to workqueue-users. keventd automatically uses this feature. XFS can now update to work-queues and have the same per-CPU performance as it had with its per-CPU worker threads. - delayed work submission there's a new queue_delayed_work(wq, work, delay) function and a new schedule_delayed_work(work, delay) function. The later one is used to correctly fix former tq_timer users. I've reverted those changes in 2.5.40 that changed tq_timer uses to schedule_work() - eg. in the case of random.c or the tty flip queue it was definitely the wrong thing to do. delayed work means a timer embedded in struct work_struct. I considered using split struct work_struct and delayed_work_struct types, but lots of code actively uses task-queues in both delayed and non-delayed mode, so i went for the more generic approach that allows both methods of work submission. Delayed timers do not cause any other overhead in the normal submission path otherwise. - multithreaded run_workqueue() implementation the run_workqueue() function can now be called from multiple contexts, and a worker thread will only use up a single entryy - this property is used by the flushing code, and can potentially be used in the future to extend the number of per-CPU worker threads. - more reliable flushing there's now a 'pending work' counter, which is used to accurately detect when the last work-function has finished execution. It's also used to correctly flush against timed requests. I'm not convinced whether the old keventd implementation got this detail right. - i switched the arguments of the queueing function(s) per Jeff's suggestion, it's more straightforward this way. Driver fixes: i have converted almost every affected driver to the new framework. This cleaned up tons of code. I also fixed a number of drivers that were still using BHs (these drivers did not compile in 2.5.40). while this means lots of changes, it might ease the QA decision whether to put this patch into 2.5. The pach converts roughly 80% of all tqueue-using code to workqueues - and all the places that are not converted to workqueues yet are places that do not compile in vanilla 2.5.40 anyway, due to unrelated changes. I've converted a fair number of drivers that do not compile in 2.5.40, and i think i've managed to convert every driver that compiles under 2.5.40.
Diffstat (limited to 'kernel')
-rw-r--r--kernel/Makefile4
-rw-r--r--kernel/context.c221
-rw-r--r--kernel/kmod.c15
-rw-r--r--kernel/sys.c8
-rw-r--r--kernel/workqueue.c387
5 files changed, 398 insertions, 237 deletions
diff --git a/kernel/Makefile b/kernel/Makefile
index 3f36152dac1b..b3fce6d3ac9c 100644
--- a/kernel/Makefile
+++ b/kernel/Makefile
@@ -2,13 +2,13 @@
# Makefile for the linux kernel.
#
-export-objs = signal.o sys.o kmod.o context.o ksyms.o pm.o exec_domain.o \
+export-objs = signal.o sys.o kmod.o workqueue.o ksyms.o pm.o exec_domain.o \
printk.o platform.o suspend.o dma.o module.o cpufreq.o
obj-y = sched.o fork.o exec_domain.o panic.o printk.o \
module.o exit.o itimer.o time.o softirq.o resource.o \
sysctl.o capability.o ptrace.o timer.o user.o \
- signal.o sys.o kmod.o context.o futex.o platform.o pid.o
+ signal.o sys.o kmod.o workqueue.o futex.o platform.o pid.o
obj-$(CONFIG_GENERIC_ISA_DMA) += dma.o
obj-$(CONFIG_SMP) += cpu.o
diff --git a/kernel/context.c b/kernel/context.c
deleted file mode 100644
index ae836aaf2ddf..000000000000
--- a/kernel/context.c
+++ /dev/null
@@ -1,221 +0,0 @@
-/*
- * linux/kernel/context.c
- *
- * Mechanism for running arbitrary tasks in process context
- *
- * dwmw2@redhat.com: Genesis
- *
- * andrewm@uow.edu.au: 2.4.0-test12
- * - Child reaping
- * - Support for tasks which re-add themselves
- * - flush_scheduled_tasks.
- */
-
-#define __KERNEL_SYSCALLS__
-
-#include <linux/module.h>
-#include <linux/kernel.h>
-#include <linux/sched.h>
-#include <linux/init.h>
-#include <linux/unistd.h>
-#include <linux/signal.h>
-#include <linux/completion.h>
-#include <linux/tqueue.h>
-
-static DECLARE_TASK_QUEUE(tq_context);
-static DECLARE_WAIT_QUEUE_HEAD(context_task_wq);
-static DECLARE_WAIT_QUEUE_HEAD(context_task_done);
-static int keventd_running;
-static struct task_struct *keventd_task;
-
-static spinlock_t tqueue_lock __cacheline_aligned_in_smp = SPIN_LOCK_UNLOCKED;
-
-typedef struct list_head task_queue;
-
-/*
- * Queue a task on a tq. Return non-zero if it was successfully
- * added.
- */
-static inline int queue_task(struct tq_struct *tq, task_queue *list)
-{
- int ret = 0;
- unsigned long flags;
-
- if (!test_and_set_bit(0, &tq->sync)) {
- spin_lock_irqsave(&tqueue_lock, flags);
- list_add_tail(&tq->list, list);
- spin_unlock_irqrestore(&tqueue_lock, flags);
- ret = 1;
- }
- return ret;
-}
-
-#define TQ_ACTIVE(q) (!list_empty(&q))
-
-static inline void run_task_queue(task_queue *list)
-{
- struct list_head head, *next;
- unsigned long flags;
-
- if (!TQ_ACTIVE(*list))
- return;
-
- spin_lock_irqsave(&tqueue_lock, flags);
- list_add(&head, list);
- list_del_init(list);
- spin_unlock_irqrestore(&tqueue_lock, flags);
-
- next = head.next;
- while (next != &head) {
- void (*f) (void *);
- struct tq_struct *p;
- void *data;
-
- p = list_entry(next, struct tq_struct, list);
- next = next->next;
- f = p->routine;
- data = p->data;
- wmb();
- p->sync = 0;
- if (f)
- f(data);
- }
-}
-
-static int need_keventd(const char *who)
-{
- if (keventd_running == 0)
- printk(KERN_ERR "%s(): keventd has not started\n", who);
- return keventd_running;
-}
-
-int current_is_keventd(void)
-{
- int ret = 0;
- if (need_keventd(__FUNCTION__))
- ret = (current == keventd_task);
- return ret;
-}
-
-/**
- * schedule_task - schedule a function for subsequent execution in process context.
- * @task: pointer to a &tq_struct which defines the function to be scheduled.
- *
- * May be called from interrupt context. The scheduled function is run at some
- * time in the near future by the keventd kernel thread. If it can sleep, it
- * should be designed to do so for the minimum possible time, as it will be
- * stalling all other scheduled tasks.
- *
- * schedule_task() returns non-zero if the task was successfully scheduled.
- * If @task is already residing on a task queue then schedule_task() fails
- * to schedule your task and returns zero.
- */
-int schedule_task(struct tq_struct *task)
-{
- int ret;
- need_keventd(__FUNCTION__);
- ret = queue_task(task, &tq_context);
- wake_up(&context_task_wq);
- return ret;
-}
-
-static int context_thread(void *startup)
-{
- struct task_struct *curtask = current;
- DECLARE_WAITQUEUE(wait, curtask);
- struct k_sigaction sa;
-
- daemonize();
- strcpy(curtask->comm, "keventd");
- current->flags |= PF_IOTHREAD;
- keventd_running = 1;
- keventd_task = curtask;
-
- spin_lock_irq(&curtask->sig->siglock);
- siginitsetinv(&curtask->blocked, sigmask(SIGCHLD));
- recalc_sigpending();
- spin_unlock_irq(&curtask->sig->siglock);
-
- complete((struct completion *)startup);
-
- /* Install a handler so SIGCLD is delivered */
- sa.sa.sa_handler = SIG_IGN;
- sa.sa.sa_flags = 0;
- siginitset(&sa.sa.sa_mask, sigmask(SIGCHLD));
- do_sigaction(SIGCHLD, &sa, (struct k_sigaction *)0);
-
- /*
- * If one of the functions on a task queue re-adds itself
- * to the task queue we call schedule() in state TASK_RUNNING
- */
- for (;;) {
- set_task_state(curtask, TASK_INTERRUPTIBLE);
- add_wait_queue(&context_task_wq, &wait);
- if (TQ_ACTIVE(tq_context))
- set_task_state(curtask, TASK_RUNNING);
- schedule();
- remove_wait_queue(&context_task_wq, &wait);
- run_task_queue(&tq_context);
- wake_up(&context_task_done);
- if (signal_pending(curtask)) {
- while (waitpid(-1, (unsigned int *)0, __WALL|WNOHANG) > 0)
- ;
- spin_lock_irq(&curtask->sig->siglock);
- flush_signals(curtask);
- recalc_sigpending();
- spin_unlock_irq(&curtask->sig->siglock);
- }
- }
-}
-
-/**
- * flush_scheduled_tasks - ensure that any scheduled tasks have run to completion.
- *
- * Forces execution of the schedule_task() queue and blocks until its completion.
- *
- * If a kernel subsystem uses schedule_task() and wishes to flush any pending
- * tasks, it should use this function. This is typically used in driver shutdown
- * handlers.
- *
- * The caller should hold no spinlocks and should hold no semaphores which could
- * cause the scheduled tasks to block.
- */
-static struct tq_struct dummy_task;
-
-void flush_scheduled_tasks(void)
-{
- int count;
- DECLARE_WAITQUEUE(wait, current);
-
- /*
- * Do it twice. It's possible, albeit highly unlikely, that
- * the caller queued a task immediately before calling us,
- * and that the eventd thread was already past the run_task_queue()
- * but not yet into wake_up(), so it woke us up before completing
- * the caller's queued task or our new dummy task.
- */
- add_wait_queue(&context_task_done, &wait);
- for (count = 0; count < 2; count++) {
- set_current_state(TASK_UNINTERRUPTIBLE);
-
- /* Queue a dummy task to make sure we get kicked */
- schedule_task(&dummy_task);
-
- /* Wait for it to complete */
- schedule();
- }
- remove_wait_queue(&context_task_done, &wait);
-}
-
-int start_context_thread(void)
-{
- static struct completion startup __initdata = COMPLETION_INITIALIZER(startup);
-
- kernel_thread(context_thread, &startup, CLONE_FS | CLONE_FILES);
- wait_for_completion(&startup);
- return 0;
-}
-
-EXPORT_SYMBOL(schedule_task);
-EXPORT_SYMBOL(flush_scheduled_tasks);
-
diff --git a/kernel/kmod.c b/kernel/kmod.c
index b4abfdfe9888..755e5807e815 100644
--- a/kernel/kmod.c
+++ b/kernel/kmod.c
@@ -28,7 +28,7 @@
#include <linux/namespace.h>
#include <linux/completion.h>
#include <linux/file.h>
-#include <linux/tqueue.h>
+#include <linux/workqueue.h>
#include <asm/uaccess.h>
@@ -346,18 +346,15 @@ static void __call_usermodehelper(void *data)
*/
int call_usermodehelper(char *path, char **argv, char **envp)
{
- DECLARE_COMPLETION(work);
+ DECLARE_COMPLETION(done);
struct subprocess_info sub_info = {
- .complete = &work,
+ .complete = &done,
.path = path,
.argv = argv,
.envp = envp,
.retval = 0,
};
- struct tq_struct tqs = {
- .routine = __call_usermodehelper,
- .data = &sub_info,
- };
+ DECLARE_WORK(work, __call_usermodehelper, &sub_info);
if (!system_running)
return -EBUSY;
@@ -369,8 +366,8 @@ int call_usermodehelper(char *path, char **argv, char **envp)
/* We can't wait on keventd! */
__call_usermodehelper(&sub_info);
} else {
- schedule_task(&tqs);
- wait_for_completion(&work);
+ schedule_work(&work);
+ wait_for_completion(&done);
}
out:
return sub_info.retval;
diff --git a/kernel/sys.c b/kernel/sys.c
index 636a5e6b6768..5b7e84384cfa 100644
--- a/kernel/sys.c
+++ b/kernel/sys.c
@@ -16,7 +16,7 @@
#include <linux/init.h>
#include <linux/highuid.h>
#include <linux/fs.h>
-#include <linux/tqueue.h>
+#include <linux/workqueue.h>
#include <linux/device.h>
#include <linux/times.h>
#include <linux/security.h>
@@ -442,12 +442,10 @@ static void deferred_cad(void *dummy)
*/
void ctrl_alt_del(void)
{
- static struct tq_struct cad_tq = {
- .routine = deferred_cad,
- };
+ static DECLARE_WORK(cad_work, deferred_cad, NULL);
if (C_A_D)
- schedule_task(&cad_tq);
+ schedule_work(&cad_work);
else
kill_proc(cad_pid, SIGINT, 1);
}
diff --git a/kernel/workqueue.c b/kernel/workqueue.c
new file mode 100644
index 000000000000..5fab2cd1933f
--- /dev/null
+++ b/kernel/workqueue.c
@@ -0,0 +1,387 @@
+/*
+ * linux/kernel/workqueue.c
+ *
+ * Generic mechanism for defining kernel helper threads for running
+ * arbitrary tasks in process context.
+ *
+ * Started by Ingo Molnar, Copyright (C) 2002
+ *
+ * Derived from the taskqueue/keventd code by:
+ *
+ * David Woodhouse <dwmw2@redhat.com>
+ * Andrew Morton <andrewm@uow.edu.au>
+ * Kai Petzke <wpp@marie.physik.tu-berlin.de>
+ * Theodore Ts'o <tytso@mit.edu>
+ */
+
+#define __KERNEL_SYSCALLS__
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/init.h>
+#include <linux/unistd.h>
+#include <linux/signal.h>
+#include <linux/completion.h>
+#include <linux/workqueue.h>
+#include <linux/slab.h>
+
+/*
+ * The per-CPU workqueue:
+ */
+struct cpu_workqueue_struct {
+
+ spinlock_t lock;
+
+ atomic_t nr_queued;
+ struct list_head worklist;
+ wait_queue_head_t more_work;
+ wait_queue_head_t work_done;
+
+ struct workqueue_struct *wq;
+ task_t *thread;
+ struct completion exit;
+
+} ____cacheline_aligned;
+
+/*
+ * The externally visible workqueue abstraction is an array of
+ * per-CPU workqueues:
+ */
+struct workqueue_struct {
+ struct cpu_workqueue_struct cpu_wq[NR_CPUS];
+};
+
+/*
+ * Queue work on a workqueue. Return non-zero if it was successfully
+ * added.
+ *
+ * We queue the work to the CPU it was submitted, but there is no
+ * guarantee that it will be processed by that CPU.
+ */
+int queue_work(struct workqueue_struct *wq, struct work_struct *work)
+{
+ unsigned long flags;
+ int ret = 0, cpu = get_cpu();
+ struct cpu_workqueue_struct *cwq = wq->cpu_wq + cpu;
+
+ if (!test_and_set_bit(0, &work->pending)) {
+ BUG_ON(!list_empty(&work->entry));
+ work->wq_data = cwq;
+
+ spin_lock_irqsave(&cwq->lock, flags);
+ list_add_tail(&work->entry, &cwq->worklist);
+ atomic_inc(&cwq->nr_queued);
+ spin_unlock_irqrestore(&cwq->lock, flags);
+
+ wake_up(&cwq->more_work);
+ ret = 1;
+ }
+ put_cpu();
+ return ret;
+}
+
+static void delayed_work_timer_fn(unsigned long __data)
+{
+ struct work_struct *work = (struct work_struct *)__data;
+ struct cpu_workqueue_struct *cwq = work->wq_data;
+ unsigned long flags;
+
+ /*
+ * Do the wakeup within the spinlock, so that flushing
+ * can be done in a guaranteed way.
+ */
+ spin_lock_irqsave(&cwq->lock, flags);
+ list_add_tail(&work->entry, &cwq->worklist);
+ wake_up(&cwq->more_work);
+ spin_unlock_irqrestore(&cwq->lock, flags);
+}
+
+int queue_delayed_work(struct workqueue_struct *wq, struct work_struct *work, unsigned long delay)
+{
+ int ret = 0, cpu = get_cpu();
+ timer_t *timer = &work->timer;
+ struct cpu_workqueue_struct *cwq = wq->cpu_wq + cpu;
+
+ if (!test_and_set_bit(0, &work->pending)) {
+ BUG_ON(timer_pending(timer));
+ BUG_ON(!list_empty(&work->entry));
+
+ /*
+ * Increase nr_queued so that the flush function
+ * knows that there's something pending.
+ */
+ atomic_inc(&cwq->nr_queued);
+ work->wq_data = cwq;
+
+ timer->expires = jiffies + delay;
+ timer->data = (unsigned long)work;
+ timer->function = delayed_work_timer_fn;
+ add_timer(timer);
+
+ ret = 1;
+ }
+ put_cpu();
+ return ret;
+}
+
+static inline void run_workqueue(struct cpu_workqueue_struct *cwq)
+{
+ unsigned long flags;
+
+ /*
+ * Keep taking off work from the queue until
+ * done.
+ */
+ spin_lock_irqsave(&cwq->lock, flags);
+ while (!list_empty(&cwq->worklist)) {
+ struct work_struct *work = list_entry(cwq->worklist.next, struct work_struct, entry);
+ void (*f) (void *) = work->func;
+ void *data = work->data;
+
+ list_del_init(cwq->worklist.next);
+ spin_unlock_irqrestore(&cwq->lock, flags);
+
+ BUG_ON(work->wq_data != cwq);
+ clear_bit(0, &work->pending);
+ f(data);
+
+ /*
+ * We only wake up 'work done' waiters (flush) when
+ * the last function has been fully processed.
+ */
+ if (atomic_dec_and_test(&cwq->nr_queued))
+ wake_up(&cwq->work_done);
+
+ spin_lock_irqsave(&cwq->lock, flags);
+ }
+ spin_unlock_irqrestore(&cwq->lock, flags);
+}
+
+typedef struct startup_s {
+ struct cpu_workqueue_struct *cwq;
+ struct completion done;
+ const char *name;
+} startup_t;
+
+static int worker_thread(void *__startup)
+{
+ startup_t *startup = __startup;
+ struct cpu_workqueue_struct *cwq = startup->cwq;
+ int cpu = cwq - cwq->wq->cpu_wq;
+ DECLARE_WAITQUEUE(wait, current);
+ struct k_sigaction sa;
+
+ daemonize();
+ sprintf(current->comm, "%s/%d", startup->name, cpu);
+ current->flags |= PF_IOTHREAD;
+ cwq->thread = current;
+
+ set_cpus_allowed(current, 1UL << cpu);
+
+ spin_lock_irq(&current->sig->siglock);
+ siginitsetinv(&current->blocked, sigmask(SIGCHLD));
+ recalc_sigpending();
+ spin_unlock_irq(&current->sig->siglock);
+
+ complete(&startup->done);
+
+ /* Install a handler so SIGCLD is delivered */
+ sa.sa.sa_handler = SIG_IGN;
+ sa.sa.sa_flags = 0;
+ siginitset(&sa.sa.sa_mask, sigmask(SIGCHLD));
+ do_sigaction(SIGCHLD, &sa, (struct k_sigaction *)0);
+
+ for (;;) {
+ set_task_state(current, TASK_INTERRUPTIBLE);
+
+ add_wait_queue(&cwq->more_work, &wait);
+ if (!cwq->thread)
+ break;
+ if (list_empty(&cwq->worklist))
+ schedule();
+ else
+ set_task_state(current, TASK_RUNNING);
+ remove_wait_queue(&cwq->more_work, &wait);
+
+ if (!list_empty(&cwq->worklist))
+ run_workqueue(cwq);
+
+ if (signal_pending(current)) {
+ while (waitpid(-1, NULL, __WALL|WNOHANG) > 0)
+ /* SIGCHLD - auto-reaping */ ;
+
+ /* zap all other signals */
+ spin_lock_irq(&current->sig->siglock);
+ flush_signals(current);
+ recalc_sigpending();
+ spin_unlock_irq(&current->sig->siglock);
+ }
+ }
+ remove_wait_queue(&cwq->more_work, &wait);
+ complete(&cwq->exit);
+
+ return 0;
+}
+
+/*
+ * flush_workqueue - ensure that any scheduled work has run to completion.
+ *
+ * Forces execution of the workqueue and blocks until its completion.
+ * This is typically used in driver shutdown handlers.
+ *
+ * NOTE: if work is being added to the queue constantly by some other
+ * context then this function might block indefinitely.
+ */
+void flush_workqueue(struct workqueue_struct *wq)
+{
+ struct cpu_workqueue_struct *cwq;
+ int cpu;
+
+ for (cpu = 0; cpu < NR_CPUS; cpu++) {
+ if (!cpu_online(cpu))
+ continue;
+ cwq = wq->cpu_wq + cpu;
+
+ if (atomic_read(&cwq->nr_queued)) {
+ DECLARE_WAITQUEUE(wait, current);
+
+ if (!list_empty(&cwq->worklist))
+ run_workqueue(cwq);
+
+ /*
+ * Wait for helper thread(s) to finish up
+ * the queue:
+ */
+ set_task_state(current, TASK_INTERRUPTIBLE);
+ add_wait_queue(&cwq->work_done, &wait);
+ if (atomic_read(&cwq->nr_queued))
+ schedule();
+ else
+ set_task_state(current, TASK_RUNNING);
+ remove_wait_queue(&cwq->work_done, &wait);
+ }
+ }
+}
+
+struct workqueue_struct *create_workqueue(const char *name)
+{
+ int ret, cpu, destroy = 0;
+ struct cpu_workqueue_struct *cwq;
+ startup_t startup;
+ struct workqueue_struct *wq;
+
+ BUG_ON(strlen(name) > 10);
+ startup.name = name;
+
+ wq = kmalloc(sizeof(*wq), GFP_KERNEL);
+ if (!wq)
+ return NULL;
+
+ for (cpu = 0; cpu < NR_CPUS; cpu++) {
+ if (!cpu_online(cpu))
+ continue;
+ cwq = wq->cpu_wq + cpu;
+
+ spin_lock_init(&cwq->lock);
+ cwq->wq = wq;
+ cwq->thread = NULL;
+ atomic_set(&cwq->nr_queued, 0);
+ INIT_LIST_HEAD(&cwq->worklist);
+ init_waitqueue_head(&cwq->more_work);
+ init_waitqueue_head(&cwq->work_done);
+
+ init_completion(&startup.done);
+ startup.cwq = cwq;
+ ret = kernel_thread(worker_thread, &startup,
+ CLONE_FS | CLONE_FILES);
+ if (ret < 0)
+ destroy = 1;
+ else {
+ wait_for_completion(&startup.done);
+ BUG_ON(!cwq->thread);
+ }
+ }
+ /*
+ * Was there any error during startup? If yes then clean up:
+ */
+ if (destroy) {
+ destroy_workqueue(wq);
+ wq = NULL;
+ }
+ return wq;
+}
+
+void destroy_workqueue(struct workqueue_struct *wq)
+{
+ struct cpu_workqueue_struct *cwq;
+ int cpu;
+
+ for (cpu = 0; cpu < NR_CPUS; cpu++) {
+ if (!cpu_online(cpu))
+ continue;
+ cwq = wq->cpu_wq + cpu;
+ if (!cwq->thread)
+ continue;
+ /*
+ * Initiate an exit and wait for it:
+ */
+ init_completion(&cwq->exit);
+ cwq->thread = NULL;
+ wake_up(&cwq->more_work);
+
+ wait_for_completion(&cwq->exit);
+ }
+ kfree(wq);
+}
+
+static struct workqueue_struct *keventd_wq;
+
+int schedule_work(struct work_struct *work)
+{
+ return queue_work(keventd_wq, work);
+}
+
+int schedule_delayed_work(struct work_struct *work, unsigned long delay)
+{
+ return queue_delayed_work(keventd_wq, work, delay);
+}
+
+void flush_scheduled_work(void)
+{
+ flush_workqueue(keventd_wq);
+}
+
+int current_is_keventd(void)
+{
+ struct cpu_workqueue_struct *cwq;
+ int cpu;
+
+ BUG_ON(!keventd_wq);
+
+ for (cpu = 0; cpu < NR_CPUS; cpu++) {
+ if (!cpu_online(cpu))
+ continue;
+ cwq = keventd_wq->cpu_wq + cpu;
+ if (current == cwq->thread)
+ return 1;
+ }
+ return 0;
+}
+
+void init_workqueues(void)
+{
+ keventd_wq = create_workqueue("events");
+ BUG_ON(!keventd_wq);
+}
+
+EXPORT_SYMBOL_GPL(create_workqueue);
+EXPORT_SYMBOL_GPL(queue_work);
+EXPORT_SYMBOL_GPL(queue_delayed_work);
+EXPORT_SYMBOL_GPL(flush_workqueue);
+EXPORT_SYMBOL_GPL(destroy_workqueue);
+
+EXPORT_SYMBOL(schedule_work);
+EXPORT_SYMBOL(schedule_delayed_work);
+EXPORT_SYMBOL(flush_scheduled_work);
+