diff options
| author | Andrej Rode <andrej.rode@ettus.com> | 2017-03-27 18:03:52 -0700 | 
|---|---|---|
| committer | Martin Braun <martin.braun@ettus.com> | 2017-12-22 15:03:45 -0800 | 
| commit | 6a12add1560545438e1bebc05efbafd05aace4f9 (patch) | |
| tree | c94dbbbd4da0c7ef41fc8849f174875a13f0b511 | |
| parent | ba4fad345d7489b40a7dab83842ac21d13c4f460 (diff) | |
| download | uhd-6a12add1560545438e1bebc05efbafd05aace4f9.tar.gz uhd-6a12add1560545438e1bebc05efbafd05aace4f9.tar.bz2 uhd-6a12add1560545438e1bebc05efbafd05aace4f9.zip  | |
mpm: mpm reorganization
35 files changed, 600 insertions, 552 deletions
diff --git a/mpm/include/mpm/CMakeLists.txt b/mpm/include/mpm/CMakeLists.txt index 8d073b622..a4ce92931 100644 --- a/mpm/include/mpm/CMakeLists.txt +++ b/mpm/include/mpm/CMakeLists.txt @@ -15,8 +15,6 @@  # along with this program.  If not, see <http://www.gnu.org/licenses/>.  #  INSTALL(FILES -    net_helper.hpp -    udev_helper.hpp      xbar_iface.hpp      DESTINATION ${INCLUDE_DIR}/mpm  ) diff --git a/mpm/include/mpm/ad937x/CMakeLists.txt b/mpm/include/mpm/ad937x/CMakeLists.txt index 3fe8d1419..834d6759a 100644 --- a/mpm/include/mpm/ad937x/CMakeLists.txt +++ b/mpm/include/mpm/ad937x/CMakeLists.txt @@ -15,6 +15,7 @@  # along with this program.  If not, see <http://www.gnu.org/licenses/>.  #  INSTALL(FILES +    adi_ctrl.hpp      ad937x_ctrl.hpp      DESTINATION ${INCLUDE_DIR}/mpm/mykonos  ) diff --git a/mpm/include/mpm/lmk04828/lmk04828_spi_iface.hpp b/mpm/include/mpm/lmk04828/lmk04828_spi_iface.hpp index fa11029a8..2743977dc 100644 --- a/mpm/include/mpm/lmk04828/lmk04828_spi_iface.hpp +++ b/mpm/include/mpm/lmk04828/lmk04828_spi_iface.hpp @@ -1,3 +1,5 @@ +#pragma once +  #include "lmk04828.hpp"  #include "uhd/types/serial.hpp"  #include <boost/shared_ptr.hpp> diff --git a/mpm/include/mpm/net_helper.hpp b/mpm/include/mpm/net_helper.hpp deleted file mode 100644 index b07e43ccc..000000000 --- a/mpm/include/mpm/net_helper.hpp +++ /dev/null @@ -1,69 +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 <http://www.gnu.org/licenses/>. -// - -#include <map> -#include <vector> -#include <string> - -namespace mpm { -namespace network { - -/*! - * A struct describing a single network interface - */ -using net_iface = struct net_iface { -    /*! MAC address of the interface in the form AABBCCDDEEFF */ -    std::string mac_addr; -    /*! vector of associated IP addresses, contains both IPv4 and IPv6 */ -    std::vector<std::string> ip_addr; -}; - -/*! - * net_ifaces contains a <interfaces name, net_iface> pair - * describing mac address and associated ip addresses for - * each interface - */ -using net_ifaces = std::map<std::string, net_iface>; - -/*! - * Convenience function to get all ip addresses of one MAC address - * \param MAC address in the form AABBCCDDEEFF - * \return vector of strings containing all IP addresses with this MAC address - */ -std::vector<std::string> get_if_addrs(const std::string& mac_addr); - -/*! - * Get information about all interfaces on this system - * \return a map with interface names as keys and the interfaces information as value - */ -net_ifaces get_net_map(); - -/*! - * Pretty print net_ifaces in the style of `ip addr` - * \param interface map net_ifaces to print - */ -void print_net_ifaces(net_ifaces my_ifaces); -} -} - -#ifdef LIBMPM_PYTHON -void export_net_iface(){ -    LIBMPM_BOOST_PREAMBLE("network") -        bp::def("get_if_addrs", &mpm::network::get_if_addrs); -} -#endif - diff --git a/mpm/include/mpm/udev_helper.hpp b/mpm/include/mpm/udev_helper.hpp deleted file mode 100644 index 055a93cee..000000000 --- a/mpm/include/mpm/udev_helper.hpp +++ /dev/null @@ -1,62 +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 <http://www.gnu.org/licenses/>. -// - -#include <libudev.h> -#include <string> -#include <vector> - -namespace mpm { -    /*! -     * The udev_helper class: -     * -     * talks to libudev and holds a udev context. Device enumeration is done -     * once during initialization. -     * On destruction the udev context is unreferenced again. -     */ -    class udev_helper{ -    public: -        udev_helper(); -        ~udev_helper(); -        /*! -         * Return the nvmem device associated with the parent address -         * \param address of the parent platform driver -         * \return a string containing the name of file of the device in /sys -         */ -        std::string get_eeprom(const std::string &address); -        /*! -         * Find spidevices associated with the spi_master -         * \param address of the parent platform driver -         * \return a vector of string containing the device paths is /dev -         */ -        std::vector<std::string> get_spidev_nodes(const std::string &spi_master); - -    private: -        udev *_udev; -        udev_enumerate *_enumerate; -    }; -} - -#ifdef LIBMPM_PYTHON -void export_udev_helper(){ -    LIBMPM_BOOST_PREAMBLE("udev") -    bp::class_<mpm::udev_helper>("udev_helper", bp::init<>()) -        .def("get_eeprom", &mpm::udev_helper::get_eeprom) -        .def("get_spidev_nodes", &mpm::udev_helper::get_spidev_nodes) -    ; -} -#endif - diff --git a/mpm/lib/CMakeLists.txt b/mpm/lib/CMakeLists.txt index 649d944c9..cfdde2d03 100644 --- a/mpm/lib/CMakeLists.txt +++ b/mpm/lib/CMakeLists.txt @@ -25,7 +25,5 @@ ADD_SUBDIRECTORY(mykonos)  ADD_SUBDIRECTORY(lmk04828)  USRP_PERIPHS_ADD_OBJECT(periphs -  net_helper.cpp -  udev_helper.cpp    xbar_iface.cpp    ) diff --git a/mpm/lib/net_helper.cpp b/mpm/lib/net_helper.cpp deleted file mode 100644 index a40fd742d..000000000 --- a/mpm/lib/net_helper.cpp +++ /dev/null @@ -1,118 +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 <http://www.gnu.org/licenses/>. -// - -#include <mpm/net_helper.hpp> -#include <uhd/exception.hpp> -#include <boost/format.hpp> -#include <netdb.h> -#include <ifaddrs.h> -#include <linux/if_link.h> -#include <linux/if_packet.h> -#include <iomanip> -#include <iostream> -#include <string> -#include <sstream> -#include <cstring> -#include <cstdint> - -namespace mpm { -namespace network{ - -template <typename ArrayType> -std::string bytearray_to_string(const ArrayType array[], size_t elements) { -    std::stringstream result; -    for (size_t i = 0; i < elements; i++) { -        result << std::uppercase << std::setfill('0') -               << std::setw(sizeof(array[i]) * -                            2) // always produce 2 hex values for each byte -               << std::hex << +array[i]; // Implicit integer promotion -    } -    return result.str(); -} - -void print_net_ifaces(net_ifaces my_ifaces) { -    /* take in a net_ifaces and pretty print information -       about all detected network interfaces */ -    for (const auto& iface : my_ifaces) { -        std::cout << "interface: " << iface.first << std::endl; -        std::cout << "\tMAC: " << iface.second.mac_addr << std::endl; -        for (const auto& addr : iface.second.ip_addr) { -            std::cout << "\tip address: " << addr << std::endl; -        } -    } -} - -net_ifaces get_net_map() { -    /* Get a map containing a string and a net_iface struct -       to describe all adresses assigned to a interface */ -    struct ifaddrs *ifaddr, *ifa; -    int family, s; -    char host[NI_MAXHOST]; -    net_ifaces net_map; - -    if (getifaddrs(&ifaddr) == -1) { -        throw uhd::system_error(str(boost::format("Error: %s") % strerror(errno))); -    } - -    /* Walk through linked list, maintaining head pointer so we -       can free list later */ - -    for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) { -        if (ifa->ifa_addr == NULL) -            continue; - -        /* Put the interaface name into the map, if it already exists -           we get an iterator to the existing element */ -        auto result = net_map.emplace( -            std::make_pair(std::string(ifa->ifa_name), net_iface())); -        auto current_iface = result.first; - -        family = ifa->ifa_addr->sa_family; -        if (family == AF_INET || family == AF_INET6) { -            s = getnameinfo(ifa->ifa_addr, -                            (family == AF_INET) ? sizeof(struct sockaddr_in) -                                                : sizeof(struct sockaddr_in6), -                            host, NI_MAXHOST, NULL, 0, NI_NUMERICHOST); -            if (s != 0) { -                printf("getnameinfo() failed: %s\n", gai_strerror(s)); -                return net_map; -            } -            current_iface->second.ip_addr.push_back(std::string(host)); -        } else if (family == AF_PACKET && ifa->ifa_data != NULL) { -            struct sockaddr_ll* s = (struct sockaddr_ll*)ifa->ifa_addr; -            uint8_t mac_addr[6]; -            memcpy(&mac_addr, s->sll_addr, 6); -            current_iface->second.mac_addr = bytearray_to_string(mac_addr, 6); -        } -    } -    freeifaddrs(ifaddr); -    return net_map; -} - -std::vector<std::string> get_if_addrs(const std::string& mac_addr) { -    /* Convenience wrapper to return all adresses associated with one -       mac address */ -    net_ifaces my_map = get_net_map(); -    for (const auto& iface : my_map) { // find -        if (iface.second.mac_addr == mac_addr) { -            return iface.second.ip_addr; -        } -    } -    return std::vector<std::string>(); -} -} -} diff --git a/mpm/lib/udev_helper.cpp b/mpm/lib/udev_helper.cpp deleted file mode 100644 index 108b4b07a..000000000 --- a/mpm/lib/udev_helper.cpp +++ /dev/null @@ -1,93 +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 <http://www.gnu.org/licenses/>. -// - -#include "mpm/udev_helper.hpp" -#include <uhd/exception.hpp> -#include <boost/format.hpp> -#include <boost/make_shared.hpp> -#include <boost/crc.hpp> -#include <utility> -#include <iostream> -#include <string> -#include <cstring> -#include <fstream> - -using namespace mpm; - -udev_helper::udev_helper(){ -    _udev = udev_new(); -    if (!_udev) { -        throw uhd::os_error("Failed to create udev!"); -	} -    _enumerate = udev_enumerate_new(_udev); - -} - -udev_helper::~udev_helper(){ -	udev_enumerate_unref(_enumerate); -	udev_unref(_udev); -} -std::string udev_helper::get_eeprom(const std::string &address){ -    udev_list_entry *devices, *dev_list_entry; -	udev_device *dev, *parent; - -    parent = udev_device_new_from_subsystem_sysname(_udev, "platform", address.c_str()); -    if (parent == NULL){ -        return std::string(); -    } -    udev_enumerate_add_match_parent(_enumerate, parent); -    udev_enumerate_add_match_subsystem(_enumerate, "nvmem"); -	udev_enumerate_scan_devices(_enumerate); - -    devices = udev_enumerate_get_list_entry(_enumerate); -    if (devices == NULL){ -        return std::string(); -    } -    udev_list_entry_foreach(dev_list_entry, devices) { -        const char *path = NULL, *sys_path = NULL; -        path  = udev_list_entry_get_name(dev_list_entry); -        dev = udev_device_new_from_syspath(_udev, path); -        sys_path = udev_device_get_syspath(dev); -        udev_device_unref(dev); -        return "/sys" + std::string(sys_path) + "/nvmem"; -    } -    return std::string(); -} - -std::vector<std::string> udev_helper::get_spidev_nodes(const std::string &spi_master){ -    udev_list_entry *devices, *dev_list_entry; -    udev_device *dev, *parent; - -    parent = udev_device_new_from_subsystem_sysname(_udev, "platform", spi_master.c_str()); -    udev_enumerate_add_match_parent(_enumerate, parent); -    udev_enumerate_add_match_subsystem(_enumerate, "spidev"); -    udev_enumerate_scan_devices(_enumerate); - -    devices = udev_enumerate_get_list_entry(_enumerate); -    std::vector<std::string> found_dev_nodes; -    if (devices != NULL){ -        udev_list_entry_foreach(dev_list_entry, devices){ -            const char *path, *dev_node; -            path = udev_list_entry_get_name(dev_list_entry); -            dev = udev_device_new_from_syspath(_udev, path); -            dev_node = udev_device_get_devnode(dev); -            found_dev_nodes.push_back(std::string(dev_node)); -            udev_device_unref(dev); -        } -    } -    return found_dev_nodes; -} diff --git a/mpm/python/CMakeLists.txt b/mpm/python/CMakeLists.txt index 7daa3bbf5..11adef92a 100644 --- a/mpm/python/CMakeLists.txt +++ b/mpm/python/CMakeLists.txt @@ -59,12 +59,8 @@ MESSAGE(STATUS "Boost library directories: ${Boost_LIBRARY_DIRS}")  MESSAGE(STATUS "Boost libraries: ${Boost_LIBRARIES}")  SET(UHD_HOST_ROOT ${CMAKE_SOURCE_DIR}/../host) -LIST(APPEND -  pyusrp_periphs_sources -  pyusrp_periphs.cpp -) -ADD_LIBRARY(pyusrp_periphs SHARED ${pyusrp_periphs_sources}) +ADD_LIBRARY(pyusrp_periphs SHARED pyusrp_periphs.cpp)  TARGET_INCLUDE_DIRECTORIES(pyusrp_periphs PUBLIC      ${PYTHON_INCLUDE_DIRS}      ${CMAKE_SOURCE_DIR}/lib/ diff --git a/mpm/python/lib_helper.cpp b/mpm/python/lib_helper.cpp index 5948628f8..58d4a5336 100644 --- a/mpm/python/lib_helper.cpp +++ b/mpm/python/lib_helper.cpp @@ -32,7 +32,6 @@ void export_helper(){      bp::class_<mpm::udev_helper>("udev_helper", bp::init<>())          .def("get_eeprom", &mpm::udev_helper::get_eeprom)          ; -    bp::def("get_if_addrs", &mpm::network::get_if_addrs);      bp::to_python_converter<std::vector< std::string >, iterable_to_python_list<std::vector< std::string > >, false>();  } diff --git a/mpm/python/pyusrp_periphs.cpp b/mpm/python/pyusrp_periphs.cpp index b111e40ca..9f6378763 100644 --- a/mpm/python/pyusrp_periphs.cpp +++ b/mpm/python/pyusrp_periphs.cpp @@ -27,11 +27,10 @@  //#include "types.hpp"  #include "converters.hpp" -#include "mpm/net_helper.hpp"  #include "mpm/xbar_iface.hpp" -#include "mpm/udev_helper.hpp"  #include "mpm/ad937x/ad937x_ctrl.hpp"  #include "mpm/lmk04828//lmk04828_spi_iface.hpp" +#include "mpm/dboards/magnesium_manager.hpp"  //#include "lib_periphs.hpp"  //#include "dboards.hpp"  #include <boost/noncopyable.hpp> @@ -43,12 +42,10 @@ BOOST_PYTHON_MODULE(libpyusrp_periphs)      bp::object package = bp::scope();      package.attr("__path__") = "libpyusrp_periphs";      export_converter(); -    export_net_iface();      //export_types(); -    export_udev_helper();      //export_spi();      //export_lmk();      export_mykonos();      export_xbar(); -    //export_dboards(); +    export_dboards();  } diff --git a/mpm/python/setup.py.in b/mpm/python/setup.py.in index 2a63d14aa..e09262d44 100755 --- a/mpm/python/setup.py.in +++ b/mpm/python/setup.py.in @@ -40,5 +40,9 @@ setup(name='usrp_mpm',        zip_safe=False,        packages=['usrp_mpm', 'usrp_mpm.periph_manager', 'usrp_mpm.dboard_manager'],        install_requires=[ -          'numpy' -      ]) +          'mprpc', +          'systemd-python', +          'pyroute2', +          'pyudev' +      ], +      tests_require=['pytest', 'pylint']) diff --git a/mpm/python/usrp_hwd.py b/mpm/python/usrp_hwd.py index a0c66f62b..f0e52e7fb 100755 --- a/mpm/python/usrp_hwd.py +++ b/mpm/python/usrp_hwd.py @@ -21,12 +21,13 @@ Main executable for the USRP Hardware Daemon  from __future__ import print_function  from logging import getLogger  from logging import StreamHandler +from systemd.journal import JournalHandler  from logging import DEBUG  from logging import Formatter -import signal +from gevent import signal  import sys  import usrp_mpm as mpm -from usrp_mpm.types import shared_state +from usrp_mpm.types import SharedState  from usrp_mpm.periph_manager import periph_manager  log = getLogger("usrp_mpm") @@ -55,17 +56,29 @@ def main():      """      # Setup logging      log.setLevel(DEBUG) -    ch = StreamHandler() -    ch.setLevel(DEBUG) -    formatter = Formatter('[%(asctime)s] [%(levelname)s] [%(name)s] [%(message)s]') -    ch.setFormatter(formatter) -    log.addHandler(ch) +    handler = StreamHandler() +    journal_handler = JournalHandler(SYSLOG_IDENTIFIER='usrp_hwd') +    handler.setLevel(DEBUG) +    formatter = Formatter('[%(asctime)s] [%(levelname)s] [%(module)s] %(message)s') +    handler.setFormatter(formatter) +    journal_formatter = Formatter('[%(levelname)s] [%(module)s] %(message)s') +    journal_handler.setFormatter(journal_formatter) +    log.addHandler(handler) +    log.addHandler(journal_handler) - -    shared = shared_state() +    shared = SharedState() +    # Create the periph_manager for this device +    # This call will be forwarded to the device specific implementation +    # e.g. in periph_manager/test.py +    # Which implementation is called will be determined during configuration +    # with cmake (-DMPM_DEVICE)      mgr = periph_manager() +    discovery_info = { +        "type": mgr._get_device_info()["type"], +        "serial": mgr._get_device_info()["serial"] +    }      _PROCESSES.append( -        mpm.spawn_discovery_process({'serial': mgr.get_serial()}, shared)) +        mpm.spawn_discovery_process(discovery_info, shared))      _PROCESSES.append(          mpm.spawn_rpc_process(mpm.types.MPM_RPC_PORT, shared, mgr))      signal.signal(signal.SIGTERM, kill_time) diff --git a/mpm/python/usrp_mpm/CMakeLists.txt b/mpm/python/usrp_mpm/CMakeLists.txt index 12bcdac11..1ab48de0a 100644 --- a/mpm/python/usrp_mpm/CMakeLists.txt +++ b/mpm/python/usrp_mpm/CMakeLists.txt @@ -24,10 +24,8 @@ SET(USRP_MPM_TOP_FILES      ${CMAKE_CURRENT_SOURCE_DIR}/types.py      ${CMAKE_CURRENT_SOURCE_DIR}/discovery.py      ${CMAKE_CURRENT_SOURCE_DIR}/rpc_server.py -    ${CMAKE_CURRENT_SOURCE_DIR}/periphs.py      )  LIST(APPEND USRP_MPM_FILES ${USRP_MPM_TOP_FILES})  ADD_SUBDIRECTORY(periph_manager)  ADD_SUBDIRECTORY(dboard_manager) -  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 c7043c70c..7606f33ce 100644 --- a/mpm/python/usrp_mpm/__init__.py +++ b/mpm/python/usrp_mpm/__init__.py @@ -18,8 +18,8 @@  MPM Module  """ -from discovery import spawn_discovery_process -from rpc_server import spawn_rpc_process -import types -import periph_manager -import dboard_manager +from .discovery import spawn_discovery_process +from .rpc_server import spawn_rpc_process +from . import types +from . import periph_manager +from . import dboard_manager diff --git a/mpm/python/usrp_mpm/dboard_manager/CMakeLists.txt b/mpm/python/usrp_mpm/dboard_manager/CMakeLists.txt index b642d506e..9cd65fa08 100644 --- a/mpm/python/usrp_mpm/dboard_manager/CMakeLists.txt +++ b/mpm/python/usrp_mpm/dboard_manager/CMakeLists.txt @@ -22,6 +22,7 @@ SET(USRP_MPM_FILES ${USRP_MPM_FILES})  SET(USRP_MPM_DBMGR_FILES      ${CMAKE_CURRENT_SOURCE_DIR}/__init__.py      ${CMAKE_CURRENT_SOURCE_DIR}/base.py +    ${CMAKE_CURRENT_SOURCE_DIR}/test.py      ${CMAKE_CURRENT_SOURCE_DIR}/magnesium.py      ${CMAKE_CURRENT_SOURCE_DIR}/eiscat.py      ${CMAKE_CURRENT_SOURCE_DIR}/unknown.py diff --git a/mpm/python/usrp_mpm/dboard_manager/__init__.py b/mpm/python/usrp_mpm/dboard_manager/__init__.py index 02746e78f..f21e5258c 100644 --- a/mpm/python/usrp_mpm/dboard_manager/__init__.py +++ b/mpm/python/usrp_mpm/dboard_manager/__init__.py @@ -18,12 +18,12 @@  dboards module __init__.py  """  from .. import libpyusrp_periphs as lib -from magnesium import magnesium -from eiscat import eiscat -from test import test -from unknown import unknown +from .magnesium import magnesium +from .eiscat import eiscat +from .test import test +from .unknown import unknown -hw_pids = { +HW_PIDS = {      eiscat.hw_pid: eiscat,      magnesium.hw_pid: magnesium,  } diff --git a/mpm/python/usrp_mpm/dboard_manager/base.py b/mpm/python/usrp_mpm/dboard_manager/base.py index 257a2424d..37e7dd2b8 100644 --- a/mpm/python/usrp_mpm/dboard_manager/base.py +++ b/mpm/python/usrp_mpm/dboard_manager/base.py @@ -17,22 +17,22 @@  """  dboard base implementation module  """ -from . import lib  import logging  import struct -log = logging.Logger("usrp_mpm.dboards") +LOG = logging.Logger(__name__) -class dboard_manager(object): +class DboardManagerBase(object):      """      Holds shared pointer to wrapped C++ implementation.      Sanitizes arguments before calling C++ functions.      Ties various constants to specific daughterboard class      """      _eeprom = {} -    def __init__(self, eeprom={}): -        self._eeprom = eeprom + +    def __init__(self, eeprom=None): +        self._eeprom = eeprom or {}      def get_serial(self):          return self._eeprom.get("serial", "") diff --git a/mpm/python/usrp_mpm/dboard_manager/eiscat.py b/mpm/python/usrp_mpm/dboard_manager/eiscat.py index f536d307b..101290574 100644 --- a/mpm/python/usrp_mpm/dboard_manager/eiscat.py +++ b/mpm/python/usrp_mpm/dboard_manager/eiscat.py @@ -17,12 +17,12 @@  """  EISCAT rx board implementation module  """ -from base import dboard_manager -from base import lib -from base import log +from . import lib +from .base import DboardManagerBase +from .base import LOG -class eiscat(dboard_manager): +class eiscat(DboardManagerBase):      hw_pid = 3      special_eeprom_addrs = {"special0": "something"} diff --git a/mpm/python/usrp_mpm/dboard_manager/magnesium.py b/mpm/python/usrp_mpm/dboard_manager/magnesium.py index d48768208..f13f1de77 100644 --- a/mpm/python/usrp_mpm/dboard_manager/magnesium.py +++ b/mpm/python/usrp_mpm/dboard_manager/magnesium.py @@ -17,34 +17,52 @@  """  magnesium dboard implementation module  """ -from base import dboard_manager -from base import lib -from base import log +from . import lib +from .base import DboardManagerBase  import struct +from logging import getLogger -class magnesium(dboard_manager): +LOG = getLogger(__name__) + + +class magnesium(DboardManagerBase): +    """ +    Holds all dboard specific information and methods of the magnesium dboard +    """      hw_pid = 2      special_eeprom_addrs = {"special0": "something"}      spi_chipselect = {"0": "lmk", "1": "mykonos", "2": "random"}      spidevs = {} +    lmk = "" +    mykonos = "" +    random = ""      def __init__(self, spi_devices, eeprom_data, *args, **kwargs):          # eeprom_data is a tuple (head_dict, raw_data)          if len(spi_devices) != len(self.spi_chipselect): -            log.error("Expected {0} spi devices, found {1} spi devices".format(len(spi_devices), len(self.spi_chipselect))) +            LOG.error("Expected {0} spi devices, found {1} spi devices".format(len(spi_devices), len(self.spi_chipselect)))              exit(1)          for spi in spi_devices:              device = self.spi_chipselect.get(spi[-1], None)              if self.chipselect is None: -                log.error("Unexpected chipselect {0}".format(spi[-1])) +                LOG.error("Unexpected chipselect {0}".format(spi[-1]))                  exit(1)              setattr(self, device, spi)          super(magnesium, self).__init__(*args, **kwargs)      def init_device(self): -        self._device = lib.db.magnesium(self.lmk, self.mykonos, self.random) +        """ +        Execute necessary init dance to bring up dboard +        """ +        LOG.debug("initialize hardware") + +        self._device = lib.dboards.magnesium( +            self.lmk, self.mykonos, self.random)      def read_eeprom_v1(self, data): +        """ +        read eeprom data version 1 +        """          # magnesium eeprom contains          # nothing          return struct.unpack_from("x", data) diff --git a/mpm/python/usrp_mpm/dboard_manager/test.py b/mpm/python/usrp_mpm/dboard_manager/test.py index b2e422bb4..80299ee41 100644 --- a/mpm/python/usrp_mpm/dboard_manager/test.py +++ b/mpm/python/usrp_mpm/dboard_manager/test.py @@ -17,8 +17,11 @@  """  magnesium dboard implementation module  """ -from base import dboard_manager -from base import log +from . import lib +from .base import DboardManagerBase +from logging import getLogger + +LOG = getLogger(__name__)  class fake_spi(object): @@ -36,7 +39,7 @@ class test_device(object):          return argument -class test(dboard_manager): +class test(DboardManagerBase):      hw_pid = 234      special_eeprom_addrs = {"special0": "something"}      spi_chipselect = {"0": "dev1", "1": "dev2", "2": "dev3"} @@ -51,6 +54,7 @@ class test(dboard_manager):          self.dev3 = "2"      def init_device(self): +        LOG.debug("initialize hardware")          self._device = test_device(self.dev1, self.dev2, self.dev3) diff --git a/mpm/python/usrp_mpm/dboard_manager/unknown.py b/mpm/python/usrp_mpm/dboard_manager/unknown.py index f33bacc46..de2354f3d 100644 --- a/mpm/python/usrp_mpm/dboard_manager/unknown.py +++ b/mpm/python/usrp_mpm/dboard_manager/unknown.py @@ -17,12 +17,13 @@  """  EISCAT rx board implementation module  """ -from base import dboard_manager -from base import lib -from base import log +from .base import DboardManagerBase +from logging import getLogger +LOG = getLogger(__name__) -class unknown(dboard_manager): + +class unknown(DboardManagerBase):      hw_pid = 0      special_eeprom_addrs = {} diff --git a/mpm/python/usrp_mpm/discovery.py b/mpm/python/usrp_mpm/discovery.py index 174866498..595368c76 100644 --- a/mpm/python/usrp_mpm/discovery.py +++ b/mpm/python/usrp_mpm/discovery.py @@ -19,19 +19,19 @@ Code to run the discovery port  """  from __future__ import print_function -import time -import ctypes -from multiprocessing import Process, Value +from logging import getLogger +from multiprocessing import Process  from six import iteritems  import socket -from types import MPM_DISCOVERY_PORT, graceful_exit +from usrp_mpm.types import MPM_DISCOVERY_PORT + +LOG = getLogger(__name__)  RESPONSE_PREAMBLE = "USRP-MPM"  RESPONSE_SEP = ";"  RESPONSE_CLAIMED_KEY = "claimed" -  def spawn_discovery_process(device_info, shared_state):      """      Returns a process that contains the device discovery. @@ -41,9 +41,9 @@ def spawn_discovery_process(device_info, shared_state):                     will be included in the response string.      """      # claim_status = Value(ctypes.c_bool, False) -    p = Process(target=_discovery_process, args=(device_info, shared_state)) -    p.start() -    return p +    proc = Process(target=_discovery_process, args=(device_info, shared_state)) +    proc.start() +    return proc  def _discovery_process(device_info, state): @@ -65,12 +65,22 @@ def _discovery_process(device_info, state):      send_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) -    try: -        while True: -            data, sender = sock.recvfrom(4096) -            if data.strip("\0") == "MPM-DISC": -                send_data = create_response_string() -                send_sock.sendto(send_data, sender) -    except: -        sock.close() -        send_sock.close() +    # try: +    while True: +        data, sender = sock.recvfrom(8000) +        LOG.info("Got poked by: %s", sender[0]) +        if data.strip("\0") == "MPM-DISC": +            LOG.info("Sending discovery response to %s port: %d", +                     sender[0], sender[1]) +            send_data = create_response_string() +            send_sock.sendto(send_data, sender) +        elif data.strip("\0").startswith("MPM-ECHO"): +            LOG.info("Received echo request") +            send_data = data +            send_sock.sendto(send_data, sender) + +    # except Exception as err: +    #     LOG.info("Error: %s", err) +    #     LOG.info("Error type: %s", type(err)) +    #     sock.close() +    #     send_sock.close() diff --git a/mpm/python/usrp_mpm/helper.py b/mpm/python/usrp_mpm/helper.py deleted file mode 100644 index 29864124a..000000000 --- a/mpm/python/usrp_mpm/helper.py +++ /dev/null @@ -1,3 +0,0 @@ - - -class eeprom_helper: diff --git a/mpm/python/usrp_mpm/periph_manager/CMakeLists.txt b/mpm/python/usrp_mpm/periph_manager/CMakeLists.txt index 879ac20c1..f4bc1d1d2 100644 --- a/mpm/python/usrp_mpm/periph_manager/CMakeLists.txt +++ b/mpm/python/usrp_mpm/periph_manager/CMakeLists.txt @@ -23,6 +23,9 @@ SET(USRP_MPM_PERIPHMGR_FILES      ${CMAKE_CURRENT_SOURCE_DIR}/__init__.py.in      ${CMAKE_CURRENT_SOURCE_DIR}/base.py      ${CMAKE_CURRENT_SOURCE_DIR}/n310.py +    ${CMAKE_CURRENT_SOURCE_DIR}/test.py +    ${CMAKE_CURRENT_SOURCE_DIR}/net.py +    ${CMAKE_CURRENT_SOURCE_DIR}/udev.py      ) -LIST(APPEND USRP_MPM_FILES ${USRP_MPM_TOP_FILES}) +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 0956b849e..d8733ba17 100644 --- a/mpm/python/usrp_mpm/periph_manager/__init__.py.in +++ b/mpm/python/usrp_mpm/periph_manager/__init__.py.in @@ -24,6 +24,6 @@ from .. import dboard_manager  from .. import types  try: -    from ${MPM_DEVICE} import ${MPM_DEVICE} as periph_manager +    from .${MPM_DEVICE} import ${MPM_DEVICE} as periph_manager  except ImportError:      raise Exception("Could not import ${MPM_DEVICE}") diff --git a/mpm/python/usrp_mpm/periph_manager/base.py b/mpm/python/usrp_mpm/periph_manager/base.py index f19265a05..c84205a76 100644 --- a/mpm/python/usrp_mpm/periph_manager/base.py +++ b/mpm/python/usrp_mpm/periph_manager/base.py @@ -18,16 +18,24 @@  Mboard implementation base class  """ -import re  import os -from . import lib -from . import types -from . import dboard_manager +from ..types import EEPROM +from .. import dboard_manager +from .udev import get_eeprom +from .udev import get_spidev_nodes +from six import iteritems -class periph_manager(object): +class PeriphManagerBase(object): +    """" +    Base class for all motherboards. Common function and API calls should +    be implemented here. Motherboard specific information can be stored in +    separate motherboard classes derived from this class +    """      # stores discovered device information in dicts +    claimed = False      dboards = {} +    mboard_info = {"type": "unknown"}      mboard_if_addrs = {}      mboard_overlays = {}      # this information has to be provided by @@ -35,41 +43,88 @@ class periph_manager(object):      mboard_eeprom_addr = ""      dboard_eeprom_addrs = {}      dboard_spimaster_addrs = {} +    updateable_components = []      def __init__(self):          # I know my EEPROM address, lets use it -        helper = lib.udev_helper.udev_helper() -        (self._eeprom_head, self._eeprom_rawdata) = types.eeprom().read_eeprom(helper.get_eeprom(self.mboard_eeprom_addr)) +        self.overlays = "" +        (self._eeprom_head, self._eeprom_rawdata) = EEPROM().read_eeprom( +            get_eeprom(self.mboard_eeprom_addr))          self._dboard_eeproms = {}          for dboard_slot, eeprom_addr in self.dboard_eeprom_addrs.iteritems():              spi_devices = []              # I know EEPROM adresses for my dboard slots -            eeprom_data = types.eeprom().read(helper.get_eeprom(eeprom_addr)) +            eeprom_data = EEPROM().read_eeprom(get_eeprom(eeprom_addr))              # I know spidev masters on the dboard slots -            hw_pid = eeprom_data.get("hw_pid", 0) -            if hw_pid in dboards.hw_pids: -                spi_devices = helper.get_spidev_nodes(self.dboard_spimaster_addrs.get(dboard_slot)) -            dboard = dboards.hw_pids.get(hw_pid, dboards.unknown) +            hw_pid = eeprom_data[0].get("hw_pid", 0) +            if hw_pid in dboard_manager.HW_PIDS: +                spi_devices = get_spidev_nodes(self.dboard_spimaster_addrs.get(dboard_slot)) +            dboard = dboard_manager.HW_PIDS.get(hw_pid, dboard_manager.unknown)              self.dboards.update({dboard_slot: dboard(spi_devices, eeprom_data)}) +    def safe_list_updateable_components(self): +        """ +        return list of updateable components +        This method does not require a claim_token in the RPC +        """ +        return self.updateable_components +      def get_overlays(self): +        """ +        get and store the list of available dt overlays +        """          self.mboard_overlays = [] -        for f in os.listdir("/lib/firmware/"): -            if f.endswith(".dtbo"): -                self.mboard_overlays.append(f.strip(".dtbo")) +        for fw_files in os.listdir("/lib/firmware/"): +            if fw_files.endswith(".dtbo"): +                self.mboard_overlays.append(fw_files.strip(".dtbo"))      def check_overlay(self): -        for f in os.listdir("/sys/kernel/device-tree/overlays/"): -            pass #do stuff +        """ +        check which dt overlay is loaded currently +        """ +        for overlay_file in os.listdir("/sys/kernel/device-tree/overlays/"): +            self.overlays = overlay_file + +    def _get_device_info(self): +        """ +        return the mboard_info dict and add a claimed field +        """ +        result = {"claimed": str(self.claimed)} +        result.update(self.mboard_info) +        return result -    def get_serial(self): -        return self._serial +    def get_dboards(self): +        """ +        get a dict with slot: hw_pid for each dboard +        """ +        result = {} +        for slot, dboard in iteritems(self.dboards): +            result.update({slot:dboard.hw_pid}) +        return result      def load_fpga_image(self, target=None): +        """ +        load a new fpga image +        """          pass      def init_device(self, *args, **kwargs): +        """ +        Do the real init on the mboard and all dboards +        """          # Load FPGA          # Init dboards          pass +    def _probe_interface(self, sender_addr): +        """ +        Overload this method in actual device implementation +        """ +        return True + +    def get_interfaces(self): +        """ +        Overload this method in actual device implementation +        """ +        return [] + diff --git a/mpm/python/usrp_mpm/periph_manager/n310.py b/mpm/python/usrp_mpm/periph_manager/n310.py index d1c31540b..1b01ac066 100644 --- a/mpm/python/usrp_mpm/periph_manager/n310.py +++ b/mpm/python/usrp_mpm/periph_manager/n310.py @@ -17,24 +17,55 @@  """  N310 implementation module  """ -from base import periph_manager +from __future__ import print_function  import struct +from .base import PeriphManagerBase +from .net import get_iface_addrs +from .net import byte_to_mac +from .net import get_mac_addr +from logging import getLogger +LOG = getLogger(__name__) -class n310(periph_manager): + +class n310(PeriphManagerBase): +    """ +    Holds N310 specific attributes and methods +    """      hw_pids = "1" +    mboard_type = "n310"      mboard_eeprom_addr = "e0007000.spi:ec@0:i2c-tunnel"      dboard_eeprom_addrs = {"A": "something", "B": "else"}      dboard_spimaster_addrs = {"A": "something", "B": "else"} +    interfaces = {}      def __init__(self, *args, **kwargs):          # First initialize parent class - will populate self._eeprom_head and self._eeprom_rawdata          super(n310, self).__init__(*args, **kwargs) -        data = self.read_eeprom_v1(self._eeprom_rawdata) +        data = self._read_eeprom_v1(self._eeprom_rawdata) +        # mac 0: mgmt port, mac1: sfp0, mac2: sfp1 +        self.interfaces["mgmt"] = { +            "mac_addr": byte_to_mac(data[0]), +            "addrs": get_iface_addrs(byte_to_mac(data[0])) +        } +        self.interfaces["sfp0"] = { +            "mac_addr": byte_to_mac(data[1]), +            "addrs": get_iface_addrs(byte_to_mac(data[1])) +        } +        self.interfaces["sfp1"] = { +            "mac_addr": byte_to_mac(data[2]), +            "addrs": get_iface_addrs(byte_to_mac(data[2])) +        } +        self.mboard_info["serial"] = data[3]  # some format +          print(data)          # if header.get("dataversion", 0) == 1: -    def read_eeprom_v1(self, data): + +    def _read_eeprom_v1(self, data): +        """ +        read eeprom with data version 1 +        """          # data_version contains          # 6 bytes mac_addr0          # 2 bytes pad @@ -44,3 +75,29 @@ class n310(periph_manager):          # 2 bytes pad          # 8 bytes serial          return struct.unpack_from("6s 2x 6s 2x 6s 2x 8s", data) + +    def get_interfaces(self): +        """ +        returns available transport interfaces +        """ +        return [iface for iface in self.interfaces.keys() +                if iface.startswith("sfp")] + +    def get_interface_addrs(self, interface): +        """ +        returns discovered ipv4 addresses for a given interface +        """ +        return self.interfaces.get(interface, {}).get("addrs", []) + +    def _probe_interface(self, sender_addr): +        """ +        Get the MAC address of the sender and store it in the FPGA ARP table +        """ +        mac_addr = get_mac_addr(sender_addr) +        if mac_addr is not None: +            # Do something with mac_address +            return True +        return False + + + diff --git a/mpm/python/usrp_mpm/periph_manager/net.py b/mpm/python/usrp_mpm/periph_manager/net.py new file mode 100644 index 000000000..2df771549 --- /dev/null +++ b/mpm/python/usrp_mpm/periph_manager/net.py @@ -0,0 +1,63 @@ + +# 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 <http://www.gnu.org/licenses/>. +# +""" +N310 implementation module +""" +import itertools +import socket +from pyroute2 import IPRoute +from logging import getLogger + +LOG = getLogger(__name__) + + +def get_iface_addrs(mac_addr): +    """ +    return ipv4 addresses for a given macaddress +    input format: "aa:bb:cc:dd:ee:ff" +    """ +    ip2 = IPRoute() +    # 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 +    """ +    ip2 = IPRoute() +    addrs = ip2.get_neighbours(dst=remote_addr) +    if len(addrs) > 1: +        LOG.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/periph_manager/test.py b/mpm/python/usrp_mpm/periph_manager/test.py index c9cbc1f3f..4daa876cb 100644 --- a/mpm/python/usrp_mpm/periph_manager/test.py +++ b/mpm/python/usrp_mpm/periph_manager/test.py @@ -17,15 +17,19 @@  """  test periph_manager implementation module  """ -from base import periph_manager +from __future__ import print_function +from .base import PeriphManagerBase  from . import dboard_manager  import random  import string -import struct -class test(periph_manager): +class test(PeriphManagerBase): +    """ +    Test periph manager class which fakes out all API calls +    """      hw_pids = "42" +    mboard_info = {"type": "mpm_test"}      mboard_eeprom_addr = None      dboard_eeprom_addrs = {"A": "something", "B": "else"}      dboard_spimaster_addrs = {"A": "something", "B": "else"} @@ -34,27 +38,36 @@ class test(periph_manager):          # First initialize parent class - will populate self._eeprom_head and self._eeprom_rawdata          # super(n310, self).__init__(*args, **kwargs)          # if header.get("dataversion", 0) == 1: -        self._eeprom = self.read_eeprom_fake() -        self._serial = "AABBCCDDEEFF" +        self._eeprom = self._read_eeprom_fake() +        print(self.mboard_info) +        self.mboard_info["serial"] = "AABBCCDDEEFF" +        self.mboard_info["name"] = self._eeprom["name"]          # I'm the test periph_manager, I know I have test dboards attached          self.dboards = { -            "A": dboard_manager.test(self.read_db_eeprom_random()), -            "B": dboard_manager.test(self.read_db_eeprom_random()) +            "A": dboard_manager.test(self._read_db_eeprom_random()), +            "B": dboard_manager.test(self._read_db_eeprom_random())          } -    def read_eeprom_fake(self): +    def _read_eeprom_fake(self): +        """ +        fake eeprom readout function, returns dict with data +        """          fake_eeprom = {              "magic": 42,              "crc": 4242,              "data_version": 42,              "hw_pid": 42, -            "hw_rev": 5 +            "hw_rev": 5, +            "name": "foo"          }          return fake_eeprom -    def read_db_eeprom_random(self): +    def _read_db_eeprom_random(self): +        """ +        fake db eeprom readout function, returns dict with fake dboard data +        """          fake_eeprom = {              "serial": ''.join(                  random.choice("ABCDEF" + string.digits) diff --git a/mpm/python/usrp_mpm/periph_manager/udev.py b/mpm/python/usrp_mpm/periph_manager/udev.py new file mode 100644 index 000000000..014e18ede --- /dev/null +++ b/mpm/python/usrp_mpm/periph_manager/udev.py @@ -0,0 +1,42 @@ +# +# 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 <http://www.gnu.org/licenses/>. +# + +import pyudev + + +def get_eeprom(address): +    """ +    Return EEPROM device path for a given I2C address +    """ +    context = pyudev.Context() +    parent = pyudev.Device.from_name(context, "platform", address) +    paths = [device.dev_node if device.dev_node is not None else device.sys_path +             for device in context.list_devices(parent=parent, subsystem="nvmem")] +    if len(paths) != 1: +        raise Exception("{0} paths to EEPROM found!".format(len(paths))) +    return paths[0] + + +def get_spidev_nodes(spi_master): +    """ +    Return found spidev device paths for a given SPI master +    """ +    context = pyudev.Context() +    parent = pyudev.Device.from_name(context, "platform", spi_master) +    paths = [device.dev_node if device.dev_node is not None else device.sys_path +             for device in context.list_devices(parent=parent, subsystem="spidev")] +    return paths diff --git a/mpm/python/usrp_mpm/periphs.py b/mpm/python/usrp_mpm/periphs.py deleted file mode 100644 index 8c00d8f50..000000000 --- a/mpm/python/usrp_mpm/periphs.py +++ /dev/null @@ -1,38 +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 <http://www.gnu.org/licenses/>. -# -""" - Module -""" - -import libpyusrp_periphs as lib -import logging -import periph_manager -import dboard_manager - - -log = logging.Logger("usrp_mpm.periphs") - -def init_periph_manager(mb_type=None, db_types={}, fpga=None): -    pass -    # Moved to periph_manager/base.py __init__ - - -    # Next steps implemented in periph_manager/derived class -    # -    # 1. Load FPGA image -    # 2. Use motherboard and daughterboard types to load the FPGA image -    # 3. Create periph_manager object wth given mb_type + db_types information diff --git a/mpm/python/usrp_mpm/rpc_server.py b/mpm/python/usrp_mpm/rpc_server.py index ddb588aa6..f712c5c87 100644 --- a/mpm/python/usrp_mpm/rpc_server.py +++ b/mpm/python/usrp_mpm/rpc_server.py @@ -18,84 +18,204 @@  Implemented RPC Servers  """  from __future__ import print_function +from logging import getLogger  from gevent.server import StreamServer -from types import graceful_exit, MPM_RPC_PORT +from gevent.pool import Pool +from gevent import signal +from gevent import spawn_later +from gevent import Greenlet +from gevent import monkey +monkey.patch_all()  from mprpc import RPCServer +from random import choice  from six import iteritems -import time - +from string import ascii_letters, digits +from threading import Timer  from multiprocessing import Process + +LOG = getLogger(__name__) + +  class MPMServer(RPCServer): -    _db_methods = {} -    def __init__(self, state, mgr): +    """ +    Main MPM RPC class which holds the periph_manager object and translates +    RPC calls to appropiate calls in the periph_manager and dboard_managers. + +    Claiming and unclaiming is implemented in python only +    """ +    _db_methods = [] +    _mb_methods = [] + +    def __init__(self, state, mgr, *args, **kwargs):          self._state = state -        # Instead do self.mboard = periphs.init_periph_manager(args...) +        self._timer = Greenlet()          self.periph_manager = mgr -        for db_slot, db in iteritems(mgr.dboards): -            methods = (m for m in dir(db) if not m.startswith('_') and callable(getattr(db, m))) -            for method in methods: -                command_name = 'db_'+ db_slot + '_' + method -                self._add_command(getattr(db,method), command_name) -                db_methods = self._db_methods.get(db_slot, []) -                db_methods.append(command_name) -                self._db_methods.update({db_slot: db_methods}) - -        # When we do init we can just add dboard/periph_manager methods with setattr(self, method) -        # Maybe using partial -        # To remove methods again we also have to remove them from self._methods dict (they're cached) -        super(MPMServer, self).__init__() +        # add public mboard methods without namespace +        self._update_component_commands(mgr, '', '_mb_methods') +        # add public dboard methods in `db_<slot>_` namespace +        for db_slot, dboard in iteritems(mgr.dboards): +            self._update_component_commands(dboard, 'db_' + db_slot + '_', '_db_methods') +        super(MPMServer, self).__init__(*args, **kwargs) + +    def _update_component_commands(self, component, namespace, storage): +        """ +        Detect available methods for an object and add them to the RPC server +        """ +        for method in (m for m in dir(component) +                       if not m.startswith('_') and callable(getattr(component, m))): +            if method.startswith('safe_'): +                command_name = namespace + method.lstrip('safe_') +                self._add_safe_command(getattr(component, method), command_name) +            else: +                command_name = namespace + method +                self._add_command(getattr(component, method), command_name) +            getattr(self, storage).append(command_name) +      def _add_command(self, function, command): -        setattr(self, command, function) +        """ +        Adds a method with the name command to the RPC server +        This command will require an acquired claim on the device +        """ +        LOG.debug("adding command %s pointing to %s", command, function) +        def new_function(token, *args): +            if token[:256] != self._state.claim_token.value: +                return False +            return function(*args) +        new_function.__doc__ = function.__doc__ +        setattr(self, command, new_function) + +    def _add_safe_command(self, function, command): +        """ +        Add a safe method which does not require a claim on the +        device +        """ +        LOG.debug("adding safe command %s pointing to %s", command, function) +        setattr(self, command, function)      def list_methods(self):          """ -        Returns all public methods of this RPC server +        Returns a tuple of public methods and +        corresponding docs of this RPC server          """ -        methods = filter(lambda entry: not entry.startswith('_'), dir(self)) # Return public methods -        methods_with_docs = map(lambda m: (m, getattr(self, m).__doc__), methods) -        return methods_with_docs +        return [(met, getattr(self, met).__doc__) +                for met in dir(self) +                if not met.startswith('_') and callable(getattr(self, met))]      def ping(self, data=None):          """          Take in data as argument and send it back +        This is a safe method which can be called without a claim on the device          """ +        LOG.debug("I was pinged from: %s:%s", self.client_host, self.client_port)          return data -    def claim(self, token): +    def claim(self, sender_id): +        """ +        claim `token` - tries to claim MPM device and provides a human readable sender_id +        This is a safe method which can be called without a claim on the device +        """ +        self._state.lock.acquire() +        if self._state.claim_status.value: +            return "" +        LOG.debug("claiming from: %s", self.client_host) +        self.periph_manager.claimed = True +        self._state.claim_token.value = ''.join(choice(ascii_letters + digits) for _ in range(256)) +        self._state.claim_status.value = True +        self._state.lock.release() +        self.sender_id = sender_id +        self._reset_timer() +        LOG.debug("giving token: %s to host: %s", self._state.claim_token.value, self.client_host) +        return self._state.claim_token.value + +    def reclaim(self, token):          """ -        claim `token` - claims the MPM device with given token +        reclaim a MPM device with a token. This operation will fail +        if the device is claimed and the token doesn't match. +        Or if the device is not claimed at all.          """ +        self._state.lock.acquire()          if self._state.claim_status.value: -            if self._state.claim_token.value == token: +            if self._state.claim_token.value == token[:256]: +                self._state.lock.release() +                LOG.debug("reclaimed from: %s", self.client_host) +                self._reset_timer()                  return True +            self._state.lock.release() +            LOG.debug("reclaim failed from: %s", self.client_host)              return False -        self._state.claim_status.value = True -        self._state.claim_token.value = token -        return True +        LOG.debug("trying to reclaim unclaimed device from: %s", self.client_host) +        return False + + + + +    def _unclaim(self): +        """ +        unconditional unclaim - for internal use +        """ +        LOG.debug("releasing claim") +        self._state.claim_status.value = False +        self._state.claim_token.value = "" +        self.sender_id = None +        self.periph_manager.claimed = False +        self._timer.kill() + +    def _reset_timer(self): +        """ +        reset unclaim timer +        """ +        self._timer.kill() +        self._timer = spawn_later(2.0, self._unclaim)      def unclaim(self, token):          """          unclaim `token` - unclaims the MPM device if it is claimed with this token          """          if self._state.claim_status.value and self._state.claim_token.value == token: -            self._state.claim_status.value = False -            self._state.claim_token.value = "" +            self._unclaim()              return True          return False +    def get_device_info(self): +        """ +        get device information +        This is as safe method which can be called without a claim on the device +        """ +        info = self.periph_manager._get_device_info() +        if self.host in ["127.0.0.1", "::1"]: +            info["connection"] = "local" +        else: +            info["connection"] = "remote" +        return info + +    def probe_interface(self, token): +        """ +        Forwards the call to periph_manager._probe_interface with the client ip addresss +        as argument. Should be used to probe the data interfaces on the device +        """ +        if token[:256] != self._state.claim_token.value: +            return False +        return self.periph_manager._probe_interface(self.host) + + +  def _rpc_server_process(shared_state, port, mgr):      """      Start the RPC server      """ -    server = StreamServer(('0.0.0.0', port), handle=MPMServer(shared_state, mgr)) -    try: -        server.serve_forever() -    except: -        server.close() +    connections = Pool(1000) +    server = StreamServer( +        ('0.0.0.0', port), +        handle=MPMServer(shared_state, mgr), +        spawn=connections) +    # catch signals and stop the stream server +    signal(signal.SIGTERM, lambda *args: server.stop()) +    signal(signal.SIGINT, lambda *args: server.stop()) +    server.serve_forever()  def spawn_rpc_process(state, udp_port, mgr): @@ -103,7 +223,7 @@ def spawn_rpc_process(state, udp_port, mgr):      Returns a process that contains the RPC server      """ -    p_args = [udp_port, state, mgr] -    p = Process(target=_rpc_server_process, args=p_args) -    p.start() -    return p +    proc_args = [udp_port, state, mgr] +    proc = Process(target=_rpc_server_process, args=proc_args) +    proc.start() +    return proc diff --git a/mpm/python/usrp_mpm/types.py b/mpm/python/usrp_mpm/types.py index 31252e0b8..cc8fe8b8d 100644 --- a/mpm/python/usrp_mpm/types.py +++ b/mpm/python/usrp_mpm/types.py @@ -20,7 +20,7 @@ MPM types  import ctypes  from multiprocessing import Value  from multiprocessing import Array -from multiprocessing import Lock +from multiprocessing import RLock  import struct  MPM_RPC_PORT = 49601 @@ -29,18 +29,26 @@ MPM_DISCOVERY_PORT = 49600  MPM_DISCOVERY_MESSAGE = "MPM-DISC" -class graceful_exit(Exception): -    pass +class SharedState(object): +    """ +    Holds information which should be shared between processes +    Usage should be kept to a minimum +    """ - -class shared_state:      def __init__(self): -        self.lock = Lock() -        self.claim_status = Value(ctypes.c_bool, False, lock=self.lock) # lock -        self.claim_token = Array(ctypes.c_char, 32, lock=self.lock) # String with max length of 32 +        self.lock = RLock() +        self.claim_status = Value( +            ctypes.c_bool, +            False, lock=self.lock)  # lock +        self.claim_token = Array( +            ctypes.c_char, 256, +            lock=self.lock)  # String with max length of 256 -class eeprom(object): +class EEPROM(object): +    """ +    Reads out common properties and rawdata out of a nvmem path +    """      # eeprom_header contains:      # 4 bytes magic      # 4 bytes CRC @@ -53,9 +61,13 @@ class eeprom(object):      eeprom_header = struct.Struct("I I H H H 2x")      def read_eeprom(self, nvmem_path): -        with open(nvmem_path, "rb") as f: -            header = f.read(16) -            data = f.read(240) +        """ +        Read the EEPROM located at nvmem_path and return a tuple (header, body) +        Header is already parsed in the common header fields +        """ +        with open(nvmem_path, "rb") as nvmem_file: +            header = nvmem_file.read(16) +            data = nvmem_file.read(240)          header = self.eeprom_header.unpack(header)          header = {              "magic": header[0], diff --git a/mpm/tools/mpm_debug.py b/mpm/tools/mpm_debug.py index 1a97a103c..dff1c78bd 100755 --- a/mpm/tools/mpm_debug.py +++ b/mpm/tools/mpm_debug.py @@ -3,6 +3,7 @@ import socket  from mprpc import RPCClient  import usrp_mpm as mpm  import argparse +import random  def parse_args(): @@ -16,6 +17,7 @@ def parse_args():      rpc_parser.add_argument("arguments", nargs="*")      disc_parser = sub_parsers.add_parser("disc", help="Issue discovery") +    echo_parser = sub_parsers.add_parser("echo", help="Issue UDP echo")      return parser.parse_args() @@ -24,6 +26,7 @@ def rpc(address, port, command, *args):          port = mpm.types.MPM_RPC_PORT      client = RPCClient(address, port)      if args: +        args = [eval(arg.lstrip("=")) if arg.startswith("=") else arg for arg in args]          result = client.call(command, *args)      else:          result = client.call(command) @@ -40,13 +43,31 @@ def discovery(address, port):      sock.settimeout(1.0) # wait max 1 second      while True:          try: -            data, sender = sock.recvfrom(4096) -            print("Received respons from: {}".format(sender[0])) +            data, sender = sock.recvfrom(8000) +            print("Received response from: {}".format(sender[0]))              print("Dicovery data: {}".format(data))          except:              break +def echo(address, port): +    if not port: +        port = mpm.types.MPM_DISCOVERY_PORT +    sock = socket.socket( +        socket.AF_INET, +        socket.SOCK_DGRAM) +    message = "MPM-ECHO" + bytearray(random.getrandbits(8) for _ in xrange(8000-8)) +    sock.sendto(message, (address, port)) +    sock.settimeout(0.05) # wait max 50 ms +    while True: +        try: +            data, sender = sock.recvfrom(9000) +            print("Received response from: {}".format(sender[0])) +            print("Echo data size: {}".format(len(data))) +        except: +            break + +  def main():      args = parse_args()      if args.command == "rpc": @@ -57,6 +78,11 @@ def main():          print(result)      elif args.command == "disc":          discovery(args.address, args.port) +        result = True +    elif args.command == "echo": +        echo(args.address, args.port) +        result = True +    return result  if __name__ == "__main__":  | 
