From d3e6dd11406893bfbc5537dfbe74d8151bbc1280 Mon Sep 17 00:00:00 2001 From: Martin Braun Date: Tue, 12 Dec 2017 09:59:50 -0800 Subject: mpm: Harmonize imports, tidy + sort modules - Moved nijesdcore to cores/ - Moved udev, net, dtoverlay, uio to sys_utils/ - Made all imports non-relative (except in __init__.py files) - Removed some unnecessary imports - Reordered some imports for Python conventions --- mpm/python/setup.py.in | 1 + mpm/python/usrp_mpm/CMakeLists.txt | 10 +- mpm/python/usrp_mpm/__init__.py | 3 + mpm/python/usrp_mpm/aurora_control.py | 2 +- mpm/python/usrp_mpm/chips/lmk04828.py | 2 +- mpm/python/usrp_mpm/cores/CMakeLists.txt | 1 + mpm/python/usrp_mpm/cores/nijesdcore.py | 325 ++++++++++++++++++++++ mpm/python/usrp_mpm/cores/tdc_sync.py | 2 +- mpm/python/usrp_mpm/dboard_manager/__init__.py | 1 - mpm/python/usrp_mpm/dboard_manager/base.py | 2 +- mpm/python/usrp_mpm/dboard_manager/eiscat.py | 10 +- mpm/python/usrp_mpm/dboard_manager/lmk_eiscat.py | 2 +- mpm/python/usrp_mpm/dboard_manager/lmk_mg.py | 3 +- mpm/python/usrp_mpm/dboard_manager/magnesium.py | 16 +- mpm/python/usrp_mpm/dboard_manager/test.py | 4 +- mpm/python/usrp_mpm/dboard_manager/unknown.py | 3 +- mpm/python/usrp_mpm/discovery.py | 2 +- mpm/python/usrp_mpm/dtoverlay.py | 129 --------- mpm/python/usrp_mpm/ethtable.py | 6 +- mpm/python/usrp_mpm/liberiotable.py | 4 +- mpm/python/usrp_mpm/net.py | 130 --------- mpm/python/usrp_mpm/nijesdcore.py | 325 ---------------------- mpm/python/usrp_mpm/periph_manager/CMakeLists.txt | 1 - mpm/python/usrp_mpm/periph_manager/__init__.py.in | 6 +- mpm/python/usrp_mpm/periph_manager/base.py | 12 +- mpm/python/usrp_mpm/periph_manager/n310.py | 14 +- mpm/python/usrp_mpm/periph_manager/test.py | 4 +- mpm/python/usrp_mpm/periph_manager/udev.py | 52 ---- mpm/python/usrp_mpm/rpc_server.py | 2 +- mpm/python/usrp_mpm/sys_utils/CMakeLists.txt | 18 ++ mpm/python/usrp_mpm/sys_utils/__init__.py | 11 + mpm/python/usrp_mpm/sys_utils/dtoverlay.py | 129 +++++++++ mpm/python/usrp_mpm/sys_utils/net.py | 130 +++++++++ mpm/python/usrp_mpm/sys_utils/sysfs_gpio.py | 199 +++++++++++++ mpm/python/usrp_mpm/sys_utils/udev.py | 51 ++++ mpm/python/usrp_mpm/sys_utils/uio.py | 167 +++++++++++ mpm/python/usrp_mpm/sysfs_gpio.py | 199 ------------- mpm/python/usrp_mpm/uio.py | 167 ----------- mpm/python/usrp_mpm/xports/xportmgr_liberio.py | 2 +- mpm/python/usrp_mpm/xports/xportmgr_udp.py | 4 +- 40 files changed, 1084 insertions(+), 1067 deletions(-) create mode 100644 mpm/python/usrp_mpm/cores/nijesdcore.py delete mode 100644 mpm/python/usrp_mpm/dtoverlay.py delete mode 100644 mpm/python/usrp_mpm/net.py delete mode 100644 mpm/python/usrp_mpm/nijesdcore.py delete mode 100644 mpm/python/usrp_mpm/periph_manager/udev.py create mode 100644 mpm/python/usrp_mpm/sys_utils/CMakeLists.txt create mode 100644 mpm/python/usrp_mpm/sys_utils/__init__.py create mode 100644 mpm/python/usrp_mpm/sys_utils/dtoverlay.py create mode 100644 mpm/python/usrp_mpm/sys_utils/net.py create mode 100644 mpm/python/usrp_mpm/sys_utils/sysfs_gpio.py create mode 100644 mpm/python/usrp_mpm/sys_utils/udev.py create mode 100644 mpm/python/usrp_mpm/sys_utils/uio.py delete mode 100644 mpm/python/usrp_mpm/sysfs_gpio.py delete mode 100644 mpm/python/usrp_mpm/uio.py (limited to 'mpm/python') diff --git a/mpm/python/setup.py.in b/mpm/python/setup.py.in index 9c6888a74..583f79189 100755 --- a/mpm/python/setup.py.in +++ b/mpm/python/setup.py.in @@ -44,6 +44,7 @@ setup(name='usrp_mpm', 'usrp_mpm.dboard_manager', 'usrp_mpm.chips', 'usrp_mpm.cores', + 'usrp_mpm.sys_utils', 'usrp_mpm.xports', ], install_requires=[ diff --git a/mpm/python/usrp_mpm/CMakeLists.txt b/mpm/python/usrp_mpm/CMakeLists.txt index b308625ca..81af6eb6b 100644 --- a/mpm/python/usrp_mpm/CMakeLists.txt +++ b/mpm/python/usrp_mpm/CMakeLists.txt @@ -24,23 +24,19 @@ SET(USRP_MPM_TOP_FILES ${CMAKE_CURRENT_SOURCE_DIR}/aurora_control.py ${CMAKE_CURRENT_SOURCE_DIR}/bfrfs.py ${CMAKE_CURRENT_SOURCE_DIR}/discovery.py - ${CMAKE_CURRENT_SOURCE_DIR}/dtoverlay.py ${CMAKE_CURRENT_SOURCE_DIR}/eeprom.py ${CMAKE_CURRENT_SOURCE_DIR}/ethtable.py ${CMAKE_CURRENT_SOURCE_DIR}/liberiotable.py ${CMAKE_CURRENT_SOURCE_DIR}/mpmlog.py ${CMAKE_CURRENT_SOURCE_DIR}/mpmtypes.py ${CMAKE_CURRENT_SOURCE_DIR}/mpmutils.py - ${CMAKE_CURRENT_SOURCE_DIR}/net.py - ${CMAKE_CURRENT_SOURCE_DIR}/nijesdcore.py ${CMAKE_CURRENT_SOURCE_DIR}/rpc_server.py - ${CMAKE_CURRENT_SOURCE_DIR}/sysfs_gpio.py - ${CMAKE_CURRENT_SOURCE_DIR}/uio.py ) LIST(APPEND USRP_MPM_FILES ${USRP_MPM_TOP_FILES}) -ADD_SUBDIRECTORY(periph_manager) -ADD_SUBDIRECTORY(dboard_manager) ADD_SUBDIRECTORY(chips) ADD_SUBDIRECTORY(cores) +ADD_SUBDIRECTORY(dboard_manager) +ADD_SUBDIRECTORY(periph_manager) +ADD_SUBDIRECTORY(sys_utils) ADD_SUBDIRECTORY(xports) SET(USRP_MPM_FILES ${USRP_MPM_FILES} PARENT_SCOPE) diff --git a/mpm/python/usrp_mpm/__init__.py b/mpm/python/usrp_mpm/__init__.py index 7a907312d..b2b23f494 100644 --- a/mpm/python/usrp_mpm/__init__.py +++ b/mpm/python/usrp_mpm/__init__.py @@ -18,10 +18,13 @@ MPM Module """ +from . import libpyusrp_periphs as lib 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 .mpmlog import get_main_logger diff --git a/mpm/python/usrp_mpm/aurora_control.py b/mpm/python/usrp_mpm/aurora_control.py index 6531e8502..cef9280b6 100644 --- a/mpm/python/usrp_mpm/aurora_control.py +++ b/mpm/python/usrp_mpm/aurora_control.py @@ -22,7 +22,7 @@ import math import time from builtins import str from builtins import object -from .mpmlog import get_logger +from usrp_mpm.mpmlog import get_logger def mean(vals): " Calculate arithmetic mean of vals " diff --git a/mpm/python/usrp_mpm/chips/lmk04828.py b/mpm/python/usrp_mpm/chips/lmk04828.py index d9907a49f..7be0c8358 100644 --- a/mpm/python/usrp_mpm/chips/lmk04828.py +++ b/mpm/python/usrp_mpm/chips/lmk04828.py @@ -20,7 +20,7 @@ LMK04828 parent driver class import math from builtins import object -from ..mpmlog import get_logger +from usrp_mpm.mpmlog import get_logger class LMK04828(object): """ diff --git a/mpm/python/usrp_mpm/cores/CMakeLists.txt b/mpm/python/usrp_mpm/cores/CMakeLists.txt index 9103ad994..25949099d 100644 --- a/mpm/python/usrp_mpm/cores/CMakeLists.txt +++ b/mpm/python/usrp_mpm/cores/CMakeLists.txt @@ -19,6 +19,7 @@ SET(USRP_MPM_FILES ${USRP_MPM_FILES}) SET(USRP_MPM_CORE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/__init__.py ${CMAKE_CURRENT_SOURCE_DIR}/tdc_sync.py + ${CMAKE_CURRENT_SOURCE_DIR}/nijesdcore.py ) LIST(APPEND USRP_MPM_FILES ${USRP_MPM_CORE_FILES}) SET(USRP_MPM_FILES ${USRP_MPM_FILES} PARENT_SCOPE) diff --git a/mpm/python/usrp_mpm/cores/nijesdcore.py b/mpm/python/usrp_mpm/cores/nijesdcore.py new file mode 100644 index 000000000..12be51181 --- /dev/null +++ b/mpm/python/usrp_mpm/cores/nijesdcore.py @@ -0,0 +1,325 @@ +# +# Copyright 2017 Ettus Research (National Instruments) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +""" +JESD FPGA Core Interface +""" + +import time +from builtins import hex +from builtins import object +from usrp_mpm.mpmlog import get_logger + +class NIMgJESDCore(object): + """ + Provide interface for the FPGA JESD Core. + Works with Magnesium/Mykonos daughterboards only. + + Arguments: + regs -- regs class to use for peek/poke + """ + + DB_ID = 0x0630 + MGT_QPLL_CONTROL = 0x2000 + MGT_PLL_POWER_DOWN_CONTROL = 0x200C + MGT_TX_RESET_CONTROL = 0x2020 + MGT_RX_RESET_CONTROL = 0x2024 + MGT_RECEIVER_CONTROL = 0x2040 + MGT_RX_DESCRAMBLER_CONTROL = 0x2050 + MGT_TRANSMITTER_CONTROL = 0x2060 + MGT_TX_TRANSCEIVER_CONTROL = 0x2064 + MGT_TX_SCRAMBLER_CONTROL = 0x2068 + LMK_SYNC_CONTROL = 0x206C + JESD_MGT_DRP_CONTROL = 0x2070 + SYSREF_CAPTURE_CONTROL = 0x2078 + JESD_SIGNATURE_REG = 0x2100 + JESD_REVISION_REG = 0x2104 + + + def __init__(self, regs, slot_idx=0): + self.regs = regs + self.log = get_logger("NIMgJESDCore-{}".format(slot_idx)) + assert hasattr(self.regs, 'peek32') + assert hasattr(self.regs, 'poke32') + # FutureWork: The following are constants for the Magnesium board. These need + # to change to variables to support other interfaces. + self.qplls_used = 1 + self.cplls_used = 0 + # Number of FPGA clock cycles per LMFC period. + self.lmfc_divider = 20 + + def unreset_qpll(self): + # new_val = self.regs.peek32(0x0) & ~0x8 + # self.log.trace("Unresetting MMCM, writing value {:X}".format(new_val)) + self.regs.poke32(0x0, 0x7) + + def check_core(self): + """ + Verify JESD core returns correct ID + """ + self.log.trace("Checking JESD Core...") + if self.regs.peek32(self.JESD_SIGNATURE_REG) != 0x4A455344: + raise Exception('JESD Core signature mismatch! Check that core is mapped correctly') + #if self.regs.peek32(JESD_REVISION_REG) != 0xFF + #error here for date revision mismatch + self.log.trace("JESD Core build code: {0}".format(hex(self.regs.peek32(self.JESD_REVISION_REG)))) + self.log.trace("DB Slot #: {}".format( (self.regs.peek32(self.DB_ID) & 0x10000) >> 16 )) + self.log.trace("DB PID: {:X}" .format( self.regs.peek32(self.DB_ID) & 0xFFFF )) + return True + + def reset(self): + """ + Reset to the core. Places the PLLs, TX MGTs and RX MGTs (along with the glue + logic) in reset. Also disables the SYSREF sampler. + """ + self.log.trace("Resetting the JESD204B FPGA core(s)...") + self._gt_reset('tx', reset_only=True) + self._gt_reset('rx', reset_only=True) + self._gt_pll_lock_control(self.qplls_used, self.cplls_used, reset_only=True) + # Disable SYSREF Sampler + self.enable_lmfc(False) + + def init_deframer(self): + " Initialize deframer " + self.log.trace("Initializing deframer...") + self.regs.poke32(self.MGT_RECEIVER_CONTROL, 0x2) + self.regs.poke32(self.MGT_RX_DESCRAMBLER_CONTROL, 0x0) + self._gt_reset('rx', reset_only=False) + self.regs.poke32(self.MGT_RECEIVER_CONTROL, 0x0) + + def init_framer(self, bypass_scrambler = True): + " Initialize framer " + self.log.trace("Initializing framer...") + # Disable DAC Sync from requesting CGS & Stop Deframer + self.regs.poke32(self.MGT_TRANSMITTER_CONTROL, (0b1 << 13) | (0b1 << 1)) + # Reset, unreset, and check the GTs + self._gt_reset('tx', reset_only=False) + # MGT phy control... enable TX Driver Swing + self.regs.poke32(self.MGT_TX_TRANSCEIVER_CONTROL, 0xF0000) + time.sleep(0.001) + # Bypass scrambler and char replacement. If the scrambler is bypassed, + # then the char replacement is also disabled. + reg_val = {True: 0x01, False: 0x10}[bypass_scrambler] + self.regs.poke32(self.MGT_TX_SCRAMBLER_CONTROL, reg_val) + # Check for Framer in Idle state + rb = self.regs.peek32(self.MGT_TRANSMITTER_CONTROL) + if rb & 0x100 != 0x100: + raise Exception('TX Framer is not idle after reset') + # Enable incoming DAC Sync + self.regs.poke32(self.MGT_TRANSMITTER_CONTROL, 0b1 << 12) + # Enable the framer + self.regs.poke32(self.MGT_TRANSMITTER_CONTROL, 0b1 << 0) + + def get_framer_status(self): + " Return True if framer is in good status " + rb = self.regs.peek32(self.MGT_TRANSMITTER_CONTROL) + self.log.trace("FPGA Framer status: {0}".format(hex(rb & 0xFF0))) + if rb & (0b1 << 8) == 0b1 << 8: + self.log.warning("Framer warning: Framer is Idle!") + elif rb & (0b1 << 6) == 0b0 << 6: + self.log.warning("Framer warning: Code Group Sync failed to complete!") + elif rb & (0b1 << 7) == 0b0 << 7: + self.log.warning("Framer warning: Lane Alignment failed to complete!") + return rb & 0xFF0 == 0x6C0 + + def get_deframer_status(self): + " Return True if deframer is in good status " + rb = self.regs.peek32(self.MGT_RECEIVER_CONTROL) + self.log.trace("FPGA Deframer status: {0}".format(hex(rb & 0xFFFFFFFF))) + if rb & (0b1 << 2) == 0b0 << 2: + self.log.warning("Deframer warning: Code Group Sync failed to complete!") + elif rb & (0b1 << 3) == 0b0 << 3: + self.log.warning("Deframer warning: Channel Bonding failed to complete!") + elif rb & (0b1 << 21) == 0b1 << 21: + self.log.warning("Deframer warning: Misc link error!") + return rb & 0xFFFFFFFF == 0xF000001C + + def init(self): + """ + Initializes the core. Must happen after the reference clock is stable. + """ + self.log.trace("Initializing JESD204B FPGA core(s)...") + self._gt_pll_power_control(self.qplls_used, self.cplls_used) + self._gt_reset('tx', reset_only=True) + self._gt_reset('rx', reset_only=True) + self._gt_pll_lock_control(self.qplls_used, self.cplls_used, reset_only=False) + # Disable SYSREF Sampler + self.enable_lmfc(False) + + def enable_lmfc(self, enable=False): + """ + Enable/disable LMFC generator in FPGA. + """ + self.log.trace("%s FPGA SYSREF Receiver..." % {True: 'Enabling', False: 'Disabling'}[enable]) + disable_bit = 0b1 + if enable: + disable_bit = 0b0 + reg_val = ((self.lmfc_divider-1) << 23) | (disable_bit << 6) + self.log.trace("Setting SYSREF Capture reg: 0x{:08X}".format(reg_val)) + self.regs.poke32(self.SYSREF_CAPTURE_CONTROL, reg_val) + + def send_sysref_pulse(self): + """ + Toggles the LMK pin that triggers a SYSREF pulse. + Note: SYSREFs must be enabled on LMK separately beforehand. + """ + self.log.trace("Sending SYSREF pulse...") + self.regs.poke32(self.LMK_SYNC_CONTROL, 0b1 << 30) # Bit 30. Self-clears. + + def _gt_reset(self, tx_or_rx, reset_only=False): + " Put MGTs into reset. Optionally unresets and enables them " + assert tx_or_rx.lower() in ('rx', 'tx') + mgt_reg = {'tx': self.MGT_TX_RESET_CONTROL, 'rx': self.MGT_RX_RESET_CONTROL}[tx_or_rx] + self.log.trace("Resetting %s MGTs..." % tx_or_rx.upper()) + self.regs.poke32(mgt_reg, 0x10) + if not reset_only: + self.regs.poke32(mgt_reg, 0x20) + rb = -1 + for _ in range(20): + rb = self.regs.peek32(mgt_reg) + if rb & 0xFFFF0000 == 0x000F0000: + self.log.trace("%s MGT Reset Cleared!" % tx_or_rx.upper()) + return True + time.sleep(0.001) + raise Exception('Timeout in GT {trx} Reset (Readback: 0x{rb:X})'.format( + trx=tx_or_rx.upper(), + rb=(rb & 0xFFFF0000), + )) + return True + + def _gt_pll_power_control(self, qplls = 0, cplls = 0): + " Power down unused CPLLs and QPLLs " + assert qplls in range(4+1) # valid is 0 - 4 + assert cplls in range(8+1) # valid is 0 - 8 + self.log.trace("Powering up {} CPLLs and {} QPLLs".format(cplls, qplls)) + reg_val = 0xFFFF000F + reg_val_on = 0x0 + # Power down state is when the corresponding bit is set. For the PLLs we wish to + # use, clear those bits. + for x in range(qplls): + reg_val_on = reg_val_on | 0x1 << x # QPLL bits are 0-3 + for y in range(16, 16 + cplls): + reg_val_on = reg_val_on | 0x1 << y # CPLL bits are 16-23, others are reserved + reg_val = reg_val ^ reg_val_on + self.regs.poke32(self.MGT_PLL_POWER_DOWN_CONTROL, reg_val) + + def _gt_pll_lock_control(self, qplls = 0, cplls = 0, reset_only=False): + """ + Turn on the PLLs we're using, and make sure lock bits are set. + QPLL bitfield mapping: the following nibble is repeated for each QPLL. For + example, QPLL0 get bits 0-3, QPLL1 get bits 4-7, etc. + [0] = reset + [1] = locked + [2] = unlocked sticky + [3] = ref clock lost sticky + ... + [16] = sticky reset (strobe) + """ + # FutureWork: CPLLs are NOT supported yet!!! + assert cplls == 0 + assert qplls in range(4+1) # valid is 0 - 4 + + # Reset QPLLs. + reg_val = 0x1111 # by default assert all resets + self.regs.poke32(self.MGT_QPLL_CONTROL, reg_val) + self.log.trace("Resetting QPLL(s)...") + + # Unreset the PLLs in use and check for lock. + if not reset_only: + if qplls > 0: + # Unreset only the QPLLs we are using. + reg_val_on = 0x0 + for nibble in range(qplls): + reg_val_on = reg_val_on | 0x1 << nibble*4 + reg_val = reg_val ^ reg_val_on + self.regs.poke32(self.MGT_QPLL_CONTROL, reg_val) + self.log.trace("Clearing QPLL reset...") + + # Check for lock a short time later. + time.sleep(0.010) + # Clear all QPLL sticky bits + self.regs.poke32(self.MGT_QPLL_CONTROL, 0b1 << 16) + # Check for lock on active quads only. + rb = self.regs.peek32(self.MGT_QPLL_CONTROL) + rb_mask = 0x0 + locked_val = 0x0 + for nibble in range(qplls): + if (rb & (0xF << nibble*4)) != (0x2 << nibble*4): + self.log.warning("GT QPLL {} failed to lock!".format(nibble)) + locked_val = locked_val | 0x2 << nibble*4 + rb_mask = rb_mask | 0xF << nibble*4 + if (rb & rb_mask) != locked_val: + raise Exception("One or more GT QPLLs failed to lock!") + self.log.trace("QPLL(s) reporting locked!") + + def set_drp_target(self, mgt_or_qpll, dev_num): + """ + Sets up access to the specified MGT or QPLL. This must be called + prior to drp_access(). It may be called repeatedly to change DRP targets + without calling the disable function first. + """ + MAX_MGTS = 4 + MAX_QPLLs = 1 + DRP_ENABLE_VAL = 0b1 + assert mgt_or_qpll.lower() in ('mgt', 'qpll') + + self.log.trace("Enabling DRP access to %s #%d...",mgt_or_qpll.upper(), dev_num) + + # Enable access to the DRP ports and select the correct channel. Channels are + # one-hot encoded with the MGT ports in bit locations [0, (MAX_MGTS-1)] and the + # QPLL in [MAX_MGTS, MAX_MGTs+MAX_QPLLs-1]. + drp_ch_sel = {'mgt': dev_num, 'qpll': dev_num + MAX_MGTS}[mgt_or_qpll.lower()] + assert drp_ch_sel in range(MAX_MGTS + MAX_QPLLs) + reg_val = (0b1 << drp_ch_sel) | (DRP_ENABLE_VAL << 16) + self.log.trace("Writing DRP Control Register (offset 0x{:04X}) with 0x{:08X}" + .format(self.JESD_MGT_DRP_CONTROL, reg_val)) + self.regs.poke32(self.JESD_MGT_DRP_CONTROL, reg_val) + + def disable_drp_target(self): + """ + Tears down access to the DRP ports. This must be called after drp_access(). + """ + self.regs.poke32(self.JESD_MGT_DRP_CONTROL, 0x0) + self.log.trace("DRP accesses disabled!") + + def drp_access(self, rd = True, addr = 0, wr_data = 0): + """ + Provides register access to the DRP ports on the MGTs or QPLLs buried inside + the JESD204b logic. Reads will return the DRP data directly. Writes will return + zeros. + """ + # Check the DRP port is not busy. + if (self.regs.peek32(self.JESD_MGT_DRP_CONTROL) & (0b1 << 20)) != 0: + self.log.error("MGT/QPLL DRP Port is reporting busy during an attempted access.") + raise Exception("MGT/QPLL DRP Port is reporting busy during an attempted access.") + + # Access the DRP registers... + rd_data = 0x0 + core_offset = 0x2800 + (addr << 2) + if rd: + rd_data = self.regs.peek32(core_offset) + rd_data_valid = rd_data & 0xFFFF + self.log.trace("Reading DRP register 0x{:04X} at DB Core offset 0x{:04X}... " + "0x{:04X}" + .format(addr, core_offset, rd_data)) + else: + self.log.trace("Writing DRP register 0x{:04X} with 0x{:04X}...".format(addr, wr_data)) + self.regs.poke32(core_offset, wr_data) + if self.regs.peek32(core_offset) != wr_data: + self.log.error("DRP read after write failed to match!") + + return rd_data + diff --git a/mpm/python/usrp_mpm/cores/tdc_sync.py b/mpm/python/usrp_mpm/cores/tdc_sync.py index 194449b65..255810710 100644 --- a/mpm/python/usrp_mpm/cores/tdc_sync.py +++ b/mpm/python/usrp_mpm/cores/tdc_sync.py @@ -19,8 +19,8 @@ TDC clock synchronization import time import math -from builtins import object from functools import reduce +from builtins import object from usrp_mpm.mpmutils import poll_with_timeout from usrp_mpm.mpmlog import get_logger diff --git a/mpm/python/usrp_mpm/dboard_manager/__init__.py b/mpm/python/usrp_mpm/dboard_manager/__init__.py index a06591c81..96d1dfcbe 100644 --- a/mpm/python/usrp_mpm/dboard_manager/__init__.py +++ b/mpm/python/usrp_mpm/dboard_manager/__init__.py @@ -17,7 +17,6 @@ """ dboards module __init__.py """ -from .. import libpyusrp_periphs as lib from .base import DboardManagerBase from .magnesium import Magnesium from .eiscat import EISCAT diff --git a/mpm/python/usrp_mpm/dboard_manager/base.py b/mpm/python/usrp_mpm/dboard_manager/base.py index 3f5f3adb8..ba82ddb3a 100644 --- a/mpm/python/usrp_mpm/dboard_manager/base.py +++ b/mpm/python/usrp_mpm/dboard_manager/base.py @@ -20,7 +20,7 @@ dboard base implementation module from builtins import object from six import iteritems -from ..mpmlog import get_logger +from usrp_mpm.mpmlog import get_logger class DboardManagerBase(object): """ diff --git a/mpm/python/usrp_mpm/dboard_manager/eiscat.py b/mpm/python/usrp_mpm/dboard_manager/eiscat.py index 15037086f..bfd3b42a9 100644 --- a/mpm/python/usrp_mpm/dboard_manager/eiscat.py +++ b/mpm/python/usrp_mpm/dboard_manager/eiscat.py @@ -21,11 +21,11 @@ EISCAT rx board implementation module import time from builtins import range from builtins import object -from ..mpmlog import get_logger -from ..uio import UIO -from . import lib -from .base import DboardManagerBase -from .lmk_eiscat import LMK04828EISCAT +from usrp_mpm.mpmlog import get_logger +from usrp_mpm.sys_utils.uio import UIO +from usrp_mpm import lib +from usrp_mpm.dboard_manager import DboardManagerBase +from usrp_mpm.dboard_manager.lmk_eiscat import LMK04828EISCAT from usrp_mpm.cores import ClockSynchronizer def create_spidev_iface_sane(dev_node): diff --git a/mpm/python/usrp_mpm/dboard_manager/lmk_eiscat.py b/mpm/python/usrp_mpm/dboard_manager/lmk_eiscat.py index 774c10277..421bb0d47 100644 --- a/mpm/python/usrp_mpm/dboard_manager/lmk_eiscat.py +++ b/mpm/python/usrp_mpm/dboard_manager/lmk_eiscat.py @@ -19,7 +19,7 @@ LMK04828 driver for use with Magnesium """ import time -from ..chips import LMK04828 +from usrp_mpm.chips import LMK04828 class LMK04828EISCAT(LMK04828): """ diff --git a/mpm/python/usrp_mpm/dboard_manager/lmk_mg.py b/mpm/python/usrp_mpm/dboard_manager/lmk_mg.py index dc3fe203b..1e19aa2f8 100644 --- a/mpm/python/usrp_mpm/dboard_manager/lmk_mg.py +++ b/mpm/python/usrp_mpm/dboard_manager/lmk_mg.py @@ -22,8 +22,7 @@ import time import math from builtins import zip from builtins import hex -from ..mpmlog import get_logger -from ..chips import LMK04828 +from usrp_mpm.chips import LMK04828 class LMK04828Mg(LMK04828): def __init__(self, regs_iface, spi_lock, ref_clock_freq, master_clock_freq, log=None): diff --git a/mpm/python/usrp_mpm/dboard_manager/magnesium.py b/mpm/python/usrp_mpm/dboard_manager/magnesium.py index 9cbb2bd9b..e43f324af 100644 --- a/mpm/python/usrp_mpm/dboard_manager/magnesium.py +++ b/mpm/python/usrp_mpm/dboard_manager/magnesium.py @@ -24,15 +24,15 @@ import time import threading import math from six import iterkeys, iteritems -from . import lib # Pulls in everything from C++-land -from .base import DboardManagerBase -from .. import nijesdcore -from ..uio import UIO -from ..mpmlog import get_logger -from .lmk_mg import LMK04828Mg -from usrp_mpm.periph_manager.udev import get_eeprom_paths +from usrp_mpm import lib # Pulls in everything from C++-land +from usrp_mpm.dboard_manager import DboardManagerBase +from usrp_mpm.dboard_manager.lmk_mg import LMK04828Mg +from usrp_mpm.cores import nijesdcore +from usrp_mpm.mpmlog import get_logger +from usrp_mpm.sys_utils.uio import UIO +from usrp_mpm.sys_utils.udev import get_eeprom_paths +from usrp_mpm.sys_utils.sysfs_gpio import SysFSGPIO from usrp_mpm.cores import ClockSynchronizer -from ..sysfs_gpio import SysFSGPIO from usrp_mpm.bfrfs import BufferFS from usrp_mpm.mpmutils import poll_with_timeout diff --git a/mpm/python/usrp_mpm/dboard_manager/test.py b/mpm/python/usrp_mpm/dboard_manager/test.py index 3dfeab167..eedafe4fd 100644 --- a/mpm/python/usrp_mpm/dboard_manager/test.py +++ b/mpm/python/usrp_mpm/dboard_manager/test.py @@ -19,9 +19,7 @@ magnesium dboard implementation module """ from builtins import object -from . import lib -from .base import DboardManagerBase -from logging import getLogger +from usrp_mpm.dboard_manager import DboardManagerBase class fake_spi(object): def __init__(self, addr): diff --git a/mpm/python/usrp_mpm/dboard_manager/unknown.py b/mpm/python/usrp_mpm/dboard_manager/unknown.py index d954154f9..94e1344cc 100644 --- a/mpm/python/usrp_mpm/dboard_manager/unknown.py +++ b/mpm/python/usrp_mpm/dboard_manager/unknown.py @@ -17,8 +17,7 @@ """ EISCAT rx board implementation module """ -from .base import DboardManagerBase -from logging import getLogger +from usrp_mpm.dboard_manager import DboardManagerBase class unknown(DboardManagerBase): hw_pid = 0 diff --git a/mpm/python/usrp_mpm/discovery.py b/mpm/python/usrp_mpm/discovery.py index 47088c7e7..2e97f3535 100644 --- a/mpm/python/usrp_mpm/discovery.py +++ b/mpm/python/usrp_mpm/discovery.py @@ -24,7 +24,7 @@ import socket from builtins import bytes from six import iteritems from usrp_mpm.mpmtypes import MPM_DISCOVERY_PORT -from .mpmlog import get_main_logger +from usrp_mpm.mpmlog import get_main_logger RESPONSE_PREAMBLE = "USRP-MPM" RESPONSE_SEP = ";" diff --git a/mpm/python/usrp_mpm/dtoverlay.py b/mpm/python/usrp_mpm/dtoverlay.py deleted file mode 100644 index 7f1bf653f..000000000 --- a/mpm/python/usrp_mpm/dtoverlay.py +++ /dev/null @@ -1,129 +0,0 @@ -# -# Copyright 2017 Ettus Research (National Instruments) -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# -""" -Manipulation of device tree overlays (Linux kernel) -""" - -import os -from .mpmlog import get_logger - -SYSFS_OVERLAY_BASE_DIR = '/sys/kernel/config/device-tree/overlays' -OVERLAY_DEFAULT_PATH = '/lib/firmware' - -def get_overlay_attrs(overlay_name): - """ - List those attributes that are connected to an overlay entry in a sysfs - node. - """ - overlay_path = os.path.join(SYSFS_OVERLAY_BASE_DIR, overlay_name) - attrs = {} - for attr_name in os.listdir(overlay_path): - try: - attr_val = open( - os.path.join(overlay_path, attr_name), 'r' - ).read().strip() - except OSError: - pass - if len(attr_val): - attrs[attr_name] = attr_val - return attrs - -def is_applied(overlay_name): - """ - Returns True if the overlay is already applied, False if not. - """ - try: - return open( - os.path.join(SYSFS_OVERLAY_BASE_DIR, overlay_name, 'status') - ).read().strip() == 'applied' - except IOError: - return False - -def list_overlays(applied_only=False): - """ - List all registered kernel modules. Returns a dict of dicts: - { - '': { - # attributes - }, - } - - All the attributes come from sysfs. See get_overlay_attrs(). - - applied_only -- Only return those overlays that are already applied. - """ - return { - overlay_name: get_overlay_attrs(overlay_name) - for overlay_name in os.listdir(SYSFS_OVERLAY_BASE_DIR) - if not applied_only \ - or get_overlay_attrs(overlay_name).get('status') == 'applied' - } - -def list_available_overlays(path): - """ - List available overlay files (dtbo) - """ - path = path or OVERLAY_DEFAULT_PATH - return [x.strip()[:-5] for x in os.listdir(path) if x.endswith('.dtbo')] - -def apply_overlay(overlay_name): - """ - Applies the given overlay. Does not check if the overlay is loaded. - """ - get_logger("DTO").trace("Applying overlay `{}'...".format(overlay_name)) - overlay_path = os.path.join(SYSFS_OVERLAY_BASE_DIR, overlay_name) - if not os.path.exists(overlay_path): - os.mkdir(overlay_path) - open( - os.path.join(SYSFS_OVERLAY_BASE_DIR, overlay_name, 'path'), 'w' - ).write("{}.dtbo".format(overlay_name)) - -def apply_overlay_safe(overlay_name): - """ - Only apply an overlay if it's not yet applied. - - Finally, checks that the overlay was applied and throws an exception if not. - """ - if is_applied(overlay_name): - get_logger("DTO").debug( - "Overlay `{}' was already applied, not applying again.".format( - overlay_name - ) - ) - else: - apply_overlay(overlay_name) - if not is_applied(overlay_name): - raise RuntimeError("Failed to apply overlay `{}'".format(overlay_name)) - -def rm_overlay(overlay_name): - """ - Removes the given overlay. Does not check if the overlay is loaded. - """ - get_logger("DTO").trace("Removing overlay `{}'...".format(overlay_name)) - os.rmdir(os.path.join(SYSFS_OVERLAY_BASE_DIR, overlay_name)) - -def rm_overlay_safe(overlay_name): - """ - Only remove an overlay if it's already applied. - """ - if overlay_name in list(list_overlays(applied_only=True).keys()): - rm_overlay(overlay_name) - else: - get_logger("DTO").debug( - "Overlay `{}' was not loaded, not removing.".format(overlay_name) - ) - diff --git a/mpm/python/usrp_mpm/ethtable.py b/mpm/python/usrp_mpm/ethtable.py index f0c622492..9657f54c7 100644 --- a/mpm/python/usrp_mpm/ethtable.py +++ b/mpm/python/usrp_mpm/ethtable.py @@ -21,9 +21,9 @@ Ethernet dispatcher table control from builtins import str from builtins import object import netaddr -from .mpmlog import get_logger -from .uio import UIO -from .net import get_mac_addr +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): """ diff --git a/mpm/python/usrp_mpm/liberiotable.py b/mpm/python/usrp_mpm/liberiotable.py index 6f2454379..be19c7c17 100644 --- a/mpm/python/usrp_mpm/liberiotable.py +++ b/mpm/python/usrp_mpm/liberiotable.py @@ -9,8 +9,8 @@ Liberio DMA dispatcher table control from builtins import str from builtins import object -from .mpmlog import get_logger -from .uio import UIO +from usrp_mpm.mpmlog import get_logger +from usrp_mpm.sys_utils.uio import UIO class LiberioDispatcherTable(object): """ diff --git a/mpm/python/usrp_mpm/net.py b/mpm/python/usrp_mpm/net.py deleted file mode 100644 index be2d3f754..000000000 --- a/mpm/python/usrp_mpm/net.py +++ /dev/null @@ -1,130 +0,0 @@ - -# Copyright 2017 Ettus Research (National Instruments) -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# -""" -N310 implementation module -""" -import itertools -import socket -from six import iteritems -from pyroute2 import IPRoute -from .mpmlog import get_logger - - -def get_valid_interfaces(iface_list): - """ - Given a list of interfaces (['eth1', 'eth2'] for example), return the - subset that contains actually valid entries. - Interfaces are checked for if they actually exist, and if so, if they're up. - """ - valid_ifaces = [] - with IPRoute() as ipr: - for iface in iface_list: - valid_iface_idx = ipr.link_lookup(ifname=iface) - if len(valid_iface_idx) == 0: - 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' \ - and len(get_iface_addrs(link_info.get_attr('IFLA_ADDRESS'))): - assert link_info.get_attr('IFLA_IFNAME') == iface - valid_ifaces.append(iface) - return valid_ifaces - - -def get_iface_info(ifname): - """ - Given an interface name (e.g. 'eth1'), return a dictionary with the - following keys: - - ip_addr: Main IPv4 address - - ip_addrs: List of valid IPv4 addresses - - mac_addr: MAC address - - All values are stored as strings. - """ - try: - with IPRoute() as ipr: - links = ipr.link_lookup(ifname=ifname) - if len(links) == 0: - raise LookupError("No interfaces known with name `{}'!" - .format(ifname)) - link_info = ipr.get_links(links)[0] - except IndexError: - raise LookupError("Could not get links for interface `{}'" - .format(ifname)) - mac_addr = link_info.get_attr('IFLA_ADDRESS') - ip_addrs = get_iface_addrs(mac_addr) - return { - 'mac_addr': mac_addr, - 'ip_addr': ip_addrs[0], - 'ip_addrs': ip_addrs, - } - -def ip_addr_to_iface(ip_addr, iface_list): - """ - Return an Ethernet interface (e.g. 'eth1') given an IP address. - - Arguments: - ip_addr -- The IP address as a string - iface_list -- A map "interface name" -> iface_info, where iface_info - is another map as returned by net.get_iface_info(). - """ - # Flip around the iface_info map and then use it to look up by IP addr - return { - iface_info['ip_addr']: iface_name - for iface_name, iface_info in iteritems(iface_list) - }[ip_addr] - - -def get_iface_addrs(mac_addr): - """ - return ipv4 addresses for a given macaddress - input format: "aa:bb:cc:dd:ee:ff" - """ - with IPRoute() as ip2: - # returns index - [link] = ip2.link_lookup(address=mac_addr) - # Only get v4 addresses - addresses = [addr.get_attrs('IFA_ADDRESS') - for addr in ip2.get_addr(family=socket.AF_INET) - if addr.get('index', None) == link] - # flatten possibly nested list - addresses = list(itertools.chain.from_iterable(addresses)) - - return addresses - - -def byte_to_mac(byte_str): - """ - converts a bytestring into nice hex representation - """ - return ':'.join(["%02x" % ord(x) for x in byte_str]) - - -def get_mac_addr(remote_addr): - """ - return MAC address of a remote host already discovered - or None if no host entry was found - """ - with IPRoute() as ip2: - addrs = ip2.get_neighbours(dst=remote_addr) - if len(addrs) > 1: - get_logger('get_mac_addr').warning("More than one device with the " - "same IP address found. " - "Picking entry at random") - if not addrs: - return None - return addrs[0].get_attr('NDA_LLADDR') diff --git a/mpm/python/usrp_mpm/nijesdcore.py b/mpm/python/usrp_mpm/nijesdcore.py deleted file mode 100644 index f46809d94..000000000 --- a/mpm/python/usrp_mpm/nijesdcore.py +++ /dev/null @@ -1,325 +0,0 @@ -# -# Copyright 2017 Ettus Research (National Instruments) -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# -""" -JESD FPGA Core Interface -""" - -import time -from builtins import hex -from builtins import object -from .mpmlog import get_logger - -class NIMgJESDCore(object): - """ - Provide interface for the FPGA JESD Core. - Works with Magnesium/Mykonos daughterboards only. - - Arguments: - regs -- regs class to use for peek/poke - """ - - DB_ID = 0x0630 - MGT_QPLL_CONTROL = 0x2000 - MGT_PLL_POWER_DOWN_CONTROL = 0x200C - MGT_TX_RESET_CONTROL = 0x2020 - MGT_RX_RESET_CONTROL = 0x2024 - MGT_RECEIVER_CONTROL = 0x2040 - MGT_RX_DESCRAMBLER_CONTROL = 0x2050 - MGT_TRANSMITTER_CONTROL = 0x2060 - MGT_TX_TRANSCEIVER_CONTROL = 0x2064 - MGT_TX_SCRAMBLER_CONTROL = 0x2068 - LMK_SYNC_CONTROL = 0x206C - JESD_MGT_DRP_CONTROL = 0x2070 - SYSREF_CAPTURE_CONTROL = 0x2078 - JESD_SIGNATURE_REG = 0x2100 - JESD_REVISION_REG = 0x2104 - - - def __init__(self, regs, slot_idx=0): - self.regs = regs - self.log = get_logger("NIMgJESDCore-{}".format(slot_idx)) - assert hasattr(self.regs, 'peek32') - assert hasattr(self.regs, 'poke32') - # FutureWork: The following are constants for the Magnesium board. These need - # to change to variables to support other interfaces. - self.qplls_used = 1 - self.cplls_used = 0 - # Number of FPGA clock cycles per LMFC period. - self.lmfc_divider = 20 - - def unreset_qpll(self): - # new_val = self.regs.peek32(0x0) & ~0x8 - # self.log.trace("Unresetting MMCM, writing value {:X}".format(new_val)) - self.regs.poke32(0x0, 0x7) - - def check_core(self): - """ - Verify JESD core returns correct ID - """ - self.log.trace("Checking JESD Core...") - if self.regs.peek32(self.JESD_SIGNATURE_REG) != 0x4A455344: - raise Exception('JESD Core signature mismatch! Check that core is mapped correctly') - #if self.regs.peek32(JESD_REVISION_REG) != 0xFF - #error here for date revision mismatch - self.log.trace("JESD Core build code: {0}".format(hex(self.regs.peek32(self.JESD_REVISION_REG)))) - self.log.trace("DB Slot #: {}".format( (self.regs.peek32(self.DB_ID) & 0x10000) >> 16 )) - self.log.trace("DB PID: {:X}" .format( self.regs.peek32(self.DB_ID) & 0xFFFF )) - return True - - def reset(self): - """ - Reset to the core. Places the PLLs, TX MGTs and RX MGTs (along with the glue - logic) in reset. Also disables the SYSREF sampler. - """ - self.log.trace("Resetting the JESD204B FPGA core(s)...") - self._gt_reset('tx', reset_only=True) - self._gt_reset('rx', reset_only=True) - self._gt_pll_lock_control(self.qplls_used, self.cplls_used, reset_only=True) - # Disable SYSREF Sampler - self.enable_lmfc(False) - - def init_deframer(self): - " Initialize deframer " - self.log.trace("Initializing deframer...") - self.regs.poke32(self.MGT_RECEIVER_CONTROL, 0x2) - self.regs.poke32(self.MGT_RX_DESCRAMBLER_CONTROL, 0x0) - self._gt_reset('rx', reset_only=False) - self.regs.poke32(self.MGT_RECEIVER_CONTROL, 0x0) - - def init_framer(self, bypass_scrambler = True): - " Initialize framer " - self.log.trace("Initializing framer...") - # Disable DAC Sync from requesting CGS & Stop Deframer - self.regs.poke32(self.MGT_TRANSMITTER_CONTROL, (0b1 << 13) | (0b1 << 1)) - # Reset, unreset, and check the GTs - self._gt_reset('tx', reset_only=False) - # MGT phy control... enable TX Driver Swing - self.regs.poke32(self.MGT_TX_TRANSCEIVER_CONTROL, 0xF0000) - time.sleep(0.001) - # Bypass scrambler and char replacement. If the scrambler is bypassed, - # then the char replacement is also disabled. - reg_val = {True: 0x01, False: 0x10}[bypass_scrambler] - self.regs.poke32(self.MGT_TX_SCRAMBLER_CONTROL, reg_val) - # Check for Framer in Idle state - rb = self.regs.peek32(self.MGT_TRANSMITTER_CONTROL) - if rb & 0x100 != 0x100: - raise Exception('TX Framer is not idle after reset') - # Enable incoming DAC Sync - self.regs.poke32(self.MGT_TRANSMITTER_CONTROL, 0b1 << 12) - # Enable the framer - self.regs.poke32(self.MGT_TRANSMITTER_CONTROL, 0b1 << 0) - - def get_framer_status(self): - " Return True if framer is in good status " - rb = self.regs.peek32(self.MGT_TRANSMITTER_CONTROL) - self.log.trace("FPGA Framer status: {0}".format(hex(rb & 0xFF0))) - if rb & (0b1 << 8) == 0b1 << 8: - self.log.warning("Framer warning: Framer is Idle!") - elif rb & (0b1 << 6) == 0b0 << 6: - self.log.warning("Framer warning: Code Group Sync failed to complete!") - elif rb & (0b1 << 7) == 0b0 << 7: - self.log.warning("Framer warning: Lane Alignment failed to complete!") - return rb & 0xFF0 == 0x6C0 - - def get_deframer_status(self): - " Return True if deframer is in good status " - rb = self.regs.peek32(self.MGT_RECEIVER_CONTROL) - self.log.trace("FPGA Deframer status: {0}".format(hex(rb & 0xFFFFFFFF))) - if rb & (0b1 << 2) == 0b0 << 2: - self.log.warning("Deframer warning: Code Group Sync failed to complete!") - elif rb & (0b1 << 3) == 0b0 << 3: - self.log.warning("Deframer warning: Channel Bonding failed to complete!") - elif rb & (0b1 << 21) == 0b1 << 21: - self.log.warning("Deframer warning: Misc link error!") - return rb & 0xFFFFFFFF == 0xF000001C - - def init(self): - """ - Initializes the core. Must happen after the reference clock is stable. - """ - self.log.trace("Initializing JESD204B FPGA core(s)...") - self._gt_pll_power_control(self.qplls_used, self.cplls_used) - self._gt_reset('tx', reset_only=True) - self._gt_reset('rx', reset_only=True) - self._gt_pll_lock_control(self.qplls_used, self.cplls_used, reset_only=False) - # Disable SYSREF Sampler - self.enable_lmfc(False) - - def enable_lmfc(self, enable=False): - """ - Enable/disable LMFC generator in FPGA. - """ - self.log.trace("%s FPGA SYSREF Receiver..." % {True: 'Enabling', False: 'Disabling'}[enable]) - disable_bit = 0b1 - if enable: - disable_bit = 0b0 - reg_val = ((self.lmfc_divider-1) << 23) | (disable_bit << 6) - self.log.trace("Setting SYSREF Capture reg: 0x{:08X}".format(reg_val)) - self.regs.poke32(self.SYSREF_CAPTURE_CONTROL, reg_val) - - def send_sysref_pulse(self): - """ - Toggles the LMK pin that triggers a SYSREF pulse. - Note: SYSREFs must be enabled on LMK separately beforehand. - """ - self.log.trace("Sending SYSREF pulse...") - self.regs.poke32(self.LMK_SYNC_CONTROL, 0b1 << 30) # Bit 30. Self-clears. - - def _gt_reset(self, tx_or_rx, reset_only=False): - " Put MGTs into reset. Optionally unresets and enables them " - assert tx_or_rx.lower() in ('rx', 'tx') - mgt_reg = {'tx': self.MGT_TX_RESET_CONTROL, 'rx': self.MGT_RX_RESET_CONTROL}[tx_or_rx] - self.log.trace("Resetting %s MGTs..." % tx_or_rx.upper()) - self.regs.poke32(mgt_reg, 0x10) - if not reset_only: - self.regs.poke32(mgt_reg, 0x20) - rb = -1 - for _ in range(20): - rb = self.regs.peek32(mgt_reg) - if rb & 0xFFFF0000 == 0x000F0000: - self.log.trace("%s MGT Reset Cleared!" % tx_or_rx.upper()) - return True - time.sleep(0.001) - raise Exception('Timeout in GT {trx} Reset (Readback: 0x{rb:X})'.format( - trx=tx_or_rx.upper(), - rb=(rb & 0xFFFF0000), - )) - return True - - def _gt_pll_power_control(self, qplls = 0, cplls = 0): - " Power down unused CPLLs and QPLLs " - assert qplls in range(4+1) # valid is 0 - 4 - assert cplls in range(8+1) # valid is 0 - 8 - self.log.trace("Powering up {} CPLLs and {} QPLLs".format(cplls, qplls)) - reg_val = 0xFFFF000F - reg_val_on = 0x0 - # Power down state is when the corresponding bit is set. For the PLLs we wish to - # use, clear those bits. - for x in range(qplls): - reg_val_on = reg_val_on | 0x1 << x # QPLL bits are 0-3 - for y in range(16, 16 + cplls): - reg_val_on = reg_val_on | 0x1 << y # CPLL bits are 16-23, others are reserved - reg_val = reg_val ^ reg_val_on - self.regs.poke32(self.MGT_PLL_POWER_DOWN_CONTROL, reg_val) - - def _gt_pll_lock_control(self, qplls = 0, cplls = 0, reset_only=False): - """ - Turn on the PLLs we're using, and make sure lock bits are set. - QPLL bitfield mapping: the following nibble is repeated for each QPLL. For - example, QPLL0 get bits 0-3, QPLL1 get bits 4-7, etc. - [0] = reset - [1] = locked - [2] = unlocked sticky - [3] = ref clock lost sticky - ... - [16] = sticky reset (strobe) - """ - # FutureWork: CPLLs are NOT supported yet!!! - assert cplls == 0 - assert qplls in range(4+1) # valid is 0 - 4 - - # Reset QPLLs. - reg_val = 0x1111 # by default assert all resets - self.regs.poke32(self.MGT_QPLL_CONTROL, reg_val) - self.log.trace("Resetting QPLL(s)...") - - # Unreset the PLLs in use and check for lock. - if not reset_only: - if qplls > 0: - # Unreset only the QPLLs we are using. - reg_val_on = 0x0 - for nibble in range(qplls): - reg_val_on = reg_val_on | 0x1 << nibble*4 - reg_val = reg_val ^ reg_val_on - self.regs.poke32(self.MGT_QPLL_CONTROL, reg_val) - self.log.trace("Clearing QPLL reset...") - - # Check for lock a short time later. - time.sleep(0.010) - # Clear all QPLL sticky bits - self.regs.poke32(self.MGT_QPLL_CONTROL, 0b1 << 16) - # Check for lock on active quads only. - rb = self.regs.peek32(self.MGT_QPLL_CONTROL) - rb_mask = 0x0 - locked_val = 0x0 - for nibble in range(qplls): - if (rb & (0xF << nibble*4)) != (0x2 << nibble*4): - self.log.warning("GT QPLL {} failed to lock!".format(nibble)) - locked_val = locked_val | 0x2 << nibble*4 - rb_mask = rb_mask | 0xF << nibble*4 - if (rb & rb_mask) != locked_val: - raise Exception("One or more GT QPLLs failed to lock!") - self.log.trace("QPLL(s) reporting locked!") - - def set_drp_target(self, mgt_or_qpll, dev_num): - """ - Sets up access to the specified MGT or QPLL. This must be called - prior to drp_access(). It may be called repeatedly to change DRP targets - without calling the disable function first. - """ - MAX_MGTS = 4 - MAX_QPLLs = 1 - DRP_ENABLE_VAL = 0b1 - assert mgt_or_qpll.lower() in ('mgt', 'qpll') - - self.log.trace("Enabling DRP access to %s #%d...",mgt_or_qpll.upper(), dev_num) - - # Enable access to the DRP ports and select the correct channel. Channels are - # one-hot encoded with the MGT ports in bit locations [0, (MAX_MGTS-1)] and the - # QPLL in [MAX_MGTS, MAX_MGTs+MAX_QPLLs-1]. - drp_ch_sel = {'mgt': dev_num, 'qpll': dev_num + MAX_MGTS}[mgt_or_qpll.lower()] - assert drp_ch_sel in range(MAX_MGTS + MAX_QPLLs) - reg_val = (0b1 << drp_ch_sel) | (DRP_ENABLE_VAL << 16) - self.log.trace("Writing DRP Control Register (offset 0x{:04X}) with 0x{:08X}" - .format(self.JESD_MGT_DRP_CONTROL, reg_val)) - self.regs.poke32(self.JESD_MGT_DRP_CONTROL, reg_val) - - def disable_drp_target(self): - """ - Tears down access to the DRP ports. This must be called after drp_access(). - """ - self.regs.poke32(self.JESD_MGT_DRP_CONTROL, 0x0) - self.log.trace("DRP accesses disabled!") - - def drp_access(self, rd = True, addr = 0, wr_data = 0): - """ - Provides register access to the DRP ports on the MGTs or QPLLs buried inside - the JESD204b logic. Reads will return the DRP data directly. Writes will return - zeros. - """ - # Check the DRP port is not busy. - if (self.regs.peek32(self.JESD_MGT_DRP_CONTROL) & (0b1 << 20)) != 0: - self.log.error("MGT/QPLL DRP Port is reporting busy during an attempted access.") - raise Exception("MGT/QPLL DRP Port is reporting busy during an attempted access.") - - # Access the DRP registers... - rd_data = 0x0 - core_offset = 0x2800 + (addr << 2) - if rd: - rd_data = self.regs.peek32(core_offset) - rd_data_valid = rd_data & 0xFFFF - self.log.trace("Reading DRP register 0x{:04X} at DB Core offset 0x{:04X}... " - "0x{:04X}" - .format(addr, core_offset, rd_data)) - else: - self.log.trace("Writing DRP register 0x{:04X} with 0x{:04X}...".format(addr, wr_data)) - self.regs.poke32(core_offset, wr_data) - if self.regs.peek32(core_offset) != wr_data: - self.log.error("DRP read after write failed to match!") - - return rd_data - diff --git a/mpm/python/usrp_mpm/periph_manager/CMakeLists.txt b/mpm/python/usrp_mpm/periph_manager/CMakeLists.txt index 49ca8e411..51bbf457c 100644 --- a/mpm/python/usrp_mpm/periph_manager/CMakeLists.txt +++ b/mpm/python/usrp_mpm/periph_manager/CMakeLists.txt @@ -24,7 +24,6 @@ SET(USRP_MPM_PERIPHMGR_FILES ${CMAKE_CURRENT_SOURCE_DIR}/base.py ${CMAKE_CURRENT_SOURCE_DIR}/n310.py ${CMAKE_CURRENT_SOURCE_DIR}/test.py - ${CMAKE_CURRENT_SOURCE_DIR}/udev.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/__init__.py.in b/mpm/python/usrp_mpm/periph_manager/__init__.py.in index 34d29137a..5d99b0b5e 100644 --- a/mpm/python/usrp_mpm/periph_manager/__init__.py.in +++ b/mpm/python/usrp_mpm/periph_manager/__init__.py.in @@ -18,9 +18,7 @@ periph_manager __init__.py """ -# This is where the import magic happens -from .. import libpyusrp_periphs as lib -from .. import dboard_manager -from .. import mpmtypes +from .base import PeriphManagerBase +# This is where the import magic happens from .${MPM_DEVICE} import ${MPM_DEVICE} as periph_manager diff --git a/mpm/python/usrp_mpm/periph_manager/base.py b/mpm/python/usrp_mpm/periph_manager/base.py index ff86137df..c73ba6ef4 100644 --- a/mpm/python/usrp_mpm/periph_manager/base.py +++ b/mpm/python/usrp_mpm/periph_manager/base.py @@ -20,16 +20,16 @@ Mboard implementation base class from __future__ import print_function import os -from concurrent import futures from hashlib import md5 +from concurrent import futures from builtins import str from builtins import range from builtins import object from six import iteritems, itervalues -from ..mpmlog import get_logger -from .udev import get_eeprom_paths -from .udev import get_spidev_nodes -from usrp_mpm import dtoverlay +from usrp_mpm.mpmlog import get_logger +from usrp_mpm.sys_utils.udev import get_eeprom_paths +from usrp_mpm.sys_utils.udev import get_spidev_nodes +from usrp_mpm.sys_utils import dtoverlay from usrp_mpm import eeprom from usrp_mpm.rpc_server import no_claim, no_rpc @@ -37,7 +37,7 @@ def get_dboard_class_from_pid(pid): """ Given a PID, return a dboard class initializer callable. """ - from .. import dboard_manager + from usrp_mpm import dboard_manager for member in itervalues(dboard_manager.__dict__): try: if issubclass(member, dboard_manager.DboardManagerBase) and \ diff --git a/mpm/python/usrp_mpm/periph_manager/n310.py b/mpm/python/usrp_mpm/periph_manager/n310.py index 384c69e97..ed1b5d164 100644 --- a/mpm/python/usrp_mpm/periph_manager/n310.py +++ b/mpm/python/usrp_mpm/periph_manager/n310.py @@ -25,17 +25,13 @@ import shutil import subprocess from six import iteritems, itervalues from builtins import object -from .base import PeriphManagerBase -from ..net import get_iface_addrs -from ..net import byte_to_mac -from ..mpmtypes import SID +from usrp_mpm.periph_manager import PeriphManagerBase +from usrp_mpm.mpmtypes import SID from usrp_mpm.rpc_server import no_rpc -from usrp_mpm import net -from usrp_mpm import dtoverlay +from usrp_mpm.sys_utils import dtoverlay +from usrp_mpm.sys_utils.sysfs_gpio import SysFSGPIO +from usrp_mpm.sys_utils.uio import UIO from usrp_mpm.xports import XportMgrUDP, XportMgrLiberio -from ..sysfs_gpio import SysFSGPIO -from .. import libpyusrp_periphs as lib -from ..uio import UIO N3XX_DEFAULT_EXT_CLOCK_FREQ = 10e6 N3XX_DEFAULT_CLOCK_SOURCE = 'external' diff --git a/mpm/python/usrp_mpm/periph_manager/test.py b/mpm/python/usrp_mpm/periph_manager/test.py index 03bd28956..dd99b56b2 100644 --- a/mpm/python/usrp_mpm/periph_manager/test.py +++ b/mpm/python/usrp_mpm/periph_manager/test.py @@ -19,10 +19,10 @@ test periph_manager implementation module """ from __future__ import print_function -from .base import PeriphManagerBase -from . import dboard_manager import random import string +from usrp_mpm.periph_manager import PeriphManagerBase +from usrp_mpm import dboard_manager class test(PeriphManagerBase): diff --git a/mpm/python/usrp_mpm/periph_manager/udev.py b/mpm/python/usrp_mpm/periph_manager/udev.py deleted file mode 100644 index 33cb3367a..000000000 --- a/mpm/python/usrp_mpm/periph_manager/udev.py +++ /dev/null @@ -1,52 +0,0 @@ -# -# Copyright 2017 Ettus Research (National Instruments) -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# - -import os -import pyudev -from ..mpmlog import get_logger - -def get_eeprom_paths(address): - """ - Return list of EEPROM device paths for a given I2C address. - If no device paths are found, an empty list is returned. - """ - context = pyudev.Context() - parent = pyudev.Device.from_name(context, "platform", address) - paths = [d.device_node if d.device_node is not None else d.sys_path - for d in context.list_devices(parent=parent, subsystem="nvmem")] - if len(paths) == 0: - return [] - # We need to sort this so 9-0050 comes before 10-0050 (etc.) - maxlen = max((len(os.path.split(p)[1]) for p in paths)) - paths = sorted( - paths, - key=lambda x: "{:>0{maxlen}}".format(os.path.split(x)[1], maxlen=maxlen) - ) - return [os.path.join(x, 'nvmem') for x in paths] - -def get_spidev_nodes(spi_master): - """ - Return list of spidev device paths for a given SPI master. If no valid paths - can be found, an empty list is returned. - """ - context = pyudev.Context() - parent = pyudev.Device.from_name(context, "platform", spi_master) - return [ - device.device_node - for device in context.list_devices(parent=parent, subsystem="spidev") - ] - diff --git a/mpm/python/usrp_mpm/rpc_server.py b/mpm/python/usrp_mpm/rpc_server.py index 086ba5812..d645cb396 100644 --- a/mpm/python/usrp_mpm/rpc_server.py +++ b/mpm/python/usrp_mpm/rpc_server.py @@ -33,7 +33,7 @@ monkey.patch_all() from builtins import str, bytes from builtins import range from mprpc import RPCServer -from .mpmlog import get_main_logger +from usrp_mpm.mpmlog import get_main_logger TIMEOUT_INTERVAL = 3.0 # Seconds before claim expires TOKEN_LEN = 16 # Length of the token string diff --git a/mpm/python/usrp_mpm/sys_utils/CMakeLists.txt b/mpm/python/usrp_mpm/sys_utils/CMakeLists.txt new file mode 100644 index 000000000..1b5a4ed0e --- /dev/null +++ b/mpm/python/usrp_mpm/sys_utils/CMakeLists.txt @@ -0,0 +1,18 @@ +# +# Copyright 2017 Ettus Research, National Instruments Company +# +# SPDX-License-Identifier: GPL-3.0 +# + +SET(USRP_MPM_FILES ${USRP_MPM_FILES}) +SET(USRP_MPM_SYSUTILS_FILES + ${CMAKE_CURRENT_SOURCE_DIR}/__init__.py + ${CMAKE_CURRENT_SOURCE_DIR}/dtoverlay.py + ${CMAKE_CURRENT_SOURCE_DIR}/net.py + ${CMAKE_CURRENT_SOURCE_DIR}/sysfs_gpio.py + ${CMAKE_CURRENT_SOURCE_DIR}/udev.py + ${CMAKE_CURRENT_SOURCE_DIR}/uio.py +) +LIST(APPEND USRP_MPM_FILES ${USRP_MPM_SYSUTILS_FILES}) +SET(USRP_MPM_FILES ${USRP_MPM_FILES} PARENT_SCOPE) + diff --git a/mpm/python/usrp_mpm/sys_utils/__init__.py b/mpm/python/usrp_mpm/sys_utils/__init__.py new file mode 100644 index 000000000..61be8ac53 --- /dev/null +++ b/mpm/python/usrp_mpm/sys_utils/__init__.py @@ -0,0 +1,11 @@ +# +# Copyright 2017 Ettus Research, National Instruments Company +# +# SPDX-License-Identifier: GPL-3.0 +# +""" +System/OS Utilities + +These are convenience functions to access Linux subsystems. +""" + diff --git a/mpm/python/usrp_mpm/sys_utils/dtoverlay.py b/mpm/python/usrp_mpm/sys_utils/dtoverlay.py new file mode 100644 index 000000000..577bafed9 --- /dev/null +++ b/mpm/python/usrp_mpm/sys_utils/dtoverlay.py @@ -0,0 +1,129 @@ +# +# Copyright 2017 Ettus Research (National Instruments) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +""" +Manipulation of device tree overlays (Linux kernel) +""" + +import os +from usrp_mpm.mpmlog import get_logger + +SYSFS_OVERLAY_BASE_DIR = '/sys/kernel/config/device-tree/overlays' +OVERLAY_DEFAULT_PATH = '/lib/firmware' + +def get_overlay_attrs(overlay_name): + """ + List those attributes that are connected to an overlay entry in a sysfs + node. + """ + overlay_path = os.path.join(SYSFS_OVERLAY_BASE_DIR, overlay_name) + attrs = {} + for attr_name in os.listdir(overlay_path): + try: + attr_val = open( + os.path.join(overlay_path, attr_name), 'r' + ).read().strip() + except OSError: + pass + if len(attr_val): + attrs[attr_name] = attr_val + return attrs + +def is_applied(overlay_name): + """ + Returns True if the overlay is already applied, False if not. + """ + try: + return open( + os.path.join(SYSFS_OVERLAY_BASE_DIR, overlay_name, 'status') + ).read().strip() == 'applied' + except IOError: + return False + +def list_overlays(applied_only=False): + """ + List all registered kernel modules. Returns a dict of dicts: + { + '': { + # attributes + }, + } + + All the attributes come from sysfs. See get_overlay_attrs(). + + applied_only -- Only return those overlays that are already applied. + """ + return { + overlay_name: get_overlay_attrs(overlay_name) + for overlay_name in os.listdir(SYSFS_OVERLAY_BASE_DIR) + if not applied_only \ + or get_overlay_attrs(overlay_name).get('status') == 'applied' + } + +def list_available_overlays(path): + """ + List available overlay files (dtbo) + """ + path = path or OVERLAY_DEFAULT_PATH + return [x.strip()[:-5] for x in os.listdir(path) if x.endswith('.dtbo')] + +def apply_overlay(overlay_name): + """ + Applies the given overlay. Does not check if the overlay is loaded. + """ + get_logger("DTO").trace("Applying overlay `{}'...".format(overlay_name)) + overlay_path = os.path.join(SYSFS_OVERLAY_BASE_DIR, overlay_name) + if not os.path.exists(overlay_path): + os.mkdir(overlay_path) + open( + os.path.join(SYSFS_OVERLAY_BASE_DIR, overlay_name, 'path'), 'w' + ).write("{}.dtbo".format(overlay_name)) + +def apply_overlay_safe(overlay_name): + """ + Only apply an overlay if it's not yet applied. + + Finally, checks that the overlay was applied and throws an exception if not. + """ + if is_applied(overlay_name): + get_logger("DTO").debug( + "Overlay `{}' was already applied, not applying again.".format( + overlay_name + ) + ) + else: + apply_overlay(overlay_name) + if not is_applied(overlay_name): + raise RuntimeError("Failed to apply overlay `{}'".format(overlay_name)) + +def rm_overlay(overlay_name): + """ + Removes the given overlay. Does not check if the overlay is loaded. + """ + get_logger("DTO").trace("Removing overlay `{}'...".format(overlay_name)) + os.rmdir(os.path.join(SYSFS_OVERLAY_BASE_DIR, overlay_name)) + +def rm_overlay_safe(overlay_name): + """ + Only remove an overlay if it's already applied. + """ + if overlay_name in list(list_overlays(applied_only=True).keys()): + rm_overlay(overlay_name) + else: + get_logger("DTO").debug( + "Overlay `{}' was not loaded, not removing.".format(overlay_name) + ) + diff --git a/mpm/python/usrp_mpm/sys_utils/net.py b/mpm/python/usrp_mpm/sys_utils/net.py new file mode 100644 index 000000000..e3deea3e7 --- /dev/null +++ b/mpm/python/usrp_mpm/sys_utils/net.py @@ -0,0 +1,130 @@ + +# Copyright 2017 Ettus Research (National Instruments) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +""" +N310 implementation module +""" +import itertools +import socket +from six import iteritems +from pyroute2 import IPRoute +from usrp_mpm.mpmlog import get_logger + + +def get_valid_interfaces(iface_list): + """ + Given a list of interfaces (['eth1', 'eth2'] for example), return the + subset that contains actually valid entries. + Interfaces are checked for if they actually exist, and if so, if they're up. + """ + valid_ifaces = [] + with IPRoute() as ipr: + for iface in iface_list: + valid_iface_idx = ipr.link_lookup(ifname=iface) + if len(valid_iface_idx) == 0: + 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' \ + and len(get_iface_addrs(link_info.get_attr('IFLA_ADDRESS'))): + assert link_info.get_attr('IFLA_IFNAME') == iface + valid_ifaces.append(iface) + return valid_ifaces + + +def get_iface_info(ifname): + """ + Given an interface name (e.g. 'eth1'), return a dictionary with the + following keys: + - ip_addr: Main IPv4 address + - ip_addrs: List of valid IPv4 addresses + - mac_addr: MAC address + + All values are stored as strings. + """ + try: + with IPRoute() as ipr: + links = ipr.link_lookup(ifname=ifname) + if len(links) == 0: + raise LookupError("No interfaces known with name `{}'!" + .format(ifname)) + link_info = ipr.get_links(links)[0] + except IndexError: + raise LookupError("Could not get links for interface `{}'" + .format(ifname)) + mac_addr = link_info.get_attr('IFLA_ADDRESS') + ip_addrs = get_iface_addrs(mac_addr) + return { + 'mac_addr': mac_addr, + 'ip_addr': ip_addrs[0], + 'ip_addrs': ip_addrs, + } + +def ip_addr_to_iface(ip_addr, iface_list): + """ + Return an Ethernet interface (e.g. 'eth1') given an IP address. + + Arguments: + ip_addr -- The IP address as a string + iface_list -- A map "interface name" -> iface_info, where iface_info + is another map as returned by net.get_iface_info(). + """ + # Flip around the iface_info map and then use it to look up by IP addr + return { + iface_info['ip_addr']: iface_name + for iface_name, iface_info in iteritems(iface_list) + }[ip_addr] + + +def get_iface_addrs(mac_addr): + """ + return ipv4 addresses for a given macaddress + input format: "aa:bb:cc:dd:ee:ff" + """ + with IPRoute() as ip2: + # returns index + [link] = ip2.link_lookup(address=mac_addr) + # Only get v4 addresses + addresses = [addr.get_attrs('IFA_ADDRESS') + for addr in ip2.get_addr(family=socket.AF_INET) + if addr.get('index', None) == link] + # flatten possibly nested list + addresses = list(itertools.chain.from_iterable(addresses)) + + return addresses + + +def byte_to_mac(byte_str): + """ + converts a bytestring into nice hex representation + """ + return ':'.join(["%02x" % ord(x) for x in byte_str]) + + +def get_mac_addr(remote_addr): + """ + return MAC address of a remote host already discovered + or None if no host entry was found + """ + with IPRoute() as ip2: + addrs = ip2.get_neighbours(dst=remote_addr) + if len(addrs) > 1: + get_logger('get_mac_addr').warning("More than one device with the " + "same IP address found. " + "Picking entry at random") + if not addrs: + return None + return addrs[0].get_attr('NDA_LLADDR') diff --git a/mpm/python/usrp_mpm/sys_utils/sysfs_gpio.py b/mpm/python/usrp_mpm/sys_utils/sysfs_gpio.py new file mode 100644 index 000000000..f8384a3b8 --- /dev/null +++ b/mpm/python/usrp_mpm/sys_utils/sysfs_gpio.py @@ -0,0 +1,199 @@ +# +# Copyright 2017 Ettus Research (National Instruments) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +""" +Access to GPIOs mapped into the PS via sysfs +""" + +import os +from builtins import object +import pyudev +from usrp_mpm.mpmlog import get_logger + +GPIO_SYSFS_BASE_DIR = '/sys/class/gpio' +GPIO_SYSFS_LABELFILE = 'label' +GPIO_SYSFS_VALUEFILE = 'value' + +def get_all_gpio_devs(parent_dev=None): + """ + Returns a list of all GPIO chips available through sysfs. Will look + something like ['gpiochip882', 'gpiochip123', ...] + + If there are multiple devices with the same label (example: daughterboards + may have a single label for a shared component), a parent device needs to + be provided to disambiguate. + + Arguments: + parent_dev -- A parent udev device. If this is provided, only GPIO devices + which are a child of the parent device are returned. + + Example: + >>> parent_dev = pyudev.Devices.from_sys_path( + pyudev.Context(), '/sys/class/i2c-adapter/i2c-10') + >>> get_all_gpio_devs(parent_dev) + """ + try: + context = pyudev.Context() + gpios = [device.sys_name + for device in context.list_devices( + subsystem="gpio").match_parent(parent_dev) + if device.device_number == 0 + ] + return gpios + except OSError: + # Typically means GPIO not available, maybe no overlay + return [] + +def get_gpio_map_info(gpio_dev): + """ + Returns all the map info for a given GPIO device. + Example: If pio_dev is 'gpio882', it will list all files + in /sys/class/gpio/gpio882/ and create a dictionary with filenames + as keys and content as value. Subdirs are skipped. + + Numbers are casted to numbers automatically. Strings remain strings. + """ + map_info = {} + map_info_path = os.path.join( + GPIO_SYSFS_BASE_DIR, gpio_dev, + ) + for info_file in os.listdir(map_info_path): + if not os.path.isfile(os.path.join(map_info_path, info_file)): + continue + map_info_value = open(os.path.join(map_info_path, info_file), 'r').read().strip() + try: + map_info[info_file] = int(map_info_value, 0) + except ValueError: + map_info[info_file] = map_info_value + # Manually add GPIO number + context = pyudev.Context() + map_info['sys_number'] = int( + pyudev.Devices.from_name(context, subsystem="gpio", sys_name=gpio_dev).sys_number + ) + return map_info + +def find_gpio_device(label, parent_dev=None, logger=None): + """ + Given a label, returns a tuple (uio_device, map_info). + uio_device is something like 'gpio882'. map_info is a dictionary with + information regarding the GPIO device read from the map info sysfs dir. + """ + gpio_devices = get_all_gpio_devs(parent_dev) + if logger: + logger.trace("Found the following UIO devices: `{0}'".format(','.join(gpio_devices))) + for gpio_device in gpio_devices: + map_info = get_gpio_map_info(gpio_device) + if logger: + logger.trace("{0} has map info: {1}".format(gpio_device, map_info)) + if map_info.get('label') == label: + if logger: + logger.trace("Device matches label: `{0}'".format(gpio_device)) + return gpio_device, map_info + if logger: + logger.warning("Found no matching gpio device for label `{0}'".format(label)) + return None, None + +class SysFSGPIO(object): + """ + API for accessing GPIOs mapped into userland via sysfs + """ + + def __init__(self, label, use_mask, ddr, init_value=0, parent_dev=None): + assert (use_mask & ddr) == ddr + self.log = get_logger("SysFSGPIO") + self._label = label + self._use_mask = use_mask + self._ddr = ddr + self._init_value = init_value + self.log.trace("Generating SysFSGPIO object for label `{}'...".format(label)) + self._gpio_dev, self._map_info = \ + find_gpio_device(label, parent_dev, self.log) + if self._gpio_dev is None: + self.log.error("Could not find GPIO device with label `{}'.".format(label)) + self.log.trace("GPIO base number is {}".format(self._map_info.get("sys_number"))) + self._base_gpio = self._map_info.get("sys_number") + self.init(self._map_info['ngpio'], + self._base_gpio, + self._use_mask, + self._ddr, + self._init_value) + + def init(self, n_gpio, base, use_mask, ddr, init_value=0): + """ + Guarantees that all the devices are created accordingly + + E.g., if use_mask & 0x1 is True, it makes sure that 'gpioXXX' is exported. + Also sets the DDRs. + """ + gpio_list = [x for x in range(n_gpio) if (1<. +# + +import os +import pyudev + +def get_eeprom_paths(address): + """ + Return list of EEPROM device paths for a given I2C address. + If no device paths are found, an empty list is returned. + """ + context = pyudev.Context() + parent = pyudev.Device.from_name(context, "platform", address) + paths = [d.device_node if d.device_node is not None else d.sys_path + for d in context.list_devices(parent=parent, subsystem="nvmem")] + if len(paths) == 0: + return [] + # We need to sort this so 9-0050 comes before 10-0050 (etc.) + maxlen = max((len(os.path.split(p)[1]) for p in paths)) + paths = sorted( + paths, + key=lambda x: "{:>0{maxlen}}".format(os.path.split(x)[1], maxlen=maxlen) + ) + return [os.path.join(x, 'nvmem') for x in paths] + +def get_spidev_nodes(spi_master): + """ + Return list of spidev device paths for a given SPI master. If no valid paths + can be found, an empty list is returned. + """ + context = pyudev.Context() + parent = pyudev.Device.from_name(context, "platform", spi_master) + return [ + device.device_node + for device in context.list_devices(parent=parent, subsystem="spidev") + ] + diff --git a/mpm/python/usrp_mpm/sys_utils/uio.py b/mpm/python/usrp_mpm/sys_utils/uio.py new file mode 100644 index 000000000..c07227776 --- /dev/null +++ b/mpm/python/usrp_mpm/sys_utils/uio.py @@ -0,0 +1,167 @@ +# +# Copyright 2017 Ettus Research (National Instruments) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +""" +Access to UIO mapped memory. +""" + +import struct +import os +import mmap +from builtins import hex +from builtins import object +import pyudev +from usrp_mpm.mpmlog import get_logger + +UIO_SYSFS_BASE_DIR = '/sys/class/uio' +UIO_DEV_BASE_DIR = '/dev' + +def get_all_uio_devs(): + """ + Return a list of all uio devices. Will look something like + ['uio0', 'uio1', ...]. + """ + try: + context = pyudev.Context() + paths = [os.path.split(device.device_node)[-1] + for device in context.list_devices(subsystem="uio")] + return paths + except OSError: + # Typically means UIO devices + return [] + +def get_uio_map_info(uio_dev, map_num): + """ + Returns all the map info for a given UIO device and map number. + Example: If uio_dev is 'uio0', and map_num is 0, it will list all files + in /sys/class/uio/uio0/maps/map0/ and create a dictionary with filenames + as keys and content as value. + + Numbers are casted to numbers automatically. Strings remain strings. + """ + map_info = {} + map_info_path = os.path.join( + UIO_SYSFS_BASE_DIR, uio_dev, 'maps', 'map{0}'.format(map_num) + ) + for info_file in os.listdir(map_info_path): + map_info_value = open(os.path.join(map_info_path, info_file), 'r').read().strip() + try: + map_info[info_file] = int(map_info_value, 0) + except ValueError: + map_info[info_file] = map_info_value + return map_info + +def find_uio_device(label, logger=None): + """ + Given a label, returns a tuple (uio_device, map_info). + uio_device is something like '/dev/uio0'. map_info is a dictionary with + information regarding the UIO device read from the map info sysfs dir. + Note: We assume a single map (map0) for all UIO devices here. + """ + uio_devices = get_all_uio_devs() + if logger: + logger.trace("Found the following UIO devices: `{0}'".format(','.join(uio_devices))) + for uio_device in uio_devices: + map0_info = get_uio_map_info(uio_device, 0) + logger.trace("{0} has map info: {1}".format(uio_device, map0_info)) + if map0_info.get('name') == label: + if logger: + logger.trace("Device matches label: `{0}'".format(uio_device)) + return os.path.join(UIO_DEV_BASE_DIR, uio_device), map0_info + if logger: + logger.warning("Found no matching UIO device for label `{0}'".format(label)) + return None, None + +class UIO(object): + """ + Provides peek/poke interfaces for uio-mapped memory. + + Arguments: + label -- Label of the UIO device. The label is set in the device tree + overlay + path -- Path to UIO device, e.g. '/dev/uio0'. This is ignored if 'label' is + provided. + length -- Number of bytes in the address space (is passed to mmap.mmap). + This is usually automatically determined. No need to set it. + Unless you really know what you're doing. + read_only -- Boolean; True == ro, False == rw + offset -- Passed to mmap.mmap. + This is usually automatically determined. No need to set it. + Unless you really know what you're doing. + """ + def __init__(self, label=None, path=None, length=None, read_only=True, offset=None): + self.log = get_logger('UIO') + if label is None: + self._path = path + self.log.trace("Using UIO device `{0}'".format(path)) + uio_device = os.path.split(path)[-1] + self.log.trace("Getting map info for UIO device `{0}'".format(uio_device)) + map_info = get_uio_map_info(uio_device, 0) + # Python can't tell the size of a uio device by itself + assert length is not None + else: + self.log.trace("Using UIO device by label `{0}'".format(label)) + self._path, map_info = find_uio_device(label, self.log) + offset = offset or map_info['offset'] # If we ever support multiple maps, check if this is correct... + assert offset == 0 # ...and then remove this line + length = length or map_info['size'] + self.log.trace("UIO device is being opened read-{0}.".format("only" if read_only else "write")) + if self._path is None: + self.log.error("Could not find a UIO device for label {0}".format(label)) + raise RuntimeError("Could not find a UIO device for label {0}".format(label)) + self._read_only = read_only + self.log.trace("Opening UIO device file {}...".format(self._path)) + self._fd = os.open(self._path, os.O_RDONLY if read_only else os.O_RDWR) + self.log.trace("Calling mmap({fd}, length={length}, offset={offset})".format( + fd=self._fd, length=hex(length), offset=hex(offset) + )) + self._mm = mmap.mmap( + self._fd, + length, + flags=mmap.MAP_SHARED, + prot=mmap.PROT_READ | (0 if read_only else mmap.PROT_WRITE), + offset=offset, + ) + + def __del__(self): + """ + Destructor needs to close the uio-mapped memory + """ + try: + self._mm.close() + os.close(self._fd) + except: + self.log.warning("Failed to properly destruct UIO object.") + pass + + def peek32(self, addr): + """ + Returns the 32-bit value starting at address addr as an integer + """ + return struct.unpack('@I', self._mm[addr:addr+4])[0] + + def poke32(self, addr, val): + """ + Writes the 32-bit value val to address starting at addr. + Will throw if read_only was set to True. + A value that exceeds 32 bits will be truncated to 32 bits. + """ + assert not self._read_only + self._mm[addr:addr+4] = struct.pack( + '@I', + (val & 0xFFFFFFFF), + ) + diff --git a/mpm/python/usrp_mpm/sysfs_gpio.py b/mpm/python/usrp_mpm/sysfs_gpio.py deleted file mode 100644 index b2d982059..000000000 --- a/mpm/python/usrp_mpm/sysfs_gpio.py +++ /dev/null @@ -1,199 +0,0 @@ -# -# Copyright 2017 Ettus Research (National Instruments) -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# -""" -Access to GPIOs mapped into the PS via sysfs -""" - -import os -from builtins import object -import pyudev -from .mpmlog import get_logger - -GPIO_SYSFS_BASE_DIR = '/sys/class/gpio' -GPIO_SYSFS_LABELFILE = 'label' -GPIO_SYSFS_VALUEFILE = 'value' - -def get_all_gpio_devs(parent_dev=None): - """ - Returns a list of all GPIO chips available through sysfs. Will look - something like ['gpiochip882', 'gpiochip123', ...] - - If there are multiple devices with the same label (example: daughterboards - may have a single label for a shared component), a parent device needs to - be provided to disambiguate. - - Arguments: - parent_dev -- A parent udev device. If this is provided, only GPIO devices - which are a child of the parent device are returned. - - Example: - >>> parent_dev = pyudev.Devices.from_sys_path( - pyudev.Context(), '/sys/class/i2c-adapter/i2c-10') - >>> get_all_gpio_devs(parent_dev) - """ - try: - context = pyudev.Context() - gpios = [device.sys_name - for device in context.list_devices( - subsystem="gpio").match_parent(parent_dev) - if device.device_number == 0 - ] - return gpios - except OSError: - # Typically means GPIO not available, maybe no overlay - return [] - -def get_gpio_map_info(gpio_dev): - """ - Returns all the map info for a given GPIO device. - Example: If pio_dev is 'gpio882', it will list all files - in /sys/class/gpio/gpio882/ and create a dictionary with filenames - as keys and content as value. Subdirs are skipped. - - Numbers are casted to numbers automatically. Strings remain strings. - """ - map_info = {} - map_info_path = os.path.join( - GPIO_SYSFS_BASE_DIR, gpio_dev, - ) - for info_file in os.listdir(map_info_path): - if not os.path.isfile(os.path.join(map_info_path, info_file)): - continue - map_info_value = open(os.path.join(map_info_path, info_file), 'r').read().strip() - try: - map_info[info_file] = int(map_info_value, 0) - except ValueError: - map_info[info_file] = map_info_value - # Manually add GPIO number - context = pyudev.Context() - map_info['sys_number'] = int( - pyudev.Devices.from_name(context, subsystem="gpio", sys_name=gpio_dev).sys_number - ) - return map_info - -def find_gpio_device(label, parent_dev=None, logger=None): - """ - Given a label, returns a tuple (uio_device, map_info). - uio_device is something like 'gpio882'. map_info is a dictionary with - information regarding the GPIO device read from the map info sysfs dir. - """ - gpio_devices = get_all_gpio_devs(parent_dev) - if logger: - logger.trace("Found the following UIO devices: `{0}'".format(','.join(gpio_devices))) - for gpio_device in gpio_devices: - map_info = get_gpio_map_info(gpio_device) - if logger: - logger.trace("{0} has map info: {1}".format(gpio_device, map_info)) - if map_info.get('label') == label: - if logger: - logger.trace("Device matches label: `{0}'".format(gpio_device)) - return gpio_device, map_info - if logger: - logger.warning("Found no matching gpio device for label `{0}'".format(label)) - return None, None - -class SysFSGPIO(object): - """ - API for accessing GPIOs mapped into userland via sysfs - """ - - def __init__(self, label, use_mask, ddr, init_value=0, parent_dev=None): - assert (use_mask & ddr) == ddr - self.log = get_logger("SysFSGPIO") - self._label = label - self._use_mask = use_mask - self._ddr = ddr - self._init_value = init_value - self.log.trace("Generating SysFSGPIO object for label `{}'...".format(label)) - self._gpio_dev, self._map_info = \ - find_gpio_device(label, parent_dev, self.log) - if self._gpio_dev is None: - self.log.error("Could not find GPIO device with label `{}'.".format(label)) - self.log.trace("GPIO base number is {}".format(self._map_info.get("sys_number"))) - self._base_gpio = self._map_info.get("sys_number") - self.init(self._map_info['ngpio'], - self._base_gpio, - self._use_mask, - self._ddr, - self._init_value) - - def init(self, n_gpio, base, use_mask, ddr, init_value=0): - """ - Guarantees that all the devices are created accordingly - - E.g., if use_mask & 0x1 is True, it makes sure that 'gpioXXX' is exported. - Also sets the DDRs. - """ - gpio_list = [x for x in range(n_gpio) if (1<. -# -""" -Access to UIO mapped memory. -""" - -import struct -import os -import mmap -from builtins import hex -from builtins import object -import pyudev -from .mpmlog import get_logger - -UIO_SYSFS_BASE_DIR = '/sys/class/uio' -UIO_DEV_BASE_DIR = '/dev' - -def get_all_uio_devs(): - """ - Return a list of all uio devices. Will look something like - ['uio0', 'uio1', ...]. - """ - try: - context = pyudev.Context() - paths = [os.path.split(device.device_node)[-1] - for device in context.list_devices(subsystem="uio")] - return paths - except OSError: - # Typically means UIO devices - return [] - -def get_uio_map_info(uio_dev, map_num): - """ - Returns all the map info for a given UIO device and map number. - Example: If uio_dev is 'uio0', and map_num is 0, it will list all files - in /sys/class/uio/uio0/maps/map0/ and create a dictionary with filenames - as keys and content as value. - - Numbers are casted to numbers automatically. Strings remain strings. - """ - map_info = {} - map_info_path = os.path.join( - UIO_SYSFS_BASE_DIR, uio_dev, 'maps', 'map{0}'.format(map_num) - ) - for info_file in os.listdir(map_info_path): - map_info_value = open(os.path.join(map_info_path, info_file), 'r').read().strip() - try: - map_info[info_file] = int(map_info_value, 0) - except ValueError: - map_info[info_file] = map_info_value - return map_info - -def find_uio_device(label, logger=None): - """ - Given a label, returns a tuple (uio_device, map_info). - uio_device is something like '/dev/uio0'. map_info is a dictionary with - information regarding the UIO device read from the map info sysfs dir. - Note: We assume a single map (map0) for all UIO devices here. - """ - uio_devices = get_all_uio_devs() - if logger: - logger.trace("Found the following UIO devices: `{0}'".format(','.join(uio_devices))) - for uio_device in uio_devices: - map0_info = get_uio_map_info(uio_device, 0) - logger.trace("{0} has map info: {1}".format(uio_device, map0_info)) - if map0_info.get('name') == label: - if logger: - logger.trace("Device matches label: `{0}'".format(uio_device)) - return os.path.join(UIO_DEV_BASE_DIR, uio_device), map0_info - if logger: - logger.warning("Found no matching UIO device for label `{0}'".format(label)) - return None, None - -class UIO(object): - """ - Provides peek/poke interfaces for uio-mapped memory. - - Arguments: - label -- Label of the UIO device. The label is set in the device tree - overlay - path -- Path to UIO device, e.g. '/dev/uio0'. This is ignored if 'label' is - provided. - length -- Number of bytes in the address space (is passed to mmap.mmap). - This is usually automatically determined. No need to set it. - Unless you really know what you're doing. - read_only -- Boolean; True == ro, False == rw - offset -- Passed to mmap.mmap. - This is usually automatically determined. No need to set it. - Unless you really know what you're doing. - """ - def __init__(self, label=None, path=None, length=None, read_only=True, offset=None): - self.log = get_logger('UIO') - if label is None: - self._path = path - self.log.trace("Using UIO device `{0}'".format(path)) - uio_device = os.path.split(path)[-1] - self.log.trace("Getting map info for UIO device `{0}'".format(uio_device)) - map_info = get_uio_map_info(uio_device, 0) - # Python can't tell the size of a uio device by itself - assert length is not None - else: - self.log.trace("Using UIO device by label `{0}'".format(label)) - self._path, map_info = find_uio_device(label, self.log) - offset = offset or map_info['offset'] # If we ever support multiple maps, check if this is correct... - assert offset == 0 # ...and then remove this line - length = length or map_info['size'] - self.log.trace("UIO device is being opened read-{0}.".format("only" if read_only else "write")) - if self._path is None: - self.log.error("Could not find a UIO device for label {0}".format(label)) - raise RuntimeError("Could not find a UIO device for label {0}".format(label)) - self._read_only = read_only - self.log.trace("Opening UIO device file {}...".format(self._path)) - self._fd = os.open(self._path, os.O_RDONLY if read_only else os.O_RDWR) - self.log.trace("Calling mmap({fd}, length={length}, offset={offset})".format( - fd=self._fd, length=hex(length), offset=hex(offset) - )) - self._mm = mmap.mmap( - self._fd, - length, - flags=mmap.MAP_SHARED, - prot=mmap.PROT_READ | (0 if read_only else mmap.PROT_WRITE), - offset=offset, - ) - - def __del__(self): - """ - Destructor needs to close the uio-mapped memory - """ - try: - self._mm.close() - os.close(self._fd) - except: - self.log.warning("Failed to properly destruct UIO object.") - pass - - def peek32(self, addr): - """ - Returns the 32-bit value starting at address addr as an integer - """ - return struct.unpack('@I', self._mm[addr:addr+4])[0] - - def poke32(self, addr, val): - """ - Writes the 32-bit value val to address starting at addr. - Will throw if read_only was set to True. - A value that exceeds 32 bits will be truncated to 32 bits. - """ - assert not self._read_only - self._mm[addr:addr+4] = struct.pack( - '@I', - (val & 0xFFFFFFFF), - ) - diff --git a/mpm/python/usrp_mpm/xports/xportmgr_liberio.py b/mpm/python/usrp_mpm/xports/xportmgr_liberio.py index 29a3d6673..8ac2f06f3 100644 --- a/mpm/python/usrp_mpm/xports/xportmgr_liberio.py +++ b/mpm/python/usrp_mpm/xports/xportmgr_liberio.py @@ -9,7 +9,7 @@ Liberio Transport manager from builtins import object from usrp_mpm.liberiotable import LiberioDispatcherTable -from usrp_mpm import libpyusrp_periphs as lib +from usrp_mpm import lib class XportMgrLiberio(object): """ diff --git a/mpm/python/usrp_mpm/xports/xportmgr_udp.py b/mpm/python/usrp_mpm/xports/xportmgr_udp.py index 18c736150..18573bad9 100644 --- a/mpm/python/usrp_mpm/xports/xportmgr_udp.py +++ b/mpm/python/usrp_mpm/xports/xportmgr_udp.py @@ -10,9 +10,9 @@ UDP Transport manager from builtins import object from six import iteritems, itervalues from usrp_mpm.ethtable import EthDispatcherTable -from usrp_mpm import net +from usrp_mpm.sys_utils import net from usrp_mpm.mpmtypes import SID -from usrp_mpm import libpyusrp_periphs as lib +from usrp_mpm import lib class XportMgrUDP(object): """ -- cgit v1.2.3