diff options
Diffstat (limited to 'mpm/python/usrp_mpm')
| -rw-r--r-- | mpm/python/usrp_mpm/CMakeLists.txt | 2 | ||||
| -rw-r--r-- | mpm/python/usrp_mpm/dboard_manager/neon.py | 4 | ||||
| -rw-r--r-- | mpm/python/usrp_mpm/dboard_manager/rhodium.py | 1 | ||||
| -rw-r--r-- | mpm/python/usrp_mpm/ethdispatch.py | 102 | ||||
| -rw-r--r-- | mpm/python/usrp_mpm/ethtable.py | 187 | ||||
| -rw-r--r-- | mpm/python/usrp_mpm/periph_manager/base.py | 264 | ||||
| -rw-r--r-- | mpm/python/usrp_mpm/periph_manager/e31x.py | 154 | ||||
| -rw-r--r-- | mpm/python/usrp_mpm/periph_manager/e31x_periphs.py | 166 | ||||
| -rw-r--r-- | mpm/python/usrp_mpm/periph_manager/e320.py | 188 | ||||
| -rw-r--r-- | mpm/python/usrp_mpm/periph_manager/e320_periphs.py | 164 | ||||
| -rw-r--r-- | mpm/python/usrp_mpm/periph_manager/n3xx.py | 199 | ||||
| -rw-r--r-- | mpm/python/usrp_mpm/periph_manager/n3xx_periphs.py | 67 | ||||
| -rw-r--r-- | mpm/python/usrp_mpm/rpc_server.py | 2 | ||||
| -rw-r--r-- | mpm/python/usrp_mpm/xports/xportmgr_liberio.py | 69 | ||||
| -rw-r--r-- | mpm/python/usrp_mpm/xports/xportmgr_udp.py | 218 | 
15 files changed, 957 insertions, 830 deletions
| diff --git a/mpm/python/usrp_mpm/CMakeLists.txt b/mpm/python/usrp_mpm/CMakeLists.txt index 6b6d99a30..e4332a617 100644 --- a/mpm/python/usrp_mpm/CMakeLists.txt +++ b/mpm/python/usrp_mpm/CMakeLists.txt @@ -16,7 +16,7 @@ set(USRP_MPM_TOP_FILES      ${CMAKE_CURRENT_SOURCE_DIR}/discovery.py      ${CMAKE_CURRENT_SOURCE_DIR}/eeprom.py      ${CMAKE_CURRENT_SOURCE_DIR}/e31x_legacy_eeprom.py -    ${CMAKE_CURRENT_SOURCE_DIR}/ethtable.py +    ${CMAKE_CURRENT_SOURCE_DIR}/ethdispatch.py      ${CMAKE_CURRENT_SOURCE_DIR}/gpsd_iface.py      ${CMAKE_CURRENT_SOURCE_DIR}/liberiotable.py      ${CMAKE_CURRENT_SOURCE_DIR}/mpmlog.py diff --git a/mpm/python/usrp_mpm/dboard_manager/neon.py b/mpm/python/usrp_mpm/dboard_manager/neon.py index 00438503d..7f9abd5ce 100644 --- a/mpm/python/usrp_mpm/dboard_manager/neon.py +++ b/mpm/python/usrp_mpm/dboard_manager/neon.py @@ -286,8 +286,8 @@ class Neon(DboardManagerBase):          return {              'name': 'ad9361_lock',              'type': 'BOOLEAN', -           'unit': 'locked' if lo_locked else 'unlocked', -           'value': str(lo_locked).lower(), +            'unit': 'locked' if lo_locked else 'unlocked', +            'value': str(lo_locked).lower(),          }      def get_catalina_temp_sensor(self, _): diff --git a/mpm/python/usrp_mpm/dboard_manager/rhodium.py b/mpm/python/usrp_mpm/dboard_manager/rhodium.py index b53b7f5bb..85f7b2b60 100644 --- a/mpm/python/usrp_mpm/dboard_manager/rhodium.py +++ b/mpm/python/usrp_mpm/dboard_manager/rhodium.py @@ -20,7 +20,6 @@ from usrp_mpm.dboard_manager.adc_rh import AD9695Rh  from usrp_mpm.dboard_manager.dac_rh import DAC37J82Rh  from usrp_mpm.mpmlog import get_logger  from usrp_mpm.sys_utils.uio import open_uio -from usrp_mpm.sys_utils.udev import get_eeprom_paths  from usrp_mpm.user_eeprom import BfrfsEEPROM diff --git a/mpm/python/usrp_mpm/ethdispatch.py b/mpm/python/usrp_mpm/ethdispatch.py new file mode 100644 index 000000000..7c7437225 --- /dev/null +++ b/mpm/python/usrp_mpm/ethdispatch.py @@ -0,0 +1,102 @@ +# +# Copyright 2017-2018 Ettus Research, a National Instruments Company +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +""" +Ethernet dispatcher table control +""" + +from builtins import str +from builtins import object +import netaddr +from usrp_mpm.mpmlog import get_logger +from usrp_mpm.sys_utils.uio import UIO + + +class EthDispatcherCtrl(object): +    """ +    Controls an Ethernet dispatcher. +    """ +    DEFAULT_VITA_PORT = (49153, 49154) +    # Address offsets: +    ETH_IP_OFFSET = 0x0000 +    ETH_PORT_OFFSET = 0x0004 +    FORWARD_ETH_BCAST_OFFSET = 0x0008 +    BRIDGE_MAC_LO_OFFSET = 0x0010 +    BRIDGE_MAC_HI_OFFSET = 0x0014 +    BRIDGE_IP_OFFSET = 0x0018 +    BRIDGE_PORT_OFFSET = 0x001c +    BRIDGE_ENABLE_OFFSET = 0x0020 + + +    def __init__(self, label): +        self.log = get_logger(label) +        self._regs = UIO(label=label, read_only=False) +        self.poke32 = self._regs.poke32 +        self.peek32 = self._regs.peek32 + +    def set_bridge_mode(self, bridge_mode): +        " Enable/Disable Bridge Mode " +        self.log.trace("Bridge Mode {}".format( +            "Enabled" if bridge_mode else "Disabled" +        )) +        self.poke32(self.BRIDGE_ENABLE_OFFSET, int(bridge_mode)) + +    def set_bridge_mac_addr(self, mac_addr): +        """ +        Set the bridge MAC address for this Ethernet dispatcher. +        Outgoing packets will have this MAC address. +        """ +        self.log.debug("Setting bridge MAC address to `{}'".format(mac_addr)) +        mac_addr_int = int(netaddr.EUI(mac_addr)) +        self.log.trace("Writing to address 0x{:04X}: 0x{:04X}".format( +            self.BRIDGE_MAC_LO_OFFSET, mac_addr_int & 0xFFFFFFFF +        )) +        self.poke32(self.BRIDGE_MAC_LO_OFFSET, mac_addr_int & 0xFFFFFFFF) +        self.log.trace("Writing to address 0x{:04X}: 0x{:04X}".format( +            self.BRIDGE_MAC_HI_OFFSET, mac_addr_int >> 32 +        )) +        self.poke32(self.BRIDGE_MAC_HI_OFFSET, mac_addr_int >> 32) + +    def set_ipv4_addr(self, ip_addr, bridge_en=False): +        """ +        Set the own IPv4 address for this Ethernet dispatcher. +        Outgoing packets will have this IP address. +        """ +        if bridge_en: +            own_ip_offset = self.BRIDGE_IP_OFFSET +        else: +            own_ip_offset = self.ETH_IP_OFFSET +        self.log.debug("Setting my own IP address to `{}'".format(ip_addr)) +        ip_addr_int = int(netaddr.IPAddress(ip_addr)) +        with self._regs: +            self.poke32(own_ip_offset, ip_addr_int) + +    def set_vita_port(self, port_value=None, port_idx=None, bridge_en=False): +        """ +        Set the port that is used for incoming VITA traffic. This is used to +        distinguish traffic that goes to the FPGA from that going to the ARM. +        """ +        port_idx = port_idx or 0 +        port_value = port_value or self.DEFAULT_VITA_PORT[port_idx] +        assert port_idx in (0,) #FIXME: Fix port_idx = 1 +        if bridge_en: +            port_reg_addr = self.BRIDGE_PORT_OFFSET +        else: +            port_reg_addr = self.ETH_PORT_OFFSET +        with self._regs: +            self.poke32(port_reg_addr, port_value) +        self.log.debug("Setting RFNOC UDP port to `{}'".format(port_value)) + +    def set_forward_policy(self, forward_eth, forward_bcast): +        """ +        Forward Ethernet packet not matching OWN_IP to CROSSOVER +        Forward broadcast packet to CPU and CROSSOVER +        """ +        reg_value = int(bool(forward_eth) << 1) | int(bool(forward_bcast)) +        self.log.trace("Writing to address 0x{:04X}: 0x{:04X}".format( +            self.FORWARD_ETH_BCAST_OFFSET, reg_value +        )) +        with self._regs: +            self.poke32(self.FORWARD_ETH_BCAST_OFFSET, reg_value) diff --git a/mpm/python/usrp_mpm/ethtable.py b/mpm/python/usrp_mpm/ethtable.py deleted file mode 100644 index 4a6566987..000000000 --- a/mpm/python/usrp_mpm/ethtable.py +++ /dev/null @@ -1,187 +0,0 @@ -# -# Copyright 2017-2018 Ettus Research, a National Instruments Company -# -# SPDX-License-Identifier: GPL-3.0-or-later -# -""" -Ethernet dispatcher table control -""" - -from builtins import str -from builtins import object -import netaddr -from usrp_mpm.mpmlog import get_logger -from usrp_mpm.sys_utils.uio import UIO -from usrp_mpm.sys_utils.net import get_mac_addr - - -class EthDispatcherTable(object): -    """ -    Controls an Ethernet dispatcher table. -    """ -    DEFAULT_VITA_PORT = (49153, 49154) -    # Address offsets: -    ETH_IP_OFFSET = 0x0000 -    ETH_PORT_OFFSET = 0x0004 -    FORWARD_ETH_BCAST_OFFSET = 0x0008 -    BRIDGE_MAC_LO_OFFSET = 0x0010 -    BRIDGE_MAC_HI_OFFSET = 0x0014 -    BRIDGE_IP_OFFSET = 0x0018 -    BRIDGE_PORT_OFFSET = 0x001c -    BRIDGE_ENABLE_OFFSET = 0x0020 -    SID_IP_OFFSET = 0x1000 -    SID_PORT_MAC_HI_OFFSET = 0x1400 -    SID_MAC_LO_OFFSET = 0x1800 -    LADDR_IP_OFFSET = 0x1D00 -    LADDR_PORT_MAC_HI_OFFSET = 0x1E00 -    LADDR_MAC_LO_OFFSET = 0x1F00 - - -    def __init__(self, label): -        self.log = get_logger(label) -        self._regs = UIO(label=label, read_only=False) -        self.poke32 = self._regs.poke32 -        self.peek32 = self._regs.peek32 - -    def set_bridge_mode(self, bridge_mode): -        " Enable/Disable Bridge Mode " -        self.log.trace("Bridge Mode {}".format( -            "Enabled" if bridge_mode else "Disabled" -        )) -        self.poke32(self.BRIDGE_ENABLE_OFFSET, int(bridge_mode)) - -    def set_bridge_mac_addr(self, mac_addr): -        """ -        Set the bridge MAC address for this Ethernet dispatcher. -        Outgoing packets will have this MAC address. -        """ -        self.log.debug("Setting bridge MAC address to `{}'".format(mac_addr)) -        mac_addr_int = int(netaddr.EUI(mac_addr)) -        self.log.trace("Writing to address 0x{:04X}: 0x{:04X}".format( -            self.BRIDGE_MAC_LO_OFFSET, mac_addr_int & 0xFFFFFFFF -        )) -        self.poke32(self.BRIDGE_MAC_LO_OFFSET, mac_addr_int & 0xFFFFFFFF) -        self.log.trace("Writing to address 0x{:04X}: 0x{:04X}".format( -            self.BRIDGE_MAC_HI_OFFSET, mac_addr_int >> 32 -        )) -        self.poke32(self.BRIDGE_MAC_HI_OFFSET, mac_addr_int >> 32) - -    def set_ipv4_addr(self, ip_addr, bridge_en=False): -        """ -        Set the own IPv4 address for this Ethernet dispatcher. -        Outgoing packets will have this IP address. -        """ -        if bridge_en: -            own_ip_offset = self.BRIDGE_IP_OFFSET -        else: -            own_ip_offset = self.ETH_IP_OFFSET -        self.log.debug("Setting my own IP address to `{}'".format(ip_addr)) -        ip_addr_int = int(netaddr.IPAddress(ip_addr)) -        with self._regs: -            self.poke32(own_ip_offset, ip_addr_int) - -    def set_vita_port(self, port_value=None, port_idx=None, bridge_en=False): -        """ -        Set the port that is used for incoming VITA traffic. This is used to -        distinguish traffic that goes to the FPGA from that going to the ARM. -        """ -        port_idx = port_idx or 0 -        port_value = port_value or self.DEFAULT_VITA_PORT[port_idx] -        assert port_idx in (0)                      #FIXME: Fix port_idx = 1 -        if bridge_en: -            port_reg_addr = self.BRIDGE_PORT_OFFSET -        else: -            port_reg_addr = self.ETH_PORT_OFFSET -        with self._regs: -            self.poke32(port_reg_addr, port_value) - -    def set_route(self, sid, ip_addr, udp_port, mac_addr=None): -        """ -        Sets up routing in the Ethernet dispatcher. From sid, only the -        destination part is important. After this call, any CHDR packet -        reaching this Ethernet dispatcher will get routed to `ip_addr' and -        `udp_port'. - -        It automatically looks up the MAC address of the destination unless a -        MAC address is given. - -        sid -- Full SID, but only destination part matters. -        ip_addr -- IPv4 destination address. String format ("1.2.3.4"). -        udp_addr -- Destination UDP port. -        mac_addr -- If given, this will replace an ARP lookup for the MAC -                    address. String format, ("aa:bb:cc:dd:ee:ff"), case does -                    not matter. -        """ -        udp_port = int(udp_port) -        if mac_addr is None: -            mac_addr = get_mac_addr(ip_addr) -        if mac_addr is None: -            self.log.error( -                "Could not resolve a MAC address for IP address `{}'".format(ip_addr) -            ) -        if sid.dst_addr == 0 or sid.dst_addr == 1: -            table_addr = sid.dst_ep -            ip_base_offset = self.SID_IP_OFFSET -            port_mac_hi_base_offset = self.SID_PORT_MAC_HI_OFFSET -            mac_lo_base_offset = self.SID_MAC_LO_OFFSET -            self.log.debug( -                "Routing SID `{sid}' (endpoint `{ep}') to IP address `{ip}', " \ -                "MAC address `{mac}', port `{port}'".format( -                    sid=str(sid), -                    ep=table_addr, -                    ip=ip_addr, -                    mac=mac_addr, -                    port=udp_port -                ) -            ) -        else: #populate local addr eth dispatch table -            table_addr = sid.dst_addr -            ip_base_offset = self.LADDR_IP_OFFSET -            port_mac_hi_base_offset = self.LADDR_PORT_MAC_HI_OFFSET -            mac_lo_base_offset = self.LADDR_MAC_LO_OFFSET -            self.log.debug( -                "Routing SID `{sid}' (local addr`{addr}') to IP address `{ip}', " \ -                "MAC address `{mac}', port `{port}'".format( -                    sid=str(sid), -                    addr=table_addr, -                    ip=ip_addr, -                    mac=mac_addr, -                    port=udp_port -                ) -            ) -        ip_addr_int = int(netaddr.IPAddress(ip_addr)) -        mac_addr_int = int(netaddr.EUI(mac_addr)) -        dst_offset = 4 * table_addr - -        def poke_and_trace(addr, data): -            " Do a poke32() and log.trace() " -            self.log.trace("Writing to address 0x{:04X}: 0x{:04X}".format( -                addr, data -            )) -            self.poke32(addr, data) - -        with self._regs: -            poke_and_trace( -                ip_base_offset + dst_offset, -                ip_addr_int -            ) -            poke_and_trace( -                mac_lo_base_offset + dst_offset, -                mac_addr_int & 0xFFFFFFFF, -            ) -            poke_and_trace( -                port_mac_hi_base_offset + dst_offset, -                (udp_port << 16) | (mac_addr_int >> 32) -            ) - -    def set_forward_policy(self, forward_eth, forward_bcast): -        """ -        Forward Ethernet packet not matching OWN_IP to CROSSOVER -        Forward broadcast packet to CPU and CROSSOVER -        """ -        reg_value = int(bool(forward_eth) << 1) | int(bool(forward_bcast)) -        self.log.trace("Writing to address 0x{:04X}: 0x{:04X}".format( -            self.FORWARD_ETH_BCAST_OFFSET, reg_value -        )) -        with self._regs: -            self.poke32(self.FORWARD_ETH_BCAST_OFFSET, reg_value) diff --git a/mpm/python/usrp_mpm/periph_manager/base.py b/mpm/python/usrp_mpm/periph_manager/base.py index be7600333..4ae30778a 100644 --- a/mpm/python/usrp_mpm/periph_manager/base.py +++ b/mpm/python/usrp_mpm/periph_manager/base.py @@ -177,7 +177,7 @@ class PeriphManagerBase(object):      ###########################################################################      def __init__(self):          # Note: args is a dictionary. -        assert len(self.pids) > 0 +        assert self.pids          assert self.mboard_eeprom_magic is not None          self.dboards = []          self._default_args = "" @@ -241,14 +241,17 @@ class PeriphManagerBase(object):          If no EEPROM is defined, returns empty values.          """ -        if len(self.mboard_eeprom_addr): +        if self.mboard_eeprom_addr:              self.log.trace("Reading EEPROM from address `{}'..."                             .format(self.mboard_eeprom_addr)) -            if (not get_eeprom_paths(self.mboard_eeprom_addr)): -                raise RuntimeError("No EEPROM found at address `{}'" -                    .format(self.mboard_eeprom_addr)) +            eeprom_paths = get_eeprom_paths(self.mboard_eeprom_addr) +            if not eeprom_paths: +                self.log.error("Could not identify EEPROM paths for %s!", +                               self.mboard_eeprom_addr) +                return {}, b'' +            self.log.trace("Found mboard EEPROM path: %s", eeprom_paths[0])              (eeprom_head, eeprom_rawdata) = eeprom.read_eeprom( -                get_eeprom_paths(self.mboard_eeprom_addr)[0], +                eeprom_paths[0],                  self.mboard_eeprom_offset,                  eeprom.MboardEEPROM.eeprom_header_format,                  eeprom.MboardEEPROM.eeprom_header_keys, @@ -512,6 +515,22 @@ class PeriphManagerBase(object):      ###########################################################################      # Misc device status controls and indicators      ########################################################################### +    def set_device_id(self, device_id): +        """ +        Sets the device ID for this motherboard. +        The device ID is used to identify the RFNoC components associated with +        this motherboard. +        """ +        raise NotImplementedError("set_device_id() not implemented.") + +    def get_device_id(self): +        """ +        Gets the device ID for this motherboard. +        The device ID is used to identify the RFNoC components associated with +        this motherboard. +        """ +        raise NotImplementedError("get_device_id() not implemented.") +      def get_init_status(self):          """          Returns the status of the device after its initialization (that happens @@ -592,6 +611,20 @@ class PeriphManagerBase(object):          """          return [dboard.device_info for dboard in self.dboards] +    @no_claim +    def get_proto_ver(self): +        """ +        Return RFNoC protocol version +        """ +        raise NotImplementedError("get_proto_ver() not implemented.") + +    @no_claim +    def get_chdr_width(self): +        """ +        Return RFNoC CHDR width +        """ +        raise NotImplementedError("get_chdr_width() not implemented.") +      ###########################################################################      # Component updating      ########################################################################### @@ -670,64 +703,10 @@ class PeriphManagerBase(object):              self.log.trace("Component info: {}".format(metadata))              # Convert all values to str              return dict([a, str(x)] for a, x in metadata.items()) -        else: -            self.log.trace("Component not found in updateable components: {}" -                           .format(component_name)) -            return {} - -    ########################################################################### -    # Crossbar control -    ########################################################################### -    @no_claim -    def get_num_xbars(self): -        """ -        Returns the number of crossbars instantiated in the current design -        """ -        return 1 # FIXME - -    @no_claim -    def get_num_blocks(self, xbar_index): -        """ -        Returns the number of blocks connected to crossbar with index -        xbar_index. - -        xbar_index -- The index of the crossbar that's being queried. -        docstring for get_num_blocks""" -        # FIXME udev lookup -        xbar_sysfs_path = '/sys/class/rfnoc_crossbar/crossbar{}/nports'.format( -            xbar_index -        ) -        return int(open(xbar_sysfs_path).read().strip()) - \ -                self.get_base_port(xbar_index) - -    @no_claim -    def get_base_port(self, xbar_index): -        """ -        Returns the index of the first port which is connected to an RFNoC -        block. Example: Assume there are two SFPs connected to the crossbar, and -        one DMA engine for CHDR traffic. The convention would be to connect -        those to ports 0, 1, and 2, respectively. This makes port 3 the first -        block to be connected to an RFNoC block. - -        xbar_index -- The index of the crossbar that's being queried -        """ -        return self.crossbar_base_port - -    def set_xbar_local_addr(self, xbar_index, local_addr): -        """ -        Program crossbar xbar_index to have the local address local_addr. -        """ -        # FIXME udev lookup -        xbar_sysfs_path = '/sys/class/rfnoc_crossbar/crossbar{}/local_addr'.format( -            xbar_index -        ) -        laddr_value = "0x{:X}".format(local_addr) -        self.log.trace("Setting local address for xbar {} to {}.".format( -            xbar_sysfs_path, laddr_value -        )) -        with open(xbar_sysfs_path, "w") as xbar_file: -            xbar_file.write(laddr_value) -        return True +        # else: +        self.log.trace("Component not found in updateable components: {}" +                       .format(component_name)) +        return {}      ##########################################################################      # Mboard Sensors @@ -823,86 +802,44 @@ class PeriphManagerBase(object):      #######################################################################      # Transport API      ####################################################################### -    def request_xport( -            self, -            dst_address, -            suggested_src_address, -            xport_type, -        ): -        """ -        When setting up a CHDR connection, this is the first call to be -        made. This function will return a list of dictionaries, each -        describing a way to open an CHDR connection. The list of dictionaries -        is sorted by preference, meaning that the caller should use the first -        option, if possible. -        All transports requested are bidirectional. - -        The callee must maintain a lock on the available CHDR xports. After -        calling request_xport(), the caller needs to pick one of the -        dictionaries, possibly amend data (e.g., if the connection is an -        Ethernet connection, then we need to know the source port, but more -        details on that in commit_xport()'s documentation). -        One way to implement a lock is to simply lock a mutex here and -        unlock it in commit_xport(), even though there are probably more -        nuanced solutions. +    def get_chdr_link_types(self): +        """ +        Return a list of ways how the UHD session can connect to this device to +        initiate CHDR traffic. -        Arguments: -        dst_sid -- The destination part of the connection, i.e., which -                   RFNoC block are we connecting to. Example: 0x0230 -        suggested_src_sid -- The source part of the connection, i.e., -                             what's the source address of packets going to -                             the destination at dst_sid. This is a -                             suggestion, MPM can override this. Example: -                             0x0001. -        xport_type -- One of the following strings: CTRL, ASYNC_MSG, -                      TX_DATA, RX_DATA. See also xports_type_t in UHD. - -        The return value is a list of dictionaries. Every dictionary has -        the following key/value pairs: -        - type: Type of transport, e.g., "UDP", "liberio". -        - ipv4 (UDP only): IPv4 address to connect to. -        - port (UDP only): IP port to connect to. -        - send_sid: String version of the SID used for this transport. This is -                    the definitive version of the SID, the suggested_src_address -                    can be ignored at this point. -        - allocation: This is an integer value which represents a score of -                      how much bandwidth is used. Note: Currently does not -                      have any unit, is just a counter. Higher numbers mean -                      higher utilization. RX means device to UHD, for -                      example, committing an RX streamer would increase this -                      value. -                      This key is optional, MPM does not have to provide it. -                      If the allocation affects the preference, it will be -                      factored into the order of the results, meaning the -                      caller does not strictly have to check its value even if -                      the transport option with the smallest allocation is -                      preferred. - -        Note: The dictionary may include other keys which should be ignored, -        or at the very least, kept intact. commit_xport() might be requiring -        them. -        """ -        raise NotImplementedError("request_xport() not implemented.") - -    def commit_xport(self, xport_info): -        """ -        When setting up a CHDR connection, this is the second call to be -        made. +        The return value is a list of strings. Every string is a key for a +        transport type. Values include: +        - "udp": Means this device can be reached via UDP +        - "liberio": Means this device can be reached via Liberio (local DMA) -        Arguments: -        xport_info -- A dictionary (string -> string). The dictionary must -                      have been originally created by request_xport(), but -                      additional key/value pairs need to be added. +        The list is filtered based on what the device knows about where the UHD +        session is. For example, on an N310, it will only either return "UDP" +        or "Liberio", depending on if we're remotely launching UHD, or locally. -        All transports need to also provide: -        - rx_mtu: In bytes, the max number of bytes going from device to UHD -        - tx_mtu: In bytes, the max number of bytes going from UHD to device +        In order to get further information about how to connect to the device, +        the keys returned from this function can be used with +        get_chdr_link_options(). +        """ +        raise NotImplementedError("get_chdr_link_types() not implemented.") -        UDP transports need to also provide: -        - src_ipv4: IPv4 address the connection is coming from. -        - src_port: IP port the connection is coming from. +    def get_chdr_link_options(self, xport_type):          """ -        raise NotImplementedError("commit_xport() not implemented.") +        Returns a list of dictionaries. Every dictionary contains information +        about one way to connect to this device in order to initiate CHDR +        traffic. + +        The interpretation of the return value is very highly dependant on the +        transport type (xport_type). +        For UDP, the every entry of the list has the following keys: +        - ipv4 (IP Address) +        - port (UDP port) +        - link_rate (bps of the link, e.g. 10e9 for 10GigE) + +        For Liberio, every entry has the following keys: +        - tx_dev: TX device (/dev/tx-dma*) +        - rx_dev: RX device (/dev/rx-dma*) +        """ +        raise NotImplementedError("get_chdr_link_options() not implemented.")      #######################################################################      # Claimer API @@ -927,3 +864,52 @@ class PeriphManagerBase(object):          """          self.log.debug("Device was unclaimed. No actions defined.") +    ####################################################################### +    # Timekeeper API +    ####################################################################### +    def get_num_timekeepers(self): +        """ +        Return the number of timekeepers +        """ +        raise NotImplementedError("get_num_timekeepers() not implemented.") + +    def get_timekeeper_time(self, tk_idx, last_pps): +        """ +        Get the time in ticks + +        Arguments: +        tk_idx: Index of timekeeper +        next_pps: If True, get time at last PPS. Otherwise, get time now. +        """ +        raise NotImplementedError( +            "get_ticks_now({}, {}) not implemented.".format(tk_idx, last_pps)) + +    def set_timekeeper_time(self, tk_idx, ticks, next_pps): +        """ +        Set the time in ticks + +        Arguments: +        tk_idx: Index of timekeeper +        ticks: Time in ticks +        next_pps: If True, set time at next PPS. Otherwise, set time now. +        """ +        raise NotImplementedError( +            "set_ticks_last_pps({}, {}, {}) not implemented." +            .format(tk_idx, ticks, next_pps)) + +    def set_tick_period(self, tk_idx, period_ns): +        """ +        Set the time per tick in nanoseconds (tick period) + +        Arguments: +        tk_idx: Index of timekeeper +        period_ns: Period in nanoseconds +        """ +        raise NotImplementedError( +            "set_tick_period({}) not implemented.".format(tk_idx, period_ns)) + +    def get_clocks(self): +        """ +        Gets the RFNoC-related clocks present in the FPGA design +        """ +        raise NotImplementedError("get_clocks() not implemented.") diff --git a/mpm/python/usrp_mpm/periph_manager/e31x.py b/mpm/python/usrp_mpm/periph_manager/e31x.py index 722f8d7a9..e93cbef25 100644 --- a/mpm/python/usrp_mpm/periph_manager/e31x.py +++ b/mpm/python/usrp_mpm/periph_manager/e31x.py @@ -30,7 +30,7 @@ from usrp_mpm import e31x_legacy_eeprom  E310_DEFAULT_CLOCK_SOURCE = 'internal'  E310_DEFAULT_TIME_SOURCE = 'internal'  E310_DEFAULT_ENABLE_FPGPIO = True -E310_FPGA_COMPAT = (1,0) +E310_FPGA_COMPAT = (4,0)  E310_DBOARD_SLOT_IDX = 0  ############################################################################### @@ -39,9 +39,7 @@ E310_DBOARD_SLOT_IDX = 0  class E310XportMgrLiberio(XportMgrLiberio):      " E310-specific Liberio configuration " -    max_chan = 4 -    xbar_dev = "/dev/crossbar0" -    xbar_port = 0 +    max_chan = 5  ###############################################################################  # Main Class @@ -178,7 +176,6 @@ class e31x(ZynqComponents, PeriphManagerBase):          self._tear_down = False          self._clock_source = None          self._time_source = None -        self._available_endpoints = list(range(256))          self.dboard = self.dboards[E310_DBOARD_SLOT_IDX]          try:              self._init_peripherals(self.args_cached) @@ -277,9 +274,6 @@ class e31x(ZynqComponents, PeriphManagerBase):          self.mboard_regs_control.get_build_timestamp()          self._check_fpga_compat()          self._update_fpga_type() -        self.crossbar_base_port = self.mboard_regs_control.get_xbar_baseport() -        self.log.debug("crossbar base port: {}".format(self.crossbar_base_port)) -          # Init clocking          self._init_ref_clock_and_time(args)          # Init CHDR transports @@ -443,7 +437,6 @@ class e31x(ZynqComponents, PeriphManagerBase):          for xport_mgr in itervalues(self._xport_mgrs):              xport_mgr.deinit()          self.log.trace("Resetting SID pool...") -        self._available_endpoints = list(range(256))          if not self._do_not_reload:              self.tear_down()          # Reset back to value from _default_args (mpm.conf) @@ -480,56 +473,22 @@ class e31x(ZynqComponents, PeriphManagerBase):              self.log.trace("Found idle overlay: %s", idle_overlay)          return is_idle +      ###########################################################################      # Transport API      ########################################################################### -    def request_xport( -            self, -            dst_address, -            suggested_src_address, -            xport_type -        ): -        """ -        See PeriphManagerBase.request_xport() for docs. -        """ -        # Try suggested address first, then just pick the first available one: -        src_address = suggested_src_address -        if src_address not in self._available_endpoints: -            if not self._available_endpoints: -                raise RuntimeError( -                    "Depleted pool of SID endpoints for this device!") -            else: -                src_address = self._available_endpoints[0] -        sid = SID(src_address << 16 | dst_address) -        # Note: This SID may change its source address! -        self.log.trace( -            "request_xport(dst=0x%04X, suggested_src_address=0x%04X, xport_type=%s): " \ -            "operating on temporary SID: %s", -            dst_address, suggested_src_address, str(xport_type), str(sid)) -        assert self.mboard_info['rpc_connection'] in ('local') -        if self.mboard_info['rpc_connection'] == 'local': -            return self._xport_mgrs['liberio'].request_xport( -                sid, -                xport_type, -            ) - -    def commit_xport(self, xport_info): +    def get_chdr_link_types(self):          """ -        See PeriphManagerBase.commit_xport() for docs. +        See PeriphManagerBase.get_chdr_link_types() for docs. +        """ +        return ['liberio'] -        Reminder: All connections are incoming, i.e. "send" or "TX" means -        remote device to local device, and "receive" or "RX" means this local -        device to remote device. "Remote device" can be, for example, a UHD -        session. +    def get_chdr_link_options(self, xport_type): +        """ +        See PeriphManagerBase.get_chdr_link_options() for docs.          """ -        ## Go, go, go -        assert self.mboard_info['rpc_connection'] in ('local') -        sid = SID(xport_info['send_sid']) -        self._available_endpoints.remove(sid.src_ep) -        self.log.debug("Committing transport for SID %s, xport info: %s", -                       str(sid), str(xport_info)) -        if self.mboard_info['rpc_connection'] == 'local': -            return self._xport_mgrs['liberio'].commit_xport(sid, xport_info) +        if xport_type == 'liberio': +            return self._xport_mgrs['liberio'].get_chdr_link_options()      ###########################################################################      # Device info @@ -550,6 +509,39 @@ class e31x(ZynqComponents, PeriphManagerBase):          })          return device_info +    def set_device_id(self, device_id): +        """ +        Sets the device ID for this motherboard. +        The device ID is used to identify the RFNoC components associated with +        this motherboard. +        """ +        self.log.debug("Setting device ID to `{}'".format(device_id)) +        self.mboard_regs_control.set_device_id(device_id) + +    def get_device_id(self): +        """ +        Gets the device ID for this motherboard. +        The device ID is used to identify the RFNoC components associated with +        this motherboard. +        """ +        return self.mboard_regs_control.get_device_id() + +    def get_proto_ver(self): +        """ +        Return RFNoC protocol version +        """ +        proto_ver = self.mboard_regs_control.get_proto_ver() +        self.log.debug("RFNoC protocol version supported by this device is {}".format(proto_ver)) +        return proto_ver + +    def get_chdr_width(self): +        """ +        Return RFNoC CHDR width +        """ +        chdr_width = self.mboard_regs_control.get_chdr_width() +        self.log.debug("CHDR width supported by the device is {}".format(chdr_width)) +        return chdr_width +      ###########################################################################      # Clock/Time API      ########################################################################### @@ -742,3 +734,59 @@ class e31x(ZynqComponents, PeriphManagerBase):          fpga_type = self.mboard_regs_control.get_fpga_type()          self.log.debug("Updating mboard FPGA type info to {}".format(fpga_type))          self.updateable_components['fpga']['type'] = fpga_type + +    ####################################################################### +    # Timekeeper API +    ####################################################################### +    def get_num_timekeepers(self): +        """ +        Return the number of timekeepers +        """ +        return self.mboard_regs_control.get_num_timekeepers() + +    def get_timekeeper_time(self, tk_idx, last_pps): +        """ +        Get the time in ticks + +        Arguments: +        tk_idx: Index of timekeeper +        next_pps: If True, get time at last PPS. Otherwise, get time now. +        """ +        return self.mboard_regs_control.get_timekeeper_time(tk_idx, last_pps) + +    def set_timekeeper_time(self, tk_idx, ticks, next_pps): +        """ +        Set the time in ticks + +        Arguments: +        tk_idx: Index of timekeeper +        ticks: Time in ticks +        next_pps: If True, set time at next PPS. Otherwise, set time now. +        """ +        self.mboard_regs_control.set_timekeeper_time(tk_idx, ticks, next_pps) + +    def set_tick_period(self, tk_idx, period_ns): +        """ +        Set the time per tick in nanoseconds (tick period) + +        Arguments: +        tk_idx: Index of timekeeper +        period_ns: Period in nanoseconds +        """ +        self.mboard_regs_control.set_tick_period(tk_idx, period_ns) + +    def get_clocks(self): +        """ +        Gets the RFNoC-related clocks present in the FPGA design +        """ +        return [ +            { +                'name': 'radio_clk', +                'freq': str(self.dboard.get_master_clock_rate()), +                'mutable': 'true' +            }, +            { +                'name': 'bus_clk', +                'freq': str(100e6), +            } +        ] diff --git a/mpm/python/usrp_mpm/periph_manager/e31x_periphs.py b/mpm/python/usrp_mpm/periph_manager/e31x_periphs.py index 0b166e5bc..330cd830e 100644 --- a/mpm/python/usrp_mpm/periph_manager/e31x_periphs.py +++ b/mpm/python/usrp_mpm/periph_manager/e31x_periphs.py @@ -33,23 +33,40 @@ class MboardRegsControl(object):      Control the FPGA Motherboard registers      """      # Motherboard registers -    MB_COMPAT_NUM     = 0x0000 -    MB_DATESTAMP      = 0x0004 -    MB_GIT_HASH       = 0x0008 -    MB_SCRATCH        = 0x000C -    MB_NUM_CE         = 0x0010 -    MB_NUM_IO_CE      = 0x0014 -    MB_CLOCK_CTRL     = 0x0018 -    MB_XADC_RB        = 0x001C -    MB_BUS_CLK_RATE   = 0x0020 -    MB_BUS_COUNTER    = 0x0024 -    MB_GPIO_MASTER    = 0x0030 -    MB_GPIO_RADIO_SRC = 0x0034 -    MB_GPS_CTRL       = 0x0038 -    MB_GPS_STATUS     = 0x003C -    MB_DBOARD_CTRL    = 0x0040 -    MB_DBOARD_STATUS  = 0x0044 -    MB_XBAR_BASEPORT  = 0x0048 +    MB_COMPAT_NUM       = 0x0000 +    MB_DATESTAMP        = 0x0004 +    MB_GIT_HASH         = 0x0008 +    MB_SCRATCH          = 0x000C +    MB_DEVICE_ID        = 0x0010 +    MB_RFNOC_INFO       = 0x0014 +    MB_CLOCK_CTRL       = 0x0018 +    MB_XADC_RB          = 0x001C +    MB_BUS_CLK_RATE     = 0x0020 +    MB_BUS_COUNTER      = 0x0024 +    MB_SFP_PORT_INFO    = 0x0028 +    MB_GPIO_CTRL        = 0x002C +    MB_GPIO_MASTER      = 0x0030 +    MB_GPIO_RADIO_SRC   = 0x0034 +    MB_GPS_CTRL         = 0x0038 +    MB_GPS_STATUS       = 0x003C +    MB_DBOARD_CTRL      = 0x0040 +    MB_DBOARD_STATUS    = 0x0044 +    MB_NUM_TIMEKEEPERS  = 0x0048 +    # Timekeeper registers +    MB_TIME_NOW_LO         = 0x1000 +    MB_TIME_NOW_HI         = 0x1004 +    MB_TIME_EVENT_LO       = 0x1008 +    MB_TIME_EVENT_HI       = 0x100C +    MB_TIME_CTRL           = 0x1010 +    MB_TIME_LAST_PPS_LO    = 0x1014 +    MB_TIME_LAST_PPS_HI    = 0x1018 +    MB_TIME_BASE_PERIOD_LO = 0x101C +    MB_TIME_BASE_PERIOD_HI = 0x1020 +    MB_TIMEKEEPER_OFFSET   = 12 + +    # Bitfield locations for the MB_RFNOC_INFO register. +    MB_RFNOC_INFO_PROTO_VER = 0 +    MB_RFNOC_INFO_CHDR_WIDTH = 16      # Bitfield locations for the MB_CLOCK_CTRL register.      MB_CLOCK_CTRL_PPS_SEL_INT = 0 @@ -100,6 +117,26 @@ class MboardRegsControl(object):          major = (compat_number>>16) & 0xff          return (major, minor) +    def get_proto_ver(self): +        """ +        Return RFNoC protocol version +        """ +        with self.regs: +            reg_val = self.peek32(self.MB_RFNOC_INFO) +            proto_ver = (reg_val & 0x0000ffff) >> self.MB_RFNOC_INFO_PROTO_VER +            self.log.trace("Read RFNOC_PROTO_VER 0x{:08X}".format(proto_ver)) +            return proto_ver + +    def get_chdr_width(self): +        """ +        Return RFNoC CHDR width +        """ +        with self.regs: +            reg_val = self.peek32(self.MB_RFNOC_INFO) +            chdr_width = (reg_val & 0xffff0000) >> self.MB_RFNOC_INFO_CHDR_WIDTH +            self.log.trace("Read RFNOC_CHDR_WIDTH 0x{:08X}".format(chdr_width)) +            return chdr_width +      def set_fp_gpio_master(self, value):          """set driver for front panel GPIO          Arguments: @@ -136,6 +173,24 @@ class MboardRegsControl(object):          with self.regs:              return self.peek32(self.MB_GPIO_RADIO_SRC) & 0xffffff +    def set_device_id(self, device_id): +        """ +        Set device ID +        """ +        with self.regs: +            self.log.trace("Writing MB_DEVICE_ID with 0x{:08X}".format(device_id)) +            return self.poke32(self.MB_DEVICE_ID, device_id) + +    def get_device_id(self): +        """ +        Get device ID +        """ +        with self.regs: +            regs_val = self.peek32(self.MB_DEVICE_ID) +            device_id = regs_val & 0x0000ffff +            self.log.trace("Read MB_DEVICE_ID 0x{:08X}".format(device_id)) +            return device_id +      def get_build_timestamp(self):          """          Returns the build date/time for the FPGA image. @@ -206,6 +261,71 @@ class MboardRegsControl(object):          else:              assert False, "Cannot set to invalid clock source: {}".format(clock_source) +    def get_num_timekeepers(self): +        """ +        Return the number of timekeepers +        """ +        with self.regs: +            return self.peek32(self.MB_NUM_TIMEKEEPERS) + +    def get_timekeeper_time(self, tk_idx, last_pps): +        """ +        Get the time in ticks + +        Arguments: +        tk_idx: Index of timekeeper +        next_pps: If True, get time at last PPS. Otherwise, get time now. +        """ +        addr_lo = \ +            (self.MB_TIME_LAST_PPS_LO if last_pps else self.MB_TIME_NOW_LO) + \ +            tk_idx * self.MB_TIMEKEEPER_OFFSET +        addr_hi = addr_lo + 4 +        with self.regs: +            time_lo = self.peek32(addr_lo) +            time_hi = self.peek32(addr_hi) +        return time_hi << 32 | time_lo + + +    def set_timekeeper_time(self, tk_idx, ticks, next_pps): +        """ +        Set the time in ticks + +        Arguments: +        tk_idx: Index of timekeeper +        ticks: Time in ticks +        next_pps: If True, set time at next PPS. Otherwise, set time now. +        """ +        addr_lo = \ +            self.MB_TIME_EVENT_LO + tk_idx * self.MB_TIMEKEEPER_OFFSET +        addr_hi = addr_lo + 4 +        addr_ctrl = \ +            self.MB_TIME_CTRL + tk_idx * self.MB_TIMEKEEPER_OFFSET +        time_lo = ticks & 0xFFFFFFFF +        time_hi = (ticks > 32) & 0xFFFFFFFF +        time_ctrl = 0x2 if next_pps else 0x1 +        self.log.trace("Setting time on timekeeper %d to %d %s", tk_idx, ticks, +                       ("on next pps" if next_pps else "now")) +        with self.regs: +            self.poke32(addr_lo, time_lo) +            self.poke32(addr_hi, time_hi) +            self.poke32(addr_ctrl, time_ctrl) + +    def set_tick_period(self, tk_idx, period_ns): +        """ +        Set the time per tick in nanoseconds (tick period) + +        Arguments: +        tk_idx: Index of timekeeper +        period_ns: Period in nanoseconds +        """ +        addr_lo = self.MB_TIME_BASE_PERIOD_LO + tk_idx * self.MB_TIMEKEEPER_OFFSET +        addr_hi = addr_lo + 4 +        period_lo = period_ns & 0xFFFFFFFF +        period_hi = (period_ns > 32) & 0xFFFFFFFF +        with self.regs: +            self.poke32(addr_lo, period_lo) +            self.poke32(addr_hi, period_hi) +      def get_fpga_type(self):          """          Reads the type of the FPGA image currently loaded @@ -248,7 +368,8 @@ class MboardRegsControl(object):              reg_val = self.peek32(self.MB_DBOARD_CTRL)              if channel_mode == "MIMO":                  reg_val = (0b1 << self.MB_DBOARD_CTRL_MIMO) -                self.log.trace("Setting channel mode in AD9361 interface: {}".format("2R2T" if channel_mode == 2 else "1R1T")) +                self.log.trace("Setting channel mode in AD9361 interface: %s", +                               "2R2T" if channel_mode == 2 else "1R1T")              else:                  # Warn if user tries to set either tx0/tx1 in mimo mode                  # as both will be set automatically @@ -269,7 +390,7 @@ class MboardRegsControl(object):          """          mask = 0b1 << self.MB_DBOARD_STATUS_TX_LOCK          with self.regs: -            reg_val =  self.peek32(self.MB_DBOARD_STATUS) +            reg_val = self.peek32(self.MB_DBOARD_STATUS)          locked = (reg_val & mask) > 0          if not locked:              self.log.warning("TX RF PLL reporting unlocked. ") @@ -283,15 +404,10 @@ class MboardRegsControl(object):          """          mask = 0b1 << self.MB_DBOARD_STATUS_RX_LOCK          with self.regs: -            reg_val =  self.peek32(self.MB_DBOARD_STATUS) +            reg_val = self.peek32(self.MB_DBOARD_STATUS)          locked = (reg_val & mask) > 0          if not locked:              self.log.warning("RX RF PLL reporting unlocked. ")          else:              self.log.trace("RX RF PLL locked")          return locked - -    def get_xbar_baseport(self): -        "Get the RFNoC crossbar base port" -        with self.regs: -            return self.peek32(self.MB_XBAR_BASEPORT) diff --git a/mpm/python/usrp_mpm/periph_manager/e320.py b/mpm/python/usrp_mpm/periph_manager/e320.py index f76e6ac3e..04999e505 100644 --- a/mpm/python/usrp_mpm/periph_manager/e320.py +++ b/mpm/python/usrp_mpm/periph_manager/e320.py @@ -32,7 +32,7 @@ E320_DEFAULT_CLOCK_SOURCE = 'internal'  E320_DEFAULT_TIME_SOURCE = 'internal'  E320_DEFAULT_ENABLE_GPS = True  E320_DEFAULT_ENABLE_FPGPIO = True -E320_FPGA_COMPAT = (3, 1) +E320_FPGA_COMPAT = (4, 0)  E320_MONITOR_THREAD_INTERVAL = 1.0 # seconds  E320_DBOARD_SLOT_IDX = 0 @@ -42,21 +42,15 @@ E320_DBOARD_SLOT_IDX = 0  ###############################################################################  class E320XportMgrUDP(XportMgrUDP):      "E320-specific UDP configuration" -    xbar_dev = "/dev/crossbar0"      iface_config = {          'sfp0': {              'label': 'misc-enet-regs', -            'xbar': 0, -            'xbar_port': 0, -            'ctrl_src_addr': 0,          }      }  class E320XportMgrLiberio(XportMgrLiberio):      " E320-specific Liberio configuration "      max_chan = 6 -    xbar_dev = "/dev/crossbar0" -    xbar_port = 1  ###############################################################################  # Main Class @@ -139,7 +133,6 @@ class e320(ZynqComponents, PeriphManagerBase):          self._ext_clock_freq = E320_DEFAULT_EXT_CLOCK_FREQ          self._clock_source = None          self._time_source = None -        self._available_endpoints = list(range(256))          self._gpsd = None          self.dboard = self.dboards[E320_DBOARD_SLOT_IDX]          from functools import partial @@ -266,7 +259,6 @@ class e320(ZynqComponents, PeriphManagerBase):          self.mboard_regs_control.get_build_timestamp()          self._check_fpga_compat()          self._update_fpga_type() -        self.crossbar_base_port = self.mboard_regs_control.get_xbar_baseport()          # Init peripherals          self.enable_gps(              enable=str2bool( @@ -274,10 +266,7 @@ class e320(ZynqComponents, PeriphManagerBase):              )          )          self.enable_fp_gpio( -            enable=args.get( -                        'enable_fp_gpio', -                        E320_DEFAULT_ENABLE_FPGPIO -                    ) +            enable=args.get('enable_fp_gpio', E320_DEFAULT_ENABLE_FPGPIO)          )          # Init clocking          self._init_ref_clock_and_time(args) @@ -285,8 +274,8 @@ class e320(ZynqComponents, PeriphManagerBase):          self._init_gps_sensors()          # Init CHDR transports          self._xport_mgrs = { -            'udp': E320XportMgrUDP(self.log.getChild('UDP'), args), -            'liberio': E320XportMgrLiberio(self.log.getChild('liberio')), +            'udp': E320XportMgrUDP(self.log, args), +            'liberio': E320XportMgrLiberio(self.log),          }          # Spawn status monitoring thread          self.log.trace("Spawning status monitor thread...") @@ -347,8 +336,6 @@ class e320(ZynqComponents, PeriphManagerBase):          super(e320, self).deinit()          for xport_mgr in itervalues(self._xport_mgrs):              xport_mgr.deinit() -        self.log.trace("Resetting SID pool...") -        self._available_endpoints = list(range(256))      def tear_down(self):          """ @@ -372,61 +359,37 @@ class e320(ZynqComponents, PeriphManagerBase):      ###########################################################################      # Transport API      ########################################################################### -    def request_xport( -            self, -            dst_address, -            suggested_src_address, -            xport_type -        ): -        """ -        See PeriphManagerBase.request_xport() for docs. -        """ -        # Try suggested address first, then just pick the first available one: -        src_address = suggested_src_address -        if src_address not in self._available_endpoints: -            if not self._available_endpoints: -                raise RuntimeError( -                    "Depleted pool of SID endpoints for this device!") -            else: -                src_address = self._available_endpoints[0] -        sid = SID(src_address << 16 | dst_address) -        # Note: This SID may change its source address! -        self.log.trace( -            "request_xport(dst=0x%04X, suggested_src_address=0x%04X, xport_type=%s): " \ -            "operating on temporary SID: %s", -            dst_address, suggested_src_address, str(xport_type), str(sid)) -        # FIXME token! +    def get_chdr_link_types(self): +        """ +        This will only ever return a single item (udp or liberio). +        """          assert self.mboard_info['rpc_connection'] in ('remote', 'local')          if self.mboard_info['rpc_connection'] == 'remote': -            return self._xport_mgrs['udp'].request_xport( -                sid, -                xport_type, -            ) -        elif self.mboard_info['rpc_connection'] == 'local': -            return self._xport_mgrs['liberio'].request_xport( -                sid, -                xport_type, -            ) +            return ["udp"] +        # else: +        return ["liberio"] -    def commit_xport(self, xport_info): +    def get_chdr_link_options(self, xport_type):          """ -        See PeriphManagerBase.commit_xport() for docs. +        Returns a list of dictionaries. Every dictionary contains information +        about one way to connect to this device in order to initiate CHDR +        traffic. + +        The interpretation of the return value is very highly dependant on the +        transport type (xport_type). +        For UDP, the every entry of the list has the following keys: +        - ipv4 (IP Address) +        - port (UDP port) +        - link_rate (bps of the link, e.g. 10e9 for 10GigE) -        Reminder: All connections are incoming, i.e. "send" or "TX" means -        remote device to local device, and "receive" or "RX" means this local -        device to remote device. "Remote device" can be, for example, a UHD -        session. +        For Liberio, every entry has the following keys: +        - tx_dev: TX device (/dev/tx-dma*) +        - rx_dev: RX device (/dev/rx-dma*)          """ -        ## Go, go, go -        assert self.mboard_info['rpc_connection'] in ('remote', 'local') -        sid = SID(xport_info['send_sid']) -        self._available_endpoints.remove(sid.src_ep) -        self.log.debug("Committing transport for SID %s, xport info: %s", -                       str(sid), str(xport_info)) -        if self.mboard_info['rpc_connection'] == 'remote': -            return self._xport_mgrs['udp'].commit_xport(sid, xport_info) -        elif self.mboard_info['rpc_connection'] == 'local': -            return self._xport_mgrs['liberio'].commit_xport(sid, xport_info) +        if xport_type not in self._xport_mgrs: +            self.log.warning("Can't get link options for unknown link type: `{}'.") +            return [] +        return self._xport_mgrs[xport_type].get_chdr_link_options()      ###########################################################################      # Device info @@ -443,10 +406,43 @@ class e320(ZynqComponents, PeriphManagerBase):                  *self.mboard_regs_control.get_compat_number()),              'fpga_version_hash': "{:x}.{}".format(                  *self.mboard_regs_control.get_git_hash()), -            'fpga': self.updateable_components.get('fpga', {}).get('type',""), +            'fpga': self.updateable_components.get('fpga', {}).get('type', ""),          })          return device_info +    def set_device_id(self, device_id): +        """ +        Sets the device ID for this motherboard. +        The device ID is used to identify the RFNoC components associated with +        this motherboard. +        """ +        self.log.debug("Setting device ID to `{}'".format(device_id)) +        self.mboard_regs_control.set_device_id(device_id) + +    def get_device_id(self): +        """ +        Gets the device ID for this motherboard. +        The device ID is used to identify the RFNoC components associated with +        this motherboard. +        """ +        return self.mboard_regs_control.get_device_id() + +    def get_proto_ver(self): +        """ +        Return RFNoC protocol version +        """ +        proto_ver = self.mboard_regs_control.get_proto_ver() +        self.log.debug("RFNoC protocol version supported by this device is {}".format(proto_ver)) +        return proto_ver + +    def get_chdr_width(self): +        """ +        Return RFNoC CHDR width +        """ +        chdr_width = self.mboard_regs_control.get_chdr_width() +        self.log.debug("CHDR width supported by the device is {}".format(chdr_width)) +        return chdr_width +      ###########################################################################      # Clock/Time API      ########################################################################### @@ -637,7 +633,7 @@ class e320(ZynqComponents, PeriphManagerBase):          except ValueError:              self.log.warning("Error when converting temperature value")          except KeyError: -            self.log.warning("Can't read temp on thermal_zone".format(sensor)) +            self.log.warning("Can't read temp on thermal_zone {}".format(sensor))          return {              'name': sensor_name,              'type': 'REALNUM', @@ -753,3 +749,59 @@ class e320(ZynqComponents, PeriphManagerBase):          fpga_type = self.mboard_regs_control.get_fpga_type()          self.log.debug("Updating mboard FPGA type info to {}".format(fpga_type))          self.updateable_components['fpga']['type'] = fpga_type + +    ####################################################################### +    # Timekeeper API +    ####################################################################### +    def get_num_timekeepers(self): +        """ +        Return the number of timekeepers +        """ +        return self.mboard_regs_control.get_num_timekeepers() + +    def get_timekeeper_time(self, tk_idx, last_pps): +        """ +        Get the time in ticks + +        Arguments: +        tk_idx: Index of timekeeper +        next_pps: If True, get time at last PPS. Otherwise, get time now. +        """ +        return self.mboard_regs_control.get_timekeeper_time(tk_idx, last_pps) + +    def set_timekeeper_time(self, tk_idx, ticks, next_pps): +        """ +        Set the time in ticks + +        Arguments: +        tk_idx: Index of timekeeper +        ticks: Time in ticks +        next_pps: If True, set time at next PPS. Otherwise, set time now. +        """ +        self.mboard_regs_control.set_timekeeper_time(tk_idx, ticks, next_pps) + +    def set_tick_period(self, tk_idx, period_ns): +        """ +        Set the time per tick in nanoseconds (tick period) + +        Arguments: +        tk_idx: Index of timekeeper +        period_ns: Period in nanoseconds +        """ +        self.mboard_regs_control.set_tick_period(tk_idx, period_ns) + +    def get_clocks(self): +        """ +        Gets the RFNoC-related clocks present in the FPGA design +        """ +        return [ +            { +                'name': 'radio_clk', +                'freq': str(self.dboard.get_master_clock_rate()), +                'mutable': 'true' +            }, +            { +                'name': 'bus_clk', +                'freq': str(200e6), +            } +        ] diff --git a/mpm/python/usrp_mpm/periph_manager/e320_periphs.py b/mpm/python/usrp_mpm/periph_manager/e320_periphs.py index 0cf7f59c6..cad5c39ad 100644 --- a/mpm/python/usrp_mpm/periph_manager/e320_periphs.py +++ b/mpm/python/usrp_mpm/periph_manager/e320_periphs.py @@ -48,25 +48,40 @@ class MboardRegsControl(object):      Control the FPGA Motherboard registers      """      # Motherboard registers -    MB_COMPAT_NUM     = 0x0000 -    MB_DATESTAMP      = 0x0004 -    MB_GIT_HASH       = 0x0008 -    MB_SCRATCH        = 0x000C -    MB_NUM_CE         = 0x0010 -    MB_NUM_IO_CE      = 0x0014 -    MB_CLOCK_CTRL     = 0x0018 -    MB_XADC_RB        = 0x001C -    MB_BUS_CLK_RATE   = 0x0020 -    MB_BUS_COUNTER    = 0x0024 -    MB_SFP_PORT_INFO  = 0x0028 -    MB_GPIO_CTRL      = 0x002C -    MB_GPIO_MASTER    = 0x0030 -    MB_GPIO_RADIO_SRC = 0x0034 -    MB_GPS_CTRL       = 0x0038 -    MB_GPS_STATUS     = 0x003C -    MB_DBOARD_CTRL    = 0x0040 -    MB_DBOARD_STATUS  = 0x0044 -    MB_XBAR_BASEPORT  = 0x0048 +    MB_COMPAT_NUM      = 0x0000 +    MB_DATESTAMP       = 0x0004 +    MB_GIT_HASH        = 0x0008 +    MB_SCRATCH         = 0x000C +    MB_DEVICE_ID       = 0x0010 +    MB_RFNOC_INFO      = 0x0014 +    MB_CLOCK_CTRL      = 0x0018 +    MB_XADC_RB         = 0x001C +    MB_BUS_CLK_RATE    = 0x0020 +    MB_BUS_COUNTER     = 0x0024 +    MB_SFP_PORT_INFO   = 0x0028 +    MB_GPIO_CTRL       = 0x002C +    MB_GPIO_MASTER     = 0x0030 +    MB_GPIO_RADIO_SRC  = 0x0034 +    MB_GPS_CTRL        = 0x0038 +    MB_GPS_STATUS      = 0x003C +    MB_DBOARD_CTRL     = 0x0040 +    MB_DBOARD_STATUS   = 0x0044 +    MB_NUM_TIMEKEEPERS = 0x0048 +    # Timekeeper registers +    MB_TIME_NOW_LO         = 0x1000 +    MB_TIME_NOW_HI         = 0x1004 +    MB_TIME_EVENT_LO       = 0x1008 +    MB_TIME_EVENT_HI       = 0x100C +    MB_TIME_CTRL           = 0x1010 +    MB_TIME_LAST_PPS_LO    = 0x1014 +    MB_TIME_LAST_PPS_HI    = 0x1018 +    MB_TIME_BASE_PERIOD_LO = 0x101C +    MB_TIME_BASE_PERIOD_HI = 0x1020 +    MB_TIMEKEEPER_OFFSET   = 12 + +    # Bitfield locations for the MB_RFNOC_INFO register. +    MB_RFNOC_INFO_PROTO_VER = 0 +    MB_RFNOC_INFO_CHDR_WIDTH = 16      # Bitfield locations for the MB_CLOCK_CTRL register.      MB_CLOCK_CTRL_PPS_SEL_INT = 0 @@ -122,6 +137,44 @@ class MboardRegsControl(object):          major = (compat_number>>16) & 0xff          return (major, minor) +    def set_device_id(self, device_id): +        """ +        Set device ID +        """ +        with self.regs: +            self.log.trace("Writing MB_DEVICE_ID with 0x{:08X}".format(device_id)) +            return self.poke32(self.MB_DEVICE_ID, device_id) + +    def get_device_id(self): +        """ +        Get device ID +        """ +        with self.regs: +            reg_val = self.peek32(self.MB_DEVICE_ID) +            device_id = reg_val & 0x0000ffff +            self.log.trace("Read MB_DEVICE_ID 0x{:08X}".format(device_id)) +            return device_id + +    def get_proto_ver(self): +        """ +        Return RFNoC protocol version +        """ +        with self.regs: +            reg_val = self.peek32(self.MB_RFNOC_INFO) +            proto_ver = (reg_val & 0x0000ffff) >> self.MB_RFNOC_INFO_PROTO_VER +            self.log.trace("Read RFNOC_PROTO_VER 0x{:08X}".format(proto_ver)) +            return proto_ver; + +    def get_chdr_width(self): +        """ +        Return RFNoC CHDR width +        """ +        with self.regs: +            reg_val = self.peek32(self.MB_RFNOC_INFO) +            chdr_width = (reg_val & 0xffff0000) >> self.MB_RFNOC_INFO_CHDR_WIDTH +            self.log.trace("Read RFNOC_CHDR_WIDTH 0x{:08X}".format(chdr_width)) +            return chdr_width +      def enable_fp_gpio(self, enable):          """ Enable front panel GPIO buffers and power supply          and set voltage 3.3 V @@ -372,7 +425,8 @@ class MboardRegsControl(object):              reg_val = self.peek32(self.MB_DBOARD_CTRL)              if channel_mode == "MIMO":                  reg_val = (0b1 << self.MB_DBOARD_CTRL_MIMO) -                self.log.trace("Setting channel mode in AD9361 interface: {}".format("2R2T" if channel_mode == 2 else "1R1T")) +                self.log.trace("Setting channel mode in AD9361 interface: %s", +                               "2R2T" if channel_mode == 2 else "1R1T")              else:                  # Warn if user tries to set either tx0/tx1 in mimo mode                  # as both will be set automatically @@ -393,7 +447,7 @@ class MboardRegsControl(object):          """          mask = 0b1 << self.MB_DBOARD_STATUS_TX_LOCK          with self.regs: -            reg_val =  self.peek32(self.MB_DBOARD_STATUS) +            reg_val = self.peek32(self.MB_DBOARD_STATUS)          locked = (reg_val & mask) > 0          if not locked:              self.log.warning("TX RF PLL reporting unlocked. ") @@ -407,7 +461,7 @@ class MboardRegsControl(object):          """          mask = 0b1 << self.MB_DBOARD_STATUS_RX_LOCK          with self.regs: -            reg_val =  self.peek32(self.MB_DBOARD_STATUS) +            reg_val = self.peek32(self.MB_DBOARD_STATUS)          locked = (reg_val & mask) > 0          if not locked:              self.log.warning("RX RF PLL reporting unlocked. ") @@ -415,7 +469,67 @@ class MboardRegsControl(object):              self.log.trace("RX RF PLL locked")          return locked -    def get_xbar_baseport(self): -        "Get the RFNoC crossbar base port" +    def get_num_timekeepers(self): +        """ +        Return the number of timekeepers +        """ +        with self.regs: +            return self.peek32(self.MB_NUM_TIMEKEEPERS) + +    def get_timekeeper_time(self, tk_idx, last_pps): +        """ +        Get the time in ticks + +        Arguments: +        tk_idx: Index of timekeeper +        next_pps: If True, get time at last PPS. Otherwise, get time now. +        """ +        addr_lo = \ +            (self.MB_TIME_LAST_PPS_LO if last_pps else self.MB_TIME_NOW_LO) + \ +            tk_idx * self.MB_TIMEKEEPER_OFFSET +        addr_hi = addr_lo + 4 +        with self.regs: +            time_lo = self.peek32(addr_lo) +            time_hi = self.peek32(addr_hi) +        return time_hi << 32 | time_lo + + +    def set_timekeeper_time(self, tk_idx, ticks, next_pps): +        """ +        Set the time in ticks + +        Arguments: +        tk_idx: Index of timekeeper +        ticks: Time in ticks +        next_pps: If True, set time at next PPS. Otherwise, set time now. +        """ +        addr_lo = \ +            self.MB_TIME_EVENT_LO + tk_idx * self.MB_TIMEKEEPER_OFFSET +        addr_hi = addr_lo + 4 +        addr_ctrl = \ +            self.MB_TIME_CTRL + tk_idx * self.MB_TIMEKEEPER_OFFSET +        time_lo = ticks & 0xFFFFFFFF +        time_hi = (ticks > 32) & 0xFFFFFFFF +        time_ctrl = 0x2 if next_pps else 0x1 +        self.log.trace("Setting time on timekeeper %d to %d %s", tk_idx, ticks, +                       ("on next pps" if next_pps else "now")) +        with self.regs: +            self.poke32(addr_lo, time_lo) +            self.poke32(addr_hi, time_hi) +            self.poke32(addr_ctrl, time_ctrl) + +    def set_tick_period(self, tk_idx, period_ns): +        """ +        Set the time per tick in nanoseconds (tick period) + +        Arguments: +        tk_idx: Index of timekeeper +        period_ns: Period in nanoseconds +        """ +        addr_lo = self.MB_TIME_BASE_PERIOD_LO + tk_idx * self.MB_TIMEKEEPER_OFFSET +        addr_hi = addr_lo + 4 +        period_lo = period_ns & 0xFFFFFFFF +        period_hi = (period_ns > 32) & 0xFFFFFFFF          with self.regs: -            return self.peek32(self.MB_XBAR_BASEPORT) +            self.poke32(addr_lo, period_lo) +            self.poke32(addr_hi, period_hi) diff --git a/mpm/python/usrp_mpm/periph_manager/n3xx.py b/mpm/python/usrp_mpm/periph_manager/n3xx.py index 2695dbfba..8d5f83569 100644 --- a/mpm/python/usrp_mpm/periph_manager/n3xx.py +++ b/mpm/python/usrp_mpm/periph_manager/n3xx.py @@ -41,7 +41,7 @@ N3XX_DEFAULT_ENABLE_PPS_EXPORT = True  N32X_DEFAULT_QSFP_RATE_PRESET = 'Ethernet'  N32X_DEFAULT_QSFP_DRIVER_PRESET = 'Optical'  N32X_QSFP_I2C_LABEL = 'qsfp-i2c' -N3XX_FPGA_COMPAT = (5, 3) +N3XX_FPGA_COMPAT = (6, 0)  N3XX_MONITOR_THREAD_INTERVAL = 1.0 # seconds  # Import daughterboard PIDs from their respective classes @@ -52,39 +52,24 @@ RHODIUM_PID = Rhodium.pids[0]  ###############################################################################  # Transport managers  ############################################################################### +# pylint: disable=too-few-public-methods  class N3xxXportMgrUDP(XportMgrUDP):      " N3xx-specific UDP configuration " -    xbar_dev = "/dev/crossbar0"      iface_config = {          'bridge0': {              'label': 'misc-enet-regs0', -            'xbar': 0, -            'xbar_port': 0, -            'ctrl_src_addr': 0,          },          'sfp0': {              'label': 'misc-enet-regs0', -            'xbar': 0, -            'xbar_port': 0, -            'ctrl_src_addr': 0,          },          'sfp1': {              'label': 'misc-enet-regs1', -            'xbar': 0, -            'xbar_port': 1, -            'ctrl_src_addr': 1,          },          'eth1': {              'label': 'misc-enet-regs0', -            'xbar': 0, -            'xbar_port': 0, -            'ctrl_src_addr': 0,          },          'eth2': {              'label': 'misc-enet-regs1', -            'xbar': 0, -            'xbar_port': 1, -            'ctrl_src_addr': 1,          },      }      bridges = {'bridge0': ['sfp0', 'sfp1', 'bridge0']} @@ -92,8 +77,7 @@ class N3xxXportMgrUDP(XportMgrUDP):  class N3xxXportMgrLiberio(XportMgrLiberio):      " N3xx-specific Liberio configuration "      max_chan = 10 -    xbar_dev = "/dev/crossbar0" -    xbar_port = 2 +# pylint: enable=too-few-public-methods  ###############################################################################  # Main Class @@ -216,7 +200,6 @@ class n3xx(ZynqComponents, PeriphManagerBase):          self._ext_clock_freq = None          self._clock_source = None          self._time_source = None -        self._available_endpoints = list(range(256))          self._bp_leds = None          self._gpsd = None          self._qsfp_retimer = None @@ -352,7 +335,6 @@ class n3xx(ZynqComponents, PeriphManagerBase):          self.mboard_regs_control.get_git_hash()          self.mboard_regs_control.get_build_timestamp()          self._check_fpga_compat() -        self.crossbar_base_port = self.mboard_regs_control.get_xbar_baseport()          # Init clocking          self.enable_ref_clock(enable=True)          self._ext_clock_freq = None @@ -463,8 +445,6 @@ class n3xx(ZynqComponents, PeriphManagerBase):          super(n3xx, self).deinit()          for xport_mgr in itervalues(self._xport_mgrs):              xport_mgr.deinit() -        self.log.trace("Resetting SID pool...") -        self._available_endpoints = list(range(256))      def tear_down(self):          """ @@ -489,66 +469,37 @@ class n3xx(ZynqComponents, PeriphManagerBase):      ###########################################################################      # Transport API      ########################################################################### -    def request_xport( -            self, -            dst_address, -            suggested_src_address, -            xport_type -        ): -        """ -        See PeriphManagerBase.request_xport() for docs. -        """ -        # Try suggested address first, then just pick the first available one: -        src_address = suggested_src_address -        if src_address not in self._available_endpoints: -            if len(self._available_endpoints) == 0: -                raise RuntimeError( -                    "Depleted pool of SID endpoints for this device!") -            else: -                src_address = self._available_endpoints[0] -        sid = SID(src_address << 16 | dst_address) -        # Note: This SID may change its source address! -        self.log.trace( -            "request_xport(dst=0x%04X, suggested_src_address=0x%04X, xport_type=%s): " \ -            "operating on temporary SID: %s", -            dst_address, suggested_src_address, str(xport_type), str(sid)) -        # FIXME token! -        assert self.device_info['rpc_connection'] in ('remote', 'local') -        if self.device_info['rpc_connection'] == 'remote': -            if self.device_info['product'] == 'n320': -                alloc_limit = 1 -            else: -                alloc_limit = 2 -            return self._xport_mgrs['udp'].request_xport( -                sid, -                xport_type, -                alloc_limit -            ) -        elif self.device_info['rpc_connection'] == 'local': -            return self._xport_mgrs['liberio'].request_xport( -                sid, -                xport_type, -            ) +    def get_chdr_link_types(self): +        """ +        This will only ever return a single item (udp or liberio). +        """ +        assert self.mboard_info['rpc_connection'] in ('remote', 'local') +        if self.mboard_info['rpc_connection'] == 'remote': +            return ["udp"] +        # else: +        return ["liberio"] -    def commit_xport(self, xport_info): +    def get_chdr_link_options(self, xport_type):          """ -        See PeriphManagerBase.commit_xport() for docs. +        Returns a list of dictionaries. Every dictionary contains information +        about one way to connect to this device in order to initiate CHDR +        traffic. + +        The interpretation of the return value is very highly dependant on the +        transport type (xport_type). +        For UDP, the every entry of the list has the following keys: +        - ipv4 (IP Address) +        - port (UDP port) +        - link_rate (bps of the link, e.g. 10e9 for 10GigE) -        Reminder: All connections are incoming, i.e. "send" or "TX" means -        remote device to local device, and "receive" or "RX" means this local -        device to remote device. "Remote device" can be, for example, a UHD -        session. +        For Liberio, every entry has the following keys: +        - tx_dev: TX device (/dev/tx-dma*) +        - rx_dev: RX device (/dev/rx-dma*)          """ -        ## Go, go, go -        assert self.device_info['rpc_connection'] in ('remote', 'local') -        sid = SID(xport_info['send_sid']) -        self._available_endpoints.remove(sid.src_ep) -        self.log.debug("Committing transport for SID %s, xport info: %s", -                       str(sid), str(xport_info)) -        if self.device_info['rpc_connection'] == 'remote': -            return self._xport_mgrs['udp'].commit_xport(sid, xport_info) -        elif self.device_info['rpc_connection'] == 'local': -            return self._xport_mgrs['liberio'].commit_xport(sid, xport_info) +        if xport_type not in self._xport_mgrs: +            self.log.warning("Can't get link options for unknown link type: `{}'.") +            return [] +        return self._xport_mgrs[xport_type].get_chdr_link_options()      ###########################################################################      # Device info @@ -569,6 +520,39 @@ class n3xx(ZynqComponents, PeriphManagerBase):          })          return device_info +    def set_device_id(self, device_id): +        """ +        Sets the device ID for this motherboard. +        The device ID is used to identify the RFNoC components associated with +        this motherboard. +        """ +        self.log.debug("Setting device ID to `{}'".format(device_id)) +        self.mboard_regs_control.set_device_id(device_id) + +    def get_device_id(self): +        """ +        Gets the device ID for this motherboard. +        The device ID is used to identify the RFNoC components associated with +        this motherboard. +        """ +        return self.mboard_regs_control.get_device_id() + +    def get_proto_ver(self): +        """ +        Return RFNoC protocol version +        """ +        proto_ver = self.mboard_regs_control.get_proto_ver() +        self.log.debug("RFNoC protocol version supported by this device is {}".format(proto_ver)) +        return proto_ver + +    def get_chdr_width(self): +        """ +        Return RFNoC CHDR width +        """ +        chdr_width = self.mboard_regs_control.get_chdr_width() +        self.log.debug("CHDR width supported by the device is {}".format(chdr_width)) +        return chdr_width +      ###########################################################################      # Clock/Time API      ########################################################################### @@ -1065,3 +1049,60 @@ class n3xx(ZynqComponents, PeriphManagerBase):          if self._bp_leds is not None:              # Turn off LINK              self._bp_leds.set(self._bp_leds.LED_LINK, 0) + +    ####################################################################### +    # Timekeeper API +    ####################################################################### +    def get_num_timekeepers(self): +        """ +        Return the number of timekeepers +        """ +        return self.mboard_regs_control.get_num_timekeepers() + +    def get_timekeeper_time(self, tk_idx, last_pps): +        """ +        Get the time in ticks + +        Arguments: +        tk_idx: Index of timekeeper +        next_pps: If True, get time at last PPS. Otherwise, get time now. +        """ +        return self.mboard_regs_control.get_timekeeper_time(tk_idx, last_pps) + +    def set_timekeeper_time(self, tk_idx, ticks, next_pps): +        """ +        Set the time in ticks + +        Arguments: +        tk_idx: Index of timekeeper +        ticks: Time in ticks +        next_pps: If True, set time at next PPS. Otherwise, set time now. +        """ +        self.mboard_regs_control.set_timekeeper_time(tk_idx, ticks, next_pps) + +    def set_tick_period(self, tk_idx, period_ns): +        """ +        Set the time per tick in nanoseconds (tick period) + +        Arguments: +        tk_idx: Index of timekeeper +        period_ns: Period in nanoseconds +        """ +        self.mboard_regs_control.set_tick_period(tk_idx, period_ns) + +    def get_clocks(self): +        """ +        Gets the RFNoC-related clocks present in the FPGA design +        """ +        return [ +            { +                'name': 'radio_clk', +                'freq': str(self.dboards[0].get_master_clock_rate()), +                'mutable': 'true' +            }, +            { +                'name': 'bus_clk', +                'freq': str(200e6), +            } +        ] + diff --git a/mpm/python/usrp_mpm/periph_manager/n3xx_periphs.py b/mpm/python/usrp_mpm/periph_manager/n3xx_periphs.py index cb6c237c2..fe005a716 100644 --- a/mpm/python/usrp_mpm/periph_manager/n3xx_periphs.py +++ b/mpm/python/usrp_mpm/periph_manager/n3xx_periphs.py @@ -165,12 +165,12 @@ class MboardRegsControl(object):      Control the FPGA Motherboard registers      """      # Motherboard registers -    M_COMPAT_NUM    = 0x0000 +    MB_COMPAT_NUM   = 0x0000      MB_DATESTAMP    = 0x0004      MB_GIT_HASH     = 0x0008      MB_SCRATCH      = 0x000C -    MB_NUM_CE       = 0x0010 -    MB_NUM_IO_CE    = 0x0014 +    MB_DEVICE_ID    = 0x0010 +    MB_RFNOC_INFO   = 0x0014      MB_CLOCK_CTRL   = 0x0018      MB_XADC_RB      = 0x001C      MB_BUS_CLK_RATE = 0x0020 @@ -179,7 +179,22 @@ class MboardRegsControl(object):      MB_SFP1_INFO    = 0x002C      MB_GPIO_MASTER  = 0x0030      MB_GPIO_RADIO_SRC  = 0x0034 -    MB_XBAR_BASEPORT   = 0x0038 +    MB_NUM_TIMEKEEPERS = 0x0048 +    # Timekeeper registers +    MB_TIME_NOW_LO         = 0x1000 +    MB_TIME_NOW_HI         = 0x1004 +    MB_TIME_EVENT_LO       = 0x1008 +    MB_TIME_EVENT_HI       = 0x100C +    MB_TIME_CTRL           = 0x1010 +    MB_TIME_LAST_PPS_LO    = 0x1014 +    MB_TIME_LAST_PPS_HI    = 0x1018 +    MB_TIME_BASE_PERIOD_LO = 0x101C +    MB_TIME_BASE_PERIOD_HI = 0x1020 +    MB_TIMEKEEPER_OFFSET   = 12 + +    # Bitfield locations for the MB_RFNOC_INFO register. +    MB_RFNOC_INFO_PROTO_VER = 0 +    MB_RFNOC_INFO_CHDR_WIDTH = 16      # Bitfield locations for the MB_CLOCK_CTRL register.      MB_CLOCK_CTRL_PPS_SEL_INT_10 = 0 # pps_sel is one-hot encoded! @@ -210,11 +225,49 @@ class MboardRegsControl(object):          2 numbers: (major compat number, minor compat number )          """          with self.regs: -            compat_number = self.peek32(self.M_COMPAT_NUM) +            compat_number = self.peek32(self.MB_COMPAT_NUM)          minor = compat_number & 0xff          major = (compat_number>>16) & 0xff          return (major, minor) +    def set_device_id(self, device_id): +        """ +        Set device ID +        """ +        with self.regs: +            self.log.trace("Writing MB_DEVICE_ID with 0x{:08X}".format(device_id)) +            return self.poke32(self.MB_DEVICE_ID, device_id) + +    def get_device_id(self): +        """ +        Get device ID +        """ +        with self.regs: +            reg_val = self.peek32(self.MB_DEVICE_ID) +            device_id = reg_val & 0x0000ffff +            self.log.trace("Read MB_DEVICE_ID 0x{:08X}".format(device_id)) +            return device_id + +    def get_proto_ver(self): +        """ +        Return RFNoC protocol version +        """ +        with self.regs: +            reg_val = self.peek32(self.MB_RFNOC_INFO) +            proto_ver = (reg_val & 0x0000ffff) >> self.MB_RFNOC_INFO_PROTO_VER +            self.log.trace("Read RFNOC_PROTO_VER 0x{:08X}".format(proto_ver)) +            return proto_ver; + +    def get_chdr_width(self): +        """ +        Return RFNoC CHDR width +        """ +        with self.regs: +            reg_val = self.peek32(self.MB_RFNOC_INFO) +            chdr_width = (reg_val & 0xffff0000) >> self.MB_RFNOC_INFO_CHDR_WIDTH +            self.log.trace("Read RFNOC_CHDR_WIDTH 0x{:08X}".format(chdr_width)) +            return chdr_width +      def set_fp_gpio_master(self, value):          """set driver for front panel GPIO          Arguments: @@ -416,10 +469,6 @@ class MboardRegsControl(object):                               .format(sfp0_type, sfp1_type))          return "" -    def get_xbar_baseport(self): -        "Get the RFNoC crossbar base port" -        with self.regs: -            return self.peek32(self.MB_XBAR_BASEPORT)  class RetimerQSFP(DS125DF410):      # (deemphasis, swing) diff --git a/mpm/python/usrp_mpm/rpc_server.py b/mpm/python/usrp_mpm/rpc_server.py index 2745ba59d..17179f04d 100644 --- a/mpm/python/usrp_mpm/rpc_server.py +++ b/mpm/python/usrp_mpm/rpc_server.py @@ -32,7 +32,7 @@ from usrp_mpm.sys_utils import net  TIMEOUT_INTERVAL = 5.0 # Seconds before claim expires (default value)  TOKEN_LEN = 16 # Length of the token string  # Compatibility number for MPM -MPM_COMPAT_NUM = (1, 2) +MPM_COMPAT_NUM = (2, 0)  def no_claim(func):      " Decorator for functions that require no token check " diff --git a/mpm/python/usrp_mpm/xports/xportmgr_liberio.py b/mpm/python/usrp_mpm/xports/xportmgr_liberio.py index c9c5ee0f1..f7c1861af 100644 --- a/mpm/python/usrp_mpm/xports/xportmgr_liberio.py +++ b/mpm/python/usrp_mpm/xports/xportmgr_liberio.py @@ -8,8 +8,6 @@ Liberio Transport manager  """  from builtins import object -from usrp_mpm.liberiotable import LiberioDispatcherTable -from usrp_mpm import lib  class XportMgrLiberio(object):      """ @@ -19,14 +17,9 @@ class XportMgrLiberio(object):      liberio_label = 'liberio'      # Number of available DMA channels      max_chan = 4 -    # Crossbar to which the Liberio DMA engine is connected -    xbar_dev = "/dev/crossbar0" -    xbar_port = 2      def __init__(self, log): -        self.log = log -        self._dma_dispatcher = LiberioDispatcherTable(self.liberio_label) -        self._data_chan_ctr = 0 +        self.log = log.getChild('Liberio')      def init(self, args):          """ @@ -36,7 +29,7 @@ class XportMgrLiberio(object):      def deinit(self):          " Clean up after a session terminates " -        self._data_chan_ctr = 0 +        pass      def get_xport_info(self):          """ @@ -48,50 +41,22 @@ class XportMgrLiberio(object):          In this case, returns an empty dict.          """ +        assert hasattr(self, 'log')          return {} -    def request_xport( -            self, -            sid, -            xport_type, -        ): -        """ -        Return liberio xport info +    def get_chdr_link_options(self):          """ -        assert xport_type in ('CTRL', 'ASYNC_MSG', 'TX_DATA', 'RX_DATA') -        if xport_type == 'CTRL': -            chan = 0 -        elif xport_type == 'ASYNC_MSG': -            chan = 1 -        else: -            if self.max_chan > 4: -                chan = 2 + self._data_chan_ctr -                self._data_chan_ctr += 1 -            else: -                if xport_type == 'RX_DATA': -                    chan = 2 -                else: -                    chan = 3 -        xport_info = { -            'type': 'liberio', -            'send_sid': str(sid), -            'muxed': str(xport_type in ('CTRL', 'ASYNC_MSG')) if (self.max_chan > 4) else 'True', -            'dma_chan': str(chan), -            'tx_dev': "/dev/tx-dma{}".format(chan), -            'rx_dev': "/dev/rx-dma{}".format(chan), -        } -        self.log.trace("Liberio: Chan: {} TX Device: {} RX Device: {}".format( -            chan, xport_info['tx_dev'], xport_info['rx_dev'])) -        self.log.trace("Liberio channel is muxed: %s", -                       "Yes" if xport_info['muxed'] else "No") -        return [xport_info] - -    def commit_xport(self, sid, xport_info): -        " Commit liberio transport " -        chan = int(xport_info['dma_chan']) -        xbar_iface = lib.xbar.xbar(self.xbar_dev) -        xbar_iface.set_route(sid.src_addr, self.xbar_port) -        self._dma_dispatcher.set_route(sid.reversed(), chan) -        self.log.trace("Liberio transport successfully committed!") -        return True +        Returns a list of dictionaries for returning by +        PeriphManagerBase.get_chdr_link_options(). +        Note: This requires a claim, which means that init() was called, and +        deinit() was not yet called. +        """ +        return [ +            { +                'tx_dev': "/dev/tx-dma{}".format(chan), +                'rx_dev': "/dev/rx-dma{}".format(chan), +                'dma_chan': str(chan), +            } +            for chan in range(self.max_chan) +        ] diff --git a/mpm/python/usrp_mpm/xports/xportmgr_udp.py b/mpm/python/usrp_mpm/xports/xportmgr_udp.py index 17762bb76..761db4a08 100644 --- a/mpm/python/usrp_mpm/xports/xportmgr_udp.py +++ b/mpm/python/usrp_mpm/xports/xportmgr_udp.py @@ -1,5 +1,6 @@  #  # Copyright 2017 Ettus Research, a National Instruments Company +# Copyright 2019 Ettus Research, a National Instruments Brand  #  # SPDX-License-Identifier: GPL-3.0-or-later  # @@ -9,10 +10,8 @@ UDP Transport manager  from builtins import object  from six import iteritems, itervalues -from usrp_mpm.ethtable import EthDispatcherTable +from usrp_mpm.ethdispatch import EthDispatcherCtrl  from usrp_mpm.sys_utils import net -from usrp_mpm.mpmtypes import SID -from usrp_mpm import lib  DEFAULT_BRIDGE_MODE = False @@ -25,40 +24,38 @@ class XportMgrUDP(object):      # iface_config = {      #     'eth1': { # Add key for every Ethernet iface connected to the FPGA      #         'label': 'misc-enet-regs0', # UIO label for the Eth table -    #         'xbar': 0, # Which crossbar? 0 -> /dev/crossbar0 -    #         'xbar_port': 0, # Which port on the crossbar it is connected to      #     },      # }      iface_config = {}      bridges = {} -    # The control addresses are typically addresses bound to the controlling -    # UHD session. When the requested source address is below or equal to this -    # number, we override requested SID source addresses based on other logic. -    max_ctrl_addr = 1      def __init__(self, log, args): -        assert len(self.iface_config) +        assert self.iface_config          assert all(( -            all((key in x for key in ('label', 'xbar', 'xbar_port'))) +            all((key in x for key in ('label',)))              for x in itervalues(self.iface_config)          )) -        self.log = log +        self.log = log.getChild('UDP')          self.log.trace("Initializing UDP xport manager...")          self._possible_chdr_ifaces = self.iface_config.keys()          self.log.trace("Identifying available network interfaces...") -        self.chdr_port = EthDispatcherTable.DEFAULT_VITA_PORT[0] -        self._chdr_ifaces = \ -            self._init_interfaces(self._possible_chdr_ifaces) +        self.chdr_port = EthDispatcherCtrl.DEFAULT_VITA_PORT[0] +        self._chdr_ifaces = self._init_interfaces(self._possible_chdr_ifaces)          self._bridge_mode = args.get('bridge_mode', DEFAULT_BRIDGE_MODE)          self._eth_dispatchers = {} -        self._allocations = {} -        self._previous_block_ep = {}      def _init_interfaces(self, possible_ifaces):          """          Enumerate all network interfaces that are currently able to stream CHDR          Returns a dictionary iface name -> iface info, where iface info is the          return value of get_iface_info(). + +        Arguments: +        - possible_ifaces: A list of strings containing iface names, e.g. +                          ["sfp0", "sfp1"] + +        Return Value: +        A list of dictionaries. The keys are determined by net.get_iface_info().          """          self.log.trace("Testing available interfaces out of `{}'".format(              list(possible_ifaces) @@ -83,7 +80,7 @@ class XportMgrUDP(object):                  "{} to {}."                  .format(len(valid_iface_infos), len(valid_iface_infos_filtered))              ) -        if len(valid_iface_infos_filtered): +        if valid_iface_infos_filtered:              self.log.debug(                  "Found CHDR interfaces: `{}'"                  .format(", ".join(list(valid_iface_infos.keys()))) @@ -104,10 +101,11 @@ class XportMgrUDP(object):              if len(self._chdr_ifaces) != 1 or bridge_iface != list(self.bridges.keys())[0]:                  self.log.error("No Bridge Interfaces found")                  raise RuntimeError("No Bridge Interfaces found") -            self.log.info("Updated dispatchers in bridge mode with bridge interface {}".format( -                bridge_iface)) +            self.log.info( +                "Updated dispatchers in bridge mode with bridge interface {}" +                .format(bridge_iface))              self._eth_dispatchers = { -                x: EthDispatcherTable(self.iface_config[x]['label']) +                x: EthDispatcherCtrl(self.iface_config[x]['label'])                  for x in self.bridges[bridge_iface]              }              for dispatcher, table in iteritems(self._eth_dispatchers): @@ -130,7 +128,7 @@ class XportMgrUDP(object):              for iface in self._chdr_ifaces:                  if iface not in self._eth_dispatchers:                      self._eth_dispatchers[iface] = \ -                        EthDispatcherTable(self.iface_config[iface]['label']) +                        EthDispatcherCtrl(self.iface_config[iface]['label'])                  self._eth_dispatchers[iface].set_ipv4_addr(                      self._chdr_ifaces[iface]['ip_addr']                  ) @@ -139,8 +137,7 @@ class XportMgrUDP(object):          """          Call this when the user calls 'init' on the periph manager          """ -        self._chdr_ifaces = \ -            self._init_interfaces(self._possible_chdr_ifaces) +        self._chdr_ifaces = self._init_interfaces(self._possible_chdr_ifaces)          if "bridge_mode" in args:              self._bridge_mode = args.get("bridge_mode")          self._update_dispatchers() @@ -153,16 +150,10 @@ class XportMgrUDP(object):                      args.get('forward_eth', False),                      args.get('forward_bcast', False)                  ) -        if 'preload_ethtables' in args: -            self._preload_ethtables( -                self._eth_dispatchers, -                args['preload_ethtables'] -            ) -      def deinit(self):          " Clean up after a session terminates " -        self._allocations = {} +        pass      def get_xport_info(self):          """ @@ -174,174 +165,25 @@ class XportMgrUDP(object):          In this case, returns the available IP addresses.          """ -        available_interfaces = \ -            self._init_interfaces(self._possible_chdr_ifaces) +        available_interfaces = self._init_interfaces(self._possible_chdr_ifaces)          return dict(zip(              ("addr", "second_addr", "third_addr", "fourth_addr"),              (x['ip_addr'] for x in itervalues(available_interfaces))          )) -    def _preload_ethtables(self, eth_dispatchers, table_file): -        """ -        Populates the ethernet tables from a JSON file +    def get_chdr_link_options(self):          """ -        import json -        try: -            eth_table_data = json.load(open(table_file)) -        except ValueError as ex: -            self.log.warning( -                "Bad values in preloading table file: %s", -                str(ex) -            ) -            return -        self.log.info( -            "Preloading Ethernet dispatch tables from JSON file `%s'.", -            table_file -        ) -        for eth_iface, data in iteritems(eth_table_data): -            if eth_iface not in eth_dispatchers: -                self.log.warning( -                    "Request to preload eth dispatcher table for " -                    "iface `{}', but no such interface is " -                    "registered. Known interfaces: {}".format( -                        str(eth_iface), -                        ",".join(eth_dispatchers.keys()) -                    ) -                ) -                continue -            eth_dispatcher = eth_dispatchers[eth_iface] -            self.log.debug("Preloading {} dispatch table".format(eth_iface)) -            try: -                for dst_addr, udp_data in iteritems(data): -                    sid = SID() -                    sid.set_dst_addr(int(dst_addr)) -                    eth_dispatcher.set_route( -                        sid, -                        udp_data['ip_addr'], -                        udp_data['port'], -                        udp_data.get('mac_addr', None) -                    ) -            except ValueError as ex: -                self.log.warning( -                    "Bad values in preloading table file: %s", -                    str(ex) -                ) +        Returns a list of dictionaries for returning by +        PeriphManagerBase.get_chdr_link_options(). -    def get_xbar_dev(self, iface): -        """ -        Given an Ethernet interface (e.g., 'eth1') returns the crossbar device -        it is connected to. +        Note: This requires a claim, which means that init() was called, and +        deinit() was not yet called.          """ -        xbar_idx = self.iface_config[iface]['xbar'] -        return "/dev/crossbar{}".format(xbar_idx) - -    def request_xport( -            self, -            sid, -            xport_type, -            alloc_limit=2 -        ): -        """ -        Return UDP xport info -        """ -        def fixup_sid(sid, iface_name): -            " Modify the source SID (e.g. the UHD SID) " -            if sid.src_addr <= self.max_ctrl_addr: -                sid.src_addr = self.iface_config[iface_name]['ctrl_src_addr'] -            return sid - -        def sort_xport_info(xport): -            """ -            We sort xport_info (which is a list of xport) as follows: -            1. Look at current allocation of xport src_addr (which is the addr -               of host). If the allocation too large, in this case larger or -               equal to 2 (since 2*125 = 250MS/s = max bandwidth of SFP+ port), -               we will use the allocation for sorting. -            2. Else, we need to look at the destination block. The priority will -               yield to the xport that has the previous destination block -               that is the same as this coming destination block. -            Note: smaller number return is the higher chance to be picked -            """ -            sid = SID(xport['send_sid']) -            src_addr = sid.src_addr -            prev_block = -1 -            if src_addr in self._previous_block_ep: -                prev_block = self._previous_block_ep[src_addr] -            allocation = int(xport['allocation']) -            if allocation >= alloc_limit: -                return allocation -            else: -                return allocation if prev_block != sid.get_dst_block() else -1; - -        assert xport_type in ('CTRL', 'ASYNC_MSG', 'TX_DATA', 'RX_DATA') -        allocation_getter = lambda iface: { -            'CTRL': 0, -            'ASYNC_MSG': 0, -            'RX_DATA': self._allocations.get(iface, {}).get('rx', 0), -            'TX_DATA': self._allocations.get(iface, {}).get('tx', 0), -        }[xport_type] -        xport_info = sorted([ +        return [              { -                'type': 'UDP',                  'ipv4': str(iface_info['ip_addr']),                  'port': str(self.chdr_port), -                'send_sid': str(fixup_sid(sid, iface_name)), -                'allocation': str(allocation_getter(iface_name)), -                'xport_type': xport_type, -                'link_speed': str(iface_info['link_speed']) +                'link_rate': str(int(iface_info['link_speed'] * 1e9 / 8))              }              for iface_name, iface_info in iteritems(self._chdr_ifaces)          ] -            , key=lambda x: sort_xport_info(x) -            , reverse=False) -        return xport_info - -    def commit_xport(self, sid, xport_info): -        """ -        Commit transport -         -        Saves the transport configuration to the device. -        Returns the status of the commit. -        """ -        self.log.trace("Sanity checking xport_info %s...", str(xport_info)) -        assert xport_info['type'] == 'UDP' -        assert any([xport_info['ipv4'] == x['ip_addr'] -                    for x in itervalues(self._chdr_ifaces)]) -        assert xport_info['port'] == str(self.chdr_port) -        assert len(xport_info.get('src_ipv4')) > 5 -        assert int(xport_info.get('src_port')) > 0 -        sender_addr = xport_info['src_ipv4'] -        sender_port = int(xport_info['src_port']) -        self.log.trace("Incoming connection is coming from %s:%d", -                       sender_addr, sender_port) -        mac_addr = net.get_mac_addr(sender_addr) -        if mac_addr is None: -            raise RuntimeError( -                "Could not find MAC address for IP address {}".format( -                    sender_addr)) -        self.log.trace("Incoming connection is coming from %s", -                       mac_addr) -        eth_iface = net.ip_addr_to_iface(xport_info['ipv4'], self._chdr_ifaces) -        xbar_port = self.iface_config[eth_iface]['xbar_port'] -        self.log.trace("Using Ethernet interface %s, crossbar port %d", -                       eth_iface, xbar_port) -        xbar_iface = lib.xbar.xbar(self.get_xbar_dev(eth_iface)) -        xbar_iface.set_route(sid.src_addr, xbar_port) -        self._eth_dispatchers[eth_iface].set_route( -            sid.reversed(), sender_addr, sender_port) -        self.log.trace("UDP transport successfully committed!") -        self._previous_block_ep[sid.src_addr] = sid.get_dst_block() -        if xport_info.get('xport_type') == 'TX_DATA': -            self._allocations[eth_iface] = \ -                {'tx': self._allocations.get(eth_iface, {}).get('tx', 0) + 1} -        if xport_info.get('xport_type') == 'RX_DATA': -            self._allocations[eth_iface] = \ -                {'rx': self._allocations.get(eth_iface, {}).get('rx', 0) + 1} -        self.log.trace( -            "New link allocations for %s: TX: %d  RX: %d", -            eth_iface, -            self._allocations.get(eth_iface, {}).get('tx', 0), -            self._allocations.get(eth_iface, {}).get('rx', 0), -        ) -        return True - | 
