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