Add the rt linux 4.1.3-rt3 as base
[kvmfornfv.git] / kernel / kernel / sched / work-simple.c
diff --git a/kernel/kernel/sched/work-simple.c b/kernel/kernel/sched/work-simple.c
new file mode 100644 (file)
index 0000000..c996f75
--- /dev/null
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2014 BMW Car IT GmbH, Daniel Wagner daniel.wagner@bmw-carit.de
+ *
+ * Provides a framework for enqueuing callbacks from irq context
+ * PREEMPT_RT_FULL safe. The callbacks are executed in kthread context.
+ */
+
+#include <linux/wait-simple.h>
+#include <linux/work-simple.h>
+#include <linux/kthread.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+
+#define SWORK_EVENT_PENDING     (1 << 0)
+
+static DEFINE_MUTEX(worker_mutex);
+static struct sworker *glob_worker;
+
+struct sworker {
+       struct list_head events;
+       struct swait_head wq;
+
+       raw_spinlock_t lock;
+
+       struct task_struct *task;
+       int refs;
+};
+
+static bool swork_readable(struct sworker *worker)
+{
+       bool r;
+
+       if (kthread_should_stop())
+               return true;
+
+       raw_spin_lock_irq(&worker->lock);
+       r = !list_empty(&worker->events);
+       raw_spin_unlock_irq(&worker->lock);
+
+       return r;
+}
+
+static int swork_kthread(void *arg)
+{
+       struct sworker *worker = arg;
+
+       for (;;) {
+               swait_event_interruptible(worker->wq,
+                                       swork_readable(worker));
+               if (kthread_should_stop())
+                       break;
+
+               raw_spin_lock_irq(&worker->lock);
+               while (!list_empty(&worker->events)) {
+                       struct swork_event *sev;
+
+                       sev = list_first_entry(&worker->events,
+                                       struct swork_event, item);
+                       list_del(&sev->item);
+                       raw_spin_unlock_irq(&worker->lock);
+
+                       WARN_ON_ONCE(!test_and_clear_bit(SWORK_EVENT_PENDING,
+                                                        &sev->flags));
+                       sev->func(sev);
+                       raw_spin_lock_irq(&worker->lock);
+               }
+               raw_spin_unlock_irq(&worker->lock);
+       }
+       return 0;
+}
+
+static struct sworker *swork_create(void)
+{
+       struct sworker *worker;
+
+       worker = kzalloc(sizeof(*worker), GFP_KERNEL);
+       if (!worker)
+               return ERR_PTR(-ENOMEM);
+
+       INIT_LIST_HEAD(&worker->events);
+       raw_spin_lock_init(&worker->lock);
+       init_swait_head(&worker->wq);
+
+       worker->task = kthread_run(swork_kthread, worker, "kswork");
+       if (IS_ERR(worker->task)) {
+               kfree(worker);
+               return ERR_PTR(-ENOMEM);
+       }
+
+       return worker;
+}
+
+static void swork_destroy(struct sworker *worker)
+{
+       kthread_stop(worker->task);
+
+       WARN_ON(!list_empty(&worker->events));
+       kfree(worker);
+}
+
+/**
+ * swork_queue - queue swork
+ *
+ * Returns %false if @work was already on a queue, %true otherwise.
+ *
+ * The work is queued and processed on a random CPU
+ */
+bool swork_queue(struct swork_event *sev)
+{
+       unsigned long flags;
+
+       if (test_and_set_bit(SWORK_EVENT_PENDING, &sev->flags))
+               return false;
+
+       raw_spin_lock_irqsave(&glob_worker->lock, flags);
+       list_add_tail(&sev->item, &glob_worker->events);
+       raw_spin_unlock_irqrestore(&glob_worker->lock, flags);
+
+       swait_wake(&glob_worker->wq);
+       return true;
+}
+EXPORT_SYMBOL_GPL(swork_queue);
+
+/**
+ * swork_get - get an instance of the sworker
+ *
+ * Returns an negative error code if the initialization if the worker did not
+ * work, %0 otherwise.
+ *
+ */
+int swork_get(void)
+{
+       struct sworker *worker;
+
+       mutex_lock(&worker_mutex);
+       if (!glob_worker) {
+               worker = swork_create();
+               if (IS_ERR(worker)) {
+                       mutex_unlock(&worker_mutex);
+                       return -ENOMEM;
+               }
+
+               glob_worker = worker;
+       }
+
+       glob_worker->refs++;
+       mutex_unlock(&worker_mutex);
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(swork_get);
+
+/**
+ * swork_put - puts an instance of the sworker
+ *
+ * Will destroy the sworker thread. This function must not be called until all
+ * queued events have been completed.
+ */
+void swork_put(void)
+{
+       mutex_lock(&worker_mutex);
+
+       glob_worker->refs--;
+       if (glob_worker->refs > 0)
+               goto out;
+
+       swork_destroy(glob_worker);
+       glob_worker = NULL;
+out:
+       mutex_unlock(&worker_mutex);
+}
+EXPORT_SYMBOL_GPL(swork_put);