Руслан Урядинский / libuavcan

Dependents:   UAVCAN UAVCAN_Subscriber

Committer:
RuslanUrya
Date:
Sat Apr 14 10:25:32 2018 +0000
Revision:
0:dfe6edabb8ec
Initial commit

Who changed what in which revision?

UserRevisionLine numberNew contents of line
RuslanUrya 0:dfe6edabb8ec 1 /*
RuslanUrya 0:dfe6edabb8ec 2 * Copyright (C) 2014 Pavel Kirienko <pavel.kirienko@gmail.com>
RuslanUrya 0:dfe6edabb8ec 3 */
RuslanUrya 0:dfe6edabb8ec 4
RuslanUrya 0:dfe6edabb8ec 5 #pragma once
RuslanUrya 0:dfe6edabb8ec 6
RuslanUrya 0:dfe6edabb8ec 7 #include <cassert>
RuslanUrya 0:dfe6edabb8ec 8 #include <ctime>
RuslanUrya 0:dfe6edabb8ec 9 #include <cstdint>
RuslanUrya 0:dfe6edabb8ec 10
RuslanUrya 0:dfe6edabb8ec 11 #include <unistd.h>
RuslanUrya 0:dfe6edabb8ec 12 #include <sys/time.h>
RuslanUrya 0:dfe6edabb8ec 13 #include <sys/types.h>
RuslanUrya 0:dfe6edabb8ec 14
RuslanUrya 0:dfe6edabb8ec 15 #include <uavcan/driver/system_clock.hpp>
RuslanUrya 0:dfe6edabb8ec 16 #include <uavcan_linux/exception.hpp>
RuslanUrya 0:dfe6edabb8ec 17
RuslanUrya 0:dfe6edabb8ec 18 namespace uavcan_linux
RuslanUrya 0:dfe6edabb8ec 19 {
RuslanUrya 0:dfe6edabb8ec 20 /**
RuslanUrya 0:dfe6edabb8ec 21 * Different adjustment modes can be used for time synchronization
RuslanUrya 0:dfe6edabb8ec 22 */
RuslanUrya 0:dfe6edabb8ec 23 enum class ClockAdjustmentMode
RuslanUrya 0:dfe6edabb8ec 24 {
RuslanUrya 0:dfe6edabb8ec 25 SystemWide, ///< Adjust the clock globally for the whole system; requires root privileges
RuslanUrya 0:dfe6edabb8ec 26 PerDriverPrivate ///< Adjust the clock only for the current driver instance
RuslanUrya 0:dfe6edabb8ec 27 };
RuslanUrya 0:dfe6edabb8ec 28
RuslanUrya 0:dfe6edabb8ec 29 /**
RuslanUrya 0:dfe6edabb8ec 30 * Linux system clock driver.
RuslanUrya 0:dfe6edabb8ec 31 * Requires librt.
RuslanUrya 0:dfe6edabb8ec 32 */
RuslanUrya 0:dfe6edabb8ec 33 class SystemClock : public uavcan::ISystemClock
RuslanUrya 0:dfe6edabb8ec 34 {
RuslanUrya 0:dfe6edabb8ec 35 uavcan::UtcDuration private_adj_;
RuslanUrya 0:dfe6edabb8ec 36 uavcan::UtcDuration gradual_adj_limit_;
RuslanUrya 0:dfe6edabb8ec 37 const ClockAdjustmentMode adj_mode_;
RuslanUrya 0:dfe6edabb8ec 38 std::uint64_t step_adj_cnt_;
RuslanUrya 0:dfe6edabb8ec 39 std::uint64_t gradual_adj_cnt_;
RuslanUrya 0:dfe6edabb8ec 40
RuslanUrya 0:dfe6edabb8ec 41 static constexpr std::int64_t Int1e6 = 1000000;
RuslanUrya 0:dfe6edabb8ec 42 static constexpr std::uint64_t UInt1e6 = 1000000;
RuslanUrya 0:dfe6edabb8ec 43
RuslanUrya 0:dfe6edabb8ec 44 bool performStepAdjustment(const uavcan::UtcDuration adjustment)
RuslanUrya 0:dfe6edabb8ec 45 {
RuslanUrya 0:dfe6edabb8ec 46 step_adj_cnt_++;
RuslanUrya 0:dfe6edabb8ec 47 const std::int64_t usec = adjustment.toUSec();
RuslanUrya 0:dfe6edabb8ec 48 timeval tv;
RuslanUrya 0:dfe6edabb8ec 49 if (gettimeofday(&tv, NULL) != 0)
RuslanUrya 0:dfe6edabb8ec 50 {
RuslanUrya 0:dfe6edabb8ec 51 return false;
RuslanUrya 0:dfe6edabb8ec 52 }
RuslanUrya 0:dfe6edabb8ec 53 tv.tv_sec += usec / Int1e6;
RuslanUrya 0:dfe6edabb8ec 54 tv.tv_usec += usec % Int1e6;
RuslanUrya 0:dfe6edabb8ec 55 return settimeofday(&tv, nullptr) == 0;
RuslanUrya 0:dfe6edabb8ec 56 }
RuslanUrya 0:dfe6edabb8ec 57
RuslanUrya 0:dfe6edabb8ec 58 bool performGradualAdjustment(const uavcan::UtcDuration adjustment)
RuslanUrya 0:dfe6edabb8ec 59 {
RuslanUrya 0:dfe6edabb8ec 60 gradual_adj_cnt_++;
RuslanUrya 0:dfe6edabb8ec 61 const std::int64_t usec = adjustment.toUSec();
RuslanUrya 0:dfe6edabb8ec 62 timeval tv;
RuslanUrya 0:dfe6edabb8ec 63 tv.tv_sec = usec / Int1e6;
RuslanUrya 0:dfe6edabb8ec 64 tv.tv_usec = usec % Int1e6;
RuslanUrya 0:dfe6edabb8ec 65 return adjtime(&tv, nullptr) == 0;
RuslanUrya 0:dfe6edabb8ec 66 }
RuslanUrya 0:dfe6edabb8ec 67
RuslanUrya 0:dfe6edabb8ec 68 public:
RuslanUrya 0:dfe6edabb8ec 69 /**
RuslanUrya 0:dfe6edabb8ec 70 * By default, the clock adjustment mode will be selected automatically - global if root, private otherwise.
RuslanUrya 0:dfe6edabb8ec 71 */
RuslanUrya 0:dfe6edabb8ec 72 explicit SystemClock(ClockAdjustmentMode adj_mode = detectPreferredClockAdjustmentMode())
RuslanUrya 0:dfe6edabb8ec 73 : gradual_adj_limit_(uavcan::UtcDuration::fromMSec(4000))
RuslanUrya 0:dfe6edabb8ec 74 , adj_mode_(adj_mode)
RuslanUrya 0:dfe6edabb8ec 75 , step_adj_cnt_(0)
RuslanUrya 0:dfe6edabb8ec 76 , gradual_adj_cnt_(0)
RuslanUrya 0:dfe6edabb8ec 77 { }
RuslanUrya 0:dfe6edabb8ec 78
RuslanUrya 0:dfe6edabb8ec 79 /**
RuslanUrya 0:dfe6edabb8ec 80 * Returns monotonic timestamp from librt.
RuslanUrya 0:dfe6edabb8ec 81 * @throws uavcan_linux::Exception.
RuslanUrya 0:dfe6edabb8ec 82 */
RuslanUrya 0:dfe6edabb8ec 83 uavcan::MonotonicTime getMonotonic() const override
RuslanUrya 0:dfe6edabb8ec 84 {
RuslanUrya 0:dfe6edabb8ec 85 timespec ts;
RuslanUrya 0:dfe6edabb8ec 86 if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0)
RuslanUrya 0:dfe6edabb8ec 87 {
RuslanUrya 0:dfe6edabb8ec 88 throw Exception("Failed to get monotonic time");
RuslanUrya 0:dfe6edabb8ec 89 }
RuslanUrya 0:dfe6edabb8ec 90 return uavcan::MonotonicTime::fromUSec(std::uint64_t(ts.tv_sec) * UInt1e6 + ts.tv_nsec / 1000);
RuslanUrya 0:dfe6edabb8ec 91 }
RuslanUrya 0:dfe6edabb8ec 92
RuslanUrya 0:dfe6edabb8ec 93 /**
RuslanUrya 0:dfe6edabb8ec 94 * Returns wall time from gettimeofday().
RuslanUrya 0:dfe6edabb8ec 95 * @throws uavcan_linux::Exception.
RuslanUrya 0:dfe6edabb8ec 96 */
RuslanUrya 0:dfe6edabb8ec 97 uavcan::UtcTime getUtc() const override
RuslanUrya 0:dfe6edabb8ec 98 {
RuslanUrya 0:dfe6edabb8ec 99 timeval tv;
RuslanUrya 0:dfe6edabb8ec 100 if (gettimeofday(&tv, NULL) != 0)
RuslanUrya 0:dfe6edabb8ec 101 {
RuslanUrya 0:dfe6edabb8ec 102 throw Exception("Failed to get UTC time");
RuslanUrya 0:dfe6edabb8ec 103 }
RuslanUrya 0:dfe6edabb8ec 104 uavcan::UtcTime utc = uavcan::UtcTime::fromUSec(std::uint64_t(tv.tv_sec) * UInt1e6 + tv.tv_usec);
RuslanUrya 0:dfe6edabb8ec 105 if (adj_mode_ == ClockAdjustmentMode::PerDriverPrivate)
RuslanUrya 0:dfe6edabb8ec 106 {
RuslanUrya 0:dfe6edabb8ec 107 utc += private_adj_;
RuslanUrya 0:dfe6edabb8ec 108 }
RuslanUrya 0:dfe6edabb8ec 109 return utc;
RuslanUrya 0:dfe6edabb8ec 110 }
RuslanUrya 0:dfe6edabb8ec 111
RuslanUrya 0:dfe6edabb8ec 112 /**
RuslanUrya 0:dfe6edabb8ec 113 * Adjusts the wall clock.
RuslanUrya 0:dfe6edabb8ec 114 * Behavior depends on the selected clock adjustment mode - @ref ClockAdjustmentMode.
RuslanUrya 0:dfe6edabb8ec 115 * Clock adjustment mode can be set only once via constructor.
RuslanUrya 0:dfe6edabb8ec 116 *
RuslanUrya 0:dfe6edabb8ec 117 * If the system wide adjustment mode is selected, two ways for performing adjustment exist:
RuslanUrya 0:dfe6edabb8ec 118 * - Gradual adjustment using adjtime(), if the phase error is less than gradual adjustment limit.
RuslanUrya 0:dfe6edabb8ec 119 * - Step adjustment using settimeofday(), if the phase error is above gradual adjustment limit.
RuslanUrya 0:dfe6edabb8ec 120 * The gradual adjustment limit can be configured at any time via the setter method.
RuslanUrya 0:dfe6edabb8ec 121 *
RuslanUrya 0:dfe6edabb8ec 122 * @throws uavcan_linux::Exception.
RuslanUrya 0:dfe6edabb8ec 123 */
RuslanUrya 0:dfe6edabb8ec 124 void adjustUtc(const uavcan::UtcDuration adjustment) override
RuslanUrya 0:dfe6edabb8ec 125 {
RuslanUrya 0:dfe6edabb8ec 126 if (adj_mode_ == ClockAdjustmentMode::PerDriverPrivate)
RuslanUrya 0:dfe6edabb8ec 127 {
RuslanUrya 0:dfe6edabb8ec 128 private_adj_ += adjustment;
RuslanUrya 0:dfe6edabb8ec 129 }
RuslanUrya 0:dfe6edabb8ec 130 else
RuslanUrya 0:dfe6edabb8ec 131 {
RuslanUrya 0:dfe6edabb8ec 132 assert(private_adj_.isZero());
RuslanUrya 0:dfe6edabb8ec 133 assert(!gradual_adj_limit_.isNegative());
RuslanUrya 0:dfe6edabb8ec 134
RuslanUrya 0:dfe6edabb8ec 135 bool success = false;
RuslanUrya 0:dfe6edabb8ec 136 if (adjustment.getAbs() < gradual_adj_limit_)
RuslanUrya 0:dfe6edabb8ec 137 {
RuslanUrya 0:dfe6edabb8ec 138 success = performGradualAdjustment(adjustment);
RuslanUrya 0:dfe6edabb8ec 139 }
RuslanUrya 0:dfe6edabb8ec 140 else
RuslanUrya 0:dfe6edabb8ec 141 {
RuslanUrya 0:dfe6edabb8ec 142 success = performStepAdjustment(adjustment);
RuslanUrya 0:dfe6edabb8ec 143 }
RuslanUrya 0:dfe6edabb8ec 144 if (!success)
RuslanUrya 0:dfe6edabb8ec 145 {
RuslanUrya 0:dfe6edabb8ec 146 throw Exception("Clock adjustment failed");
RuslanUrya 0:dfe6edabb8ec 147 }
RuslanUrya 0:dfe6edabb8ec 148 }
RuslanUrya 0:dfe6edabb8ec 149 }
RuslanUrya 0:dfe6edabb8ec 150
RuslanUrya 0:dfe6edabb8ec 151 /**
RuslanUrya 0:dfe6edabb8ec 152 * Sets the maximum phase error to use adjtime().
RuslanUrya 0:dfe6edabb8ec 153 * If the phase error exceeds this value, settimeofday() will be used instead.
RuslanUrya 0:dfe6edabb8ec 154 */
RuslanUrya 0:dfe6edabb8ec 155 void setGradualAdjustmentLimit(uavcan::UtcDuration limit)
RuslanUrya 0:dfe6edabb8ec 156 {
RuslanUrya 0:dfe6edabb8ec 157 if (limit.isNegative())
RuslanUrya 0:dfe6edabb8ec 158 {
RuslanUrya 0:dfe6edabb8ec 159 limit = uavcan::UtcDuration();
RuslanUrya 0:dfe6edabb8ec 160 }
RuslanUrya 0:dfe6edabb8ec 161 gradual_adj_limit_ = limit;
RuslanUrya 0:dfe6edabb8ec 162 }
RuslanUrya 0:dfe6edabb8ec 163
RuslanUrya 0:dfe6edabb8ec 164 uavcan::UtcDuration getGradualAdjustmentLimit() const { return gradual_adj_limit_; }
RuslanUrya 0:dfe6edabb8ec 165
RuslanUrya 0:dfe6edabb8ec 166 ClockAdjustmentMode getAdjustmentMode() const { return adj_mode_; }
RuslanUrya 0:dfe6edabb8ec 167
RuslanUrya 0:dfe6edabb8ec 168 /**
RuslanUrya 0:dfe6edabb8ec 169 * This is only applicable if the selected clock adjustment mode is private.
RuslanUrya 0:dfe6edabb8ec 170 * In system wide mode this method will always return zero duration.
RuslanUrya 0:dfe6edabb8ec 171 */
RuslanUrya 0:dfe6edabb8ec 172 uavcan::UtcDuration getPrivateAdjustment() const { return private_adj_; }
RuslanUrya 0:dfe6edabb8ec 173
RuslanUrya 0:dfe6edabb8ec 174 /**
RuslanUrya 0:dfe6edabb8ec 175 * Statistics that allows to evaluate clock sync preformance.
RuslanUrya 0:dfe6edabb8ec 176 */
RuslanUrya 0:dfe6edabb8ec 177 std::uint64_t getStepAdjustmentCount() const { return step_adj_cnt_; }
RuslanUrya 0:dfe6edabb8ec 178 std::uint64_t getGradualAdjustmentCount() const { return gradual_adj_cnt_; }
RuslanUrya 0:dfe6edabb8ec 179 std::uint64_t getAdjustmentCount() const
RuslanUrya 0:dfe6edabb8ec 180 {
RuslanUrya 0:dfe6edabb8ec 181 return getStepAdjustmentCount() + getGradualAdjustmentCount();
RuslanUrya 0:dfe6edabb8ec 182 }
RuslanUrya 0:dfe6edabb8ec 183
RuslanUrya 0:dfe6edabb8ec 184 /**
RuslanUrya 0:dfe6edabb8ec 185 * This static method decides what is the optimal clock sync adjustment mode for the current configuration.
RuslanUrya 0:dfe6edabb8ec 186 * It selects system wide mode if the application is running as root; otherwise it prefers
RuslanUrya 0:dfe6edabb8ec 187 * the private adjustment mode because the system wide mode requires root privileges.
RuslanUrya 0:dfe6edabb8ec 188 */
RuslanUrya 0:dfe6edabb8ec 189 static ClockAdjustmentMode detectPreferredClockAdjustmentMode()
RuslanUrya 0:dfe6edabb8ec 190 {
RuslanUrya 0:dfe6edabb8ec 191 const bool godmode = geteuid() == 0;
RuslanUrya 0:dfe6edabb8ec 192 return godmode ? ClockAdjustmentMode::SystemWide : ClockAdjustmentMode::PerDriverPrivate;
RuslanUrya 0:dfe6edabb8ec 193 }
RuslanUrya 0:dfe6edabb8ec 194 };
RuslanUrya 0:dfe6edabb8ec 195
RuslanUrya 0:dfe6edabb8ec 196 }