diff options
Diffstat (limited to 'services/common_time/clock_recovery.cpp')
-rw-r--r-- | services/common_time/clock_recovery.cpp | 423 |
1 files changed, 0 insertions, 423 deletions
diff --git a/services/common_time/clock_recovery.cpp b/services/common_time/clock_recovery.cpp deleted file mode 100644 index 3a7c70c..0000000 --- a/services/common_time/clock_recovery.cpp +++ /dev/null @@ -1,423 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* - * A service that exchanges time synchronization information between - * a master that defines a timeline and clients that follow the timeline. - */ - -#define __STDC_LIMIT_MACROS -#define LOG_TAG "common_time" -#include <utils/Log.h> -#include <stdint.h> - -#include <common_time/local_clock.h> -#include <assert.h> - -#include "clock_recovery.h" -#include "common_clock.h" -#ifdef TIME_SERVICE_DEBUG -#include "diag_thread.h" -#endif - -// Define log macro so we can make LOGV into LOGE when we are exclusively -// debugging this code. -#ifdef TIME_SERVICE_DEBUG -#define LOG_TS ALOGE -#else -#define LOG_TS ALOGV -#endif - -namespace android { - -ClockRecoveryLoop::ClockRecoveryLoop(LocalClock* local_clock, - CommonClock* common_clock) { - assert(NULL != local_clock); - assert(NULL != common_clock); - - local_clock_ = local_clock; - common_clock_ = common_clock; - - local_clock_can_slew_ = local_clock_->initCheck() && - (local_clock_->setLocalSlew(0) == OK); - tgt_correction_ = 0; - cur_correction_ = 0; - - // Precompute the max rate at which we are allowed to change the VCXO - // control. - uint64_t N = 0x10000ull * 1000ull; - uint64_t D = local_clock_->getLocalFreq() * kMinFullRangeSlewChange_mSec; - LinearTransform::reduce(&N, &D); - while ((N > INT32_MAX) || (D > UINT32_MAX)) { - N >>= 1; - D >>= 1; - LinearTransform::reduce(&N, &D); - } - time_to_cur_slew_.a_to_b_numer = static_cast<int32_t>(N); - time_to_cur_slew_.a_to_b_denom = static_cast<uint32_t>(D); - - reset(true, true); - -#ifdef TIME_SERVICE_DEBUG - diag_thread_ = new DiagThread(common_clock_, local_clock_); - if (diag_thread_ != NULL) { - status_t res = diag_thread_->startWorkThread(); - if (res != OK) - ALOGW("Failed to start A@H clock recovery diagnostic thread."); - } else - ALOGW("Failed to allocate diagnostic thread."); -#endif -} - -ClockRecoveryLoop::~ClockRecoveryLoop() { -#ifdef TIME_SERVICE_DEBUG - diag_thread_->stopWorkThread(); -#endif -} - -// Constants. -const float ClockRecoveryLoop::dT = 1.0; -const float ClockRecoveryLoop::Kc = 1.0f; -const float ClockRecoveryLoop::Ti = 15.0f; -const float ClockRecoveryLoop::Tf = 0.05; -const float ClockRecoveryLoop::bias_Fc = 0.01; -const float ClockRecoveryLoop::bias_RC = (dT / (2 * 3.14159f * bias_Fc)); -const float ClockRecoveryLoop::bias_Alpha = (dT / (bias_RC + dT)); -const int64_t ClockRecoveryLoop::panic_thresh_ = 50000; -const int64_t ClockRecoveryLoop::control_thresh_ = 10000; -const float ClockRecoveryLoop::COmin = -100.0f; -const float ClockRecoveryLoop::COmax = 100.0f; -const uint32_t ClockRecoveryLoop::kMinFullRangeSlewChange_mSec = 300; -const int ClockRecoveryLoop::kSlewChangeStepPeriod_mSec = 10; - - -void ClockRecoveryLoop::reset(bool position, bool frequency) { - Mutex::Autolock lock(&lock_); - reset_l(position, frequency); -} - -uint32_t ClockRecoveryLoop::findMinRTTNdx(DisciplineDataPoint* data, - uint32_t count) { - uint32_t min_rtt = 0; - for (uint32_t i = 1; i < count; ++i) - if (data[min_rtt].rtt > data[i].rtt) - min_rtt = i; - - return min_rtt; -} - -bool ClockRecoveryLoop::pushDisciplineEvent(int64_t local_time, - int64_t nominal_common_time, - int64_t rtt) { - Mutex::Autolock lock(&lock_); - - int64_t local_common_time = 0; - common_clock_->localToCommon(local_time, &local_common_time); - int64_t raw_delta = nominal_common_time - local_common_time; - -#ifdef TIME_SERVICE_DEBUG - ALOGE("local=%lld, common=%lld, delta=%lld, rtt=%lld\n", - local_common_time, nominal_common_time, - raw_delta, rtt); -#endif - - // If we have not defined a basis for common time, then we need to use these - // initial points to do so. In order to avoid significant initial error - // from a particularly bad startup data point, we collect the first N data - // points and choose the best of them before moving on. - if (!common_clock_->isValid()) { - if (startup_filter_wr_ < kStartupFilterSize) { - DisciplineDataPoint& d = startup_filter_data_[startup_filter_wr_]; - d.local_time = local_time; - d.nominal_common_time = nominal_common_time; - d.rtt = rtt; - startup_filter_wr_++; - } - - if (startup_filter_wr_ == kStartupFilterSize) { - uint32_t min_rtt = findMinRTTNdx(startup_filter_data_, - kStartupFilterSize); - - common_clock_->setBasis( - startup_filter_data_[min_rtt].local_time, - startup_filter_data_[min_rtt].nominal_common_time); - } - - return true; - } - - int64_t observed_common; - int64_t delta; - float delta_f, dCO; - int32_t tgt_correction; - - if (OK != common_clock_->localToCommon(local_time, &observed_common)) { - // Since we just checked to make certain that this conversion was valid, - // and no one else in the system should be messing with it, if this - // conversion is suddenly invalid, it is a good reason to panic. - ALOGE("Failed to convert local time to common time in %s:%d", - __PRETTY_FUNCTION__, __LINE__); - return false; - } - - // Implement a filter which should match NTP filtering behavior when a - // client is associated with only one peer of lower stratum. Basically, - // always use the best of the N last data points, where best is defined as - // lowest round trip time. NTP uses an N of 8; we use a value of 6. - // - // TODO(johngro) : experiment with other filter strategies. The goal here - // is to mitigate the effects of high RTT data points which typically have - // large asymmetries in the TX/RX legs. Downside of the existing NTP - // approach (particularly because of the PID controller we are using to - // produce the control signal from the filtered data) are that the rate at - // which discipline events are actually acted upon becomes irregular and can - // become drawn out (the time between actionable event can go way up). If - // the system receives a strong high quality data point, the proportional - // component of the controller can produce a strong correction which is left - // in place for too long causing overshoot. In addition, the integral - // component of the system currently is an approximation based on the - // assumption of a more or less homogeneous sampling of the error. Its - // unclear what the effect of undermining this assumption would be right - // now. - - // Two ideas which come to mind immediately would be to... - // 1) Keep a history of more data points (32 or so) and ignore data points - // whose RTT is more than a certain number of standard deviations outside - // of the norm. - // 2) Eliminate the PID controller portion of this system entirely. - // Instead, move to a system which uses a very wide filter (128 data - // points or more) with a sum-of-least-squares line fitting approach to - // tracking the long term drift. This would take the place of the I - // component in the current PID controller. Also use a much more narrow - // outlier-rejector filter (as described in #1) to drive a short term - // correction factor similar to the P component of the PID controller. - assert(filter_wr_ < kFilterSize); - filter_data_[filter_wr_].local_time = local_time; - filter_data_[filter_wr_].observed_common_time = observed_common; - filter_data_[filter_wr_].nominal_common_time = nominal_common_time; - filter_data_[filter_wr_].rtt = rtt; - filter_data_[filter_wr_].point_used = false; - uint32_t current_point = filter_wr_; - filter_wr_ = (filter_wr_ + 1) % kFilterSize; - if (!filter_wr_) - filter_full_ = true; - - uint32_t scan_end = filter_full_ ? kFilterSize : filter_wr_; - uint32_t min_rtt = findMinRTTNdx(filter_data_, scan_end); - // We only use packets with low RTTs for control. If the packet RTT - // is less than the panic threshold, we can probably eat the jitter with the - // control loop. Otherwise, take the packet only if it better than all - // of the packets we have in the history. That way we try to track - // something, even if it is noisy. - if (current_point == min_rtt || rtt < control_thresh_) { - delta_f = delta = nominal_common_time - observed_common; - - last_error_est_valid_ = true; - last_error_est_usec_ = delta; - - // Compute the error then clamp to the panic threshold. If we ever - // exceed this amt of error, its time to panic and reset the system. - // Given that the error in the measurement of the error could be as - // high as the RTT of the data point, we don't actually panic until - // the implied error (delta) is greater than the absolute panic - // threashold plus the RTT. IOW - we don't panic until we are - // absoluely sure that our best case sync is worse than the absolute - // panic threshold. - int64_t effective_panic_thresh = panic_thresh_ + rtt; - if ((delta > effective_panic_thresh) || - (delta < -effective_panic_thresh)) { - // PANIC!!! - reset_l(false, true); - return false; - } - - } else { - // We do not have a good packet to look at, but we also do not want to - // free-run the clock at some crazy slew rate. So we guess the - // trajectory of the clock based on the last controller output and the - // estimated bias of our clock against the master. - // The net effect of this is that CO == CObias after some extended - // period of no feedback. - delta_f = last_delta_f_ - dT*(CO - CObias); - delta = delta_f; - } - - // Velocity form PI control equation. - dCO = Kc * (1.0f + dT/Ti) * delta_f - Kc * last_delta_f_; - CO += dCO * Tf; // Filter CO by applying gain <1 here. - - // Save error terms for later. - last_delta_f_ = delta_f; - - // Clamp CO to +/- 100ppm. - if (CO < COmin) - CO = COmin; - else if (CO > COmax) - CO = COmax; - - // Update the controller bias. - CObias = bias_Alpha * CO + (1.0f - bias_Alpha) * lastCObias; - lastCObias = CObias; - - // Convert PPM to 16-bit int range. Add some guard band (-0.01) so we - // don't get fp weirdness. - tgt_correction = CO * 327.66; - - // If there was a change in the amt of correction to use, update the - // system. - setTargetCorrection_l(tgt_correction); - - LOG_TS("clock_loop %lld %f %f %f %d\n", raw_delta, delta_f, CO, CObias, tgt_correction); - -#ifdef TIME_SERVICE_DEBUG - diag_thread_->pushDisciplineEvent( - local_time, - observed_common, - nominal_common_time, - tgt_correction, - rtt); -#endif - - return true; -} - -int32_t ClockRecoveryLoop::getLastErrorEstimate() { - Mutex::Autolock lock(&lock_); - - if (last_error_est_valid_) - return last_error_est_usec_; - else - return ICommonClock::kErrorEstimateUnknown; -} - -void ClockRecoveryLoop::reset_l(bool position, bool frequency) { - assert(NULL != common_clock_); - - if (position) { - common_clock_->resetBasis(); - startup_filter_wr_ = 0; - } - - if (frequency) { - last_error_est_valid_ = false; - last_error_est_usec_ = 0; - last_delta_f_ = 0.0; - CO = 0.0f; - lastCObias = CObias = 0.0f; - setTargetCorrection_l(0); - applySlew_l(); - } - - filter_wr_ = 0; - filter_full_ = false; -} - -void ClockRecoveryLoop::setTargetCorrection_l(int32_t tgt) { - // When we make a change to the slew rate, we need to be careful to not - // change it too quickly as it can anger some HDMI sinks out there, notably - // some Sony panels from the 2010-2011 timeframe. From experimenting with - // some of these sinks, it seems like swinging from one end of the range to - // another in less that 190mSec or so can start to cause trouble. Adding in - // a hefty margin, we limit the system to a full range sweep in no less than - // 300mSec. - if (tgt_correction_ != tgt) { - int64_t now = local_clock_->getLocalTime(); - status_t res; - - tgt_correction_ = tgt; - - // Set up the transformation to figure out what the slew should be at - // any given point in time in the future. - time_to_cur_slew_.a_zero = now; - time_to_cur_slew_.b_zero = cur_correction_; - - // Make sure the sign of the slope is headed in the proper direction. - bool needs_increase = (cur_correction_ < tgt_correction_); - bool is_increasing = (time_to_cur_slew_.a_to_b_numer > 0); - if (( needs_increase && !is_increasing) || - (!needs_increase && is_increasing)) { - time_to_cur_slew_.a_to_b_numer = -time_to_cur_slew_.a_to_b_numer; - } - - // Finally, figure out when the change will be finished and start the - // slew operation. - time_to_cur_slew_.doReverseTransform(tgt_correction_, - &slew_change_end_time_); - - applySlew_l(); - } -} - -bool ClockRecoveryLoop::applySlew_l() { - bool ret = true; - - // If cur == tgt, there is no ongoing sleq rate change and we are already - // finished. - if (cur_correction_ == tgt_correction_) - goto bailout; - - if (local_clock_can_slew_) { - int64_t now = local_clock_->getLocalTime(); - int64_t tmp; - - if (now >= slew_change_end_time_) { - cur_correction_ = tgt_correction_; - next_slew_change_timeout_.setTimeout(-1); - } else { - time_to_cur_slew_.doForwardTransform(now, &tmp); - - if (tmp > INT16_MAX) - cur_correction_ = INT16_MAX; - else if (tmp < INT16_MIN) - cur_correction_ = INT16_MIN; - else - cur_correction_ = static_cast<int16_t>(tmp); - - next_slew_change_timeout_.setTimeout(kSlewChangeStepPeriod_mSec); - ret = false; - } - - local_clock_->setLocalSlew(cur_correction_); - } else { - // Since we are not actually changing the rate of a HW clock, we don't - // need to worry to much about changing the slew rate so fast that we - // anger any downstream HDMI devices. - cur_correction_ = tgt_correction_; - next_slew_change_timeout_.setTimeout(-1); - - // The SW clock recovery implemented by the common clock class expects - // values expressed in PPM. CO is in ppm. - common_clock_->setSlew(local_clock_->getLocalTime(), CO); - } - -bailout: - return ret; -} - -int ClockRecoveryLoop::applyRateLimitedSlew() { - Mutex::Autolock lock(&lock_); - - int ret = next_slew_change_timeout_.msecTillTimeout(); - if (!ret) { - if (applySlew_l()) - next_slew_change_timeout_.setTimeout(-1); - ret = next_slew_change_timeout_.msecTillTimeout(); - } - - return ret; -} - -} // namespace android |