diff options
author | Martin Braun <martin.braun@ettus.com> | 2019-07-03 20:15:35 -0700 |
---|---|---|
committer | Martin Braun <martin.braun@ettus.com> | 2019-11-26 12:16:25 -0800 |
commit | c256b9df6502536c2e451e690f1ad5962c664d1a (patch) | |
tree | a83ad13e6f5978bbe14bb3ecf8294ba1e3d28db4 /mpm | |
parent | 9a8435ed998fc5c65257f4c55768750b227ab19e (diff) | |
download | uhd-c256b9df6502536c2e451e690f1ad5962c664d1a.tar.gz uhd-c256b9df6502536c2e451e690f1ad5962c664d1a.tar.bz2 uhd-c256b9df6502536c2e451e690f1ad5962c664d1a.zip |
x300/mpmd: Port all RFNoC devices to the new RFNoC framework
Co-Authored-By: Alex Williams <alex.williams@ni.com>
Co-Authored-By: Sugandha Gupta <sugandha.gupta@ettus.com>
Co-Authored-By: Brent Stapleton <brent.stapleton@ettus.com>
Co-Authored-By: Ciro Nishiguchi <ciro.nishiguchi@ni.com>
Diffstat (limited to '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 - |