BACKPORT: timekeeping: Use READ/WRITE_ONCE() for tick_do_timer_cpu

tick_do_timer_cpu is used lockless to check which CPU needs to take care
of the per tick timekeeping duty. This is done to avoid a thundering
herd problem on jiffies_lock.

The read and writes are not annotated so KCSAN complains about data races:

  BUG: KCSAN: data-race in tick_nohz_idle_stop_tick / tick_nohz_next_event

  write to 0xffffffff8a2bda30 of 4 bytes by task 0 on cpu 26:
   tick_nohz_idle_stop_tick+0x3b1/0x4a0
   do_idle+0x1e3/0x250

  read to 0xffffffff8a2bda30 of 4 bytes by task 0 on cpu 16:
   tick_nohz_next_event+0xe7/0x1e0
   tick_nohz_get_sleep_length+0xa7/0xe0
   menu_select+0x82/0xb90
   cpuidle_select+0x44/0x60
   do_idle+0x1c2/0x250

  value changed: 0x0000001a -> 0xffffffff

Annotate them with READ/WRITE_ONCE() to document the intentional data race.

Bug: 361037203
Reported-by: Mirsad Todorovac <mirsad.todorovac@alu.unizg.hr>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Signed-off-by: Ingo Molnar <mingo@kernel.org>
Tested-by: Sean Anderson <sean.anderson@seco.com>
Link: https://lore.kernel.org/r/87cyqy7rt3.ffs@tglx
Change-Id: I622c0e57d0622cfd52308b26935f8579e195b428
(cherry picked from commit f87cbcb345)
[quic_mojha: Resolved merge conflict in kernel/time/tick-sched.c, kernel/time/tick-common.c]
Signed-off-by: Mukesh Ojha <quic_mojha@quicinc.com>
This commit is contained in:
Thomas Gleixner 2024-08-12 13:35:51 +05:30 committed by Treehugger Robot
parent 4b38cee3d2
commit a94ba5ab28
2 changed files with 27 additions and 18 deletions

View File

