History

clocksource: Make negative motion detection more robust

Guenter reported boot stalls on a emulated ARM 32-bit platform, which has a 24-bit wide clocksource. It turns out that the calculated maximal idle time, which limits idle sleeps to prevent clocksource wrap arounds, is close to the point where the negative motion detection triggers. max_idle_ns: 597268854 ns negative motion tripping point: 671088640 ns If the idle wakeup is delayed beyond that point, the clocksource advances far enough to trigger the negative motion detection. This prevents the clock to advance and in the worst case the system stalls completely if the consecutive sleeps based on the stale clock are delayed as well. Cure this by calculating a more robust cut-off value for negative motion, which covers 87.5% of the actual clocksource counter width. Compare the delta against this value to catch negative motion. This is specifically for clock sources with a small counter width as their wrap around time is close to the half counter width. For clock sources with wide counters this is not a problem because the maximum idle time is far from the half counter width due to the math overflow protection constraints. For the case at hand this results in a tripping point of 1174405120ns. Note, that this cannot prevent issues when the delay exceeds the 87.5% margin, but that's not different from the previous unchecked version which allowed arbitrary time jumps. Systems with small counter width are prone to invalid results, but this problem is unlikely to be seen on real hardware. If such a system completely stalls for more than half a second, then there are other more urgent problems than the counter wrapping around.

Thomas Gleixner committed 1 year ago

Commit 76031d91 parent e70140b4 files · +20 −7Browse files

Unified · Split

4 files
  1. include/linux/clocksource.h +2 −0
  2. kernel/time/clocksource.c +10 −1
  3. kernel/time/timekeeping.c +4 −2
  4. kernel/time/timekeeping_internal.h +4 −4
include/linux/clocksource.h+2−0
@@ -49,6 +49,7 @@ struct module; · 48 unmodified lines
49 * @archdata: Optional arch-specific data49 * @archdata: Optional arch-specific data
50 * @max_cycles: Maximum safe cycle value which won't overflow on50 * @max_cycles: Maximum safe cycle value which won't overflow on
51 * multiplication51 * multiplication
52 * @max_raw_delta: Maximum safe delta value for negative motion detection
52 * @name: Pointer to clocksource name53 * @name: Pointer to clocksource name
53 * @list: List head for registration (internal)54 * @list: List head for registration (internal)
54 * @freq_khz: Clocksource frequency in khz.55 * @freq_khz: Clocksource frequency in khz.
@@ -109,6 +110,7 @@ struct clocksource { · 54 unmodified lines
109 struct arch_clocksource_data archdata;110 struct arch_clocksource_data archdata;
110#endif111#endif
111 u64 max_cycles;112 u64 max_cycles;
113 u64 max_raw_delta;
112 const char *name;114 const char *name;
113 struct list_head list;115 struct list_head list;
114 u32 freq_khz;116 u32 freq_khz;
kernel/time/clocksource.c+10−1
@@ -24,7 +24,7 @@ static void clocksource_enqueue(struct clocksource *cs); · 23 unmodified lines
24 24
25static noinline u64 cycles_to_nsec_safe(struct clocksource *cs, u64 start, u64 end)25static noinline u64 cycles_to_nsec_safe(struct clocksource *cs, u64 start, u64 end)
26{26{
27 u64 delta = clocksource_delta(end, start, cs->mask);27 u64 delta = clocksource_delta(end, start, cs->mask, cs->max_raw_delta);
28 28
29 if (likely(delta < cs->max_cycles))29 if (likely(delta < cs->max_cycles))
30 return clocksource_cyc2ns(delta, cs->mult, cs->shift);30 return clocksource_cyc2ns(delta, cs->mult, cs->shift);
@@ -993,6 +993,15 @@ static inline void clocksource_update_max_deferment(struct clocksource *cs) · 962 unmodified lines
993 cs->max_idle_ns = clocks_calc_max_nsecs(cs->mult, cs->shift,993 cs->max_idle_ns = clocks_calc_max_nsecs(cs->mult, cs->shift,
994 cs->maxadj, cs->mask,994 cs->maxadj, cs->mask,
995 &cs->max_cycles);995 &cs->max_cycles);
996
997 /*
998 * Threshold for detecting negative motion in clocksource_delta().
999 *
1000 * Allow for 0.875 of the counter width so that overly long idle
1001 * sleeps, which go slightly over mask/2, do not trigger the
1002 * negative motion detection.
1003 */
1004 cs->max_raw_delta = (cs->mask >> 1) + (cs->mask >> 2) + (cs->mask >> 3);
996}1005}
997 1006
998static struct clocksource *clocksource_find_best(bool oneshot, bool skipcur)1007static struct clocksource *clocksource_find_best(bool oneshot, bool skipcur)
kernel/time/timekeeping.c+4−2
@@ -755,7 +755,8 @@ static void timekeeping_forward_now(struct timekeeper *tk) · 754 unmodified lines
755 u64 cycle_now, delta;755 u64 cycle_now, delta;
756 756
757 cycle_now = tk_clock_read(&tk->tkr_mono);757 cycle_now = tk_clock_read(&tk->tkr_mono);
758 delta = clocksource_delta(cycle_now, tk->tkr_mono.cycle_last, tk->tkr_mono.mask);758 delta = clocksource_delta(cycle_now, tk->tkr_mono.cycle_last, tk->tkr_mono.mask,
759 tk->tkr_mono.clock->max_raw_delta);
759 tk->tkr_mono.cycle_last = cycle_now;760 tk->tkr_mono.cycle_last = cycle_now;
760 tk->tkr_raw.cycle_last = cycle_now;761 tk->tkr_raw.cycle_last = cycle_now;
761 762
@@ -2230,7 +2231,8 @@ static bool timekeeping_advance(enum timekeeping_adv_mode mode) · 1468 unmodified lines
2230 return false;2231 return false;
2231 2232
2232 offset = clocksource_delta(tk_clock_read(&tk->tkr_mono),2233 offset = clocksource_delta(tk_clock_read(&tk->tkr_mono),
2233 tk->tkr_mono.cycle_last, tk->tkr_mono.mask);2234 tk->tkr_mono.cycle_last, tk->tkr_mono.mask,
2235 tk->tkr_mono.clock->max_raw_delta);
2234 2236
2235 /* Check if there's really nothing to do */2237 /* Check if there's really nothing to do */
2236 if (offset < real_tk->cycle_interval && mode == TK_ADV_TICK)2238 if (offset < real_tk->cycle_interval && mode == TK_ADV_TICK)
kernel/time/timekeeping_internal.h+4−4
@@ -30,15 +30,15 @@ static inline void timekeeping_inc_mg_floor_swaps(void) · 29 unmodified lines
30 30
31#endif31#endif
32 32
33static inline u64 clocksource_delta(u64 now, u64 last, u64 mask)33static inline u64 clocksource_delta(u64 now, u64 last, u64 mask, u64 max_delta)
34{34{
35 u64 ret = (now - last) & mask;35 u64 ret = (now - last) & mask;
36 36
37 /*37 /*
38 * Prevent time going backwards by checking the MSB of mask in38 * Prevent time going backwards by checking the result against
39 * the result. If set, return 0.39 * @max_delta. If greater, return 0.
40 */40 */
41 return ret & ~(mask >> 1) ? 0 : ret;41 return ret > max_delta ? 0 : ret;
42}42}
43 43
44/* Semi public for serialization of non timekeeper VDSO updates. */44/* Semi public for serialization of non timekeeper VDSO updates. */

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t