PM / sleep: Make it possible to quiesce timers during suspend-to-idle
[deliverable/linux.git] / drivers / cpuidle / cpuidle.c
index 23a8d6cc8d30116d2da48449df9bc20ed7982236..4d534582514e014b5fdb3fc5e0b9db7e52c7b306 100644 (file)
@@ -20,6 +20,7 @@
 #include <linux/hrtimer.h>
 #include <linux/module.h>
 #include <linux/suspend.h>
+#include <linux/tick.h>
 #include <trace/events/power.h>
 
 #include "cpuidle.h"
@@ -69,18 +70,20 @@ int cpuidle_play_dead(void)
  * cpuidle_find_deepest_state - Find deepest state meeting specific conditions.
  * @drv: cpuidle driver for the given CPU.
  * @dev: cpuidle device for the given CPU.
+ * @freeze: Whether or not the state should be suitable for suspend-to-idle.
  */
 static int cpuidle_find_deepest_state(struct cpuidle_driver *drv,
-                                     struct cpuidle_device *dev)
+                                     struct cpuidle_device *dev, bool freeze)
 {
        unsigned int latency_req = 0;
-       int i, ret = CPUIDLE_DRIVER_STATE_START - 1;
+       int i, ret = freeze ? -1 : CPUIDLE_DRIVER_STATE_START - 1;
 
        for (i = CPUIDLE_DRIVER_STATE_START; i < drv->state_count; i++) {
                struct cpuidle_state *s = &drv->states[i];
                struct cpuidle_state_usage *su = &dev->states_usage[i];
 
-               if (s->disabled || su->disable || s->exit_latency <= latency_req)
+               if (s->disabled || su->disable || s->exit_latency <= latency_req
+                   || (freeze && !s->enter_freeze))
                        continue;
 
                latency_req = s->exit_latency;
@@ -89,10 +92,31 @@ static int cpuidle_find_deepest_state(struct cpuidle_driver *drv,
        return ret;
 }
 
+static void enter_freeze_proper(struct cpuidle_driver *drv,
+                               struct cpuidle_device *dev, int index)
+{
+       tick_freeze();
+       /*
+        * The state used here cannot be a "coupled" one, because the "coupled"
+        * cpuidle mechanism enables interrupts and doing that with timekeeping
+        * suspended is generally unsafe.
+        */
+       drv->states[index].enter_freeze(dev, drv, index);
+       WARN_ON(!irqs_disabled());
+       /*
+        * timekeeping_resume() that will be called by tick_unfreeze() for the
+        * last CPU executing it calls functions containing RCU read-side
+        * critical sections, so tell RCU about that.
+        */
+       RCU_NONIDLE(tick_unfreeze());
+}
+
 /**
  * cpuidle_enter_freeze - Enter an idle state suitable for suspend-to-idle.
  *
- * Find the deepest state available and enter it.
+ * If there are states with the ->enter_freeze callback, find the deepest of
+ * them and enter it with frozen tick.  Otherwise, find the deepest state
+ * available and enter it normally.
  */
 void cpuidle_enter_freeze(void)
 {
@@ -100,7 +124,22 @@ void cpuidle_enter_freeze(void)
        struct cpuidle_driver *drv = cpuidle_get_cpu_driver(dev);
        int index;
 
-       index = cpuidle_find_deepest_state(drv, dev);
+       /*
+        * Find the deepest state with ->enter_freeze present, which guarantees
+        * that interrupts won't be enabled when it exits and allows the tick to
+        * be frozen safely.
+        */
+       index = cpuidle_find_deepest_state(drv, dev, true);
+       if (index >= 0) {
+               enter_freeze_proper(drv, dev, index);
+               return;
+       }
+
+       /*
+        * It is not safe to freeze the tick, find the deepest state available
+        * at all and try to enter it normally.
+        */
+       index = cpuidle_find_deepest_state(drv, dev, false);
        if (index >= 0)
                cpuidle_enter(drv, dev, index);
        else
This page took 0.030972 seconds and 5 git commands to generate.