Skip to content

Commit 3f69d04

Browse files
Frederic WeisbeckerKAGA-KOKO
authored andcommitted
tick: Shut down low-res tick from dying CPU
The timekeeping duty is handed over from the outgoing CPU within stop machine. This works well if CONFIG_NO_HZ_COMMON=n or the tick is in high-res mode. However in low-res dynticks mode, the tick isn't cancelled until the clockevent is shut down, which can happen later. The tick may therefore fire again once IRQs are re-enabled on stop machine and until IRQs are disabled for good upon the last call to idle. That's so many opportunities for a timekeeper to go idle and the outgoing CPU to take over that duty. This is why tick_nohz_idle_stop_tick() is called one last time on idle if the CPU is seen offline: so that the timekeeping duty is handed over again in case the CPU has re-taken the duty. This means there are two timekeeping handovers on CPU down hotplug with different undocumented constraints and purposes: 1) A handover on stop machine for !dynticks || highres. All online CPUs are guaranteed to be non-idle and the timekeeping duty can be safely handed-over. The hrtimer tick is cancelled so it is guaranteed that in dynticks mode the outgoing CPU won't take again the duty. 2) A handover on last idle call for dynticks && lowres. Setting the duty to TICK_DO_TIMER_NONE makes sure that a CPU will take over the timekeeping. Prepare for consolidating the handover to a single place (the first one) with shutting down the low-res tick as well from tick_cancel_sched_timer() as well. This will simplify the handover and unify the tick cancellation between high-res and low-res. Signed-off-by: Frederic Weisbecker <frederic@kernel.org> Signed-off-by: Thomas Gleixner <tglx@linutronix.de> Reviewed-by: Thomas Gleixner <tglx@linutronix.de> Link: https://lore.kernel.org/r/20240225225508.11587-15-frederic@kernel.org
1 parent 7988e5a commit 3f69d04

File tree

3 files changed

+29
-10
lines changed

3 files changed

+29
-10
lines changed

kernel/time/tick-common.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -410,7 +410,8 @@ int tick_cpu_dying(unsigned int dying_cpu)
410410
if (tick_do_timer_cpu == dying_cpu)
411411
tick_do_timer_cpu = cpumask_first(cpu_online_mask);
412412

413-
tick_cancel_sched_timer(dying_cpu);
413+
/* Make sure the CPU won't try to retake the timekeeping duty */
414+
tick_sched_timer_dying(dying_cpu);
414415

415416
/* Remove CPU from timer broadcasting */
416417
tick_offline_cpu(dying_cpu);

kernel/time/tick-sched.c

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,14 @@ static enum hrtimer_restart tick_nohz_handler(struct hrtimer *timer)
308308
return HRTIMER_RESTART;
309309
}
310310

311+
static void tick_sched_timer_cancel(struct tick_sched *ts)
312+
{
313+
if (tick_sched_flag_test(ts, TS_FLAG_HIGHRES))
314+
hrtimer_cancel(&ts->sched_timer);
315+
else if (tick_sched_flag_test(ts, TS_FLAG_NOHZ))
316+
tick_program_event(KTIME_MAX, 1);
317+
}
318+
311319
#ifdef CONFIG_NO_HZ_FULL
312320
cpumask_var_t tick_nohz_full_mask;
313321
EXPORT_SYMBOL_GPL(tick_nohz_full_mask);
@@ -1040,10 +1048,7 @@ static void tick_nohz_stop_tick(struct tick_sched *ts, int cpu)
10401048
* the tick timer.
10411049
*/
10421050
if (unlikely(expires == KTIME_MAX)) {
1043-
if (tick_sched_flag_test(ts, TS_FLAG_HIGHRES))
1044-
hrtimer_cancel(&ts->sched_timer);
1045-
else
1046-
tick_program_event(KTIME_MAX, 1);
1051+
tick_sched_timer_cancel(ts);
10471052
return;
10481053
}
10491054

@@ -1598,14 +1603,27 @@ void tick_setup_sched_timer(bool hrtimer)
15981603
tick_nohz_activate(ts);
15991604
}
16001605

1601-
void tick_cancel_sched_timer(int cpu)
1606+
/*
1607+
* Shut down the tick and make sure the CPU won't try to retake the timekeeping
1608+
* duty before disabling IRQs in idle for the last time.
1609+
*/
1610+
void tick_sched_timer_dying(int cpu)
16021611
{
1612+
struct tick_device *td = &per_cpu(tick_cpu_device, cpu);
16031613
struct tick_sched *ts = &per_cpu(tick_cpu_sched, cpu);
1614+
struct clock_event_device *dev = td->evtdev;
16041615
ktime_t idle_sleeptime, iowait_sleeptime;
16051616
unsigned long idle_calls, idle_sleeps;
16061617

1607-
if (tick_sched_flag_test(ts, TS_FLAG_HIGHRES))
1608-
hrtimer_cancel(&ts->sched_timer);
1618+
/* This must happen before hrtimers are migrated! */
1619+
tick_sched_timer_cancel(ts);
1620+
1621+
/*
1622+
* If the clockevents doesn't support CLOCK_EVT_STATE_ONESHOT_STOPPED,
1623+
* make sure not to call low-res tick handler.
1624+
*/
1625+
if (tick_sched_flag_test(ts, TS_FLAG_NOHZ))
1626+
dev->event_handler = clockevents_handle_noop;
16091627

16101628
idle_sleeptime = ts->idle_sleeptime;
16111629
iowait_sleeptime = ts->iowait_sleeptime;

kernel/time/tick-sched.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,9 +106,9 @@ extern struct tick_sched *tick_get_tick_sched(int cpu);
106106

107107
extern void tick_setup_sched_timer(bool hrtimer);
108108
#if defined CONFIG_NO_HZ_COMMON || defined CONFIG_HIGH_RES_TIMERS
109-
extern void tick_cancel_sched_timer(int cpu);
109+
extern void tick_sched_timer_dying(int cpu);
110110
#else
111-
static inline void tick_cancel_sched_timer(int cpu) { }
111+
static inline void tick_sched_timer_dying(int cpu) { }
112112
#endif
113113

114114
#ifdef CONFIG_GENERIC_CLOCKEVENTS_BROADCAST

0 commit comments

Comments
 (0)