]> git.kernelconcepts.de Git - karo-tx-linux.git/blobdiff - arch/arm/common/bL_switcher.c
ARM: bL_switcher: Add switch completion callback for bL_switch_request()
[karo-tx-linux.git] / arch / arm / common / bL_switcher.c
index 335ff76d4c5a27c8a41c26b100cbda6e03726d86..34316be404d51c3c4a02068d53f85944ec9a15cb 100644 (file)
@@ -9,6 +9,7 @@
  * published by the Free Software Foundation.
  */
 
+#include <linux/atomic.h>
 #include <linux/init.h>
 #include <linux/kernel.h>
 #include <linux/module.h>
 #include <linux/clockchips.h>
 #include <linux/hrtimer.h>
 #include <linux/tick.h>
+#include <linux/notifier.h>
 #include <linux/mm.h>
+#include <linux/mutex.h>
+#include <linux/spinlock.h>
 #include <linux/string.h>
 #include <linux/sysfs.h>
 #include <linux/irqchip/arm-gic.h>
@@ -222,10 +226,13 @@ static int bL_switch_to(unsigned int new_cluster_id)
 }
 
 struct bL_thread {
+       spinlock_t lock;
        struct task_struct *task;
        wait_queue_head_t wq;
        int wanted_cluster;
        struct completion started;
+       bL_switch_completion_handler completer;
+       void *completer_cookie;
 };
 
 static struct bL_thread bL_threads[NR_CPUS];
@@ -235,6 +242,8 @@ static int bL_switcher_thread(void *arg)
        struct bL_thread *t = arg;
        struct sched_param param = { .sched_priority = 1 };
        int cluster;
+       bL_switch_completion_handler completer;
+       void *completer_cookie;
 
        sched_setscheduler_nocheck(current, SCHED_FIFO, &param);
        complete(&t->started);
@@ -245,9 +254,21 @@ static int bL_switcher_thread(void *arg)
                wait_event_interruptible(t->wq,
                                t->wanted_cluster != -1 ||
                                kthread_should_stop());
-               cluster = xchg(&t->wanted_cluster, -1);
-               if (cluster != -1)
+
+               spin_lock(&t->lock);
+               cluster = t->wanted_cluster;
+               completer = t->completer;
+               completer_cookie = t->completer_cookie;
+               t->wanted_cluster = -1;
+               t->completer = NULL;
+               spin_unlock(&t->lock);
+
+               if (cluster != -1) {
                        bL_switch_to(cluster);
+
+                       if (completer)
+                               completer(completer_cookie);
+               }
        } while (!kthread_should_stop());
 
        return 0;
@@ -268,16 +289,30 @@ static struct task_struct *bL_switcher_thread_create(int cpu, void *arg)
 }
 
 /*
- * bL_switch_request - Switch to a specific cluster for the given CPU
+ * bL_switch_request_cb - Switch to a specific cluster for the given CPU,
+ *      with completion notification via a callback
  *
  * @cpu: the CPU to switch
  * @new_cluster_id: the ID of the cluster to switch to.
+ * @completer: switch completion callback.  if non-NULL,
+ *     @completer(@completer_cookie) will be called on completion of
+ *     the switch, in non-atomic context.
+ * @completer_cookie: opaque context argument for @completer.
  *
  * This function causes a cluster switch on the given CPU by waking up
  * the appropriate switcher thread.  This function may or may not return
  * before the switch has occurred.
+ *
+ * If a @completer callback function is supplied, it will be called when
+ * the switch is complete.  This can be used to determine asynchronously
+ * when the switch is complete, regardless of when bL_switch_request()
+ * returns.  When @completer is supplied, no new switch request is permitted
+ * for the affected CPU until after the switch is complete, and @completer
+ * has returned.
  */
-int bL_switch_request(unsigned int cpu, unsigned int new_cluster_id)
+int bL_switch_request_cb(unsigned int cpu, unsigned int new_cluster_id,
+                        bL_switch_completion_handler completer,
+                        void *completer_cookie)
 {
        struct bL_thread *t;
 
@@ -287,25 +322,59 @@ int bL_switch_request(unsigned int cpu, unsigned int new_cluster_id)
        }
 
        t = &bL_threads[cpu];
+
        if (IS_ERR(t->task))
                return PTR_ERR(t->task);
        if (!t->task)
                return -ESRCH;
 
+       spin_lock(&t->lock);
+       if (t->completer) {
+               spin_unlock(&t->lock);
+               return -EBUSY;
+       }
+       t->completer = completer;
+       t->completer_cookie = completer_cookie;
        t->wanted_cluster = new_cluster_id;
+       spin_unlock(&t->lock);
        wake_up(&t->wq);
        return 0;
 }
-EXPORT_SYMBOL_GPL(bL_switch_request);
+EXPORT_SYMBOL_GPL(bL_switch_request_cb);
 
 /*
  * Activation and configuration code.
  */
 
