timekeeping_apply_adjustment() modifies xtime_nsec to ensure monotonicity when mult changes: xtime_nsec -= offset This ensures that the time reported to userspace does not jump when the multiplier is adjusted from one tick to the next. However, the ntp_error accumulator which tracks the difference between intended and actual clock position was not being updated to reflect this additional discrepancy. An earlier attempt at this compensation existed as: ntp_error -= (interval - offset) << ntp_error_shift but was removed in commit c2cda2a5bda9 ("timekeeping/ntp: Don't align NTP frequency adjustments to ticks") because it was a major source of NTP error. That's because (interval - offset) was wrong: the subtraction of "interval" prematurely accounted for the changed xtime_interval of the next tick, which would be correctly accounted in the next accumulation anyway — a double subtraction. What is actually needed is just the "offset" part: ntp_error must be told that xtime_nsec moved by "offset" without a corresponding change in the intended position. For the normal ±1 mult dithering this is negligible (the adjustments cancel over time), but for larger mult changes — such as when an external reference clock sets a new frequency — the one-time uncompensated offset is significant. Fix by adjusting ntp_error by the correct amount: ntp_error += offset << ntp_error_shift This keeps ntp_error consistent with the actual xtime_nsec position after the adjustment, and ensures the discrepancy is correctly smoothed away over time and the clock returns to where it should have been.
Unified · Split
| @@ -2404,6 +2404,11 @@ static __always_inline void timekeeping_apply_adjustment(struct timekeeper *tk, · 2403 unmodified lines | ||
| 2404 | 2404 | * xtime_nsec_2 = xtime_nsec_1 - offset |
| 2405 | 2405 | * Which simplifies to: |
| 2406 | 2406 | * xtime_nsec -= offset |
| 2407 | + * | |
| 2408 | + * When subtracting offset from xtime_nsec, the same amount | |
| 2409 | + * (in appropriate units) has to be added to ntp_error, in | |
| 2410 | + * order to correctly track the delta between the time | |
| 2411 | + * reported in xtime_nsec, and the intended time. | |
| 2407 | 2412 | */ |
| 2408 | 2413 | if ((mult_adj > 0) && (tk->tkr_mono.mult + mult_adj < mult_adj)) { |
| 2409 | 2414 | /* NTP adjustment caused clocksource mult overflow */ |
| @@ -2414,6 +2419,7 @@ static __always_inline void timekeeping_apply_adjustment(struct timekeeper *tk, · 4 unmodified lines | ||
| 2414 | 2419 | tk->tkr_mono.mult += mult_adj; |
| 2415 | 2420 | tk->xtime_interval += interval; |
| 2416 | 2421 | tk->tkr_mono.xtime_nsec -= offset; |
| 2422 | + tk->ntp_error += offset << tk->ntp_error_shift; | |
| 2417 | 2423 | } |
| 2418 | 2424 | |
| 2419 | 2425 | /* |