]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
sched: idle: Consolidate the handling of two special cases
authorRafael J. Wysocki <rafael.j.wysocki@intel.com>
Fri, 13 Mar 2026 12:25:41 +0000 (13:25 +0100)
committerRafael J. Wysocki <rafael.j.wysocki@intel.com>
Mon, 16 Mar 2026 19:29:47 +0000 (20:29 +0100)
There are two special cases in the idle loop that are handled
inconsistently even though they are analogous.

The first one is when a cpuidle driver is absent and the default CPU
idle time power management implemented by the architecture code is used.
In that case, the scheduler tick is stopped every time before invoking
default_idle_call().

The second one is when a cpuidle driver is present, but there is only
one idle state in its table.  In that case, the scheduler tick is never
stopped at all.

Since each of these approaches has its drawbacks, reconcile them with
the help of one simple heuristic.  Namely, stop the tick if the CPU has
been woken up by it in the previous iteration of the idle loop, or let
it tick otherwise.

Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Reviewed-by: Christian Loehle <christian.loehle@arm.com>
Reviewed-by: Frederic Weisbecker <frederic@kernel.org>
Reviewed-by: Qais Yousef <qyousef@layalina.io>
Reviewed-by: Aboorva Devarajan <aboorvad@linux.ibm.com>
Fixes: ed98c3491998 ("sched: idle: Do not stop the tick before cpuidle_idle_call()")
[ rjw: Added Fixes tag, changelog edits ]
Link: https://patch.msgid.link/4741364.LvFx2qVVIh@rafael.j.wysocki
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
kernel/sched/idle.c

index b9544916512273553f1938611226bbaddac15803..a83be0c834ddb5ec8a236c525d8a5edd84b3175a 100644 (file)
@@ -161,6 +161,14 @@ static int call_cpuidle(struct cpuidle_driver *drv, struct cpuidle_device *dev,
        return cpuidle_enter(drv, dev, next_state);
 }
 
+static void idle_call_stop_or_retain_tick(bool stop_tick)
+{
+       if (stop_tick || tick_nohz_tick_stopped())
+               tick_nohz_idle_stop_tick();
+       else
+               tick_nohz_idle_retain_tick();
+}
+
 /**
  * cpuidle_idle_call - the main idle function
  *
@@ -170,7 +178,7 @@ static int call_cpuidle(struct cpuidle_driver *drv, struct cpuidle_device *dev,
  * set, and it returns with polling set.  If it ever stops polling, it
  * must clear the polling bit.
  */
-static void cpuidle_idle_call(void)
+static void cpuidle_idle_call(bool stop_tick)
 {
        struct cpuidle_device *dev = cpuidle_get_device();
        struct cpuidle_driver *drv = cpuidle_get_cpu_driver(dev);
@@ -186,7 +194,7 @@ static void cpuidle_idle_call(void)
        }
 
        if (cpuidle_not_available(drv, dev)) {
-               tick_nohz_idle_stop_tick();
+               idle_call_stop_or_retain_tick(stop_tick);
 
                default_idle_call();
                goto exit_idle;
@@ -222,17 +230,19 @@ static void cpuidle_idle_call(void)
                next_state = cpuidle_find_deepest_state(drv, dev, max_latency_ns);
                call_cpuidle(drv, dev, next_state);
        } else if (drv->state_count > 1) {
-               bool stop_tick = true;
+               /*
+                * stop_tick is expected to be true by default by cpuidle
+                * governors, which allows them to select idle states with
+                * target residency above the tick period length.
+                */
+               stop_tick = true;
 
                /*
                 * Ask the cpuidle framework to choose a convenient idle state.
                 */
                next_state = cpuidle_select(drv, dev, &stop_tick);
 
-               if (stop_tick || tick_nohz_tick_stopped())
-                       tick_nohz_idle_stop_tick();
-               else
-                       tick_nohz_idle_retain_tick();
+               idle_call_stop_or_retain_tick(stop_tick);
 
                entered_state = call_cpuidle(drv, dev, next_state);
                /*
@@ -240,7 +250,7 @@ static void cpuidle_idle_call(void)
                 */
                cpuidle_reflect(dev, entered_state);
        } else {
-               tick_nohz_idle_retain_tick();
+               idle_call_stop_or_retain_tick(stop_tick);
 
                /*
                 * If there is only a single idle state (or none), there is
@@ -268,6 +278,7 @@ exit_idle:
 static void do_idle(void)
 {
        int cpu = smp_processor_id();
+       bool got_tick = false;
 
        /*
         * Check if we need to update blocked load
@@ -338,8 +349,9 @@ static void do_idle(void)
                        tick_nohz_idle_restart_tick();
                        cpu_idle_poll();
                } else {
-                       cpuidle_idle_call();
+                       cpuidle_idle_call(got_tick);
                }
+               got_tick = tick_nohz_idle_got_tick();
                arch_cpu_idle_exit();
        }