diff --git a/repos/os/include/timer_session/connection.h b/repos/os/include/timer_session/connection.h index c2e4cd6a9e..4f7a99b3bd 100644 --- a/repos/os/include/timer_session/connection.h +++ b/repos/os/include/timer_session/connection.h @@ -187,16 +187,8 @@ class Timer::Connection : public Genode::Connection, ** Time_source helpers ** *************************/ - /* - * The higher the factor shift, the more precise is the time - * interpolation but the more likely it becomes that an overflow - * would occur during calculations. In this case, the timer - * down-scales the values live which is avoidable overhead. - */ - enum { TS_TO_US_RATIO_SHIFT = 4 }; enum { MIN_TIMEOUT_US = 5000 }; enum { REAL_TIME_UPDATE_PERIOD_US = 500000 }; - enum { MAX_TS = ~(Timestamp)0ULL >> TS_TO_US_RATIO_SHIFT }; enum { MAX_INTERPOLATION_QUALITY = 3 }; enum { MAX_REMOTE_TIME_LATENCY_US = 500 }; enum { MAX_REMOTE_TIME_TRIALS = 5 }; @@ -210,14 +202,17 @@ class Timer::Connection : public Genode::Connection, Duration _real_time { Milliseconds(_ms) }; Duration _interpolated_time { _real_time }; unsigned _interpolation_quality { 0 }; - unsigned long _us_to_ts_factor { 1UL << TS_TO_US_RATIO_SHIFT }; + unsigned long _us_to_ts_factor { 1UL }; + unsigned _us_to_ts_factor_shift { 0 }; Timestamp _timestamp(); void _update_interpolation_quality(unsigned long min_factor, unsigned long max_factor); - unsigned long _ts_to_us_ratio(Timestamp ts, unsigned long us); + unsigned long _ts_to_us_ratio(Timestamp ts, + unsigned long us, + unsigned shift); void _update_real_time(); diff --git a/repos/os/src/lib/timeout/timer_connection.cc b/repos/os/src/lib/timeout/timer_connection.cc index c166826069..b06ec5b713 100644 --- a/repos/os/src/lib/timeout/timer_connection.cc +++ b/repos/os/src/lib/timeout/timer_connection.cc @@ -25,8 +25,6 @@ void Timer::Connection::_update_interpolation_quality(unsigned long min_factor, /* * If the factor gets adapted less than 12.5%, we raise the * interpolation-quality value. Otherwise, we reset it to zero. - * We can safely do the shift on the factor as it should be - * at least of value 1 << TS_TO_US_RATIO_SHIFT. */ if ((max_factor - min_factor) < (max_factor >> 3)) { if (_interpolation_quality < MAX_INTERPOLATION_QUALITY) { @@ -38,7 +36,8 @@ void Timer::Connection::_update_interpolation_quality(unsigned long min_factor, unsigned long Timer::Connection::_ts_to_us_ratio(Timestamp ts, - unsigned long us) + unsigned long us, + unsigned shift) { /* * If the timestamp difference is to big to do the following @@ -46,7 +45,8 @@ unsigned long Timer::Connection::_ts_to_us_ratio(Timestamp ts, * and time difference down equally. This should neither happen * often nor have much effect on the resulting factor. */ - while (ts > MAX_TS) { + Timestamp const max_ts = ~(Timestamp)0ULL >> shift; + while (ts > max_ts) { warning("timestamp value too big"); ts >>= 1; us >>= 1; @@ -59,13 +59,13 @@ unsigned long Timer::Connection::_ts_to_us_ratio(Timestamp ts, * the calculation. This upscaling must be considered when using * the result. */ - Timestamp const result = (ts << TS_TO_US_RATIO_SHIFT) / us; + Timestamp const result = (ts << shift) / us; unsigned long const result_ul = (unsigned long)result; - if (result == result_ul) { - return result_ul; } - - warning("Timestamp-to-time ratio too big"); - return ~0UL; + if (result != result_ul) { + warning("Timestamp-to-time ratio too big"); + return ~0UL; + } + return result_ul; } diff --git a/repos/os/src/lib/timeout/timer_connection_time.cc b/repos/os/src/lib/timeout/timer_connection_time.cc index a467c30aac..f4f6409fce 100644 --- a/repos/os/src/lib/timeout/timer_connection_time.cc +++ b/repos/os/src/lib/timeout/timer_connection_time.cc @@ -23,55 +23,150 @@ void Timer::Connection::_update_real_time() { Lock_guard lock_guard(_real_time_lock); - Timestamp ts = 0UL; - unsigned long ms = 0UL; - unsigned long us_diff = ~0UL; + /* + * Update timestamp, time, and real-time value + */ + + Timestamp ts = 0UL; + unsigned long ms = 0UL; + unsigned long latency_us = ~0UL; + + /* + * We retry reading out timestamp plus remote time until the result + * fulfills a given latency. If the maximum number of trials is + * reached, we take the results that has the lowest latency. + */ for (unsigned remote_time_trials = 0; remote_time_trials < MAX_REMOTE_TIME_TRIALS; remote_time_trials++) { - /* determine time and timestamp difference since the last call */ + /* read out the two time values close in succession */ Timestamp volatile new_ts = _timestamp(); unsigned long volatile new_ms = elapsed_ms(); + /* + * If interpolation is not ready, yet we cannot determine a latency + * and take the values as they are. + */ if (_interpolation_quality < MAX_INTERPOLATION_QUALITY) { ms = new_ms; ts = new_ts; break; } + /* determine latency between reading out timestamp and time value */ + Timestamp const ts_diff = _timestamp() - new_ts; + unsigned long const new_latency_us = + _ts_to_us_ratio(ts_diff, _us_to_ts_factor, _us_to_ts_factor_shift); - Timestamp const ts_diff = _timestamp() - new_ts; - unsigned long const new_us_diff = _ts_to_us_ratio(ts_diff, - _us_to_ts_factor); - - /* remember results only if the latency was better than last time */ - if (new_us_diff < us_diff) { + /* remember results if the latency was better than on the last trial */ + if (new_latency_us < latency_us) { ms = new_ms; ts = new_ts; - if (us_diff < MAX_REMOTE_TIME_LATENCY_US) { + /* take the results if the latency fulfills the given maximum */ + if (latency_us < MAX_REMOTE_TIME_LATENCY_US) { break; } } } + /* determine timestamp and time difference */ unsigned long const ms_diff = ms - _ms; - Timestamp const ts_diff = ts - _ts; + Timestamp ts_diff = ts - _ts; - /* update real time and values for next difference calculation */ + /* overwrite timestamp, time, and real time member */ _ms = ms; _ts = ts; _real_time += Milliseconds(ms_diff); - unsigned long const new_factor = _ts_to_us_ratio(ts_diff, ms_diff * 1000UL); - unsigned long const old_factor = _us_to_ts_factor; - /* update interpolation-quality value */ - if (old_factor > new_factor) { _update_interpolation_quality(new_factor, old_factor); } - else { _update_interpolation_quality(old_factor, new_factor); } + /* + * Update timestamp-to-time factor and its shift + */ - _us_to_ts_factor = new_factor; + enum { MAX_FACTOR_SHIFT = sizeof(unsigned long) * 8 }; + + unsigned factor_shift = _us_to_ts_factor_shift; + unsigned long old_factor = _us_to_ts_factor; + unsigned long const us_diff = ms_diff * 1000UL; + Timestamp max_ts_diff = ~(Timestamp)0ULL >> factor_shift; + Timestamp min_ts_diff_shifted = ~(Timestamp)0ULL >> 1; + + /* + * If the calculation type is bigger than the resulting factor type, + * we have to apply further limitations to avoid a loss at the final cast. + */ + if (sizeof(Timestamp) > sizeof(unsigned long)) { + + Timestamp limit_ts_diff_shifted = (Timestamp)~0UL * us_diff; + Timestamp const limit_ts_diff = limit_ts_diff_shifted >> + factor_shift; + + /* + * Avoid that we leave the factor shift on such a high level that + * casting the factor to its final type causes a loss. + */ + if (max_ts_diff > limit_ts_diff) { + max_ts_diff = limit_ts_diff; + } + /* + * Avoid that we raise the factor shift such that casting the factor + * to its final type causes a loss. + */ + limit_ts_diff_shifted >>= 1; + if (min_ts_diff_shifted > limit_ts_diff_shifted) { + min_ts_diff_shifted = limit_ts_diff_shifted; + } + } + + struct Factor_update_failed : Genode::Exception { }; + try { + /* meet the timestamp-difference limit before applying the shift */ + while (ts_diff > max_ts_diff) { + + /* if possible, lower the shift to meet the limitation */ + if (!factor_shift) { + error("timestamp difference too big"); + throw Factor_update_failed(); + } + factor_shift--; + max_ts_diff = (max_ts_diff << 1) | 1; + old_factor >>= 1; + } + /* + * Apply current shift to timestamp difference and try to even + * raise the shift successively to get as much precision as possible. + */ + Timestamp ts_diff_shifted = ts_diff << factor_shift; + while (ts_diff_shifted <= min_ts_diff_shifted && + factor_shift < MAX_FACTOR_SHIFT) + { + factor_shift++; + ts_diff_shifted <<= 1; + old_factor <<= 1; + } + /* + * The cast to unsigned long does not cause a loss because of the + * limitations we applied to the timestamp difference. + */ + + unsigned long const new_factor = + (unsigned long)((Timestamp)ts_diff_shifted / us_diff); + + /* update interpolation-quality value */ + if (old_factor > new_factor) { _update_interpolation_quality(new_factor, old_factor); } + else { _update_interpolation_quality(old_factor, new_factor); } + + /* overwrite factor and factor-shift member */ + _us_to_ts_factor_shift = factor_shift; + _us_to_ts_factor = new_factor; + + } catch (Factor_update_failed) { + + /* disable interpolation */ + _interpolation_quality = 0; + } } @@ -88,30 +183,27 @@ Duration Timer::Connection::curr_time() * a yet unstable factor, there's an increased risk that the * interpolated time falsely reaches an enourmous level. Then * the value would stand still for quite some time because we - * can't let it jump back to a more realistic level. This - * would also eliminate updates of the real time as the - * timeout scheduler that manages our update timeout also - * uses this function. + * can't let it jump back to a more realistic level. */ if (_interpolation_quality == MAX_INTERPOLATION_QUALITY) { - /* locally buffer real-time related members */ - Timestamp const ts = _ts; - unsigned long const us_to_ts_factor = _us_to_ts_factor; + /* buffer interpolation related members and free the lock */ + Timestamp const ts = _ts; + unsigned long const us_to_ts_factor = _us_to_ts_factor; + unsigned const us_to_ts_factor_shift = _us_to_ts_factor_shift; lock_guard.destruct(); - /* get time difference since the last real time update */ + /* interpolate time difference since the last real time update */ Timestamp const ts_diff = _timestamp() - ts; - unsigned long const us_diff = _ts_to_us_ratio(ts_diff, us_to_ts_factor); + unsigned long const us_diff = _ts_to_us_ratio(ts_diff, us_to_ts_factor, + us_to_ts_factor_shift); interpolated_time += Microseconds(us_diff); } else { - /* - * Use remote timer instead of timestamps - */ + /* use remote timer instead of timestamps */ interpolated_time += Milliseconds(elapsed_ms() - _ms); lock_guard.destruct();