Skip to content

Commit fcf190c

Browse files
ahunter6KAGA-KOKO
authored andcommitted
timekeeping: Make delta calculation overflow safe
Kernel timekeeping is designed to keep the change in cycles (since the last timer interrupt) below max_cycles, which prevents multiplication overflow when converting cycles to nanoseconds. However, if timer interrupts stop, the calculation will eventually overflow. Add protection against that. In timekeeping_cycles_to_ns() calculation, check against max_cycles, falling back to a slower higher precision calculation. In timekeeping_forward_now(), process delta in chunks of at most max_cycles. Suggested-by: Thomas Gleixner <[email protected]> Signed-off-by: Adrian Hunter <[email protected]> Signed-off-by: Thomas Gleixner <[email protected]> Link: https://lore.kernel.org/r/[email protected]
1 parent e809a80 commit fcf190c

File tree

1 file changed

+29
-11
lines changed

1 file changed

+29
-11
lines changed

kernel/time/timekeeping.c

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -364,19 +364,32 @@ static void tk_setup_internals(struct timekeeper *tk, struct clocksource *clock)
364364
}
365365

366366
/* Timekeeper helper functions. */
367+
static noinline u64 delta_to_ns_safe(const struct tk_read_base *tkr, u64 delta)
368+
{
369+
return mul_u64_u32_add_u64_shr(delta, tkr->mult, tkr->xtime_nsec, tkr->shift);
370+
}
371+
367372
static inline u64 timekeeping_cycles_to_ns(const struct tk_read_base *tkr, u64 cycles)
368373
{
369374
/* Calculate the delta since the last update_wall_time() */
370375
u64 mask = tkr->mask, delta = (cycles - tkr->cycle_last) & mask;
371376

372-
if (IS_ENABLED(CONFIG_CLOCKSOURCE_VALIDATE_LAST_CYCLE)) {
373-
/*
374-
* Handle clocksource inconsistency between CPUs to prevent
375-
* time from going backwards by checking for the MSB of the
376-
* mask being set in the delta.
377-
*/
378-
if (unlikely(delta & ~(mask >> 1)))
379-
return tkr->xtime_nsec >> tkr->shift;
377+
/*
378+
* This detects the case where the delta overflows the multiplication
379+
* with tkr->mult.
380+
*/
381+
if (unlikely(delta > tkr->clock->max_cycles)) {
382+
if (IS_ENABLED(CONFIG_CLOCKSOURCE_VALIDATE_LAST_CYCLE)) {
383+
/*
384+
* Handle clocksource inconsistency between CPUs to prevent
385+
* time from going backwards by checking for the MSB of the
386+
* mask being set in the delta.
387+
*/
388+
if (unlikely(delta & ~(mask >> 1)))
389+
return tkr->xtime_nsec >> tkr->shift;
390+
}
391+
392+
return delta_to_ns_safe(tkr, delta);
380393
}
381394

382395
return ((delta * tkr->mult) + tkr->xtime_nsec) >> tkr->shift;
@@ -789,10 +802,15 @@ static void timekeeping_forward_now(struct timekeeper *tk)
789802
tk->tkr_mono.cycle_last = cycle_now;
790803
tk->tkr_raw.cycle_last = cycle_now;
791804

792-
tk->tkr_mono.xtime_nsec += delta * tk->tkr_mono.mult;
793-
tk->tkr_raw.xtime_nsec += delta * tk->tkr_raw.mult;
805+
while (delta > 0) {
806+
u64 max = tk->tkr_mono.clock->max_cycles;
807+
u64 incr = delta < max ? delta : max;
794808

795-
tk_normalize_xtime(tk);
809+
tk->tkr_mono.xtime_nsec += incr * tk->tkr_mono.mult;
810+
tk->tkr_raw.xtime_nsec += incr * tk->tkr_raw.mult;
811+
tk_normalize_xtime(tk);
812+
delta -= incr;
813+
}
796814
}
797815

798816
/**

0 commit comments

Comments
 (0)