diff options
Diffstat (limited to 'arch/s390/kernel/time.c')
-rw-r--r-- | arch/s390/kernel/time.c | 41 |
1 files changed, 40 insertions, 1 deletions
diff --git a/arch/s390/kernel/time.c b/arch/s390/kernel/time.c index 5ba6c67ddd99..33082f6cbb5d 100644 --- a/arch/s390/kernel/time.c +++ b/arch/s390/kernel/time.c @@ -62,6 +62,8 @@ unsigned char ptff_function_mask[16]; static unsigned long long lpar_offset; static unsigned long long initial_leap_seconds; +static unsigned long long tod_steering_end; +static unsigned long long tod_steering_delta; /* * Get time offsets with PTFF @@ -71,8 +73,13 @@ void __init time_early_init(void) struct ptff_qto qto; struct ptff_qui qui; + /* Initialize TOD steering parameters */ + tod_steering_end = sched_clock_base_cc; + vdso_data->ts_end = tod_steering_end; + if (!test_facility(28)) return; + ptff(&ptff_function_mask, sizeof(ptff_function_mask), PTFF_QAF); /* get LPAR offset */ @@ -204,7 +211,22 @@ void read_boot_clock64(struct timespec64 *ts) static cycle_t read_tod_clock(struct clocksource *cs) { - return get_tod_clock(); + unsigned long long now, adj; + + preempt_disable(); /* protect from changes to steering parameters */ + now = get_tod_clock(); + adj = tod_steering_end - now; + if (unlikely((s64) adj >= 0)) + /* + * manually steer by 1 cycle every 2^16 cycles. This + * corresponds to shifting the tod delta by 15. 1s is + * therefore steered in ~9h. The adjust will decrease + * over time, until it finally reaches 0. + */ + now += ((s64) tod_steering_delta < 0) ? + (adj >> 15) : -(adj >> 15); + preempt_enable(); + return now; } static struct clocksource clocksource_tod = { @@ -379,10 +401,27 @@ static inline int check_sync_clock(void) */ static void clock_sync_global(unsigned long long delta) { + unsigned long now, adj; struct ptff_qto qto; /* Fixup the monotonic sched clock. */ sched_clock_base_cc += delta; + /* Adjust TOD steering parameters. */ + vdso_data->tb_update_count++; + now = get_tod_clock(); + adj = tod_steering_end - now; + if (unlikely((s64) adj >= 0)) + /* Calculate how much of the old adjustment is left. */ + tod_steering_delta = ((s64) tod_steering_delta < 0) ? + -(adj >> 15) : (adj >> 15); + tod_steering_delta += delta; + if ((abs(tod_steering_delta) >> 48) != 0) + panic("TOD clock sync offset %lli is too large to drift\n", + tod_steering_delta); + tod_steering_end = now + (abs(tod_steering_delta) << 15); + vdso_data->ts_dir = (tod_steering_delta < 0) ? 0 : 1; + vdso_data->ts_end = tod_steering_end; + vdso_data->tb_update_count++; /* Update LPAR offset. */ if (ptff_query(PTFF_QTO) && ptff(&qto, sizeof(qto), PTFF_QTO) == 0) lpar_offset = qto.tod_epoch_difference; |