aboutsummaryrefslogtreecommitdiffstats
path: root/mpm
diff options
context:
space:
mode:
authorSugandha Gupta <sugandha.gupta@ettus.com>2019-01-25 11:34:47 -0800
committerBrent Stapleton <brent.stapleton@ettus.com>2019-05-01 15:17:23 -0700
commit178b35569b1a25180a80a23b945b10b04c9f10f5 (patch)
tree3adb6f78ebd148867a50526c60fe7bf9694a4a72 /mpm
parent8a400f6a30942c9d6d3596f6989720eb4cff058b (diff)
downloaduhd-178b35569b1a25180a80a23b945b10b04c9f10f5.tar.gz
uhd-178b35569b1a25180a80a23b945b10b04c9f10f5.tar.bz2
uhd-178b35569b1a25180a80a23b945b10b04c9f10f5.zip
e310/e320: Move E310 to MPM architecture and refactor
- Turns the E310 into an MPM device (like N3xx, E320) - Factor out common code between E320 and E310, maximize sharing between the two devices - Remove all pre-MPM E310 code that is no longer needed - Modify MPM to remove all existing overlays before applying new ones (this is necessary to enable idle image mode for E310) Co-authored-by: Virendra Kakade <virendra.kakade@ni.com> Signed-off-by: Virendra Kakade <virendra.kakade@ni.com>
Diffstat (limited to 'mpm')
-rw-r--r--mpm/CMakeLists.txt5
-rw-r--r--mpm/include/mpm/CMakeLists.txt2
-rw-r--r--mpm/include/mpm/ad9361/CMakeLists.txt1
-rw-r--r--mpm/include/mpm/ad9361/ad9361_ctrl.hpp47
-rw-r--r--mpm/include/mpm/ad9361/e31x_defaults.hpp45
-rw-r--r--mpm/include/mpm/dboards/CMakeLists.txt5
-rw-r--r--mpm/include/mpm/dboards/e31x_db_manager.hpp43
-rw-r--r--mpm/lib/CMakeLists.txt2
-rw-r--r--mpm/lib/dboards/CMakeLists.txt4
-rw-r--r--mpm/lib/dboards/e31x_db_manager.cpp79
-rw-r--r--mpm/python/CMakeLists.txt2
-rw-r--r--mpm/python/pyusrp_periphs/e31x/pyusrp_periphs.cpp30
-rw-r--r--mpm/python/usrp_mpm/CMakeLists.txt1
-rw-r--r--mpm/python/usrp_mpm/dboard_manager/CMakeLists.txt1
-rw-r--r--mpm/python/usrp_mpm/dboard_manager/__init__.py1
-rw-r--r--mpm/python/usrp_mpm/dboard_manager/e31x_db.py212
-rw-r--r--mpm/python/usrp_mpm/dboard_manager/neon.py19
-rw-r--r--mpm/python/usrp_mpm/e31x_legacy_eeprom.py103
-rw-r--r--mpm/python/usrp_mpm/periph_manager/CMakeLists.txt2
-rw-r--r--mpm/python/usrp_mpm/periph_manager/base.py6
-rw-r--r--mpm/python/usrp_mpm/periph_manager/e31x.py744
-rw-r--r--mpm/python/usrp_mpm/periph_manager/e31x_periphs.py297
-rw-r--r--mpm/python/usrp_mpm/rpc_server.py29
-rw-r--r--mpm/tools/CMakeLists.txt4
24 files changed, 1668 insertions, 16 deletions
diff --git a/mpm/CMakeLists.txt b/mpm/CMakeLists.txt
index 4f6c3b651..78759cc0b 100644
--- a/mpm/CMakeLists.txt
+++ b/mpm/CMakeLists.txt
@@ -123,7 +123,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 tests)
+set(MPM_ALL_DEVICES n3xx e320 e31x 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
@@ -139,12 +139,15 @@ if(MPM_DEVICE STREQUAL "n3xx")
set(ENABLE_MAGNESIUM ON)
elseif(MPM_DEVICE STREQUAL "e320")
set(ENABLE_E320 ON)
+elseif(MPM_DEVICE STREQUAL "e31x")
+ set(ENABLE_E300 ON)
endif()
MPM_REGISTER_COMPONENT("LibMPM" ENABLE_LIBMPM ON "Boost_FOUND" OFF ON)
MPM_REGISTER_COMPONENT("Mykonos" ENABLE_MYKONOS ON "ENABLE_LIBMPM" OFF OFF)
MPM_REGISTER_COMPONENT("Magnesium" ENABLE_MAGNESIUM ON "ENABLE_MYKONOS" OFF OFF)
MPM_REGISTER_COMPONENT("E320" ENABLE_E320 ON "ENABLE_LIBMPM" OFF OFF)
+MPM_REGISTER_COMPONENT("E300" ENABLE_E300 ON "ENABLE_LIBMPM" OFF OFF)
add_subdirectory(include)
include_directories(
diff --git a/mpm/include/mpm/CMakeLists.txt b/mpm/include/mpm/CMakeLists.txt
index e5b7193c6..f3637c913 100644
--- a/mpm/include/mpm/CMakeLists.txt
+++ b/mpm/include/mpm/CMakeLists.txt
@@ -11,7 +11,7 @@ install(FILES
if(ENABLE_MYKONOS)
add_subdirectory(ad937x)
-elseif(ENABLE_E320)
+elseif(ENABLE_E320 OR ENABLE_E300)
add_subdirectory(ad9361)
endif(ENABLE_MYKONOS)
diff --git a/mpm/include/mpm/ad9361/CMakeLists.txt b/mpm/include/mpm/ad9361/CMakeLists.txt
index 94ea655a7..2b2ea03ec 100644
--- a/mpm/include/mpm/ad9361/CMakeLists.txt
+++ b/mpm/include/mpm/ad9361/CMakeLists.txt
@@ -8,5 +8,6 @@
install(FILES
ad9361_ctrl.hpp
e320_defaults.hpp
+ e31x_defaults.hpp
DESTINATION ${INCLUDE_DIR}/mpm/catalina
)
diff --git a/mpm/include/mpm/ad9361/ad9361_ctrl.hpp b/mpm/include/mpm/ad9361/ad9361_ctrl.hpp
index 7673d7460..2405317fd 100644
--- a/mpm/include/mpm/ad9361/ad9361_ctrl.hpp
+++ b/mpm/include/mpm/ad9361/ad9361_ctrl.hpp
@@ -14,11 +14,16 @@
#include <functional>
#include <string>
#include <vector>
+#include <future>
namespace mpm { namespace chips {
using uhd::usrp::ad9361_ctrl;
}}; // namespace mpm::chips
+//! Async calls
+std::future<double> handle_tune;
+std::future<double> handle_set_clock_rate;
+
// TODO: pull in filter_info_base
#ifdef LIBMPM_PYTHON
void export_catalina(py::module& top_module)
@@ -58,6 +63,48 @@ void export_catalina(py::module& top_module)
.def("_get_filter", &ad9361_ctrl::get_filter)
.def("set_filter", &ad9361_ctrl::set_filter)
.def("output_digital_test_tone", &ad9361_ctrl::output_digital_test_tone);
+
+ m.def("async__tune", +[](
+ ad9361_ctrl& catalina,
+ const std::string &which,
+ const double value
+ ){
+ handle_tune = std::async(std::launch::async,
+ &ad9361_ctrl::tune,
+ &catalina,
+ which,
+ value
+ );
+ });
+ m.def("await__tune", +[](
+ )->bool{
+ if (handle_tune.wait_for(std::chrono::seconds(0))
+ == std::future_status::ready){
+ handle_tune.get();
+ return true;
+ }
+ return false;
+ });
+ m.def("async__set_clock_rate", +[](
+ ad9361_ctrl& catalina,
+ const double value
+ ){
+ handle_set_clock_rate = std::async(std::launch::async,
+ &ad9361_ctrl::set_clock_rate,
+ &catalina,
+ value
+ );
+ });
+ m.def("await__set_clock_rate", +[](
+ )->bool{
+ if (handle_set_clock_rate.wait_for(std::chrono::seconds(0))
+ == std::future_status::ready){
+ handle_set_clock_rate.get();
+ return true;
+ }
+ return false;
+ });
+
}
#endif
diff --git a/mpm/include/mpm/ad9361/e31x_defaults.hpp b/mpm/include/mpm/ad9361/e31x_defaults.hpp
new file mode 100644
index 000000000..c2188af8b
--- /dev/null
+++ b/mpm/include/mpm/ad9361/e31x_defaults.hpp
@@ -0,0 +1,45 @@
+//
+// Copyright 2018 Ettus Research, a National Instruments Company
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#ifndef INCLUDED_E31X_DEFAULTS_HPP
+#define INCLUDED_E31X_DEFAULTS_HPP
+
+#include "ad9361_ctrl.hpp"
+
+namespace mpm { namespace types { namespace e31x {
+
+using namespace uhd::usrp;
+
+class e31x_ad9361_client_t : public uhd::usrp::ad9361_params {
+public:
+ ~e31x_ad9361_client_t() {}
+ double get_band_edge(frequency_band_t band) {
+ switch (band) {
+ case AD9361_RX_BAND0: return 1.2e9;
+ case AD9361_RX_BAND1: return 2.6e9;
+ case AD9361_TX_BAND0: return 2940.0e6;
+ default: return 0;
+ }
+ }
+ clocking_mode_t get_clocking_mode() {
+ return clocking_mode_t::AD9361_XTAL_N_CLK_PATH;
+ }
+ digital_interface_mode_t get_digital_interface_mode() {
+ return AD9361_DDR_FDD_LVCMOS;
+ }
+ digital_interface_delays_t get_digital_interface_timing() {
+ digital_interface_delays_t delays;
+ delays.rx_clk_delay = 0;
+ delays.rx_data_delay = 0xF;
+ delays.tx_clk_delay = 0;
+ delays.tx_data_delay = 0xF;
+ return delays;
+ }
+};
+
+}}} // namespace
+
+#endif // INCLUDED_E31X_DEFAULTS_HPP
diff --git a/mpm/include/mpm/dboards/CMakeLists.txt b/mpm/include/mpm/dboards/CMakeLists.txt
index 03a5404bc..d1dd569d7 100644
--- a/mpm/include/mpm/dboards/CMakeLists.txt
+++ b/mpm/include/mpm/dboards/CMakeLists.txt
@@ -13,4 +13,9 @@ elseif(ENABLE_E320)
neon_manager.hpp
DESTINATION ${INCLUDE_DIR}/mpm/dboards
)
+elseif(ENABLE_E300)
+ install(FILES
+ e31x_db_manager.hpp
+ DESTINATION ${INCLUDE_DIR}/mpm/dboards
+ )
endif(ENABLE_MAGNESIUM)
diff --git a/mpm/include/mpm/dboards/e31x_db_manager.hpp b/mpm/include/mpm/dboards/e31x_db_manager.hpp
new file mode 100644
index 000000000..8777778e9
--- /dev/null
+++ b/mpm/include/mpm/dboards/e31x_db_manager.hpp
@@ -0,0 +1,43 @@
+//
+// Copyright 2018 Ettus Research, a National Instruments Company
+// Copyright 2019 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#pragma once
+
+#include <mpm/ad9361/ad9361_ctrl.hpp>
+#include <mpm/types/lockable.hpp>
+#include <mpm/types/regs_iface.hpp>
+#include <boost/shared_ptr.hpp>
+#include <memory>
+#include <mutex>
+
+namespace mpm { namespace dboards {
+ class e31x_db_manager
+ {
+ public:
+ e31x_db_manager(const std::string &catalina_spidev);
+
+ /*! Return a reference to the radio chip controls
+ */
+ mpm::chips::ad9361_ctrl::sptr get_radio_ctrl(){ return _catalina_ctrl; }
+
+ private:
+ mpm::chips::ad9361_ctrl::sptr _catalina_ctrl;
+ };
+
+}}; /* namespace mpm::dboards */
+
+#ifdef LIBMPM_PYTHON
+void export_e31x_db(py::module& top_module)
+{
+ using namespace mpm::dboards;
+ auto m = top_module.def_submodule("dboards");
+
+ py::class_<mpm::dboards::e31x_db_manager>(m, "e31x_db_manager")
+ .def(py::init<std::string>())
+ .def("get_radio_ctrl", &mpm::dboards::e31x_db_manager::get_radio_ctrl);
+}
+#endif
diff --git a/mpm/lib/CMakeLists.txt b/mpm/lib/CMakeLists.txt
index 10f8f0d9d..82a5d8e16 100644
--- a/mpm/lib/CMakeLists.txt
+++ b/mpm/lib/CMakeLists.txt
@@ -14,7 +14,7 @@ add_subdirectory(types)
if(ENABLE_MYKONOS)
add_subdirectory(mykonos)
-elseif(ENABLE_E320)
+elseif(ENABLE_E320 OR ENABLE_E300)
add_subdirectory(catalina)
endif(ENABLE_MYKONOS)
diff --git a/mpm/lib/dboards/CMakeLists.txt b/mpm/lib/dboards/CMakeLists.txt
index 0760d9fd1..4a13528eb 100644
--- a/mpm/lib/dboards/CMakeLists.txt
+++ b/mpm/lib/dboards/CMakeLists.txt
@@ -16,4 +16,8 @@ elseif(ENABLE_E320)
USRP_PERIPHS_ADD_OBJECT(dboards
neon_manager.cpp
)
+elseif(ENABLE_E300)
+ USRP_PERIPHS_ADD_OBJECT(dboards
+ e31x_db_manager.cpp
+ )
endif(ENABLE_MAGNESIUM)
diff --git a/mpm/lib/dboards/e31x_db_manager.cpp b/mpm/lib/dboards/e31x_db_manager.cpp
new file mode 100644
index 000000000..b433dde30
--- /dev/null
+++ b/mpm/lib/dboards/e31x_db_manager.cpp
@@ -0,0 +1,79 @@
+//
+// Copyright 2018 Ettus Research, a National Instruments Company
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#include <mpm/ad9361/e31x_defaults.hpp>
+#include <mpm/dboards/e31x_db_manager.hpp>
+#include <mpm/spi/spi_iface.hpp>
+#include <mpm/spi/spi_regs_iface.hpp>
+#include <mpm/types/regs_iface.hpp>
+#include <boost/make_shared.hpp>
+#include <memory>
+
+using namespace mpm::dboards;
+using namespace mpm::chips;
+using namespace mpm::types;
+using namespace mpm::types::e31x;
+
+namespace { /*anon*/
+
+constexpr uint32_t AD9361_SPI_WRITE_CMD = 0x00800000;
+constexpr uint32_t AD9361_SPI_READ_CMD = 0x00000000;
+constexpr uint32_t AD9361_SPI_ADDR_MASK = 0x003FFF00;
+constexpr uint32_t AD9361_SPI_ADDR_SHIFT = 8;
+constexpr uint32_t AD9361_SPI_DATA_MASK = 0x000000FF;
+constexpr uint32_t AD9361_SPI_DATA_SHIFT = 0;
+constexpr uint32_t AD9361_SPI_NUM_BITS = 24;
+constexpr uint32_t AD9361_SPI_SPEED_HZ = 2000000;
+constexpr int AD9361_SPI_MODE = 1;
+
+} // namespace /*anon*/
+
+/*! MPM-style E310 SPI Iface for AD9361 CTRL
+ *
+ */
+class e310_ad9361_io_spi : public ad9361_io
+{
+public:
+ e310_ad9361_io_spi(regs_iface::sptr regs_iface, uint32_t slave_num) :
+ _regs_iface(regs_iface), _slave_num(slave_num) { }
+
+ ~e310_ad9361_io_spi() {/*nop*/}
+
+ uint8_t peek8(uint32_t reg)
+ {
+ return _regs_iface->peek8(reg);
+ }
+
+ void poke8(uint32_t reg, uint8_t val)
+ {
+ _regs_iface->poke8(reg, val);
+ }
+
+private:
+ regs_iface::sptr _regs_iface;
+ uint32_t _slave_num;
+};
+
+e31x_db_manager::e31x_db_manager(const std::string &catalina_spidev)
+{
+ // Make the MPM-style low level SPI Regs iface
+ auto spi_iface = mpm::spi::make_spi_regs_iface(
+ mpm::spi::spi_iface::make_spidev(catalina_spidev, AD9361_SPI_SPEED_HZ, AD9361_SPI_MODE),
+ AD9361_SPI_ADDR_SHIFT,
+ AD9361_SPI_DATA_SHIFT,
+ AD9361_SPI_READ_CMD,
+ AD9361_SPI_WRITE_CMD);
+ // Make the SPI interface
+ auto spi_io_iface = std::make_shared<e310_ad9361_io_spi>(spi_iface, 0);
+ // Translate from a std shared_ptr to Boost (for legacy compatability)
+ auto spi_io_iface_boost = boost::shared_ptr<e310_ad9361_io_spi>(
+ spi_io_iface.get(),
+ [spi_io_iface](...) mutable { spi_io_iface.reset(); });
+ // Make the actual Catalina Ctrl object
+ _catalina_ctrl = ad9361_ctrl::make_spi(
+ boost::make_shared<e31x_ad9361_client_t>(),
+ spi_io_iface_boost);
+}
diff --git a/mpm/python/CMakeLists.txt b/mpm/python/CMakeLists.txt
index c78dc60b1..3a5b30d4e 100644
--- a/mpm/python/CMakeLists.txt
+++ b/mpm/python/CMakeLists.txt
@@ -12,6 +12,8 @@ if(MPM_DEVICE STREQUAL "n3xx")
add_library(pyusrp_periphs SHARED pyusrp_periphs/n3xx/pyusrp_periphs.cpp)
elseif(MPM_DEVICE STREQUAL "e320")
add_library(pyusrp_periphs SHARED pyusrp_periphs/e320/pyusrp_periphs.cpp)
+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
diff --git a/mpm/python/pyusrp_periphs/e31x/pyusrp_periphs.cpp b/mpm/python/pyusrp_periphs/e31x/pyusrp_periphs.cpp
new file mode 100644
index 000000000..b7971407c
--- /dev/null
+++ b/mpm/python/pyusrp_periphs/e31x/pyusrp_periphs.cpp
@@ -0,0 +1,30 @@
+//
+// Copyright 2018 Ettus Research, a National Instruments Company
+// Copyright 2019 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#include <pybind11/pybind11.h>
+namespace py = pybind11;
+#define LIBMPM_PYTHON
+
+// Allow boost::shared_ptr<T> to be a holder class of an object (PyBind11
+// supports std::shared_ptr and std::unique_ptr out of the box)
+#include <boost/shared_ptr.hpp>
+PYBIND11_DECLARE_HOLDER_TYPE(T, boost::shared_ptr<T>);
+
+#include <mpm/ad9361/ad9361_ctrl.hpp>
+#include <mpm/dboards/e31x_db_manager.hpp>
+#include <mpm/spi/spi_python.hpp>
+#include <mpm/types/types_python.hpp>
+#include <mpm/xbar_iface.hpp>
+
+PYBIND11_MODULE(libpyusrp_periphs, m)
+{
+ export_types(m);
+ export_spi(m);
+ export_xbar(m);
+ export_catalina(m);
+ export_e31x_db(m);
+}
diff --git a/mpm/python/usrp_mpm/CMakeLists.txt b/mpm/python/usrp_mpm/CMakeLists.txt
index 1b85c8561..6b6d99a30 100644
--- a/mpm/python/usrp_mpm/CMakeLists.txt
+++ b/mpm/python/usrp_mpm/CMakeLists.txt
@@ -15,6 +15,7 @@ set(USRP_MPM_TOP_FILES
${CMAKE_CURRENT_SOURCE_DIR}/components.py
${CMAKE_CURRENT_SOURCE_DIR}/discovery.py
${CMAKE_CURRENT_SOURCE_DIR}/eeprom.py
+ ${CMAKE_CURRENT_SOURCE_DIR}/e31x_legacy_eeprom.py
${CMAKE_CURRENT_SOURCE_DIR}/ethtable.py
${CMAKE_CURRENT_SOURCE_DIR}/gpsd_iface.py
${CMAKE_CURRENT_SOURCE_DIR}/liberiotable.py
diff --git a/mpm/python/usrp_mpm/dboard_manager/CMakeLists.txt b/mpm/python/usrp_mpm/dboard_manager/CMakeLists.txt
index 650a21aab..3e4f6ba76 100644
--- a/mpm/python/usrp_mpm/dboard_manager/CMakeLists.txt
+++ b/mpm/python/usrp_mpm/dboard_manager/CMakeLists.txt
@@ -19,6 +19,7 @@ set(USRP_MPM_DBMGR_FILES
${CMAKE_CURRENT_SOURCE_DIR}/dac_rh.py
${CMAKE_CURRENT_SOURCE_DIR}/eiscat.py
${CMAKE_CURRENT_SOURCE_DIR}/neon.py
+ ${CMAKE_CURRENT_SOURCE_DIR}/e31x_db.py
${CMAKE_CURRENT_SOURCE_DIR}/gain_rh.py
${CMAKE_CURRENT_SOURCE_DIR}/gaintables_rh.py
${CMAKE_CURRENT_SOURCE_DIR}/lmk_eiscat.py
diff --git a/mpm/python/usrp_mpm/dboard_manager/__init__.py b/mpm/python/usrp_mpm/dboard_manager/__init__.py
index 70e7881db..77fd84436 100644
--- a/mpm/python/usrp_mpm/dboard_manager/__init__.py
+++ b/mpm/python/usrp_mpm/dboard_manager/__init__.py
@@ -10,6 +10,7 @@ from .base import DboardManagerBase
from .magnesium import Magnesium
from .rhodium import Rhodium
from .neon import Neon
+from .e31x_db import E31x_db
from .eiscat import EISCAT
from .test import test
from .unknown import unknown
diff --git a/mpm/python/usrp_mpm/dboard_manager/e31x_db.py b/mpm/python/usrp_mpm/dboard_manager/e31x_db.py
new file mode 100644
index 000000000..358b69f88
--- /dev/null
+++ b/mpm/python/usrp_mpm/dboard_manager/e31x_db.py
@@ -0,0 +1,212 @@
+#
+# Copyright 2018 Ettus Research, a National Instruments Company
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+"""
+E310 dboard (RF and control) implementation module
+"""
+
+import threading
+import time
+from six import iterkeys, iteritems
+from usrp_mpm import lib # Pulls in everything from C++-land
+from usrp_mpm.bfrfs import BufferFS
+from usrp_mpm.dboard_manager import DboardManagerBase
+from usrp_mpm.mpmlog import get_logger
+from usrp_mpm.sys_utils.udev import get_eeprom_paths
+from usrp_mpm.sys_utils.uio import UIO
+from usrp_mpm.periph_manager.e31x_periphs import MboardRegsControl
+from usrp_mpm.mpmutils import async_exec
+
+###############################################################################
+# Main dboard control class
+###############################################################################
+class E31x_db(DboardManagerBase):
+ """
+ Holds all dboard specific information and methods of the E31x_db dboard
+ """
+ #########################################################################
+ # Overridables
+ #
+ # See DboardManagerBase for documentation on these fields
+ #########################################################################
+ pids = [0x0110]
+ rx_sensor_callback_map = {
+ 'ad9361_temperature': 'get_catalina_temp_sensor',
+ 'rssi' : 'get_rssi_sensor',
+ 'lo_lock' : 'get_lo_lock_sensor',
+ }
+ tx_sensor_callback_map = {
+ 'ad9361_temperature': 'get_catalina_temp_sensor',
+ }
+ # Maps the chipselects to the corresponding devices:
+ spi_chipselect = {"catalina": 0,
+ }
+
+ default_master_clock_rate = 16e6
+ MIN_MASTER_CLK_RATE = 220e3
+ MAX_MASTER_CLK_RATE = 61.44e6
+
+ def __init__(self, slot_idx, **kwargs):
+ super(E31x_db, self).__init__(slot_idx, **kwargs)
+ self.log = get_logger("E31x_db-{}".format(slot_idx))
+ self.log.trace("Initializing e31x daughterboard, slot index %d",
+ self.slot_idx)
+ self.rev = int(self.device_info['rev'])
+ self.log.trace("This is a rev: {}".format(chr(65 + self.rev)))
+ # These will get updated during init()
+ self.master_clock_rate = None
+ # Predeclare some attributes to make linter happy:
+ self.catalina = None
+ self.eeprom_fs = None
+ self.eeprom_path = None
+ # Now initialize all peripherals. If that doesn't work, put this class
+ # into a non-functional state (but don't crash, or we can't talk to it
+ # any more):
+ try:
+ self._init_periphs()
+ self._periphs_initialized = True
+ except Exception as ex:
+ self.log.error("Failed to initialize peripherals: %s",
+ str(ex))
+ self._periphs_initialized = False
+
+ def _init_periphs(self):
+ """
+ Initialize power and peripherals that don't need user-settings
+ """
+ self.log.debug("Loading C++ drivers...")
+ # Setup Catalina / the E31x_db Manager
+ self._device = lib.dboards.e31x_db_manager(
+ self._spi_nodes['catalina']
+ )
+ self.catalina = self._device.get_radio_ctrl()
+ self.log.trace("Loaded C++ drivers.")
+ self._init_cat_api(self.catalina)
+
+ def _init_cat_api(self, cat):
+ """
+ Propagate the C++ Catalina API into Python land.
+ """
+ def export_method(obj, method):
+ " Export a method object, including docstring "
+ meth_obj = getattr(obj, method)
+ def func(*args):
+ " Functor for storing docstring too "
+ return meth_obj(*args)
+ func.__doc__ = meth_obj.__doc__
+ return func
+ self.log.trace("Forwarding AD9361 methods to E31x_db class...")
+ for method in [
+ x for x in dir(self.catalina)
+ if not x.startswith("_") and \
+ callable(getattr(self.catalina, x))]:
+ self.log.trace("adding {}".format(method))
+ setattr(self, method, export_method(cat, method))
+
+ def init(self, args):
+ if not self._periphs_initialized:
+ error_msg = "Cannot run init(), peripherals are not initialized!"
+ self.log.error(error_msg)
+ raise RuntimeError(error_msg)
+ master_clock_rate = \
+ float(args.get('master_clock_rate',
+ self.default_master_clock_rate))
+ assert self.MIN_MASTER_CLK_RATE <= master_clock_rate <= self.MAX_MASTER_CLK_RATE, \
+ "Invalid master clock rate: {:.02f} MHz".format(
+ master_clock_rate / 1e6)
+ master_clock_rate_changed = master_clock_rate != self.master_clock_rate
+ if master_clock_rate_changed:
+ self.master_clock_rate = master_clock_rate
+ self.log.debug("Updating master clock rate to {:.02f} MHz!".format(
+ self.master_clock_rate / 1e6
+ ))
+ # Some default chains on -- needed for setup purposes
+ self.catalina.set_active_chains(True, False, True, False)
+ self.set_catalina_clock_rate(self.master_clock_rate)
+
+ return True
+
+ def get_master_clock_rate(self):
+ " Return master clock rate (== sampling rate) "
+ return self.master_clock_rate
+
+ ##########################################################################
+ # Sensors
+ ##########################################################################
+ def get_ad9361_lo_lock(self, which):
+ """
+ Return LO lock status (Boolean!) of AD9361. 'which' must be
+ either 'tx' or 'rx'
+ """
+ self.mboard_regs_label = "mboard-regs"
+ self.mboard_regs_control = MboardRegsControl(
+ self.mboard_regs_label, self.log)
+ if which == "tx":
+ locked = self. mboard_regs_control.get_ad9361_tx_lo_lock()
+ elif which == "rx":
+ locked = self. mboard_regs_control.get_ad9361_rx_lo_lock()
+ else:
+ locked = False
+ return locked
+
+ def get_lo_lock_sensor(self, which):
+ """
+ Get sensor dict with LO lock status
+ """
+ self.log.trace("Reading LO Lock.")
+ lo_locked = self.get_ad9361_lo_lock(which)
+ return {
+ 'name': 'ad9361_lock',
+ 'type': 'BOOLEAN',
+ 'unit': 'locked' if lo_locked else 'unlocked',
+ 'value': str(lo_locked).lower(),
+ }
+
+ def get_catalina_temp_sensor(self, _):
+ """
+ Get temperature sensor reading of Catalina.
+ """
+ # Note: the unused argument is channel
+ self.log.trace("Reading Catalina temperature.")
+ return {
+ 'name': 'ad9361_temperature',
+ 'type': 'REALNUM',
+ 'unit': 'C',
+ 'value': str(self.catalina.get_temperature())
+ }
+
+ def get_rssi_val(self, which):
+ """
+ Return the current RSSI of `which` chain in Catalina
+ """
+ return self.catalina.get_rssi(which)
+
+ def get_rssi_sensor(self, chan):
+ """
+ Return a sensor dictionary containing the current RSSI of `which` chain in Catalina
+ """
+ which = 'RX' + str(chan+1)
+ return {
+ 'name': 'rssi',
+ 'type': 'REALNUM',
+ 'unit': 'dB',
+ 'value': str(self.get_rssi_val(which)),
+ }
+
+ def set_catalina_clock_rate(self, rate):
+ """
+ Async call to catalina set_clock_rate
+ """
+ self.log.trace("Setting Clock rate to {}".format(rate))
+ async_exec(lib.ad9361, "set_clock_rate", self.catalina, rate)
+ return rate
+
+ def catalina_tune(self, which, freq):
+ """
+ Async call to catalina tune
+ """
+ self.log.trace("Tuning {} {}".format(which, freq))
+ async_exec(lib.ad9361, "tune", self.catalina, which, freq)
+ return self.catalina.get_freq(which)
diff --git a/mpm/python/usrp_mpm/dboard_manager/neon.py b/mpm/python/usrp_mpm/dboard_manager/neon.py
index c0eb10eae..00438503d 100644
--- a/mpm/python/usrp_mpm/dboard_manager/neon.py
+++ b/mpm/python/usrp_mpm/dboard_manager/neon.py
@@ -18,7 +18,7 @@ from usrp_mpm.mpmlog import get_logger
from usrp_mpm.sys_utils.udev import get_eeprom_paths
from usrp_mpm.sys_utils.uio import UIO
from usrp_mpm.periph_manager.e320_periphs import MboardRegsControl
-
+from usrp_mpm.mpmutils import async_exec
###############################################################################
# Main dboard control class
@@ -186,7 +186,7 @@ class Neon(DboardManagerBase):
))
# Some default chains on -- needed for setup purposes
self.catalina.set_active_chains(True, False, True, False)
- self.catalina.set_clock_rate(self.master_clock_rate)
+ self.set_catalina_clock_rate(self.master_clock_rate)
return True
@@ -321,3 +321,18 @@ class Neon(DboardManagerBase):
'value': str(self.get_rssi_val(which)),
}
+ def set_catalina_clock_rate(self, rate):
+ """
+ Async call to catalina set_clock_rate
+ """
+ self.log.trace("Setting Clock rate to {}".format(rate))
+ async_exec(lib.ad9361, "set_clock_rate", self.catalina, rate)
+ return rate
+
+ def catalina_tune(self, which, freq):
+ """
+ Async call to catalina tune
+ """
+ self.log.trace("Tuning {} {}".format(which, freq))
+ async_exec(lib.ad9361, "tune", self.catalina, which, freq)
+ return self.catalina.get_freq(which)
diff --git a/mpm/python/usrp_mpm/e31x_legacy_eeprom.py b/mpm/python/usrp_mpm/e31x_legacy_eeprom.py
new file mode 100644
index 000000000..4c0fb19a5
--- /dev/null
+++ b/mpm/python/usrp_mpm/e31x_legacy_eeprom.py
@@ -0,0 +1,103 @@
+#
+# Copyright 2017 Ettus Research, a National Instruments Company
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+"""
+EEPROM management code
+"""
+
+import struct
+import zlib
+from builtins import zip
+from builtins import object
+
+
+class MboardEEPROM(object):
+ """
+ Given a nvmem path, read out EEPROM values from the motherboard's EEPROM.
+ The format of data in the EEPROM must follow the following standard:
+
+ E310 Legacy EEPROM Format
+
+ - 2 bytes data_version_major
+ - 2 bytes data_version_minor
+ - 6 bytes MAC address
+ - 2 bytes hw_pid
+ - 2 bytes hw_rev
+ - 8 bytes serial number (zero-terminated string of 7 characters)
+ - 12 bytes padding
+ - 8 bytes user_name
+
+ MAC addresses are ignored here; they are read elsewhere. If we really need
+ to know the MAC address of an interface, we can fish it out the raw data,
+ or ask the system.
+ """
+
+ # Refer e300_eeprom_manager.hpp.
+ eeprom_header_format = "<H H 6s H H 7s 12s 8s"
+ eeprom_header_keys = ('data_version_major', 'data_version_minor', 'mac_address', 'pid', 'rev', 'serial', 'pad', 'user_name')
+
+class DboardEEPROM(object):
+ """
+ Given a nvmem path, read out EEPROM values from the daughterboard's EEPROM.
+ The format of data in the EEPROM must follow the following standard:
+
+ E310 Legacy EEPROM Format
+
+ - 2 bytes data_version_major
+ - 2 bytes data_version_minor
+ - 2 bytes hw_pid
+ - 2 bytes hw_rev
+ - 8 bytes serial number (zero-terminated string of 7 characters)
+ - 12 bytes padding
+ """
+
+ # Refer e300_eeprom_manager.hpp.
+ eeprom_header_format = "<H H H H 7s 12s"
+ eeprom_header_keys = ('data_version_major', 'data_version_minor', 'pid', 'rev', 'serial', 'pad')
+
+def read_eeprom(
+ isMotherboard,
+ nvmem_path,
+ offset,
+ eeprom_header_format,
+ eeprom_header_keys,
+ max_size=None,
+):
+ """
+ Read the EEPROM located at nvmem_path and return a tuple (header, data)
+ Header is already parsed in the common header fields
+ Data contains the full eeprom data structure
+
+ nvmem_path -- Path to readable file (typically something in sysfs)
+ eeprom_header_format -- List of header formats, by version
+ eeprom_header_keys -- List of keys for the entries in the EEPROM
+ max_size -- Max number of bytes to be read. If omitted, will read the full file.
+ """
+
+ max_size = max_size or -1
+ with open(nvmem_path, "rb") as nvmem_file:
+ data = nvmem_file.read(max_size)[offset:]
+ eeprom_parser = struct.Struct(eeprom_header_format)
+ eeprom_keys = eeprom_header_keys
+ parsed_data = eeprom_parser.unpack_from(data)
+
+ if isMotherboard: # E310 MB.
+ # Rectify the PID and REV parsing. Reverse the bytes.
+ # PID and REV are the 4th and 5th elements in the tuple.
+ parsed_data_list = list(parsed_data)
+ parsed_data_list[3] = struct.unpack("<H", struct.pack(">H", parsed_data_list[3]))[0]
+ parsed_data_list[4] = struct.unpack("<H", struct.pack(">H", parsed_data_list[4]))[0]
+ parsed_data = tuple(parsed_data_list)
+
+ else: # E310 DB.
+ # Rectify the PID and REV parsing. Reverse the bytes.
+ # PID and REV are the 3rd and 4th elements in the tuple.
+ parsed_data_list = list(parsed_data)
+ parsed_data_list[2] = struct.unpack("<H", struct.pack(">H", parsed_data_list[2]))[0]
+ parsed_data_list[3] = struct.unpack("<H", struct.pack(">H", parsed_data_list[3]))[0]
+ parsed_data = tuple(parsed_data_list)
+
+ ret_val = (dict(list(zip(eeprom_keys, parsed_data))),data)
+ return ret_val \ No newline at end of file
diff --git a/mpm/python/usrp_mpm/periph_manager/CMakeLists.txt b/mpm/python/usrp_mpm/periph_manager/CMakeLists.txt
index 994b1d64a..987bc184b 100644
--- a/mpm/python/usrp_mpm/periph_manager/CMakeLists.txt
+++ b/mpm/python/usrp_mpm/periph_manager/CMakeLists.txt
@@ -15,6 +15,8 @@ set(USRP_MPM_PERIPHMGR_FILES
${CMAKE_CURRENT_SOURCE_DIR}/n3xx_periphs.py
${CMAKE_CURRENT_SOURCE_DIR}/e320.py
${CMAKE_CURRENT_SOURCE_DIR}/e320_periphs.py
+ ${CMAKE_CURRENT_SOURCE_DIR}/e31x.py
+ ${CMAKE_CURRENT_SOURCE_DIR}/e31x_periphs.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/base.py b/mpm/python/usrp_mpm/periph_manager/base.py
index 2dc69e908..e297effbd 100644
--- a/mpm/python/usrp_mpm/periph_manager/base.py
+++ b/mpm/python/usrp_mpm/periph_manager/base.py
@@ -131,6 +131,12 @@ class PeriphManagerBase(object):
# specific implementation. Each PeriphManagerBase-derived class should list
# information required to update the component, like a callback function
updateable_components = {}
+ # The RPC server checks this value to determine if it needs to clear
+ # the RPC method registry. This is typically to remove stale references
+ # to RPC methods caused by removal of overlay on unclaim() by peripheral
+ # manager. Additionally the RPC server will re-register all methods on
+ # a claim(). Override and set to True in the derived class if desired.
+ clear_rpc_method_registry_on_unclaim = False
@staticmethod
def generate_device_info(eeprom_md, mboard_info, dboard_infos):
diff --git a/mpm/python/usrp_mpm/periph_manager/e31x.py b/mpm/python/usrp_mpm/periph_manager/e31x.py
new file mode 100644
index 000000000..722f8d7a9
--- /dev/null
+++ b/mpm/python/usrp_mpm/periph_manager/e31x.py
@@ -0,0 +1,744 @@
+#
+# Copyright 2018-2019 Ettus Research, a National Instruments Company
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+"""
+E310 implementation module
+"""
+
+from __future__ import print_function
+import bisect
+import copy
+import re
+import threading
+from six import iteritems, itervalues
+from usrp_mpm.components import ZynqComponents
+from usrp_mpm.dboard_manager import E31x_db
+from usrp_mpm.mpmtypes import SID
+from usrp_mpm.mpmutils import assert_compat_number, str2bool
+from usrp_mpm.periph_manager import PeriphManagerBase
+from usrp_mpm.rpc_server import no_rpc
+from usrp_mpm.sys_utils import dtoverlay
+from usrp_mpm.sys_utils.sysfs_thermal import read_sysfs_sensors_value
+from usrp_mpm.sys_utils.udev import get_spidev_nodes
+from usrp_mpm.xports import XportMgrLiberio
+from usrp_mpm.periph_manager.e31x_periphs import MboardRegsControl
+from usrp_mpm.sys_utils.udev import get_eeprom_paths
+from usrp_mpm import e31x_legacy_eeprom
+
+E310_DEFAULT_CLOCK_SOURCE = 'internal'
+E310_DEFAULT_TIME_SOURCE = 'internal'
+E310_DEFAULT_ENABLE_FPGPIO = True
+E310_FPGA_COMPAT = (1,0)
+E310_DBOARD_SLOT_IDX = 0
+
+###############################################################################
+# Transport managers
+###############################################################################
+
+class E310XportMgrLiberio(XportMgrLiberio):
+ " E310-specific Liberio configuration "
+ max_chan = 4
+ xbar_dev = "/dev/crossbar0"
+ xbar_port = 0
+
+###############################################################################
+# Main Class
+###############################################################################
+class e31x(ZynqComponents, PeriphManagerBase):
+ """
+ Holds E310 specific attributes and methods
+ """
+ #########################################################################
+ # Overridables
+ #
+ # See PeriphManagerBase for documentation on these fields
+ #########################################################################
+ description = "E300-Series Device"
+ # 0x77d2 and 0x77d3
+ pids = {0x77D2: 'e310_sg1', #sg1
+ 0x77D3: 'e310_sg3'} #sg3
+ mboard_eeprom_addr = "e0004000.i2c"
+ mboard_eeprom_offset = 0
+ mboard_eeprom_max_len = 64
+ # We have two nvem paths on the E310.
+ # This one ensures that we get the right path for the MB.
+ mboard_eeprom_path_index = 1
+ mboard_info = {"type": "e3xx"}
+ mboard_sensor_callback_map = {
+ 'ref_locked': 'get_ref_lock_sensor',
+ 'temp_fpga' : 'get_fpga_temp_sensor',
+ 'temp_mb' : 'get_mb_temp_sensor',
+ }
+ dboard_eeprom_addr = "e0004000.i2c"
+
+ # Actual DB EEPROM bytes are just 28. Reading just a couple more.
+ # Refer e300_eeprom_manager.hpp
+ dboard_eeprom_max_len = 32
+
+ max_num_dboards = 1
+
+ # We're on a Zynq target, so the following two come from the Zynq standard
+ # device tree overlay (tree/arch/arm/boot/dts/zynq-7000.dtsi)
+ dboard_spimaster_addrs = ["e0006000.spi"]
+ # E310-specific settings
+ # Label for the mboard UIO
+ mboard_regs_label = "mboard-regs"
+ # Override the list of updateable components
+ updateable_components = {
+ 'fpga': {
+ 'callback': "update_fpga",
+ 'path': '/lib/firmware/{}.bin',
+ 'reset': True,
+ },
+ 'dts': {
+ 'callback': "update_dts",
+ 'path': '/lib/firmware/{}.dts',
+ 'output': '/lib/firmware/{}.dtbo',
+ 'reset': False,
+ },
+ }
+ # This class removes the overlay in tear_down() resulting
+ # in stale references to methods in the RPC server. Setting
+ # this to True ensures that the RPC server clears all registered
+ # methods on unclaim() and registers them on the following claim().
+ clear_rpc_method_registry_on_unclaim = True
+
+ @classmethod
+ def generate_device_info(cls, eeprom_md, mboard_info, dboard_infos):
+ """
+ Generate dictionary describing the device.
+ """
+ # Add the default PeriphManagerBase information first
+ device_info = super().generate_device_info(
+ eeprom_md, mboard_info, dboard_infos)
+ # Then add E31x-specific information
+ mb_pid = eeprom_md.get('pid')
+ device_info['product'] = cls.pids.get(mb_pid, 'unknown')
+ return device_info
+
+ @staticmethod
+ def list_required_dt_overlays(device_info):
+ """
+ Lists device tree overlays that need to be applied before this class can
+ be used. List of strings.
+ Are applied in order.
+
+ eeprom_md -- Dictionary of info read out from the mboard EEPROM
+ device_args -- Arbitrary dictionary of info, typically user-defined
+ """
+ return [device_info['product']]
+
+
+ @staticmethod
+ def get_idle_dt_overlay(device_info):
+ """
+ Overlay to be applied to enter low power idle state.
+ """
+ # e.g. e310_sg3_idle
+ idle_overlay = device_info['product'] + '_idle'
+ return idle_overlay
+
+ ###########################################################################
+ # Ctor and device initialization tasks
+ ###########################################################################
+ def __init__(self, args):
+ """
+ Does partial initialization which loads low power idle image
+ """
+ super(e31x, self).__init__()
+ # Start clean by removing MPM-owned overlays.
+ active_overlays = self.list_active_overlays()
+ mpm_overlays = self.list_owned_overlays()
+ for overlay in active_overlays:
+ if overlay in mpm_overlays:
+ dtoverlay.rm_overlay(overlay)
+ # Apply idle overlay on boot to save power until
+ # an application tries to use the device.
+ self.args_cached = args
+ self.apply_idle_overlay()
+ self._device_initialized = False
+
+ def _init_normal(self):
+ """
+ Does full initialization
+ """
+ if self._device_initialized:
+ return
+ if self.is_idle():
+ self.remove_idle_overlay()
+ self.overlay_apply()
+ self.init_dboards(self.args_cached)
+ if not self._device_initialized:
+ # Don't try and figure out what's going on. Just give up.
+ return
+ # Initialize _do_not_reload with value from _default_args (mpm.conf)
+ self._do_not_reload = str2bool(self._default_args.get("no_reload_fpga", "False"))
+ self._tear_down = False
+ self._clock_source = None
+ self._time_source = None
+ self._available_endpoints = list(range(256))
+ self.dboard = self.dboards[E310_DBOARD_SLOT_IDX]
+ try:
+ self._init_peripherals(self.args_cached)
+ except Exception as ex:
+ self.log.error("Failed to initialize motherboard: %s", str(ex))
+ self._initialization_status = str(ex)
+ self._device_initialized = False
+
+ def _init_dboards(self, dboard_infos, override_dboard_pids, default_args):
+ """
+ Initialize all the daughterboards
+
+ dboard_infos -- List of dictionaries as returned from
+ PeriphManagerBase._get_dboard_eeprom_info()
+ override_dboard_pids -- List of dboard PIDs to force
+ default_args -- Default args
+ """
+ # Override the base class's implementation in order to avoid initializing our one "dboard"
+ # in the same way that, for example, N310's dboards are initialized. Specifically,
+ # - skip dboard EEPROM setup (we don't have one)
+ # - change the way we handle SPI devices
+ if override_dboard_pids:
+ self.log.warning("Overriding daughterboard PIDs with: {}"
+ .format(override_dboard_pids))
+ raise NotImplementedError("Can't override dboard pids")
+ # We have only one dboard
+ dboard_info = dboard_infos[0]
+ # Set up the SPI nodes
+ spi_nodes = []
+ for spi_addr in self.dboard_spimaster_addrs:
+ for spi_node in get_spidev_nodes(spi_addr):
+ bisect.insort(spi_nodes, spi_node)
+
+ self.log.trace("Found spidev nodes: {0}".format(spi_nodes))
+
+ if not spi_nodes:
+ self.log.warning("No SPI nodes for dboard %d.", E310_DBOARD_SLOT_IDX)
+ else:
+ dboard_info.update({
+ 'spi_nodes': spi_nodes,
+ 'default_args': default_args,
+ })
+
+ self.dboards.append(E31x_db(E310_DBOARD_SLOT_IDX, **dboard_info))
+ self.log.info("Found %d daughterboard(s).", len(self.dboards))
+
+ def _check_fpga_compat(self):
+ " Throw an exception if the compat numbers don't match up "
+ actual_compat = self.mboard_regs_control.get_compat_number()
+ self.log.debug("Actual FPGA compat number: {:d}.{:d}".format(
+ actual_compat[0], actual_compat[1]
+ ))
+ assert_compat_number(
+ E310_FPGA_COMPAT,
+ self.mboard_regs_control.get_compat_number(),
+ component="FPGA",
+ fail_on_old_minor=True,
+ log=self.log
+ )
+
+ def _init_ref_clock_and_time(self, default_args):
+ """
+ Initialize clock and time sources. After this function returns, the
+ reference signals going to the FPGA are valid.
+ """
+ if not self.dboards:
+ self.log.warning(
+ "No dboards found, skipping setting clock and time source "
+ "configuration."
+ )
+ self._clock_source = E310_DEFAULT_CLOCK_SOURCE
+ self._time_source = E310_DEFAULT_TIME_SOURCE
+ else:
+ self.set_clock_source(
+ default_args.get('clock_source', E310_DEFAULT_CLOCK_SOURCE)
+ )
+ self.set_time_source(
+ default_args.get('time_source', E310_DEFAULT_TIME_SOURCE)
+ )
+
+ 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 Mboard Regs
+ self.mboard_regs_control = MboardRegsControl(
+ self.mboard_regs_label, self.log)
+ self.mboard_regs_control.get_git_hash()
+ self.mboard_regs_control.get_build_timestamp()
+ self._check_fpga_compat()
+ self._update_fpga_type()
+ self.crossbar_base_port = self.mboard_regs_control.get_xbar_baseport()
+ self.log.debug("crossbar base port: {}".format(self.crossbar_base_port))
+
+ # Init clocking
+ self._init_ref_clock_and_time(args)
+ # Init CHDR transports
+ self._xport_mgrs = {
+ 'liberio': E310XportMgrLiberio(self.log.getChild('liberio')),
+ }
+ # Init complete.
+ self.log.debug("mboard info: {}".format(self.mboard_info))
+
+ def _read_mboard_eeprom(self):
+ """
+ Read out mboard EEPROM.
+ Returns a tuple: (eeprom_dict, eeprom_rawdata), where the the former is
+ a de-serialized dictionary representation of the data, and the latter
+ is a binary string with the raw data.
+
+ If no EEPROM is defined, returns empty values.
+ """
+ if len(self.mboard_eeprom_addr):
+ (eeprom_head, eeprom_rawdata) = e31x_legacy_eeprom.read_eeprom(
+ True, # isMotherboard
+ get_eeprom_paths(self.mboard_eeprom_addr)[self.mboard_eeprom_path_index],
+ self.mboard_eeprom_offset,
+ e31x_legacy_eeprom.MboardEEPROM.eeprom_header_format,
+ e31x_legacy_eeprom.MboardEEPROM.eeprom_header_keys,
+ self.mboard_eeprom_max_len
+ )
+ self.log.trace("Found EEPROM metadata: `{}'"
+ .format(str(eeprom_head)))
+ self.log.trace("Read {} bytes of EEPROM data."
+ .format(len(eeprom_rawdata)))
+ return eeprom_head, eeprom_rawdata
+ # Nothing defined? Return defaults.
+ self.log.trace("No mboard EEPROM path defined. "
+ "Skipping mboard EEPROM readout.")
+ return {}, b''
+
+ def _get_dboard_eeprom_info(self):
+ """
+ Read back EEPROM info from the daughterboards
+ """
+ if self.dboard_eeprom_addr is None:
+ self.log.debug("No dboard EEPROM addresses given.")
+ return []
+ dboard_eeprom_addrs = self.dboard_eeprom_addr \
+ if isinstance(self.dboard_eeprom_addr, list) \
+ else [self.dboard_eeprom_addr]
+ dboard_eeprom_paths = []
+ self.log.trace("Identifying dboard EEPROM paths from addrs `{}'..."
+ .format(",".join(dboard_eeprom_addrs)))
+ for dboard_eeprom_addr in dboard_eeprom_addrs:
+ self.log.trace("Resolving %s...", dboard_eeprom_addr)
+ dboard_eeprom_paths += get_eeprom_paths(dboard_eeprom_addr)
+ self.log.trace("Found dboard EEPROM paths: {}"
+ .format(",".join(dboard_eeprom_paths)))
+ if len(dboard_eeprom_paths) > self.max_num_dboards:
+ self.log.warning("Found more EEPROM paths than daughterboards. "
+ "Ignoring some of them.")
+ dboard_eeprom_paths = dboard_eeprom_paths[:self.max_num_dboards]
+ dboard_info = []
+ for dboard_idx, dboard_eeprom_path in enumerate(dboard_eeprom_paths):
+ self.log.debug("Reading EEPROM info for dboard %d...", dboard_idx)
+ dboard_eeprom_md, dboard_eeprom_rawdata = e31x_legacy_eeprom.read_eeprom(
+ False, # is not motherboard.
+ dboard_eeprom_path,
+ self.dboard_eeprom_offset,
+ e31x_legacy_eeprom.DboardEEPROM.eeprom_header_format,
+ e31x_legacy_eeprom.DboardEEPROM.eeprom_header_keys,
+ self.dboard_eeprom_max_len
+ )
+ self.log.trace("Found dboard EEPROM metadata: `{}'"
+ .format(str(dboard_eeprom_md)))
+ self.log.trace("Read %d bytes of dboard EEPROM data.",
+ len(dboard_eeprom_rawdata))
+ db_pid = dboard_eeprom_md.get('pid')
+ if db_pid is None:
+ self.log.warning("No dboard PID found in dboard EEPROM!")
+ else:
+ self.log.debug("Found dboard PID in EEPROM: 0x{:04X}"
+ .format(db_pid))
+ dboard_info.append({
+ 'eeprom_md': dboard_eeprom_md,
+ 'eeprom_rawdata': dboard_eeprom_rawdata,
+ 'pid': db_pid,
+ })
+ return dboard_info
+
+ ###########################################################################
+ # Session init and deinit
+ ###########################################################################
+ def claim(self):
+ """
+ Fully initializes a device when the rpc_server claim()
+ gets called to revive the device from idle state to be used
+ by an UHD application
+ """
+ super(e31x, self).claim()
+ try:
+ self._init_normal()
+ except Exception as ex:
+ self.log.error("e31x claim() failed: %s", str(ex))
+
+ def init(self, args):
+ """
+ Calls init() on the parent class, and then programs the Ethernet
+ dispatchers accordingly.
+ """
+ if not self._device_initialized:
+ self.log.warning(
+ "Cannot run init(), device was never fully initialized!")
+ return False
+ if args.get("clock_source", "") != "":
+ self.set_clock_source(args.get("clock_source"))
+ if args.get("time_source", "") != "":
+ self.set_time_source(args.get("time_source"))
+ if "no_reload_fpga" in args:
+ self._do_not_reload = str2bool(args.get("no_reload_fpga")) or args.get("no_reload_fpga") == ""
+ result = super(e31x, self).init(args)
+ for xport_mgr in itervalues(self._xport_mgrs):
+ xport_mgr.init(args)
+ return result
+
+
+ def apply_idle_overlay(self):
+ """
+ Load all overlays required to go into idle power savings mode.
+ """
+ idle_overlay = self.get_idle_dt_overlay(self.device_info)
+ self.log.debug("Motherboard requests device tree overlay for Idle power savings mode: {}".format(
+ idle_overlay
+ ))
+ dtoverlay.apply_overlay_safe(idle_overlay)
+
+ def remove_idle_overlay(self):
+ """
+ Remove idle mode overlay.
+ """
+ idle_overlay = self.get_idle_dt_overlay(self.device_info)
+ self.log.trace("Removing Idle overlay: {}".format(
+ idle_overlay
+ ))
+ dtoverlay.rm_overlay(idle_overlay)
+
+ def list_owned_overlays(self):
+ """
+ Lists all overlays that can be possibly applied by MPM.
+ """
+ all_overlays = self.list_required_dt_overlays(self.device_info)
+ all_overlays.append(self.get_idle_dt_overlay(self.device_info))
+ return all_overlays
+
+ def deinit(self):
+ """
+ Clean up after a UHD session terminates.
+ """
+ if not self._device_initialized:
+ self.log.warning(
+ "Cannot run deinit(), device was never fully initialized!")
+ return
+ super(e31x, self).deinit()
+ for xport_mgr in itervalues(self._xport_mgrs):
+ xport_mgr.deinit()
+ self.log.trace("Resetting SID pool...")
+ self._available_endpoints = list(range(256))
+ if not self._do_not_reload:
+ self.tear_down()
+ # Reset back to value from _default_args (mpm.conf)
+ self._do_not_reload = str2bool(self._default_args.get("no_reload_fpga", "False"))
+
+ def tear_down(self):
+ """
+ Tear down all members that need to be specially handled before
+ deconstruction.
+ For E310, this means the overlay.
+ """
+ self.log.trace("Tearing down E310 device...")
+ self._tear_down = True
+ self.dboards = []
+ self.dboard = None
+ self.mboard_regs_control = None
+ self._device_initialized = False
+ active_overlays = self.list_active_overlays()
+ self.log.trace("E310 has active device tree overlays: {}".format(
+ active_overlays
+ ))
+ for overlay in active_overlays:
+ dtoverlay.rm_overlay(overlay)
+ self.apply_idle_overlay()
+
+ def is_idle(self):
+ """
+ Determine if the device is in the idle state.
+ """
+ active_overlays = self.list_active_overlays()
+ idle_overlay = self.get_idle_dt_overlay(self.device_info)
+ is_idle = idle_overlay in active_overlays
+ if is_idle:
+ self.log.trace("Found idle overlay: %s", idle_overlay)
+ return is_idle
+
+ ###########################################################################
+ # Transport API
+ ###########################################################################
+ def request_xport(
+ self,
+ dst_address,
+ suggested_src_address,
+ xport_type
+ ):
+ """
+ See PeriphManagerBase.request_xport() for docs.
+ """
+ # Try suggested address first, then just pick the first available one:
+ src_address = suggested_src_address
+ if src_address not in self._available_endpoints:
+ if not self._available_endpoints:
+ raise RuntimeError(
+ "Depleted pool of SID endpoints for this device!")
+ else:
+ src_address = self._available_endpoints[0]
+ sid = SID(src_address << 16 | dst_address)
+ # Note: This SID may change its source address!
+ self.log.trace(
+ "request_xport(dst=0x%04X, suggested_src_address=0x%04X, xport_type=%s): " \
+ "operating on temporary SID: %s",
+ dst_address, suggested_src_address, str(xport_type), str(sid))
+ assert self.mboard_info['rpc_connection'] in ('local')
+ if self.mboard_info['rpc_connection'] == 'local':
+ return self._xport_mgrs['liberio'].request_xport(
+ sid,
+ xport_type,
+ )
+
+ def commit_xport(self, xport_info):
+ """
+ See PeriphManagerBase.commit_xport() for docs.
+
+ Reminder: All connections are incoming, i.e. "send" or "TX" means
+ remote device to local device, and "receive" or "RX" means this local
+ device to remote device. "Remote device" can be, for example, a UHD
+ session.
+ """
+ ## Go, go, go
+ assert self.mboard_info['rpc_connection'] in ('local')
+ sid = SID(xport_info['send_sid'])
+ self._available_endpoints.remove(sid.src_ep)
+ self.log.debug("Committing transport for SID %s, xport info: %s",
+ str(sid), str(xport_info))
+ if self.mboard_info['rpc_connection'] == 'local':
+ return self._xport_mgrs['liberio'].commit_xport(sid, xport_info)
+
+ ###########################################################################
+ # Device info
+ ###########################################################################
+ def get_device_info_dyn(self):
+ """
+ Append the device info with current IP addresses.
+ """
+ if not self._device_initialized:
+ return {}
+ device_info = {}
+ device_info.update({
+ 'fpga_version': "{}.{}".format(
+ *self.mboard_regs_control.get_compat_number()),
+ 'fpga_version_hash': "{:x}.{}".format(
+ *self.mboard_regs_control.get_git_hash()),
+ 'fpga': self.updateable_components.get('fpga', {}).get('type', ""),
+ })
+ return device_info
+
+ ###########################################################################
+ # Clock/Time API
+ ###########################################################################
+ def get_clock_sources(self):
+ " Lists all available clock sources. "
+ self.log.trace("Listing available clock sources...")
+ return ('internal',)
+
+ def get_clock_source(self):
+ " Returns the currently selected clock source "
+ return self._clock_source
+
+ def set_clock_source(self, *args):
+ """
+ Switch reference clock.
+
+ Throws if clock_source is not a valid value.
+ """
+ clock_source = args[0]
+ assert clock_source in self.get_clock_sources()
+ self.log.debug("Setting clock source to `{}'".format(clock_source))
+ if clock_source == self.get_clock_source():
+ self.log.trace("Nothing to do -- clock source already set.")
+ return
+ self._clock_source = clock_source
+ self.mboard_regs_control.set_clock_source(clock_source)
+
+ def get_time_sources(self):
+ " Returns list of valid time sources "
+ return ['internal', 'external', 'gpsdo']
+
+ def get_time_source(self):
+ " Return the currently selected time source "
+ return self._time_source
+
+ def set_time_source(self, time_source):
+ " Set a time source "
+ assert time_source in self.get_time_sources()
+ if time_source == self.get_time_source():
+ self.log.trace("Nothing to do -- time source already set.")
+ return
+ self._time_source = time_source
+ self.mboard_regs_control.set_time_source(time_source)
+
+ ###########################################################################
+ # Hardware peripheral controls
+ ###########################################################################
+
+ def set_fp_gpio_master(self, value):
+ """set driver for front panel GPIO
+ Arguments:
+ value {unsigned} -- value is a single bit bit mask of 12 pins GPIO
+ """
+ self.mboard_regs_control.set_fp_gpio_master(value)
+
+ def get_fp_gpio_master(self):
+ """get "who" is driving front panel gpio
+ The return value is a bit mask of 8 pins GPIO.
+ 0: means the pin is driven by PL
+ 1: means the pin is driven by PS
+ """
+ return self.mboard_regs_control.get_fp_gpio_master()
+
+ def set_fp_gpio_radio_src(self, value):
+ """set driver for front panel GPIO
+ Arguments:
+ value {unsigned} -- value is 2-bit bit mask of 8 pins GPIO
+ 00: means the pin is driven by radio 0
+ 01: means the pin is driven by radio 1
+ """
+ self.mboard_regs_control.set_fp_gpio_radio_src(value)
+
+ def get_fp_gpio_radio_src(self):
+ """get which radio is driving front panel gpio
+ The return value is 2-bit bit mask of 8 pins GPIO.
+ 00: means the pin is driven by radio 0
+ 01: means the pin is driven by radio 1
+ """
+ return self.mboard_regs_control.get_fp_gpio_radio_src()
+
+ def set_channel_mode(self, channel_mode):
+ "Set channel mode in FPGA and select which tx channel to use"
+ self.mboard_regs_control.set_channel_mode(channel_mode)
+
+ ###########################################################################
+ # Sensors
+ ###########################################################################
+ def get_ref_lock_sensor(self):
+ """
+ #TODO: Where is ref lock signal coming from?
+ """
+ self.log.trace("Querying ref lock status.")
+ lock_status = bool(self.mboard_regs_control.get_refclk_lock())
+ return {
+ 'name': 'ref_locked',
+ 'type': 'BOOLEAN',
+ 'unit': 'locked' if lock_status else 'unlocked',
+ 'value': str(lock_status).lower(),
+ }
+
+ def get_mb_temp_sensor(self):
+ """
+ Get temperature sensor reading of the E310.
+ """
+ self.log.trace("Reading temperature.")
+ temp = '-1'
+ raw_val = {}
+ data_probes = ['temp1_input']
+ try:
+ for data_probe in data_probes:
+ raw_val[data_probe] = read_sysfs_sensors_value('jc-42.4-temp', data_probe, 'hwmon', 'name')[0]
+ temp = str(raw_val['temp1_input'] / 1000)
+ except ValueError:
+ self.log.warning("Error when converting temperature value")
+ except KeyError:
+ self.log.warning("Can't read temp on thermal_zone".format(sensor))
+ return {
+ 'name': 'temp_mb',
+ 'type': 'REALNUM',
+ 'unit': 'C',
+ 'value': temp
+ }
+
+ def get_fpga_temp_sensor(self):
+ """
+ Get temperature sensor reading of the E310.
+ """
+ self.log.trace("Reading temperature.")
+ temp = '-1'
+ raw_val = {}
+ data_probes = ['in_temp0_raw', 'in_temp0_scale', 'in_temp0_offset']
+ try:
+ for data_probe in data_probes:
+ raw_val[data_probe] = read_sysfs_sensors_value('xadc', data_probe, 'iio', 'name')[0]
+ temp = str((raw_val['in_temp0_raw'] + raw_val['in_temp0_offset']) * raw_val['in_temp0_scale'] / 1000)
+ except ValueError:
+ self.log.warning("Error when converting temperature value")
+ except KeyError:
+ self.log.warning("Can't read temp on thermal_zone".format(sensor))
+ return {
+ 'name': 'temp_fpga',
+ 'type': 'REALNUM',
+ 'unit': 'C',
+ 'value': temp
+ }
+
+ ###########################################################################
+ # EEPROMs
+ ###########################################################################
+ def get_mb_eeprom(self):
+ """
+ Return a dictionary with EEPROM contents.
+
+ All key/value pairs are string -> string.
+
+ We don't actually return the EEPROM contents, instead, we return the
+ mboard info again. This filters the EEPROM contents to what we think
+ the user wants to know/see.
+ """
+ return self.mboard_info
+
+ def set_mb_eeprom(self, eeprom_vals):
+ """
+ See PeriphManagerBase.set_mb_eeprom() for docs.
+ """
+ self.log.warn("Called set_mb_eeprom(), but not implemented!")
+ raise NotImplementedError
+
+ def get_db_eeprom(self, dboard_idx):
+ """
+ See PeriphManagerBase.get_db_eeprom() for docs.
+ """
+ if dboard_idx != E310_DBOARD_SLOT_IDX:
+ self.log.warn("Trying to access invalid dboard index {}. "
+ "Using the only dboard.".format(dboard_idx))
+ db_eeprom_data = copy.copy(self.dboard.device_info)
+ return db_eeprom_data
+
+ def set_db_eeprom(self, dboard_idx, eeprom_data):
+ self.log.warn("Called set_db_eeprom(), but not implemented!")
+ raise NotImplementedError
+
+ ###########################################################################
+ # Component updating
+ ###########################################################################
+ # Note: Component updating functions defined by ZynqComponents
+ @no_rpc
+ def _update_fpga_type(self):
+ """Update the fpga type stored in the updateable components"""
+ fpga_type = self.mboard_regs_control.get_fpga_type()
+ self.log.debug("Updating mboard FPGA type info to {}".format(fpga_type))
+ self.updateable_components['fpga']['type'] = fpga_type
diff --git a/mpm/python/usrp_mpm/periph_manager/e31x_periphs.py b/mpm/python/usrp_mpm/periph_manager/e31x_periphs.py
new file mode 100644
index 000000000..0b166e5bc
--- /dev/null
+++ b/mpm/python/usrp_mpm/periph_manager/e31x_periphs.py
@@ -0,0 +1,297 @@
+#
+# Copyright 2018 Ettus Research, a National Instruments Company
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+"""
+E310 peripherals
+"""
+
+import datetime
+import math
+from usrp_mpm.sys_utils.sysfs_gpio import SysFSGPIO, GPIOBank
+from usrp_mpm.sys_utils.uio import UIO
+
+class FrontpanelGPIO(GPIOBank):
+ """
+ Abstraction layer for the front panel GPIO
+ """
+ EMIO_BASE = 54
+ FP_GPIO_OFFSET = 32 # Bit offset within the ps_gpio_* pins
+
+ def __init__(self, ddr):
+ GPIOBank.__init__(
+ self,
+ {'label': 'zynq_gpio'},
+ self.FP_GPIO_OFFSET + self.EMIO_BASE,
+ 0xFF, # use_mask
+ ddr
+ )
+
+class MboardRegsControl(object):
+ """
+ Control the FPGA Motherboard registers
+ """
+ # Motherboard registers
+ MB_COMPAT_NUM = 0x0000
+ MB_DATESTAMP = 0x0004
+ MB_GIT_HASH = 0x0008
+ MB_SCRATCH = 0x000C
+ MB_NUM_CE = 0x0010
+ MB_NUM_IO_CE = 0x0014
+ MB_CLOCK_CTRL = 0x0018
+ MB_XADC_RB = 0x001C
+ MB_BUS_CLK_RATE = 0x0020
+ MB_BUS_COUNTER = 0x0024
+ MB_GPIO_MASTER = 0x0030
+ MB_GPIO_RADIO_SRC = 0x0034
+ MB_GPS_CTRL = 0x0038
+ MB_GPS_STATUS = 0x003C
+ MB_DBOARD_CTRL = 0x0040
+ MB_DBOARD_STATUS = 0x0044
+ MB_XBAR_BASEPORT = 0x0048
+
+ # Bitfield locations for the MB_CLOCK_CTRL register.
+ MB_CLOCK_CTRL_PPS_SEL_INT = 0
+ MB_CLOCK_CTRL_PPS_SEL_EXT = 1
+ MB_CLOCK_CTRL_REF_CLK_LOCKED = 2
+
+ # Bitfield locations for the MB_GPS_CTRL register.
+ #FIXME: Update for E310
+ MB_GPS_CTRL_PWR_EN = 0
+ MB_GPS_CTRL_RST_N = 1
+ MB_GPS_CTRL_INITSURV_N = 2
+
+ # Bitfield locations for the MB_GPS_STATUS register.
+ #FIXME: Update for E310
+ MB_GPS_STATUS_LOCK = 0
+ MB_GPS_STATUS_ALARM = 1
+ MB_GPS_STATUS_PHASELOCK = 2
+ MB_GPS_STATUS_SURVEY = 3
+ MB_GPS_STATUS_WARMUP = 4
+
+ # Bitfield locations for the MB_DBOARD_CTRL register.
+ MB_DBOARD_CTRL_MIMO = 0
+ MB_DBOARD_CTRL_TX_CHAN_SEL = 1
+
+ # Bitfield locations for the MB_DBOARD_STATUS register.
+ MB_DBOARD_STATUS_RX_LOCK = 6
+ MB_DBOARD_STATUS_TX_LOCK = 7
+
+ def __init__(self, label, log):
+ self.log = log
+ self.regs = UIO(
+ label=label,
+ read_only=False
+ )
+ self.poke32 = self.regs.poke32
+ self.peek32 = self.regs.peek32
+
+ def get_compat_number(self):
+ """get FPGA compat number
+
+ This function reads back FPGA compat number.
+ The return is a tuple of
+ 2 numbers: (major compat number, minor compat number )
+ """
+ with self.regs:
+ compat_number = self.peek32(self.MB_COMPAT_NUM)
+ minor = compat_number & 0xff
+ major = (compat_number>>16) & 0xff
+ return (major, minor)
+
+ def set_fp_gpio_master(self, value):
+ """set driver for front panel GPIO
+ Arguments:
+ value {unsigned} -- value is a single bit bit mask of 8 pins GPIO
+ """
+ with self.regs:
+ return self.poke32(self.MB_GPIO_MASTER, value)
+
+ def get_fp_gpio_master(self):
+ """get "who" is driving front panel gpio
+ The return value is a bit mask of 8 pins GPIO.
+ 0: means the pin is driven by PL
+ 1: means the pin is driven by PS
+ """
+ with self.regs:
+ return self.peek32(self.MB_GPIO_MASTER) & 0xfff
+
+ def set_fp_gpio_radio_src(self, value):
+ """set driver for front panel GPIO
+ Arguments:
+ value {unsigned} -- value is 2-bit bit mask of 8 pins GPIO
+ 00: means the pin is driven by radio 0
+ 01: means the pin is driven by radio 1
+ """
+ with self.regs:
+ return self.poke32(self.MB_GPIO_RADIO_SRC, value)
+
+ def get_fp_gpio_radio_src(self):
+ """get which radio is driving front panel gpio
+ The return value is 2-bit bit mask of 8 pins GPIO.
+ 00: means the pin is driven by radio 0
+ 01: means the pin is driven by radio 1
+ """
+ with self.regs:
+ return self.peek32(self.MB_GPIO_RADIO_SRC) & 0xffffff
+
+ def get_build_timestamp(self):
+ """
+ Returns the build date/time for the FPGA image.
+ The return is datetime string with the ISO 8601 format
+ (YYYY-MM-DD HH:MM:SS.mmmmmm)
+ """
+ with self.regs:
+ datestamp_rb = self.peek32(self.MB_DATESTAMP)
+ if datestamp_rb > 0:
+ dt_str = datetime.datetime(
+ year=((datestamp_rb>>17)&0x3F)+2000,
+ month=(datestamp_rb>>23)&0x0F,
+ day=(datestamp_rb>>27)&0x1F,
+ hour=(datestamp_rb>>12)&0x1F,
+ minute=(datestamp_rb>>6)&0x3F,
+ second=((datestamp_rb>>0)&0x3F))
+ self.log.trace("FPGA build timestamp: {}".format(str(dt_str)))
+ return str(dt_str)
+ else:
+ # Compatibility with FPGAs without datestamp capability
+ return ''
+
+ def get_git_hash(self):
+ """
+ Returns the GIT hash for the FPGA build.
+ The return is a tuple of
+ 2 numbers: (short git hash, bool: is the tree dirty?)
+ """
+ with self.regs:
+ git_hash_rb = self.peek32(self.MB_GIT_HASH)
+ git_hash = git_hash_rb & 0x0FFFFFFF
+ tree_dirty = ((git_hash_rb & 0xF0000000) > 0)
+ dirtiness_qualifier = 'dirty' if tree_dirty else 'clean'
+ self.log.trace("FPGA build GIT Hash: {:07x} ({})".format(
+ git_hash, dirtiness_qualifier))
+ return (git_hash, dirtiness_qualifier)
+
+ def set_time_source(self, time_source):
+ """
+ Set time source
+ """
+ pps_sel_val = 0x0
+ if time_source == 'internal':
+ self.log.trace("Setting time source to internal")
+ pps_sel_val = self.MB_CLOCK_CTRL_PPS_SEL_INT
+ elif time_source == 'gpsdo':
+ self.log.debug("Setting time source to gpsdo...")
+ pps_sel_val = self.MB_CLOCK_CTRL_PPS_SEL_GPS
+ elif time_source == 'external':
+ self.log.debug("Setting time source to external...")
+ pps_sel_val = self.MB_CLOCK_CTRL_PPS_SEL_EXT
+ else:
+ assert False, "Cannot set to invalid time source: {}".format(time_source)
+ with self.regs:
+ reg_val = self.peek32(self.MB_CLOCK_CTRL) & 0xFFFFFF90
+ # prevent glitches by writing a cleared value first, then the final value.
+ self.poke32(self.MB_CLOCK_CTRL, reg_val)
+ reg_val = reg_val | (pps_sel_val & 0x6F)
+ self.log.trace("Writing MB_CLOCK_CTRL to 0x{:08X}".format(reg_val))
+ self.poke32(self.MB_CLOCK_CTRL, reg_val)
+
+ def set_clock_source(self, clock_source):
+ """
+ Set clock source
+ """
+ if clock_source == 'internal':
+ self.log.trace("Setting clock source to internal")
+ else:
+ assert False, "Cannot set to invalid clock source: {}".format(clock_source)
+
+ def get_fpga_type(self):
+ """
+ Reads the type of the FPGA image currently loaded
+ Returns a string with the type (SG1, SG3)
+ """
+ #TODO: Add SG1 and SG3?
+ return ""
+
+ def get_gps_status(self):
+ """
+ Get GPS status
+ """
+ mask = 0x1F
+ with self.regs:
+ gps_status = self.peek32(self.MB_GPS_STATUS) & mask
+ return gps_status
+
+ def get_refclk_lock(self):
+ """
+ Check the status of the reference clock in FPGA.
+ """
+ mask = 0b1 << self.MB_CLOCK_CTRL_REF_CLK_LOCKED
+ with self.regs:
+ reg_val = self.peek32(self.MB_CLOCK_CTRL)
+ locked = (reg_val & mask) > 0
+ if not locked:
+ self.log.warning("Reference Clock reporting unlocked. "
+ "MB_CLOCK_CTRL reg: 0x{:08X}".format(reg_val))
+ else:
+ self.log.trace("Reference Clock locked!")
+ return locked
+
+ def set_channel_mode(self, channel_mode):
+ """
+ Set channel mode in FPGA and select which tx channel to use
+ channel mode = "MIMO" for mimo
+ channel mode = "SISO_TX1", "SISO_TX0" for siso tx1, tx0 respectively.
+ """
+ with self.regs:
+ reg_val = self.peek32(self.MB_DBOARD_CTRL)
+ if channel_mode == "MIMO":
+ reg_val = (0b1 << self.MB_DBOARD_CTRL_MIMO)
+ self.log.trace("Setting channel mode in AD9361 interface: {}".format("2R2T" if channel_mode == 2 else "1R1T"))
+ else:
+ # Warn if user tries to set either tx0/tx1 in mimo mode
+ # as both will be set automatically
+ if channel_mode == "SISO_TX1":
+ # in SISO mode, Channel 1
+ reg_val = (0b1 << self.MB_DBOARD_CTRL_TX_CHAN_SEL) | (0b0 << self.MB_DBOARD_CTRL_MIMO)
+ self.log.trace("Setting TX channel in AD9361 interface to: TX1")
+ elif channel_mode == "SISO_TX0":
+ # in SISO mode, Channel 0
+ reg_val = (0b0 << self.MB_DBOARD_CTRL_TX_CHAN_SEL) | (0b0 << self.MB_DBOARD_CTRL_MIMO)
+ self.log.trace("Setting TX channel in AD9361 interface to: TX0")
+ self.log.trace("Writing MB_DBOARD_CTRL to 0x{:08X}".format(reg_val))
+ self.poke32(self.MB_DBOARD_CTRL, reg_val)
+
+ def get_ad9361_tx_lo_lock(self):
+ """
+ Check the status of TX LO lock from CTRL_OUT pins from Catalina
+ """
+ mask = 0b1 << self.MB_DBOARD_STATUS_TX_LOCK
+ with self.regs:
+ reg_val = self.peek32(self.MB_DBOARD_STATUS)
+ locked = (reg_val & mask) > 0
+ if not locked:
+ self.log.warning("TX RF PLL reporting unlocked. ")
+ else:
+ self.log.trace("TX RF PLL locked")
+ return locked
+
+ def get_ad9361_rx_lo_lock(self):
+ """
+ Check the status of RX LO lock from CTRL_OUT pins from Catalina
+ """
+ mask = 0b1 << self.MB_DBOARD_STATUS_RX_LOCK
+ with self.regs:
+ reg_val = self.peek32(self.MB_DBOARD_STATUS)
+ locked = (reg_val & mask) > 0
+ if not locked:
+ self.log.warning("RX RF PLL reporting unlocked. ")
+ else:
+ self.log.trace("RX RF PLL locked")
+ return locked
+
+ def get_xbar_baseport(self):
+ "Get the RFNoC crossbar base port"
+ with self.regs:
+ return self.peek32(self.MB_XBAR_BASEPORT)
diff --git a/mpm/python/usrp_mpm/rpc_server.py b/mpm/python/usrp_mpm/rpc_server.py
index c67bf769b..80b4eb44e 100644
--- a/mpm/python/usrp_mpm/rpc_server.py
+++ b/mpm/python/usrp_mpm/rpc_server.py
@@ -286,6 +286,8 @@ class MPMServer(RPCServer):
self._state.claim_status.value = True
self.periph_manager.claimed = True
self.periph_manager.claim()
+ if self.periph_manager.clear_rpc_method_registry_on_unclaim:
+ self._init_rpc_calls(self.periph_manager)
self._state.lock.release()
self.session_id = session_id + " ({})".format(self.client_host)
self._reset_timer()
@@ -337,6 +339,8 @@ class MPMServer(RPCServer):
self._state.claim_status.value = False
self._state.claim_token.value = b''
self.session_id = None
+ if self.periph_manager.clear_rpc_method_registry_on_unclaim:
+ self.clear_method_registry()
try:
self.periph_manager.claimed = False
self.periph_manager.unclaim()
@@ -451,19 +455,13 @@ class MPMServer(RPCServer):
###########################################################################
# Update components
###########################################################################
- def reset_mgr(self):
+ def clear_method_registry(self):
"""
- Reset the Peripheral Manager for this RPC server.
+ Clear all the methods in the RPC server method cache.
"""
- self.log.info("Resetting peripheral manager.")
- self.periph_manager.tear_down()
- self.periph_manager = None
- self.periph_manager = self._mgr_generator()
- self._init_rpc_calls(self.periph_manager)
# RPCServer caches RPC methods, but that cache is not accessible here
# (because Cython). Re-running `RPCServer.__init__` clears that cache,
- # and allows us to register new RPC methods (which we need to do because
- # we're resetting the PeriphManager).
+ # and allows us to register new RPC methods.
# A note on maintenance: This has been deemed safe through inspection of
# the RPCServer source code. However, this is not typical Python, and
# changes in future versions of RPCServer may cause issues.
@@ -471,6 +469,19 @@ class MPMServer(RPCServer):
pack_params={'use_bin_type': True},
)
+ def reset_mgr(self):
+ """
+ Reset the Peripheral Manager for this RPC server.
+ """
+ self.log.info("Resetting peripheral manager.")
+ self.periph_manager.tear_down()
+ self.periph_manager = None
+ self.periph_manager = self._mgr_generator()
+ self._init_rpc_calls(self.periph_manager)
+ # Clear the method cache in order to remove stale references to
+ # methods from the old peripheral manager (the one before reset)
+ self.clear_method_registry()
+
def update_component(self, token, file_metadata_l, data_l):
""""
Updates the device component files specified by the metadata and data
diff --git a/mpm/tools/CMakeLists.txt b/mpm/tools/CMakeLists.txt
index 05838b897..245720656 100644
--- a/mpm/tools/CMakeLists.txt
+++ b/mpm/tools/CMakeLists.txt
@@ -13,7 +13,7 @@ install(PROGRAMS
set(eeprom_tool_sources)
set(eeprom_tool_libs)
-if(ENABLE_LIBMPM)
+if(ENABLE_LIBMPM AND NOT ENABLE_E300)
message(STATUS "Adding MPM EEPROM tools...")
set(eeprom_tool_libs eeprom.c)
list(APPEND eeprom_tool_sources
@@ -23,7 +23,7 @@ if(ENABLE_LIBMPM)
eeprom-init.c
eeprom-set-flags.c
)
-endif(ENABLE_LIBMPM)
+endif(ENABLE_LIBMPM AND NOT ENABLE_E300)
if(ENABLE_MYKONOS)
message(STATUS "Adding N3XX-specific EEPROM tools...")
set(eeprom_tool_libs eeprom.c)