sched/idle: Conditionally handle tick broadcast in default_idle_call()
authorThomas Gleixner <tglx@linutronix.de>
Thu, 29 Feb 2024 14:23:36 +0000 (15:23 +0100)
committerBorislav Petkov (AMD) <bp@alien8.de>
Fri, 1 Mar 2024 20:04:27 +0000 (21:04 +0100)
The x86 architecture has an idle routine for AMD CPUs which are affected
by erratum 400. On the affected CPUs the local APIC timer stops in the
C1E halt state.

It therefore requires tick broadcasting. The invocation of
tick_broadcast_enter()/exit() from this function violates the RCU
constraints because it can end up in lockdep or tracing, which
rightfully triggers a warning.

tick_broadcast_enter()/exit() must be invoked before ct_cpuidle_enter()
and after ct_cpuidle_exit() in default_idle_call().

Add a static branch conditional invocation of tick_broadcast_enter()/exit()
into this function to allow X86 to replace the AMD specific idle code. It's
guarded by a config switch which will be selected by x86. Otherwise it's
a NOOP.

Reported-by: Borislav Petkov <bp@alien8.de>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Signed-off-by: Borislav Petkov (AMD) <bp@alien8.de>
Link: https://lore.kernel.org/r/20240229142248.266708822@linutronix.de
include/linux/cpu.h
include/linux/tick.h
kernel/sched/idle.c
kernel/time/Kconfig

index dcb89c9871640f0a92c85c5f16d94a8e199c3a23..715017d4432b56c10846fa247d0b5ab9e8668fd1 100644 (file)
@@ -196,6 +196,8 @@ void arch_cpu_idle(void);
 void arch_cpu_idle_prepare(void);
 void arch_cpu_idle_enter(void);
 void arch_cpu_idle_exit(void);
+void arch_tick_broadcast_enter(void);
+void arch_tick_broadcast_exit(void);
 void __noreturn arch_cpu_idle_dead(void);
 
 #ifdef CONFIG_ARCH_HAS_CPU_FINALIZE_INIT
index 716d17f31c4539d18bcdd94c993ef47bf1c42368..e134f286df8aa1273fe5346db031bdc39838369e 100644 (file)
@@ -12,6 +12,7 @@
 #include <linux/cpumask.h>
 #include <linux/sched.h>
 #include <linux/rcupdate.h>
+#include <linux/static_key.h>
 
 #ifdef CONFIG_GENERIC_CLOCKEVENTS
 extern void __init tick_init(void);
@@ -63,6 +64,8 @@ enum tick_broadcast_state {
        TICK_BROADCAST_ENTER,
 };
 
+extern struct static_key_false arch_needs_tick_broadcast;
+
 #ifdef CONFIG_GENERIC_CLOCKEVENTS_BROADCAST
 extern void tick_broadcast_control(enum tick_broadcast_mode mode);
 #else
index 31231925f1ece276c96269a2516d4d00ff011420..31ad8115329574aef1b9c5b458c1c33dc673b2f2 100644 (file)
@@ -81,6 +81,25 @@ void __weak arch_cpu_idle(void)
        cpu_idle_force_poll = 1;
 }
 
+#ifdef CONFIG_GENERIC_CLOCKEVENTS_BROADCAST_IDLE
+DEFINE_STATIC_KEY_FALSE(arch_needs_tick_broadcast);
+
+static inline void cond_tick_broadcast_enter(void)
+{
+       if (static_branch_unlikely(&arch_needs_tick_broadcast))
+               tick_broadcast_enter();
+}
+
+static inline void cond_tick_broadcast_exit(void)
+{
+       if (static_branch_unlikely(&arch_needs_tick_broadcast))
+               tick_broadcast_exit();
+}
+#else
+static inline void cond_tick_broadcast_enter(void) { }
+static inline void cond_tick_broadcast_exit(void) { }
+#endif
+
 /**
  * default_idle_call - Default CPU idle routine.
  *
@@ -90,6 +109,7 @@ void __cpuidle default_idle_call(void)
 {
        instrumentation_begin();
        if (!current_clr_polling_and_test()) {
+               cond_tick_broadcast_enter();
                trace_cpu_idle(1, smp_processor_id());
                stop_critical_timings();
 
@@ -99,6 +119,7 @@ void __cpuidle default_idle_call(void)
 
                start_critical_timings();
                trace_cpu_idle(PWR_EVENT_EXIT, smp_processor_id());
+               cond_tick_broadcast_exit();
        }
        local_irq_enable();
        instrumentation_end();
index bae8f11070befeefb1fe5c5ed1957c806f9195ff..fc3b1a06c9816e9802e9a8a68084707f38d91ecc 100644 (file)
@@ -39,6 +39,11 @@ config GENERIC_CLOCKEVENTS_BROADCAST
        bool
        depends on GENERIC_CLOCKEVENTS
 
+# Handle broadcast in default_idle_call()
+config GENERIC_CLOCKEVENTS_BROADCAST_IDLE
+       bool
+       depends on GENERIC_CLOCKEVENTS_BROADCAST
+
 # Automatically adjust the min. reprogramming time for
 # clock event device
 config GENERIC_CLOCKEVENTS_MIN_ADJUST