tick: Sanitize broadcast control logic
[platform/adaptation/renesas_rcar/renesas_kernel.git] / kernel / time / tick-common.c
index 84c7cfc..64522ec 100644 (file)
@@ -18,6 +18,7 @@
 #include <linux/percpu.h>
 #include <linux/profile.h>
 #include <linux/sched.h>
+#include <linux/module.h>
 
 #include <asm/irq_regs.h>
 
@@ -193,7 +194,8 @@ static void tick_setup_device(struct tick_device *td,
         * When global broadcasting is active, check if the current
         * device is registered as a placeholder for broadcast mode.
         * This allows us to handle this x86 misfeature in a generic
-        * way.
+        * way. This function also returns !=0 when we keep the
+        * current active broadcast state for this CPU.
         */
        if (tick_device_uses_broadcast(newdev, cpu))
                return;
@@ -204,6 +206,66 @@ static void tick_setup_device(struct tick_device *td,
                tick_setup_oneshot(newdev, handler, next_event);
 }
 
+void tick_install_replacement(struct clock_event_device *newdev)
+{
+       struct tick_device *td = &__get_cpu_var(tick_cpu_device);
+       int cpu = smp_processor_id();
+
+       clockevents_exchange_device(td->evtdev, newdev);
+       tick_setup_device(td, newdev, cpu, cpumask_of(cpu));
+       if (newdev->features & CLOCK_EVT_FEAT_ONESHOT)
+               tick_oneshot_notify();
+}
+
+static bool tick_check_percpu(struct clock_event_device *curdev,
+                             struct clock_event_device *newdev, int cpu)
+{
+       if (!cpumask_test_cpu(cpu, newdev->cpumask))
+               return false;
+       if (cpumask_equal(newdev->cpumask, cpumask_of(cpu)))
+               return true;
+       /* Check if irq affinity can be set */
+       if (newdev->irq >= 0 && !irq_can_set_affinity(newdev->irq))
+               return false;
+       /* Prefer an existing cpu local device */
+       if (curdev && cpumask_equal(curdev->cpumask, cpumask_of(cpu)))
+               return false;
+       return true;
+}
+
+static bool tick_check_preferred(struct clock_event_device *curdev,
+                                struct clock_event_device *newdev)
+{
+       /* Prefer oneshot capable device */
+       if (!(newdev->features & CLOCK_EVT_FEAT_ONESHOT)) {
+               if (curdev && (curdev->features & CLOCK_EVT_FEAT_ONESHOT))
+                       return false;
+               if (tick_oneshot_mode_active())
+                       return false;
+       }
+
+       /*
+        * Use the higher rated one, but prefer a CPU local device with a lower
+        * rating than a non-CPU local device
+        */
+       return !curdev ||
+               newdev->rating > curdev->rating ||
+              !cpumask_equal(curdev->cpumask, newdev->cpumask);
+}
+
+/*
+ * Check whether the new device is a better fit than curdev. curdev
+ * can be NULL !
+ */
+bool tick_check_replacement(struct clock_event_device *curdev,
+                           struct clock_event_device *newdev)
+{
+       if (tick_check_percpu(curdev, newdev, smp_processor_id()))
+               return false;
+
+       return tick_check_preferred(curdev, newdev);
+}
+
 /*
  * Check, if the new registered device should be used. Called with
  * clockevents_lock held and interrupts disabled.
@@ -222,40 +284,15 @@ void tick_check_new_device(struct clock_event_device *newdev)
        curdev = td->evtdev;
 
        /* cpu local device ? */
-       if (!cpumask_equal(newdev->cpumask, cpumask_of(cpu))) {
-
-               /*
-                * If the cpu affinity of the device interrupt can not
-                * be set, ignore it.
-                */
-               if (!irq_can_set_affinity(newdev->irq))
-                       goto out_bc;
+       if (!tick_check_percpu(curdev, newdev, cpu))
+               goto out_bc;
 
-               /*
-                * If we have a cpu local device already, do not replace it
-                * by a non cpu local device
-                */
-               if (curdev && cpumask_equal(curdev->cpumask, cpumask_of(cpu)))
-                       goto out_bc;
-       }
+       /* Preference decision */
+       if (!tick_check_preferred(curdev, newdev))
+               goto out_bc;
 
-       /*
-        * If we have an active device, then check the rating and the oneshot
-        * feature.
-        */
-       if (curdev) {
-               /*
-                * Prefer one shot capable devices !
-                */
-               if ((curdev->features & CLOCK_EVT_FEAT_ONESHOT) &&
-                   !(newdev->features & CLOCK_EVT_FEAT_ONESHOT))
-                       goto out_bc;
-               /*
-                * Check the rating
-                */
-               if (curdev->rating >= newdev->rating)
-                       goto out_bc;
-       }
+       if (!try_module_get(newdev->owner))
+               return;
 
        /*
         * Replace the eventually existing device by the new