@ -85,7 +85,7 @@ int tick_is_oneshot_available(void)
*/
static void tick_periodic(int cpu)
{
if (tick_do_timer_cpu == cpu) {
if (READ_ONCE(tick_do_timer_cpu) == cpu) {
raw_spin_lock(&jiffies_lock);
write_seqcount_begin(&jiffies_seq);
@ -219,8 +219,8 @@ static void tick_setup_device(struct tick_device *td,
* If no cpu took the do_timer update, assign it to
* this cpu:
*/
if (tick_do_timer_cpu == TICK_DO_TIMER_BOOT) {
tick_do_timer_cpu = cpu;
if (READ_ONCE(tick_do_timer_cpu) == TICK_DO_TIMER_BOOT) {
WRITE_ONCE(tick_do_timer_cpu, cpu);
tick_next_period = ktime_get();
#ifdef CONFIG_NO_HZ_FULL
/*
@ -236,7 +236,7 @@ static void tick_setup_device(struct tick_device *td,
!tick_nohz_full_cpu(cpu)) {
tick_take_do_timer_from_boot();
tick_do_timer_boot_cpu = -1;
WARN_ON(tick_do_timer_cpu != cpu);
WARN_ON(READ_ONCE(tick_do_timer_cpu) != cpu);
#endif
}

View File

@ -185,7 +185,7 @@ static ktime_t tick_init_jiffy_update(void)
static void tick_sched_do_timer(struct tick_sched *ts, ktime_t now)
{
int cpu = smp_processor_id();
int tick_cpu, cpu = smp_processor_id();
#ifdef CONFIG_NO_HZ_COMMON
/*
@ -198,16 +198,18 @@ static void tick_sched_do_timer(struct tick_sched *ts, ktime_t now)
* If nohz_full is enabled, this should not happen because the
* tick_do_timer_cpu never relinquishes.
*/
if (unlikely(tick_do_timer_cpu == TICK_DO_TIMER_NONE)) {
tick_cpu = READ_ONCE(tick_do_timer_cpu);
if (unlikely(tick_cpu == TICK_DO_TIMER_NONE)) {
#ifdef CONFIG_NO_HZ_FULL
WARN_ON_ONCE(tick_nohz_full_running);
#endif
tick_do_timer_cpu = cpu;
WRITE_ONCE(tick_do_timer_cpu, cpu);
tick_cpu = cpu;
}
#endif
/* Check, if the jiffies need an update */
if (tick_do_timer_cpu == cpu) {
if (tick_cpu == cpu) {
tick_do_update_jiffies64(now);
trace_android_vh_jiffies_update(NULL);
}
@ -553,7 +555,7 @@ bool tick_nohz_cpu_hotpluggable(unsigned int cpu)
* timers, workqueues, timekeeping, ...) on behalf of full dynticks
* CPUs. It must remain online when nohz full is enabled.
*/
if (tick_nohz_full_running && tick_do_timer_cpu == cpu)
if (tick_nohz_full_running && READ_ONCE(tick_do_timer_cpu) == cpu)
return false;
return true;
}
@ -806,6 +808,7 @@ static ktime_t tick_nohz_next_event(struct tick_sched *ts, int cpu)
u64 basemono, next_tick, delta, expires;
unsigned long basejiff;
unsigned int seq;
int tick_cpu;
/* Read jiffies and the time when jiffies were updated last */
do {
@ -868,8 +871,9 @@ static ktime_t tick_nohz_next_event(struct tick_sched *ts, int cpu)
* Otherwise we can sleep as long as we want.
*/
delta = timekeeping_max_deferment();
if (cpu != tick_do_timer_cpu &&
(tick_do_timer_cpu != TICK_DO_TIMER_NONE || !ts->do_timer_last))
tick_cpu = READ_ONCE(tick_do_timer_cpu);
if (tick_cpu != cpu &&
(tick_cpu != TICK_DO_TIMER_NONE || !ts->do_timer_last))
delta = KTIME_MAX;
/* Calculate the next expiry time */
@ -890,6 +894,7 @@ static void tick_nohz_stop_tick(struct tick_sched *ts, int cpu)
u64 basemono = ts->timer_expires_base;
u64 expires = ts->timer_expires;
ktime_t tick = expires;
int tick_cpu;
/* Make sure we won't be trying to stop it twice in a row. */
ts->timer_expires_base = 0;
@ -902,10 +907,11 @@ static void tick_nohz_stop_tick(struct tick_sched *ts, int cpu)
* do_timer() never invoked. Keep track of the fact that it
* was the one which had the do_timer() duty last.
*/
if (cpu == tick_do_timer_cpu) {
tick_do_timer_cpu = TICK_DO_TIMER_NONE;
tick_cpu = READ_ONCE(tick_do_timer_cpu);
if (tick_cpu == cpu) {
WRITE_ONCE(tick_do_timer_cpu, TICK_DO_TIMER_NONE);
ts->do_timer_last = 1;
} else if (tick_do_timer_cpu != TICK_DO_TIMER_NONE) {
} else if (tick_cpu != TICK_DO_TIMER_NONE) {
ts->do_timer_last = 0;
}
@ -1068,8 +1074,10 @@ static bool can_stop_idle_tick(int cpu, struct tick_sched *ts)
* invoked.
*/
if (unlikely(!cpu_online(cpu))) {
if (cpu == tick_do_timer_cpu)
tick_do_timer_cpu = TICK_DO_TIMER_NONE;
int tick_cpu = READ_ONCE(tick_do_timer_cpu);
if (tick_cpu == cpu)
WRITE_ONCE(tick_do_timer_cpu, TICK_DO_TIMER_NONE);
/*
* Make sure the CPU doesn't get fooled by obsolete tick
* deadline if it comes back online later.
@ -1088,15 +1096,16 @@ static bool can_stop_idle_tick(int cpu, struct tick_sched *ts)
return false;
if (tick_nohz_full_enabled()) {
int tick_cpu = READ_ONCE(tick_do_timer_cpu);
/*
* Keep the tick alive to guarantee timekeeping progression
* if there are full dynticks CPUs around
*/
if (tick_do_timer_cpu == cpu)
if (tick_cpu == cpu)
return false;
/* Should not happen for nohz-full */
if (WARN_ON_ONCE(tick_do_timer_cpu == TICK_DO_TIMER_NONE))
if (WARN_ON_ONCE(tick_cpu == TICK_DO_TIMER_NONE))
return false;
}