1# 2# Copyright 2018-2019 Ettus Research, a National Instruments Company 3# Copyright 2019 Ettus Research, a National Instruments Brand 4# 5# SPDX-License-Identifier: GPL-3.0-or-later 6# 7""" 8E310 implementation module 9""" 10 11from __future__ import print_function 12import copy 13import re 14from six import itervalues 15from usrp_mpm.components import ZynqComponents 16from usrp_mpm.dboard_manager import E31x_db 17from usrp_mpm.gpsd_iface import GPSDIfaceExtension 18from usrp_mpm.mpmutils import assert_compat_number, str2bool 19from usrp_mpm.periph_manager import PeriphManagerBase 20from usrp_mpm.rpc_server import no_rpc 21from usrp_mpm.sys_utils import dtoverlay 22from usrp_mpm.sys_utils.sysfs_thermal import read_sysfs_sensors_value 23from usrp_mpm.sys_utils.udev import get_spidev_nodes 24from usrp_mpm.xports import XportMgrUDP 25from usrp_mpm.periph_manager.e31x_periphs import MboardRegsControl 26from usrp_mpm.sys_utils.udev import get_eeprom_paths 27from usrp_mpm import e31x_legacy_eeprom 28 29E310_DEFAULT_CLOCK_SOURCE = 'internal' 30E310_DEFAULT_TIME_SOURCE = 'internal' 31E310_DEFAULT_ENABLE_FPGPIO = True 32E310_DEFAULT_DONT_RELOAD_FPGA = False # False means idle image gets reloaded 33E310_FPGA_COMPAT = (6, 0) 34E310_DBOARD_SLOT_IDX = 0 35E310_GPIO_SRC_PS = "PS" 36# We use the index positions of RFA and RFB to map between name and radio index 37E310_GPIO_SRCS = ("RFA", "RFB", E310_GPIO_SRC_PS) 38E310_FPGPIO_WIDTH = 6 39E310_GPIO_BANKS = ["INT0",] 40 41############################################################################### 42# Transport managers 43############################################################################### 44# pylint: disable=too-few-public-methods 45 46class E310XportMgrUDP(XportMgrUDP): 47 "E310-specific UDP configuration" 48 iface_config = { 49 'int0': { 50 'label': 'misc-enet-int-regs', 51 'type': 'internal', 52 }, 53 'eth0': { 54 'label': '', 55 'type': 'forward', 56 } 57 } 58 59 60# pylint: enable=too-few-public-methods 61 62############################################################################### 63# Main Class 64############################################################################### 65# We need to disable the no-self-use check, because we might require self to 66# become an RPC method, but PyLint doesnt' know that. 67# pylint: disable=no-self-use 68class e31x(ZynqComponents, PeriphManagerBase): 69 """ 70 Holds E310 specific attributes and methods 71 """ 72 ######################################################################### 73 # Overridables 74 # 75 # See PeriphManagerBase for documentation on these fields 76 ######################################################################### 77 description = "E300-Series Device" 78 # 0x77d2 and 0x77d3 79 pids = {0x77D2: 'e310_sg1', #sg1 80 0x77D3: 'e310_sg3'} #sg3 81 # The E310 has a single EEPROM that stores both DB and MB information 82 mboard_eeprom_addr = "e0004000.i2c" 83 mboard_eeprom_offset = 0 84 mboard_eeprom_max_len = 64 85 # We have two nvem paths on the E310. 86 # This one ensures that we get the right path for the MB. 87 mboard_eeprom_path_index = 1 88 mboard_info = {"type": "e3xx"} 89 mboard_sensor_callback_map = { 90 'ref_locked': 'get_ref_lock_sensor', 91 'gps_locked': 'get_gps_lock_sensor', 92 'temp_fpga' : 'get_fpga_temp_sensor', 93 'temp_mb' : 'get_mb_temp_sensor', 94 } 95 # The E310 has a single EEPROM that stores both DB and MB information 96 dboard_eeprom_addr = "e0004000.i2c" 97 dboard_eeprom_path_index = 0 98 # Actual DB EEPROM bytes are just 28. Reading just a couple more. 99 # Refer e300_eeprom_manager.hpp 100 dboard_eeprom_max_len = 32 101 max_num_dboards = 1 102 # We're on a Zynq target, so the following two come from the Zynq standard 103 # device tree overlay (tree/arch/arm/boot/dts/zynq-7000.dtsi) 104 dboard_spimaster_addrs = ["e0006000.spi"] 105 # E310-specific settings 106 # Label for the mboard UIO 107 mboard_regs_label = "mboard-regs" 108 # Override the list of updateable components 109 updateable_components = { 110 'fpga': { 111 'callback': "update_fpga", 112 'path': '/lib/firmware/{}.bin', 113 'reset': True, 114 }, 115 'dts': { 116 'callback': "update_dts", 117 'path': '/lib/firmware/{}.dts', 118 'output': '/lib/firmware/{}.dtbo', 119 'reset': False, 120 }, 121 } 122 # This class removes the overlay in tear_down() resulting 123 # in stale references to methods in the RPC server. Setting 124 # this to True ensures that the RPC server clears all registered 125 # methods on unclaim() and registers them on the following claim(). 126 clear_rpc_registry_on_unclaim = True 127 128 @classmethod 129 def generate_device_info(cls, eeprom_md, mboard_info, dboard_infos): 130 """ 131 Generate dictionary describing the device. 132 """ 133 # Add the default PeriphManagerBase information first 134 device_info = super().generate_device_info( 135 eeprom_md, mboard_info, dboard_infos) 136 # Then add E31x-specific information 137 mb_pid = eeprom_md.get('pid') 138 device_info['product'] = cls.pids.get(mb_pid, 'unknown') 139 return device_info 140 141 @staticmethod 142 def list_required_dt_overlays(device_info): 143 """ 144 Returns the name of the overlay for the regular image (not idle). 145 Either returns e310_sg1 or e310_sg3. 146 """ 147 return [device_info['product']] 148 ### End of overridables ################################################### 149 150 @staticmethod 151 def get_idle_dt_overlay(device_info): 152 """ 153 Overlay to be applied to enter low power idle state. 154 """ 155 # e.g. e310_sg3_idle 156 idle_overlay = device_info['product'] + '_idle' 157 return idle_overlay 158 159 ########################################################################### 160 # Ctor and device initialization tasks 161 ########################################################################### 162 def __init__(self, args): 163 """ 164 Does partial initialization which loads low power idle image 165 """ 166 self._time_source = None 167 self._gpsd = None 168 self.dboards = [] 169 self.dboard = None 170 self.mboard_regs_control = None 171 self._xport_mgrs = {} 172 self._initialization_status = "" 173 self._device_initialized = False 174 self.args_cached = args 175 # This will load the regular image to obtain all FPGA info 176 super(e31x, self).__init__() 177 args = self._update_default_args(args) 178 # Permanently store the value from mpm.conf: 179 self._do_not_reload_default = \ 180 str2bool(args.get("no_reload_fpga", E310_DEFAULT_DONT_RELOAD_FPGA)) 181 # This flag can change depending on UHD args: 182 self._do_not_reload = self._do_not_reload_default 183 # If we don't want to reload, we'll complete initialization now: 184 if self._do_not_reload: 185 try: 186 self.log.info("Not reloading FPGA images!") 187 self._init_normal() 188 except BaseException as ex: 189 self.log.error("Failed to initialize motherboard: %s", str(ex)) 190 self._initialization_status = str(ex) 191 self._device_initialized = False 192 else: # Otherwise, put the USRP into low-power mode: 193 # Start clean by removing MPM-owned overlays. 194 active_overlays = self.list_active_overlays() 195 mpm_overlays = self.list_owned_overlays() 196 for overlay in active_overlays: 197 if overlay in mpm_overlays: 198 dtoverlay.rm_overlay(overlay) 199 # Apply idle overlay on boot to save power until 200 # an application tries to use the device. 201 self.apply_idle_overlay() 202 self._device_initialized = False 203 self._init_gps_sensors() 204 205 def _init_gps_sensors(self): 206 """ 207 Init and register the GPSd Iface and related sensor functions 208 209 Note: The GPS chip is not connected to the FPGA, so this is initialized 210 regardless of the idle state 211 """ 212 self.log.trace("Initializing GPSd interface") 213 self._gpsd = GPSDIfaceExtension() 214 new_methods = self._gpsd.extend(self) 215 for method_name in new_methods: 216 try: 217 # Extract the sensor name from the getter 218 sensor_name = re.search(r"get_(.*)_sensor", method_name).group(1) 219 # Register it with the MB sensor framework 220 self.mboard_sensor_callback_map[sensor_name] = method_name 221 self.log.trace("Adding %s sensor function", sensor_name) 222 except AttributeError: 223 # re.search will return None is if can't find the sensor name 224 self.log.warning("Error while registering sensor function: %s", method_name) 225 226 227 def _init_normal(self): 228 """ 229 Does full initialization. This gets called during claim(), because the 230 E310 usually gets freshly initialized on every UHD session for power 231 usage reasons, unless no_reload_fpga was provided in mpm.conf. 232 """ 233 if self._device_initialized: 234 return 235 if self.is_idle(): 236 self.remove_idle_overlay() 237 self.overlay_apply() 238 self.init_dboards(self.args_cached) 239 if not self._device_initialized: 240 # Don't try and figure out what's going on. Just give up. 241 return 242 self._time_source = None 243 self.dboard = self.dboards[E310_DBOARD_SLOT_IDX] 244 try: 245 self._init_peripherals(self.args_cached) 246 except BaseException as ex: 247 self.log.error("Failed to initialize motherboard: %s", str(ex)) 248 self._initialization_status = str(ex) 249 self._device_initialized = False 250 251 def _init_dboards(self, dboard_infos, override_dboard_pids, default_args): 252 """ 253 Initialize all the daughterboards 254 255 dboard_infos -- List of dictionaries as returned from 256 PeriphManagerBase._get_dboard_eeprom_info() 257 override_dboard_pids -- List of dboard PIDs to force 258 default_args -- Default args 259 """ 260 # Overriding DB PIDs doesn't work here, the DB is coupled to the MB 261 if override_dboard_pids: 262 raise NotImplementedError("Can't override dboard pids") 263 # We have only one dboard 264 dboard_info = dboard_infos[0] 265 # Set up the SPI nodes 266 assert len(self.dboard_spimaster_addrs) == 1 267 spi_nodes = get_spidev_nodes(self.dboard_spimaster_addrs[0]) 268 assert spi_nodes 269 self.log.trace("Found spidev nodes: {0}".format(str(spi_nodes))) 270 dboard_info.update({ 271 'spi_nodes': spi_nodes, 272 'default_args': default_args, 273 }) 274 self.dboards.append(E31x_db(E310_DBOARD_SLOT_IDX, **dboard_info)) 275 self.log.info("Found %d daughterboard(s).", len(self.dboards)) 276 277 def _check_fpga_compat(self): 278 " Throw an exception if the compat numbers don't match up " 279 actual_compat = self.mboard_regs_control.get_compat_number() 280 self.log.debug("Actual FPGA compat number: {:d}.{:d}".format( 281 actual_compat[0], actual_compat[1] 282 )) 283 assert_compat_number( 284 E310_FPGA_COMPAT, 285 self.mboard_regs_control.get_compat_number(), 286 component="FPGA", 287 fail_on_old_minor=True, 288 log=self.log 289 ) 290 291 def _init_ref_clock_and_time(self, default_args): 292 """ 293 Initialize clock and time sources. After this function returns, the 294 reference signals going to the FPGA are valid. 295 """ 296 if not self.dboards: 297 self.log.warning( 298 "No dboards found, skipping setting clock and time source " 299 "configuration." 300 ) 301 self._time_source = E310_DEFAULT_TIME_SOURCE 302 else: 303 self.set_clock_source( 304 default_args.get('clock_source', E310_DEFAULT_CLOCK_SOURCE) 305 ) 306 self.set_time_source( 307 default_args.get('time_source', E310_DEFAULT_TIME_SOURCE) 308 ) 309 310 def _init_peripherals(self, args): 311 """ 312 Turn on all peripherals. This may throw an error on failure, so make 313 sure to catch it. 314 315 Peripherals are initialized in the order of least likely to fail, to most 316 likely. 317 """ 318 # Sanity checks 319 assert self.mboard_info.get('product') in self.pids.values(), \ 320 "Device product could not be determined!" 321 # Init Mboard Regs 322 self.mboard_regs_control = MboardRegsControl( 323 self.mboard_regs_label, self.log) 324 self.mboard_regs_control.get_git_hash() 325 self.mboard_regs_control.get_build_timestamp() 326 self._check_fpga_compat() 327 self._update_fpga_type() 328 # Init clocking 329 self._init_ref_clock_and_time(args) 330 # Init CHDR transports 331 self._xport_mgrs = { 332 'udp': E310XportMgrUDP(self.log, args) 333 } 334 # Init complete. 335 self.log.debug("mboard info: {}".format(self.mboard_info)) 336 337 def _read_mboard_eeprom(self): 338 """ 339 Read out mboard EEPROM. 340 Returns a tuple: (eeprom_dict, eeprom_rawdata), where the the former is 341 a de-serialized dictionary representation of the data, and the latter 342 is a binary string with the raw data. 343 344 If no EEPROM is defined, returns empty values. 345 """ 346 eeprom_path = \ 347 get_eeprom_paths(self.mboard_eeprom_addr)[self.mboard_eeprom_path_index] 348 if not eeprom_path: 349 self.log.error("Could not identify EEPROM path for %s!", 350 self.mboard_eeprom_addr) 351 return {}, b'' 352 self.log.trace("MB EEPROM: Using path {}".format(eeprom_path)) 353 (eeprom_head, eeprom_rawdata) = e31x_legacy_eeprom.read_eeprom( 354 True, # is_motherboard 355 eeprom_path, 356 self.mboard_eeprom_offset, 357 e31x_legacy_eeprom.MboardEEPROM.eeprom_header_format, 358 e31x_legacy_eeprom.MboardEEPROM.eeprom_header_keys, 359 self.mboard_eeprom_max_len 360 ) 361 self.log.trace("Read %d bytes of EEPROM data.", len(eeprom_rawdata)) 362 return eeprom_head, eeprom_rawdata 363 364 def _get_dboard_eeprom_info(self): 365 """ 366 Read back EEPROM info from the daughterboards 367 """ 368 assert self.dboard_eeprom_addr 369 self.log.trace("Identifying dboard EEPROM paths from `{}'..." 370 .format(self.dboard_eeprom_addr)) 371 dboard_eeprom_path = \ 372 get_eeprom_paths(self.dboard_eeprom_addr)[self.dboard_eeprom_path_index] 373 self.log.trace("Using dboard EEPROM paths: {}".format(dboard_eeprom_path)) 374 self.log.debug("Reading EEPROM info for dboard...") 375 dboard_eeprom_md, dboard_eeprom_rawdata = e31x_legacy_eeprom.read_eeprom( 376 False, # is not motherboard. 377 dboard_eeprom_path, 378 self.dboard_eeprom_offset, 379 e31x_legacy_eeprom.DboardEEPROM.eeprom_header_format, 380 e31x_legacy_eeprom.DboardEEPROM.eeprom_header_keys, 381 self.dboard_eeprom_max_len 382 ) 383 self.log.trace("Read %d bytes of dboard EEPROM data.", 384 len(dboard_eeprom_rawdata)) 385 db_pid = dboard_eeprom_md.get('pid') 386 if db_pid is None: 387 self.log.warning("No DB PID found in dboard EEPROM!") 388 else: 389 self.log.debug("Found DB PID in EEPROM: 0x{:04X}".format(db_pid)) 390 return [{ 391 'eeprom_md': dboard_eeprom_md, 392 'eeprom_rawdata': dboard_eeprom_rawdata, 393 'pid': db_pid, 394 }] 395 396 ########################################################################### 397 # Session init and deinit 398 ########################################################################### 399 def claim(self): 400 """ 401 Fully initializes a device when the rpc_server claim() 402 gets called to revive the device from idle state to be used 403 by an UHD application 404 """ 405 super(e31x, self).claim() 406 try: 407 self._init_normal() 408 except BaseException as ex: 409 self.log.error("e31x claim() failed: %s", str(ex)) 410 411 def init(self, args): 412 """ 413 Calls init() on the parent class, and updates time/clock source. 414 """ 415 if not self._device_initialized: 416 self.log.warning( 417 "Cannot run init(), device was never fully initialized!") 418 return False 419 if args.get("clock_source", "") != "": 420 self.set_clock_source(args.get("clock_source")) 421 if args.get("time_source", "") != "": 422 self.set_time_source(args.get("time_source")) 423 if "no_reload_fpga" in args: 424 self._do_not_reload = \ 425 str2bool(args.get("no_reload_fpga")) or args.get("no_reload_fpga") == "" 426 result = super(e31x, self).init(args) 427 for xport_mgr in itervalues(self._xport_mgrs): 428 xport_mgr.init(args) 429 return result 430 431 def apply_idle_overlay(self): 432 """ 433 Load all overlays required to go into idle power savings mode. 434 """ 435 idle_overlay = self.get_idle_dt_overlay(self.device_info) 436 self.log.debug("Motherboard requests device tree overlay for Idle power savings mode: {}".format( 437 idle_overlay 438 )) 439 dtoverlay.apply_overlay_safe(idle_overlay) 440 441 def remove_idle_overlay(self): 442 """ 443 Remove idle mode overlay. 444 """ 445 idle_overlay = self.get_idle_dt_overlay(self.device_info) 446 self.log.trace("Removing Idle overlay: {}".format( 447 idle_overlay 448 )) 449 dtoverlay.rm_overlay(idle_overlay) 450 451 def list_owned_overlays(self): 452 """ 453 Lists all overlays that can be possibly applied by MPM. 454 """ 455 all_overlays = self.list_required_dt_overlays(self.device_info) 456 all_overlays.append(self.get_idle_dt_overlay(self.device_info)) 457 return all_overlays 458 459 def deinit(self): 460 """ 461 Clean up after a UHD session terminates. 462 """ 463 if not self._device_initialized: 464 self.log.warning( 465 "Cannot run deinit(), device was never fully initialized!") 466 return 467 super(e31x, self).deinit() 468 for xport_mgr in itervalues(self._xport_mgrs): 469 xport_mgr.deinit() 470 if not self._do_not_reload: 471 self.tear_down() 472 # Reset back to value from _default_args (mpm.conf) 473 self._do_not_reload = self._do_not_reload_default 474 475 def tear_down(self): 476 """ 477 Tear down all members that need to be specially handled before 478 deconstruction. 479 For E310, this means the overlay. 480 """ 481 self.log.trace("Tearing down E310 device...") 482 self.dboards = [] 483 self.dboard.tear_down() 484 self.dboard = None 485 self.mboard_regs_control = None 486 self._device_initialized = False 487 active_overlays = self.list_active_overlays() 488 self.log.trace("E310 has active device tree overlays: {}".format( 489 active_overlays 490 )) 491 for overlay in active_overlays: 492 dtoverlay.rm_overlay(overlay) 493 self.apply_idle_overlay() 494 self.log.debug("Teardown complete!") 495 496 def is_idle(self): 497 """ 498 Determine if the device is in the idle state. 499 """ 500 active_overlays = self.list_active_overlays() 501 idle_overlay = self.get_idle_dt_overlay(self.device_info) 502 is_idle = idle_overlay in active_overlays 503 if is_idle: 504 self.log.trace("Found idle overlay: %s", idle_overlay) 505 return is_idle 506 507 508 ########################################################################### 509 # Transport API 510 ########################################################################### 511 def get_chdr_link_types(self): 512 """ 513 See PeriphManagerBase.get_chdr_link_types() for docs. 514 """ 515 assert self.mboard_info['rpc_connection'] in ('remote', 'local') 516 return ["udp"] 517 518 def get_chdr_link_options(self, xport_type): 519 """ 520 See PeriphManagerBase.get_chdr_link_options() for docs. 521 """ 522 if xport_type not in self._xport_mgrs: 523 self.log.warning("Can't get link options for unknown link type: `{}'." 524 .format(xport_type)) 525 return [] 526 if xport_type == "udp": 527 return self._xport_mgrs[xport_type].get_chdr_link_options( 528 self.mboard_info['rpc_connection']) 529 else: 530 return self._xport_mgrs[xport_type].get_chdr_link_options() 531 532 ########################################################################### 533 # Device info 534 ########################################################################### 535 def get_device_info_dyn(self): 536 """ 537 Append the device info with current IP addresses. 538 """ 539 if not self._device_initialized: 540 return {} 541 device_info = {} 542 device_info.update({ 543 'fpga_version': "{}.{}".format( 544 *self.mboard_regs_control.get_compat_number()), 545 'fpga_version_hash': "{:x}.{}".format( 546 *self.mboard_regs_control.get_git_hash()), 547 'fpga': self.updateable_components.get('fpga', {}).get('type', ""), 548 }) 549 return device_info 550 551 ########################################################################### 552 # Clock/Time API 553 ########################################################################### 554 def get_clock_sources(self): 555 " Lists all available clock sources. " 556 self.log.trace("Listing available clock sources...") 557 return ('internal',) 558 559 def get_clock_source(self): 560 " Returns the currently selected clock source " 561 return E310_DEFAULT_CLOCK_SOURCE 562 563 def set_clock_source(self, *args): 564 """ 565 Note: E310 only supports one clock source ('internal'), so no need to do 566 an awful lot here. 567 """ 568 clock_source = args[0] 569 assert clock_source in self.get_clock_sources(), \ 570 "Cannot set to invalid clock source: {}".format(clock_source) 571 572 def get_time_sources(self): 573 " Returns list of valid time sources " 574 return ['internal', 'external', 'gpsdo'] 575 576 def get_time_source(self): 577 " Return the currently selected time source " 578 return self._time_source 579 580 def set_time_source(self, time_source): 581 " Set a time source " 582 assert time_source in self.get_time_sources(), \ 583 "Cannot set to invalid time source: {}".format(time_source) 584 if time_source == self.get_time_source(): 585 self.log.trace("Nothing to do -- time source already set.") 586 return 587 self._time_source = time_source 588 self.mboard_regs_control.set_time_source(time_source) 589 590 ########################################################################### 591 # GPIO API 592 ########################################################################### 593 def get_gpio_banks(self): 594 """ 595 Returns a list of GPIO banks over which MPM has any control 596 """ 597 return E310_GPIO_BANKS 598 599 def get_gpio_srcs(self, bank): 600 """ 601 Return a list of valid GPIO sources for a given bank 602 """ 603 assert bank in self.get_gpio_banks(), "Invalid GPIO bank: {}".format(bank) 604 return E310_GPIO_SRCS 605 606 def get_gpio_src(self, bank): 607 """ 608 Return the currently selected GPIO source for a given bank. The return 609 value is a list of strings. The length of the vector is identical to 610 the number of controllable GPIO pins on this bank. 611 """ 612 assert bank in self.get_gpio_banks(), "Invalid GPIO bank: {}".format(bank) 613 gpio_master_reg = self.mboard_regs_control.get_fp_gpio_master() 614 gpio_radio_src_reg = self.mboard_regs_control.get_fp_gpio_radio_src() 615 def get_gpio_src_i(gpio_pin_index): 616 """ 617 Return the current radio source given a pin index. 618 """ 619 if gpio_master_reg & (1 << gpio_pin_index): 620 return E310_GPIO_SRC_PS 621 radio_src = (gpio_radio_src_reg >> (2 * gpio_pin_index)) & 0b11 622 assert radio_src in (0, 1) 623 return E310_GPIO_SRCS[radio_src] 624 return [get_gpio_src_i(i) for i in range(E310_FPGPIO_WIDTH)] 625 626 def set_gpio_src(self, bank, src): 627 """ 628 Set the GPIO source for a given bank. 629 """ 630 assert bank in self.get_gpio_banks(), "Invalid GPIO bank: {}".format(bank) 631 assert len(src) == E310_FPGPIO_WIDTH, \ 632 "Invalid number of GPIO sources!" 633 gpio_master_reg = 0x00 634 gpio_radio_src_reg = self.mboard_regs_control.get_fp_gpio_radio_src() 635 for src_index, src_name in enumerate(src): 636 if src_name not in self.get_gpio_srcs(bank): 637 raise RuntimeError( 638 "Invalid GPIO source name `{}' at bit position {}!" 639 .format(src_name, src_index)) 640 gpio_master_flag = (src_name == E310_GPIO_SRC_PS) 641 gpio_master_reg = gpio_master_reg | (gpio_master_flag << src_index) 642 if gpio_master_flag: 643 continue 644 # If PS is not the master, we also need to update the radio source: 645 radio_index = E310_GPIO_SRCS.index(src_name) 646 gpio_radio_src_reg = gpio_radio_src_reg | (radio_index << (2*src_index)) 647 self.log.trace("Updating GPIO source: master==0x{:02X} radio_src={:03X}" 648 .format(gpio_master_reg, gpio_radio_src_reg)) 649 self.mboard_regs_control.set_fp_gpio_master(gpio_master_reg) 650 self.mboard_regs_control.set_fp_gpio_radio_src(gpio_radio_src_reg) 651 652 ########################################################################### 653 # Hardware peripheral controls 654 ########################################################################### 655 def set_channel_mode(self, channel_mode): 656 "Set channel mode in FPGA and select which tx channel to use" 657 self.mboard_regs_control.set_channel_mode(channel_mode) 658 659 ########################################################################### 660 # Sensors 661 ########################################################################### 662 def get_ref_lock_sensor(self): 663 """ 664 Return main refclock lock status. In the FPGA, this is the reflck output 665 of the ppsloop module. 666 """ 667 self.log.trace("Querying ref lock status.") 668 lock_status = bool(self.mboard_regs_control.get_refclk_lock()) 669 return { 670 'name': 'ref_locked', 671 'type': 'BOOLEAN', 672 'unit': 'locked' if lock_status else 'unlocked', 673 'value': str(lock_status).lower(), 674 } 675 676 def get_gps_lock_sensor(self): 677 """ 678 Get lock status of GPS as a sensor dict 679 """ 680 gps_locked = self._gpsd.get_gps_lock() 681 return { 682 'name': 'gps_lock', 683 'type': 'BOOLEAN', 684 'unit': 'locked' if gps_locked else 'unlocked', 685 'value': str(gps_locked).lower(), 686 } 687 688 def get_mb_temp_sensor(self): 689 """ 690 Get temperature sensor reading of the E310. 691 """ 692 self.log.trace("Reading temperature.") 693 temp = '-1' 694 raw_val = {} 695 data_probes = ['temp1_input'] 696 try: 697 for data_probe in data_probes: 698 raw_val[data_probe] = read_sysfs_sensors_value( 699 'jc-42.4-temp', data_probe, 'hwmon', 'name')[0] 700 temp = str(raw_val['temp1_input'] / 1000) 701 except ValueError: 702 self.log.warning("Error when converting temperature value") 703 except KeyError: 704 self.log.warning("Can't read MB temperature!") 705 return { 706 'name': 'temp_mb', 707 'type': 'REALNUM', 708 'unit': 'C', 709 'value': temp 710 } 711 712 def get_fpga_temp_sensor(self): 713 """ 714 Get temperature sensor reading of the E310. 715 """ 716 self.log.trace("Reading temperature.") 717 temp = '-1' 718 raw_val = {} 719 data_probes = ['in_temp0_raw', 'in_temp0_scale', 'in_temp0_offset'] 720 try: 721 for data_probe in data_probes: 722 raw_val[data_probe] = read_sysfs_sensors_value( 723 'xadc', data_probe, 'iio', 'name')[0] 724 temp = str((raw_val['in_temp0_raw'] + raw_val['in_temp0_offset']) \ 725 * raw_val['in_temp0_scale'] / 1000) 726 except ValueError: 727 self.log.warning("Error when converting temperature value") 728 except KeyError: 729 self.log.warning("Can't read FPGA temperature!") 730 return { 731 'name': 'temp_fpga', 732 'type': 'REALNUM', 733 'unit': 'C', 734 'value': temp 735 } 736 737 ########################################################################### 738 # EEPROMs 739 ########################################################################### 740 def get_mb_eeprom(self): 741 """ 742 Return a dictionary with EEPROM contents. 743 744 All key/value pairs are string -> string. 745 746 We don't actually return the EEPROM contents, instead, we return the 747 mboard info again. This filters the EEPROM contents to what we think 748 the user wants to know/see. 749 """ 750 return self.mboard_info 751 752 def set_mb_eeprom(self, eeprom_vals): 753 """ 754 See PeriphManagerBase.set_mb_eeprom() for docs. 755 """ 756 self.log.warn("Called set_mb_eeprom(), but not implemented!") 757 758 def get_db_eeprom(self, dboard_idx): 759 """ 760 See PeriphManagerBase.get_db_eeprom() for docs. 761 """ 762 if dboard_idx != E310_DBOARD_SLOT_IDX: 763 self.log.warn("Trying to access invalid dboard index {}. " 764 "Using the only dboard.".format(dboard_idx)) 765 db_eeprom_data = copy.copy(self.dboard.device_info) 766 return db_eeprom_data 767 768 def set_db_eeprom(self, dboard_idx, eeprom_data): 769 """ 770 See PeriphManagerBase.set_db_eeprom() for docs. 771 """ 772 self.log.warn("Called set_db_eeprom(), but not implemented!") 773 774 ########################################################################### 775 # Component updating 776 ########################################################################### 777 # Note: Component updating functions defined by ZynqComponents 778 @no_rpc 779 def _update_fpga_type(self): 780 """Update the fpga type stored in the updateable components""" 781 fpga_type = "" # FIXME 782 self.log.debug("Updating mboard FPGA type info to {}".format(fpga_type)) 783 self.updateable_components['fpga']['type'] = fpga_type 784 785 ####################################################################### 786 # Timekeeper API 787 ####################################################################### 788 def get_clocks(self): 789 """ 790 Gets the RFNoC-related clocks present in the FPGA design 791 """ 792 return [ 793 { 794 'name': 'radio_clk', 795 'freq': str(self.dboard.get_master_clock_rate()), 796 'mutable': 'true' 797 }, 798 { 799 'name': 'bus_clk', 800 'freq': str(100e6), 801 } 802 ] 803