+static DEFINE_MUTEX(bL_switcher_activation_lock);
+static BLOCKING_NOTIFIER_HEAD(bL_activation_notifier);
 static unsigned int bL_switcher_active;
 static unsigned int bL_switcher_cpu_original_cluster[NR_CPUS];
 static cpumask_t bL_switcher_removed_logical_cpus;
 
+int bL_switcher_register_notifier(struct notifier_block *nb)
+{
+       return blocking_notifier_chain_register(&bL_activation_notifier, nb);
+}
+EXPORT_SYMBOL_GPL(bL_switcher_register_notifier);
+
+int bL_switcher_unregister_notifier(struct notifier_block *nb)
+{
+       return blocking_notifier_chain_unregister(&bL_activation_notifier, nb);
+}
+EXPORT_SYMBOL_GPL(bL_switcher_unregister_notifier);
+
+static int bL_activation_notify(unsigned long val)
+{
+       int ret;
+
+       ret = blocking_notifier_call_chain(&bL_activation_notifier, val, NULL);
+       if (ret & NOTIFY_STOP_MASK)
+               pr_err("%s: notifier chain failed with status 0x%x\n",
+                       __func__, ret);
+       return notifier_to_errno(ret);
+}
+
 static void bL_switcher_restore_cpus(void)
 {
        int i;
@@ -413,22 +482,27 @@ static int bL_switcher_enable(void)
 {
        int cpu, ret;
 
+       mutex_lock(&bL_switcher_activation_lock);
        cpu_hotplug_driver_lock();
        if (bL_switcher_active) {
                cpu_hotplug_driver_unlock();
+               mutex_unlock(&bL_switcher_activation_lock);
                return 0;
        }
 
        pr_info("big.LITTLE switcher initializing\n");
 
+       ret = bL_activation_notify(BL_NOTIFY_PRE_ENABLE);
+       if (ret)
+               goto error;
+
        ret = bL_switcher_halve_cpus();
-       if (ret) {
-               cpu_hotplug_driver_unlock();
-               return ret;
-       }
+       if (ret)
+               goto error;
 
        for_each_online_cpu(cpu) {
                struct bL_thread *t = &bL_threads[cpu];
+               spin_lock_init(&t->lock);
                init_waitqueue_head(&t->wq);
                init_completion(&t->started);
                t->wanted_cluster = -1;
@@ -436,10 +510,18 @@ static int bL_switcher_enable(void)
        }
 
        bL_switcher_active = 1;
-       cpu_hotplug_driver_unlock();
-
+       bL_activation_notify(BL_NOTIFY_POST_ENABLE);
        pr_info("big.LITTLE switcher initialized\n");
-       return 0;
+       goto out;
+
+error:
+       pr_warn("big.LITTLE switcher initialization failed\n");
+       bL_activation_notify(BL_NOTIFY_POST_DISABLE);
+
+out:
+       cpu_hotplug_driver_unlock();
+       mutex_unlock(&bL_switcher_activation_lock);
+       return ret;
 }
 
 #ifdef CONFIG_SYSFS
@@ -450,11 +532,17 @@ static void bL_switcher_disable(void)
        struct bL_thread *t;
        struct task_struct *task;
 
+       mutex_lock(&bL_switcher_activation_lock);
        cpu_hotplug_driver_lock();
-       if (!bL_switcher_active) {
-               cpu_hotplug_driver_unlock();
-               return;
+
+       if (!bL_switcher_active)
+               goto out;
+
+       if (bL_activation_notify(BL_NOTIFY_PRE_DISABLE) != 0) {
+               bL_activation_notify(BL_NOTIFY_POST_ENABLE);
+               goto out;
        }
+
        bL_switcher_active = 0;
 
        /*
@@ -496,7 +584,11 @@ static void bL_switcher_disable(void)
        }
 
        bL_switcher_restore_cpus();
+       bL_activation_notify(BL_NOTIFY_POST_DISABLE);
+
+out:
        cpu_hotplug_driver_unlock();
+       mutex_unlock(&bL_switcher_activation_lock);
 }
 
 static ssize_t bL_switcher_active_show(struct kobject *kobj,
@@ -554,6 +646,20 @@ static int __init bL_switcher_sysfs_init(void)
 
 #endif  /* CONFIG_SYSFS */
 
+bool bL_switcher_get_enabled(void)
+{
+       mutex_lock(&bL_switcher_activation_lock);
+
+       return bL_switcher_active;
+}
+EXPORT_SYMBOL_GPL(bL_switcher_get_enabled);
+
+void bL_switcher_put_enabled(void)
+{
+       mutex_unlock(&bL_switcher_activation_lock);
+}
+EXPORT_SYMBOL_GPL(bL_switcher_put_enabled);
+
 /*
  * Veto any CPU hotplug operation on those CPUs we've removed
  * while the switcher is active.