1 //
2 // Copyright 2019 Ettus Research, a National Instruments Brand
3 //
4 // SPDX-License-Identifier: GPL-3.0-or-later
5 //
6 
7 #include <uhd/exception.hpp>
8 #include <uhd/rfnoc/mb_controller.hpp>
9 #include <uhd/utils/algorithm.hpp>
10 #include <uhd/utils/log.hpp>
11 #include <atomic>
12 #include <chrono>
13 #include <thread>
14 
15 using namespace uhd::rfnoc;
16 using namespace std::chrono_literals;
17 
18 namespace {
19 const std::vector<std::string> SYNCHRONIZABLE_REF_SOURCES = {"gpsdo", "external"};
20 }
21 
synchronize(std::vector<mb_controller::sptr> & mb_controllers,const uhd::time_spec_t & time_spec,const bool quiet)22 bool mb_controller::synchronize(std::vector<mb_controller::sptr>& mb_controllers,
23     const uhd::time_spec_t& time_spec,
24     const bool quiet)
25 {
26     if (mb_controllers.empty()) {
27         return false;
28     }
29     if (mb_controllers.size() == 1) {
30         UHD_LOG_TRACE("MB_CTRL", "Skipping time synchronization of a single USRP.");
31         mb_controllers.at(0)->get_timekeeper(0)->set_time_now(time_spec);
32         return true;
33     }
34     // Verify that all devices share a time reference, and that it is a common
35     // one
36     const std::string time_source = mb_controllers.at(0)->get_time_source();
37     if (!uhd::has(SYNCHRONIZABLE_REF_SOURCES, time_source)) {
38         if (!quiet) {
39             UHD_LOG_WARNING("MB_CTRL",
40                 "The selected time source "
41                     << time_source << " does not allow synchronization between devices.");
42         }
43         return false;
44     }
45     for (auto& mbc : mb_controllers) {
46         if (mbc->get_time_source() != time_source) {
47             if (!quiet) {
48                 UHD_LOG_WARNING("MB_CTRL",
49                     "Motherboards do not share a time source, and thus cannot be "
50                     "synchronized!");
51             }
52             return false;
53         }
54     }
55 
56     // Get a reference to all timekeepers
57     std::vector<timekeeper::sptr> timekeepers;
58     timekeepers.reserve(mb_controllers.size());
59     for (auto& mbc : mb_controllers) {
60         // If we also want to sync other timekeepers, this would be the place to
61         // do that
62         timekeepers.push_back(mbc->get_timekeeper(0));
63     }
64 
65     if (!quiet) {
66         UHD_LOGGER_INFO("MB_CTRL") << "    1) catch time transition at pps edge";
67     }
68     const auto end_time                   = std::chrono::steady_clock::now() + 1100ms;
69     const time_spec_t time_start_last_pps = timekeepers.front()->get_time_last_pps();
70     while (time_start_last_pps == timekeepers.front()->get_time_last_pps()) {
71         if (std::chrono::steady_clock::now() > end_time) {
72             // This is always bad, and we'll throw regardless of quiet
73             throw uhd::runtime_error("Board 0 may not be getting a PPS signal!\n"
74                                      "No PPS detected within the time interval.\n"
75                                      "See the application notes for your device.\n");
76         }
77         std::this_thread::sleep_for(1ms);
78     }
79 
80     if (!quiet) {
81         UHD_LOGGER_INFO("MB_CTRL") << "    2) set times next pps (synchronously)";
82     }
83 
84     for (auto& timekeeper : timekeepers) {
85         timekeeper->set_time_next_pps(time_spec);
86     }
87     std::this_thread::sleep_for(1s);
88 
89     // verify that the time registers are read to be within a few RTT
90     size_t m = 0;
91     for (auto& timekeeper : timekeepers) {
92         time_spec_t time_0 = timekeepers.front()->get_time_now();
93         time_spec_t time_i = timekeeper->get_time_now();
94         // 10 ms: greater than RTT but not too big
95         constexpr double MAX_DEVIATION = 0.01;
96         if (time_i < time_0 or (time_i - time_0) > time_spec_t(MAX_DEVIATION)) {
97             if (!quiet) {
98                 UHD_LOGGER_WARNING("MULTI_USRP")
99                     << boost::format(
100                            "Detected time deviation between board %d and board 0.\n"
101                            "Board 0 time is %f seconds.\n"
102                            "Board %d time is %f seconds.\n")
103                            % m % time_0.get_real_secs() % m % time_i.get_real_secs();
104             }
105             return false;
106         }
107         m++;
108     }
109     return true;
110 }
111 
112 /******************************************************************************
113  * Timekeeper API
114  *****************************************************************************/
timekeeper()115 mb_controller::timekeeper::timekeeper()
116 {
117     // nop
118 }
119 
get_time_now()120 uhd::time_spec_t mb_controller::timekeeper::get_time_now()
121 {
122     return time_spec_t::from_ticks(get_ticks_now(), _tick_rate);
123 }
124 
get_time_last_pps()125 uhd::time_spec_t mb_controller::timekeeper::get_time_last_pps()
126 {
127     return time_spec_t::from_ticks(get_ticks_last_pps(), _tick_rate);
128 }
129 
set_time_now(const uhd::time_spec_t & time)130 void mb_controller::timekeeper::set_time_now(const uhd::time_spec_t& time)
131 {
132     set_ticks_now(time.to_ticks(_tick_rate));
133 }
134 
set_time_next_pps(const uhd::time_spec_t & time)135 void mb_controller::timekeeper::set_time_next_pps(const uhd::time_spec_t& time)
136 {
137     set_ticks_next_pps(time.to_ticks(_tick_rate));
138 }
139 
set_tick_rate(const double tick_rate)140 void mb_controller::timekeeper::set_tick_rate(const double tick_rate)
141 {
142     if (_tick_rate == tick_rate) {
143         return;
144     }
145     _tick_rate = tick_rate;
146 
147     // The period is the inverse of the tick rate, normalized by nanoseconds,
148     // and represented as Q32 (e.g., period == 1ns means period_ns == 1<<32)
149     const uint64_t period_ns =
150         static_cast<uint64_t>(1e9 / tick_rate * (uint64_t(1) << 32));
151     set_period(period_ns);
152 }
153 
get_num_timekeepers() const154 size_t mb_controller::get_num_timekeepers() const
155 {
156     return _timekeepers.size();
157 }
158 
get_timekeeper(const size_t tk_idx) const159 mb_controller::timekeeper::sptr mb_controller::get_timekeeper(const size_t tk_idx) const
160 {
161     if (!_timekeepers.count(tk_idx)) {
162         throw uhd::index_error(
163             std::string("No timekeeper with index ") + std::to_string(tk_idx));
164     }
165 
166     return _timekeepers.at(tk_idx);
167 }
168 
register_timekeeper(const size_t idx,timekeeper::sptr tk)169 void mb_controller::register_timekeeper(const size_t idx, timekeeper::sptr tk)
170 {
171     _timekeepers.emplace(idx, std::move(tk));
172 }
173 
get_gpio_banks() const174 std::vector<std::string> mb_controller::get_gpio_banks() const
175 {
176     return {};
177 }
178 
get_gpio_srcs(const std::string &) const179 std::vector<std::string> mb_controller::get_gpio_srcs(const std::string&) const
180 {
181     throw uhd::not_implemented_error(
182         "get_gpio_srcs() not supported on this motherboard!");
183 }
184 
get_gpio_src(const std::string &)185 std::vector<std::string> mb_controller::get_gpio_src(const std::string&)
186 {
187     throw uhd::not_implemented_error("get_gpio_src() not supported on this motherboard!");
188 }
189 
set_gpio_src(const std::string &,const std::vector<std::string> &)190 void mb_controller::set_gpio_src(const std::string&, const std::vector<std::string>&)
191 {
192     throw uhd::not_implemented_error("set_gpio_src() not supported on this motherboard!");
193 }
194