diff options
-rw-r--r-- | mpm/CMakeLists.txt | 50 | ||||
-rw-r--r-- | mpm/python/CMakeLists.txt | 23 | ||||
-rw-r--r-- | mpm/python/usrp_mpm/CMakeLists.txt | 2 | ||||
-rw-r--r-- | mpm/python/usrp_mpm/__init__.py.in (renamed from mpm/python/usrp_mpm/__init__.py) | 18 | ||||
-rw-r--r-- | mpm/python/usrp_mpm/periph_manager/CMakeLists.txt | 1 | ||||
-rw-r--r-- | mpm/python/usrp_mpm/periph_manager/sim.py | 302 | ||||
-rw-r--r-- | mpm/python/usrp_mpm/sys_utils/net.py | 10 | ||||
-rw-r--r-- | mpm/python/usrp_mpm/xports/xportmgr_udp.py | 14 |
8 files changed, 376 insertions, 44 deletions
diff --git a/mpm/CMakeLists.txt b/mpm/CMakeLists.txt index a30d3acd9..e426716a6 100644 --- a/mpm/CMakeLists.txt +++ b/mpm/CMakeLists.txt @@ -119,7 +119,7 @@ if(_has_no_psabi) message(STATUS "Disabling psABI warnings.") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-psabi") endif(_has_no_psabi) -set(MPM_ALL_DEVICES n3xx e320 e31x tests) +set(MPM_ALL_DEVICES n3xx e320 e31x sim tests) set(MPM_DEVICE "n3xx" CACHE STRING "Choose an MPM device to build") set_property(CACHE MPM_DEVICE PROPERTY STRINGS ${MPM_ALL_DEVICES}) # Validate MPM_DEVICE @@ -137,6 +137,9 @@ elseif(MPM_DEVICE STREQUAL "e320") set(ENABLE_E320 ON) elseif(MPM_DEVICE STREQUAL "e31x") set(ENABLE_E300 ON) +elseif(MPM_DEVICE STREQUAL "sim") + set(ENABLE_SIM TRUE) + set(ENABLE_LIBMPM OFF) endif() MPM_REGISTER_COMPONENT("LibMPM" ENABLE_LIBMPM ON "Boost_FOUND" OFF ON) @@ -145,33 +148,38 @@ MPM_REGISTER_COMPONENT("Magnesium" ENABLE_MAGNESIUM OFF "ENABLE_MYKONOS" OFF OFF MPM_REGISTER_COMPONENT("E320" ENABLE_E320 OFF "ENABLE_LIBMPM" OFF OFF) MPM_REGISTER_COMPONENT("E300" ENABLE_E300 OFF "ENABLE_LIBMPM" OFF OFF) -add_subdirectory(include) -include_directories( - ${CMAKE_CURRENT_SOURCE_DIR}/include - ${CMAKE_BINARY_DIR}/include - ${UHD_HOST_ROOT}/include -) +if(NOT ENABLE_SIM) + add_subdirectory(include) + include_directories( + ${CMAKE_CURRENT_SOURCE_DIR}/include + ${CMAKE_BINARY_DIR}/include + ${UHD_HOST_ROOT}/include + ) -add_subdirectory(lib) + add_subdirectory(lib) -message("usrp_periphs objects: ${usrp_periphs_objects}") -add_library(usrp-periphs SHARED ${usrp_periphs_objects}) -target_link_libraries(usrp-periphs - udev - ${Boost_LIBRARIES} -) + message("usrp_periphs objects: ${usrp_periphs_objects}") + add_library(usrp-periphs SHARED ${usrp_periphs_objects}) + target_link_libraries(usrp-periphs + udev + ${Boost_LIBRARIES} + ) -install(TARGETS usrp-periphs LIBRARY DESTINATION ${LIBRARY_DIR} COMPONENT libraries) + install(TARGETS usrp-periphs LIBRARY DESTINATION ${LIBRARY_DIR} COMPONENT libraries) -# TODO: Come up with a versioning scheme for the MPM ABI. Not high priority -# though... we're the only ones linking against that. -set_target_properties(usrp-periphs PROPERTIES VERSION "${MPM_VERSION_MAJOR}.${MPM_VERSION_API}.${MPM_VERSION_ABI}") -set_target_properties(usrp-periphs PROPERTIES SOVERSION ${MPM_VERSION_MAJOR}) + # TODO: Come up with a versioning scheme for the MPM ABI. Not high priority + # though... we're the only ones linking against that. + set_target_properties(usrp-periphs PROPERTIES VERSION "${MPM_VERSION_MAJOR}.${MPM_VERSION_API}.${MPM_VERSION_ABI}") + set_target_properties(usrp-periphs PROPERTIES SOVERSION ${MPM_VERSION_MAJOR}) + + add_subdirectory(systemd) + add_subdirectory(tools) +endif(NOT ENABLE_SIM) enable_testing() add_subdirectory(python) -add_subdirectory(tools) -add_subdirectory(systemd) + + ######################################################################## # Print Summary diff --git a/mpm/python/CMakeLists.txt b/mpm/python/CMakeLists.txt index 5691a67a2..b1bc9fa16 100644 --- a/mpm/python/CMakeLists.txt +++ b/mpm/python/CMakeLists.txt @@ -16,15 +16,17 @@ elseif(MPM_DEVICE STREQUAL "e31x") add_library(pyusrp_periphs SHARED pyusrp_periphs/e31x/pyusrp_periphs.cpp) endif(MPM_DEVICE STREQUAL "n3xx") -target_include_directories(pyusrp_periphs PUBLIC - ${PYTHON_INCLUDE_DIRS} - ${CMAKE_SOURCE_DIR}/lib/ - ${UHD_HOST_ROOT}/lib/usrp/common - ${UHD_HOST_ROOT}/lib/usrp/common/ad9361_driver - ${UHD_HOST_ROOT}/lib/deps/pybind11/include -) -target_link_libraries(pyusrp_periphs ${Boost_LIBRARIES} usrp-periphs) -add_custom_command(TARGET pyusrp_periphs POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/libpyusrp_periphs.so ${CMAKE_CURRENT_BINARY_DIR}/usrp_mpm/libpyusrp_periphs.so) +if(NOT ENABLE_SIM) + target_include_directories(pyusrp_periphs PUBLIC + ${PYTHON_INCLUDE_DIRS} + ${CMAKE_SOURCE_DIR}/lib/ + ${UHD_HOST_ROOT}/lib/usrp/common + ${UHD_HOST_ROOT}/lib/usrp/common/ad9361_driver + ${UHD_HOST_ROOT}/lib/deps/pybind11/include + ) + target_link_libraries(pyusrp_periphs ${Boost_LIBRARIES} usrp-periphs) + add_custom_command(TARGET pyusrp_periphs POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/libpyusrp_periphs.so ${CMAKE_CURRENT_BINARY_DIR}/usrp_mpm/libpyusrp_periphs.so) +endif(NOT ENABLE_SIM) set(USRP_MPM_FILES "") add_subdirectory(usrp_mpm) @@ -34,9 +36,12 @@ set(SETUP_PY_IN "${CMAKE_CURRENT_SOURCE_DIR}/setup.py.in") set(SETUP_PY "${CMAKE_CURRENT_BINARY_DIR}/setup.py") set(PERIPH_MGR_INIT_IN "${CMAKE_CURRENT_SOURCE_DIR}/usrp_mpm/periph_manager/__init__.py.in") set(PERIPH_MGR_INIT "${CMAKE_CURRENT_BINARY_DIR}/usrp_mpm/periph_manager/__init__.py") +set(USRP_MPM_INIT_IN "${CMAKE_CURRENT_SOURCE_DIR}/usrp_mpm/__init__.py.in") +set(USRP_MPM_INIT "${CMAKE_CURRENT_BINARY_DIR}/usrp_mpm/__init__.py") configure_file(${SETUP_PY_IN} ${SETUP_PY}) configure_file(${PERIPH_MGR_INIT_IN} ${PERIPH_MGR_INIT}) +configure_file(${USRP_MPM_INIT_IN} ${USRP_MPM_INIT}) configure_file("${CMAKE_CURRENT_SOURCE_DIR}/usrp_hwd.py" "${CMAKE_CURRENT_BINARY_DIR}/usrp_hwd.py" COPYONLY) add_custom_command(OUTPUT ${OUTPUT} diff --git a/mpm/python/usrp_mpm/CMakeLists.txt b/mpm/python/usrp_mpm/CMakeLists.txt index 9ea114e0b..93c140a8d 100644 --- a/mpm/python/usrp_mpm/CMakeLists.txt +++ b/mpm/python/usrp_mpm/CMakeLists.txt @@ -9,7 +9,7 @@ ######################################################################## set(USRP_MPM_FILES ${USRP_MPM_FILES}) set(USRP_MPM_TOP_FILES - ${CMAKE_CURRENT_SOURCE_DIR}/__init__.py + ${CMAKE_CURRENT_SOURCE_DIR}/__init__.py.in ${CMAKE_CURRENT_SOURCE_DIR}/aurora_control.py ${CMAKE_CURRENT_SOURCE_DIR}/bfrfs.py ${CMAKE_CURRENT_SOURCE_DIR}/components.py diff --git a/mpm/python/usrp_mpm/__init__.py b/mpm/python/usrp_mpm/__init__.py.in index a8efbd498..c2134d092 100644 --- a/mpm/python/usrp_mpm/__init__.py +++ b/mpm/python/usrp_mpm/__init__.py.in @@ -7,18 +7,24 @@ MPM Module """ -from . import libpyusrp_periphs as lib +__simulated__ = ("${MPM_DEVICE}" == "sim") + from .discovery import spawn_discovery_process from .rpc_server import spawn_rpc_process from . import mpmtypes -from . import periph_manager -from . import dboard_manager -from . import xports -from . import cores -from . import chips from . import gpsd_iface from .mpmlog import get_main_logger +if not __simulated__: + from . import libpyusrp_periphs as lib + from . import periph_manager + from . import dboard_manager + from . import xports + from . import cores + from . import chips +else: + from . import periph_manager + __version__ = periph_manager.__version__ __githash__ = periph_manager.__githash__ __mpm_device__ = periph_manager.__mpm_device__ diff --git a/mpm/python/usrp_mpm/periph_manager/CMakeLists.txt b/mpm/python/usrp_mpm/periph_manager/CMakeLists.txt index 4f8520c12..747b8967a 100644 --- a/mpm/python/usrp_mpm/periph_manager/CMakeLists.txt +++ b/mpm/python/usrp_mpm/periph_manager/CMakeLists.txt @@ -18,6 +18,7 @@ set(USRP_MPM_PERIPHMGR_FILES ${CMAKE_CURRENT_SOURCE_DIR}/e320_periphs.py ${CMAKE_CURRENT_SOURCE_DIR}/e31x.py ${CMAKE_CURRENT_SOURCE_DIR}/e31x_periphs.py + ${CMAKE_CURRENT_SOURCE_DIR}/sim.py ) list(APPEND USRP_MPM_FILES ${USRP_MPM_PERIPHMGR_FILES}) set(USRP_MPM_FILES ${USRP_MPM_FILES} PARENT_SCOPE) diff --git a/mpm/python/usrp_mpm/periph_manager/sim.py b/mpm/python/usrp_mpm/periph_manager/sim.py new file mode 100644 index 000000000..00ba5ba38 --- /dev/null +++ b/mpm/python/usrp_mpm/periph_manager/sim.py @@ -0,0 +1,302 @@ +# +# Copyright 2020 Ettus Research, a National Instruments Brand +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +""" +usrp simulation module + +This module is used to emulate a usrp when running on a standard +computer. You can build mpm in this configuration by using the cmake +flag -DMPM_DEVICE=sim +""" + +from pyroute2 import IPRoute +from usrp_mpm.xports import XportMgrUDP +from usrp_mpm.mpmlog import get_logger +from usrp_mpm.rpc_server import no_claim +from usrp_mpm.periph_manager import PeriphManagerBase + +CLOCK_SOURCE_INTERNAL = "internal" + +E320_DBOARD_SLOT_IDX = 0 + +class SimXportMgrUDP(XportMgrUDP): + """This is an adaptor class for the normal XportMgrUDP + In radios, the interface names are hardcoded. Since we are on a + desktop computer, we generate the names at runtime. + """ + def __init__(self, log, args, eth_dispatcher_cls): + with IPRoute() as ipr: + self.iface_config = { + link.get_attr('IFLA_IFNAME'): { + 'label': link.get_attr('IFLA_IFNAME'), + 'type': 'forward' + } for link in ipr.get_links() + } + super().__init__(log, args, eth_dispatcher_cls) + +class SimEthDispatcher: + """This is the hardware specific part of the normal XportMgrUDP + that we have to simulate. We get the ipv4 addr with IPRoute + instead of registers + """ + DEFAULT_VITA_PORT = (49153, 49154) + LOG = None + + def __init__(self, if_name): + self.log = get_logger(if_name) + self.if_name = if_name + + def set_ipv4_addr(self, addr): + """This doesn't actually change the ipv4 address, it just + checks to make sure the requested address is already our + address, and complains otherwise. + """ + with IPRoute() as ipr: + valid_iface_idx = ipr.link_lookup(ifname=self.if_name)[0] + link_info = ipr.get_links(valid_iface_idx)[0] + real_addr = link_info.get_attr('IFLA_ADDRESS') + if addr != real_addr: + self.log.warning("Cannot change ip address on simulator! Requested: {}, Actual: {}" + .format(addr, real_addr)) + +class sim(PeriphManagerBase): + """This is a periph manager that is designed to run on a regular + computer rather than the arm core on an SDR + """ + ######################################################################### + # Overridables + # + # See PeriphManagerBase for documentation on these fields + ######################################################################### + description = "E320-Series Device - SIMULATED" + pids = {0xE320: 'e320'} + + mboard_info = {"type": "e3xx", "product": "e320"} + mboard_max_rev = 7 # RevC + mboard_sensor_callback_map = {} + + ########################################################################### + # Ctor and device initialization tasks + ########################################################################### + def __init__(self, args): + super().__init__() + self.device_id = 1 + + # Unlike the real hardware drivers, if there is an exception here, + # we just crash. No use missing an error when testing. + self._init_peripherals(args) + self.init_dboards(args) + if not args.get('skip_boot_init', False): + self.init(args) + + @classmethod + def generate_device_info(cls, eeprom_md, mboard_info, dboard_infos): + """ + Hard-code our product map + """ + # Add the default PeriphManagerBase information first + device_info = super().generate_device_info( + eeprom_md, mboard_info, dboard_infos) + # Then add device-specific information + mb_pid = eeprom_md.get('pid') + device_info['product'] = cls.pids.get(mb_pid, 'unknown') + return device_info + + def _read_mboard_eeprom(self): + """ + Read out a simulated mboard eeprom and saves it to the appropriate member variable + """ + self._eeprom_head = sim._generate_eeprom_head() + + self.log.trace("Found EEPROM metadata: '{}'" + .format(str(self._eeprom_head))) + return (self._eeprom_head, None) + + @staticmethod + def _generate_eeprom_head(serial=b'3196D2A', rev=2, rev_compat=2): + return {'pid': 0xE320, + 'rev': rev, + 'rev_compat': rev_compat, + 'serial': serial} + + def _init_peripherals(self, args): + """ + Turn on all peripherals. This may throw an error on failure, so make + sure to catch it. + + Peripherals are initialized in the order of least likely to fail, to most + likely. + """ + # Sanity checks + assert self.mboard_info.get('product') in self.pids.values(), \ + "Device product could not be determined!" + # Init peripherals + + # Init CHDR transports + self._xport_mgrs = { + 'udp': SimXportMgrUDP(self.log, args, SimEthDispatcher) + } + #TODO: Actually create transports here when RFNoC is integrated + self.log.trace("CHDR transport creation was skipped") + + # Init complete. + self.log.debug("Device info: {}".format(self.device_info)) + + ########################################################################### + # Device info + ########################################################################### + def get_device_info_dyn(self): + """ + Append the device info with current IP addresses. + """ + if not self._device_initialized: + return {} + device_info = self._xport_mgrs['udp'].get_xport_info() + self.log.warn("get_device_info_dyn() - FPGA functionality not implemented yet") + 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.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.device_id + + @no_claim + def get_proto_ver(self): + """ + Return RFNoC protocol version + """ + return 0x100 + + @no_claim + def get_chdr_width(self): + """ + Return RFNoC CHDR width + """ + return 64 + + ########################################################################### + # Transport API + ########################################################################### + def get_chdr_link_types(self): + """ + This will only ever return a single item (udp). + """ + return ["udp"] + + def get_chdr_link_options(self, xport_type): + """ + 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) + """ + if xport_type not in self._xport_mgrs: + self.log.warning("Can't get link options for unknown link type: '{}'." + .format(xport_type)) + return [] + return self._xport_mgrs[xport_type].get_chdr_link_options() + + ####################################################################### + # Timekeeper API + ####################################################################### + def get_master_clock_rate(self): + """ Return the master clock rate set during init """ + return self._master_clock_rate + + def get_num_timekeepers(self): + """ + Return the number of timekeepers + """ + return 1 + + 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.log.debug("Setting timekeeper time (tx_idx:{}, ticks: {}, next_pps: {})" + .format(tk_idx, ticks, next_pps)) + + 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 0 + + 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.log.debug("Setting tick period (tk_idx: {}, period_ns: {})" + .format(tk_idx, period_ns)) + + def get_clocks(self): + """ + Gets the RFNoC-related clocks present in the FPGA design + """ + return [ + { + 'name': 'radio_clk', + 'freq': str(122.88e6), + 'mutable': 'true' + }, + { + 'name': 'bus_clk', + 'freq': str(200e6), + }, + { + 'name': 'ctrl_clk', + 'freq': str(40e6), + } + ] + + def get_time_sources(self): + " Returns list of valid time sources " + return (CLOCK_SOURCE_INTERNAL,) + + def get_clock_sources(self): + " Lists all available clock sources. " + return (CLOCK_SOURCE_INTERNAL,) + + def get_clock_source(self): + " Returns the current Clock Source " + return CLOCK_SOURCE_INTERNAL + + def set_clock_source(self, source): + " No-op which sets the clock source on a real radio " + self.log.debug("Setting clock source to {}".format(source)) + + def set_channel_mode(self, channel_mode): + " No-op which sets the channel mode on a real radio " + self.log.debug("Using channel mode {}".format(channel_mode)) diff --git a/mpm/python/usrp_mpm/sys_utils/net.py b/mpm/python/usrp_mpm/sys_utils/net.py index a9be7b87d..6ba5f0c3e 100644 --- a/mpm/python/usrp_mpm/sys_utils/net.py +++ b/mpm/python/usrp_mpm/sys_utils/net.py @@ -31,7 +31,10 @@ def get_valid_interfaces(iface_list): continue valid_iface_idx = valid_iface_idx[0] link_info = ipr.get_links(valid_iface_idx)[0] - if link_info.get_attr('IFLA_OPERSTATE') == 'UP' \ + # IFLA_OPERSTATE attribute isn't implemented on WSL + # Workaround is ignore it in the simulator + from usrp_mpm import __simulated__ + if (link_info.get_attr('IFLA_OPERSTATE') == 'UP' or __simulated__) \ and len(get_iface_addrs(link_info.get_attr('IFLA_ADDRESS'))): assert link_info.get_attr('IFLA_IFNAME') == iface valid_ifaces.append(iface) @@ -83,6 +86,11 @@ def get_link_speed(ifname): The speed is Megabits/sec (from kernel at https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-class-net) """ + # This wasn't implemented in WSL or in the linux pc I tested it on + # We will return a sensible default + from usrp_mpm import __simulated__ + if __simulated__: + return 1000 net_sysfs = [device for device in pyudev.Context().list_devices(subsystem='net') if device.sys_name == ifname][0] diff --git a/mpm/python/usrp_mpm/xports/xportmgr_udp.py b/mpm/python/usrp_mpm/xports/xportmgr_udp.py index 099a67d2a..197a628e4 100644 --- a/mpm/python/usrp_mpm/xports/xportmgr_udp.py +++ b/mpm/python/usrp_mpm/xports/xportmgr_udp.py @@ -8,10 +8,10 @@ UDP Transport manager """ +import importlib import subprocess from six import iteritems, itervalues from usrp_mpm import prefs -from usrp_mpm.ethdispatch import EthDispatcherCtrl from usrp_mpm.sys_utils import net DEFAULT_BRIDGE_MODE = False @@ -30,7 +30,9 @@ class XportMgrUDP: iface_config = {} bridges = {} - def __init__(self, log, args): + def __init__(self, log, args, eth_dispatcher_cls=None): + self.eth_dispatcher_cls = eth_dispatcher_cls or \ + importlib.import_module('usrp_mpm.ethdispatch').EthDispatcherCtrl assert self.iface_config assert all(( all((key in x for key in ('label',))) @@ -40,7 +42,7 @@ class XportMgrUDP: 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 = EthDispatcherCtrl.DEFAULT_VITA_PORT[0] + self.chdr_port = self.eth_dispatcher_cls.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 = {} @@ -106,7 +108,7 @@ class XportMgrUDP: "Updated dispatchers in bridge mode with bridge interface {}" .format(bridge_iface)) self._eth_dispatchers = { - x: EthDispatcherCtrl(self.iface_config[x]['label']) + x: self.eth_dispatcher_cls(self.iface_config[x]['label']) for x in self.bridges[bridge_iface] } for dispatcher, table in iteritems(self._eth_dispatchers): @@ -132,7 +134,7 @@ class XportMgrUDP: continue if iface not in self._eth_dispatchers: self._eth_dispatchers[iface] = \ - EthDispatcherCtrl(self.iface_config[iface]['label']) + self.eth_dispatcher_cls(self.iface_config[iface]['label']) self._eth_dispatchers[iface].set_ipv4_addr( self._chdr_ifaces[iface]['ip_addr'] ) @@ -218,7 +220,7 @@ class XportMgrUDP: deinit() was not yet called. """ assert host_location in ('remote', 'local', 'all') - + return [ { 'ipv4': str(iface_info['ip_addr']) if (self.iface_config[iface_name]['type'] != 'internal') |