1 //
2 // Copyright 2012-2015 Ettus Research LLC
3 // Copyright 2018 Ettus Research, a National Instruments Company
4 //
5 // SPDX-License-Identifier: GPL-3.0-or-later
6 //
7 
8 #include "b200_impl.hpp"
9 #include "../../transport/libusb1_base.hpp"
10 #include "b200_regs.hpp"
11 #include <uhd/config.hpp>
12 #include <uhd/exception.hpp>
13 #include <uhd/transport/usb_control.hpp>
14 #include <uhd/usrp/dboard_eeprom.hpp>
15 #include <uhd/utils/cast.hpp>
16 #include <uhd/utils/log.hpp>
17 #include <uhd/utils/paths.hpp>
18 #include <uhd/utils/safe_call.hpp>
19 #include <uhd/utils/static.hpp>
20 #include <uhd/cal/database.hpp>
21 #include <boost/filesystem.hpp>
22 #include <boost/format.hpp>
23 #include <boost/functional/hash.hpp>
24 #include <boost/lexical_cast.hpp>
25 #include <chrono>
26 #include <cmath>
27 #include <cstdio>
28 #include <ctime>
29 #include <functional>
30 #include <memory>
31 
32 using namespace uhd;
33 using namespace uhd::usrp;
34 using namespace uhd::usrp::gpio_atr;
35 using namespace uhd::transport;
36 
37 namespace {
38 constexpr int64_t REENUMERATION_TIMEOUT_MS = 3000;
39 }
40 
41 // B200 + B210:
42 class b200_ad9361_client_t : public ad9361_params
43 {
44 public:
~b200_ad9361_client_t()45     ~b200_ad9361_client_t() {}
get_band_edge(frequency_band_t band)46     double get_band_edge(frequency_band_t band)
47     {
48         switch (band) {
49             case AD9361_RX_BAND0:
50                 return 2.2e9; // Port C
51             case AD9361_RX_BAND1:
52                 return 4.0e9; // Port B
53             case AD9361_TX_BAND0:
54                 return 2.5e9; // Port B
55             default:
56                 return 0;
57         }
58     }
get_clocking_mode()59     clocking_mode_t get_clocking_mode()
60     {
61         return clocking_mode_t::AD9361_XTAL_N_CLK_PATH;
62     }
get_digital_interface_mode()63     digital_interface_mode_t get_digital_interface_mode()
64     {
65         return AD9361_DDR_FDD_LVCMOS;
66     }
get_digital_interface_timing()67     digital_interface_delays_t get_digital_interface_timing()
68     {
69         digital_interface_delays_t delays;
70         delays.rx_clk_delay  = 0;
71         delays.rx_data_delay = 0xF;
72         delays.tx_clk_delay  = 0;
73         delays.tx_data_delay = 0xF;
74         return delays;
75     }
76 };
77 
78 // B205
79 class b2xxmini_ad9361_client_t : public ad9361_params
80 {
81 public:
~b2xxmini_ad9361_client_t()82     ~b2xxmini_ad9361_client_t() {}
get_band_edge(frequency_band_t band)83     double get_band_edge(frequency_band_t band)
84     {
85         switch (band) {
86             case AD9361_RX_BAND0:
87                 return 0; // Set these all to
88             case AD9361_RX_BAND1:
89                 return 0; // zero, so RF port A
90             case AD9361_TX_BAND0:
91                 return 0; // is used all the time
92             default:
93                 return 0; // On both Rx and Tx
94         }
95     }
get_clocking_mode()96     clocking_mode_t get_clocking_mode()
97     {
98         return clocking_mode_t::AD9361_XTAL_N_CLK_PATH;
99     }
get_digital_interface_mode()100     digital_interface_mode_t get_digital_interface_mode()
101     {
102         return AD9361_DDR_FDD_LVCMOS;
103     }
get_digital_interface_timing()104     digital_interface_delays_t get_digital_interface_timing()
105     {
106         digital_interface_delays_t delays;
107         delays.rx_clk_delay  = 0;
108         delays.rx_data_delay = 0xF;
109         delays.tx_clk_delay  = 0;
110         delays.tx_data_delay = 0xF;
111         return delays;
112     }
113 };
114 
115 /***********************************************************************
116  * Helpers
117  **********************************************************************/
check_option_valid(const std::string & name,const std::vector<std::string> & valid_options,const std::string & option)118 std::string check_option_valid(const std::string& name,
119     const std::vector<std::string>& valid_options,
120     const std::string& option)
121 {
122     if (std::find(valid_options.begin(), valid_options.end(), option)
123         == valid_options.end()) {
124         throw uhd::runtime_error(
125             str(boost::format("Invalid option chosen for: %s") % name));
126     }
127 
128     return option;
129 }
130 
131 /***********************************************************************
132  * Discovery
133  **********************************************************************/
134 //! Look up the type of B-Series device we're currently running.
135 //  Throws a uhd::runtime_error if the USB PID and the product ID stored
136 //  in the MB EEPROM are invalid,
get_b200_product(const usb_device_handle::sptr & handle,const mboard_eeprom_t & mb_eeprom)137 b200_product_t get_b200_product(
138     const usb_device_handle::sptr& handle, const mboard_eeprom_t& mb_eeprom)
139 {
140     // Try USB PID first
141     uint16_t product_id = handle->get_product_id();
142     if (B2XX_PID_TO_PRODUCT.has_key(product_id))
143         return B2XX_PID_TO_PRODUCT[product_id];
144 
145     // Try EEPROM product ID code second
146     if (mb_eeprom["product"].empty()) {
147         throw uhd::runtime_error("B200: Missing product ID on EEPROM.");
148     }
149     product_id = boost::lexical_cast<uint16_t>(mb_eeprom["product"]);
150     if (not B2XX_PRODUCT_ID.has_key(product_id)) {
151         throw uhd::runtime_error(
152             str(boost::format("B200 unknown product code: 0x%04x") % product_id));
153     }
154     return B2XX_PRODUCT_ID[product_id];
155 }
156 
get_b200_device_handles(const device_addr_t & hint)157 std::vector<usb_device_handle::sptr> get_b200_device_handles(const device_addr_t& hint)
158 {
159     std::vector<usb_device_handle::vid_pid_pair_t> vid_pid_pair_list;
160 
161     if (hint.has_key("vid") && hint.has_key("pid") && hint.has_key("type")
162         && hint["type"] == "b200") {
163         vid_pid_pair_list.push_back(usb_device_handle::vid_pid_pair_t(
164             uhd::cast::hexstr_cast<uint16_t>(hint.get("vid")),
165             uhd::cast::hexstr_cast<uint16_t>(hint.get("pid"))));
166     } else {
167         vid_pid_pair_list = b200_vid_pid_pairs;
168     }
169 
170     // find the usrps and load firmware
171     return usb_device_handle::get_device_list(vid_pid_pair_list);
172 }
173 
b200_find(const device_addr_t & hint)174 static device_addrs_t b200_find(const device_addr_t& hint)
175 {
176     device_addrs_t b200_addrs;
177 
178     // return an empty list of addresses when type is set to non-b200
179     if (hint.has_key("type") and hint["type"] != "b200")
180         return b200_addrs;
181 
182     // Return an empty list of addresses when an address or resource is specified,
183     // since an address and resource is intended for a different, non-USB, device.
184     for (device_addr_t hint_i : separate_device_addr(hint)) {
185         if (hint_i.has_key("addr") || hint_i.has_key("resource"))
186             return b200_addrs;
187     }
188 
189     // Important note:
190     // The get device list calls are nested inside the for loop.
191     // This allows the usb guts to decontruct when not in use,
192     // so that re-enumeration after fw load can occur successfully.
193     // This requirement is a courtesy of libusb1.0 on windows.
194     size_t found = 0;
195     for (usb_device_handle::sptr handle : get_b200_device_handles(hint)) {
196         // extract the firmware path for the b200
197         std::string b200_fw_image;
198         try {
199             b200_fw_image = hint.get("fw", B200_FW_FILE_NAME);
200             b200_fw_image =
201                 uhd::find_image_path(b200_fw_image, STR(UHD_IMAGES_DIR)); // FIXME
202         } catch (uhd::exception& e) {
203             UHD_LOGGER_WARNING("B200") << e.what();
204             return b200_addrs;
205         }
206         UHD_LOGGER_DEBUG("B200") << "the firmware image: " << b200_fw_image;
207 
208         usb_control::sptr control;
209         try {
210             control = usb_control::make(handle, 0);
211         } catch (const uhd::exception&) {
212             continue;
213         } // ignore claimed
214 
215         // check if fw was already loaded
216         if (!(handle->firmware_loaded())) {
217             b200_iface::make(control)->load_firmware(b200_fw_image);
218         }
219 
220         found++;
221     }
222 
223     const auto timeout_time = std::chrono::steady_clock::now()
224                               + std::chrono::milliseconds(REENUMERATION_TIMEOUT_MS);
225     // search for the device until found or timeout
226     while (std::chrono::steady_clock::now() < timeout_time and b200_addrs.empty()
227            and found != 0) {
228         for (usb_device_handle::sptr handle : get_b200_device_handles(hint)) {
229             usb_control::sptr control;
230             try {
231                 control = usb_control::make(handle, 0);
232             } catch (const uhd::exception&) {
233                 continue;
234             } // ignore claimed
235 
236             b200_iface::sptr iface          = b200_iface::make(control);
237             const mboard_eeprom_t mb_eeprom = b200_impl::get_mb_eeprom(iface);
238 
239             device_addr_t new_addr;
240             new_addr["type"]   = "b200";
241             new_addr["name"]   = mb_eeprom["name"];
242             new_addr["serial"] = handle->get_serial();
243             try {
244                 // Turn the 16-Bit product ID into a string representation
245                 new_addr["product"] = B2XX_STR_NAMES[get_b200_product(handle, mb_eeprom)];
246             } catch (const uhd::runtime_error&) {
247                 // No problem if this fails -- this is just device discovery, after all.
248                 new_addr["product"] = "B2??";
249             }
250 
251             // this is a found b200 when the hint serial and name match or blank
252             if ((not hint.has_key("name") or hint["name"] == new_addr["name"])
253                 and (not hint.has_key("serial")
254                         or hint["serial"] == new_addr["serial"])) {
255                 b200_addrs.push_back(new_addr);
256             }
257         }
258     }
259 
260     return b200_addrs;
261 }
262 
263 /***********************************************************************
264  * Make
265  **********************************************************************/
b200_make(const device_addr_t & device_addr)266 static device::sptr b200_make(const device_addr_t& device_addr)
267 {
268     uhd::transport::usb_device_handle::sptr handle;
269 
270     // We try twice, because the first time, the link might be in a bad state
271     // and we might need to reset the link, but if that didn't help, trying
272     // a third time is pointless.
273     try {
274         return device::sptr(new b200_impl(device_addr, handle));
275     } catch (const uhd::usb_error&) {
276         UHD_LOGGER_INFO("B200") << "Detected bad USB state; resetting.";
277         libusb::device_handle::sptr dev_handle(libusb::device_handle::get_cached_handle(
278             std::static_pointer_cast<libusb::special_handle>(handle)->get_device()));
279         dev_handle->clear_endpoints(
280             B200_USB_CTRL_RECV_ENDPOINT, B200_USB_CTRL_SEND_ENDPOINT);
281         dev_handle->clear_endpoints(
282             B200_USB_DATA_RECV_ENDPOINT, B200_USB_DATA_SEND_ENDPOINT);
283         dev_handle->reset_device();
284     }
285 
286     return device::sptr(new b200_impl(device_addr, handle));
287 }
288 
UHD_STATIC_BLOCK(register_b200_device)289 UHD_STATIC_BLOCK(register_b200_device)
290 {
291     device::register_device(&b200_find, &b200_make, device::USRP);
292 }
293 
294 /***********************************************************************
295  * Structors
296  **********************************************************************/
b200_impl(const uhd::device_addr_t & device_addr,usb_device_handle::sptr & handle)297 b200_impl::b200_impl(
298     const uhd::device_addr_t& device_addr, usb_device_handle::sptr& handle)
299     : _product(B200)
300     , // Some safe value
301     _revision(0)
302     , _enable_user_regs(device_addr.has_key("enable_user_regs"))
303     , _time_source(UNKNOWN)
304     , _tick_rate(0.0) // Forces a clock initialization at startup
305 {
306     _tree                 = property_tree::make();
307     _type                 = device::USRP;
308     const fs_path mb_path = "/mboards/0";
309 
310     // try to match the given device address with something on the USB bus
311     uint16_t vid       = B200_VENDOR_ID;
312     uint16_t pid       = B200_PRODUCT_ID;
313     bool specified_vid = false;
314     bool specified_pid = false;
315 
316     if (device_addr.has_key("vid")) {
317         vid           = uhd::cast::hexstr_cast<uint16_t>(device_addr.get("vid"));
318         specified_vid = true;
319     }
320 
321     if (device_addr.has_key("pid")) {
322         pid           = uhd::cast::hexstr_cast<uint16_t>(device_addr.get("pid"));
323         specified_pid = true;
324     }
325 
326     std::vector<usb_device_handle::vid_pid_pair_t>
327         vid_pid_pair_list; // search list for devices.
328 
329     // Search only for specified VID and PID if both specified
330     if (specified_vid && specified_pid) {
331         vid_pid_pair_list.push_back(usb_device_handle::vid_pid_pair_t(vid, pid));
332     }
333     // Search for all supported PIDs limited to specified VID if only VID specified
334     else if (specified_vid) {
335         vid_pid_pair_list.push_back(
336             usb_device_handle::vid_pid_pair_t(vid, B200_PRODUCT_ID));
337         vid_pid_pair_list.push_back(
338             usb_device_handle::vid_pid_pair_t(vid, B200MINI_PRODUCT_ID));
339         vid_pid_pair_list.push_back(
340             usb_device_handle::vid_pid_pair_t(vid, B205MINI_PRODUCT_ID));
341         vid_pid_pair_list.push_back(
342             usb_device_handle::vid_pid_pair_t(vid, B200_PRODUCT_NI_ID));
343         vid_pid_pair_list.push_back(
344             usb_device_handle::vid_pid_pair_t(vid, B210_PRODUCT_NI_ID));
345     }
346     // Search for all supported VIDs limited to specified PID if only PID specified
347     else if (specified_pid) {
348         vid_pid_pair_list.push_back(
349             usb_device_handle::vid_pid_pair_t(B200_VENDOR_ID, pid));
350         vid_pid_pair_list.push_back(
351             usb_device_handle::vid_pid_pair_t(B200_VENDOR_NI_ID, pid));
352     }
353     // Search for all supported devices if neither VID nor PID specified
354     else {
355         vid_pid_pair_list.push_back(
356             usb_device_handle::vid_pid_pair_t(B200_VENDOR_ID, B200_PRODUCT_ID));
357         vid_pid_pair_list.push_back(
358             usb_device_handle::vid_pid_pair_t(B200_VENDOR_ID, B200MINI_PRODUCT_ID));
359         vid_pid_pair_list.push_back(
360             usb_device_handle::vid_pid_pair_t(B200_VENDOR_ID, B205MINI_PRODUCT_ID));
361         vid_pid_pair_list.push_back(
362             usb_device_handle::vid_pid_pair_t(B200_VENDOR_NI_ID, B200_PRODUCT_NI_ID));
363         vid_pid_pair_list.push_back(
364             usb_device_handle::vid_pid_pair_t(B200_VENDOR_NI_ID, B210_PRODUCT_NI_ID));
365     }
366 
367     std::vector<usb_device_handle::sptr> device_list =
368         usb_device_handle::get_device_list(vid_pid_pair_list);
369 
370     // locate the matching handle in the device list
371     for (usb_device_handle::sptr dev_handle : device_list) {
372         try {
373             if (dev_handle->get_serial() == device_addr["serial"]) {
374                 handle = dev_handle;
375                 break;
376             }
377         } catch (const uhd::exception&) {
378             continue;
379         }
380     }
381     UHD_ASSERT_THROW(handle.get() != NULL); // better be found
382 
383     // create control objects
384     usb_control::sptr control = usb_control::make(handle, 0);
385     _iface                    = b200_iface::make(control);
386     this->check_fw_compat(); // check after making
387 
388     ////////////////////////////////////////////////////////////////////
389     // setup the mboard eeprom
390     ////////////////////////////////////////////////////////////////////
391     const mboard_eeprom_t mb_eeprom = get_mb_eeprom(_iface);
392     _tree->create<mboard_eeprom_t>(mb_path / "eeprom")
393         .set(mb_eeprom)
394         .add_coerced_subscriber(
395             std::bind(&b200_impl::set_mb_eeprom, this, std::placeholders::_1));
396 
397     ////////////////////////////////////////////////////////////////////
398     // Identify the device type
399     ////////////////////////////////////////////////////////////////////
400     std::string default_file_name;
401     std::string product_name;
402     try {
403         // This will throw if the product ID is invalid:
404         _product          = get_b200_product(handle, mb_eeprom);
405         default_file_name = B2XX_FPGA_FILE_NAME.get(_product);
406         product_name      = B2XX_STR_NAMES.get(_product);
407     } catch (const uhd::runtime_error& e) {
408         // The only reason we may let this pass is if the user specified
409         // the FPGA file name:
410         if (not device_addr.has_key("fpga")) {
411             throw e;
412         }
413         // In this case, we must provide a default product name:
414         product_name = "B200?";
415     }
416     if (not mb_eeprom["revision"].empty()) {
417         _revision = boost::lexical_cast<size_t>(mb_eeprom["revision"]);
418     }
419 
420     UHD_LOGGER_INFO("B200") << "Detected Device: " << B2XX_STR_NAMES[_product];
421 
422     _gpsdo_capable = (not(_product == B200MINI or _product == B205MINI));
423 
424     ////////////////////////////////////////////////////////////////////
425     // Set up frontend mapping
426     ////////////////////////////////////////////////////////////////////
427     // Explanation: The AD9361 has 2 frontends, FE1 and FE2.
428     // On the B210 FE1 maps to the B-side (or radio 1), and FE2 maps
429     // to the A-side (or radio 0). So, logically, the radios are swapped
430     // between the host side and the AD9361-side.
431     // B200 is more complicated: On Revs <= 4, the A-side is connected,
432     // which means FE2 is used (like B210). On Revs >= 5, the left side
433     // ("B-side") is connected, because these revs use an AD9364, which
434     // does not have an FE2, so we don't swap FEs.
435 
436     // Swapped setup:
437     _fe1                 = 1;
438     _fe2                 = 0;
439     _gpio_state.swap_atr = 1;
440     // Unswapped setup:
441     if (_product == B200MINI or _product == B205MINI
442         or (_product == B200 and _revision >= 5)) {
443         _fe1                 = 0; // map radio0 to FE1
444         _fe2                 = 1; // map radio1 to FE2
445         _gpio_state.swap_atr = 0; // ATRs for radio0 are mapped to FE1
446     }
447 
448     ////////////////////////////////////////////////////////////////////
449     // Load the FPGA image, then reset GPIF
450     ////////////////////////////////////////////////////////////////////
451     // extract the FPGA path for the B200
452     std::string b200_fpga_image = find_image_path(
453         device_addr.has_key("fpga") ? device_addr["fpga"] : default_file_name);
454 
455     uint32_t status = _iface->load_fpga(b200_fpga_image);
456 
457     if (status != 0) {
458         throw uhd::runtime_error(str(boost::format("fx3 is in state %1%") % status));
459     }
460 
461     _iface->reset_gpif();
462 
463     ////////////////////////////////////////////////////////////////////
464     // Create control transport
465     ////////////////////////////////////////////////////////////////////
466     uint8_t usb_speed = _iface->get_usb_speed();
467     UHD_LOGGER_INFO("B200") << "Operating over USB " << (int)usb_speed << ".";
468     const std::string min_frame_size = (usb_speed == 3) ? "1024" : "512";
469 
470     device_addr_t ctrl_xport_args;
471     ctrl_xport_args["recv_frame_size"] = min_frame_size;
472     ctrl_xport_args["num_recv_frames"] = "16";
473     ctrl_xport_args["send_frame_size"] = min_frame_size;
474     ctrl_xport_args["num_send_frames"] = "16";
475 
476     // This may throw a uhd::usb_error, which will be caught by b200_make().
477     _ctrl_transport = usb_zero_copy::make(handle,
478         B200_USB_CTRL_RECV_INTERFACE,
479         B200_USB_CTRL_RECV_ENDPOINT, // interface, endpoint
480         B200_USB_CTRL_SEND_INTERFACE,
481         B200_USB_CTRL_SEND_ENDPOINT, // interface, endpoint
482         ctrl_xport_args);
483     while (_ctrl_transport->get_recv_buff(0.0)) {
484     } // flush ctrl xport
485     _tree->create<double>(mb_path / "link_max_rate")
486         .set((usb_speed == 3) ? B200_MAX_RATE_USB3 : B200_MAX_RATE_USB2);
487     _tree->create<int>(mb_path / "usb_version").set(usb_speed);
488 
489     ////////////////////////////////////////////////////////////////////
490     // Async task structure
491     ////////////////////////////////////////////////////////////////////
492     _async_task_data.reset(new AsyncTaskData());
493     _async_task_data->async_md.reset(new async_md_type(1000 /*messages deep*/));
494     if (_gpsdo_capable) {
495         _async_task_data->gpsdo_uart =
496             b200_uart::make(_ctrl_transport, B200_TX_GPS_UART_SID);
497     }
498     _async_task = uhd::msg_task::make(std::bind(
499         &b200_impl::handle_async_task, this, _ctrl_transport, _async_task_data));
500 
501     ////////////////////////////////////////////////////////////////////
502     // Local control endpoint
503     ////////////////////////////////////////////////////////////////////
504     _local_ctrl = radio_ctrl_core_3000::make(false /*lilE*/,
505         _ctrl_transport,
506         zero_copy_if::sptr() /*null*/,
507         B200_LOCAL_CTRL_SID);
508     _local_ctrl->hold_task(_async_task);
509     _async_task_data->local_ctrl = _local_ctrl; // weak
510     this->check_fpga_compat();
511 
512     /* Initialize the GPIOs, set the default bandsels to the lower range. Note
513      * that calling update_bandsel calls update_gpio_state(). */
514     update_bandsel("RX", 800e6);
515     update_bandsel("TX", 850e6);
516 
517     ////////////////////////////////////////////////////////////////////
518     // Create the GPSDO control
519     ////////////////////////////////////////////////////////////////////
520     if (_gpsdo_capable) {
521         if ((_local_ctrl->peek32(RB32_CORE_STATUS) & 0xff) != B200_GPSDO_ST_NONE) {
522             UHD_LOGGER_INFO("B200") << "Detecting internal GPSDO.... " << std::flush;
523             try {
524                 _gps = gps_ctrl::make(_async_task_data->gpsdo_uart);
525             } catch (std::exception& e) {
526                 UHD_LOGGER_ERROR("B200")
527                     << "An error occurred making GPSDO control: " << e.what();
528             }
529             if (_gps and _gps->gps_detected()) {
530                 for (const std::string& name : _gps->get_sensors()) {
531                     _tree->create<sensor_value_t>(mb_path / "sensors" / name)
532                         .set_publisher(std::bind(&gps_ctrl::get_sensor, _gps, name));
533                 }
534             } else {
535                 _local_ctrl->poke32(TOREG(SR_CORE_GPSDO_ST), B200_GPSDO_ST_NONE);
536             }
537         }
538     }
539 
540     ////////////////////////////////////////////////////////////////////
541     // Initialize the properties tree
542     ////////////////////////////////////////////////////////////////////
543     _tree->create<std::string>("/name").set("B-Series Device");
544     _tree->create<std::string>(mb_path / "name").set(product_name);
545     _tree->create<std::string>(mb_path / "codename")
546         .set((_product == B200MINI or _product == B205MINI) ? "Pixie" : "Sasquatch");
547 
548     ////////////////////////////////////////////////////////////////////
549     // Create data transport
550     // This happens after FPGA ctrl instantiated so any junk that might
551     // be in the FPGAs buffers doesn't get pulled into the transport
552     // before being cleared.
553     ////////////////////////////////////////////////////////////////////
554     device_addr_t data_xport_args;
555     const int max_transfer = usb_speed == 3 ? 1024 : 512;
556     int recv_frame_size =
557         device_addr.cast<int>("recv_frame_size", B200_USB_DATA_DEFAULT_FRAME_SIZE);
558     // Check that recv_frame_size limits.
559     if (recv_frame_size < B200_USB_DATA_MIN_RECV_FRAME_SIZE) {
560         UHD_LOGGER_WARNING("B200") << "Requested recv_frame_size of " << recv_frame_size
561                                    << " is too small. It will be set to "
562                                    << B200_USB_DATA_MIN_RECV_FRAME_SIZE << ".";
563         recv_frame_size = B200_USB_DATA_MIN_RECV_FRAME_SIZE;
564     } else if (recv_frame_size > B200_USB_DATA_MAX_RECV_FRAME_SIZE) {
565         UHD_LOGGER_WARNING("B200") << "Requested recv_frame_size of " << recv_frame_size
566                                    << " is too large. It will be set to "
567                                    << B200_USB_DATA_MAX_RECV_FRAME_SIZE << ".";
568         recv_frame_size = B200_USB_DATA_MAX_RECV_FRAME_SIZE;
569     } else if (recv_frame_size % max_transfer == 0 or recv_frame_size % 8 != 0) {
570         // The Cypress FX3 does not properly handle recv_frame_sizes that are
571         // aligned to the maximum transfer size and the FPGA code requires the
572         // data to be aligned to 8 byte words.  The code below coerces the
573         // recv_frame_size to a value that is a multiple of 8 bytes, not
574         // a multiple of the maximum transfer size, and aligned to 24 bytes
575         // to support full 8 byte word alignment for sc8, sc12, and sc16 data
576         // types.
577 
578         // Align to 8 byte words
579         recv_frame_size += 8 - (recv_frame_size % 8);
580         if (recv_frame_size % max_transfer == 0) {
581             recv_frame_size = (((recv_frame_size - 16) / 24) * 24) + 16;
582         }
583         UHD_LOGGER_WARNING("B200")
584             << "The recv_frame_size must be a multiple of 8 bytes and not a multiple of "
585             << max_transfer << " bytes.  Requested recv_frame_size of "
586             << device_addr["recv_frame_size"] << " coerced to " << recv_frame_size << ".";
587     }
588 
589     data_xport_args["recv_frame_size"] = std::to_string(recv_frame_size);
590     data_xport_args["num_recv_frames"] = device_addr.get("num_recv_frames", "16");
591     data_xport_args["send_frame_size"] = device_addr.get(
592         "send_frame_size", std::to_string(B200_USB_DATA_DEFAULT_FRAME_SIZE));
593     data_xport_args["num_send_frames"] = device_addr.get("num_send_frames", "16");
594 
595     // This may throw a uhd::usb_error, which will be caught by b200_make().
596     _data_transport = usb_zero_copy::make(handle, // identifier
597         B200_USB_DATA_RECV_INTERFACE,
598         B200_USB_DATA_RECV_ENDPOINT, // interface, endpoint
599         B200_USB_DATA_SEND_INTERFACE,
600         B200_USB_DATA_SEND_ENDPOINT, // interface, endpoint
601         data_xport_args // param hints
602     );
603     while (_data_transport->get_recv_buff(0.0)) {
604     } // flush ctrl xport
605     _demux = recv_packet_demuxer_3000::make(_data_transport);
606 
607     ////////////////////////////////////////////////////////////////////
608     // create time and clock control objects
609     ////////////////////////////////////////////////////////////////////
610     _spi_iface = b200_local_spi_core::make(_local_ctrl);
611     if (not(_product == B200MINI or _product == B205MINI)) {
612         _adf4001_iface = std::make_shared<b200_ref_pll_ctrl>(_spi_iface);
613     }
614 
615     ////////////////////////////////////////////////////////////////////
616     // Init codec - turns on clocks
617     ////////////////////////////////////////////////////////////////////
618     UHD_LOGGER_INFO("B200") << "Initialize CODEC control...";
619     reset_codec();
620     ad9361_params::sptr client_settings;
621     if (_product == B200MINI or _product == B205MINI) {
622         client_settings = std::make_shared<b2xxmini_ad9361_client_t>();
623     } else {
624         client_settings = std::make_shared<b200_ad9361_client_t>();
625     }
626     _codec_ctrl = ad9361_ctrl::make_spi(client_settings, _spi_iface, AD9361_SLAVENO);
627 
628     ////////////////////////////////////////////////////////////////////
629     // create codec control objects
630     ////////////////////////////////////////////////////////////////////
631     {
632         const fs_path codec_path = mb_path / ("rx_codecs") / "A";
633         _tree->create<std::string>(codec_path / "name")
634             .set(product_name + " RX dual ADC");
635         _tree->create<int>(codec_path / "gains"); // empty cuz gains are in frontend
636     }
637     {
638         const fs_path codec_path = mb_path / ("tx_codecs") / "A";
639         _tree->create<std::string>(codec_path / "name")
640             .set(product_name + " TX dual DAC");
641         _tree->create<int>(codec_path / "gains"); // empty cuz gains are in frontend
642     }
643 
644     ////////////////////////////////////////////////////////////////////
645     // create clock control objects
646     ////////////////////////////////////////////////////////////////////
647     _tree->create<double>(mb_path / "tick_rate")
648         .set_coercer(std::bind(&b200_impl::set_tick_rate, this, std::placeholders::_1))
649         .set_publisher(std::bind(&b200_impl::get_tick_rate, this))
650         .add_coerced_subscriber(
651             std::bind(&b200_impl::update_tick_rate, this, std::placeholders::_1));
652     _tree->create<meta_range_t>(mb_path / "tick_rate/range").set_publisher([this]() {
653         return this->_codec_ctrl->get_clock_rate_range();
654     });
655     _tree->create<time_spec_t>(mb_path / "time" / "cmd");
656     _tree->create<bool>(mb_path / "auto_tick_rate").set(false);
657 
658     ////////////////////////////////////////////////////////////////////
659     // and do the misc mboard sensors
660     ////////////////////////////////////////////////////////////////////
661     _tree->create<sensor_value_t>(mb_path / "sensors" / "ref_locked")
662         .set_publisher(std::bind(&b200_impl::get_ref_locked, this));
663 
664     ////////////////////////////////////////////////////////////////////
665     // create frontend mapping
666     ////////////////////////////////////////////////////////////////////
667     std::vector<size_t> default_map(2, 0);
668     default_map[1] = 1; // Set this to A->0 B->1 even if there's only A
669     _tree->create<std::vector<size_t>>(mb_path / "rx_chan_dsp_mapping").set(default_map);
670     _tree->create<std::vector<size_t>>(mb_path / "tx_chan_dsp_mapping").set(default_map);
671     _tree->create<subdev_spec_t>(mb_path / "rx_subdev_spec")
672         .set_coercer(
673             std::bind(&b200_impl::coerce_subdev_spec, this, std::placeholders::_1))
674         .set(subdev_spec_t())
675         .add_coerced_subscriber(
676             std::bind(&b200_impl::update_subdev_spec, this, "rx", std::placeholders::_1));
677     _tree->create<subdev_spec_t>(mb_path / "tx_subdev_spec")
678         .set_coercer(
679             std::bind(&b200_impl::coerce_subdev_spec, this, std::placeholders::_1))
680         .set(subdev_spec_t())
681         .add_coerced_subscriber(
682             std::bind(&b200_impl::update_subdev_spec, this, "tx", std::placeholders::_1));
683 
684     ////////////////////////////////////////////////////////////////////
685     // setup radio control
686     ////////////////////////////////////////////////////////////////////
687     UHD_LOGGER_INFO("B200") << "Initialize Radio control...";
688     const size_t num_radio_chains = ((_local_ctrl->peek32(RB32_CORE_STATUS) >> 8) & 0xff);
689     UHD_ASSERT_THROW(num_radio_chains > 0);
690     UHD_ASSERT_THROW(num_radio_chains <= 2);
691     _radio_perifs.resize(num_radio_chains);
692     _codec_mgr = ad936x_manager::make(_codec_ctrl, num_radio_chains);
693     _codec_mgr->init_codec();
694     for (size_t i = 0; i < _radio_perifs.size(); i++)
695         this->setup_radio(i);
696 
697     // now test each radio module's connection to the codec interface
698     for (radio_perifs_t& perif : _radio_perifs) {
699         _codec_mgr->loopback_self_test(
700             [&perif](const uint32_t value) {
701                 perif.ctrl->poke32(TOREG(SR_CODEC_IDLE), value);
702             },
703             [&perif]() { return perif.ctrl->peek64(RB64_CODEC_READBACK); });
704     }
705 
706     // register time now and pps onto available radio cores
707     _tree->create<time_spec_t>(mb_path / "time" / "now")
708         .set_publisher(std::bind(&time_core_3000::get_time_now, _radio_perifs[0].time64))
709         .add_coerced_subscriber(
710             std::bind(&b200_impl::set_time, this, std::placeholders::_1))
711         .set(0.0);
712     // re-sync the times when the tick rate changes
713     _tree->access<double>(mb_path / "tick_rate")
714         .add_coerced_subscriber(std::bind(&b200_impl::sync_times, this));
715     _tree->create<time_spec_t>(mb_path / "time" / "pps")
716         .set_publisher(
717             std::bind(&time_core_3000::get_time_last_pps, _radio_perifs[0].time64));
718     for (radio_perifs_t& perif : _radio_perifs) {
719         _tree->access<time_spec_t>(mb_path / "time" / "pps")
720             .add_coerced_subscriber(std::bind(
721                 &time_core_3000::set_time_next_pps, perif.time64, std::placeholders::_1));
722     }
723 
724     // setup time source props
725     const std::vector<std::string> time_sources =
726         (_gpsdo_capable)
727             ? std::vector<std::string>{"none", "internal", "external", "gpsdo"}
728             : std::vector<std::string>{"none", "internal", "external"};
729     _tree->create<std::vector<std::string>>(mb_path / "time_source" / "options")
730         .set(time_sources);
731     _tree->create<std::string>(mb_path / "time_source" / "value")
732         .set_coercer(std::bind(
733             &check_option_valid, "time source", time_sources, std::placeholders::_1))
734         .add_coerced_subscriber(
735             std::bind(&b200_impl::update_time_source, this, std::placeholders::_1));
736     // setup reference source props
737     const std::vector<std::string> clock_sources =
738         (_gpsdo_capable) ? std::vector<std::string>{"internal", "external", "gpsdo"}
739                          : std::vector<std::string>{"internal", "external"};
740     _tree->create<std::vector<std::string>>(mb_path / "clock_source" / "options")
741         .set(clock_sources);
742     _tree->create<std::string>(mb_path / "clock_source" / "value")
743         .set_coercer(std::bind(
744             &check_option_valid, "clock source", clock_sources, std::placeholders::_1))
745         .add_coerced_subscriber(
746             std::bind(&b200_impl::update_clock_source, this, std::placeholders::_1));
747 
748     ////////////////////////////////////////////////////////////////////
749     // front panel gpio
750     ////////////////////////////////////////////////////////////////////
751     _radio_perifs[0].fp_gpio =
752         gpio_atr_3000::make(_radio_perifs[0].ctrl, TOREG(SR_FP_GPIO), RB32_FP_GPIO);
753     for (const gpio_attr_map_t::value_type attr : gpio_attr_map) {
754         switch (attr.first) {
755             case usrp::gpio_atr::GPIO_SRC:
756                 _tree
757                     ->create<std::vector<std::string>>(
758                         mb_path / "gpio" / "FP0" / attr.second)
759                     .set(std::vector<std::string>(
760                         32, usrp::gpio_atr::default_attr_value_map.at(attr.first)))
761                     .add_coerced_subscriber([this](const std::vector<std::string>&) {
762                         throw uhd::runtime_error("This device does not support setting "
763                                                  "the GPIO_SRC attribute.");
764                     });
765                 break;
766             case usrp::gpio_atr::GPIO_CTRL:
767             case usrp::gpio_atr::GPIO_DDR:
768                 _tree
769                     ->create<std::vector<std::string>>(
770                         mb_path / "gpio" / "FP0" / attr.second)
771                     .set(std::vector<std::string>(
772                         32, usrp::gpio_atr::default_attr_value_map.at(attr.first)))
773                     .add_coerced_subscriber([this, attr](
774                                                 const std::vector<std::string> str_val) {
775                         uint32_t val = 0;
776                         for (size_t i = 0; i < str_val.size(); i++) {
777                             val += usrp::gpio_atr::gpio_attr_value_pair.at(attr.second)
778                                        .at(str_val[i])
779                                    << i;
780                         }
781                         _radio_perifs[0].fp_gpio->set_gpio_attr(attr.first, val);
782                     });
783                 break;
784             case usrp::gpio_atr::GPIO_READBACK:
785                 _tree->create<uint32_t>(mb_path / "gpio" / "FP0" / "READBACK")
786                     .set_publisher(
787                         std::bind(&gpio_atr_3000::read_gpio, _radio_perifs[0].fp_gpio));
788                 break;
789             default:
790                 _tree->create<uint32_t>(mb_path / "gpio" / "FP0" / attr.second)
791                     .set(0)
792                     .add_coerced_subscriber(std::bind(&gpio_atr_3000::set_gpio_attr,
793                         _radio_perifs[0].fp_gpio,
794                         attr.first,
795                         std::placeholders::_1));
796         }
797     }
798 
799     ////////////////////////////////////////////////////////////////////
800     // dboard eeproms but not really
801     ////////////////////////////////////////////////////////////////////
802     dboard_eeprom_t db_eeprom;
803     _tree->create<dboard_eeprom_t>(mb_path / "dboards" / "A" / "rx_eeprom")
804         .set(db_eeprom);
805     _tree->create<dboard_eeprom_t>(mb_path / "dboards" / "A" / "tx_eeprom")
806         .set(db_eeprom);
807     _tree->create<dboard_eeprom_t>(mb_path / "dboards" / "A" / "gdb_eeprom")
808         .set(db_eeprom);
809 
810     ////////////////////////////////////////////////////////////////////
811     // do some post-init tasks
812     ////////////////////////////////////////////////////////////////////
813     // Init the clock rate and the auto mcr appropriately
814     if (not device_addr.has_key("master_clock_rate")) {
815         UHD_LOGGER_INFO("B200") << "Setting master clock rate selection to 'automatic'.";
816     }
817     // We can automatically choose a master clock rate, but not if the user specifies one
818     const double default_tick_rate =
819         device_addr.cast<double>("master_clock_rate", ad936x_manager::DEFAULT_TICK_RATE);
820     _tree->access<double>(mb_path / "tick_rate").set(default_tick_rate);
821     _tree->access<bool>(mb_path / "auto_tick_rate")
822         .set(not device_addr.has_key("master_clock_rate"));
823 
824     // subdev spec contains full width of selections
825     subdev_spec_t rx_spec, tx_spec;
826     for (const std::string& fe :
827         _tree->list(mb_path / "dboards" / "A" / "rx_frontends")) {
828         rx_spec.push_back(subdev_spec_pair_t("A", fe));
829     }
830     for (const std::string& fe :
831         _tree->list(mb_path / "dboards" / "A" / "tx_frontends")) {
832         tx_spec.push_back(subdev_spec_pair_t("A", fe));
833     }
834     _tree->access<subdev_spec_t>(mb_path / "rx_subdev_spec").set(rx_spec);
835     _tree->access<subdev_spec_t>(mb_path / "tx_subdev_spec").set(tx_spec);
836 
837     // init to internal clock and time source
838     _tree->access<std::string>(mb_path / "clock_source/value").set("internal");
839     _tree->access<std::string>(mb_path / "time_source/value").set("internal");
840 
841     // Set the DSP chains to some safe value
842     for (size_t i = 0; i < _radio_perifs.size(); i++) {
843         _radio_perifs[i].ddc->set_host_rate(
844             default_tick_rate / ad936x_manager::DEFAULT_DECIM);
845         _radio_perifs[i].duc->set_host_rate(
846             default_tick_rate / ad936x_manager::DEFAULT_INTERP);
847     }
848 }
849 
~b200_impl(void)850 b200_impl::~b200_impl(void)
851 {
852     UHD_SAFE_CALL(_async_task.reset();)
853 }
854 
855 /***********************************************************************
856  * setup radio control objects
857  **********************************************************************/
setup_radio(const size_t dspno)858 void b200_impl::setup_radio(const size_t dspno)
859 {
860     radio_perifs_t& perif = _radio_perifs[dspno];
861     const fs_path mb_path = "/mboards/0";
862 
863     ////////////////////////////////////////////////////////////////////
864     // Set up transport
865     ////////////////////////////////////////////////////////////////////
866     const uint32_t sid = (dspno == 0) ? B200_CTRL0_MSG_SID : B200_CTRL1_MSG_SID;
867 
868     ////////////////////////////////////////////////////////////////////
869     // radio control
870     ////////////////////////////////////////////////////////////////////
871     perif.ctrl = radio_ctrl_core_3000::make(
872         false /*lilE*/, _ctrl_transport, zero_copy_if::sptr() /*null*/, sid);
873     perif.ctrl->hold_task(_async_task);
874     _async_task_data->radio_ctrl[dspno] = perif.ctrl; // weak
875     _tree->access<time_spec_t>(mb_path / "time" / "cmd")
876         .add_coerced_subscriber(std::bind(
877             &radio_ctrl_core_3000::set_time, perif.ctrl, std::placeholders::_1));
878     _tree->access<double>(mb_path / "tick_rate")
879         .add_coerced_subscriber(std::bind(
880             &radio_ctrl_core_3000::set_tick_rate, perif.ctrl, std::placeholders::_1));
881     this->register_loopback_self_test(perif.ctrl);
882 
883     ////////////////////////////////////////////////////////////////////
884     // Set up peripherals
885     ////////////////////////////////////////////////////////////////////
886     perif.atr = gpio_atr_3000::make_write_only(perif.ctrl, TOREG(SR_ATR));
887     perif.atr->set_atr_mode(MODE_ATR, 0xFFFFFFFF);
888     // create rx dsp control objects
889     perif.framer = rx_vita_core_3000::make(perif.ctrl, TOREG(SR_RX_CTRL));
890     perif.ddc = rx_dsp_core_3000::make(perif.ctrl, TOREG(SR_RX_DSP), true /*is_b200?*/);
891     perif.ddc->set_link_rate(10e9 / 8); // whatever
892     perif.ddc->set_mux(usrp::fe_connection_t(dspno == 1 ? "IbQb" : "IQ"));
893     perif.ddc->set_freq(rx_dsp_core_3000::DEFAULT_CORDIC_FREQ);
894     perif.deframer = tx_vita_core_3000::make_no_radio_buff(perif.ctrl, TOREG(SR_TX_CTRL));
895     perif.duc      = tx_dsp_core_3000::make(perif.ctrl, TOREG(SR_TX_DSP));
896     perif.duc->set_link_rate(10e9 / 8); // whatever
897     perif.duc->set_freq(tx_dsp_core_3000::DEFAULT_CORDIC_FREQ);
898     if (_enable_user_regs) {
899         UHD_LOG_DEBUG("B200", "Enabling user settings registers");
900         perif.user_settings = user_settings_core_3000::make(
901             perif.ctrl, TOREG(SR_USER_SR_BASE), TOREG(SR_USER_RB_ADDR));
902         if (!perif.user_settings) {
903             const std::string error_msg = "Failed to create user settings bus!";
904             UHD_LOG_ERROR("B200", error_msg);
905             throw uhd::runtime_error(error_msg);
906         }
907     }
908 
909     ////////////////////////////////////////////////////////////////////
910     // create time control objects
911     ////////////////////////////////////////////////////////////////////
912     time_core_3000::readback_bases_type time64_rb_bases;
913     time64_rb_bases.rb_now = RB64_TIME_NOW;
914     time64_rb_bases.rb_pps = RB64_TIME_PPS;
915     perif.time64 = time_core_3000::make(perif.ctrl, TOREG(SR_TIME), time64_rb_bases);
916 
917     ////////////////////////////////////////////////////////////////////
918     // connect rx dsp control objects
919     ////////////////////////////////////////////////////////////////////
920     const fs_path rx_dsp_path = mb_path / "rx_dsps" / dspno;
921     perif.ddc->populate_subtree(_tree->subtree(rx_dsp_path));
922     _tree->create<bool>(rx_dsp_path / "rate" / "set").set(false);
923     _tree->access<double>(rx_dsp_path / "rate" / "value")
924         .set_coercer(std::bind(&b200_impl::coerce_rx_samp_rate,
925             this,
926             perif.ddc,
927             dspno,
928             std::placeholders::_1))
929         .add_coerced_subscriber([this, rx_dsp_path](const double) {
930             if (this->_tree) {
931                 this->_tree->access<bool>(rx_dsp_path / "rate" / "set").set(true);
932             }
933         })
934         .add_coerced_subscriber(std::bind(
935             &b200_impl::update_rx_samp_rate, this, dspno, std::placeholders::_1));
936     _tree->create<stream_cmd_t>(rx_dsp_path / "stream_cmd")
937         .add_coerced_subscriber(std::bind(&rx_vita_core_3000::issue_stream_command,
938             perif.framer,
939             std::placeholders::_1));
940     _tree->access<double>(mb_path / "tick_rate")
941         .add_coerced_subscriber(std::bind(
942             &rx_vita_core_3000::set_tick_rate, perif.framer, std::placeholders::_1))
943         .add_coerced_subscriber(std::bind(&b200_impl::update_rx_dsp_tick_rate,
944             this,
945             std::placeholders::_1,
946             perif.ddc,
947             rx_dsp_path));
948 
949     ////////////////////////////////////////////////////////////////////
950     // create tx dsp control objects
951     ////////////////////////////////////////////////////////////////////
952     const fs_path tx_dsp_path = mb_path / "tx_dsps" / dspno;
953     perif.duc->populate_subtree(_tree->subtree(tx_dsp_path));
954     _tree->create<bool>(tx_dsp_path / "rate" / "set").set(false);
955     _tree->access<double>(tx_dsp_path / "rate" / "value")
956         .set_coercer(std::bind(&b200_impl::coerce_tx_samp_rate,
957             this,
958             perif.duc,
959             dspno,
960             std::placeholders::_1))
961         .add_coerced_subscriber([this, tx_dsp_path](const double) {
962             if (this->_tree) {
963                 this->_tree->access<bool>(tx_dsp_path / "rate" / "set").set(true);
964             }
965         })
966         .add_coerced_subscriber(std::bind(
967             &b200_impl::update_tx_samp_rate, this, dspno, std::placeholders::_1));
968     _tree->access<double>(mb_path / "tick_rate")
969         .add_coerced_subscriber(std::bind(&b200_impl::update_tx_dsp_tick_rate,
970             this,
971             std::placeholders::_1,
972             perif.duc,
973             tx_dsp_path));
974 
975     ////////////////////////////////////////////////////////////////////
976     // create RF frontend interfacing
977     ////////////////////////////////////////////////////////////////////
978     // The "calibration serial" is the motherboard serial plus the frontend
979     // (A or B) separated by colon, e.g. "1234ABC:A".
980     const std::string cal_serial =
981         _tree->access<mboard_eeprom_t>(mb_path / "eeprom").get()["serial"] + "#"
982         + (dspno ? "B" : "A");
983     // The "calibration key" is either b2xxmini_power_cal_$dir_$ant, or
984     // b2xx_power_cal_$dir_$ant, depending on the form factor.
985     // $dir is either "tx" or "rx", and "ant" is either "tx_rx" or "rx2" (i.e.,
986     // sanitized version of the antenna names that work in filenames.
987     const std::string cal_key_base = (_product == B200MINI or _product == B205MINI)
988                                          ? "b2xxmini_pwr_"
989                                          : "b2xx_pwr_";
990     for (direction_t dir : std::vector<direction_t>{RX_DIRECTION, TX_DIRECTION}) {
991         const std::string dir_key = (dir == RX_DIRECTION) ? "rx" : "tx";
992         const std::string key     = std::string(((dir == RX_DIRECTION) ? "RX" : "TX"))
993                                 + std::string(((dspno == _fe1) ? "1" : "2"));
994         const fs_path rf_fe_path =
995             mb_path / "dboards" / "A" / (dir_key + "_frontends") / (dspno ? "B" : "A");
996         const std::vector<std::string> ants =
997             (dir == RX_DIRECTION) ? std::vector<std::string>{"TX/RX", "RX2"}
998                                   : std::vector<std::string>{"TX/RX"};
999 
1000         // This will connect all the AD936x-specific items
1001         _codec_mgr->populate_frontend_subtree(_tree->subtree(rf_fe_path), key, dir);
1002 
1003         // Antenna controls are board-specific, not AD936x specific
1004         if (dir == RX_DIRECTION) {
1005             _tree->create<std::string>(rf_fe_path / "antenna" / "value")
1006                 .add_coerced_subscriber([this, dspno](const std::string& antenna) {
1007                     this->update_antenna_sel(dspno, antenna);
1008                 })
1009                 .set("RX2");
1010         } else if (dir == TX_DIRECTION) {
1011             _tree->create<std::string>(rf_fe_path / "antenna" / "value").set("TX/RX");
1012         }
1013 
1014         // We don't add any baseband correction
1015         auto ggroup                    = uhd::gain_group::make();
1016         constexpr char HW_GAIN_STAGE[] = "hw";
1017         ggroup->register_fcns(HW_GAIN_STAGE,
1018             {// Get gain range:
1019                 [key]() { return ad9361_ctrl::get_gain_range(key); },
1020                 // Get gain:
1021                 [this, rf_fe_path, key]() {
1022                     return _tree
1023                         ->access<double>(rf_fe_path / "gains"
1024                                          / ad9361_ctrl::get_gain_names(key).at(0)
1025                                          / "value")
1026                         .get();
1027                 },
1028                 // Set gain:
1029                 [this, rf_fe_path, key](const double gain) {
1030                     _tree
1031                         ->access<double>(rf_fe_path / "gains"
1032                                          / ad9361_ctrl::get_gain_names(key).at(0)
1033                                          / "value")
1034                         .set(gain);
1035                 }});
1036         // Add power controls
1037         perif.pwr_mgr.insert({dir_key,
1038             pwr_cal_mgr::make(
1039                 cal_serial,
1040                 "B200-CAL-" + key,
1041                 // Frequency getter:
1042                 [this, rf_fe_path]() {
1043                     return _tree->access<double>(rf_fe_path / "freq" / "value").get();
1044                 },
1045                 // Current key getter (see notes on calibration key above):
1046                 [this, rf_fe_path, cal_key_base, dir_key]() {
1047                     return cal_key_base + dir_key + "_"
1048                            + pwr_cal_mgr::sanitize_antenna_name(
1049                                _tree->access<std::string>(
1050                                         rf_fe_path / "antenna" / "value")
1051                                    .get());
1052                 },
1053                 ggroup)});
1054         perif.pwr_mgr.at(dir_key)->populate_subtree(_tree->subtree(rf_fe_path));
1055         perif.pwr_mgr.at(dir_key)->set_temperature(
1056             _tree->access<sensor_value_t>(rf_fe_path / "sensors" / "temp")
1057                 .get()
1058                 .to_int());
1059 
1060         // Now connect all the b200_impl-specific items
1061         _tree->create<sensor_value_t>(rf_fe_path / "sensors" / "lo_locked")
1062             .set_publisher(
1063                 [this, dir]() { return this->get_fe_pll_locked(dir == TX_DIRECTION); });
1064         _tree->access<double>(rf_fe_path / "freq" / "value")
1065             .add_coerced_subscriber([this, key](const double freq) {
1066                 return this->update_bandsel(key, freq);
1067             })
1068             // Every time we retune, we re-set the power level.
1069             .add_coerced_subscriber([this, pwr_mgr = perif.pwr_mgr.at(dir_key)](
1070                                         const double) { pwr_mgr->update_power(); })
1071 
1072             ;
1073         _tree->create<std::vector<std::string>>(rf_fe_path / "antenna" / "options")
1074             .set(ants);
1075         // When we set the gain, we need to disable power tracking. Note that
1076         // the power manager also calls into the gains property, and thus
1077         // clobbers its own tracking mode, but that's OK because set_power() will
1078         // always reset the tracking mode.
1079         _tree
1080             ->access<double>(
1081                 rf_fe_path / "gains" / ad9361_ctrl::get_gain_names(key).at(0) / "value")
1082             .add_coerced_subscriber([pwr_mgr = perif.pwr_mgr.at(dir_key)](const double) {
1083                 pwr_mgr->set_tracking_mode(pwr_cal_mgr::tracking_mode::TRACK_GAIN);
1084             });
1085 
1086         if (_enable_user_regs) {
1087             _tree->create<uhd::wb_iface::sptr>(rf_fe_path / "user_settings/iface")
1088                 .set(perif.user_settings);
1089         }
1090     }
1091 }
1092 
1093 /***********************************************************************
1094  * loopback tests
1095  **********************************************************************/
1096 
register_loopback_self_test(wb_iface::sptr iface)1097 void b200_impl::register_loopback_self_test(wb_iface::sptr iface)
1098 {
1099     bool test_fail = false;
1100     UHD_LOGGER_INFO("B200") << "Performing register loopback test... ";
1101     size_t hash = size_t(time(NULL));
1102     for (size_t i = 0; i < 100; i++) {
1103         boost::hash_combine(hash, i);
1104         iface->poke32(TOREG(SR_TEST), uint32_t(hash));
1105         test_fail = iface->peek32(RB32_TEST) != uint32_t(hash);
1106         if (test_fail)
1107             break; // exit loop on any failure
1108     }
1109     UHD_LOGGER_INFO("B200") << "Register loopback test "
1110                             << ((test_fail) ? "failed" : "passed");
1111 }
1112 
1113 /***********************************************************************
1114  * Sample and tick rate comprehension below
1115  **********************************************************************/
enforce_tick_rate_limits(size_t chan_count,double tick_rate,const std::string & direction)1116 void b200_impl::enforce_tick_rate_limits(
1117     size_t chan_count, double tick_rate, const std::string& direction /*= ""*/)
1118 {
1119     const size_t max_chans = 2;
1120     if (chan_count > max_chans) {
1121         throw uhd::value_error(boost::str(
1122             boost::format("cannot not setup %d %s channels (maximum is %d)") % chan_count
1123             % (direction.empty() ? "data" : direction) % max_chans));
1124     } else {
1125         const double max_tick_rate =
1126             ad9361_device_t::AD9361_MAX_CLOCK_RATE / ((chan_count <= 1) ? 1 : 2);
1127         if (tick_rate - max_tick_rate >= 1.0) {
1128             throw uhd::value_error(boost::str(
1129                 boost::format(
1130                     "current master clock rate (%.6f MHz) exceeds maximum possible "
1131                     "master clock rate (%.6f MHz) when using %d %s channels")
1132                 % (tick_rate / 1e6) % (max_tick_rate / 1e6) % chan_count
1133                 % (direction.empty() ? "data" : direction)));
1134         }
1135         const double min_tick_rate =
1136             ad9361_device_t::AD9361_MIN_CLOCK_RATE / ((chan_count <= 1) ? 1 : 2);
1137         if (min_tick_rate - tick_rate >= 1.0) {
1138             throw uhd::value_error(boost::str(
1139                 boost::format(
1140                     "current master clock rate (%.6f MHz) is less than minimum possible "
1141                     "master clock rate (%.6f MHz) when using %d %s channels")
1142                 % (tick_rate / 1e6) % (min_tick_rate / 1e6) % chan_count
1143                 % (direction.empty() ? "data" : direction)));
1144         }
1145     }
1146 }
1147 
set_tick_rate(const double new_tick_rate)1148 double b200_impl::set_tick_rate(const double new_tick_rate)
1149 {
1150     UHD_LOGGER_INFO("B200") << (boost::format("Asking for clock rate %.6f MHz... ")
1151                                    % (new_tick_rate / 1e6))
1152                             << std::flush;
1153     check_tick_rate_with_current_streamers(new_tick_rate); // Defined in b200_io_impl.cpp
1154 
1155     // Make sure the clock rate is actually changed before doing
1156     // the full Monty of setting regs and loopback tests etc.
1157     if (std::abs(new_tick_rate - _tick_rate) < 1.0) {
1158         UHD_LOGGER_INFO("B200") << "OK";
1159         return _tick_rate;
1160     }
1161 
1162     _tick_rate = _codec_ctrl->set_clock_rate(new_tick_rate);
1163     UHD_LOGGER_INFO("B200") << (boost::format("Actually got clock rate %.6f MHz.")
1164                                 % (_tick_rate / 1e6));
1165 
1166     for (radio_perifs_t& perif : _radio_perifs) {
1167         perif.time64->set_tick_rate(_tick_rate);
1168         perif.time64->self_test();
1169     }
1170     return _tick_rate;
1171 }
1172 
1173 /***********************************************************************
1174  * compat checks
1175  **********************************************************************/
1176 
check_fw_compat(void)1177 void b200_impl::check_fw_compat(void)
1178 {
1179     uint16_t compat_num   = _iface->get_compat_num();
1180     uint32_t compat_major = (uint32_t)(compat_num >> 8);
1181     uint32_t compat_minor = (uint32_t)(compat_num & 0xFF);
1182 
1183     if (compat_major != B200_FW_COMPAT_NUM_MAJOR) {
1184         throw uhd::runtime_error(str(
1185             boost::format(
1186                 "Expected firmware compatibility number %d.%d, but got %d.%d:\n"
1187                 "The firmware build is not compatible with the host code build.\n"
1188                 "%s")
1189             % int(B200_FW_COMPAT_NUM_MAJOR) % int(B200_FW_COMPAT_NUM_MINOR) % compat_major
1190             % compat_minor % print_utility_error("uhd_images_downloader.py")));
1191     }
1192     _tree->create<std::string>("/mboards/0/fw_version")
1193         .set(str(boost::format("%u.%u") % compat_major % compat_minor));
1194 }
1195 
check_fpga_compat(void)1196 void b200_impl::check_fpga_compat(void)
1197 {
1198     const uint64_t compat       = _local_ctrl->peek64(0);
1199     const uint32_t signature    = uint32_t(compat >> 32);
1200     const uint16_t compat_major = uint16_t(compat >> 16);
1201     const uint16_t compat_minor = uint16_t(compat & 0xffff);
1202     if (signature != 0xACE0BA5E)
1203         throw uhd::runtime_error(
1204             "b200::check_fpga_compat signature register readback failed");
1205 
1206     const uint16_t expected = ((_product == B200MINI or _product == B205MINI)
1207                                    ? B205_FPGA_COMPAT_NUM
1208                                    : B200_FPGA_COMPAT_NUM);
1209     if (compat_major != expected) {
1210         throw uhd::runtime_error(str(
1211             boost::format("Expected FPGA compatibility number %d, but got %d:\n"
1212                           "The FPGA build is not compatible with the host code build.\n"
1213                           "%s")
1214             % int(expected) % compat_major
1215             % print_utility_error("uhd_images_downloader.py")));
1216     }
1217     _tree->create<std::string>("/mboards/0/fpga_version")
1218         .set(str(boost::format("%u.%u") % compat_major % compat_minor));
1219 }
1220 
1221 /***********************************************************************
1222  * Reference time and clock
1223  **********************************************************************/
1224 
update_clock_source(const std::string & source)1225 void b200_impl::update_clock_source(const std::string& source)
1226 {
1227     // For B205, ref_sel selects whether or not to lock to the external clock source
1228     if (_product == B200MINI or _product == B205MINI) {
1229         if (source == "external" and _time_source == EXTERNAL) {
1230             throw uhd::value_error(
1231                 "external reference cannot be both a clock source and a time source");
1232         }
1233 
1234         if (source == "internal") {
1235             if (_gpio_state.ref_sel != 0) {
1236                 _gpio_state.ref_sel = 0;
1237                 this->update_gpio_state();
1238             }
1239         } else if (source == "external") {
1240             if (_gpio_state.ref_sel != 1) {
1241                 _gpio_state.ref_sel = 1;
1242                 this->update_gpio_state();
1243             }
1244         } else {
1245             throw uhd::key_error("update_clock_source: unknown source: " + source);
1246         }
1247         return;
1248     }
1249 
1250     // For all other devices, ref_sel selects the external or gpsdo clock source
1251     // and the ADF4001 selects whether to lock to it or not
1252     if (source == "internal") {
1253         _adf4001_iface->set_lock_to_ext_ref(false);
1254     } else if (source == "external") {
1255         if (_gpio_state.ref_sel != 0) {
1256             _gpio_state.ref_sel = 0;
1257             this->update_gpio_state();
1258         }
1259         _adf4001_iface->set_lock_to_ext_ref(true);
1260     } else if (source == "gpsdo") {
1261         if (not _gps or not _gps->gps_detected()) {
1262             throw uhd::key_error(
1263                 "update_clock_source: gpsdo selected, but no gpsdo detected!");
1264         }
1265         if (_gpio_state.ref_sel != 1) {
1266             _gpio_state.ref_sel = 1;
1267             this->update_gpio_state();
1268         }
1269         _adf4001_iface->set_lock_to_ext_ref(true);
1270     } else {
1271         throw uhd::key_error("update_clock_source: unknown source: " + source);
1272     }
1273 }
1274 
update_time_source(const std::string & source)1275 void b200_impl::update_time_source(const std::string& source)
1276 {
1277     if ((_product == B200MINI or _product == B205MINI) and source == "external"
1278         and _gpio_state.ref_sel == 1) {
1279         throw uhd::value_error(
1280             "external reference cannot be both a time source and a clock source");
1281     }
1282 
1283     // We assume source is valid for this device (if it's gone through
1284     // the prop three, then it definitely is thanks to our coercer)
1285     time_source_t value;
1286     if (source == "none")
1287         value = NONE;
1288     else if (source == "internal")
1289         value = INTERNAL;
1290     else if (source == "external")
1291         value = EXTERNAL;
1292     else if (_gps and source == "gpsdo")
1293         value = GPSDO;
1294     else
1295         throw uhd::key_error("update_time_source: unknown source: " + source);
1296     if (_time_source != value) {
1297         _local_ctrl->poke32(TOREG(SR_CORE_SYNC), value);
1298         _time_source = value;
1299     }
1300 }
1301 
set_time(const uhd::time_spec_t & t)1302 void b200_impl::set_time(const uhd::time_spec_t& t)
1303 {
1304     for (radio_perifs_t& perif : _radio_perifs)
1305         perif.time64->set_time_sync(t);
1306     _local_ctrl->poke32(TOREG(SR_CORE_SYNC), 1 << 2 | uint32_t(_time_source));
1307     _local_ctrl->poke32(TOREG(SR_CORE_SYNC), _time_source);
1308 }
1309 
sync_times()1310 void b200_impl::sync_times()
1311 {
1312     set_time(_radio_perifs[0].time64->get_time_now());
1313 }
1314 
1315 /***********************************************************************
1316  * GPIO setup
1317  **********************************************************************/
1318 
update_bandsel(const std::string & which,double freq)1319 void b200_impl::update_bandsel(const std::string& which, double freq)
1320 {
1321     // B205 does not have bandsels
1322     if (_product == B200MINI or _product == B205MINI) {
1323         return;
1324     }
1325 
1326     if (which[0] == 'R') {
1327         if (freq < 2.2e9) {
1328             _gpio_state.rx_bandsel_a = 0;
1329             _gpio_state.rx_bandsel_b = 0;
1330             _gpio_state.rx_bandsel_c = 1;
1331         } else if ((freq >= 2.2e9) && (freq < 4e9)) {
1332             _gpio_state.rx_bandsel_a = 0;
1333             _gpio_state.rx_bandsel_b = 1;
1334             _gpio_state.rx_bandsel_c = 0;
1335         } else if ((freq >= 4e9) && (freq <= 6e9)) {
1336             _gpio_state.rx_bandsel_a = 1;
1337             _gpio_state.rx_bandsel_b = 0;
1338             _gpio_state.rx_bandsel_c = 0;
1339         } else {
1340             UHD_THROW_INVALID_CODE_PATH();
1341         }
1342     } else if (which[0] == 'T') {
1343         if (freq < 2.5e9) {
1344             _gpio_state.tx_bandsel_a = 0;
1345             _gpio_state.tx_bandsel_b = 1;
1346         } else if ((freq >= 2.5e9) && (freq <= 6e9)) {
1347             _gpio_state.tx_bandsel_a = 1;
1348             _gpio_state.tx_bandsel_b = 0;
1349         } else {
1350             UHD_THROW_INVALID_CODE_PATH();
1351         }
1352     } else {
1353         UHD_THROW_INVALID_CODE_PATH();
1354     }
1355 
1356     update_gpio_state();
1357 }
1358 
reset_codec()1359 void b200_impl::reset_codec()
1360 {
1361     _gpio_state.codec_arst = 1;
1362     update_gpio_state();
1363     _gpio_state.codec_arst = 0;
1364     update_gpio_state();
1365 }
1366 
update_gpio_state(void)1367 void b200_impl::update_gpio_state(void)
1368 {
1369     const uint32_t misc_word =
1370         0 | (_gpio_state.swap_atr << 8) | (_gpio_state.tx_bandsel_a << 7)
1371         | (_gpio_state.tx_bandsel_b << 6) | (_gpio_state.rx_bandsel_a << 5)
1372         | (_gpio_state.rx_bandsel_b << 4) | (_gpio_state.rx_bandsel_c << 3)
1373         | (_gpio_state.codec_arst << 2) | (_gpio_state.mimo << 1)
1374         | (_gpio_state.ref_sel << 0);
1375 
1376     _local_ctrl->poke32(TOREG(SR_CORE_MISC), misc_word);
1377 }
1378 
update_atrs(void)1379 void b200_impl::update_atrs(void)
1380 {
1381     if (_radio_perifs.size() > _fe1 and _radio_perifs[_fe1].atr) {
1382         radio_perifs_t& perif = _radio_perifs[_fe1];
1383         const bool enb_rx     = bool(perif.rx_streamer.lock());
1384         const bool enb_tx     = bool(perif.tx_streamer.lock());
1385         const bool is_rx2     = perif.ant_rx2;
1386         const uint32_t rxonly = (enb_rx) ? ((is_rx2) ? STATE_RX1_RX2 : STATE_RX1_TXRX)
1387                                          : STATE_OFF;
1388         const uint32_t txonly = (enb_tx) ? (STATE_TX1_TXRX) : STATE_OFF;
1389         uint32_t fd           = STATE_OFF;
1390         if (enb_rx and enb_tx)
1391             fd = STATE_FDX1_TXRX;
1392         if (enb_rx and not enb_tx)
1393             fd = rxonly;
1394         if (not enb_rx and enb_tx)
1395             fd = txonly;
1396         gpio_atr_3000::sptr atr = perif.atr;
1397         atr->set_atr_reg(ATR_REG_IDLE, STATE_OFF);
1398         atr->set_atr_reg(ATR_REG_RX_ONLY, rxonly);
1399         atr->set_atr_reg(ATR_REG_TX_ONLY, txonly);
1400         atr->set_atr_reg(ATR_REG_FULL_DUPLEX, fd);
1401     }
1402     if (_radio_perifs.size() > _fe2 and _radio_perifs[_fe2].atr) {
1403         radio_perifs_t& perif = _radio_perifs[_fe2];
1404         const bool enb_rx     = bool(perif.rx_streamer.lock());
1405         const bool enb_tx     = bool(perif.tx_streamer.lock());
1406         const bool is_rx2     = perif.ant_rx2;
1407         const uint32_t rxonly = (enb_rx) ? ((is_rx2) ? STATE_RX2_RX2 : STATE_RX2_TXRX)
1408                                          : STATE_OFF;
1409         const uint32_t txonly = (enb_tx) ? (STATE_TX2_TXRX) : STATE_OFF;
1410         uint32_t fd           = STATE_OFF;
1411         if (enb_rx and enb_tx)
1412             fd = STATE_FDX2_TXRX;
1413         if (enb_rx and not enb_tx)
1414             fd = rxonly;
1415         if (not enb_rx and enb_tx)
1416             fd = txonly;
1417         gpio_atr_3000::sptr atr = perif.atr;
1418         atr->set_atr_reg(ATR_REG_IDLE, STATE_OFF);
1419         atr->set_atr_reg(ATR_REG_RX_ONLY, rxonly);
1420         atr->set_atr_reg(ATR_REG_TX_ONLY, txonly);
1421         atr->set_atr_reg(ATR_REG_FULL_DUPLEX, fd);
1422     }
1423 }
1424 
update_antenna_sel(const size_t which,const std::string & ant)1425 void b200_impl::update_antenna_sel(const size_t which, const std::string& ant)
1426 {
1427     if (ant != "TX/RX" and ant != "RX2")
1428         throw uhd::value_error("b200: unknown RX antenna option: " + ant);
1429     _radio_perifs[which].ant_rx2 = (ant == "RX2");
1430     this->update_atrs();
1431 }
1432 
update_enables(void)1433 void b200_impl::update_enables(void)
1434 {
1435     // extract settings from state variables
1436     const bool enb_tx1 = (_radio_perifs.size() > _fe1)
1437                          and bool(_radio_perifs[_fe1].tx_streamer.lock());
1438     const bool enb_rx1 = (_radio_perifs.size() > _fe1)
1439                          and bool(_radio_perifs[_fe1].rx_streamer.lock());
1440     const bool enb_tx2 = (_radio_perifs.size() > _fe2)
1441                          and bool(_radio_perifs[_fe2].tx_streamer.lock());
1442     const bool enb_rx2 = (_radio_perifs.size() > _fe2)
1443                          and bool(_radio_perifs[_fe2].rx_streamer.lock());
1444     const size_t num_rx = (enb_rx1 ? 1 : 0) + (enb_rx2 ? 1 : 0);
1445     const size_t num_tx = (enb_tx1 ? 1 : 0) + (enb_tx2 ? 1 : 0);
1446     const bool mimo     = num_rx == 2 or num_tx == 2;
1447 
1448     if ((num_rx + num_tx) == 3) {
1449         throw uhd::runtime_error(
1450             "b200: 2 RX 1 TX and 1 RX 2 TX configurations not possible");
1451     }
1452 
1453     // setup the active chains in the codec
1454     _codec_ctrl->set_active_chains(enb_tx1, enb_tx2, enb_rx1, enb_rx2);
1455     if ((num_rx + num_tx) == 0)
1456         _codec_ctrl->set_active_chains(true, false, true, false); // enable something
1457 
1458     // figure out if mimo is enabled based on new state
1459     _gpio_state.mimo = (mimo) ? 1 : 0;
1460     update_gpio_state();
1461 
1462     // atrs change based on enables
1463     this->update_atrs();
1464 }
1465 
get_ref_locked(void)1466 sensor_value_t b200_impl::get_ref_locked(void)
1467 {
1468     const bool lock = (_local_ctrl->peek32(RB32_CORE_MISC) & 0x1) == 0x1;
1469     return sensor_value_t("Ref", lock, "locked", "unlocked");
1470 }
1471 
get_fe_pll_locked(const bool is_tx)1472 sensor_value_t b200_impl::get_fe_pll_locked(const bool is_tx)
1473 {
1474     const uint32_t st = _local_ctrl->peek32(RB32_CORE_PLL);
1475     const bool locked = is_tx ? ((st & 0x1) > 0) : ((st & 0x2) > 0);
1476     return sensor_value_t("LO", locked, "locked", "unlocked");
1477 }
1478