diff options
Diffstat (limited to 'libs/common_time')
-rw-r--r-- | libs/common_time/Android.mk | 36 | ||||
-rw-r--r-- | libs/common_time/clock_recovery.cpp | 423 | ||||
-rw-r--r-- | libs/common_time/clock_recovery.h | 159 | ||||
-rw-r--r-- | libs/common_time/common_clock.cpp | 150 | ||||
-rw-r--r-- | libs/common_time/common_clock.h | 57 | ||||
-rw-r--r-- | libs/common_time/common_clock_service.cpp | 157 | ||||
-rw-r--r-- | libs/common_time/common_clock_service.h | 91 | ||||
-rw-r--r-- | libs/common_time/common_time_config_service.cpp | 112 | ||||
-rw-r--r-- | libs/common_time/common_time_config_service.h | 60 | ||||
-rw-r--r-- | libs/common_time/common_time_server.cpp | 1506 | ||||
-rw-r--r-- | libs/common_time/common_time_server.h | 324 | ||||
-rw-r--r-- | libs/common_time/common_time_server_api.cpp | 438 | ||||
-rw-r--r-- | libs/common_time/common_time_server_packets.cpp | 293 | ||||
-rw-r--r-- | libs/common_time/common_time_server_packets.h | 189 | ||||
-rw-r--r-- | libs/common_time/diag_thread.cpp | 323 | ||||
-rw-r--r-- | libs/common_time/diag_thread.h | 76 | ||||
-rw-r--r-- | libs/common_time/main.cpp | 43 | ||||
-rw-r--r-- | libs/common_time/utils.cpp | 164 | ||||
-rw-r--r-- | libs/common_time/utils.h | 83 |
19 files changed, 4684 insertions, 0 deletions
diff --git a/libs/common_time/Android.mk b/libs/common_time/Android.mk new file mode 100644 index 0000000..75eb528 --- /dev/null +++ b/libs/common_time/Android.mk @@ -0,0 +1,36 @@ +LOCAL_PATH:= $(call my-dir) + +# +# common_time_service +# + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + common_clock_service.cpp \ + common_time_config_service.cpp \ + common_time_server.cpp \ + common_time_server_api.cpp \ + common_time_server_packets.cpp \ + clock_recovery.cpp \ + common_clock.cpp \ + main.cpp \ + utils.cpp + +# Uncomment to enable vesbose logging and debug service. +#TIME_SERVICE_DEBUG=true +ifeq ($(TIME_SERVICE_DEBUG), true) +LOCAL_SRC_FILES += diag_thread.cpp +LOCAL_CFLAGS += -DTIME_SERVICE_DEBUG +endif + +LOCAL_SHARED_LIBRARIES := \ + libbinder \ + libcommon_time_client \ + libutils \ + liblog + +LOCAL_MODULE_TAGS := optional +LOCAL_MODULE := common_time + +include $(BUILD_EXECUTABLE) diff --git a/libs/common_time/clock_recovery.cpp b/libs/common_time/clock_recovery.cpp new file mode 100644 index 0000000..3a7c70c --- /dev/null +++ b/libs/common_time/clock_recovery.cpp @@ -0,0 +1,423 @@ +/* + * 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 diff --git a/libs/common_time/clock_recovery.h b/libs/common_time/clock_recovery.h new file mode 100644 index 0000000..b6c87ff --- /dev/null +++ b/libs/common_time/clock_recovery.h @@ -0,0 +1,159 @@ +/* + * 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. + */ + +#ifndef __CLOCK_RECOVERY_H__ +#define __CLOCK_RECOVERY_H__ + +#include <stdint.h> +#include <common_time/ICommonClock.h> +#include <utils/LinearTransform.h> +#include <utils/threads.h> + +#ifdef TIME_SERVICE_DEBUG +#include "diag_thread.h" +#endif + +#include "utils.h" + +namespace android { + +class CommonClock; +class LocalClock; + +class ClockRecoveryLoop { + public: + ClockRecoveryLoop(LocalClock* local_clock, CommonClock* common_clock); + ~ClockRecoveryLoop(); + + void reset(bool position, bool frequency); + bool pushDisciplineEvent(int64_t local_time, + int64_t nominal_common_time, + int64_t data_point_rtt); + int32_t getLastErrorEstimate(); + + // Applies the next step in any ongoing slew change operation. Returns a + // timeout suitable for use with poll/select indicating the number of mSec + // until the next change should be applied. + int applyRateLimitedSlew(); + + private: + + // Tuned using the "Good Gain" method. + // See: + // http://techteach.no/publications/books/dynamics_and_control/tuning_pid_controller.pdf + + // Controller period (1Hz for now). + static const float dT; + + // Controller gain, positive and unitless. Larger values converge faster, + // but can cause instability. + static const float Kc; + + // Integral reset time. Smaller values cause loop to track faster, but can + // also cause instability. + static const float Ti; + + // Controller output filter time constant. Range (0-1). Smaller values make + // output smoother, but slow convergence. + static const float Tf; + + // Low-pass filter for bias tracker. + static const float bias_Fc; // HZ + static const float bias_RC; // Computed in constructor. + static const float bias_Alpha; // Computed inconstructor. + + // The maximum allowed error (as indicated by a pushDisciplineEvent) before + // we panic. + static const int64_t panic_thresh_; + + // The maximum allowed error rtt time for packets to be used for control + // feedback, unless the packet is the best in recent memory. + static const int64_t control_thresh_; + + typedef struct { + int64_t local_time; + int64_t observed_common_time; + int64_t nominal_common_time; + int64_t rtt; + bool point_used; + } DisciplineDataPoint; + + static uint32_t findMinRTTNdx(DisciplineDataPoint* data, uint32_t count); + + void reset_l(bool position, bool frequency); + void setTargetCorrection_l(int32_t tgt); + bool applySlew_l(); + + // The local clock HW abstraction we use as the basis for common time. + LocalClock* local_clock_; + bool local_clock_can_slew_; + + // The common clock we end up controlling along with the lock used to + // serialize operations. + CommonClock* common_clock_; + Mutex lock_; + + // parameters maintained while running and reset during a reset + // of the frequency correction. + bool last_error_est_valid_; + int32_t last_error_est_usec_; + float last_delta_f_; + int32_t integrated_error_; + int32_t tgt_correction_; + int32_t cur_correction_; + LinearTransform time_to_cur_slew_; + int64_t slew_change_end_time_; + Timeout next_slew_change_timeout_; + + // Contoller Output. + float CO; + + // Bias tracking for trajectory estimation. + float CObias; + float lastCObias; + + // Controller output bounds. The controller will not try to + // slew faster that +/-100ppm offset from center per interation. + static const float COmin; + static const float COmax; + + // State kept for filtering the discipline data. + static const uint32_t kFilterSize = 16; + DisciplineDataPoint filter_data_[kFilterSize]; + uint32_t filter_wr_; + bool filter_full_; + + static const uint32_t kStartupFilterSize = 4; + DisciplineDataPoint startup_filter_data_[kStartupFilterSize]; + uint32_t startup_filter_wr_; + + // Minimum number of milliseconds over which we allow a full range change + // (from rail to rail) of the VCXO control signal. This is the rate + // limiting factor which keeps us from changing the clock rate so fast that + // we get in trouble with certain HDMI sinks. + static const uint32_t kMinFullRangeSlewChange_mSec; + + // How much time (in msec) to wait + static const int kSlewChangeStepPeriod_mSec; + +#ifdef TIME_SERVICE_DEBUG + sp<DiagThread> diag_thread_; +#endif +}; + +} // namespace android + +#endif // __CLOCK_RECOVERY_H__ diff --git a/libs/common_time/common_clock.cpp b/libs/common_time/common_clock.cpp new file mode 100644 index 0000000..c9eb388 --- /dev/null +++ b/libs/common_time/common_clock.cpp @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2012 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. + */ + +#define __STDC_LIMIT_MACROS + +#define LOG_TAG "common_time" +#include <utils/Log.h> + +#include <stdint.h> + +#include <utils/Errors.h> +#include <utils/LinearTransform.h> + +#include "common_clock.h" + +namespace android { + +CommonClock::CommonClock() { + cur_slew_ = 0; + cur_trans_valid_ = false; + + cur_trans_.a_zero = 0; + cur_trans_.b_zero = 0; + cur_trans_.a_to_b_numer = local_to_common_freq_numer_ = 1; + cur_trans_.a_to_b_denom = local_to_common_freq_denom_ = 1; + duration_trans_ = cur_trans_; +} + +bool CommonClock::init(uint64_t local_freq) { + Mutex::Autolock lock(&lock_); + + if (!local_freq) + return false; + + uint64_t numer = kCommonFreq; + uint64_t denom = local_freq; + + LinearTransform::reduce(&numer, &denom); + if ((numer > UINT32_MAX) || (denom > UINT32_MAX)) { + ALOGE("Overflow in CommonClock::init while trying to reduce %lld/%lld", + kCommonFreq, local_freq); + return false; + } + + cur_trans_.a_to_b_numer = local_to_common_freq_numer_ = + static_cast<uint32_t>(numer); + cur_trans_.a_to_b_denom = local_to_common_freq_denom_ = + static_cast<uint32_t>(denom); + duration_trans_ = cur_trans_; + + return true; +} + +status_t CommonClock::localToCommon(int64_t local, int64_t *common_out) const { + Mutex::Autolock lock(&lock_); + + if (!cur_trans_valid_) + return INVALID_OPERATION; + + if (!cur_trans_.doForwardTransform(local, common_out)) + return INVALID_OPERATION; + + return OK; +} + +status_t CommonClock::commonToLocal(int64_t common, int64_t *local_out) const { + Mutex::Autolock lock(&lock_); + + if (!cur_trans_valid_) + return INVALID_OPERATION; + + if (!cur_trans_.doReverseTransform(common, local_out)) + return INVALID_OPERATION; + + return OK; +} + +int64_t CommonClock::localDurationToCommonDuration(int64_t localDur) const { + int64_t ret; + duration_trans_.doForwardTransform(localDur, &ret); + return ret; +} + +void CommonClock::setBasis(int64_t local, int64_t common) { + Mutex::Autolock lock(&lock_); + + cur_trans_.a_zero = local; + cur_trans_.b_zero = common; + cur_trans_valid_ = true; +} + +void CommonClock::resetBasis() { + Mutex::Autolock lock(&lock_); + + cur_trans_.a_zero = 0; + cur_trans_.b_zero = 0; + cur_trans_valid_ = false; +} + +status_t CommonClock::setSlew(int64_t change_time, int32_t ppm) { + Mutex::Autolock lock(&lock_); + + int64_t new_local_basis; + int64_t new_common_basis; + + if (cur_trans_valid_) { + new_local_basis = change_time; + if (!cur_trans_.doForwardTransform(change_time, &new_common_basis)) { + ALOGE("Overflow when attempting to set slew rate to %d", ppm); + return INVALID_OPERATION; + } + } else { + new_local_basis = 0; + new_common_basis = 0; + } + + cur_slew_ = ppm; + uint32_t n1 = local_to_common_freq_numer_; + uint32_t n2 = 1000000 + cur_slew_; + + uint32_t d1 = local_to_common_freq_denom_; + uint32_t d2 = 1000000; + + // n1/d1 has already been reduced, no need to do so here. + LinearTransform::reduce(&n1, &d2); + LinearTransform::reduce(&n2, &d1); + LinearTransform::reduce(&n2, &d2); + + cur_trans_.a_zero = new_local_basis; + cur_trans_.b_zero = new_common_basis; + cur_trans_.a_to_b_numer = n1 * n2; + cur_trans_.a_to_b_denom = d1 * d2; + + return OK; +} + +} // namespace android diff --git a/libs/common_time/common_clock.h b/libs/common_time/common_clock.h new file mode 100644 index 0000000..b786fdc --- /dev/null +++ b/libs/common_time/common_clock.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2012 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. + */ + +#ifndef __COMMON_CLOCK_H__ +#define __COMMON_CLOCK_H__ + +#include <stdint.h> + +#include <utils/Errors.h> +#include <utils/LinearTransform.h> +#include <utils/threads.h> + +namespace android { + +class CommonClock { + public: + CommonClock(); + + bool init(uint64_t local_freq); + + status_t localToCommon(int64_t local, int64_t *common_out) const; + status_t commonToLocal(int64_t common, int64_t *local_out) const; + int64_t localDurationToCommonDuration(int64_t localDur) const; + uint64_t getCommonFreq() const { return kCommonFreq; } + bool isValid() const { return cur_trans_valid_; } + status_t setSlew(int64_t change_time, int32_t ppm); + void setBasis(int64_t local, int64_t common); + void resetBasis(); + private: + mutable Mutex lock_; + + int32_t cur_slew_; + uint32_t local_to_common_freq_numer_; + uint32_t local_to_common_freq_denom_; + + LinearTransform duration_trans_; + LinearTransform cur_trans_; + bool cur_trans_valid_; + + static const uint64_t kCommonFreq = 1000000ull; +}; + +} // namespace android +#endif // __COMMON_CLOCK_H__ diff --git a/libs/common_time/common_clock_service.cpp b/libs/common_time/common_clock_service.cpp new file mode 100644 index 0000000..9ca6f35 --- /dev/null +++ b/libs/common_time/common_clock_service.cpp @@ -0,0 +1,157 @@ +/* + * 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. + */ + +#include <common_time/local_clock.h> +#include <utils/String8.h> + +#include "common_clock_service.h" +#include "common_clock.h" +#include "common_time_server.h" + +namespace android { + +sp<CommonClockService> CommonClockService::instantiate( + CommonTimeServer& timeServer) { + sp<CommonClockService> tcc = new CommonClockService(timeServer); + if (tcc == NULL) + return NULL; + + defaultServiceManager()->addService(ICommonClock::kServiceName, tcc); + return tcc; +} + +status_t CommonClockService::dump(int fd, const Vector<String16>& args) { + Mutex::Autolock lock(mRegistrationLock); + return mTimeServer.dumpClockInterface(fd, args, mListeners.size()); +} + +status_t CommonClockService::isCommonTimeValid(bool* valid, + uint32_t* timelineID) { + return mTimeServer.isCommonTimeValid(valid, timelineID); +} + +status_t CommonClockService::commonTimeToLocalTime(int64_t commonTime, + int64_t* localTime) { + return mTimeServer.getCommonClock().commonToLocal(commonTime, localTime); +} + +status_t CommonClockService::localTimeToCommonTime(int64_t localTime, + int64_t* commonTime) { + return mTimeServer.getCommonClock().localToCommon(localTime, commonTime); +} + +status_t CommonClockService::getCommonTime(int64_t* commonTime) { + return localTimeToCommonTime(mTimeServer.getLocalClock().getLocalTime(), commonTime); +} + +status_t CommonClockService::getCommonFreq(uint64_t* freq) { + *freq = mTimeServer.getCommonClock().getCommonFreq(); + return OK; +} + +status_t CommonClockService::getLocalTime(int64_t* localTime) { + *localTime = mTimeServer.getLocalClock().getLocalTime(); + return OK; +} + +status_t CommonClockService::getLocalFreq(uint64_t* freq) { + *freq = mTimeServer.getLocalClock().getLocalFreq(); + return OK; +} + +status_t CommonClockService::getEstimatedError(int32_t* estimate) { + *estimate = mTimeServer.getEstimatedError(); + return OK; +} + +status_t CommonClockService::getTimelineID(uint64_t* id) { + *id = mTimeServer.getTimelineID(); + return OK; +} + +status_t CommonClockService::getState(State* state) { + *state = mTimeServer.getState(); + return OK; +} + +status_t CommonClockService::getMasterAddr(struct sockaddr_storage* addr) { + return mTimeServer.getMasterAddr(addr); +} + +status_t CommonClockService::registerListener( + const sp<ICommonClockListener>& listener) { + Mutex::Autolock lock(mRegistrationLock); + + { // scoping for autolock pattern + Mutex::Autolock lock(mCallbackLock); + // check whether this is a duplicate + for (size_t i = 0; i < mListeners.size(); i++) { + if (mListeners[i]->asBinder() == listener->asBinder()) + return ALREADY_EXISTS; + } + } + + mListeners.add(listener); + mTimeServer.reevaluateAutoDisableState(0 != mListeners.size()); + return listener->asBinder()->linkToDeath(this); +} + +status_t CommonClockService::unregisterListener( + const sp<ICommonClockListener>& listener) { + Mutex::Autolock lock(mRegistrationLock); + status_t ret_val = NAME_NOT_FOUND; + + { // scoping for autolock pattern + Mutex::Autolock lock(mCallbackLock); + for (size_t i = 0; i < mListeners.size(); i++) { + if (mListeners[i]->asBinder() == listener->asBinder()) { + mListeners[i]->asBinder()->unlinkToDeath(this); + mListeners.removeAt(i); + ret_val = OK; + break; + } + } + } + + mTimeServer.reevaluateAutoDisableState(0 != mListeners.size()); + return ret_val; +} + +void CommonClockService::binderDied(const wp<IBinder>& who) { + Mutex::Autolock lock(mRegistrationLock); + + { // scoping for autolock pattern + Mutex::Autolock lock(mCallbackLock); + for (size_t i = 0; i < mListeners.size(); i++) { + if (mListeners[i]->asBinder() == who) { + mListeners.removeAt(i); + break; + } + } + } + + mTimeServer.reevaluateAutoDisableState(0 != mListeners.size()); +} + +void CommonClockService::notifyOnTimelineChanged(uint64_t timelineID) { + Mutex::Autolock lock(mCallbackLock); + + for (size_t i = 0; i < mListeners.size(); i++) { + mListeners[i]->onTimelineChanged(timelineID); + } +} + +}; // namespace android diff --git a/libs/common_time/common_clock_service.h b/libs/common_time/common_clock_service.h new file mode 100644 index 0000000..bd663f0 --- /dev/null +++ b/libs/common_time/common_clock_service.h @@ -0,0 +1,91 @@ +/* + * 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. + */ + +#ifndef ANDROID_COMMON_CLOCK_SERVICE_H +#define ANDROID_COMMON_CLOCK_SERVICE_H + +#include <sys/socket.h> +#include <common_time/ICommonClock.h> + +namespace android { + +class CommonTimeServer; + +class CommonClockService : public BnCommonClock, + public android::IBinder::DeathRecipient { + public: + static sp<CommonClockService> instantiate(CommonTimeServer& timeServer); + + virtual status_t dump(int fd, const Vector<String16>& args); + + virtual status_t isCommonTimeValid(bool* valid, uint32_t *timelineID); + virtual status_t commonTimeToLocalTime(int64_t common_time, + int64_t* local_time); + virtual status_t localTimeToCommonTime(int64_t local_time, + int64_t* common_time); + virtual status_t getCommonTime(int64_t* common_time); + virtual status_t getCommonFreq(uint64_t* freq); + virtual status_t getLocalTime(int64_t* local_time); + virtual status_t getLocalFreq(uint64_t* freq); + virtual status_t getEstimatedError(int32_t* estimate); + virtual status_t getTimelineID(uint64_t* id); + virtual status_t getState(ICommonClock::State* state); + virtual status_t getMasterAddr(struct sockaddr_storage* addr); + + virtual status_t registerListener( + const sp<ICommonClockListener>& listener); + virtual status_t unregisterListener( + const sp<ICommonClockListener>& listener); + + void notifyOnTimelineChanged(uint64_t timelineID); + + private: + CommonClockService(CommonTimeServer& timeServer) + : mTimeServer(timeServer) { }; + + virtual void binderDied(const wp<IBinder>& who); + + CommonTimeServer& mTimeServer; + + // locks used to synchronize access to the list of registered listeners. + // The callback lock is held whenever the list is used to perform callbacks + // or while the list is being modified. The registration lock used to + // serialize access across registerListener, unregisterListener, and + // binderDied. + // + // The reason for two locks is that registerListener, unregisterListener, + // and binderDied each call into the core service and obtain the core + // service thread lock when they call reevaluateAutoDisableState. The core + // service thread obtains the main thread lock whenever its thread is + // running, and sometimes needs to call notifyOnTimelineChanged which then + // obtains the callback lock. If callers of registration functions were + // holding the callback lock when they called into the core service, we + // would have a classic A/B, B/A ordering deadlock. To avoid this, the + // registration functions hold the registration lock for the duration of + // their call, but hold the callback lock only while they mutate the list. + // This way, the list's size cannot change (because of the registration + // lock) during the call into reevaluateAutoDisableState, but the core work + // thread can still safely call notifyOnTimelineChanged while holding the + // main thread lock. + Mutex mCallbackLock; + Mutex mRegistrationLock; + + Vector<sp<ICommonClockListener> > mListeners; +}; + +}; // namespace android + +#endif // ANDROID_COMMON_CLOCK_SERVICE_H diff --git a/libs/common_time/common_time_config_service.cpp b/libs/common_time/common_time_config_service.cpp new file mode 100644 index 0000000..9585618 --- /dev/null +++ b/libs/common_time/common_time_config_service.cpp @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2012 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. + */ + +#include <utils/String8.h> + +#include "common_time_config_service.h" +#include "common_time_server.h" + +namespace android { + +sp<CommonTimeConfigService> CommonTimeConfigService::instantiate( + CommonTimeServer& timeServer) { + sp<CommonTimeConfigService> ctcs = new CommonTimeConfigService(timeServer); + if (ctcs == NULL) + return NULL; + + defaultServiceManager()->addService(ICommonTimeConfig::kServiceName, ctcs); + return ctcs; +} + +status_t CommonTimeConfigService::dump(int fd, const Vector<String16>& args) { + return mTimeServer.dumpConfigInterface(fd, args); +} + +status_t CommonTimeConfigService::getMasterElectionPriority(uint8_t *priority) { + return mTimeServer.getMasterElectionPriority(priority); +} + +status_t CommonTimeConfigService::setMasterElectionPriority(uint8_t priority) { + return mTimeServer.setMasterElectionPriority(priority); +} + +status_t CommonTimeConfigService::getMasterElectionEndpoint( + struct sockaddr_storage *addr) { + return mTimeServer.getMasterElectionEndpoint(addr); +} + +status_t CommonTimeConfigService::setMasterElectionEndpoint( + const struct sockaddr_storage *addr) { + return mTimeServer.setMasterElectionEndpoint(addr); +} + +status_t CommonTimeConfigService::getMasterElectionGroupId(uint64_t *id) { + return mTimeServer.getMasterElectionGroupId(id); +} + +status_t CommonTimeConfigService::setMasterElectionGroupId(uint64_t id) { + return mTimeServer.setMasterElectionGroupId(id); +} + +status_t CommonTimeConfigService::getInterfaceBinding(String16& ifaceName) { + String8 tmp; + status_t ret = mTimeServer.getInterfaceBinding(tmp); + ifaceName = String16(tmp); + return ret; +} + +status_t CommonTimeConfigService::setInterfaceBinding(const String16& ifaceName) { + String8 tmp(ifaceName); + return mTimeServer.setInterfaceBinding(tmp); +} + +status_t CommonTimeConfigService::getMasterAnnounceInterval(int *interval) { + return mTimeServer.getMasterAnnounceInterval(interval); +} + +status_t CommonTimeConfigService::setMasterAnnounceInterval(int interval) { + return mTimeServer.setMasterAnnounceInterval(interval); +} + +status_t CommonTimeConfigService::getClientSyncInterval(int *interval) { + return mTimeServer.getClientSyncInterval(interval); +} + +status_t CommonTimeConfigService::setClientSyncInterval(int interval) { + return mTimeServer.setClientSyncInterval(interval); +} + +status_t CommonTimeConfigService::getPanicThreshold(int *threshold) { + return mTimeServer.getPanicThreshold(threshold); +} + +status_t CommonTimeConfigService::setPanicThreshold(int threshold) { + return mTimeServer.setPanicThreshold(threshold); +} + +status_t CommonTimeConfigService::getAutoDisable(bool *autoDisable) { + return mTimeServer.getAutoDisable(autoDisable); +} + +status_t CommonTimeConfigService::setAutoDisable(bool autoDisable) { + return mTimeServer.setAutoDisable(autoDisable); +} + +status_t CommonTimeConfigService::forceNetworklessMasterMode() { + return mTimeServer.forceNetworklessMasterMode(); +} + +}; // namespace android diff --git a/libs/common_time/common_time_config_service.h b/libs/common_time/common_time_config_service.h new file mode 100644 index 0000000..89806dd --- /dev/null +++ b/libs/common_time/common_time_config_service.h @@ -0,0 +1,60 @@ +/* * Copyright (C) 2012 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. + */ + +#ifndef ANDROID_COMMON_TIME_CONFIG_SERVICE_H +#define ANDROID_COMMON_TIME_CONFIG_SERVICE_H + +#include <sys/socket.h> +#include <common_time/ICommonTimeConfig.h> + +namespace android { + +class String16; +class CommonTimeServer; + +class CommonTimeConfigService : public BnCommonTimeConfig { + public: + static sp<CommonTimeConfigService> instantiate(CommonTimeServer& timeServer); + + virtual status_t dump(int fd, const Vector<String16>& args); + + virtual status_t getMasterElectionPriority(uint8_t *priority); + virtual status_t setMasterElectionPriority(uint8_t priority); + virtual status_t getMasterElectionEndpoint(struct sockaddr_storage *addr); + virtual status_t setMasterElectionEndpoint(const struct sockaddr_storage *addr); + virtual status_t getMasterElectionGroupId(uint64_t *id); + virtual status_t setMasterElectionGroupId(uint64_t id); + virtual status_t getInterfaceBinding(String16& ifaceName); + virtual status_t setInterfaceBinding(const String16& ifaceName); + virtual status_t getMasterAnnounceInterval(int *interval); + virtual status_t setMasterAnnounceInterval(int interval); + virtual status_t getClientSyncInterval(int *interval); + virtual status_t setClientSyncInterval(int interval); + virtual status_t getPanicThreshold(int *threshold); + virtual status_t setPanicThreshold(int threshold); + virtual status_t getAutoDisable(bool *autoDisable); + virtual status_t setAutoDisable(bool autoDisable); + virtual status_t forceNetworklessMasterMode(); + + private: + CommonTimeConfigService(CommonTimeServer& timeServer) + : mTimeServer(timeServer) { } + CommonTimeServer& mTimeServer; + +}; + +}; // namespace android + +#endif // ANDROID_COMMON_TIME_CONFIG_SERVICE_H diff --git a/libs/common_time/common_time_server.cpp b/libs/common_time/common_time_server.cpp new file mode 100644 index 0000000..21e706f --- /dev/null +++ b/libs/common_time/common_time_server.cpp @@ -0,0 +1,1506 @@ +/* + * Copyright (C) 2012 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 LOG_TAG "common_time" +#include <utils/Log.h> + +#include <arpa/inet.h> +#include <assert.h> +#include <fcntl.h> +#include <linux/if_ether.h> +#include <net/if.h> +#include <net/if_arp.h> +#include <netinet/ip.h> +#include <poll.h> +#include <stdio.h> +#include <sys/eventfd.h> +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/socket.h> + +#include <common_time/local_clock.h> +#include <binder/IPCThreadState.h> +#include <binder/ProcessState.h> +#include <utils/Timers.h> + +#include "common_clock_service.h" +#include "common_time_config_service.h" +#include "common_time_server.h" +#include "common_time_server_packets.h" +#include "clock_recovery.h" +#include "common_clock.h" + +#define MAX_INT ((int)0x7FFFFFFF) + +namespace android { + +const char* CommonTimeServer::kDefaultMasterElectionAddr = "255.255.255.255"; +const uint16_t CommonTimeServer::kDefaultMasterElectionPort = 8886; +const uint64_t CommonTimeServer::kDefaultSyncGroupID = 1; +const uint8_t CommonTimeServer::kDefaultMasterPriority = 1; +const uint32_t CommonTimeServer::kDefaultMasterAnnounceIntervalMs = 10000; +const uint32_t CommonTimeServer::kDefaultSyncRequestIntervalMs = 1000; +const uint32_t CommonTimeServer::kDefaultPanicThresholdUsec = 50000; +const bool CommonTimeServer::kDefaultAutoDisable = true; +const int CommonTimeServer::kSetupRetryTimeoutMs = 30000; +const int64_t CommonTimeServer::kNoGoodDataPanicThresholdUsec = 600000000ll; +const uint32_t CommonTimeServer::kRTTDiscardPanicThreshMultiplier = 5; + +// timeout value representing an infinite timeout +const int CommonTimeServer::kInfiniteTimeout = -1; + +/*** Initial state constants ***/ + +// number of WhoIsMaster attempts sent before giving up +const int CommonTimeServer::kInitial_NumWhoIsMasterRetries = 6; + +// timeout used when waiting for a response to a WhoIsMaster request +const int CommonTimeServer::kInitial_WhoIsMasterTimeoutMs = 500; + +/*** Client state constants ***/ + +// number of sync requests that can fail before a client assumes its master +// is dead +const int CommonTimeServer::kClient_NumSyncRequestRetries = 10; + +/*** Master state constants ***/ + +/*** Ronin state constants ***/ + +// number of WhoIsMaster attempts sent before declaring ourselves master +const int CommonTimeServer::kRonin_NumWhoIsMasterRetries = 20; + +// timeout used when waiting for a response to a WhoIsMaster request +const int CommonTimeServer::kRonin_WhoIsMasterTimeoutMs = 500; + +/*** WaitForElection state constants ***/ + +// how long do we wait for an announcement from a master before +// trying another election? +const int CommonTimeServer::kWaitForElection_TimeoutMs = 12500; + +CommonTimeServer::CommonTimeServer() + : Thread(false) + , mState(ICommonClock::STATE_INITIAL) + , mClockRecovery(&mLocalClock, &mCommonClock) + , mSocket(-1) + , mLastPacketRxLocalTime(0) + , mTimelineID(ICommonClock::kInvalidTimelineID) + , mClockSynced(false) + , mCommonClockHasClients(false) + , mStateChangeLog("Recent State Change Events", 30) + , mElectionLog("Recent Master Election Traffic", 30) + , mBadPktLog("Recent Bad Packet RX Info", 8) + , mInitial_WhoIsMasterRequestTimeouts(0) + , mClient_MasterDeviceID(0) + , mClient_MasterDevicePriority(0) + , mRonin_WhoIsMasterRequestTimeouts(0) { + // zero out sync stats + resetSyncStats(); + + // Setup the master election endpoint to use the default. + struct sockaddr_in* meep = + reinterpret_cast<struct sockaddr_in*>(&mMasterElectionEP); + memset(&mMasterElectionEP, 0, sizeof(mMasterElectionEP)); + inet_aton(kDefaultMasterElectionAddr, &meep->sin_addr); + meep->sin_family = AF_INET; + meep->sin_port = htons(kDefaultMasterElectionPort); + + // Zero out the master endpoint. + memset(&mMasterEP, 0, sizeof(mMasterEP)); + mMasterEPValid = false; + mBindIfaceValid = false; + setForceLowPriority(false); + + // Set all remaining configuration parameters to their defaults. + mDeviceID = 0; + mSyncGroupID = kDefaultSyncGroupID; + mMasterPriority = kDefaultMasterPriority; + mMasterAnnounceIntervalMs = kDefaultMasterAnnounceIntervalMs; + mSyncRequestIntervalMs = kDefaultSyncRequestIntervalMs; + mPanicThresholdUsec = kDefaultPanicThresholdUsec; + mAutoDisable = kDefaultAutoDisable; + + // Create the eventfd we will use to signal our thread to wake up when + // needed. + mWakeupThreadFD = eventfd(0, EFD_NONBLOCK); + + // seed the random number generator (used to generated timeline IDs) + srand48(static_cast<unsigned int>(systemTime())); +} + +CommonTimeServer::~CommonTimeServer() { + shutdownThread(); + + // No need to grab the lock here. We are in the destructor; if the the user + // has a thread in any of the APIs while the destructor is being called, + // there is a threading problem a the application level we cannot reasonably + // do anything about. + cleanupSocket_l(); + + if (mWakeupThreadFD >= 0) { + close(mWakeupThreadFD); + mWakeupThreadFD = -1; + } +} + +bool CommonTimeServer::startServices() { + // start the ICommonClock service + mICommonClock = CommonClockService::instantiate(*this); + if (mICommonClock == NULL) + return false; + + // start the ICommonTimeConfig service + mICommonTimeConfig = CommonTimeConfigService::instantiate(*this); + if (mICommonTimeConfig == NULL) + return false; + + return true; +} + +bool CommonTimeServer::threadLoop() { + // Register our service interfaces. + if (!startServices()) + return false; + + // Hold the lock while we are in the main thread loop. It will release the + // lock when it blocks, and hold the lock at all other times. + mLock.lock(); + runStateMachine_l(); + mLock.unlock(); + + IPCThreadState::self()->stopProcess(); + return false; +} + +bool CommonTimeServer::runStateMachine_l() { + if (!mLocalClock.initCheck()) + return false; + + if (!mCommonClock.init(mLocalClock.getLocalFreq())) + return false; + + // Enter the initial state. + becomeInitial("startup"); + + // run the state machine + while (!exitPending()) { + struct pollfd pfds[2]; + int rc, timeout; + int eventCnt = 0; + int64_t wakeupTime; + uint32_t t1, t2; + bool needHandleTimeout = false; + + // We are always interested in our wakeup FD. + pfds[eventCnt].fd = mWakeupThreadFD; + pfds[eventCnt].events = POLLIN; + pfds[eventCnt].revents = 0; + eventCnt++; + + // If we have a valid socket, then we are interested in what it has to + // say as well. + if (mSocket >= 0) { + pfds[eventCnt].fd = mSocket; + pfds[eventCnt].events = POLLIN; + pfds[eventCnt].revents = 0; + eventCnt++; + } + + t1 = static_cast<uint32_t>(mCurTimeout.msecTillTimeout()); + t2 = static_cast<uint32_t>(mClockRecovery.applyRateLimitedSlew()); + timeout = static_cast<int>(t1 < t2 ? t1 : t2); + + // Note, we were holding mLock when this function was called. We + // release it only while we are blocking and hold it at all other times. + mLock.unlock(); + rc = poll(pfds, eventCnt, timeout); + wakeupTime = mLocalClock.getLocalTime(); + mLock.lock(); + + // Is it time to shutdown? If so, don't hesitate... just do it. + if (exitPending()) + break; + + // Did the poll fail? This should never happen and is fatal if it does. + if (rc < 0) { + ALOGE("%s:%d poll failed", __PRETTY_FUNCTION__, __LINE__); + return false; + } + + if (rc == 0) { + needHandleTimeout = !mCurTimeout.msecTillTimeout(); + if (needHandleTimeout) + mCurTimeout.setTimeout(kInfiniteTimeout); + } + + // Were we woken up on purpose? If so, clear the eventfd with a read. + if (pfds[0].revents) + clearPendingWakeupEvents_l(); + + // Is out bind address dirty? If so, clean up our socket (if any). + // Alternatively, do we have an active socket but should be auto + // disabled? If so, release the socket and enter the proper sync state. + bool droppedSocket = false; + if (mBindIfaceDirty || ((mSocket >= 0) && shouldAutoDisable())) { + cleanupSocket_l(); + mBindIfaceDirty = false; + droppedSocket = true; + } + + // Do we not have a socket but should have one? If so, try to set one + // up. + if ((mSocket < 0) && mBindIfaceValid && !shouldAutoDisable()) { + if (setupSocket_l()) { + // Success! We are now joining a new network (either coming + // from no network, or coming from a potentially different + // network). Force our priority to be lower so that we defer to + // any other masters which may already be on the network we are + // joining. Later, when we enter either the client or the + // master state, we will clear this flag and go back to our + // normal election priority. + setForceLowPriority(true); + switch (mState) { + // If we were in initial (whether we had a immediately + // before this network or not) we want to simply reset the + // system and start again. Forcing a transition from + // INITIAL to INITIAL should do the job. + case CommonClockService::STATE_INITIAL: + becomeInitial("bound interface"); + break; + + // If we were in the master state, then either we were the + // master in a no-network situation, or we were the master + // of a different network and have moved to a new interface. + // In either case, immediately transition to Ronin at low + // priority. If there is no one in the network we just + // joined, we will become master soon enough. If there is, + // we want to be certain to defer master status to the + // existing timeline currently running on the network. + // + case CommonClockService::STATE_MASTER: + becomeRonin("leaving networkless mode"); + break; + + // If we were in any other state (CLIENT, RONIN, or + // WAIT_FOR_ELECTION) then we must be moving from one + // network to another. We have lost our old master; + // transition to RONIN in an attempt to find a new master. + // If there are none out there, we will just assume + // responsibility for the timeline we used to be a client + // of. + default: + becomeRonin("bound interface"); + break; + } + } else { + // That's odd... we failed to set up our socket. This could be + // due to some transient network change which will work itself + // out shortly; schedule a retry attempt in the near future. + mCurTimeout.setTimeout(kSetupRetryTimeoutMs); + } + + // One way or the other, we don't have any data to process at this + // point (since we just tried to bulid a new socket). Loop back + // around and wait for the next thing to do. + continue; + } else if (droppedSocket) { + // We just lost our socket, and for whatever reason (either no + // config, or auto disable engaged) we are not supposed to rebuild + // one at this time. We are not going to rebuild our socket until + // something about our config/auto-disabled status changes, so we + // are basically in network-less mode. If we are already in either + // INITIAL or MASTER, just stay there until something changes. If + // we are in any other state (CLIENT, RONIN or WAIT_FOR_ELECTION), + // then transition to either INITIAL or MASTER depending on whether + // or not our timeline is valid. + mStateChangeLog.log(ANDROID_LOG_INFO, LOG_TAG, + "Entering networkless mode interface is %s, " + "shouldAutoDisable = %s", + mBindIfaceValid ? "valid" : "invalid", + shouldAutoDisable() ? "true" : "false"); + if ((mState != ICommonClock::STATE_INITIAL) && + (mState != ICommonClock::STATE_MASTER)) { + if (mTimelineID == ICommonClock::kInvalidTimelineID) + becomeInitial("network-less mode"); + else + becomeMaster("network-less mode"); + } + + continue; + } + + // Time to handle the timeouts? + if (needHandleTimeout) { + if (!handleTimeout()) + ALOGE("handleTimeout failed"); + continue; + } + + // Does our socket have data for us (assuming we still have one, we + // may have RXed a packet at the same time as a config change telling us + // to shut our socket down)? If so, process its data. + if ((mSocket >= 0) && (eventCnt > 1) && (pfds[1].revents)) { + mLastPacketRxLocalTime = wakeupTime; + if (!handlePacket()) + ALOGE("handlePacket failed"); + } + } + + cleanupSocket_l(); + return true; +} + +void CommonTimeServer::clearPendingWakeupEvents_l() { + int64_t tmp; + read(mWakeupThreadFD, &tmp, sizeof(tmp)); +} + +void CommonTimeServer::wakeupThread_l() { + int64_t tmp = 1; + write(mWakeupThreadFD, &tmp, sizeof(tmp)); +} + +void CommonTimeServer::cleanupSocket_l() { + if (mSocket >= 0) { + close(mSocket); + mSocket = -1; + } +} + +void CommonTimeServer::shutdownThread() { + // Flag the work thread for shutdown. + this->requestExit(); + + // Signal the thread in case its sleeping. + mLock.lock(); + wakeupThread_l(); + mLock.unlock(); + + // Wait for the thread to exit. + this->join(); +} + +bool CommonTimeServer::setupSocket_l() { + int rc; + bool ret_val = false; + struct sockaddr_in* ipv4_addr = NULL; + char masterElectionEPStr[64]; + const int one = 1; + + // This should never be needed, but if we happened to have an old socket + // lying around, be sure to not leak it before proceeding. + cleanupSocket_l(); + + // If we don't have a valid endpoint to bind to, then how did we get here in + // the first place? Regardless, we know that we are going to fail to bind, + // so don't even try. + if (!mBindIfaceValid) + return false; + + sockaddrToString(mMasterElectionEP, true, masterElectionEPStr, + sizeof(masterElectionEPStr)); + mStateChangeLog.log(ANDROID_LOG_INFO, LOG_TAG, + "Building socket :: bind = %s master election = %s", + mBindIface.string(), masterElectionEPStr); + + // TODO: add proper support for IPv6. Right now, we block IPv6 addresses at + // the configuration interface level. + if (AF_INET != mMasterElectionEP.ss_family) { + mStateChangeLog.log(ANDROID_LOG_WARN, LOG_TAG, + "TODO: add proper IPv6 support"); + goto bailout; + } + + // open a UDP socket for the timeline serivce + mSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (mSocket < 0) { + mStateChangeLog.log(ANDROID_LOG_ERROR, LOG_TAG, + "Failed to create socket (errno = %d)", errno); + goto bailout; + } + + // Bind to the selected interface using Linux's spiffy SO_BINDTODEVICE. + struct ifreq ifr; + memset(&ifr, 0, sizeof(ifr)); + snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "%s", mBindIface.string()); + ifr.ifr_name[sizeof(ifr.ifr_name) - 1] = 0; + rc = setsockopt(mSocket, SOL_SOCKET, SO_BINDTODEVICE, + (void *)&ifr, sizeof(ifr)); + if (rc) { + mStateChangeLog.log(ANDROID_LOG_ERROR, LOG_TAG, + "Failed to bind socket at to interface %s " + "(errno = %d)", ifr.ifr_name, errno); + goto bailout; + } + + // Bind our socket to INADDR_ANY and the master election port. The + // interface binding we made using SO_BINDTODEVICE should limit us to + // traffic only on the interface we are interested in. We need to bind to + // INADDR_ANY and the specific master election port in order to be able to + // receive both unicast traffic and master election multicast traffic with + // just a single socket. + struct sockaddr_in bindAddr; + ipv4_addr = reinterpret_cast<struct sockaddr_in*>(&mMasterElectionEP); + memcpy(&bindAddr, ipv4_addr, sizeof(bindAddr)); + bindAddr.sin_addr.s_addr = INADDR_ANY; + rc = bind(mSocket, + reinterpret_cast<const sockaddr *>(&bindAddr), + sizeof(bindAddr)); + if (rc) { + mStateChangeLog.log(ANDROID_LOG_ERROR, LOG_TAG, + "Failed to bind socket to port %hu (errno = %d)", + ntohs(bindAddr.sin_port), errno); + goto bailout; + } + + if (0xE0000000 == (ntohl(ipv4_addr->sin_addr.s_addr) & 0xF0000000)) { + // If our master election endpoint is a multicast address, be sure to join + // the multicast group. + struct ip_mreq mreq; + mreq.imr_multiaddr = ipv4_addr->sin_addr; + mreq.imr_interface.s_addr = htonl(INADDR_ANY); + rc = setsockopt(mSocket, IPPROTO_IP, IP_ADD_MEMBERSHIP, + &mreq, sizeof(mreq)); + if (rc == -1) { + ALOGE("Failed to join multicast group at %s. (errno = %d)", + masterElectionEPStr, errno); + goto bailout; + } + + // disable loopback of multicast packets + const int zero = 0; + rc = setsockopt(mSocket, IPPROTO_IP, IP_MULTICAST_LOOP, + &zero, sizeof(zero)); + if (rc == -1) { + mStateChangeLog.log(ANDROID_LOG_ERROR, LOG_TAG, + "Failed to disable multicast loopback " + "(errno = %d)", errno); + goto bailout; + } + } else + if (ntohl(ipv4_addr->sin_addr.s_addr) == 0xFFFFFFFF) { + // If the master election address is the broadcast address, then enable + // the broadcast socket option + rc = setsockopt(mSocket, SOL_SOCKET, SO_BROADCAST, &one, sizeof(one)); + if (rc == -1) { + mStateChangeLog.log(ANDROID_LOG_ERROR, LOG_TAG, + "Failed to enable broadcast (errno = %d)", + errno); + goto bailout; + } + } else { + // If the master election address is neither broadcast, nor multicast, + // then we are misconfigured. The config API layer should prevent this + // from ever happening. + goto bailout; + } + + // Set the TTL of sent packets to 1. (Time protocol sync should never leave + // the local subnet) + rc = setsockopt(mSocket, IPPROTO_IP, IP_TTL, &one, sizeof(one)); + if (rc == -1) { + mStateChangeLog.log(ANDROID_LOG_ERROR, LOG_TAG, + "Failed to set TTL to %d (errno = %d)", one, errno); + goto bailout; + } + + // get the device's unique ID + if (!assignDeviceID()) + goto bailout; + + ret_val = true; + +bailout: + if (!ret_val) + cleanupSocket_l(); + return ret_val; +} + +// generate a unique device ID that can be used for arbitration +bool CommonTimeServer::assignDeviceID() { + if (!mBindIfaceValid) + return false; + + struct ifreq ifr; + memset(&ifr, 0, sizeof(ifr)); + ifr.ifr_addr.sa_family = AF_INET; + strlcpy(ifr.ifr_name, mBindIface.string(), IFNAMSIZ); + + int rc = ioctl(mSocket, SIOCGIFHWADDR, &ifr); + if (rc) { + ALOGE("%s:%d ioctl failed", __PRETTY_FUNCTION__, __LINE__); + return false; + } + + if (ifr.ifr_addr.sa_family != ARPHRD_ETHER) { + ALOGE("%s:%d got non-Ethernet address", __PRETTY_FUNCTION__, __LINE__); + return false; + } + + mDeviceID = 0; + for (int i = 0; i < ETH_ALEN; i++) { + mDeviceID = (mDeviceID << 8) | ifr.ifr_hwaddr.sa_data[i]; + } + + return true; +} + +// generate a new timeline ID +void CommonTimeServer::assignTimelineID() { + do { + mTimelineID = (static_cast<uint64_t>(lrand48()) << 32) + | static_cast<uint64_t>(lrand48()); + } while (mTimelineID == ICommonClock::kInvalidTimelineID); +} + +// Select a preference between the device IDs of two potential masters. +// Returns true if the first ID wins, or false if the second ID wins. +bool CommonTimeServer::arbitrateMaster( + uint64_t deviceID1, uint8_t devicePrio1, + uint64_t deviceID2, uint8_t devicePrio2) { + return ((devicePrio1 > devicePrio2) || + ((devicePrio1 == devicePrio2) && (deviceID1 > deviceID2))); +} + +static void hexDumpToString(const uint8_t* src, size_t src_len, + char* dst, size_t dst_len) { + size_t offset = 0; + size_t i; + + for (i = 0; (i < src_len) && (offset < dst_len); ++i) { + int res; + if (0 == (i % 16)) { + res = snprintf(dst + offset, dst_len - offset, "\n%04x :", i); + if (res < 0) + break; + offset += res; + if (offset >= dst_len) + break; + } + + res = snprintf(dst + offset, dst_len - offset, " %02x", src[i]); + if (res < 0) + break; + offset += res; + } + + dst[dst_len - 1] = 0; +} + +bool CommonTimeServer::handlePacket() { + uint8_t buf[256]; + struct sockaddr_storage srcAddr; + socklen_t srcAddrLen = sizeof(srcAddr); + + ssize_t recvBytes = recvfrom( + mSocket, buf, sizeof(buf), 0, + reinterpret_cast<const sockaddr *>(&srcAddr), &srcAddrLen); + + if (recvBytes < 0) { + mBadPktLog.log(ANDROID_LOG_ERROR, LOG_TAG, + "recvfrom failed (res %d, errno %d)", + recvBytes, errno); + return false; + } + + UniversalTimeServicePacket pkt; + if (pkt.deserializePacket(buf, recvBytes, mSyncGroupID) < 0) { + char hex[256]; + char srcEPStr[64]; + + hexDumpToString(buf, static_cast<size_t>(recvBytes), hex, sizeof(hex)); + sockaddrToString(srcAddr, true, srcEPStr, sizeof(srcEPStr)); + + mBadPktLog.log("Failed to parse %d byte packet from %s.%s", + recvBytes, srcEPStr, hex); + return false; + } + + bool result; + switch (pkt.packetType) { + case TIME_PACKET_WHO_IS_MASTER_REQUEST: + result = handleWhoIsMasterRequest(&pkt.p.who_is_master_request, + srcAddr); + break; + + case TIME_PACKET_WHO_IS_MASTER_RESPONSE: + result = handleWhoIsMasterResponse(&pkt.p.who_is_master_response, + srcAddr); + break; + + case TIME_PACKET_SYNC_REQUEST: + result = handleSyncRequest(&pkt.p.sync_request, srcAddr); + break; + + case TIME_PACKET_SYNC_RESPONSE: + result = handleSyncResponse(&pkt.p.sync_response, srcAddr); + break; + + case TIME_PACKET_MASTER_ANNOUNCEMENT: + result = handleMasterAnnouncement(&pkt.p.master_announcement, + srcAddr); + break; + + default: { + char srcEPStr[64]; + sockaddrToString(srcAddr, true, srcEPStr, sizeof(srcEPStr)); + + mBadPktLog.log(ANDROID_LOG_WARN, LOG_TAG, + "unknown packet type (%d) from %s", + pkt.packetType, srcEPStr); + + result = false; + } break; + } + + return result; +} + +bool CommonTimeServer::handleTimeout() { + // If we have no socket, then this must be a timeout to retry socket setup. + if (mSocket < 0) + return true; + + switch (mState) { + case ICommonClock::STATE_INITIAL: + return handleTimeoutInitial(); + case ICommonClock::STATE_CLIENT: + return handleTimeoutClient(); + case ICommonClock::STATE_MASTER: + return handleTimeoutMaster(); + case ICommonClock::STATE_RONIN: + return handleTimeoutRonin(); + case ICommonClock::STATE_WAIT_FOR_ELECTION: + return handleTimeoutWaitForElection(); + } + + return false; +} + +bool CommonTimeServer::handleTimeoutInitial() { + if (++mInitial_WhoIsMasterRequestTimeouts == + kInitial_NumWhoIsMasterRetries) { + // none of our attempts to discover a master succeeded, so make + // this device the master + return becomeMaster("initial timeout"); + } else { + // retry the WhoIsMaster request + return sendWhoIsMasterRequest(); + } +} + +bool CommonTimeServer::handleTimeoutClient() { + if (shouldPanicNotGettingGoodData()) + return becomeInitial("timeout panic, no good data"); + + if (mClient_SyncRequestPending) { + mClient_SyncRequestPending = false; + + if (++mClient_SyncRequestTimeouts < kClient_NumSyncRequestRetries) { + // a sync request has timed out, so retry + return sendSyncRequest(); + } else { + // The master has failed to respond to a sync request for too many + // times in a row. Assume the master is dead and start electing + // a new master. + return becomeRonin("master not responding"); + } + } else { + // initiate the next sync request + return sendSyncRequest(); + } +} + +bool CommonTimeServer::handleTimeoutMaster() { + // send another announcement from the master + return sendMasterAnnouncement(); +} + +bool CommonTimeServer::handleTimeoutRonin() { + if (++mRonin_WhoIsMasterRequestTimeouts == kRonin_NumWhoIsMasterRetries) { + // no other master is out there, so we won the election + return becomeMaster("no better masters detected"); + } else { + return sendWhoIsMasterRequest(); + } +} + +bool CommonTimeServer::handleTimeoutWaitForElection() { + return becomeRonin("timeout waiting for election conclusion"); +} + +bool CommonTimeServer::handleWhoIsMasterRequest( + const WhoIsMasterRequestPacket* request, + const sockaddr_storage& srcAddr) { + // Skip our own messages which come back via broadcast loopback. + if (request->senderDeviceID == mDeviceID) + return true; + + char srcEPStr[64]; + sockaddrToString(srcAddr, true, srcEPStr, sizeof(srcEPStr)); + mElectionLog.log("RXed WhoIs master request while in state %s. " + "src %s reqTID %016llx ourTID %016llx", + stateToString(mState), srcEPStr, + request->timelineID, mTimelineID); + + if (mState == ICommonClock::STATE_MASTER) { + // is this request related to this master's timeline? + if (request->timelineID != ICommonClock::kInvalidTimelineID && + request->timelineID != mTimelineID) + return true; + + WhoIsMasterResponsePacket pkt; + pkt.initHeader(mTimelineID, mSyncGroupID); + pkt.deviceID = mDeviceID; + pkt.devicePriority = effectivePriority(); + + mElectionLog.log("TXing WhoIs master resp to %s while in state %s. " + "ourTID %016llx ourGID %016llx ourDID %016llx " + "ourPrio %u", + srcEPStr, stateToString(mState), + mTimelineID, mSyncGroupID, + pkt.deviceID, pkt.devicePriority); + + uint8_t buf[256]; + ssize_t bufSz = pkt.serializePacket(buf, sizeof(buf)); + if (bufSz < 0) + return false; + + ssize_t sendBytes = sendto( + mSocket, buf, bufSz, 0, + reinterpret_cast<const sockaddr *>(&srcAddr), + sizeof(srcAddr)); + if (sendBytes == -1) { + ALOGE("%s:%d sendto failed", __PRETTY_FUNCTION__, __LINE__); + return false; + } + } else if (mState == ICommonClock::STATE_RONIN) { + // if we hear a WhoIsMaster request from another device following + // the same timeline and that device wins arbitration, then we will stop + // trying to elect ourselves master and will instead wait for an + // announcement from the election winner + if (request->timelineID != mTimelineID) + return true; + + if (arbitrateMaster(request->senderDeviceID, + request->senderDevicePriority, + mDeviceID, + effectivePriority())) + return becomeWaitForElection("would lose election"); + + return true; + } else if (mState == ICommonClock::STATE_INITIAL) { + // If a group of devices booted simultaneously (e.g. after a power + // outage) and all of them are in the initial state and there is no + // master, then each device may time out and declare itself master at + // the same time. To avoid this, listen for + // WhoIsMaster(InvalidTimeline) requests from peers. If we would lose + // arbitration against that peer, reset our timeout count so that the + // peer has a chance to become master before we time out. + if (request->timelineID == ICommonClock::kInvalidTimelineID && + arbitrateMaster(request->senderDeviceID, + request->senderDevicePriority, + mDeviceID, + effectivePriority())) { + mInitial_WhoIsMasterRequestTimeouts = 0; + } + } + + return true; +} + +bool CommonTimeServer::handleWhoIsMasterResponse( + const WhoIsMasterResponsePacket* response, + const sockaddr_storage& srcAddr) { + // Skip our own messages which come back via broadcast loopback. + if (response->deviceID == mDeviceID) + return true; + + char srcEPStr[64]; + sockaddrToString(srcAddr, true, srcEPStr, sizeof(srcEPStr)); + mElectionLog.log("RXed WhoIs master response while in state %s. " + "src %s respTID %016llx respDID %016llx respPrio %u " + "ourTID %016llx", + stateToString(mState), srcEPStr, + response->timelineID, + response->deviceID, + static_cast<uint32_t>(response->devicePriority), + mTimelineID); + + if (mState == ICommonClock::STATE_INITIAL || mState == ICommonClock::STATE_RONIN) { + return becomeClient(srcAddr, + response->deviceID, + response->devicePriority, + response->timelineID, + "heard whois response"); + } else if (mState == ICommonClock::STATE_CLIENT) { + // if we get multiple responses because there are multiple devices + // who believe that they are master, then follow the master that + // wins arbitration + if (arbitrateMaster(response->deviceID, + response->devicePriority, + mClient_MasterDeviceID, + mClient_MasterDevicePriority)) { + return becomeClient(srcAddr, + response->deviceID, + response->devicePriority, + response->timelineID, + "heard whois response"); + } + } + + return true; +} + +bool CommonTimeServer::handleSyncRequest(const SyncRequestPacket* request, + const sockaddr_storage& srcAddr) { + SyncResponsePacket pkt; + pkt.initHeader(mTimelineID, mSyncGroupID); + + if ((mState == ICommonClock::STATE_MASTER) && + (mTimelineID == request->timelineID)) { + int64_t rxLocalTime = mLastPacketRxLocalTime; + int64_t rxCommonTime; + + // If we are master on an actual network and have actual clients, then + // we are no longer low priority. + setForceLowPriority(false); + + if (OK != mCommonClock.localToCommon(rxLocalTime, &rxCommonTime)) { + return false; + } + + int64_t txLocalTime = mLocalClock.getLocalTime();; + int64_t txCommonTime; + if (OK != mCommonClock.localToCommon(txLocalTime, &txCommonTime)) { + return false; + } + + pkt.nak = 0; + pkt.clientTxLocalTime = request->clientTxLocalTime; + pkt.masterRxCommonTime = rxCommonTime; + pkt.masterTxCommonTime = txCommonTime; + } else { + pkt.nak = 1; + pkt.clientTxLocalTime = 0; + pkt.masterRxCommonTime = 0; + pkt.masterTxCommonTime = 0; + } + + uint8_t buf[256]; + ssize_t bufSz = pkt.serializePacket(buf, sizeof(buf)); + if (bufSz < 0) + return false; + + ssize_t sendBytes = sendto( + mSocket, &buf, bufSz, 0, + reinterpret_cast<const sockaddr *>(&srcAddr), + sizeof(srcAddr)); + if (sendBytes == -1) { + ALOGE("%s:%d sendto failed", __PRETTY_FUNCTION__, __LINE__); + return false; + } + + return true; +} + +bool CommonTimeServer::handleSyncResponse( + const SyncResponsePacket* response, + const sockaddr_storage& srcAddr) { + if (mState != ICommonClock::STATE_CLIENT) + return true; + + assert(mMasterEPValid); + if (!sockaddrMatch(srcAddr, mMasterEP, true)) { + char srcEP[64], expectedEP[64]; + sockaddrToString(srcAddr, true, srcEP, sizeof(srcEP)); + sockaddrToString(mMasterEP, true, expectedEP, sizeof(expectedEP)); + ALOGI("Dropping sync response from unexpected address." + " Expected %s Got %s", expectedEP, srcEP); + return true; + } + + if (response->nak) { + // if our master is no longer accepting requests, then we need to find + // a new master + return becomeRonin("master NAK'ed"); + } + + mClient_SyncRequestPending = 0; + mClient_SyncRequestTimeouts = 0; + mClient_PacketRTTLog.logRX(response->clientTxLocalTime, + mLastPacketRxLocalTime); + + bool result; + if (!(mClient_SyncRespsRXedFromCurMaster++)) { + // the first request/response exchange between a client and a master + // may take unusually long due to ARP, so discard it. + result = true; + } else { + int64_t clientTxLocalTime = response->clientTxLocalTime; + int64_t clientRxLocalTime = mLastPacketRxLocalTime; + int64_t masterTxCommonTime = response->masterTxCommonTime; + int64_t masterRxCommonTime = response->masterRxCommonTime; + + int64_t rtt = (clientRxLocalTime - clientTxLocalTime); + int64_t avgLocal = (clientTxLocalTime + clientRxLocalTime) >> 1; + int64_t avgCommon = (masterTxCommonTime + masterRxCommonTime) >> 1; + + // if the RTT of the packet is significantly larger than the panic + // threshold, we should simply discard it. Its better to do nothing + // than to take cues from a packet like that. + int rttCommon = mCommonClock.localDurationToCommonDuration(rtt); + if (rttCommon > (static_cast<int64_t>(mPanicThresholdUsec) * + kRTTDiscardPanicThreshMultiplier)) { + ALOGV("Dropping sync response with RTT of %lld uSec", rttCommon); + mClient_ExpiredSyncRespsRXedFromCurMaster++; + if (shouldPanicNotGettingGoodData()) + return becomeInitial("RX panic, no good data"); + } else { + result = mClockRecovery.pushDisciplineEvent(avgLocal, avgCommon, rttCommon); + mClient_LastGoodSyncRX = clientRxLocalTime; + + if (result) { + // indicate to listeners that we've synced to the common timeline + notifyClockSync(); + } else { + ALOGE("Panic! Observed clock sync error is too high to tolerate," + " resetting state machine and starting over."); + notifyClockSyncLoss(); + return becomeInitial("panic"); + } + } + } + + mCurTimeout.setTimeout(mSyncRequestIntervalMs); + return result; +} + +bool CommonTimeServer::handleMasterAnnouncement( + const MasterAnnouncementPacket* packet, + const sockaddr_storage& srcAddr) { + uint64_t newDeviceID = packet->deviceID; + uint8_t newDevicePrio = packet->devicePriority; + uint64_t newTimelineID = packet->timelineID; + + // Skip our own messages which come back via broadcast loopback. + if (newDeviceID == mDeviceID) + return true; + + char srcEPStr[64]; + sockaddrToString(srcAddr, true, srcEPStr, sizeof(srcEPStr)); + mElectionLog.log("RXed master announcement while in state %s. " + "src %s srcDevID %lld srcPrio %u srcTID %016llx", + stateToString(mState), srcEPStr, + newDeviceID, static_cast<uint32_t>(newDevicePrio), + newTimelineID); + + if (mState == ICommonClock::STATE_INITIAL || + mState == ICommonClock::STATE_RONIN || + mState == ICommonClock::STATE_WAIT_FOR_ELECTION) { + // if we aren't currently following a master, then start following + // this new master + return becomeClient(srcAddr, + newDeviceID, + newDevicePrio, + newTimelineID, + "heard master announcement"); + } else if (mState == ICommonClock::STATE_CLIENT) { + // if the new master wins arbitration against our current master, + // then become a client of the new master + if (arbitrateMaster(newDeviceID, + newDevicePrio, + mClient_MasterDeviceID, + mClient_MasterDevicePriority)) + return becomeClient(srcAddr, + newDeviceID, + newDevicePrio, + newTimelineID, + "heard master announcement"); + } else if (mState == ICommonClock::STATE_MASTER) { + // two masters are competing - if the new one wins arbitration, then + // cease acting as master + if (arbitrateMaster(newDeviceID, newDevicePrio, + mDeviceID, effectivePriority())) + return becomeClient(srcAddr, newDeviceID, + newDevicePrio, newTimelineID, + "heard master announcement"); + } + + return true; +} + +bool CommonTimeServer::sendWhoIsMasterRequest() { + assert(mState == ICommonClock::STATE_INITIAL || mState == ICommonClock::STATE_RONIN); + + // If we have no socket, then we must be in the unconfigured initial state. + // Don't report any errors, just don't try to send the initial who-is-master + // query. Eventually, our network will either become configured, or we will + // be forced into network-less master mode by higher level code. + if (mSocket < 0) { + assert(mState == ICommonClock::STATE_INITIAL); + return true; + } + + bool ret = false; + WhoIsMasterRequestPacket pkt; + pkt.initHeader(mSyncGroupID); + pkt.senderDeviceID = mDeviceID; + pkt.senderDevicePriority = effectivePriority(); + + uint8_t buf[256]; + ssize_t bufSz = pkt.serializePacket(buf, sizeof(buf)); + if (bufSz >= 0) { + char dstEPStr[64]; + sockaddrToString(mMasterElectionEP, true, dstEPStr, sizeof(dstEPStr)); + mElectionLog.log("TXing WhoIs master request to %s while in state %s. " + "ourTID %016llx ourGID %016llx ourDID %016llx " + "ourPrio %u", + dstEPStr, stateToString(mState), + mTimelineID, mSyncGroupID, + pkt.senderDeviceID, pkt.senderDevicePriority); + + ssize_t sendBytes = sendto( + mSocket, buf, bufSz, 0, + reinterpret_cast<const sockaddr *>(&mMasterElectionEP), + sizeof(mMasterElectionEP)); + if (sendBytes < 0) + ALOGE("WhoIsMaster sendto failed (errno %d)", errno); + ret = true; + } + + if (mState == ICommonClock::STATE_INITIAL) { + mCurTimeout.setTimeout(kInitial_WhoIsMasterTimeoutMs); + } else { + mCurTimeout.setTimeout(kRonin_WhoIsMasterTimeoutMs); + } + + return ret; +} + +bool CommonTimeServer::sendSyncRequest() { + // If we are sending sync requests, then we must be in the client state and + // we must have a socket (when we have no network, we are only supposed to + // be in INITIAL or MASTER) + assert(mState == ICommonClock::STATE_CLIENT); + assert(mSocket >= 0); + + bool ret = false; + SyncRequestPacket pkt; + pkt.initHeader(mTimelineID, mSyncGroupID); + pkt.clientTxLocalTime = mLocalClock.getLocalTime(); + + if (!mClient_FirstSyncTX) + mClient_FirstSyncTX = pkt.clientTxLocalTime; + + mClient_PacketRTTLog.logTX(pkt.clientTxLocalTime); + + uint8_t buf[256]; + ssize_t bufSz = pkt.serializePacket(buf, sizeof(buf)); + if (bufSz >= 0) { + ssize_t sendBytes = sendto( + mSocket, buf, bufSz, 0, + reinterpret_cast<const sockaddr *>(&mMasterEP), + sizeof(mMasterEP)); + if (sendBytes < 0) + ALOGE("SyncRequest sendto failed (errno %d)", errno); + ret = true; + } + + mClient_SyncsSentToCurMaster++; + mCurTimeout.setTimeout(mSyncRequestIntervalMs); + mClient_SyncRequestPending = true; + + return ret; +} + +bool CommonTimeServer::sendMasterAnnouncement() { + bool ret = false; + assert(mState == ICommonClock::STATE_MASTER); + + // If we are being asked to send a master announcement, but we have no + // socket, we must be in network-less master mode. Don't bother to send the + // announcement, and don't bother to schedule a timeout. When the network + // comes up, the work thread will get poked and start the process of + // figuring out who the current master should be. + if (mSocket < 0) { + mCurTimeout.setTimeout(kInfiniteTimeout); + return true; + } + + MasterAnnouncementPacket pkt; + pkt.initHeader(mTimelineID, mSyncGroupID); + pkt.deviceID = mDeviceID; + pkt.devicePriority = effectivePriority(); + + uint8_t buf[256]; + ssize_t bufSz = pkt.serializePacket(buf, sizeof(buf)); + if (bufSz >= 0) { + char dstEPStr[64]; + sockaddrToString(mMasterElectionEP, true, dstEPStr, sizeof(dstEPStr)); + mElectionLog.log("TXing Master announcement to %s while in state %s. " + "ourTID %016llx ourGID %016llx ourDID %016llx " + "ourPrio %u", + dstEPStr, stateToString(mState), + mTimelineID, mSyncGroupID, + pkt.deviceID, pkt.devicePriority); + + ssize_t sendBytes = sendto( + mSocket, buf, bufSz, 0, + reinterpret_cast<const sockaddr *>(&mMasterElectionEP), + sizeof(mMasterElectionEP)); + if (sendBytes < 0) + ALOGE("MasterAnnouncement sendto failed (errno %d)", errno); + ret = true; + } + + mCurTimeout.setTimeout(mMasterAnnounceIntervalMs); + return ret; +} + +bool CommonTimeServer::becomeClient(const sockaddr_storage& masterEP, + uint64_t masterDeviceID, + uint8_t masterDevicePriority, + uint64_t timelineID, + const char* cause) { + char newEPStr[64], oldEPStr[64]; + sockaddrToString(masterEP, true, newEPStr, sizeof(newEPStr)); + sockaddrToString(mMasterEP, mMasterEPValid, oldEPStr, sizeof(oldEPStr)); + + mStateChangeLog.log(ANDROID_LOG_INFO, LOG_TAG, + "%s --> CLIENT (%s) :%s" + " OldMaster: %02x-%014llx::%016llx::%s" + " NewMaster: %02x-%014llx::%016llx::%s", + stateToString(mState), cause, + (mTimelineID != timelineID) ? " (new timeline)" : "", + mClient_MasterDevicePriority, mClient_MasterDeviceID, + mTimelineID, oldEPStr, + masterDevicePriority, masterDeviceID, + timelineID, newEPStr); + + if (mTimelineID != timelineID) { + // start following a new timeline + mTimelineID = timelineID; + mClockRecovery.reset(true, true); + notifyClockSyncLoss(); + } else { + // start following a new master on the existing timeline + mClockRecovery.reset(false, true); + } + + mMasterEP = masterEP; + mMasterEPValid = true; + + // If we are on a real network as a client of a real master, then we should + // no longer force low priority. If our master disappears, we should have + // the high priority bit set during the election to replace the master + // because this group was a real group and not a singleton created in + // networkless mode. + setForceLowPriority(false); + + mClient_MasterDeviceID = masterDeviceID; + mClient_MasterDevicePriority = masterDevicePriority; + resetSyncStats(); + + setState(ICommonClock::STATE_CLIENT); + + // add some jitter to when the various clients send their requests + // in order to reduce the likelihood that a group of clients overload + // the master after receiving a master announcement + usleep((lrand48() % 100) * 1000); + + return sendSyncRequest(); +} + +bool CommonTimeServer::becomeMaster(const char* cause) { + uint64_t oldTimelineID = mTimelineID; + if (mTimelineID == ICommonClock::kInvalidTimelineID) { + // this device has not been following any existing timeline, + // so it will create a new timeline and declare itself master + assert(!mCommonClock.isValid()); + + // set the common time basis + mCommonClock.setBasis(mLocalClock.getLocalTime(), 0); + + // assign an arbitrary timeline iD + assignTimelineID(); + + // notify listeners that we've created a common timeline + notifyClockSync(); + } + + mStateChangeLog.log(ANDROID_LOG_INFO, LOG_TAG, + "%s --> MASTER (%s) : %s timeline %016llx", + stateToString(mState), cause, + (oldTimelineID == mTimelineID) ? "taking ownership of" + : "creating new", + mTimelineID); + + memset(&mMasterEP, 0, sizeof(mMasterEP)); + mMasterEPValid = false; + mClient_MasterDevicePriority = effectivePriority(); + mClient_MasterDeviceID = mDeviceID; + mClockRecovery.reset(false, true); + resetSyncStats(); + + setState(ICommonClock::STATE_MASTER); + return sendMasterAnnouncement(); +} + +bool CommonTimeServer::becomeRonin(const char* cause) { + // If we were the client of a given timeline, but had never received even a + // single time sync packet, then we transition back to Initial instead of + // Ronin. If we transition to Ronin and end up becoming the new Master, we + // will be unable to service requests for other clients because we never + // actually knew what time it was. By going to initial, we ensure that + // other clients who know what time it is, but would lose master arbitration + // in the Ronin case, will step up and become the proper new master of the + // old timeline. + + char oldEPStr[64]; + sockaddrToString(mMasterEP, mMasterEPValid, oldEPStr, sizeof(oldEPStr)); + memset(&mMasterEP, 0, sizeof(mMasterEP)); + mMasterEPValid = false; + + if (mCommonClock.isValid()) { + mStateChangeLog.log(ANDROID_LOG_INFO, LOG_TAG, + "%s --> RONIN (%s) : lost track of previously valid timeline " + "%02x-%014llx::%016llx::%s (%d TXed %d RXed %d RXExpired)", + stateToString(mState), cause, + mClient_MasterDevicePriority, mClient_MasterDeviceID, + mTimelineID, oldEPStr, + mClient_SyncsSentToCurMaster, + mClient_SyncRespsRXedFromCurMaster, + mClient_ExpiredSyncRespsRXedFromCurMaster); + + mRonin_WhoIsMasterRequestTimeouts = 0; + setState(ICommonClock::STATE_RONIN); + return sendWhoIsMasterRequest(); + } else { + mStateChangeLog.log(ANDROID_LOG_INFO, LOG_TAG, + "%s --> INITIAL (%s) : never synced timeline " + "%02x-%014llx::%016llx::%s (%d TXed %d RXed %d RXExpired)", + stateToString(mState), cause, + mClient_MasterDevicePriority, mClient_MasterDeviceID, + mTimelineID, oldEPStr, + mClient_SyncsSentToCurMaster, + mClient_SyncRespsRXedFromCurMaster, + mClient_ExpiredSyncRespsRXedFromCurMaster); + + return becomeInitial("ronin, no timeline"); + } +} + +bool CommonTimeServer::becomeWaitForElection(const char* cause) { + mStateChangeLog.log(ANDROID_LOG_INFO, LOG_TAG, + "%s --> WAIT_FOR_ELECTION (%s) : dropping out of election," + " waiting %d mSec for completion.", + stateToString(mState), cause, kWaitForElection_TimeoutMs); + + setState(ICommonClock::STATE_WAIT_FOR_ELECTION); + mCurTimeout.setTimeout(kWaitForElection_TimeoutMs); + return true; +} + +bool CommonTimeServer::becomeInitial(const char* cause) { + mStateChangeLog.log(ANDROID_LOG_INFO, LOG_TAG, + "Entering INITIAL (%s), total reset.", + cause); + + setState(ICommonClock::STATE_INITIAL); + + // reset clock recovery + mClockRecovery.reset(true, true); + + // reset internal state bookkeeping. + mCurTimeout.setTimeout(kInfiniteTimeout); + memset(&mMasterEP, 0, sizeof(mMasterEP)); + mMasterEPValid = false; + mLastPacketRxLocalTime = 0; + mTimelineID = ICommonClock::kInvalidTimelineID; + mClockSynced = false; + mInitial_WhoIsMasterRequestTimeouts = 0; + mClient_MasterDeviceID = 0; + mClient_MasterDevicePriority = 0; + mRonin_WhoIsMasterRequestTimeouts = 0; + resetSyncStats(); + + // send the first request to discover the master + return sendWhoIsMasterRequest(); +} + +void CommonTimeServer::notifyClockSync() { + if (!mClockSynced) { + mClockSynced = true; + mICommonClock->notifyOnTimelineChanged(mTimelineID); + } +} + +void CommonTimeServer::notifyClockSyncLoss() { + if (mClockSynced) { + mClockSynced = false; + mICommonClock->notifyOnTimelineChanged( + ICommonClock::kInvalidTimelineID); + } +} + +void CommonTimeServer::setState(ICommonClock::State s) { + mState = s; +} + +const char* CommonTimeServer::stateToString(ICommonClock::State s) { + switch(s) { + case ICommonClock::STATE_INITIAL: + return "INITIAL"; + case ICommonClock::STATE_CLIENT: + return "CLIENT"; + case ICommonClock::STATE_MASTER: + return "MASTER"; + case ICommonClock::STATE_RONIN: + return "RONIN"; + case ICommonClock::STATE_WAIT_FOR_ELECTION: + return "WAIT_FOR_ELECTION"; + default: + return "unknown"; + } +} + +void CommonTimeServer::sockaddrToString(const sockaddr_storage& addr, + bool addrValid, + char* buf, size_t bufLen) { + if (!bufLen || !buf) + return; + + if (addrValid) { + switch (addr.ss_family) { + case AF_INET: { + const struct sockaddr_in* sa = + reinterpret_cast<const struct sockaddr_in*>(&addr); + unsigned long a = ntohl(sa->sin_addr.s_addr); + uint16_t p = ntohs(sa->sin_port); + snprintf(buf, bufLen, "%lu.%lu.%lu.%lu:%hu", + ((a >> 24) & 0xFF), ((a >> 16) & 0xFF), + ((a >> 8) & 0xFF), (a & 0xFF), p); + } break; + + case AF_INET6: { + const struct sockaddr_in6* sa = + reinterpret_cast<const struct sockaddr_in6*>(&addr); + const uint8_t* a = sa->sin6_addr.s6_addr; + uint16_t p = ntohs(sa->sin6_port); + snprintf(buf, bufLen, + "%02X%02X:%02X%02X:%02X%02X:%02X%02X:" + "%02X%02X:%02X%02X:%02X%02X:%02X%02X port %hd", + a[0], a[1], a[ 2], a[ 3], a[ 4], a[ 5], a[ 6], a[ 7], + a[8], a[9], a[10], a[11], a[12], a[13], a[14], a[15], + p); + } break; + + default: + snprintf(buf, bufLen, + "<unknown sockaddr family %d>", addr.ss_family); + break; + } + } else { + snprintf(buf, bufLen, "<none>"); + } + + buf[bufLen - 1] = 0; +} + +bool CommonTimeServer::sockaddrMatch(const sockaddr_storage& a1, + const sockaddr_storage& a2, + bool matchAddressOnly) { + if (a1.ss_family != a2.ss_family) + return false; + + switch (a1.ss_family) { + case AF_INET: { + const struct sockaddr_in* sa1 = + reinterpret_cast<const struct sockaddr_in*>(&a1); + const struct sockaddr_in* sa2 = + reinterpret_cast<const struct sockaddr_in*>(&a2); + + if (sa1->sin_addr.s_addr != sa2->sin_addr.s_addr) + return false; + + return (matchAddressOnly || (sa1->sin_port == sa2->sin_port)); + } break; + + case AF_INET6: { + const struct sockaddr_in6* sa1 = + reinterpret_cast<const struct sockaddr_in6*>(&a1); + const struct sockaddr_in6* sa2 = + reinterpret_cast<const struct sockaddr_in6*>(&a2); + + if (memcmp(&sa1->sin6_addr, &sa2->sin6_addr, sizeof(sa2->sin6_addr))) + return false; + + return (matchAddressOnly || (sa1->sin6_port == sa2->sin6_port)); + } break; + + // Huh? We don't deal in non-IPv[46] addresses. Not sure how we got + // here, but we don't know how to comapre these addresses and simply + // default to a no-match decision. + default: return false; + } +} + +bool CommonTimeServer::shouldPanicNotGettingGoodData() { + if (mClient_FirstSyncTX) { + int64_t now = mLocalClock.getLocalTime(); + int64_t delta = now - (mClient_LastGoodSyncRX + ? mClient_LastGoodSyncRX + : mClient_FirstSyncTX); + int64_t deltaUsec = mCommonClock.localDurationToCommonDuration(delta); + + if (deltaUsec >= kNoGoodDataPanicThresholdUsec) + return true; + } + + return false; +} + +void CommonTimeServer::PacketRTTLog::logTX(int64_t txTime) { + txTimes[wrPtr] = txTime; + rxTimes[wrPtr] = 0; + wrPtr = (wrPtr + 1) % RTT_LOG_SIZE; + if (!wrPtr) + logFull = true; +} + +void CommonTimeServer::PacketRTTLog::logRX(int64_t txTime, int64_t rxTime) { + if (!logFull && !wrPtr) + return; + + uint32_t i = logFull ? wrPtr : 0; + do { + if (txTimes[i] == txTime) { + rxTimes[i] = rxTime; + break; + } + i = (i + 1) % RTT_LOG_SIZE; + } while (i != wrPtr); +} + +} // namespace android diff --git a/libs/common_time/common_time_server.h b/libs/common_time/common_time_server.h new file mode 100644 index 0000000..6e18050 --- /dev/null +++ b/libs/common_time/common_time_server.h @@ -0,0 +1,324 @@ +/* + * Copyright (C) 2012 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. + */ + +#ifndef ANDROID_COMMON_TIME_SERVER_H +#define ANDROID_COMMON_TIME_SERVER_H + +#include <arpa/inet.h> +#include <stdint.h> +#include <sys/socket.h> + +#include <common_time/ICommonClock.h> +#include <common_time/local_clock.h> +#include <utils/String8.h> + +#include "clock_recovery.h" +#include "common_clock.h" +#include "common_time_server_packets.h" +#include "utils.h" + +#define RTT_LOG_SIZE 30 + +namespace android { + +class CommonClockService; +class CommonTimeConfigService; + +/***** time service implementation *****/ + +class CommonTimeServer : public Thread { + public: + CommonTimeServer(); + ~CommonTimeServer(); + + bool startServices(); + + // Common Clock API methods + CommonClock& getCommonClock() { return mCommonClock; } + LocalClock& getLocalClock() { return mLocalClock; } + uint64_t getTimelineID(); + int32_t getEstimatedError(); + ICommonClock::State getState(); + status_t getMasterAddr(struct sockaddr_storage* addr); + status_t isCommonTimeValid(bool* valid, uint32_t* timelineID); + + // Config API methods + status_t getMasterElectionPriority(uint8_t *priority); + status_t setMasterElectionPriority(uint8_t priority); + status_t getMasterElectionEndpoint(struct sockaddr_storage *addr); + status_t setMasterElectionEndpoint(const struct sockaddr_storage *addr); + status_t getMasterElectionGroupId(uint64_t *id); + status_t setMasterElectionGroupId(uint64_t id); + status_t getInterfaceBinding(String8& ifaceName); + status_t setInterfaceBinding(const String8& ifaceName); + status_t getMasterAnnounceInterval(int *interval); + status_t setMasterAnnounceInterval(int interval); + status_t getClientSyncInterval(int *interval); + status_t setClientSyncInterval(int interval); + status_t getPanicThreshold(int *threshold); + status_t setPanicThreshold(int threshold); + status_t getAutoDisable(bool *autoDisable); + status_t setAutoDisable(bool autoDisable); + status_t forceNetworklessMasterMode(); + + // Method used by the CommonClockService to notify the core service about + // changes in the number of active common clock clients. + void reevaluateAutoDisableState(bool commonClockHasClients); + + status_t dumpClockInterface(int fd, const Vector<String16>& args, + size_t activeClients); + status_t dumpConfigInterface(int fd, const Vector<String16>& args); + + private: + class PacketRTTLog { + public: + PacketRTTLog() { + resetLog(); + } + + void resetLog() { + wrPtr = 0; + logFull = 0; + } + + void logTX(int64_t txTime); + void logRX(int64_t txTime, int64_t rxTime); + void dumpLog(int fd, const CommonClock& cclk); + + private: + uint32_t wrPtr; + bool logFull; + int64_t txTimes[RTT_LOG_SIZE]; + int64_t rxTimes[RTT_LOG_SIZE]; + }; + + bool threadLoop(); + + bool runStateMachine_l(); + bool setupSocket_l(); + + void assignTimelineID(); + bool assignDeviceID(); + + static bool arbitrateMaster(uint64_t deviceID1, uint8_t devicePrio1, + uint64_t deviceID2, uint8_t devicePrio2); + + bool handlePacket(); + bool handleWhoIsMasterRequest (const WhoIsMasterRequestPacket* request, + const sockaddr_storage& srcAddr); + bool handleWhoIsMasterResponse(const WhoIsMasterResponsePacket* response, + const sockaddr_storage& srcAddr); + bool handleSyncRequest (const SyncRequestPacket* request, + const sockaddr_storage& srcAddr); + bool handleSyncResponse (const SyncResponsePacket* response, + const sockaddr_storage& srcAddr); + bool handleMasterAnnouncement (const MasterAnnouncementPacket* packet, + const sockaddr_storage& srcAddr); + + bool handleTimeout(); + bool handleTimeoutInitial(); + bool handleTimeoutClient(); + bool handleTimeoutMaster(); + bool handleTimeoutRonin(); + bool handleTimeoutWaitForElection(); + + bool sendWhoIsMasterRequest(); + bool sendSyncRequest(); + bool sendMasterAnnouncement(); + + bool becomeClient(const sockaddr_storage& masterAddr, + uint64_t masterDeviceID, + uint8_t masterDevicePriority, + uint64_t timelineID, + const char* cause); + bool becomeMaster(const char* cause); + bool becomeRonin(const char* cause); + bool becomeWaitForElection(const char* cause); + bool becomeInitial(const char* cause); + + void notifyClockSync(); + void notifyClockSyncLoss(); + + ICommonClock::State mState; + void setState(ICommonClock::State s); + + void clearPendingWakeupEvents_l(); + void wakeupThread_l(); + void cleanupSocket_l(); + void shutdownThread(); + + inline uint8_t effectivePriority() const { + return (mMasterPriority & 0x7F) | + (mForceLowPriority ? 0x00 : 0x80); + } + + inline bool shouldAutoDisable() const { + return (mAutoDisable && !mCommonClockHasClients); + } + + inline void resetSyncStats() { + mClient_SyncRequestPending = false; + mClient_SyncRequestTimeouts = 0; + mClient_SyncsSentToCurMaster = 0; + mClient_SyncRespsRXedFromCurMaster = 0; + mClient_ExpiredSyncRespsRXedFromCurMaster = 0; + mClient_FirstSyncTX = 0; + mClient_LastGoodSyncRX = 0; + mClient_PacketRTTLog.resetLog(); + } + + bool shouldPanicNotGettingGoodData(); + + // Helper to keep track of the state machine's current timeout + Timeout mCurTimeout; + + // common clock, local clock abstraction, and clock recovery loop + CommonClock mCommonClock; + LocalClock mLocalClock; + ClockRecoveryLoop mClockRecovery; + + // implementation of ICommonClock + sp<CommonClockService> mICommonClock; + + // implementation of ICommonTimeConfig + sp<CommonTimeConfigService> mICommonTimeConfig; + + // UDP socket for the time sync protocol + int mSocket; + + // eventfd used to wakeup the work thread in response to configuration + // changes. + int mWakeupThreadFD; + + // timestamp captured when a packet is received + int64_t mLastPacketRxLocalTime; + + // ID of the timeline that this device is following + uint64_t mTimelineID; + + // flag for whether the clock has been synced to a timeline + bool mClockSynced; + + // flag used to indicate that clients should be considered to be lower + // priority than all of their peers during elections. This flag is set and + // cleared by the state machine. It is set when the client joins a new + // network. If the client had been a master in the old network (or an + // isolated master with no network connectivity) it should defer to any + // masters which may already be on the network. It will be cleared whenever + // the state machine transitions to the master state. + bool mForceLowPriority; + inline void setForceLowPriority(bool val) { + mForceLowPriority = val; + if (mState == ICommonClock::STATE_MASTER) + mClient_MasterDevicePriority = effectivePriority(); + } + + // Lock to synchronize access to internal state and configuration. + Mutex mLock; + + // Flag updated by the common clock service to indicate that it does or does + // not currently have registered clients. When the the auto disable flag is + // cleared on the common time service, the service will participate in + // network synchronization whenever it has a valid network interface to bind + // to. When the auto disable flag is set on the common time service, it + // will only participate in network synchronization when it has both a valid + // interface AND currently active common clock clients. + bool mCommonClockHasClients; + + // Internal logs used for dumpsys. + LogRing mStateChangeLog; + LogRing mElectionLog; + LogRing mBadPktLog; + + // Configuration info + struct sockaddr_storage mMasterElectionEP; // Endpoint over which we conduct master election + String8 mBindIface; // Endpoint for the service to bind to. + bool mBindIfaceValid; // whether or not the bind Iface is valid. + bool mBindIfaceDirty; // whether or not the bind Iface is valid. + struct sockaddr_storage mMasterEP; // Endpoint of our current master (if any) + bool mMasterEPValid; + uint64_t mDeviceID; // unique ID of this device + uint64_t mSyncGroupID; // synchronization group ID of this device. + uint8_t mMasterPriority; // Priority of this device in master election. + uint32_t mMasterAnnounceIntervalMs; + uint32_t mSyncRequestIntervalMs; + uint32_t mPanicThresholdUsec; + bool mAutoDisable; + + // Config defaults. + static const char* kDefaultMasterElectionAddr; + static const uint16_t kDefaultMasterElectionPort; + static const uint64_t kDefaultSyncGroupID; + static const uint8_t kDefaultMasterPriority; + static const uint32_t kDefaultMasterAnnounceIntervalMs; + static const uint32_t kDefaultSyncRequestIntervalMs; + static const uint32_t kDefaultPanicThresholdUsec; + static const bool kDefaultAutoDisable; + + // Priority mask and shift fields. + static const uint64_t kDeviceIDMask; + static const uint8_t kDevicePriorityMask; + static const uint8_t kDevicePriorityHiLowBit; + static const uint32_t kDevicePriorityShift; + + // Unconfgurable constants + static const int kSetupRetryTimeoutMs; + static const int64_t kNoGoodDataPanicThresholdUsec; + static const uint32_t kRTTDiscardPanicThreshMultiplier; + + /*** status while in the Initial state ***/ + int mInitial_WhoIsMasterRequestTimeouts; + static const int kInitial_NumWhoIsMasterRetries; + static const int kInitial_WhoIsMasterTimeoutMs; + + /*** status while in the Client state ***/ + uint64_t mClient_MasterDeviceID; + uint8_t mClient_MasterDevicePriority; + bool mClient_SyncRequestPending; + int mClient_SyncRequestTimeouts; + uint32_t mClient_SyncsSentToCurMaster; + uint32_t mClient_SyncRespsRXedFromCurMaster; + uint32_t mClient_ExpiredSyncRespsRXedFromCurMaster; + int64_t mClient_FirstSyncTX; + int64_t mClient_LastGoodSyncRX; + PacketRTTLog mClient_PacketRTTLog; + static const int kClient_NumSyncRequestRetries; + + + /*** status while in the Master state ***/ + static const uint32_t kDefaultMaster_AnnouncementIntervalMs; + + /*** status while in the Ronin state ***/ + int mRonin_WhoIsMasterRequestTimeouts; + static const int kRonin_NumWhoIsMasterRetries; + static const int kRonin_WhoIsMasterTimeoutMs; + + /*** status while in the WaitForElection state ***/ + static const int kWaitForElection_TimeoutMs; + + static const int kInfiniteTimeout; + + static const char* stateToString(ICommonClock::State s); + static void sockaddrToString(const sockaddr_storage& addr, bool addrValid, + char* buf, size_t bufLen); + static bool sockaddrMatch(const sockaddr_storage& a1, + const sockaddr_storage& a2, + bool matchAddressOnly); +}; + +} // namespace android + +#endif // ANDROID_COMMON_TIME_SERVER_H diff --git a/libs/common_time/common_time_server_api.cpp b/libs/common_time/common_time_server_api.cpp new file mode 100644 index 0000000..e157071 --- /dev/null +++ b/libs/common_time/common_time_server_api.cpp @@ -0,0 +1,438 @@ +/* + * Copyright (C) 2012 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 LOG_TAG "common_time" +#include <utils/Log.h> + +#include <binder/IServiceManager.h> +#include <binder/IPCThreadState.h> + +#include "common_time_server.h" + +namespace android { + +// +// Clock API +// +uint64_t CommonTimeServer::getTimelineID() { + AutoMutex _lock(&mLock); + return mTimelineID; +} + +ICommonClock::State CommonTimeServer::getState() { + AutoMutex _lock(&mLock); + return mState; +} + +status_t CommonTimeServer::getMasterAddr(struct sockaddr_storage* addr) { + AutoMutex _lock(&mLock); + if (mMasterEPValid) { + memcpy(addr, &mMasterEP, sizeof(*addr)); + return OK; + } + + return UNKNOWN_ERROR; +} + +int32_t CommonTimeServer::getEstimatedError() { + AutoMutex _lock(&mLock); + + if (ICommonClock::STATE_MASTER == mState) + return 0; + + if (!mClockSynced) + return ICommonClock::kErrorEstimateUnknown; + + return mClockRecovery.getLastErrorEstimate(); +} + +status_t CommonTimeServer::isCommonTimeValid(bool* valid, + uint32_t* timelineID) { + AutoMutex _lock(&mLock); + *valid = mCommonClock.isValid(); + *timelineID = mTimelineID; + return OK; +} + +// +// Config API +// +status_t CommonTimeServer::getMasterElectionPriority(uint8_t *priority) { + AutoMutex _lock(&mLock); + *priority = mMasterPriority; + return OK; +} + +status_t CommonTimeServer::setMasterElectionPriority(uint8_t priority) { + AutoMutex _lock(&mLock); + + if (priority > 0x7F) + return BAD_VALUE; + + mMasterPriority = priority; + return OK; +} + +status_t CommonTimeServer::getMasterElectionEndpoint( + struct sockaddr_storage *addr) { + AutoMutex _lock(&mLock); + memcpy(addr, &mMasterElectionEP, sizeof(*addr)); + return OK; +} + +status_t CommonTimeServer::setMasterElectionEndpoint( + const struct sockaddr_storage *addr) { + AutoMutex _lock(&mLock); + + if (!addr) + return BAD_VALUE; + + // TODO: add proper support for IPv6 + if (addr->ss_family != AF_INET) + return BAD_VALUE; + + // Only multicast and broadcast endpoints with explicit ports are allowed. + uint16_t ipv4Port = ntohs( + reinterpret_cast<const struct sockaddr_in*>(addr)->sin_port); + if (!ipv4Port) + return BAD_VALUE; + + uint32_t ipv4Addr = ntohl( + reinterpret_cast<const struct sockaddr_in*>(addr)->sin_addr.s_addr); + if ((ipv4Addr != 0xFFFFFFFF) && (0xE0000000 != (ipv4Addr & 0xF0000000))) + return BAD_VALUE; + + memcpy(&mMasterElectionEP, addr, sizeof(mMasterElectionEP)); + + // Force a rebind in order to change election enpoints. + mBindIfaceDirty = true; + wakeupThread_l(); + return OK; +} + +status_t CommonTimeServer::getMasterElectionGroupId(uint64_t *id) { + AutoMutex _lock(&mLock); + *id = mSyncGroupID; + return OK; +} + +status_t CommonTimeServer::setMasterElectionGroupId(uint64_t id) { + AutoMutex _lock(&mLock); + mSyncGroupID = id; + return OK; +} + +status_t CommonTimeServer::getInterfaceBinding(String8& ifaceName) { + AutoMutex _lock(&mLock); + if (!mBindIfaceValid) + return INVALID_OPERATION; + ifaceName = mBindIface; + return OK; +} + +status_t CommonTimeServer::setInterfaceBinding(const String8& ifaceName) { + AutoMutex _lock(&mLock); + + mBindIfaceDirty = true; + if (ifaceName.size()) { + mBindIfaceValid = true; + mBindIface = ifaceName; + } else { + mBindIfaceValid = false; + mBindIface.clear(); + } + + wakeupThread_l(); + return OK; +} + +status_t CommonTimeServer::getMasterAnnounceInterval(int *interval) { + AutoMutex _lock(&mLock); + *interval = mMasterAnnounceIntervalMs; + return OK; +} + +status_t CommonTimeServer::setMasterAnnounceInterval(int interval) { + AutoMutex _lock(&mLock); + + if (interval > (6 *3600000)) // Max interval is once every 6 hrs + return BAD_VALUE; + + if (interval < 500) // Min interval is once per 0.5 seconds + return BAD_VALUE; + + mMasterAnnounceIntervalMs = interval; + if (ICommonClock::STATE_MASTER == mState) { + int pendingTimeout = mCurTimeout.msecTillTimeout(); + if ((kInfiniteTimeout == pendingTimeout) || + (pendingTimeout > interval)) { + mCurTimeout.setTimeout(mMasterAnnounceIntervalMs); + wakeupThread_l(); + } + } + + return OK; +} + +status_t CommonTimeServer::getClientSyncInterval(int *interval) { + AutoMutex _lock(&mLock); + *interval = mSyncRequestIntervalMs; + return OK; +} + +status_t CommonTimeServer::setClientSyncInterval(int interval) { + AutoMutex _lock(&mLock); + + if (interval > (3600000)) // Max interval is once every 60 min + return BAD_VALUE; + + if (interval < 250) // Min interval is once per 0.25 seconds + return BAD_VALUE; + + mSyncRequestIntervalMs = interval; + if (ICommonClock::STATE_CLIENT == mState) { + int pendingTimeout = mCurTimeout.msecTillTimeout(); + if ((kInfiniteTimeout == pendingTimeout) || + (pendingTimeout > interval)) { + mCurTimeout.setTimeout(mSyncRequestIntervalMs); + wakeupThread_l(); + } + } + + return OK; +} + +status_t CommonTimeServer::getPanicThreshold(int *threshold) { + AutoMutex _lock(&mLock); + *threshold = mPanicThresholdUsec; + return OK; +} + +status_t CommonTimeServer::setPanicThreshold(int threshold) { + AutoMutex _lock(&mLock); + + if (threshold < 1000) // Min threshold is 1mSec + return BAD_VALUE; + + mPanicThresholdUsec = threshold; + return OK; +} + +status_t CommonTimeServer::getAutoDisable(bool *autoDisable) { + AutoMutex _lock(&mLock); + *autoDisable = mAutoDisable; + return OK; +} + +status_t CommonTimeServer::setAutoDisable(bool autoDisable) { + AutoMutex _lock(&mLock); + mAutoDisable = autoDisable; + wakeupThread_l(); + return OK; +} + +status_t CommonTimeServer::forceNetworklessMasterMode() { + AutoMutex _lock(&mLock); + + // Can't force networkless master mode if we are currently bound to a + // network. + if (mSocket >= 0) + return INVALID_OPERATION; + + becomeMaster("force networkless"); + + return OK; +} + +void CommonTimeServer::reevaluateAutoDisableState(bool commonClockHasClients) { + AutoMutex _lock(&mLock); + bool needWakeup = (mAutoDisable && mMasterEPValid && + (commonClockHasClients != mCommonClockHasClients)); + + mCommonClockHasClients = commonClockHasClients; + + if (needWakeup) { + ALOGI("Waking up service, auto-disable is engaged and service now has%s" + " clients", mCommonClockHasClients ? "" : " no"); + wakeupThread_l(); + } +} + +#define dump_printf(a, b...) do { \ + int res; \ + res = snprintf(buffer, sizeof(buffer), a, b); \ + buffer[sizeof(buffer) - 1] = 0; \ + if (res > 0) \ + write(fd, buffer, res); \ +} while (0) +#define checked_percentage(a, b) ((0 == b) ? 0.0f : ((100.0f * a) / b)) + +status_t CommonTimeServer::dumpClockInterface(int fd, + const Vector<String16>& args, + size_t activeClients) { + AutoMutex _lock(&mLock); + const size_t SIZE = 256; + char buffer[SIZE]; + + if (checkCallingPermission(String16("android.permission.DUMP")) == false) { + snprintf(buffer, SIZE, "Permission Denial: " + "can't dump CommonClockService from pid=%d, uid=%d\n", + IPCThreadState::self()->getCallingPid(), + IPCThreadState::self()->getCallingUid()); + write(fd, buffer, strlen(buffer)); + } else { + int64_t commonTime; + int64_t localTime; + bool synced; + char maStr[64]; + + localTime = mLocalClock.getLocalTime(); + synced = (OK == mCommonClock.localToCommon(localTime, &commonTime)); + sockaddrToString(mMasterEP, mMasterEPValid, maStr, sizeof(maStr)); + + dump_printf("Common Clock Service Status\nLocal time : %lld\n", + localTime); + + if (synced) + dump_printf("Common time : %lld\n", commonTime); + else + dump_printf("Common time : %s\n", "not synced"); + + dump_printf("Timeline ID : %016llx\n", mTimelineID); + dump_printf("State : %s\n", stateToString(mState)); + dump_printf("Master Addr : %s\n", maStr); + + + if (synced) { + int32_t est = (ICommonClock::STATE_MASTER != mState) + ? mClockRecovery.getLastErrorEstimate() + : 0; + dump_printf("Error Est. : %.3f msec\n", + static_cast<float>(est) / 1000.0); + } else { + dump_printf("Error Est. : %s\n", "unknown"); + } + + dump_printf("Syncs TXes : %u\n", mClient_SyncsSentToCurMaster); + dump_printf("Syncs RXes : %u (%.2f%%)\n", + mClient_SyncRespsRXedFromCurMaster, + checked_percentage( + mClient_SyncRespsRXedFromCurMaster, + mClient_SyncsSentToCurMaster)); + dump_printf("RXs Expired : %u (%.2f%%)\n", + mClient_ExpiredSyncRespsRXedFromCurMaster, + checked_percentage( + mClient_ExpiredSyncRespsRXedFromCurMaster, + mClient_SyncsSentToCurMaster)); + + if (!mClient_LastGoodSyncRX) { + dump_printf("Last Good RX : %s\n", "unknown"); + } else { + int64_t localDelta, usecDelta; + localDelta = localTime - mClient_LastGoodSyncRX; + usecDelta = mCommonClock.localDurationToCommonDuration(localDelta); + dump_printf("Last Good RX : %lld uSec ago\n", usecDelta); + } + + dump_printf("Active Clients : %u\n", activeClients); + mClient_PacketRTTLog.dumpLog(fd, mCommonClock); + mStateChangeLog.dumpLog(fd); + mElectionLog.dumpLog(fd); + mBadPktLog.dumpLog(fd); + } + + return NO_ERROR; +} + +status_t CommonTimeServer::dumpConfigInterface(int fd, + const Vector<String16>& args) { + AutoMutex _lock(&mLock); + const size_t SIZE = 256; + char buffer[SIZE]; + + if (checkCallingPermission(String16("android.permission.DUMP")) == false) { + snprintf(buffer, SIZE, "Permission Denial: " + "can't dump CommonTimeConfigService from pid=%d, uid=%d\n", + IPCThreadState::self()->getCallingPid(), + IPCThreadState::self()->getCallingUid()); + write(fd, buffer, strlen(buffer)); + } else { + char meStr[64]; + + sockaddrToString(mMasterElectionEP, true, meStr, sizeof(meStr)); + + dump_printf("Common Time Config Service Status\n" + "Bound Interface : %s\n", + mBindIfaceValid ? mBindIface.string() : "<unbound>"); + dump_printf("Master Election Endpoint : %s\n", meStr); + dump_printf("Master Election Group ID : %016llx\n", mSyncGroupID); + dump_printf("Master Announce Interval : %d mSec\n", + mMasterAnnounceIntervalMs); + dump_printf("Client Sync Interval : %d mSec\n", + mSyncRequestIntervalMs); + dump_printf("Panic Threshold : %d uSec\n", + mPanicThresholdUsec); + dump_printf("Base ME Prio : 0x%02x\n", + static_cast<uint32_t>(mMasterPriority)); + dump_printf("Effective ME Prio : 0x%02x\n", + static_cast<uint32_t>(effectivePriority())); + dump_printf("Auto Disable Allowed : %s\n", + mAutoDisable ? "yes" : "no"); + dump_printf("Auto Disable Engaged : %s\n", + shouldAutoDisable() ? "yes" : "no"); + } + + return NO_ERROR; +} + +void CommonTimeServer::PacketRTTLog::dumpLog(int fd, const CommonClock& cclk) { + const size_t SIZE = 256; + char buffer[SIZE]; + uint32_t avail = !logFull ? wrPtr : RTT_LOG_SIZE; + + if (!avail) + return; + + dump_printf("\nPacket Log (%d entries)\n", avail); + + uint32_t ndx = 0; + uint32_t i = logFull ? wrPtr : 0; + do { + if (rxTimes[i]) { + int64_t delta = rxTimes[i] - txTimes[i]; + int64_t deltaUsec = cclk.localDurationToCommonDuration(delta); + dump_printf("pkt[%2d] : localTX %12lld localRX %12lld " + "(%.3f msec RTT)\n", + ndx, txTimes[i], rxTimes[i], + static_cast<float>(deltaUsec) / 1000.0); + } else { + dump_printf("pkt[%2d] : localTX %12lld localRX never\n", + ndx, txTimes[i]); + } + i = (i + 1) % RTT_LOG_SIZE; + ndx++; + } while (i != wrPtr); +} + +#undef dump_printf +#undef checked_percentage + +} // namespace android diff --git a/libs/common_time/common_time_server_packets.cpp b/libs/common_time/common_time_server_packets.cpp new file mode 100644 index 0000000..9833c37 --- /dev/null +++ b/libs/common_time/common_time_server_packets.cpp @@ -0,0 +1,293 @@ +/* + * Copyright (C) 2012 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 LOG_TAG "common_time" +#include <utils/Log.h> + +#include <arpa/inet.h> +#include <stdint.h> + +#include "common_time_server_packets.h" + +namespace android { + +const uint32_t TimeServicePacketHeader::kMagic = + (static_cast<uint32_t>('c') << 24) | + (static_cast<uint32_t>('c') << 16) | + (static_cast<uint32_t>('l') << 8) | + static_cast<uint32_t>('k'); + +const uint16_t TimeServicePacketHeader::kCurVersion = 1; + +#define SERIALIZE_FIELD(field_name, type, converter) \ + do { \ + if ((offset + sizeof(field_name)) > length) \ + return -1; \ + *((type*)(data + offset)) = converter(field_name); \ + offset += sizeof(field_name); \ + } while (0) +#define SERIALIZE_INT16(field_name) SERIALIZE_FIELD(field_name, int16_t, htons) +#define SERIALIZE_INT32(field_name) SERIALIZE_FIELD(field_name, int32_t, htonl) +#define SERIALIZE_INT64(field_name) SERIALIZE_FIELD(field_name, int64_t, htonq) + +#define DESERIALIZE_FIELD(field_name, type, converter) \ + do { \ + if ((offset + sizeof(field_name)) > length) \ + return -1; \ + field_name = converter(*((type*)(data + offset))); \ + offset += sizeof(field_name); \ + } while (0) +#define DESERIALIZE_INT16(field_name) DESERIALIZE_FIELD(field_name, int16_t, ntohs) +#define DESERIALIZE_INT32(field_name) DESERIALIZE_FIELD(field_name, int32_t, ntohl) +#define DESERIALIZE_INT64(field_name) DESERIALIZE_FIELD(field_name, int64_t, ntohq) + +#define kDevicePriorityShift 56 +#define kDeviceIDMask ((static_cast<uint64_t>(1) << kDevicePriorityShift) - 1) + +inline uint64_t packDeviceID(uint64_t devID, uint8_t prio) { + return (devID & kDeviceIDMask) | + (static_cast<uint64_t>(prio) << kDevicePriorityShift); +} + +inline uint64_t unpackDeviceID(uint64_t packed) { + return (packed & kDeviceIDMask); +} + +inline uint8_t unpackDevicePriority(uint64_t packed) { + return static_cast<uint8_t>(packed >> kDevicePriorityShift); +} + +ssize_t TimeServicePacketHeader::serializeHeader(uint8_t* data, + uint32_t length) { + ssize_t offset = 0; + int16_t pktType = static_cast<int16_t>(packetType); + SERIALIZE_INT32(magic); + SERIALIZE_INT16(version); + SERIALIZE_INT16(pktType); + SERIALIZE_INT64(timelineID); + SERIALIZE_INT64(syncGroupID); + return offset; +} + +ssize_t TimeServicePacketHeader::deserializeHeader(const uint8_t* data, + uint32_t length) { + ssize_t offset = 0; + int16_t tmp; + DESERIALIZE_INT32(magic); + DESERIALIZE_INT16(version); + DESERIALIZE_INT16(tmp); + DESERIALIZE_INT64(timelineID); + DESERIALIZE_INT64(syncGroupID); + packetType = static_cast<TimeServicePacketType>(tmp); + return offset; +} + +ssize_t TimeServicePacketHeader::serializePacket(uint8_t* data, + uint32_t length) { + ssize_t ret, tmp; + + ret = serializeHeader(data, length); + if (ret < 0) + return ret; + + data += ret; + length -= ret; + + switch (packetType) { + case TIME_PACKET_WHO_IS_MASTER_REQUEST: + tmp =((WhoIsMasterRequestPacket*)(this))->serializePacket(data, + length); + break; + case TIME_PACKET_WHO_IS_MASTER_RESPONSE: + tmp =((WhoIsMasterResponsePacket*)(this))->serializePacket(data, + length); + break; + case TIME_PACKET_SYNC_REQUEST: + tmp =((SyncRequestPacket*)(this))->serializePacket(data, length); + break; + case TIME_PACKET_SYNC_RESPONSE: + tmp =((SyncResponsePacket*)(this))->serializePacket(data, length); + break; + case TIME_PACKET_MASTER_ANNOUNCEMENT: + tmp =((MasterAnnouncementPacket*)(this))->serializePacket(data, + length); + break; + default: + return -1; + } + + if (tmp < 0) + return tmp; + + return ret + tmp; +} + +ssize_t UniversalTimeServicePacket::deserializePacket( + const uint8_t* data, + uint32_t length, + uint64_t expectedSyncGroupID) { + ssize_t ret; + TimeServicePacketHeader* header; + if (length < 8) + return -1; + + packetType = ntohs(*((uint16_t*)(data + 6))); + switch (packetType) { + case TIME_PACKET_WHO_IS_MASTER_REQUEST: + ret = p.who_is_master_request.deserializePacket(data, length); + header = &p.who_is_master_request; + break; + case TIME_PACKET_WHO_IS_MASTER_RESPONSE: + ret = p.who_is_master_response.deserializePacket(data, length); + header = &p.who_is_master_response; + break; + case TIME_PACKET_SYNC_REQUEST: + ret = p.sync_request.deserializePacket(data, length); + header = &p.sync_request; + break; + case TIME_PACKET_SYNC_RESPONSE: + ret = p.sync_response.deserializePacket(data, length); + header = &p.sync_response; + break; + case TIME_PACKET_MASTER_ANNOUNCEMENT: + ret = p.master_announcement.deserializePacket(data, length); + header = &p.master_announcement; + break; + default: + return -1; + } + + if ((ret >= 0) && !header->checkPacket(expectedSyncGroupID)) + ret = -1; + + return ret; +} + +ssize_t WhoIsMasterRequestPacket::serializePacket(uint8_t* data, + uint32_t length) { + ssize_t offset = serializeHeader(data, length); + if (offset > 0) { + uint64_t packed = packDeviceID(senderDeviceID, senderDevicePriority); + SERIALIZE_INT64(packed); + } + return offset; +} + +ssize_t WhoIsMasterRequestPacket::deserializePacket(const uint8_t* data, + uint32_t length) { + ssize_t offset = deserializeHeader(data, length); + if (offset > 0) { + uint64_t packed; + DESERIALIZE_INT64(packed); + senderDeviceID = unpackDeviceID(packed); + senderDevicePriority = unpackDevicePriority(packed); + } + return offset; +} + +ssize_t WhoIsMasterResponsePacket::serializePacket(uint8_t* data, + uint32_t length) { + ssize_t offset = serializeHeader(data, length); + if (offset > 0) { + uint64_t packed = packDeviceID(deviceID, devicePriority); + SERIALIZE_INT64(packed); + } + return offset; +} + +ssize_t WhoIsMasterResponsePacket::deserializePacket(const uint8_t* data, + uint32_t length) { + ssize_t offset = deserializeHeader(data, length); + if (offset > 0) { + uint64_t packed; + DESERIALIZE_INT64(packed); + deviceID = unpackDeviceID(packed); + devicePriority = unpackDevicePriority(packed); + } + return offset; +} + +ssize_t SyncRequestPacket::serializePacket(uint8_t* data, + uint32_t length) { + ssize_t offset = serializeHeader(data, length); + if (offset > 0) { + SERIALIZE_INT64(clientTxLocalTime); + } + return offset; +} + +ssize_t SyncRequestPacket::deserializePacket(const uint8_t* data, + uint32_t length) { + ssize_t offset = deserializeHeader(data, length); + if (offset > 0) { + DESERIALIZE_INT64(clientTxLocalTime); + } + return offset; +} + +ssize_t SyncResponsePacket::serializePacket(uint8_t* data, + uint32_t length) { + ssize_t offset = serializeHeader(data, length); + if (offset > 0) { + SERIALIZE_INT64(clientTxLocalTime); + SERIALIZE_INT64(masterRxCommonTime); + SERIALIZE_INT64(masterTxCommonTime); + SERIALIZE_INT32(nak); + } + return offset; +} + +ssize_t SyncResponsePacket::deserializePacket(const uint8_t* data, + uint32_t length) { + ssize_t offset = deserializeHeader(data, length); + if (offset > 0) { + DESERIALIZE_INT64(clientTxLocalTime); + DESERIALIZE_INT64(masterRxCommonTime); + DESERIALIZE_INT64(masterTxCommonTime); + DESERIALIZE_INT32(nak); + } + return offset; +} + +ssize_t MasterAnnouncementPacket::serializePacket(uint8_t* data, + uint32_t length) { + ssize_t offset = serializeHeader(data, length); + if (offset > 0) { + uint64_t packed = packDeviceID(deviceID, devicePriority); + SERIALIZE_INT64(packed); + } + return offset; +} + +ssize_t MasterAnnouncementPacket::deserializePacket(const uint8_t* data, + uint32_t length) { + ssize_t offset = deserializeHeader(data, length); + if (offset > 0) { + uint64_t packed; + DESERIALIZE_INT64(packed); + deviceID = unpackDeviceID(packed); + devicePriority = unpackDevicePriority(packed); + } + return offset; +} + +} // namespace android + diff --git a/libs/common_time/common_time_server_packets.h b/libs/common_time/common_time_server_packets.h new file mode 100644 index 0000000..57ba8a2 --- /dev/null +++ b/libs/common_time/common_time_server_packets.h @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2012 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. + */ + +#ifndef ANDROID_COMMON_TIME_SERVER_PACKETS_H +#define ANDROID_COMMON_TIME_SERVER_PACKETS_H + +#include <stdint.h> +#include <common_time/ICommonClock.h> + +namespace android { + +/***** time sync protocol packets *****/ + +enum TimeServicePacketType { + TIME_PACKET_WHO_IS_MASTER_REQUEST = 1, + TIME_PACKET_WHO_IS_MASTER_RESPONSE, + TIME_PACKET_SYNC_REQUEST, + TIME_PACKET_SYNC_RESPONSE, + TIME_PACKET_MASTER_ANNOUNCEMENT, +}; + +class TimeServicePacketHeader { + public: + friend class UniversalTimeServicePacket; + // magic number identifying the protocol + uint32_t magic; + + // protocol version of the packet + uint16_t version; + + // type of the packet + TimeServicePacketType packetType; + + // the timeline ID + uint64_t timelineID; + + // synchronization group this packet belongs to (used to operate multiple + // synchronization domains which all use the same master election endpoint) + uint64_t syncGroupID; + + ssize_t serializePacket(uint8_t* data, uint32_t length); + + protected: + void initHeader(TimeServicePacketType type, + const uint64_t tlID, + const uint64_t groupID) { + magic = kMagic; + version = kCurVersion; + packetType = type; + timelineID = tlID; + syncGroupID = groupID; + } + + bool checkPacket(uint64_t expectedSyncGroupID) const { + return ((magic == kMagic) && + (version == kCurVersion) && + (!expectedSyncGroupID || (syncGroupID == expectedSyncGroupID))); + } + + ssize_t serializeHeader(uint8_t* data, uint32_t length); + ssize_t deserializeHeader(const uint8_t* data, uint32_t length); + + private: + static const uint32_t kMagic; + static const uint16_t kCurVersion; +}; + +// packet querying for a suitable master +class WhoIsMasterRequestPacket : public TimeServicePacketHeader { + public: + uint64_t senderDeviceID; + uint8_t senderDevicePriority; + + void initHeader(const uint64_t groupID) { + TimeServicePacketHeader::initHeader(TIME_PACKET_WHO_IS_MASTER_REQUEST, + ICommonClock::kInvalidTimelineID, + groupID); + } + + ssize_t serializePacket(uint8_t* data, uint32_t length); + ssize_t deserializePacket(const uint8_t* data, uint32_t length); +}; + +// response to a WhoIsMaster request +class WhoIsMasterResponsePacket : public TimeServicePacketHeader { + public: + uint64_t deviceID; + uint8_t devicePriority; + + void initHeader(const uint64_t tlID, const uint64_t groupID) { + TimeServicePacketHeader::initHeader(TIME_PACKET_WHO_IS_MASTER_RESPONSE, + tlID, groupID); + } + + ssize_t serializePacket(uint8_t* data, uint32_t length); + ssize_t deserializePacket(const uint8_t* data, uint32_t length); +}; + +// packet sent by a client requesting correspondence between local +// and common time +class SyncRequestPacket : public TimeServicePacketHeader { + public: + // local time when this request was transmitted + int64_t clientTxLocalTime; + + void initHeader(const uint64_t tlID, const uint64_t groupID) { + TimeServicePacketHeader::initHeader(TIME_PACKET_SYNC_REQUEST, + tlID, groupID); + } + + ssize_t serializePacket(uint8_t* data, uint32_t length); + ssize_t deserializePacket(const uint8_t* data, uint32_t length); +}; + +// response to a sync request sent by the master +class SyncResponsePacket : public TimeServicePacketHeader { + public: + // local time when this request was transmitted by the client + int64_t clientTxLocalTime; + + // common time when the master received the request + int64_t masterRxCommonTime; + + // common time when the master transmitted the response + int64_t masterTxCommonTime; + + // flag that is set if the recipient of the sync request is not acting + // as a master for the requested timeline + uint32_t nak; + + void initHeader(const uint64_t tlID, const uint64_t groupID) { + TimeServicePacketHeader::initHeader(TIME_PACKET_SYNC_RESPONSE, + tlID, groupID); + } + + ssize_t serializePacket(uint8_t* data, uint32_t length); + ssize_t deserializePacket(const uint8_t* data, uint32_t length); +}; + +// announcement of the master's presence +class MasterAnnouncementPacket : public TimeServicePacketHeader { + public: + // the master's device ID + uint64_t deviceID; + uint8_t devicePriority; + + void initHeader(const uint64_t tlID, const uint64_t groupID) { + TimeServicePacketHeader::initHeader(TIME_PACKET_MASTER_ANNOUNCEMENT, + tlID, groupID); + } + + ssize_t serializePacket(uint8_t* data, uint32_t length); + ssize_t deserializePacket(const uint8_t* data, uint32_t length); +}; + +class UniversalTimeServicePacket { + public: + uint16_t packetType; + union { + WhoIsMasterRequestPacket who_is_master_request; + WhoIsMasterResponsePacket who_is_master_response; + SyncRequestPacket sync_request; + SyncResponsePacket sync_response; + MasterAnnouncementPacket master_announcement; + } p; + + ssize_t deserializePacket(const uint8_t* data, + uint32_t length, + uint64_t expectedSyncGroupID); +}; + +}; // namespace android + +#endif // ANDROID_COMMON_TIME_SERVER_PACKETS_H + + diff --git a/libs/common_time/diag_thread.cpp b/libs/common_time/diag_thread.cpp new file mode 100644 index 0000000..4cb9551 --- /dev/null +++ b/libs/common_time/diag_thread.cpp @@ -0,0 +1,323 @@ +/* + * 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. + */ + +#define LOG_TAG "common_time" +#include <utils/Log.h> + +#include <fcntl.h> +#include <linux/in.h> +#include <linux/tcp.h> +#include <poll.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <unistd.h> +#include <utils/Errors.h> +#include <utils/misc.h> + +#include <common_time/local_clock.h> + +#include "common_clock.h" +#include "diag_thread.h" + +#define kMaxEvents 16 +#define kListenPort 9876 + +static bool setNonblocking(int fd) { + int flags = fcntl(fd, F_GETFL); + if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) { + ALOGE("Failed to set socket (%d) to non-blocking mode (errno %d)", + fd, errno); + return false; + } + + return true; +} + +static bool setNodelay(int fd) { + int tmp = 1; + if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &tmp, sizeof(tmp)) < 0) { + ALOGE("Failed to set socket (%d) to no-delay mode (errno %d)", + fd, errno); + return false; + } + + return true; +} + +namespace android { + +DiagThread::DiagThread(CommonClock* common_clock, LocalClock* local_clock) { + common_clock_ = common_clock; + local_clock_ = local_clock; + listen_fd_ = -1; + data_fd_ = -1; + kernel_logID_basis_known_ = false; + discipline_log_ID_ = 0; +} + +DiagThread::~DiagThread() { +} + +status_t DiagThread::startWorkThread() { + status_t res; + stopWorkThread(); + res = run("Diag"); + + if (res != OK) + ALOGE("Failed to start work thread (res = %d)", res); + + return res; +} + +void DiagThread::stopWorkThread() { + status_t res; + res = requestExitAndWait(); // block until thread exit. + if (res != OK) + ALOGE("Failed to stop work thread (res = %d)", res); +} + +bool DiagThread::openListenSocket() { + bool ret = false; + int flags; + cleanupListenSocket(); + + if ((listen_fd_ = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) { + ALOGE("Socket failed."); + goto bailout; + } + + // Set non-blocking operation + if (!setNonblocking(listen_fd_)) + goto bailout; + + struct sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = INADDR_ANY; + addr.sin_port = htons(kListenPort); + + if (bind(listen_fd_, (struct sockaddr*)&addr, sizeof(addr)) < 0) { + ALOGE("Bind failed."); + goto bailout; + } + + if (listen(listen_fd_, 1) < 0) { + ALOGE("Listen failed."); + goto bailout; + } + + ret = true; +bailout: + if (!ret) + cleanupListenSocket(); + + return ret; +} + +void DiagThread::cleanupListenSocket() { + if (listen_fd_ >= 0) { + int res; + + struct linger l; + l.l_onoff = 1; + l.l_linger = 0; + + setsockopt(listen_fd_, SOL_SOCKET, SO_LINGER, &l, sizeof(l)); + shutdown(listen_fd_, SHUT_RDWR); + close(listen_fd_); + listen_fd_ = -1; + } +} + +void DiagThread::cleanupDataSocket() { + if (data_fd_ >= 0) { + int res; + + struct linger l; + l.l_onoff = 1; + l.l_linger = 0; + + setsockopt(data_fd_, SOL_SOCKET, SO_LINGER, &l, sizeof(l)); + shutdown(data_fd_, SHUT_RDWR); + close(data_fd_); + data_fd_ = -1; + } +} + +void DiagThread::resetLogIDs() { + // Drain and discard all of the events from the kernel + struct local_time_debug_event events[kMaxEvents]; + while(local_clock_->getDebugLog(events, kMaxEvents) > 0) + ; + + { + Mutex::Autolock lock(&discipline_log_lock_); + discipline_log_.clear(); + discipline_log_ID_ = 0; + } + + kernel_logID_basis_known_ = false; +} + +void DiagThread::pushDisciplineEvent(int64_t observed_local_time, + int64_t observed_common_time, + int64_t nominal_common_time, + int32_t total_correction, + int32_t rtt) { + Mutex::Autolock lock(&discipline_log_lock_); + + DisciplineEventRecord evt; + + evt.event_id = discipline_log_ID_++; + + evt.action_local_time = local_clock_->getLocalTime(); + common_clock_->localToCommon(evt.action_local_time, + &evt.action_common_time); + + evt.observed_local_time = observed_local_time; + evt.observed_common_time = observed_common_time; + evt.nominal_common_time = nominal_common_time; + evt.total_correction = total_correction; + evt.rtt = rtt; + + discipline_log_.push_back(evt); + while (discipline_log_.size() > kMaxDisciplineLogSize) + discipline_log_.erase(discipline_log_.begin()); +} + +bool DiagThread::threadLoop() { + struct pollfd poll_fds[1]; + + if (!openListenSocket()) { + ALOGE("Failed to open listen socket"); + goto bailout; + } + + while (!exitPending()) { + memset(&poll_fds, 0, sizeof(poll_fds)); + + if (data_fd_ < 0) { + poll_fds[0].fd = listen_fd_; + poll_fds[0].events = POLLIN; + } else { + poll_fds[0].fd = data_fd_; + poll_fds[0].events = POLLRDHUP | POLLIN; + } + + int poll_res = poll(poll_fds, NELEM(poll_fds), 50); + if (poll_res < 0) { + ALOGE("Fatal error (%d,%d) while waiting on events", + poll_res, errno); + goto bailout; + } + + if (exitPending()) + break; + + if (poll_fds[0].revents) { + if (poll_fds[0].fd == listen_fd_) { + data_fd_ = accept(listen_fd_, NULL, NULL); + + if (data_fd_ < 0) { + ALOGW("Failed accept on socket %d with err %d", + listen_fd_, errno); + } else { + if (!setNonblocking(data_fd_)) + cleanupDataSocket(); + if (!setNodelay(data_fd_)) + cleanupDataSocket(); + } + } else + if (poll_fds[0].fd == data_fd_) { + if (poll_fds[0].revents & POLLRDHUP) { + // Connection hung up; time to clean up. + cleanupDataSocket(); + } else + if (poll_fds[0].revents & POLLIN) { + uint8_t cmd; + if (read(data_fd_, &cmd, sizeof(cmd)) > 0) { + switch(cmd) { + case 'r': + case 'R': + resetLogIDs(); + break; + } + } + } + } + } + + struct local_time_debug_event events[kMaxEvents]; + int amt = local_clock_->getDebugLog(events, kMaxEvents); + + if (amt > 0) { + for (int i = 0; i < amt; i++) { + struct local_time_debug_event& e = events[i]; + + if (!kernel_logID_basis_known_) { + kernel_logID_basis_ = e.local_timesync_event_id; + kernel_logID_basis_known_ = true; + } + + char buf[1024]; + int64_t common_time; + status_t res = common_clock_->localToCommon(e.local_time, + &common_time); + snprintf(buf, sizeof(buf), "E,%lld,%lld,%lld,%d\n", + e.local_timesync_event_id - kernel_logID_basis_, + e.local_time, + common_time, + (OK == res) ? 1 : 0); + buf[sizeof(buf) - 1] = 0; + + if (data_fd_ >= 0) + write(data_fd_, buf, strlen(buf)); + } + } + + { // scope for autolock pattern + Mutex::Autolock lock(&discipline_log_lock_); + + while (discipline_log_.size() > 0) { + char buf[1024]; + DisciplineEventRecord& e = *discipline_log_.begin(); + snprintf(buf, sizeof(buf), + "D,%lld,%lld,%lld,%lld,%lld,%lld,%d,%d\n", + e.event_id, + e.action_local_time, + e.action_common_time, + e.observed_local_time, + e.observed_common_time, + e.nominal_common_time, + e.total_correction, + e.rtt); + buf[sizeof(buf) - 1] = 0; + + if (data_fd_ >= 0) + write(data_fd_, buf, strlen(buf)); + + discipline_log_.erase(discipline_log_.begin()); + } + } + } + +bailout: + cleanupDataSocket(); + cleanupListenSocket(); + return false; +} + +} // namespace android diff --git a/libs/common_time/diag_thread.h b/libs/common_time/diag_thread.h new file mode 100644 index 0000000..c630e0d --- /dev/null +++ b/libs/common_time/diag_thread.h @@ -0,0 +1,76 @@ +/* + * 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. + */ + +#ifndef __DIAG_THREAD_H__ +#define __DIAG_THREAD_H__ + +#include <utils/List.h> +#include <utils/threads.h> + +namespace android { + +class CommonClock; +class LocalClock; + +class DiagThread : public Thread { + public: + DiagThread(CommonClock* common_clock, LocalClock* local_clock); + ~DiagThread(); + + status_t startWorkThread(); + void stopWorkThread(); + virtual bool threadLoop(); + + void pushDisciplineEvent(int64_t observed_local_time, + int64_t observed_common_time, + int64_t nominal_common_time, + int32_t total_correction, + int32_t rtt); + + private: + typedef struct { + int64_t event_id; + int64_t action_local_time; + int64_t action_common_time; + int64_t observed_local_time; + int64_t observed_common_time; + int64_t nominal_common_time; + int32_t total_correction; + int32_t rtt; + } DisciplineEventRecord; + + bool openListenSocket(); + void cleanupListenSocket(); + void cleanupDataSocket(); + void resetLogIDs(); + + CommonClock* common_clock_; + LocalClock* local_clock_; + int listen_fd_; + int data_fd_; + + int64_t kernel_logID_basis_; + bool kernel_logID_basis_known_; + + static const size_t kMaxDisciplineLogSize = 16; + Mutex discipline_log_lock_; + List<DisciplineEventRecord> discipline_log_; + int64_t discipline_log_ID_; +}; + +} // namespace android + +#endif //__ DIAG_THREAD_H__ diff --git a/libs/common_time/main.cpp b/libs/common_time/main.cpp new file mode 100644 index 0000000..49eb30a --- /dev/null +++ b/libs/common_time/main.cpp @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2012 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 LOG_TAG "common_time" +#include <utils/Log.h> + +#include <binder/IPCThreadState.h> +#include <binder/ProcessState.h> + +#include "common_time_server.h" + +int main(int argc, char *argv[]) { + using namespace android; + + sp<CommonTimeServer> service = new CommonTimeServer(); + if (service == NULL) + return 1; + + ProcessState::self()->startThreadPool(); + service->run("CommonTimeServer", ANDROID_PRIORITY_NORMAL); + + IPCThreadState::self()->joinThreadPool(); + return 0; +} + diff --git a/libs/common_time/utils.cpp b/libs/common_time/utils.cpp new file mode 100644 index 0000000..ed2c77d --- /dev/null +++ b/libs/common_time/utils.cpp @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2012 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. + */ + +#define LOG_TAG "common_time" +#include <utils/Log.h> + +#include "utils.h" + +namespace android { + +void Timeout::setTimeout(int msec) { + if (msec < 0) { + mSystemEndTime = 0; + return; + } + + mSystemEndTime = systemTime() + (static_cast<nsecs_t>(msec) * 1000000); +} + +int Timeout::msecTillTimeout(nsecs_t nowTime) { + if (!mSystemEndTime) { + return -1; + } + + if (mSystemEndTime < nowTime) { + return 0; + } + + nsecs_t delta = mSystemEndTime - nowTime; + delta += 999999; + delta /= 1000000; + if (delta > 0x7FFFFFFF) { + return 0x7FFFFFFF; + } + + return static_cast<int>(delta); +} + +LogRing::LogRing(const char* header, size_t entries) + : mSize(entries) + , mWr(0) + , mIsFull(false) + , mHeader(header) { + mRingBuffer = new Entry[mSize]; + if (NULL == mRingBuffer) + ALOGE("Failed to allocate log ring with %u entries.", mSize); +} + +LogRing::~LogRing() { + if (NULL != mRingBuffer) + delete[] mRingBuffer; +} + +void LogRing::log(int prio, const char* tag, const char* fmt, ...) { + va_list argp; + va_start(argp, fmt); + internalLog(prio, tag, fmt, argp); + va_end(argp); +} + +void LogRing::log(const char* fmt, ...) { + va_list argp; + va_start(argp, fmt); + internalLog(0, NULL, fmt, argp); + va_end(argp); +} + +void LogRing::internalLog(int prio, + const char* tag, + const char* fmt, + va_list argp) { + if (NULL != mRingBuffer) { + Mutex::Autolock lock(&mLock); + String8 s(String8::formatV(fmt, argp)); + Entry* last = NULL; + + if (mIsFull || mWr) + last = &(mRingBuffer[(mWr + mSize - 1) % mSize]); + + + if ((NULL != last) && !last->s.compare(s)) { + gettimeofday(&(last->last_ts), NULL); + ++last->count; + } else { + gettimeofday(&mRingBuffer[mWr].first_ts, NULL); + mRingBuffer[mWr].last_ts = mRingBuffer[mWr].first_ts; + mRingBuffer[mWr].count = 1; + mRingBuffer[mWr].s.setTo(s); + + mWr = (mWr + 1) % mSize; + if (!mWr) + mIsFull = true; + } + } + + if (NULL != tag) + LOG_PRI_VA(prio, tag, fmt, argp); +} + +void LogRing::dumpLog(int fd) { + if (NULL == mRingBuffer) + return; + + Mutex::Autolock lock(&mLock); + + if (!mWr && !mIsFull) + return; + + char buf[1024]; + int res; + size_t start = mIsFull ? mWr : 0; + size_t count = mIsFull ? mSize : mWr; + static const char* kTimeFmt = "%a %b %d %Y %H:%M:%S"; + + res = snprintf(buf, sizeof(buf), "\n%s\n", mHeader); + if (res > 0) + write(fd, buf, res); + + for (size_t i = 0; i < count; ++i) { + struct tm t; + char timebuf[64]; + char repbuf[96]; + size_t ndx = (start + i) % mSize; + + if (1 != mRingBuffer[ndx].count) { + localtime_r(&mRingBuffer[ndx].last_ts.tv_sec, &t); + strftime(timebuf, sizeof(timebuf), kTimeFmt, &t); + snprintf(repbuf, sizeof(repbuf), + " (repeated %d times, last was %s.%03ld)", + mRingBuffer[ndx].count, + timebuf, + mRingBuffer[ndx].last_ts.tv_usec / 1000); + repbuf[sizeof(repbuf) - 1] = 0; + } else { + repbuf[0] = 0; + } + + localtime_r(&mRingBuffer[ndx].first_ts.tv_sec, &t); + strftime(timebuf, sizeof(timebuf), kTimeFmt, &t); + res = snprintf(buf, sizeof(buf), "[%2d] %s.%03ld :: %s%s\n", + i, timebuf, + mRingBuffer[ndx].first_ts.tv_usec / 1000, + mRingBuffer[ndx].s.string(), + repbuf); + + if (res > 0) + write(fd, buf, res); + } +} + +} // namespace android diff --git a/libs/common_time/utils.h b/libs/common_time/utils.h new file mode 100644 index 0000000..c28cf0a --- /dev/null +++ b/libs/common_time/utils.h @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2012 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. + */ + +#ifndef __UTILS_H__ +#define __UTILS_H__ + +#include <stdint.h> +#include <unistd.h> + +#include <utils/String8.h> +#include <utils/threads.h> +#include <utils/Timers.h> + +namespace android { + +class Timeout { + public: + Timeout() : mSystemEndTime(0) { } + + // Set a timeout which should occur msec milliseconds from now. + // Negative values will cancel any current timeout; + void setTimeout(int msec); + + // Return the number of milliseconds until the timeout occurs, or -1 if + // no timeout is scheduled. + int msecTillTimeout(nsecs_t nowTime); + int msecTillTimeout() { return msecTillTimeout(systemTime()); } + + private: + // The systemTime() at which the timeout will be complete, or 0 if no + // timeout is currently scheduled. + nsecs_t mSystemEndTime; +}; + +class LogRing { + public: + LogRing(const char* header, size_t entries); + ~LogRing(); + + // Send a log message to logcat as well as storing it in the ring buffer. + void log(int prio, const char* tag, const char* fmt, ...); + + // Add a log message the ring buffer, do not send the message to logcat. + void log(const char* fmt, ...); + + // Dump the log to an fd (dumpsys style) + void dumpLog(int fd); + + private: + class Entry { + public: + uint32_t count; + struct timeval first_ts; + struct timeval last_ts; + String8 s; + }; + + Mutex mLock; + Entry* mRingBuffer; + size_t mSize; + size_t mWr; + bool mIsFull; + const char* mHeader; + + void internalLog(int prio, const char* tag, const char* fmt, va_list va); +}; + +} // namespace android + +#endif // __UTILS_H__ |