diff options
32 files changed, 4656 insertions, 26 deletions
diff --git a/firmware/usrp3/n230/n230_burner.py b/firmware/usrp3/n230/n230_burner.py index 3f6bb3fcf..7b9920de7 100755 --- a/firmware/usrp3/n230/n230_burner.py +++ b/firmware/usrp3/n230/n230_burner.py @@ -216,8 +216,8 @@ class ctrl_socket(object):              #Map Xilinx header field to N230 specific ones              if xil_header and xil_header['a'].split(';')[0] == 'n230':                  n230_header['valid'] = True -                n230_header['user-id'] = int(xil_header['a'].split(';')[1].split('=')[1][0:-1], 16) -                n230_header['safe-image'] = n230_header['user-id'] >> 16 == 0x5AFE +                n230_header['user-id'] = int(xil_header['a'].split(';')[1].split('=')[1], 16) +                n230_header['safe-image'] = (n230_header['user-id'] >> 16 == 0x5AFE)                  n230_header['product'] = xil_header['b']                  n230_header['timestamp'] = xil_header['c'] + ' ' + xil_header['d']                  n230_header['filesize'] = xil_header['hl'] + xil_header['bl'] diff --git a/firmware/usrp3/n230/n230_debug.py b/firmware/usrp3/n230/n230_debug.py index 7b9944672..f9ff64ab7 100755 --- a/firmware/usrp3/n230/n230_debug.py +++ b/firmware/usrp3/n230/n230_debug.py @@ -178,6 +178,8 @@ class discovery_socket(object):              while 1:                  try:                      (in_pkt, addr_pair) = self._sock.recvfrom(UDP_MAX_XFER_BYTES) +                    if len(in_pkt) < 20: +                        continue                      (flags, ack_seq, block_size, addr, data) = unpack_fw_command(in_pkt)                      addrs.append(addr_pair[0])                  except socket.error: @@ -189,11 +191,12 @@ class discovery_socket(object):  # Communications class, holds a socket and send/recv routine  ########################################################################  class ctrl_socket(object): -    def __init__(self, addr): +    def __init__(self, addr, port):          self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)          self._sock.settimeout(UDP_TIMEOUT) -        self._sock.connect((addr, N230_FW_COMMS_UDP_PORT)) +        self._sock.connect((addr, port))          self.set_callbacks(lambda *a: None, lambda *a: None) +        self.peek(0)    #Dummy read      def set_callbacks(self, progress_cb, status_cb):          self._progress_cb = progress_cb @@ -209,16 +212,6 @@ class ctrl_socket(object):          self.send(pkt)          return self.recv() -    def read_time_stats(self): -        print -        regs = ['    ingress1','    ingress2','     egress1','     egress1'] -        for reg in range (0, 4): -            print("%s " % regs[reg]), -            data = self.peek64(0xA000 + 32 + (reg*8), fmt='i') -            print("%10d    " % (data)), -            print("%10f uS" % (data * 0.0217)) -        print -      def peek(self, peek_addr, fmt=None):          out_pkt = pack_fw_command(N230_FW_COMMS_CMD_PEEK32|N230_FW_COMMS_FLAGS_ACK, seq(), 1, peek_addr, [0])          in_pkt = self.send_and_recv(out_pkt) @@ -252,7 +245,7 @@ class ctrl_socket(object):                  raise Exception("invalid COE file. Must contain 8192 words!")              self.poke(N230_FW_LOADER_ADDR, 0)    #Load start address              for i in range(0, len(coe_words)): -                self.poke(N230_FW_LOADER_DATA, int(coe_words[i],16), (i<len(coe_words)-1)) +                self.poke(N230_FW_LOADER_DATA, int(coe_words[i],16), (i%10==0) and (i<len(coe_words)-1))                  draw_progress_bar(((i+1)*100)/len(coe_words))              print("\nRebooting...")              out_pkt = pack_fw_command(N230_FW_COMMS_CMD_POKE32, seq(), 1, N230_FW_LOADER_BOOT_DONE_ADDR, [1]) @@ -273,15 +266,15 @@ class ctrl_socket(object):      def read_hash(self):          fpga_hash = self.peek(N230_FPGA_HASH_ADDR) -        fpga_status = "clean" if (fpga_hash & 0xf0000000 == 0x0) else "dirty" +        fpga_status = "clean" if (fpga_hash & 0xf0000000 == 0x0) else "modified"          fw_hash = self.peek(N230_FW_HASH_ADDR) -        fw_status = "clean" if (fw_hash & 0xf0000000 == 0x0) else "dirty" -        print("FPGA Git Hash is: %x (%s)" % (fpga_hash & 0xfffffff, fpga_status)) -        print("Firmware Git Hash is: %x (%s)" % (fw_hash & 0xfffffff, fw_status)) +        fw_status = "clean" if (fw_hash & 0xf0000000 == 0x0) else "modified" +        print("FPGA Version     : %x (%s)" % (fpga_hash & 0xfffffff, fpga_status)) +        print("Firmware Version : %x (%s)" % (fw_hash & 0xfffffff, fw_status))      def is_claimed(self):          claimed = self.peek(0x10008) -        print("Claimed: %s") % ('Yes' if claimed else 'No') +        print("Claimed          : %s") % ('YES' if claimed else 'NO')      def reset_fpga(self):          print("Reseting USRP...") @@ -334,7 +327,6 @@ def get_options():      parser.add_option("--data", type="int",         help="Data for poke", default=None)      parser.add_option("--fw",   type="string",      help="Path to FW image to load", default=None)      parser.add_option("--hash", action="store_true",help="Display FPGA git hash", default=False) -    parser.add_option("--time", action="store_true",help="Display CHDR timestamp Stats", default=False)      parser.add_option("--reset", action="store_true",help="Reset and Reload USRP FPGA.", default=False)      parser.add_option("--jesd204test", action="store_true",help="Test mini-SAS connectors with loopback cable..", default=False) @@ -352,7 +344,7 @@ if __name__=='__main__':              disc_sock = discovery_socket()              for addr in disc_sock.discover():                  print '==== FOUND ' + addr + ' ====' -                ctrl_sock = ctrl_socket(addr) +                ctrl_sock = ctrl_socket(addr, N230_FW_COMMS_UDP_PORT)                  ctrl_sock.read_hash()                  ctrl_sock.is_claimed()              sys.exit() @@ -362,7 +354,7 @@ if __name__=='__main__':      if not options.addr:           raise Exception('No address specified') -    ctrl_sock = ctrl_socket(addr=options.addr) +    ctrl_sock = ctrl_socket(options.addr, N230_FW_COMMS_UDP_PORT)      if options.fw is not None:          file_path = options.fw @@ -388,9 +380,6 @@ if __name__=='__main__':          ctrl_sock.poke(addr,data)          print("POKE[0x%x (%d)] <= 0x%x (%d)" % (addr,addr,data,data)) -    if options.time: -        ctrl_sock.read_time_stats() -      if options.reset:          ctrl_sock.reset_fpga() diff --git a/host/lib/CMakeLists.txt b/host/lib/CMakeLists.txt index 5fb75671d..21a979109 100644 --- a/host/lib/CMakeLists.txt +++ b/host/lib/CMakeLists.txt @@ -81,6 +81,7 @@ LIBUHD_REGISTER_COMPONENT("E300" ENABLE_E300 OFF "ENABLE_LIBUHD" OFF OFF)  LIBUHD_REGISTER_COMPONENT("USRP1" ENABLE_USRP1 ON "ENABLE_LIBUHD;ENABLE_USB" OFF OFF)  LIBUHD_REGISTER_COMPONENT("USRP2" ENABLE_USRP2 ON "ENABLE_LIBUHD" OFF OFF)  LIBUHD_REGISTER_COMPONENT("X300" ENABLE_X300 ON "ENABLE_LIBUHD" OFF OFF) +LIBUHD_REGISTER_COMPONENT("N230" ENABLE_N230 ON "ENABLE_LIBUHD" OFF OFF)  LIBUHD_REGISTER_COMPONENT("OctoClock" ENABLE_OCTOCLOCK ON "ENABLE_LIBUHD" OFF OFF)  ######################################################################## diff --git a/host/lib/usrp/CMakeLists.txt b/host/lib/usrp/CMakeLists.txt index a2b94b01c..695f7f83d 100644 --- a/host/lib/usrp/CMakeLists.txt +++ b/host/lib/usrp/CMakeLists.txt @@ -58,3 +58,4 @@ INCLUDE_SUBDIRECTORY(e100)  INCLUDE_SUBDIRECTORY(e300)  INCLUDE_SUBDIRECTORY(x300)  INCLUDE_SUBDIRECTORY(b200) +INCLUDE_SUBDIRECTORY(n230) diff --git a/host/lib/usrp/common/CMakeLists.txt b/host/lib/usrp/common/CMakeLists.txt index e63a09935..27de9c061 100644 --- a/host/lib/usrp/common/CMakeLists.txt +++ b/host/lib/usrp/common/CMakeLists.txt @@ -39,4 +39,5 @@ LIBUHD_APPEND_SOURCES(      ${CMAKE_CURRENT_SOURCE_DIR}/validate_subdev_spec.cpp      ${CMAKE_CURRENT_SOURCE_DIR}/recv_packet_demuxer.cpp      ${CMAKE_CURRENT_SOURCE_DIR}/fifo_ctrl_excelsior.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/usrp3_fw_ctrl_iface.cpp  ) diff --git a/host/lib/usrp/common/constrained_device_args.hpp b/host/lib/usrp/common/constrained_device_args.hpp new file mode 100644 index 000000000..1bfd1df00 --- /dev/null +++ b/host/lib/usrp/common/constrained_device_args.hpp @@ -0,0 +1,283 @@ +// +// Copyright 2014 Ettus Research LLC +// +// 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/>. +// + +#ifndef INCLUDED_LIBUHD_USRP_COMMON_CONSTRAINED_DEV_ARGS_HPP +#define INCLUDED_LIBUHD_USRP_COMMON_CONSTRAINED_DEV_ARGS_HPP + +#include <uhd/types/device_addr.hpp> +#include <uhd/exception.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/format.hpp> +#include <boost/algorithm/string.hpp> +#include <boost/assign/list_of.hpp> +#include <vector> +#include <string> + +namespace uhd { +namespace usrp { + +    /*! +     * constrained_device_args_t provides a base and utilities to +     * map key=value pairs passed in through the device creation +     * args interface (device_addr_t). +     * +     * Inherit from this class to create typed device specific +     * arguments and use the base class methods to handle parsing +     * the device_addr or any key=value string to populate the args +     * +     * This file contains a library of different types of args the +     * the user can pass in. The library can be extended to support +     * non-intrinsic types by the client. +     * +     */ +    class constrained_device_args_t { +    public: //Types + +        /*! +         * Base argument type. All other arguments inherit from this. +         */ +        class generic_arg { +        public: +            generic_arg(const std::string& key): _key(key) {} +            inline const std::string& key() const { return _key; } +            inline virtual std::string to_string() const = 0; +        private: +            std::string _key; +        }; + +        /*! +         * String argument type. Can be case sensitive or insensitive +         */ +        template<bool case_sensitive> +        class str_arg : public generic_arg { +        public: +            str_arg(const std::string& name, const std::string& default_value) : +                generic_arg(name) { set(default_value); } + +            inline void set(const std::string& value) { +                _value = case_sensitive ? value : boost::algorithm::to_lower_copy(value); +            } +            inline const std::string& get() const { +                return _value; +            } +            inline void parse(const std::string& str_rep) { +                set(str_rep); +            } +            inline virtual std::string to_string() const { +                return key() + "=" + get(); +            } +            inline bool operator==(const std::string& rhs) const { +                return get() == boost::algorithm::to_lower_copy(rhs); +            } +        private: +            std::string _value; +        }; +        typedef str_arg<false>  str_ci_arg; +        typedef str_arg<true>   str_cs_arg; + +        /*! +         * Numeric argument type. The template type data_t allows the +         * client to constrain the type of the number. +         */ +        template<typename data_t> +        class num_arg : public generic_arg { +        public: +            num_arg(const std::string& name, const data_t default_value) : +                generic_arg(name) { set(default_value); } + +            inline void set(const data_t value) { +                _value = value; +            } +            inline const data_t get() const { +                return _value; +            } +            inline void parse(const std::string& str_rep) { +                try { +                    _value = boost::lexical_cast<data_t>(str_rep); +                } catch (std::exception& ex) { +                    throw uhd::value_error(str(boost::format( +                        "Error parsing numeric parameter %s: %s.") % +                        key() % ex.what() +                    )); +                } +            } +            inline virtual std::string to_string() const { +                return key() + "=" + boost::lexical_cast<std::string>(get()); +            } +        private: +            data_t _value; +        }; + +        /*! +         * Enumeration argument type. The template type enum_t allows the +         * client to use their own enum and specify a string mapping for +         * the values of the enum +         * +         * NOTE: The constraint on enum_t is that the values must start with +         * 0 and be sequential +         */ +        template<typename enum_t> +        class enum_arg : public generic_arg { +        public: +            enum_arg( +                const std::string& name, +                const enum_t default_value, +                const std::vector<std::string>& values) : +                    generic_arg(name), _str_values(values) +            { set(default_value); } + +            inline void set(const enum_t value) { +                _value = value; +            } +            inline const enum_t get() const { +                return _value; +            } +            inline void parse(const std::string& str_rep, bool assert_invalid = true) { +                std::string valid_values_str; +                for (size_t i = 0; i < _str_values.size(); i++) { +                    if (boost::algorithm::to_lower_copy(str_rep) == +                        boost::algorithm::to_lower_copy(_str_values[i])) +                    { +                        valid_values_str += ((i==0)?"":", ") + _str_values[i]; +                        set(static_cast<enum_t>(static_cast<int>(i))); +                        return; +                    } +                } +                //If we reach here then, the string enum value was invalid +                if (assert_invalid) { +                    throw uhd::value_error(str(boost::format( +                        "Invalid device arg value: %s=%s (Valid: {%s})") % +                        key() % str_rep % valid_values_str +                    )); +                } +            } +            inline virtual std::string to_string() const { +                size_t index = static_cast<size_t>(static_cast<int>(_value)); +                UHD_ASSERT_THROW(index < _str_values.size()); +                return key() + "=" + _str_values[index]; +            } + +        private: +            enum_t                      _value; +            std::vector<std::string>    _str_values; +        }; + +        /*! +         * Boolean argument type. +         */ +        class bool_arg : public generic_arg { +        public: +            bool_arg(const std::string& name, const bool default_value) : +                generic_arg(name) { set(default_value); } + +            inline void set(const bool value) { +                _value = value; +            } +            inline bool get() const { +                return _value; +            } +            inline void parse(const std::string& str_rep) { +                try { +                    _value = (boost::lexical_cast<int>(str_rep) != 0); +                } catch (std::exception& ex) { +                    if (str_rep.empty()) { +                        //If str_rep is empty then the device_addr was set +                        //without a value which means that the user "set" the flag +                        _value = true; +                    } else if (boost::algorithm::to_lower_copy(str_rep) == "true" || +                        boost::algorithm::to_lower_copy(str_rep) == "yes" || +                        boost::algorithm::to_lower_copy(str_rep) == "y") { +                        _value = true; +                    } else if (boost::algorithm::to_lower_copy(str_rep) == "false" || +                            boost::algorithm::to_lower_copy(str_rep) == "no" || +                            boost::algorithm::to_lower_copy(str_rep) == "n") { +                        _value = false; +                    } else { +                        throw uhd::value_error(str(boost::format( +                            "Error parsing boolean parameter %s: %s.") % +                            key() % ex.what() +                        )); +                    } +                } +            } +            inline virtual std::string to_string() const { +                return key() + "=" + (get() ? "true" : "false"); +            } +        private: +            bool _value; +        }; + +    public: //Methods +        constrained_device_args_t() {} +        virtual ~constrained_device_args_t() {} + +        void parse(const std::string& str_args) { +            device_addr_t dev_args(str_args); +            _parse(dev_args); +        } + +        void parse(const device_addr_t& dev_args) { +            _parse(dev_args); +        } + +        inline virtual std::string to_string() const = 0; + +    protected:  //Methods +        //Override _parse to provide an implementation to parse all +        //client specific device args +        virtual void _parse(const device_addr_t& dev_args) = 0; + +        /*! +         * Utility: Ensure that the value of the device arg is between min and max +         */ +        template<typename num_data_t> +        static inline void _enforce_range(const num_arg<num_data_t>& arg, const num_data_t& min, const num_data_t& max) { +            if (arg.get() > max || arg.get() < min) { +                throw uhd::value_error(str(boost::format( +                    "Invalid device arg value: %s (Minimum: %s, Maximum: %s)") % +                    arg.to_string() % +                    boost::lexical_cast<std::string>(min) % boost::lexical_cast<std::string>(max))); +            } +        } + +        /*! +         * Utility: Ensure that the value of the device arg is is contained in valid_values +         */ +        template<typename arg_t, typename data_t> +        static inline void _enforce_discrete(const arg_t& arg, const std::vector<data_t>& valid_values) { +            bool match = false; +            BOOST_FOREACH(const data_t& val, valid_values) { +                if (val == arg.get()) { +                    match = true; +                    break; +                } +            } +            if (!match) { +                std::string valid_values_str; +                for (size_t i = 0; i < valid_values.size(); i++) { +                    valid_values_str += ((i==0)?"":", ") + boost::lexical_cast<std::string>(valid_values[i]); +                    throw uhd::value_error(str(boost::format( +                        "Invalid device arg value: %s (Valid: {%s})") % +                        arg.to_string() % valid_values_str +                    )); +                } +            } +        } +    }; +}} //namespaces + +#endif /* INCLUDED_LIBUHD_USRP_COMMON_CONSTRAINED_DEV_ARGS_HPP */ diff --git a/host/lib/usrp/common/usrp3_fw_ctrl_iface.cpp b/host/lib/usrp/common/usrp3_fw_ctrl_iface.cpp new file mode 100644 index 000000000..d3f11b9ee --- /dev/null +++ b/host/lib/usrp/common/usrp3_fw_ctrl_iface.cpp @@ -0,0 +1,243 @@ +// +// Copyright 2013 Ettus Research LLC +// +// 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 "usrp3_fw_ctrl_iface.hpp" + +#include "../../../firmware/usrp3/include/fw_comm_protocol.h" +#include <uhd/utils/byteswap.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/exception.hpp> +#include <boost/format.hpp> +#include <boost/asio.hpp> //used for htonl and ntohl +#include <boost/foreach.hpp> + +namespace uhd { namespace usrp { namespace usrp3 { + +//---------------------------------------------------------- +// Factory method +//---------------------------------------------------------- +uhd::wb_iface::sptr usrp3_fw_ctrl_iface::make( +    uhd::transport::udp_simple::sptr udp_xport, +    boost::uint16_t product_id) +{ +    return wb_iface::sptr(new usrp3_fw_ctrl_iface(udp_xport, product_id)); +} + +//---------------------------------------------------------- +// udp_fw_ctrl_iface +//---------------------------------------------------------- + +usrp3_fw_ctrl_iface::usrp3_fw_ctrl_iface( +    uhd::transport::udp_simple::sptr udp_xport, +    boost::uint16_t product_id) : +    _product_id(product_id), _udp_xport(udp_xport), _seq_num(0) +{ +    flush(); +    peek32(0); +} + +usrp3_fw_ctrl_iface::~usrp3_fw_ctrl_iface() +{ +    flush(); +} + +void usrp3_fw_ctrl_iface::flush() +{ +    boost::mutex::scoped_lock lock(_mutex); +    _flush(); +} + +void usrp3_fw_ctrl_iface::poke32(const wb_addr_type addr, const boost::uint32_t data) +{ +    boost::mutex::scoped_lock lock(_mutex); + +    for (size_t i = 1; i <= NUM_RETRIES; i++) { +        try { +            _poke32(addr, data); +            return; +        } catch(const std::exception &ex) { +            const std::string error_msg = str(boost::format( +                "udp fw poke32 failure #%u\n%s") % i % ex.what()); +            UHD_MSG(warning) << error_msg << std::endl; +            if (i == NUM_RETRIES) throw uhd::io_error(error_msg); +        } +    } +} + +boost::uint32_t usrp3_fw_ctrl_iface::peek32(const wb_addr_type addr) +{ +    boost::mutex::scoped_lock lock(_mutex); + +    for (size_t i = 1; i <= NUM_RETRIES; i++) { +        try { +            return _peek32(addr); +        } catch(const std::exception &ex) { +            const std::string error_msg = str(boost::format( +                "udp fw peek32 failure #%u\n%s") % i % ex.what()); +            UHD_MSG(warning) << error_msg << std::endl; +            if (i == NUM_RETRIES) throw uhd::io_error(error_msg); +        } +    } +    return 0; +} + +void usrp3_fw_ctrl_iface::_poke32(const wb_addr_type addr, const boost::uint32_t data) +{ +    //Load request struct +    fw_comm_pkt_t request; +    request.id = uhd::htonx<boost::uint32_t>(FW_COMM_GENERATE_ID(_product_id)); +    request.flags = uhd::htonx<boost::uint32_t>(FW_COMM_FLAGS_ACK | FW_COMM_CMD_POKE32); +    request.sequence = uhd::htonx<boost::uint32_t>(_seq_num++); +    request.addr = uhd::htonx(addr); +    request.data_words = 1; +    request.data[0] = uhd::htonx(data); + +    //Send request +    _flush(); +    _udp_xport->send(boost::asio::buffer(&request, sizeof(request))); + +    //Recv reply +    fw_comm_pkt_t reply; +    const size_t nbytes = _udp_xport->recv(boost::asio::buffer(&reply, sizeof(reply)), 1.0); +    if (nbytes == 0) throw uhd::io_error("udp fw poke32 - reply timed out"); + +    //Sanity checks +    const size_t flags = uhd::ntohx<boost::uint32_t>(reply.flags); +    UHD_ASSERT_THROW(nbytes == sizeof(reply)); +    UHD_ASSERT_THROW(not (flags & FW_COMM_FLAGS_ERROR_MASK)); +    UHD_ASSERT_THROW(flags & FW_COMM_CMD_POKE32); +    UHD_ASSERT_THROW(flags & FW_COMM_FLAGS_ACK); +    UHD_ASSERT_THROW(reply.sequence == request.sequence); +    UHD_ASSERT_THROW(reply.addr == request.addr); +    UHD_ASSERT_THROW(reply.data[0] == request.data[0]); +} + +boost::uint32_t usrp3_fw_ctrl_iface::_peek32(const wb_addr_type addr) +{ +    //Load request struct +    fw_comm_pkt_t request; +    request.id = uhd::htonx<boost::uint32_t>(FW_COMM_GENERATE_ID(_product_id)); +    request.flags = uhd::htonx<boost::uint32_t>(FW_COMM_FLAGS_ACK | FW_COMM_CMD_PEEK32); +    request.sequence = uhd::htonx<boost::uint32_t>(_seq_num++); +    request.addr = uhd::htonx(addr); +    request.data_words = 1; +    request.data[0] = 0; + +    //Send request +    _flush(); +    _udp_xport->send(boost::asio::buffer(&request, sizeof(request))); + +    //Recv reply +    fw_comm_pkt_t reply; +    const size_t nbytes = _udp_xport->recv(boost::asio::buffer(&reply, sizeof(reply)), 1.0); +    if (nbytes == 0) throw uhd::io_error("udp fw peek32 - reply timed out"); + +    //Sanity checks +    const size_t flags = uhd::ntohx<boost::uint32_t>(reply.flags); +    UHD_ASSERT_THROW(nbytes == sizeof(reply)); +    UHD_ASSERT_THROW(not (flags & FW_COMM_FLAGS_ERROR_MASK)); +    UHD_ASSERT_THROW(flags & FW_COMM_CMD_PEEK32); +    UHD_ASSERT_THROW(flags & FW_COMM_FLAGS_ACK); +    UHD_ASSERT_THROW(reply.sequence == request.sequence); +    UHD_ASSERT_THROW(reply.addr == request.addr); + +    //return result! +    return uhd::ntohx<boost::uint32_t>(reply.data[0]); +} + +void usrp3_fw_ctrl_iface::_flush(void) +{ +    char buff[FW_COMM_PROTOCOL_MTU] = {}; +    while (_udp_xport->recv(boost::asio::buffer(buff), 0.0)) { +        /*NOP*/ +    } +} + +std::vector<std::string> usrp3_fw_ctrl_iface::discover_devices( +    const std::string& addr_hint, const std::string& port, +    boost::uint16_t product_id) +{ +    std::vector<std::string> addrs; + +    //Create a UDP transport to communicate: +    //Some devices will cause a throw when opened for a broadcast address. +    //We print and recover so the caller can loop through all bcast addrs. +    uhd::transport::udp_simple::sptr udp_bcast_xport; +    try { +        udp_bcast_xport = uhd::transport::udp_simple::make_broadcast(addr_hint, port); +    } catch(const std::exception &e) { +        UHD_MSG(error) << boost::format("Cannot open UDP transport on %s for discovery\n%s") +        % addr_hint % e.what() << std::endl; +        return addrs; +    } + +    //Send dummy request +    fw_comm_pkt_t request; +    request.id = uhd::htonx<boost::uint32_t>(FW_COMM_GENERATE_ID(product_id)); +    request.flags = uhd::htonx<boost::uint32_t>(FW_COMM_FLAGS_ACK|FW_COMM_CMD_ECHO); +    request.sequence = uhd::htonx<boost::uint32_t>(std::rand()); +    udp_bcast_xport->send(boost::asio::buffer(&request, sizeof(request))); + +    //loop for replies until timeout +    while (true) { +        char buff[FW_COMM_PROTOCOL_MTU] = {}; +        const size_t nbytes = udp_bcast_xport->recv(boost::asio::buffer(buff), 0.050); +        if (nbytes != sizeof(fw_comm_pkt_t)) break; //No more responses or responses are invalid + +        const fw_comm_pkt_t *reply = (const fw_comm_pkt_t *)buff; +        if (request.id       == reply->id && +            request.flags    == reply->flags && +            request.sequence == reply->sequence) +        { +            addrs.push_back(udp_bcast_xport->get_recv_addr()); +        } +    } + +    return addrs; +} + +boost::uint32_t usrp3_fw_ctrl_iface::get_iface_id( +    const std::string& addr, const std::string& port, +    boost::uint16_t product_id) +{ +    uhd::transport::udp_simple::sptr udp_xport = +        uhd::transport::udp_simple::make_connected(addr, port); + +    //Send dummy request +    fw_comm_pkt_t request; +    request.id = uhd::htonx<boost::uint32_t>(FW_COMM_GENERATE_ID(product_id)); +    request.flags = uhd::htonx<boost::uint32_t>(FW_COMM_FLAGS_ACK|FW_COMM_CMD_ECHO); +    request.sequence = uhd::htonx<boost::uint32_t>(std::rand()); +    udp_xport->send(boost::asio::buffer(&request, sizeof(request))); + +    //loop for replies until timeout +    char buff[FW_COMM_PROTOCOL_MTU] = {}; +    const size_t nbytes = udp_xport->recv(boost::asio::buffer(buff), 1.0); + +    const fw_comm_pkt_t *reply = (const fw_comm_pkt_t *)buff; +    if (nbytes            >  0 && +        request.id        == reply->id && +        request.flags     == reply->flags && +        request.sequence  == reply->sequence) +    { +        return uhd::ntohx<boost::uint32_t>(reply->data[0]); +    } else { +        throw uhd::io_error("udp get_iface_id - bad response"); +    } +} + +}}} //namespace diff --git a/host/lib/usrp/common/usrp3_fw_ctrl_iface.hpp b/host/lib/usrp/common/usrp3_fw_ctrl_iface.hpp new file mode 100644 index 000000000..3617f3083 --- /dev/null +++ b/host/lib/usrp/common/usrp3_fw_ctrl_iface.hpp @@ -0,0 +1,69 @@ +// +// Copyright 2014 Ettus Research LLC +// +// 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/>. +// + +#ifndef INCLUDED_LIBUHD_USRP_USRP3_UDP_FW_CTRL_IFACE_HPP +#define INCLUDED_LIBUHD_USRP_USRP3_UDP_FW_CTRL_IFACE_HPP + +#include <uhd/types/wb_iface.hpp> +#include <uhd/transport/udp_simple.hpp> +#include <boost/thread/mutex.hpp> +#include <vector> + +namespace uhd { namespace usrp { namespace usrp3 { + +class usrp3_fw_ctrl_iface : public uhd::wb_iface +{ +public: +    usrp3_fw_ctrl_iface( +        uhd::transport::udp_simple::sptr udp_xport, +        boost::uint16_t product_id); +    virtual ~usrp3_fw_ctrl_iface(); + +    // -- uhd::wb_iface -- +    void poke32(const wb_addr_type addr, const boost::uint32_t data); +    boost::uint32_t peek32(const wb_addr_type addr); +    void flush(); + +    static uhd::wb_iface::sptr make( +        uhd::transport::udp_simple::sptr udp_xport, +        boost::uint16_t product_id); +    // -- uhd::wb_iface -- + +    static std::vector<std::string> discover_devices( +        const std::string& addr_hint, const std::string& port, +        boost::uint16_t product_id); + +    static boost::uint32_t get_iface_id( +        const std::string& addr, const std::string& port, +        boost::uint16_t product_id); + +private: +    void _poke32(const wb_addr_type addr, const boost::uint32_t data); +    boost::uint32_t _peek32(const wb_addr_type addr); +    void _flush(void); + +    boost::uint16_t                     _product_id; +    uhd::transport::udp_simple::sptr    _udp_xport; +    boost::uint32_t                     _seq_num; +    boost::mutex                        _mutex; + +    static const size_t NUM_RETRIES = 3; +}; + +}}} //namespace + +#endif //INCLUDED_LIBUHD_USRP_USRP3_USRP3_UDP_FW_CTRL_HPP diff --git a/host/lib/usrp/cores/CMakeLists.txt b/host/lib/usrp/cores/CMakeLists.txt index 7a0f6cc93..720f1d957 100644 --- a/host/lib/usrp/cores/CMakeLists.txt +++ b/host/lib/usrp/cores/CMakeLists.txt @@ -42,4 +42,5 @@ LIBUHD_APPEND_SOURCES(      ${CMAKE_CURRENT_SOURCE_DIR}/radio_ctrl_core_3000.cpp      ${CMAKE_CURRENT_SOURCE_DIR}/gpio_atr_3000.cpp      ${CMAKE_CURRENT_SOURCE_DIR}/dma_fifo_core_3000.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/user_settings_core_3000.cpp  ) diff --git a/host/lib/usrp/cores/user_settings_core_3000.cpp b/host/lib/usrp/cores/user_settings_core_3000.cpp new file mode 100644 index 000000000..549264f57 --- /dev/null +++ b/host/lib/usrp/cores/user_settings_core_3000.cpp @@ -0,0 +1,85 @@ +// +// Copyright 2012 Ettus Research LLC +// +// 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 "user_settings_core_3000.hpp" +#include <uhd/exception.hpp> +#include <boost/thread/thread.hpp> + +using namespace uhd; + +#define REG_USER_SR_ADDR   _sr_base_addr + 0 +#define REG_USER_SR_DATA   _sr_base_addr + 4 +#define REG_USER_RB_ADDR   _sr_base_addr + 8 + +class user_settings_core_3000_impl : public user_settings_core_3000 { +public: +    user_settings_core_3000_impl( +        wb_iface::sptr iface, +        const wb_addr_type sr_base_addr, const wb_addr_type rb_reg_addr): +        _iface(iface), _sr_base_addr(sr_base_addr), _rb_reg_addr(rb_reg_addr) +    { +    } + +    void poke64(const wb_addr_type offset, const boost::uint64_t value) +    { +        if (offset % sizeof(boost::uint64_t) != 0) throw uhd::value_error("poke64: Incorrect address alignment"); +        poke32(offset, static_cast<boost::uint32_t>(value)); +        poke32(offset + 4, static_cast<boost::uint32_t>(value >> 32)); +    } + +    boost::uint64_t peek64(const wb_addr_type offset) +    { +        if (offset % sizeof(boost::uint64_t) != 0) throw uhd::value_error("peek64: Incorrect address alignment"); + +        boost::unique_lock<boost::mutex> lock(_mutex); +        _iface->poke32(REG_USER_RB_ADDR, offset >> 3);  //Translate byte offset to 64-bit offset +        return _iface->peek64(_rb_reg_addr); +    } + +    void poke32(const wb_addr_type offset, const boost::uint32_t value) +    { +        if (offset % sizeof(boost::uint32_t) != 0) throw uhd::value_error("poke32: Incorrect address alignment"); + +        boost::unique_lock<boost::mutex> lock(_mutex); +        _iface->poke32(REG_USER_SR_ADDR, offset >> 2);   //Translate byte offset to 64-bit offset +        _iface->poke32(REG_USER_SR_DATA, value); +    } + +    boost::uint32_t peek32(const wb_addr_type offset) +    { +        if (offset % sizeof(boost::uint32_t) != 0) throw uhd::value_error("peek32: Incorrect address alignment"); + +        boost::uint64_t value = peek64((offset >> 3) << 3); +        if ((offset & 0x7) == 0) { +            return static_cast<boost::uint32_t>(value); +        } else { +            return static_cast<boost::uint32_t>(value >> 32); +        } +    } + +private: +    wb_iface::sptr      _iface; +    const wb_addr_type  _sr_base_addr; +    const wb_addr_type  _rb_reg_addr; +    boost::mutex        _mutex; +}; + +wb_iface::sptr user_settings_core_3000::make(wb_iface::sptr iface, +    const wb_addr_type sr_base_addr, const wb_addr_type rb_reg_addr) +{ +    return sptr(new user_settings_core_3000_impl(iface, sr_base_addr, rb_reg_addr)); +} diff --git a/host/lib/usrp/cores/user_settings_core_3000.hpp b/host/lib/usrp/cores/user_settings_core_3000.hpp new file mode 100644 index 000000000..6891b9e81 --- /dev/null +++ b/host/lib/usrp/cores/user_settings_core_3000.hpp @@ -0,0 +1,35 @@ +// +// Copyright 2012 Ettus Research LLC +// +// 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/>. +// + +#ifndef INCLUDED_LIBUHD_USRP_USER_SETTINGS_CORE_3000_HPP +#define INCLUDED_LIBUHD_USRP_USER_SETTINGS_CORE_3000_HPP + +#include <uhd/config.hpp> +#include <boost/utility.hpp> +#include <boost/shared_ptr.hpp> +#include <uhd/types/wb_iface.hpp> + +class user_settings_core_3000 : public uhd::wb_iface { +public: +    virtual ~user_settings_core_3000() {} + +    static sptr make( +        wb_iface::sptr iface, +        const wb_addr_type sr_base_addr, const wb_addr_type rb_reg_addr); +}; + +#endif /* INCLUDED_LIBUHD_USRP_USER_SETTINGS_CORE_3000_HPP */ diff --git a/host/lib/usrp/n230/CMakeLists.txt b/host/lib/usrp/n230/CMakeLists.txt new file mode 100644 index 000000000..9eaccffba --- /dev/null +++ b/host/lib/usrp/n230/CMakeLists.txt @@ -0,0 +1,37 @@ +# +# Copyright 2013 Ettus Research LLC +# +# 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/>. +# + +######################################################################## +# This file included, use CMake directory variables +######################################################################## + +######################################################################## +# Conditionally configure the N230 support +######################################################################## +IF(ENABLE_N230) +    LIBUHD_APPEND_SOURCES( +        ${CMAKE_CURRENT_SOURCE_DIR}/n230_cores.cpp +        ${CMAKE_CURRENT_SOURCE_DIR}/n230_impl.cpp +        ${CMAKE_CURRENT_SOURCE_DIR}/n230_resource_manager.cpp +        ${CMAKE_CURRENT_SOURCE_DIR}/n230_eeprom_manager.cpp +        ${CMAKE_CURRENT_SOURCE_DIR}/n230_stream_manager.cpp +        ${CMAKE_CURRENT_SOURCE_DIR}/n230_clk_pps_ctrl.cpp +        ${CMAKE_CURRENT_SOURCE_DIR}/n230_frontend_ctrl.cpp +        ${CMAKE_CURRENT_SOURCE_DIR}/n230_uart.cpp +        ${CMAKE_CURRENT_SOURCE_DIR}/n230_image_loader.cpp +   ) +ENDIF(ENABLE_N230) diff --git a/host/lib/usrp/n230/n230_clk_pps_ctrl.cpp b/host/lib/usrp/n230/n230_clk_pps_ctrl.cpp new file mode 100644 index 000000000..a36fa133d --- /dev/null +++ b/host/lib/usrp/n230/n230_clk_pps_ctrl.cpp @@ -0,0 +1,158 @@ +// +// Copyright 2013-2014 Ettus Research LLC +// +// 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 "n230_clk_pps_ctrl.hpp" + +#include <uhd/utils/msg.hpp> +#include <uhd/utils/safe_call.hpp> +#include <boost/cstdint.hpp> +#include <boost/format.hpp> +#include <boost/foreach.hpp> +#include <stdexcept> +#include <cmath> +#include <cstdlib> + +namespace uhd { namespace usrp { namespace n230 { + +class n230_clk_pps_ctrl_impl : public n230_clk_pps_ctrl +{ +public: +    n230_clk_pps_ctrl_impl( +        ad9361_ctrl::sptr codec_ctrl, +        n230_ref_pll_ctrl::sptr ref_pll_ctrl, +        fpga::core_misc_reg_t& core_misc_reg, +        fpga::core_pps_sel_reg_t& core_pps_sel, +        fpga::core_radio_status_reg_t& core_status_reg, +        const std::vector<time_core_3000::sptr>& time_cores +    ): _codec_ctrl(codec_ctrl), +       _ref_pll_ctrl(ref_pll_ctrl), +       _core_misc_reg(core_misc_reg), +       _core_pps_sel_reg(core_pps_sel), +       _core_status_reg(core_status_reg), +       _time_cores(time_cores), +       _tick_rate(0.0), +       _clock_source("<undefined>"), +       _time_source("<undefined>") +    { +    } + +    virtual ~n230_clk_pps_ctrl_impl() +    { +    } + +    double set_tick_rate(const double rate) +    { +        UHD_MSG(status) << "Configuring a tick rate of " << rate/1e6 << " MHz... "; +        _tick_rate = _codec_ctrl->set_clock_rate(rate); +        UHD_MSG(status) << "got " << _tick_rate/1e6 << " MHz\n"; + +        BOOST_FOREACH(time_core_3000::sptr& time_core, _time_cores) { +            time_core->set_tick_rate(_tick_rate); +            time_core->self_test(); +        } + +        return _tick_rate; +    } + +    double get_tick_rate() +    { +        return _tick_rate; +    } + +    void set_clock_source(const std::string &source) +    { +        if (_clock_source == source) return; + +        if (source == "internal") { +            _ref_pll_ctrl->set_lock_to_ext_ref(false); +        } else if (source == "external" || source == "gpsdo") { +            _ref_pll_ctrl->set_lock_to_ext_ref(true); +        } else { +            throw uhd::key_error("set_clock_source: unknown source: " + source); +        } +        _core_misc_reg.write(fpga::core_misc_reg_t::REF_SEL, (source == "gpsdo") ? 1 : 0); + +        _clock_source = source; +    } + +    const std::string& get_clock_source() +    { +        return _clock_source; +    } + +    uhd::sensor_value_t get_ref_locked() +    { +        bool locked = false; +        if (_clock_source == "external" || _clock_source == "gpsdo") { +            locked = (_core_status_reg.read(fpga::core_radio_status_reg_t::REF_LOCKED) == 1); +        } else { +            //If the source is internal, the charge pump on the ADF4001 is tristated which +            //means that the 40MHz VCTXXO is free running i.e. always "locked" +            locked = true; +        } +        return sensor_value_t("Ref", locked, "locked", "unlocked"); +    } + +    void set_pps_source(const std::string &source) +    { +        if (_time_source == source) return; + +        if (source == "none" or source == "gpsdo") { +            _core_pps_sel_reg.write(fpga::core_pps_sel_reg_t::EXT_PPS_EN, 0); +        } else if (source == "external") { +            _core_pps_sel_reg.write(fpga::core_pps_sel_reg_t::EXT_PPS_EN, 1); +        } else { +            throw uhd::key_error("update_time_source: unknown source: " + source); +        } + +        _time_source = source; +    } + +    const std::string& get_pps_source() +    { +        return _time_source; +    } + +private: +    ad9361_ctrl::sptr                   _codec_ctrl; +    n230_ref_pll_ctrl::sptr             _ref_pll_ctrl; +    fpga::core_misc_reg_t&              _core_misc_reg; +    fpga::core_pps_sel_reg_t&           _core_pps_sel_reg; +    fpga::core_radio_status_reg_t&      _core_status_reg; +    std::vector<time_core_3000::sptr>   _time_cores; +    double                              _tick_rate; +    std::string                         _clock_source; +    std::string                         _time_source; +}; + +}}} //namespace + +using namespace uhd::usrp::n230; +using namespace uhd::usrp; + +n230_clk_pps_ctrl::sptr n230_clk_pps_ctrl::make( +    ad9361_ctrl::sptr codec_ctrl, +    n230_ref_pll_ctrl::sptr ref_pll_ctrl, +    fpga::core_misc_reg_t& core_misc_reg, +    fpga::core_pps_sel_reg_t& core_pps_sel_reg, +    fpga::core_radio_status_reg_t& core_status_reg, +    const std::vector<time_core_3000::sptr>& time_cores) +{ +    return sptr(new n230_clk_pps_ctrl_impl( +        codec_ctrl, ref_pll_ctrl, core_misc_reg, core_pps_sel_reg, core_status_reg, time_cores)); +} + diff --git a/host/lib/usrp/n230/n230_clk_pps_ctrl.hpp b/host/lib/usrp/n230/n230_clk_pps_ctrl.hpp new file mode 100644 index 000000000..e97a163fa --- /dev/null +++ b/host/lib/usrp/n230/n230_clk_pps_ctrl.hpp @@ -0,0 +1,89 @@ +// +// Copyright 2013-2014 Ettus Research LLC +// +// 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/>. +// + +#ifndef INCLUDED_N230_CLK_PPS_CTRL_HPP +#define INCLUDED_N230_CLK_PPS_CTRL_HPP + +#include "time_core_3000.hpp" +#include "ad9361_ctrl.hpp" +#include <uhd/types/sensors.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> +#include <vector> +#include "n230_cores.hpp" +#include "n230_fpga_defs.h" + +namespace uhd { namespace usrp { namespace n230 { + +class n230_clk_pps_ctrl : boost::noncopyable +{ +public: +    typedef boost::shared_ptr<n230_clk_pps_ctrl> sptr; + +    static sptr make( +        ad9361_ctrl::sptr codec_ctrl, +        n230_ref_pll_ctrl::sptr ref_pll_ctrl, +        fpga::core_misc_reg_t& core_misc_reg, +        fpga::core_pps_sel_reg_t& core_pps_sel_reg, +        fpga::core_radio_status_reg_t& core_status_reg, +        const std::vector<time_core_3000::sptr>& time_cores); + +    virtual ~n230_clk_pps_ctrl() {} + +    /*********************************************************************** +     * Tick Rate +     **********************************************************************/ +    /*! Set the master clock rate of the device. +     * \return the clock frequency in Hz +     */ +    virtual double set_tick_rate(const double rate) = 0; + +    /*! Get the master clock rate of the device. +     * \return the clock frequency in Hz +     */ +    virtual double get_tick_rate() = 0; + +    /*********************************************************************** +     * Reference clock +     **********************************************************************/ +    /*! Set the reference clock source of the device. +     */ +    virtual void set_clock_source(const std::string &source) = 0; + +    /*! Get the reference clock source of the device. +     */ +    virtual const std::string& get_clock_source() = 0; + +    /*! Get the reference clock lock status. +     */ +    virtual uhd::sensor_value_t get_ref_locked() = 0; + +    /*********************************************************************** +     * Time source +     **********************************************************************/ +    /*! Set the time source of the device. +     */ +    virtual void set_pps_source(const std::string &source) = 0; + +    /*! Get the reference clock source of the device. +     */ +    virtual const std::string& get_pps_source() = 0; +}; + +}}} //namespace + +#endif /* INCLUDED_N230_CLK_PPS_CTRL_HPP */ diff --git a/host/lib/usrp/n230/n230_cores.cpp b/host/lib/usrp/n230/n230_cores.cpp new file mode 100644 index 000000000..14e99b692 --- /dev/null +++ b/host/lib/usrp/n230/n230_cores.cpp @@ -0,0 +1,91 @@ +// +// Copyright 2013-2014 Ettus Research LLC +// +// 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 "../../../firmware/usrp3/n230/n230_fw_defs.h" +#include "n230_cores.hpp" +#include "n230_fpga_defs.h" + +namespace uhd { namespace usrp { namespace n230 { + +n230_core_spi_core::n230_core_spi_core( +    uhd::wb_iface::sptr iface, +    perif_t default_perif) : +    _spi_core(spi_core_3000::make(iface, +                                  fpga::sr_addr(fpga::SR_CORE_SPI), +                                  fpga::rb_addr(fpga::RB_CORE_SPI))), +    _current_perif(default_perif), +    _last_perif(default_perif) +{ +    change_perif(default_perif); +} + +boost::uint32_t n230_core_spi_core::transact_spi( +    int which_slave, +    const spi_config_t &config, +    boost::uint32_t data, +    size_t num_bits, +    bool readback) +{ +    boost::mutex::scoped_lock lock(_mutex); +    return _spi_core->transact_spi(which_slave, config, data, num_bits, readback); +} + +void n230_core_spi_core::change_perif(perif_t perif) +{ +    boost::mutex::scoped_lock lock(_mutex); +    _last_perif = _current_perif; +    _current_perif = perif; + +    switch (_current_perif) { +        case CODEC: +            _spi_core->set_divider(fw::CPU_CLOCK_FREQ/fw::CODEC_SPI_CLOCK_FREQ); +            break; +        case PLL: +            _spi_core->set_divider(fw::CPU_CLOCK_FREQ/fw::ADF4001_SPI_CLOCK_FREQ); +            break; +    } +} + +void n230_core_spi_core::restore_perif() +{ +    change_perif(_last_perif); +} + +n230_ref_pll_ctrl::n230_ref_pll_ctrl(n230_core_spi_core::sptr spi) : +    adf4001_ctrl(spi, fpga::ADF4001_SPI_SLAVE_NUM), +    _spi(spi) +{ +} + +void n230_ref_pll_ctrl::set_lock_to_ext_ref(bool external) +{ +    _spi->change_perif(n230_core_spi_core::PLL); +    adf4001_ctrl::set_lock_to_ext_ref(external); +    _spi->restore_perif(); +} + +}}} //namespace + +using namespace uhd::usrp::n230; +using namespace uhd::usrp; + +n230_core_spi_core::sptr n230_core_spi_core::make( +    uhd::wb_iface::sptr iface, n230_core_spi_core::perif_t default_perif) +{ +    return sptr(new n230_core_spi_core(iface, default_perif)); +} + diff --git a/host/lib/usrp/n230/n230_cores.hpp b/host/lib/usrp/n230/n230_cores.hpp new file mode 100644 index 000000000..3f56c1889 --- /dev/null +++ b/host/lib/usrp/n230/n230_cores.hpp @@ -0,0 +1,71 @@ +// +// Copyright 2013-2014 Ettus Research LLC +// +// 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/>. +// + +#ifndef INCLUDED_N230_CORES_HPP +#define INCLUDED_N230_CORES_HPP + +#include "spi_core_3000.hpp" +#include "adf4001_ctrl.hpp" +#include <boost/thread/mutex.hpp> + +namespace uhd { namespace usrp { namespace n230 { + +class n230_core_spi_core : boost::noncopyable, public uhd::spi_iface { + +public: +    typedef boost::shared_ptr<n230_core_spi_core> sptr; + +    enum perif_t { +        CODEC, PLL +    }; + +    n230_core_spi_core(uhd::wb_iface::sptr iface, perif_t default_perif); + +    virtual boost::uint32_t transact_spi( +        int which_slave, +        const spi_config_t &config, +        boost::uint32_t data, +        size_t num_bits, +        bool readback); + +    void change_perif(perif_t perif); +    void restore_perif(); + +    static sptr make(uhd::wb_iface::sptr iface, perif_t default_perif = CODEC); + +private: +    spi_core_3000::sptr     _spi_core; +    perif_t                 _current_perif; +    perif_t                 _last_perif; +    boost::mutex            _mutex; +}; + +class n230_ref_pll_ctrl : public adf4001_ctrl { +public: +    typedef boost::shared_ptr<n230_ref_pll_ctrl> sptr; + +    n230_ref_pll_ctrl(n230_core_spi_core::sptr spi); +    void set_lock_to_ext_ref(bool external); + +private: +    n230_core_spi_core::sptr _spi; +}; + + +}}} //namespace + +#endif /* INCLUDED_N230_CORES_HPP */ diff --git a/host/lib/usrp/n230/n230_defaults.h b/host/lib/usrp/n230/n230_defaults.h new file mode 100644 index 000000000..a25978585 --- /dev/null +++ b/host/lib/usrp/n230/n230_defaults.h @@ -0,0 +1,65 @@ +// +// Copyright 2014 Ettus Research LLC +// +// 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/>. +// + +#ifndef INCLUDED_N230_DEFAULTS_H +#define INCLUDED_N230_DEFAULTS_H + +#include <stdint.h> +#ifndef __cplusplus +#include <stdbool.h> +#endif +#include <uhd/transport/udp_constants.hpp> + +namespace uhd { +namespace usrp { +namespace n230 { + +static const double DEFAULT_TICK_RATE       = 46.08e6; +static const double MAX_TICK_RATE           = 50e6; +static const double MIN_TICK_RATE           = 1e6; + +static const double DEFAULT_TX_SAMP_RATE    = 1.0e6; +static const double DEFAULT_RX_SAMP_RATE    = 1.0e6; +static const double DEFAULT_DDC_FREQ        = 0.0; +static const double DEFAULT_DUC_FREQ        = 0.0; + +static const double DEFAULT_FE_GAIN         = 0.0; +static const double DEFAULT_FE_FREQ         = 1.0e9; +static const double DEFAULT_FE_BW           = 56e6; + +static const std::string DEFAULT_TIME_SRC   = "none"; +static const std::string DEFAULT_CLOCK_SRC  = "internal"; + +static const size_t DEFAULT_FRAME_SIZE      = 1500 - 20 - 8; //default ipv4 mtu - ipv4 header - udp header +static const size_t MAX_FRAME_SIZE          = 8000; +static const size_t MIN_FRAME_SIZE          = IP_PROTOCOL_MIN_MTU_SIZE; + +static const size_t DEFAULT_NUM_FRAMES      = 32; + +//A 1MiB SRAM is shared between two radios so we allocate each +//radio 0.5MiB minus 8 packets worth of buffering to ensure +//that the FIFO does not overflow +static const size_t DEFAULT_SEND_BUFF_SIZE  = 500*1024; +#if defined(UHD_PLATFORM_MACOS) || defined(UHD_PLATFORM_BSD) +static const size_t DEFAULT_RECV_BUFF_SIZE  = 0x100000; //1Mib +#elif defined(UHD_PLATFORM_LINUX) || defined(UHD_PLATFORM_WIN32) +static const size_t DEFAULT_RECV_BUFF_SIZE  = 0x2000000;//32MiB +#endif + +}}}    //namespace + +#endif /* INCLUDED_N230_DEFAULTS_H */ diff --git a/host/lib/usrp/n230/n230_device_args.hpp b/host/lib/usrp/n230/n230_device_args.hpp new file mode 100644 index 000000000..014a6cd14 --- /dev/null +++ b/host/lib/usrp/n230/n230_device_args.hpp @@ -0,0 +1,128 @@ +// +// Copyright 2014 Ettus Research LLC +// +// 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/>. +// + +#ifndef INCLUDED_N230_DEV_ARGS_HPP +#define INCLUDED_N230_DEV_ARGS_HPP + +#include <uhd/types/wb_iface.hpp> +#include <uhd/transport/udp_simple.hpp> +#include <boost/thread/mutex.hpp> +#include "../common/constrained_device_args.hpp" +#include "n230_defaults.h" + +namespace uhd { namespace usrp { namespace n230 { + +class n230_device_args_t : public constrained_device_args_t +{ +public: +    enum loopback_mode_t { LOOPBACK_OFF=0, LOOPBACK_RADIO=1, LOOPBACK_CODEC=2 }; + +    n230_device_args_t(): +        _master_clock_rate("master_clock_rate", n230::DEFAULT_TICK_RATE), +        _send_frame_size("send_frame_size", n230::DEFAULT_FRAME_SIZE), +        _recv_frame_size("recv_frame_size", n230::DEFAULT_FRAME_SIZE), +        _num_send_frames("num_send_frames", n230::DEFAULT_NUM_FRAMES), +        _num_recv_frames("num_recv_frames", n230::DEFAULT_NUM_FRAMES), +        _send_buff_size("send_buff_size", n230::DEFAULT_SEND_BUFF_SIZE), +        _recv_buff_size("recv_buff_size", n230::DEFAULT_RECV_BUFF_SIZE), +        _safe_mode("safe_mode", false), +        _loopback_mode("loopback_mode", LOOPBACK_OFF, boost::assign::list_of("off")("radio")("codec")) +    {} + +    double get_master_clock_rate() const { +        return _master_clock_rate.get(); +    } +    size_t get_send_frame_size() const { +        return _send_frame_size.get(); +    } +    size_t get_recv_frame_size() const { +        return _recv_frame_size.get(); +    } +    size_t get_num_send_frames() const { +        return _num_send_frames.get(); +    } +    size_t get_num_recv_frames() const { +        return _num_recv_frames.get(); +    } +    size_t get_send_buff_size() const { +        return _send_buff_size.get(); +    } +    size_t get_recv_buff_size() const { +        return _recv_buff_size.get(); +    } +    bool get_safe_mode() const { +        return _safe_mode.get(); +    } +    loopback_mode_t get_loopback_mode() const { +        return _loopback_mode.get(); +    } + +    inline virtual std::string to_string() const { +        return  _master_clock_rate.to_string() + ", " + +                _send_frame_size.to_string() + ", " + +                _recv_frame_size.to_string() + ", " + +                _num_send_frames.to_string() + ", " + +                _num_recv_frames.to_string() + ", " + +                _send_buff_size.to_string() + ", " + +                _recv_buff_size.to_string() + ", " + +                _safe_mode.to_string() + ", " + +                _loopback_mode.to_string(); +    } +private: +    virtual void _parse(const device_addr_t& dev_args) { +        //Extract parameters from dev_args +        if (dev_args.has_key(_master_clock_rate.key())) +            _master_clock_rate.parse(dev_args[_master_clock_rate.key()]); +        if (dev_args.has_key(_send_frame_size.key())) +            _send_frame_size.parse(dev_args[_send_frame_size.key()]); +        if (dev_args.has_key(_recv_frame_size.key())) +            _recv_frame_size.parse(dev_args[_recv_frame_size.key()]); +        if (dev_args.has_key(_num_send_frames.key())) +            _num_send_frames.parse(dev_args[_num_send_frames.key()]); +        if (dev_args.has_key(_num_recv_frames.key())) +            _num_recv_frames.parse(dev_args[_num_recv_frames.key()]); +        if (dev_args.has_key(_send_buff_size.key())) +            _send_buff_size.parse(dev_args[_send_buff_size.key()]); +        if (dev_args.has_key(_recv_buff_size.key())) +            _recv_buff_size.parse(dev_args[_recv_buff_size.key()]); +        if (dev_args.has_key(_safe_mode.key())) +            _safe_mode.parse(dev_args[_safe_mode.key()]); +        if (dev_args.has_key(_loopback_mode.key())) +            _loopback_mode.parse(dev_args[_loopback_mode.key()], false /* assert invalid */); + +        //Sanity check params +        _enforce_range(_master_clock_rate, MIN_TICK_RATE, MAX_TICK_RATE); +        _enforce_range(_send_frame_size, MIN_FRAME_SIZE, MAX_FRAME_SIZE); +        _enforce_range(_recv_frame_size, MIN_FRAME_SIZE, MAX_FRAME_SIZE); +        _enforce_range(_num_send_frames, (size_t)2, (size_t)UINT_MAX); +        _enforce_range(_num_recv_frames, (size_t)2, (size_t)UINT_MAX); +    } + +    constrained_device_args_t::num_arg<double>           _master_clock_rate; +    constrained_device_args_t::num_arg<size_t>           _send_frame_size; +    constrained_device_args_t::num_arg<size_t>           _recv_frame_size; +    constrained_device_args_t::num_arg<size_t>           _num_send_frames; +    constrained_device_args_t::num_arg<size_t>           _num_recv_frames; +    constrained_device_args_t::num_arg<size_t>           _send_buff_size; +    constrained_device_args_t::num_arg<size_t>           _recv_buff_size; +    constrained_device_args_t::bool_arg                  _safe_mode; +    constrained_device_args_t::enum_arg<loopback_mode_t> _loopback_mode; +}; + +}}} //namespace + +#endif //INCLUDED_N230_DEV_ARGS_HPP diff --git a/host/lib/usrp/n230/n230_eeprom_manager.cpp b/host/lib/usrp/n230/n230_eeprom_manager.cpp new file mode 100644 index 000000000..ed40b47be --- /dev/null +++ b/host/lib/usrp/n230/n230_eeprom_manager.cpp @@ -0,0 +1,180 @@ +// +// Copyright 2013-2014 Ettus Research LLC +// +// 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 "../../../firmware/usrp3/n230/n230_eeprom.h" +#include <uhd/utils/byteswap.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/exception.hpp> +#include <uhd/types/mac_addr.hpp> +#include <boost/format.hpp> +#include <boost/asio.hpp> //used for htonl and ntohl +#include "n230_eeprom_manager.hpp" + +namespace uhd { namespace usrp { namespace n230 { + +const double n230_eeprom_manager::UDP_TIMEOUT_IN_SEC = 2.0; + +n230_eeprom_manager::n230_eeprom_manager(const std::string& addr): +    _seq_num(0) +{ +    _udp_xport = transport::udp_simple::make_connected( +        addr, BOOST_STRINGIZE(N230_FW_COMMS_FLASH_PROG_PORT)); +    read_mb_eeprom(); +} + +static const std::string _bytes_to_string(const uint8_t* bytes, size_t max_len) +{ +    std::string out; +    for (size_t i = 0; i < max_len; i++) { +        if (bytes[i] < 32 or bytes[i] > 127) return out; +        out += bytes[i]; +    } +    return out; +} + +static void _string_to_bytes(const std::string &string, size_t max_len, uint8_t* buffer) +{ +    byte_vector_t bytes; +    const size_t len = std::min(string.size(), max_len); +    for (size_t i = 0; i < len; i++){ +        buffer[i] = string[i]; +    } +    if (len < max_len - 1) buffer[len] = '\0'; +} + +const mboard_eeprom_t& n230_eeprom_manager::read_mb_eeprom() +{ +    boost::mutex::scoped_lock lock(_mutex); + +    //Read EEPROM from device +    _transact(N230_FLASH_COMM_CMD_READ_NV_DATA); +    const n230_eeprom_map_t* map_ptr = reinterpret_cast<const n230_eeprom_map_t*>(_response.data); +    const n230_eeprom_map_t& map = *map_ptr; + +    _mb_eeprom["product"] = boost::lexical_cast<std::string>( +        uhd::htonx<boost::uint16_t>(map.hw_product)); +    _mb_eeprom["revision"] = boost::lexical_cast<std::string>( +        uhd::htonx<boost::uint16_t>(map.hw_revision)); +    _mb_eeprom["serial"] = _bytes_to_string( +        map.serial, N230_EEPROM_SERIAL_LEN); + +    //Extract ethernet info +    _mb_eeprom["gateway"] = boost::asio::ip::address_v4( +        uhd::htonx<boost::uint32_t>(map.gateway)).to_string(); +    for (size_t i = 0; i < N230_MAX_NUM_ETH_PORTS; i++) { +        const std::string n(1, i+'0'); +        _mb_eeprom["ip-addr"+n] = boost::asio::ip::address_v4( +            uhd::htonx<boost::uint32_t>(map.eth_info[i].ip_addr)).to_string(); +        _mb_eeprom["subnet"+n] = boost::asio::ip::address_v4( +            uhd::htonx<boost::uint32_t>(map.eth_info[i].subnet)).to_string(); +        byte_vector_t mac_addr(map.eth_info[i].mac_addr, map.eth_info[i].mac_addr + 6); +        _mb_eeprom["mac-addr"+n] = mac_addr_t::from_bytes(mac_addr).to_string(); +    } + +    _mb_eeprom["name"] = _bytes_to_string( +        map.user_name, N230_EEPROM_NAME_LEN); + +    return _mb_eeprom; +} + +void n230_eeprom_manager::write_mb_eeprom(const mboard_eeprom_t& eeprom) +{ +    boost::mutex::scoped_lock lock(_mutex); + +    _mb_eeprom = eeprom; + +    n230_eeprom_map_t* map_ptr = reinterpret_cast<n230_eeprom_map_t*>(_request.data); +    memset(map_ptr, 0xff, sizeof(n230_eeprom_map_t)); //Initialize to erased state +    //Read EEPROM from device +    _transact(N230_FLASH_COMM_CMD_READ_NV_DATA); +    memcpy(map_ptr, _response.data, sizeof(n230_eeprom_map_t)); +    n230_eeprom_map_t& map = *map_ptr; + +    map.data_version_major = uhd::htonx<boost::uint16_t>(N230_EEPROM_VER_MAJOR); +    map.data_version_minor = uhd::htonx<boost::uint16_t>(N230_EEPROM_VER_MINOR); + +    if (_mb_eeprom.has_key("product")) { +        map.hw_product = uhd::htonx<boost::uint16_t>( +            boost::lexical_cast<boost::uint16_t>(_mb_eeprom["product"])); +    } +    if (_mb_eeprom.has_key("revision")) { +        map.hw_revision = uhd::htonx<boost::uint16_t>( +            boost::lexical_cast<boost::uint16_t>(_mb_eeprom["revision"])); +    } +    if (_mb_eeprom.has_key("serial")) { +        _string_to_bytes(_mb_eeprom["serial"], N230_EEPROM_SERIAL_LEN, map.serial); +    } + +    //Push ethernet info +    if (_mb_eeprom.has_key("gateway")){ +        map.gateway = uhd::htonx<boost::uint32_t>( +            boost::asio::ip::address_v4::from_string(_mb_eeprom["gateway"]).to_ulong()); +    } +    for (size_t i = 0; i < N230_MAX_NUM_ETH_PORTS; i++) { +        const std::string n(1, i+'0'); +        if (_mb_eeprom.has_key("ip-addr"+n)){ +            map.eth_info[i].ip_addr = uhd::htonx<boost::uint32_t>( +                boost::asio::ip::address_v4::from_string(_mb_eeprom["ip-addr"+n]).to_ulong()); +        } +        if (_mb_eeprom.has_key("subnet"+n)){ +            map.eth_info[i].subnet = uhd::htonx<boost::uint32_t>( +                boost::asio::ip::address_v4::from_string(_mb_eeprom["subnet"+n]).to_ulong()); +        } +        if (_mb_eeprom.has_key("mac-addr"+n)) { +            byte_vector_t mac_addr = mac_addr_t::from_string(_mb_eeprom["mac-addr"+n]).to_bytes(); +            std::copy(mac_addr.begin(), mac_addr.end(), map.eth_info[i].mac_addr); +        } +    } +    //store the name +    if (_mb_eeprom.has_key("name")) { +        _string_to_bytes(_mb_eeprom["name"], N230_EEPROM_NAME_LEN, map.user_name); +    } + +    //Write EEPROM to device +    _transact(N230_FLASH_COMM_CMD_WRITE_NV_DATA); +} + +void n230_eeprom_manager::_transact(const boost::uint32_t command) +{ +    //Load request struct +    _request.flags = uhd::htonx<boost::uint32_t>(N230_FLASH_COMM_FLAGS_ACK | command); +    _request.seq = uhd::htonx<boost::uint32_t>(_seq_num++); + +    //Send request +    _flush_xport(); +    _udp_xport->send(boost::asio::buffer(&_request, sizeof(_request))); + +    //Recv reply +    const size_t nbytes = _udp_xport->recv(boost::asio::buffer(&_response, sizeof(_response)), UDP_TIMEOUT_IN_SEC); +    if (nbytes == 0) throw uhd::io_error("n230_eeprom_manager::_transact failure"); + +    //Sanity checks +    const size_t flags = uhd::ntohx<boost::uint32_t>(_response.flags); +    UHD_ASSERT_THROW(nbytes == sizeof(_response)); +    UHD_ASSERT_THROW(_response.seq == _request.seq); +    UHD_ASSERT_THROW(flags & command); +} + +void n230_eeprom_manager::_flush_xport() +{ +    char buff[sizeof(n230_flash_prog_t)] = {}; +    while (_udp_xport->recv(boost::asio::buffer(buff), 0.0)) { +        /*NOP*/ +    } +} + +}}};    //namespace diff --git a/host/lib/usrp/n230/n230_eeprom_manager.hpp b/host/lib/usrp/n230/n230_eeprom_manager.hpp new file mode 100644 index 000000000..612124d89 --- /dev/null +++ b/host/lib/usrp/n230/n230_eeprom_manager.hpp @@ -0,0 +1,58 @@ +// +// Copyright 2013-2014 Ettus Research LLC +// +// 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/>. +// + +#ifndef INCLUDED_N230_EEPROM_MANAGER_HPP +#define INCLUDED_N230_EEPROM_MANAGER_HPP + +#include "../../../firmware/usrp3/n230/n230_fw_host_iface.h" +#include <boost/thread/mutex.hpp> +#include <uhd/transport/udp_simple.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/usrp/mboard_eeprom.hpp> + +namespace uhd { namespace usrp { namespace n230 { + +class n230_eeprom_manager : boost::noncopyable +{ +public: +    n230_eeprom_manager(const std::string& addr); + +    const mboard_eeprom_t& read_mb_eeprom(); +    void write_mb_eeprom(const mboard_eeprom_t& eeprom); + +    inline const mboard_eeprom_t& get_mb_eeprom() { +        return _mb_eeprom; +    } + +private:    //Functions +    void _transact(const boost::uint32_t command); +    void _flush_xport(); + +private:    //Members +    mboard_eeprom_t             _mb_eeprom; +    transport::udp_simple::sptr _udp_xport; +    n230_flash_prog_t           _request; +    n230_flash_prog_t           _response; +    boost::uint32_t             _seq_num; +    boost::mutex                _mutex; + +    static const double UDP_TIMEOUT_IN_SEC; +}; + +}}} //namespace + +#endif /* INCLUDED_N230_EEPROM_MANAGER_HPP */ diff --git a/host/lib/usrp/n230/n230_fpga_defs.h b/host/lib/usrp/n230/n230_fpga_defs.h new file mode 100644 index 000000000..5a2dd5c68 --- /dev/null +++ b/host/lib/usrp/n230/n230_fpga_defs.h @@ -0,0 +1,202 @@ +// +// Copyright 2014 Ettus Research LLC +// +// 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/>. +// + +#ifndef INCLUDED_N230_FPGA_DEFS_H +#define INCLUDED_N230_FPGA_DEFS_H + +#include <stdint.h> +#ifndef __cplusplus +#include <stdbool.h> +#endif +#include <uhd/utils/soft_register.hpp> + +namespace uhd { +namespace usrp { +namespace n230 { +namespace fpga { + +static inline uint32_t sr_addr(uint32_t offset) { +    return (offset*4); +} + +static inline uint32_t rb_addr(uint32_t offset) { +    return (offset*8); +} + +static const size_t NUM_RADIOS = 2; +static const double BUS_CLK_RATE = 80e6; + +/******************************************************************* + * CVITA Routing + *******************************************************************/ +static const uint32_t CVITA_UDP_PORT    = 49153; +static const bool CVITA_BIG_ENDIAN      = true; + +enum xb_endpoint_t { +    N230_XB_DST_E0    = 0, +    N230_XB_DST_E1    = 1, +    N230_XB_DST_R0    = 2, +    N230_XB_DST_R1    = 3, +    N230_XB_DST_GCTRL = 4, +    N230_XB_DST_UART  = 5 +}; + +static const boost::uint8_t RADIO_CTRL_SUFFIX = 0x00; +static const boost::uint8_t RADIO_FC_SUFFIX   = 0x01; +static const boost::uint8_t RADIO_DATA_SUFFIX = 0x02; + +/******************************************************************* + * Seting Register Base addresses + *******************************************************************/ +static const uint32_t SR_CORE_RADIO_CONTROL = 3; +static const uint32_t SR_CORE_LOOPBACK      = 4; +static const uint32_t SR_CORE_BIST1         = 5; +static const uint32_t SR_CORE_BIST2         = 6; +static const uint32_t SR_CORE_SPI           = 8; +static const uint32_t SR_CORE_MISC          = 16; +static const uint32_t SR_CORE_DATA_DELAY    = 17; +static const uint32_t SR_CORE_CLK_DELAY     = 18; +static const uint32_t SR_CORE_COMPAT        = 24; +static const uint32_t SR_CORE_READBACK      = 32; +static const uint32_t SR_CORE_GPSDO_ST      = 40; +static const uint32_t SR_CORE_PPS_SEL       = 48; + +static const uint32_t RB_CORE_SIGNATUE      = 0; +static const uint32_t RB_CORE_SPI           = 1; +static const uint32_t RB_CORE_STATUS        = 2; +static const uint32_t RB_CORE_BIST          = 3; +static const uint32_t RB_CORE_VERSION_HASH  = 4; + +/******************************************************************* + * Seting Register Base addresses + *******************************************************************/ +static const uint32_t SR_RADIO_SPI          = 8; +static const uint32_t SR_RADIO_ATR          = 12; +static const uint32_t SR_RADIO_SW_RST       = 20; +static const uint32_t SR_RADIO_TEST         = 21; +static const uint32_t SR_RADIO_CODEC_IDLE   = 22; +static const uint32_t SR_RADIO_READBACK     = 32; +static const uint32_t SR_RADIO_TX_CTRL      = 64; +static const uint32_t SR_RADIO_RX_CTRL      = 96; +static const uint32_t SR_RADIO_RX_DSP       = 144; +static const uint32_t SR_RADIO_TX_DSP       = 184; +static const uint32_t SR_RADIO_TIME         = 128; +static const uint32_t SR_RADIO_RX_FMT       = 136; +static const uint32_t SR_RADIO_TX_FMT       = 138; +static const uint32_t SR_RADIO_USER_SR      = 253; + +static const uint32_t RB_RADIO_TEST         = 0; +static const uint32_t RB_RADIO_TIME_NOW     = 1; +static const uint32_t RB_RADIO_TIME_PPS     = 2; +static const uint32_t RB_RADIO_CODEC_DATA   = 3; +static const uint32_t RB_RADIO_DEBUG        = 4; +static const uint32_t RB_RADIO_FRAMER       = 5; +static const uint32_t SR_RADIO_USER_RB      = 7; + +static const uint32_t AD9361_SPI_SLAVE_NUM  = 0x1; +static const uint32_t ADF4001_SPI_SLAVE_NUM = 0x2; + +static const uint32_t RB_N230_PRODUCT_ID    = 1; +static const uint32_t RB_N230_COMPAT_MAJOR  = 0x20; +static const uint32_t RB_N230_COMPAT_SAFE   = 0xC0; + +/******************************************************************* + * Codec Interface Specific + *******************************************************************/ + +// Matches delay setting of 0x00 in AD9361 register 0x006 +static const uint32_t CODEC_DATA_DELAY      = 0; +static const uint32_t CODEC_CLK_DELAY       = 16; + +//This number must be < 46.08MHz to make sure we don't +//violate timing for radio_clk. It is only used during +//initialization so the exact value does not matter. +static const double CODEC_DEFAULT_CLK_RATE  = 40e6; + +/******************************************************************* + * Link Specific + *******************************************************************/ +static const double N230_LINK_RATE_BPS      = 1e9/8; + +/******************************************************************* + * GPSDO status + *******************************************************************/ +static const uint32_t GPSDO_ST_NONE = 0x83; + +/******************************************************************* + * Register Objects + *******************************************************************/ +class core_radio_ctrl_reg_t : public soft_reg32_wo_t { +public: +    UHD_DEFINE_SOFT_REG_FIELD(MIMO,         /*width*/ 1, /*shift*/ 0);  //[0] +    UHD_DEFINE_SOFT_REG_FIELD(CODEC_ARST,   /*width*/ 1, /*shift*/ 1);  //[1] + +    core_radio_ctrl_reg_t(): +        soft_reg32_wo_t(fpga::sr_addr(fpga::SR_CORE_RADIO_CONTROL)) +    { +        //Initial values +        set(CODEC_ARST, 0); +        set(MIMO, 1);   //MIMO always ON for now +    } +}; + +class core_misc_reg_t : public soft_reg32_wo_t { +public: +    UHD_DEFINE_SOFT_REG_FIELD(REF_SEL,      /*width*/ 1, /*shift*/ 0);  //[0] +    UHD_DEFINE_SOFT_REG_FIELD(RX_BANDSEL_C, /*width*/ 1, /*shift*/ 1);  //[1] +    UHD_DEFINE_SOFT_REG_FIELD(RX_BANDSEL_B, /*width*/ 1, /*shift*/ 2);  //[2] +    UHD_DEFINE_SOFT_REG_FIELD(RX_BANDSEL_A, /*width*/ 1, /*shift*/ 3);  //[3] +    UHD_DEFINE_SOFT_REG_FIELD(TX_BANDSEL_B, /*width*/ 1, /*shift*/ 4);  //[4] +    UHD_DEFINE_SOFT_REG_FIELD(TX_BANDSEL_A, /*width*/ 1, /*shift*/ 5);  //[5] + +    core_misc_reg_t(): +        soft_reg32_wo_t(fpga::sr_addr(fpga::SR_CORE_MISC)) +    { +        //Initial values +        set(REF_SEL, 0); +        set(RX_BANDSEL_C, 0); +        set(RX_BANDSEL_B, 0); +        set(RX_BANDSEL_A, 0); +        set(TX_BANDSEL_B, 0); +        set(TX_BANDSEL_A, 0); +    } +}; + +class core_pps_sel_reg_t : public soft_reg32_wo_t { +public: +    UHD_DEFINE_SOFT_REG_FIELD(EXT_PPS_EN,   /*width*/ 1, /*shift*/ 0);  //[0] + +    core_pps_sel_reg_t(): +        soft_reg32_wo_t(fpga::sr_addr(fpga::SR_CORE_PPS_SEL)) +    { +        //Initial values +        set(EXT_PPS_EN, 0); +    } +}; + +class core_radio_status_reg_t : public soft_reg64_ro_t { +public: +    UHD_DEFINE_SOFT_REG_FIELD(REF_LOCKED,   /*width*/ 1, /*shift*/ 0);  //[0] + +    core_radio_status_reg_t(): +        soft_reg64_ro_t(fpga::rb_addr(fpga::RB_CORE_STATUS)) +    { } +}; + +}}}}    //namespace + +#endif /* INCLUDED_N230_FPGA_DEFS_H */ diff --git a/host/lib/usrp/n230/n230_frontend_ctrl.cpp b/host/lib/usrp/n230/n230_frontend_ctrl.cpp new file mode 100644 index 000000000..46e8c218f --- /dev/null +++ b/host/lib/usrp/n230/n230_frontend_ctrl.cpp @@ -0,0 +1,239 @@ +// +// Copyright 2013-2014 Ettus Research LLC +// +// 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 "n230_frontend_ctrl.hpp" + +#include <uhd/utils/msg.hpp> +#include <uhd/exception.hpp> +#include <uhd/types/dict.hpp> +#include <boost/thread.hpp> +#include "n230_fpga_defs.h" + +namespace uhd { namespace usrp { namespace n230 { + +/* ATR Control Bits */ +static const boost::uint32_t TX_ENABLE      = (1 << 7); +static const boost::uint32_t SFDX_RX        = (1 << 6); +static const boost::uint32_t SFDX_TX        = (1 << 5); +static const boost::uint32_t SRX_RX         = (1 << 4); +static const boost::uint32_t SRX_TX         = (1 << 3); +static const boost::uint32_t LED_RX         = (1 << 2); +static const boost::uint32_t LED_TXRX_RX    = (1 << 1); +static const boost::uint32_t LED_TXRX_TX    = (1 << 0); + +/* ATR State Definitions. */ +static const boost::uint32_t STATE_OFF      = 0x00; +static const boost::uint32_t STATE_RX_RX2   = (SFDX_RX +                                                | SFDX_TX +                                                | LED_RX); +static const boost::uint32_t STATE_RX_TXRX  = (SRX_RX +                                                | SRX_TX +                                                | LED_TXRX_RX); +static const boost::uint32_t STATE_FDX_TXRX = (TX_ENABLE +                                                | SFDX_RX +                                                | SFDX_TX +                                                | LED_TXRX_TX +                                                | LED_RX); +static const boost::uint32_t STATE_TX_TXRX  = (TX_ENABLE +                                                | SFDX_RX +                                                | SFDX_TX +                                                | LED_TXRX_TX); + + +class n230_frontend_ctrl_impl : public n230_frontend_ctrl +{ +public: +    n230_frontend_ctrl_impl( +        radio_ctrl_core_3000::sptr core_ctrl, +        fpga::core_misc_reg_t& core_misc_reg, +        ad9361_ctrl::sptr codec_ctrl, +        const std::vector<gpio_core_200_32wo::sptr>& gpio_cores +    ): _core_ctrl(core_ctrl), +       _codec_ctrl(codec_ctrl), +       _gpio_cores(gpio_cores), +       _core_misc_reg(core_misc_reg) +    { +    } + +    virtual ~n230_frontend_ctrl_impl() +    { +    } + +    void set_antenna_sel(const size_t which, const std::string &ant) +    { +        if (ant != "TX/RX" and ant != "RX2") +            throw uhd::value_error("n230: unknown RX antenna option: " + ant); + +        _fe_states[which].rx_ant = ant; +        _flush_atr_state(); +    } + +    void set_stream_state(const fe_state_t fe0_state_, const fe_state_t fe1_state_) +    { +        //Update soft-state +        _fe_states[0].state = fe0_state_; +        _fe_states[1].state = fe1_state_; + +        const fe_state_t fe0_state = _fe_states[0].state; +        const fe_state_t fe1_state = (_gpio_cores.size() > 1) ? _fe_states[1].state : NONE_STREAMING; + +        const size_t num_tx = (_is_tx(fe0_state) ? 1 : 0) + (_is_tx(fe1_state) ? 1 : 0); +        const size_t num_rx = (_is_rx(fe0_state) ? 1 : 0) + (_is_rx(fe1_state) ? 1 : 0); + +        //setup the active chains in the codec +        if ((num_rx + num_tx) == 0) { +            _codec_ctrl->set_active_chains( +                true, false, +                true, false); //enable something +        } else { +            _codec_ctrl->set_active_chains( +                _is_tx(fe0_state), _is_tx(fe1_state), +                _is_rx(fe0_state), _is_rx(fe1_state)); +        } + +        _core_misc_reg.flush(); +        //atrs change based on enables +        _flush_atr_state(); +    } + + +    void set_stream_state(const size_t which, const fe_state_t state) +    { +        if (which == 0) { +            set_stream_state(state, _fe_states[1].state); +        } else if (which == 1) { +            set_stream_state(_fe_states[0].state, state); +        } else { +            throw uhd::value_error("n230: unknown stream index option: " + which); +        } +    } + +    void set_bandsel(const std::string& which, double freq) +    { +        using namespace n230::fpga; + +        if(which[0] == 'R') { +            if(freq < 2.2e9) { +                _core_misc_reg.set(core_misc_reg_t::RX_BANDSEL_A, 0); +                _core_misc_reg.set(core_misc_reg_t::RX_BANDSEL_B, 0); +                _core_misc_reg.set(core_misc_reg_t::RX_BANDSEL_C, 1); +            } else if((freq >= 2.2e9) && (freq < 4e9)) { +                _core_misc_reg.set(core_misc_reg_t::RX_BANDSEL_A, 0); +                _core_misc_reg.set(core_misc_reg_t::RX_BANDSEL_B, 1); +                _core_misc_reg.set(core_misc_reg_t::RX_BANDSEL_C, 0); +            } else if((freq >= 4e9) && (freq <= 6e9)) { +                _core_misc_reg.set(core_misc_reg_t::RX_BANDSEL_A, 1); +                _core_misc_reg.set(core_misc_reg_t::RX_BANDSEL_B, 0); +                _core_misc_reg.set(core_misc_reg_t::RX_BANDSEL_C, 0); +            } else { +                UHD_THROW_INVALID_CODE_PATH(); +            } +        } else if(which[0] == 'T') { +            if(freq < 2.5e9) { +                _core_misc_reg.set(core_misc_reg_t::TX_BANDSEL_A, 0); +                _core_misc_reg.set(core_misc_reg_t::TX_BANDSEL_B, 1); +            } else if((freq >= 2.5e9) && (freq <= 6e9)) { +                _core_misc_reg.set(core_misc_reg_t::TX_BANDSEL_A, 1); +                _core_misc_reg.set(core_misc_reg_t::TX_BANDSEL_B, 0); +            } else { +                UHD_THROW_INVALID_CODE_PATH(); +            } +        } else { +            UHD_THROW_INVALID_CODE_PATH(); +        } + +        _core_misc_reg.flush(); +    } + +    void set_self_test_mode(self_test_mode_t mode) +    { +        switch (mode) { +            case LOOPBACK_RADIO: { +                _core_ctrl->poke32(fpga::sr_addr(fpga::SR_CORE_LOOPBACK), 0x1); +            } break; +            case LOOPBACK_CODEC: { +                _core_ctrl->poke32(fpga::sr_addr(fpga::SR_CORE_LOOPBACK), 0x0); +                _codec_ctrl->data_port_loopback(true); +            } break; +            //Default = disable +            default: +            case LOOPBACK_DISABLED: { +                _core_ctrl->poke32(fpga::sr_addr(fpga::SR_CORE_LOOPBACK), 0x0); +                _codec_ctrl->data_port_loopback(false); +            } break; +        } +    } + +private: +    void _flush_atr_state() +    { +        for (size_t i = 0; i < _gpio_cores.size(); i++) { +            const fe_state_cache_t& fe_state_cache = _fe_states[i]; +            const bool enb_rx = _is_rx(fe_state_cache.state); +            const bool enb_tx = _is_tx(fe_state_cache.state); +            const bool is_rx2 = (fe_state_cache.rx_ant == "RX2"); +            const size_t rxonly = (enb_rx)? ((is_rx2)? STATE_RX_RX2 : STATE_RX_TXRX) : STATE_OFF; +            const size_t txonly = (enb_tx)? (STATE_TX_TXRX) : STATE_OFF; +            size_t fd = STATE_OFF; +            if (enb_rx and enb_tx) fd = STATE_FDX_TXRX; +            if (enb_rx and not enb_tx) fd = rxonly; +            if (not enb_rx and enb_tx) fd = txonly; +            _gpio_cores[i]->set_atr_reg(gpio_atr::ATR_REG_IDLE, STATE_OFF); +            _gpio_cores[i]->set_atr_reg(gpio_atr::ATR_REG_RX_ONLY, rxonly); +            _gpio_cores[i]->set_atr_reg(gpio_atr::ATR_REG_TX_ONLY, txonly); +            _gpio_cores[i]->set_atr_reg(gpio_atr::ATR_REG_FULL_DUPLEX, fd); +        } +    } + +    inline static bool _is_tx(const fe_state_t state) +    { +        return state == TX_STREAMING || state == TXRX_STREAMING; +    } + +    inline static bool _is_rx(const fe_state_t state) +    { +        return state == RX_STREAMING || state == TXRX_STREAMING; +    } + +private: +    struct fe_state_cache_t { +        fe_state_cache_t() : state(NONE_STREAMING), rx_ant("RX2") +        {} +        fe_state_t state; +        std::string rx_ant; +    }; + +    radio_ctrl_core_3000::sptr              _core_ctrl; +    ad9361_ctrl::sptr                       _codec_ctrl; +    std::vector<gpio_core_200_32wo::sptr>   _gpio_cores; +    fpga::core_misc_reg_t&                  _core_misc_reg; +    uhd::dict<size_t, fe_state_cache_t>     _fe_states; +}; + +}}} //namespace + +using namespace uhd::usrp::n230; + +n230_frontend_ctrl::sptr n230_frontend_ctrl::make( +        radio_ctrl_core_3000::sptr core_ctrl, +        fpga::core_misc_reg_t& core_misc_reg, +        ad9361_ctrl::sptr codec_ctrl, +        const std::vector<gpio_core_200_32wo::sptr>& gpio_cores) +{ +    return sptr(new n230_frontend_ctrl_impl(core_ctrl, core_misc_reg, codec_ctrl, gpio_cores)); +} + diff --git a/host/lib/usrp/n230/n230_frontend_ctrl.hpp b/host/lib/usrp/n230/n230_frontend_ctrl.hpp new file mode 100644 index 000000000..c10ac8d09 --- /dev/null +++ b/host/lib/usrp/n230/n230_frontend_ctrl.hpp @@ -0,0 +1,76 @@ +// +// Copyright 2013-2014 Ettus Research LLC +// +// 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/>. +// + +#ifndef INCLUDED_N230_FRONTEND_CTRL_HPP +#define INCLUDED_N230_FRONTEND_CTRL_HPP + +#include "radio_ctrl_core_3000.hpp" +#include "ad9361_ctrl.hpp" +#include "gpio_core_200.hpp" +#include <uhd/types/sensors.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> +#include <vector> +#include "n230_fpga_defs.h" + +namespace uhd { namespace usrp { namespace n230 { + +enum fe_state_t { +    NONE_STREAMING, TX_STREAMING, RX_STREAMING, TXRX_STREAMING +}; + +enum self_test_mode_t { +    LOOPBACK_DISABLED, LOOPBACK_RADIO, LOOPBACK_CODEC +}; + + +class n230_frontend_ctrl : boost::noncopyable +{ +public: +    typedef boost::shared_ptr<n230_frontend_ctrl> sptr; + +    static sptr make( +        radio_ctrl_core_3000::sptr core_ctrl, +        fpga::core_misc_reg_t& core_misc_reg, +        ad9361_ctrl::sptr codec_ctrl, +        const std::vector<gpio_core_200_32wo::sptr>& gpio_cores); + +    virtual ~n230_frontend_ctrl() {} + +    virtual void set_antenna_sel( +        const size_t which, +        const std::string &ant) = 0; + +    virtual void set_stream_state( +        const size_t which, +        const fe_state_t state) = 0; + +    virtual void set_stream_state( +        const fe_state_t fe0_state, +        const fe_state_t fe1_state) = 0; + +    virtual void set_bandsel( +        const std::string& which, +        double freq) = 0; + +    virtual void set_self_test_mode( +        self_test_mode_t mode) = 0; +}; + +}}} //namespace + +#endif /* INCLUDED_N230_FRONTEND_CTRL_HPP */ diff --git a/host/lib/usrp/n230/n230_image_loader.cpp b/host/lib/usrp/n230/n230_image_loader.cpp new file mode 100644 index 000000000..48b41c7a9 --- /dev/null +++ b/host/lib/usrp/n230/n230_image_loader.cpp @@ -0,0 +1,190 @@ +// +// Copyright 2016 Ettus Research LLC +// +// 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 <fstream> +#include <algorithm> +#include <uhd/image_loader.hpp> +#include <uhd/exception.hpp> +#include <uhd/utils/static.hpp> +#include <uhd/utils/byteswap.hpp> +#include <uhd/transport/udp_simple.hpp> +#include <boost/algorithm/string.hpp> +#include <boost/filesystem.hpp> +#include "n230_impl.hpp" +#include "../../../firmware/usrp3/n230/n230_fw_host_iface.h" + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::transport; + +struct xil_bitfile_hdr_t { +    xil_bitfile_hdr_t(): +        valid(false), userid(0), product(""), +        fpga(""), timestamp(""), filesize(0) +    {} + +    bool            valid; +    boost::uint32_t userid; +    std::string     product; +    std::string     fpga; +    std::string     timestamp; +    boost::uint32_t filesize; +}; + +static inline boost::uint16_t _to_uint16(boost::uint8_t* buf) { +    return (static_cast<boost::uint16_t>(buf[0]) << 8) | +           (static_cast<boost::uint16_t>(buf[1]) << 0); +} + +static inline boost::uint32_t _to_uint32(boost::uint8_t* buf) { +    return (static_cast<boost::uint32_t>(buf[0]) << 24) | +           (static_cast<boost::uint32_t>(buf[1]) << 16) | +           (static_cast<boost::uint32_t>(buf[2]) << 8)  | +           (static_cast<boost::uint32_t>(buf[3]) << 0); +} + +static void _parse_bitfile_header(const std::string& filepath, xil_bitfile_hdr_t& hdr) { +    // Read header into memory +    std::ifstream img_file(filepath.c_str(), std::ios::binary); +    static const size_t MAX_HDR_SIZE = 1024; +    boost::scoped_array<char> hdr_buf(new char[MAX_HDR_SIZE]); +    img_file.seekg(0, std::ios::beg); +    img_file.read(hdr_buf.get(), MAX_HDR_SIZE); +    img_file.close(); + +    //Parse header +    size_t ptr = 0; +    boost::uint8_t* buf = reinterpret_cast<boost::uint8_t*>(hdr_buf.get());  //Shortcut + +    boost::uint8_t signature[10] = {0x00, 0x09, 0x0f, 0xf0, 0x0f, 0xf0, 0x0f, 0xf0, 0x0f, 0xf0}; +    if (memcmp(buf, signature, 10) == 0) {  //Validate signature +        ptr += _to_uint16(buf + ptr) + 2; +        ptr += _to_uint16(buf + ptr) + 1; + +        std::string fields[4]; +        for (size_t i = 0; i < 4; i++) { +            size_t key = buf[ptr++] - 'a'; +            boost::uint16_t len = _to_uint16(buf + ptr); ptr += 2; +            fields[key] = std::string(reinterpret_cast<char*>(buf + ptr), size_t(len)); ptr += len; +        } + +        hdr.filesize = _to_uint32(buf + ++ptr); ptr += 4; +        hdr.fpga = fields[1]; +        hdr.timestamp = fields[2] + std::string(" ") + fields[3]; + +        std::vector<std::string> tokens; +        boost::split(tokens, fields[0], boost::is_any_of(";")); +        if (tokens.size() == 3) { +            hdr.product = tokens[0]; +            std::vector<std::string> uidtokens; +            boost::split(uidtokens, tokens[1], boost::is_any_of("=")); +            if (uidtokens.size() == 2 and uidtokens[0] == "UserID") { +                std::stringstream stream; +                stream << uidtokens[1]; +                stream >> std::hex >> hdr.userid; +                hdr.valid = true; +            } +        } +    } +} + +static size_t _send_and_recv( +    udp_simple::sptr xport, +    n230_flash_prog_t& out, n230_flash_prog_t& in) +{ +    static boost::uint32_t seqno = 0; +    out.seq = htonx<boost::uint32_t>(++seqno); +    xport->send(boost::asio::buffer(&out, sizeof(n230_flash_prog_t))); +    size_t len = xport->recv(boost::asio::buffer(&in, udp_simple::mtu), 0.5); +    if (len != sizeof(n230_flash_prog_t) or ntohx<boost::uint32_t>(in.seq) != seqno) { +        throw uhd::io_error("Error communicating with the device."); +    } +    return len; +} + + +static bool n230_image_loader(const image_loader::image_loader_args_t &loader_args){ +    // Run discovery routine and ensure that exactly one N230 is specified +    device_addrs_t devs = usrp::n230::n230_impl::n230_find(loader_args.args); +    if (devs.size() == 0 or !loader_args.load_fpga) return false; +    if (devs.size() > 1) { +        throw uhd::runtime_error("Multiple devices match the specified args. To avoid accidentally updating the " +                                 "wrong device, please narrow the search by specifying a unique \"addr\" argument."); +    } +    device_addr_t dev = devs[0]; + +    // Sanity check the specified bitfile +    if (not boost::filesystem::exists(loader_args.fpga_path)) { +        throw uhd::runtime_error(str(boost::format("The file \"%s\" does not exist.") % loader_args.fpga_path)); +    } +    xil_bitfile_hdr_t hdr; +    _parse_bitfile_header(loader_args.fpga_path, hdr); + +    // Create a UDP communication link +    udp_simple::sptr udp_xport = +        udp_simple::make_connected(dev["addr"],BOOST_STRINGIZE(N230_FW_COMMS_FLASH_PROG_PORT)); + +    if (hdr.valid and hdr.product == "n230") { +        if (hdr.userid != 0x5AFE0000) { +            std::cout << boost::format("Unit: USRP N230 (%s, %s)\n-- FPGA Image: %s\n") +                         % dev["addr"] % dev["serial"] % loader_args.fpga_path; + +            // Write image +            std::ifstream image(loader_args.fpga_path.c_str(), std::ios::binary); +            size_t image_size = boost::filesystem::file_size(loader_args.fpga_path); + +            static const size_t SECTOR_SIZE = 65536; +            static const size_t IMAGE_BASE  = 0x400000; + +            n230_flash_prog_t out, in; +            size_t bytes_written = 0; +            while (bytes_written < image_size) { +                size_t payload_size = std::min<size_t>(image_size - bytes_written, N230_FLASH_COMM_MAX_PAYLOAD_SIZE); +                if (bytes_written % SECTOR_SIZE == 0) { +                    out.flags = htonx<boost::uint32_t>(N230_FLASH_COMM_FLAGS_ACK|N230_FLASH_COMM_CMD_ERASE_FPGA); +                    out.offset = htonx<boost::uint32_t>(bytes_written + IMAGE_BASE); +                    out.size = htonx<boost::uint32_t>(payload_size); +                    _send_and_recv(udp_xport, out, in); +                } +                out.flags = htonx<boost::uint32_t>(N230_FLASH_COMM_FLAGS_ACK|N230_FLASH_COMM_CMD_WRITE_FPGA); +                out.offset = htonx<boost::uint32_t>(bytes_written + IMAGE_BASE); +                out.size = htonx<boost::uint32_t>(payload_size); +                image.read((char*)out.data, payload_size); +                _send_and_recv(udp_xport, out, in); +                bytes_written += ntohx<boost::uint32_t>(in.size); +                std::cout << boost::format("\r-- Loading FPGA image: %d%%") +                             % (int(double(bytes_written) / double(image_size) * 100.0)) +                         << std::flush; +            } +            std::cout << std::endl << "Image loaded successfully." << std::endl; +            return true; +        } else { +            throw uhd::runtime_error("This utility cannot burn a failsafe image!"); +        } +    } else { +        throw uhd::runtime_error(str(boost::format("The file at path \"%s\" is not a valid USRP N230 FPGA image.") +                                     % loader_args.fpga_path)); +    } +} + +UHD_STATIC_BLOCK(register_n230_image_loader){ +    std::string recovery_instructions = "Aborting. Your USRP N230 device will likely boot in safe mode.\n" +                                        "Please re-run this command with the additional \"safe_mode\" device argument\n" +                                        "to recover your device."; + +    image_loader::register_image_loader("n230", n230_image_loader, recovery_instructions); +} diff --git a/host/lib/usrp/n230/n230_impl.cpp b/host/lib/usrp/n230/n230_impl.cpp new file mode 100644 index 000000000..d05ab9ee9 --- /dev/null +++ b/host/lib/usrp/n230/n230_impl.cpp @@ -0,0 +1,551 @@ +// +// Copyright 2014 Ettus Research LLC +// +// 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 "n230_impl.hpp" + +#include "../../../firmware/usrp3/n230/n230_fw_host_iface.h" +#include "../../../firmware/usrp3/n230/n230_fw_defs.h" +#include "../../../firmware/usrp3/include/fw_comm_protocol.h" +#include "usrp3_fw_ctrl_iface.hpp" +#include "validate_subdev_spec.hpp" +#include <uhd/utils/static.hpp> +#include <uhd/transport/if_addrs.hpp> +#include <uhd/transport/udp_zero_copy.hpp> +#include <uhd/usrp/subdev_spec.hpp> +#include <uhd/utils/byteswap.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/types/sensors.hpp> +#include <uhd/types/ranges.hpp> +#include <uhd/usrp/mboard_eeprom.hpp> +#include <uhd/usrp/dboard_eeprom.hpp> +#include <uhd/usrp/gps_ctrl.hpp> +#include <boost/format.hpp> +#include <boost/foreach.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/bind.hpp> +#include <boost/algorithm/string.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/asio/ip/address_v4.hpp> +#include <boost/asio.hpp> //used for htonl and ntohl +#include <boost/make_shared.hpp> +#include "n230_defaults.h" +#include "n230_fpga_defs.h" + +namespace uhd { namespace usrp { namespace n230 { + +using namespace uhd::transport; +namespace asio = boost::asio; + +//---------------------------------------------------------- +// Static device registration with framework +//---------------------------------------------------------- +UHD_STATIC_BLOCK(register_n230_device) +{ +    device::register_device(&n230_impl::n230_find, &n230_impl::n230_make, device::USRP); +} + +//---------------------------------------------------------- +// Device discovery +//---------------------------------------------------------- +uhd::device_addrs_t n230_impl::n230_find(const uhd::device_addr_t &multi_dev_hint) +{ +    //handle the multi-device discovery +    device_addrs_t hints = separate_device_addr(multi_dev_hint); +    if (hints.size() > 1){ +        device_addrs_t found_devices; +        std::string error_msg; +        BOOST_FOREACH(const device_addr_t &hint_i, hints){ +            device_addrs_t found_devices_i = n230_find(hint_i); +            if (found_devices_i.size() != 1) error_msg += str(boost::format( +                "Could not resolve device hint \"%s\" to a single device." +            ) % hint_i.to_string()); +            else found_devices.push_back(found_devices_i[0]); +        } +        if (found_devices.empty()) return device_addrs_t(); +        if (not error_msg.empty()) throw uhd::value_error(error_msg); +        return device_addrs_t(1, combine_device_addrs(found_devices)); +    } + +    //initialize the hint for a single device case +    UHD_ASSERT_THROW(hints.size() <= 1); +    hints.resize(1); //in case it was empty +    device_addr_t hint = hints[0]; +    device_addrs_t n230_addrs; + +    //return an empty list of addresses when type is set to non-n230 +    if (hint.has_key("type") and hint["type"] != "n230") return n230_addrs; + +    //Return an empty list of addresses when a resource is specified, +    //since a resource is intended for a different, non-networked, device. +    if (hint.has_key("resource")) return n230_addrs; + +    //if no address was specified, send a broadcast on each interface +    if (not hint.has_key("addr")) { +        BOOST_FOREACH(const if_addrs_t &if_addrs, get_if_addrs()) { +            //avoid the loopback device +            if (if_addrs.inet == asio::ip::address_v4::loopback().to_string()) continue; + +            //create a new hint with this broadcast address +            device_addr_t new_hint = hint; +            new_hint["addr"] = if_addrs.bcast; + +            //call discover with the new hint and append results +            device_addrs_t new_n230_addrs = n230_find(new_hint); +            n230_addrs.insert(n230_addrs.begin(), +                new_n230_addrs.begin(), new_n230_addrs.end() +            ); +        } +        return n230_addrs; +    } + +    std::vector<std::string> discovered_addrs = +        usrp3::usrp3_fw_ctrl_iface::discover_devices( +            hint["addr"], BOOST_STRINGIZE(N230_FW_COMMS_UDP_PORT), N230_FW_PRODUCT_ID); + +    BOOST_FOREACH(const std::string& addr, discovered_addrs) +    { +        device_addr_t new_addr; +        new_addr["type"] = "n230"; +        new_addr["addr"] = addr; + +        //Attempt a simple 2-way communication with a connected socket. +        //Reason: Although the USRP will respond the broadcast above, +        //we may not be able to communicate directly (non-broadcast). +        udp_simple::sptr ctrl_xport = udp_simple::make_connected(new_addr["addr"], BOOST_STRINGIZE(N230_FW_COMMS_UDP_PORT)); + +        usrp3::usrp3_fw_ctrl_iface::sptr fw_ctrl = usrp3::usrp3_fw_ctrl_iface::make(ctrl_xport, N230_FW_PRODUCT_ID); +        uint32_t compat_reg_addr = fw::reg_addr(fw::WB_SBRB_BASE, fw::RB_ZPU_COMPAT); +        if (fw::get_prod_num(fw_ctrl->peek32(compat_reg_addr)) == fw::PRODUCT_NUM) { +            if (!n230_resource_manager::is_device_claimed(fw_ctrl)) { +                //Not claimed by another process or host +                try { +                    //Try to read the EEPROM to get the name and serial +                    n230_eeprom_manager eeprom_mgr(new_addr["addr"]); +                    const mboard_eeprom_t& eeprom = eeprom_mgr.get_mb_eeprom(); +                    new_addr["name"] = eeprom["name"]; +                    new_addr["serial"] = eeprom["serial"]; +                } +                catch(const std::exception &) +                { +                    //set these values as empty string so the device may still be found +                    //and the filter's below can still operate on the discovered device +                    new_addr["name"] = ""; +                    new_addr["serial"] = ""; +                } +                //filter the discovered device below by matching optional keys +                if ((not hint.has_key("name")    or hint["name"]    == new_addr["name"]) and +                    (not hint.has_key("serial")  or hint["serial"]  == new_addr["serial"])) +                { +                    n230_addrs.push_back(new_addr); +                } +            } +        } +    } + +    return n230_addrs; +} + +/*********************************************************************** + * Make + **********************************************************************/ +device::sptr n230_impl::n230_make(const device_addr_t &device_addr) +{ +    return device::sptr(new n230_impl(device_addr)); +} + +/*********************************************************************** + * n230_impl::ctor + **********************************************************************/ +n230_impl::n230_impl(const uhd::device_addr_t& dev_addr) +{ +    UHD_MSG(status) << "N230 initialization sequence..." << std::endl; +    _dev_args.parse(dev_addr); +    _tree = uhd::property_tree::make(); + +    //TODO: Only supports one motherboard per device class. +    const fs_path mb_path = "/mboards/0"; + +    //Initialize subsystems +    std::vector<std::string> ip_addrs(1, dev_addr["addr"]); +    if (dev_addr.has_key("secondary-addr")) { +        ip_addrs.push_back(dev_addr["secondary-addr"]); +    } +    _resource_mgr = boost::make_shared<n230_resource_manager>(ip_addrs, _dev_args.get_safe_mode()); +    _eeprom_mgr = boost::make_shared<n230_eeprom_manager>(ip_addrs[0]); +    _stream_mgr = boost::make_shared<n230_stream_manager>(_dev_args, _resource_mgr, _tree); + +    //Build property tree +    _initialize_property_tree(mb_path); + +    //Debug loopback mode +    switch(_dev_args.get_loopback_mode()) { +    case n230_device_args_t::LOOPBACK_RADIO: +        UHD_MSG(status) << "DEBUG: Running in TX->RX Radio loopback mode.\n"; +        _resource_mgr->get_frontend_ctrl().set_self_test_mode(LOOPBACK_RADIO); +        break; +    case n230_device_args_t::LOOPBACK_CODEC: +        UHD_MSG(status) << "DEBUG: Running in TX->RX CODEC loopback mode.\n"; +        _resource_mgr->get_frontend_ctrl().set_self_test_mode(LOOPBACK_CODEC); +        break; +    default: +        _resource_mgr->get_frontend_ctrl().set_self_test_mode(LOOPBACK_DISABLED); +        break; +    } +} + +/*********************************************************************** + * n230_impl::dtor + **********************************************************************/ +n230_impl::~n230_impl() +{ +    _stream_mgr.reset(); +    _eeprom_mgr.reset(); +    _resource_mgr.reset(); +    _tree.reset(); +} + +/*********************************************************************** + * n230_impl::get_rx_stream + **********************************************************************/ +rx_streamer::sptr n230_impl::get_rx_stream(const uhd::stream_args_t &args) +{ +    return _stream_mgr->get_rx_stream(args); +} + +/*********************************************************************** + * n230_impl::get_tx_stream + **********************************************************************/ +tx_streamer::sptr n230_impl::get_tx_stream(const uhd::stream_args_t &args) +{ +    return _stream_mgr->get_tx_stream(args); +} + +/*********************************************************************** + * n230_impl::recv_async_msg + **********************************************************************/ +bool n230_impl::recv_async_msg(uhd::async_metadata_t &async_metadata, double timeout) +{ +    return _stream_mgr->recv_async_msg(async_metadata, timeout); +} + +/*********************************************************************** + * _initialize_property_tree + **********************************************************************/ +void n230_impl::_initialize_property_tree(const fs_path& mb_path) +{ +    //------------------------------------------------------------------ +    // General info +    //------------------------------------------------------------------ +    _tree->create<std::string>("/name").set("N230 Device"); + +    _tree->create<std::string>(mb_path / "name").set("N230"); +    _tree->create<std::string>(mb_path / "codename").set("N230"); +    _tree->create<std::string>(mb_path / "dboards").set("none");    //No dboards. + +    _tree->create<std::string>(mb_path / "fw_version").set(str(boost::format("%u.%u") +        % _resource_mgr->get_version(FIRMWARE, COMPAT_MAJOR) +        % _resource_mgr->get_version(FIRMWARE, COMPAT_MINOR))); +    _tree->create<std::string>(mb_path / "fw_version_hash").set(str(boost::format("%s") +        % _resource_mgr->get_version_hash(FIRMWARE))); +    _tree->create<std::string>(mb_path / "fpga_version").set(str(boost::format("%u.%u") +        % _resource_mgr->get_version(FPGA, COMPAT_MAJOR) +        % _resource_mgr->get_version(FPGA, COMPAT_MINOR))); +    _tree->create<std::string>(mb_path / "fpga_version_hash").set(str(boost::format("%s") +        % _resource_mgr->get_version_hash(FPGA))); + +    _tree->create<double>(mb_path / "link_max_rate").set(_resource_mgr->get_max_link_rate()); + +    //------------------------------------------------------------------ +    // EEPROM +    //------------------------------------------------------------------ +    _tree->create<mboard_eeprom_t>(mb_path / "eeprom") +        .set(_eeprom_mgr->get_mb_eeprom())  //Set first... +        .subscribe(boost::bind(&n230_eeprom_manager::write_mb_eeprom, _eeprom_mgr, _1));  //..then enable writer + +    //------------------------------------------------------------------ +    // Create codec nodes +    //------------------------------------------------------------------ +    const fs_path rx_codec_path = mb_path / ("rx_codecs") / "A"; +    _tree->create<std::string>(rx_codec_path / "name") +        .set("N230 RX dual ADC"); +    _tree->create<int>(rx_codec_path / "gains");    //Empty because gains are in frontend + +    const fs_path tx_codec_path = mb_path / ("tx_codecs") / "A"; +    _tree->create<std::string>(tx_codec_path / "name") +        .set("N230 TX dual DAC"); +    _tree->create<int>(tx_codec_path / "gains");    //Empty because gains are in frontend + +    //------------------------------------------------------------------ +    // Create clock and time control nodes +    //------------------------------------------------------------------ +    _tree->create<double>(mb_path / "tick_rate") +        .coerce(boost::bind(&n230_clk_pps_ctrl::set_tick_rate, _resource_mgr->get_clk_pps_ctrl_sptr(), _1)) +        .publish(boost::bind(&n230_clk_pps_ctrl::get_tick_rate, _resource_mgr->get_clk_pps_ctrl_sptr())) +        .subscribe(boost::bind(&n230_stream_manager::update_tick_rate, _stream_mgr, _1)); + +    //Register time now and pps onto available radio cores +    //radio0 is the master +    _tree->create<time_spec_t>(mb_path / "time" / "cmd"); +    _tree->create<time_spec_t>(mb_path / "time" / "now") +        .publish(boost::bind(&time_core_3000::get_time_now, _resource_mgr->get_radio(0).time)); +    _tree->create<time_spec_t>(mb_path / "time" / "pps") +        .publish(boost::bind(&time_core_3000::get_time_last_pps, _resource_mgr->get_radio(0).time)); + +    //Setup time source props +    _tree->create<std::string>(mb_path / "time_source" / "value") +        .subscribe(boost::bind(&n230_impl::_check_time_source, this, _1)) +        .subscribe(boost::bind(&n230_clk_pps_ctrl::set_pps_source, _resource_mgr->get_clk_pps_ctrl_sptr(), _1)) +        .set(n230::DEFAULT_TIME_SRC); +    static const std::vector<std::string> time_sources = boost::assign::list_of("none")("external")("gpsdo"); +    _tree->create<std::vector<std::string> >(mb_path / "time_source" / "options") +        .set(time_sources); + +    //Setup reference source props +    _tree->create<std::string>(mb_path / "clock_source" / "value") +        .subscribe(boost::bind(&n230_impl::_check_clock_source, this, _1)) +        .subscribe(boost::bind(&n230_clk_pps_ctrl::set_clock_source, _resource_mgr->get_clk_pps_ctrl_sptr(), _1)) +        .set(n230::DEFAULT_CLOCK_SRC); +    static const std::vector<std::string> clock_sources = boost::assign::list_of("internal")("external")("gpsdo"); +    _tree->create<std::vector<std::string> >(mb_path / "clock_source" / "options") +        .set(clock_sources); +    _tree->create<sensor_value_t>(mb_path / "sensors" / "ref_locked") +        .publish(boost::bind(&n230_clk_pps_ctrl::get_ref_locked, _resource_mgr->get_clk_pps_ctrl_sptr())); + +    //------------------------------------------------------------------ +    // Create frontend mapping +    //------------------------------------------------------------------ +    _tree->create<subdev_spec_t>(mb_path / "rx_subdev_spec") +        .set(subdev_spec_t()) +        .subscribe(boost::bind(&n230_impl::_update_rx_subdev_spec, this, _1)); +    _tree->create<subdev_spec_t>(mb_path / "tx_subdev_spec") +        .set(subdev_spec_t()) +        .subscribe(boost::bind(&n230_impl::_update_tx_subdev_spec, this, _1)); + +    //------------------------------------------------------------------ +    // Create a fake dboard to put frontends in +    //------------------------------------------------------------------ +    //For completeness we give it a fake EEPROM as well +    dboard_eeprom_t db_eeprom;  //Default state: ID is 0xffff, Version and serial empty +    _tree->create<dboard_eeprom_t>(mb_path / "dboards" / "A" / "rx_eeprom").set(db_eeprom); +    _tree->create<dboard_eeprom_t>(mb_path / "dboards" / "A" / "tx_eeprom").set(db_eeprom); +    _tree->create<dboard_eeprom_t>(mb_path / "dboards" / "A" / "gdb_eeprom").set(db_eeprom); + +    //------------------------------------------------------------------ +    // Create radio specific nodes +    //------------------------------------------------------------------ +    for (size_t radio_instance = 0; radio_instance < fpga::NUM_RADIOS; radio_instance++) { +        _initialize_radio_properties(mb_path, radio_instance); +    } +    //Update tick rate on newly created radio objects +    _tree->access<double>(mb_path / "tick_rate").set(_dev_args.get_master_clock_rate()); + +    //------------------------------------------------------------------ +    // Initialize subdev specs +    //------------------------------------------------------------------ +    subdev_spec_t rx_spec, tx_spec; +    BOOST_FOREACH(const std::string &fe, _tree->list(mb_path / "dboards" / "A" / "rx_frontends")) +    { +        rx_spec.push_back(subdev_spec_pair_t("A", fe)); +    } +    BOOST_FOREACH(const std::string &fe, _tree->list(mb_path / "dboards" / "A" / "tx_frontends")) +    { +        tx_spec.push_back(subdev_spec_pair_t("A", fe)); +    } +    _tree->access<subdev_spec_t>(mb_path / "rx_subdev_spec").set(rx_spec); +    _tree->access<subdev_spec_t>(mb_path / "tx_subdev_spec").set(tx_spec); + +    // GPSDO sensors +    uhd::gps_ctrl::sptr gps_ctrl = _resource_mgr->get_gps_ctrl(); +    if (gps_ctrl and gps_ctrl->gps_detected()) +    { +        BOOST_FOREACH(const std::string &name, gps_ctrl->get_sensors()) +        { +            _tree->create<sensor_value_t>(mb_path / "sensors" / name) +                .publish(boost::bind(&gps_ctrl::get_sensor, gps_ctrl, name)); +        } +    } +} + +/*********************************************************************** + * _initialize_radio_properties + **********************************************************************/ +void n230_impl::_initialize_radio_properties(const fs_path& mb_path, size_t instance) +{ +    radio_resource_t& perif = _resource_mgr->get_radio(instance); + +    //Time +    _tree->access<time_spec_t>(mb_path / "time" / "cmd") +        .subscribe(boost::bind(&radio_ctrl_core_3000::set_time, perif.ctrl, _1)); +    _tree->access<double>(mb_path / "tick_rate") +        .subscribe(boost::bind(&radio_ctrl_core_3000::set_tick_rate, perif.ctrl, _1)); +    _tree->access<time_spec_t>(mb_path / "time" / "now") +        .subscribe(boost::bind(&time_core_3000::set_time_now, perif.time, _1)); +    _tree->access<time_spec_t>(mb_path / "time" / "pps") +        .subscribe(boost::bind(&time_core_3000::set_time_next_pps, perif.time, _1)); + +    //RX DSP +    _tree->access<double>(mb_path / "tick_rate") +        .subscribe(boost::bind(&rx_vita_core_3000::set_tick_rate, perif.framer, _1)) +        .subscribe(boost::bind(&rx_dsp_core_3000::set_tick_rate, perif.ddc, _1)); +    const fs_path rx_dsp_path = mb_path / "rx_dsps" / str(boost::format("%u") % instance); +    _tree->create<meta_range_t>(rx_dsp_path / "rate" / "range") +        .publish(boost::bind(&rx_dsp_core_3000::get_host_rates, perif.ddc)); +    _tree->create<double>(rx_dsp_path / "rate" / "value") +        .coerce(boost::bind(&rx_dsp_core_3000::set_host_rate, perif.ddc, _1)) +        .subscribe(boost::bind(&n230_stream_manager::update_rx_samp_rate, _stream_mgr, instance, _1)) +        .set(n230::DEFAULT_RX_SAMP_RATE); +    _tree->create<double>(rx_dsp_path / "freq" / "value") +        .coerce(boost::bind(&rx_dsp_core_3000::set_freq, perif.ddc, _1)) +        .set(n230::DEFAULT_DDC_FREQ); +    _tree->create<meta_range_t>(rx_dsp_path / "freq" / "range") +        .publish(boost::bind(&rx_dsp_core_3000::get_freq_range, perif.ddc)); +    _tree->create<stream_cmd_t>(rx_dsp_path / "stream_cmd") +        .subscribe(boost::bind(&rx_vita_core_3000::issue_stream_command, perif.framer, _1)); + +    //TX DSP +    _tree->access<double>(mb_path / "tick_rate") +        .subscribe(boost::bind(&tx_dsp_core_3000::set_tick_rate, perif.duc, _1)); +    const fs_path tx_dsp_path = mb_path / "tx_dsps" / str(boost::format("%u") % instance); +    _tree->create<meta_range_t>(tx_dsp_path / "rate" / "range") +        .publish(boost::bind(&tx_dsp_core_3000::get_host_rates, perif.duc)); +    _tree->create<double>(tx_dsp_path / "rate" / "value") +        .coerce(boost::bind(&tx_dsp_core_3000::set_host_rate, perif.duc, _1)) +        .subscribe(boost::bind(&n230_stream_manager::update_tx_samp_rate, _stream_mgr, instance, _1)) +        .set(n230::DEFAULT_TX_SAMP_RATE); +    _tree->create<double>(tx_dsp_path / "freq" / "value") +        .coerce(boost::bind(&tx_dsp_core_3000::set_freq, perif.duc, _1)) +        .set(n230::DEFAULT_DUC_FREQ); +    _tree->create<meta_range_t>(tx_dsp_path / "freq" / "range") +        .publish(boost::bind(&tx_dsp_core_3000::get_freq_range, perif.duc)); + +    //RF Frontend Interfacing +    static const std::vector<std::string> data_directions = boost::assign::list_of("tx")("rx"); +    BOOST_FOREACH(const std::string& direction, data_directions) { +        const std::string key = boost::to_upper_copy(direction) + str(boost::format("%u") % (instance + 1)); +        const fs_path rf_fe_path = mb_path / "dboards" / "A" / (direction + "_frontends") / ((instance==0)?"A":"B"); + +        _tree->create<std::string>(rf_fe_path / "name") +            .set("FE-" + key); +        _tree->create<int>(rf_fe_path / "sensors"); //empty TODO +        BOOST_FOREACH(const std::string &name, ad9361_ctrl::get_gain_names(key)) { +            _tree->create<meta_range_t>(rf_fe_path / "gains" / name / "range") +                .set(ad9361_ctrl::get_gain_range(key)); +            _tree->create<double>(rf_fe_path / "gains" / name / "value") +                .coerce(boost::bind(&ad9361_ctrl::set_gain, _resource_mgr->get_codec_ctrl_sptr(), key, _1)) +                .set(n230::DEFAULT_FE_GAIN); +        } +        _tree->create<std::string>(rf_fe_path / "connection") +            .set("IQ"); +        _tree->create<bool>(rf_fe_path / "enabled") +            .set(true); +        _tree->create<bool>(rf_fe_path / "use_lo_offset") +            .set(false); +        _tree->create<double>(rf_fe_path / "bandwidth" / "value") +            .coerce(boost::bind(&ad9361_ctrl::set_bw_filter, _resource_mgr->get_codec_ctrl_sptr(), key, _1)) +            .set(n230::DEFAULT_FE_BW); +        _tree->create<meta_range_t>(rf_fe_path / "bandwidth" / "range") +            .publish(boost::bind(&ad9361_ctrl::get_bw_filter_range, key)); +        _tree->create<double>(rf_fe_path / "freq" / "value") +            .set(n230::DEFAULT_FE_FREQ) +            .coerce(boost::bind(&ad9361_ctrl::tune, _resource_mgr->get_codec_ctrl_sptr(), key, _1)) +            .subscribe(boost::bind(&n230_frontend_ctrl::set_bandsel, _resource_mgr->get_frontend_ctrl_sptr(), key, _1)); +        _tree->create<meta_range_t>(rf_fe_path / "freq" / "range") +            .publish(boost::bind(&ad9361_ctrl::get_rf_freq_range)); + +        //User settings +        _tree->create<uhd::wb_iface::sptr>(rf_fe_path / "user_settings" / "iface") +            .set(perif.user_settings); + +        //Setup antenna stuff +        if (key[0] == 'R') { +            static const std::vector<std::string> ants = boost::assign::list_of("TX/RX")("RX2"); +            _tree->create<std::vector<std::string> >(rf_fe_path / "antenna" / "options") +                .set(ants); +            _tree->create<std::string>(rf_fe_path / "antenna" / "value") +                .subscribe(boost::bind(&n230_frontend_ctrl::set_antenna_sel, _resource_mgr->get_frontend_ctrl_sptr(), instance, _1)) +                .set("RX2"); +        } +        if (key[0] == 'T') { +            static const std::vector<std::string> ants(1, "TX/RX"); +            _tree->create<std::vector<std::string> >(rf_fe_path / "antenna" / "options") +                .set(ants); +            _tree->create<std::string>(rf_fe_path / "antenna" / "value") +                .set("TX/RX"); +        } +    } +} + +void n230_impl::_update_rx_subdev_spec(const uhd::usrp::subdev_spec_t &spec) +{ +    //sanity checking +    if (spec.size()) validate_subdev_spec(_tree, spec, "rx"); +    UHD_ASSERT_THROW(spec.size() <= fpga::NUM_RADIOS); + +    if (spec.size() > 0) { +        UHD_ASSERT_THROW(spec[0].db_name == "A"); +        UHD_ASSERT_THROW(spec[0].sd_name == "A"); +    } +    if (spec.size() > 1) { +        //TODO we can support swapping at a later date, only this combo is supported +        UHD_ASSERT_THROW(spec[1].db_name == "A"); +        UHD_ASSERT_THROW(spec[1].sd_name == "B"); +    } + +    _stream_mgr->update_stream_states(); +} + +void n230_impl::_update_tx_subdev_spec(const uhd::usrp::subdev_spec_t &spec) +{ +    //sanity checking +    if (spec.size()) validate_subdev_spec(_tree, spec, "tx"); +    UHD_ASSERT_THROW(spec.size() <= fpga::NUM_RADIOS); + +    if (spec.size() > 0) { +        UHD_ASSERT_THROW(spec[0].db_name == "A"); +        UHD_ASSERT_THROW(spec[0].sd_name == "A"); +    } +    if (spec.size() > 1) { +        //TODO we can support swapping at a later date, only this combo is supported +        UHD_ASSERT_THROW(spec[1].db_name == "A"); +        UHD_ASSERT_THROW(spec[1].sd_name == "B"); +    } + +    _stream_mgr->update_stream_states(); +} + +void n230_impl::_check_time_source(std::string source) +{ +    if (source == "gpsdo") +    { +        uhd::gps_ctrl::sptr gps_ctrl = _resource_mgr->get_gps_ctrl(); +        if (not (gps_ctrl and gps_ctrl->gps_detected())) +            throw uhd::runtime_error("GPSDO time source not available"); +    } +} + +void n230_impl::_check_clock_source(std::string source) +{ +    if (source == "gpsdo") +    { +        uhd::gps_ctrl::sptr gps_ctrl = _resource_mgr->get_gps_ctrl(); +        if (not (gps_ctrl and gps_ctrl->gps_detected())) +            throw uhd::runtime_error("GPSDO clock source not available"); +    } +} + +}}} //namespace diff --git a/host/lib/usrp/n230/n230_impl.hpp b/host/lib/usrp/n230/n230_impl.hpp new file mode 100644 index 000000000..b644dd8a3 --- /dev/null +++ b/host/lib/usrp/n230/n230_impl.hpp @@ -0,0 +1,81 @@ +// +// Copyright 2014 Ettus Research LLC +// +// 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/>. +// + +#ifndef INCLUDED_N230_IMPL_HPP +#define INCLUDED_N230_IMPL_HPP + +#include <uhd/property_tree.hpp> +#include <uhd/device.hpp> +#include <uhd/usrp/subdev_spec.hpp> + +#include "n230_device_args.hpp" +#include "n230_eeprom_manager.hpp" +#include "n230_resource_manager.hpp" +#include "n230_stream_manager.hpp" +#include "recv_packet_demuxer_3000.hpp" + +namespace uhd { namespace usrp { namespace n230 { + +class n230_impl : public uhd::device +{ +public: //Functions +    // ctor and dtor +    n230_impl(const uhd::device_addr_t& device_addr); +    virtual ~n230_impl(void); + +    //--------------------------------------------------------------------- +    // uhd::device interface +    // +    static sptr make(const uhd::device_addr_t &hint, size_t which = 0); + +    //! Make a new receive streamer from the streamer arguments +    virtual uhd::rx_streamer::sptr get_rx_stream(const uhd::stream_args_t &args); + +    //! Make a new transmit streamer from the streamer arguments +    virtual uhd::tx_streamer::sptr get_tx_stream(const uhd::stream_args_t &args); + +    //!Receive and asynchronous message from the device. +    virtual bool recv_async_msg(uhd::async_metadata_t &async_metadata, double timeout = 0.1); + +    //!Registration methods the discovery and factory system. +    //[static void register_device(const find_t &find, const make_t &make)] +    static uhd::device_addrs_t n230_find(const uhd::device_addr_t &hint); +    static uhd::device::sptr n230_make(const uhd::device_addr_t &device_addr); +    // +    //--------------------------------------------------------------------- + +    typedef uhd::transport::bounded_buffer<uhd::async_metadata_t> async_md_type; + +private:    //Functions +    void _initialize_property_tree(const fs_path& mb_path); +    void _initialize_radio_properties(const fs_path& mb_path, size_t instance); + +    void _update_rx_subdev_spec(const uhd::usrp::subdev_spec_t &); +    void _update_tx_subdev_spec(const uhd::usrp::subdev_spec_t &); +    void _check_time_source(std::string); +    void _check_clock_source(std::string); + +private:    //Classes and Members +    n230_device_args_t                        _dev_args; +    boost::shared_ptr<n230_resource_manager>  _resource_mgr; +    boost::shared_ptr<n230_eeprom_manager>    _eeprom_mgr; +    boost::shared_ptr<n230_stream_manager>    _stream_mgr; +}; + +}}} //namespace + +#endif /* INCLUDED_N230_IMPL_HPP */ diff --git a/host/lib/usrp/n230/n230_resource_manager.cpp b/host/lib/usrp/n230/n230_resource_manager.cpp new file mode 100644 index 000000000..7f1e9a6f0 --- /dev/null +++ b/host/lib/usrp/n230/n230_resource_manager.cpp @@ -0,0 +1,529 @@ +// +// Copyright 2013 Ettus Research LLC +// +// 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 "n230_resource_manager.hpp" + +#include "../../../firmware/usrp3/n230/n230_fw_host_iface.h" +#include "../../../firmware/usrp3/n230/n230_fw_defs.h" +#include "usrp3_fw_ctrl_iface.hpp" +#include <uhd/transport/if_addrs.hpp> +#include <uhd/transport/udp_zero_copy.hpp> +#include <uhd/utils/byteswap.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/utils/images.hpp> +#include <uhd/utils/platform.hpp> +#include <uhd/utils/paths.hpp> +#include <boost/format.hpp> +#include <boost/thread.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/functional/hash.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/make_shared.hpp> + +#define IF_DATA_I_MASK  0xFFF00000 +#define IF_DATA_Q_MASK  0x0000FFF0 + +namespace uhd { namespace usrp { namespace n230 { + +//Constants +static const uint8_t N230_HOST_SRC_ADDR_ETH0 = 0; +static const uint8_t N230_HOST_SRC_ADDR_ETH1 = 1; +static const uint8_t N230_HOST_DEST_ADDR     = 2; + +static const uint8_t N230_ETH0_IFACE_ID  = 0; +static const uint8_t N230_ETH1_IFACE_ID  = 1; + +class n230_ad9361_client_t : public ad9361_params { +public: +    ~n230_ad9361_client_t() {} +    double get_band_edge(frequency_band_t band) { +        switch (band) { +            case AD9361_RX_BAND0:   return 2.2e9; +            case AD9361_RX_BAND1:   return 4.0e9; +            case AD9361_TX_BAND0:   return 2.5e9; +            default:                return 0; +        } +    } +    clocking_mode_t get_clocking_mode() { +        return AD9361_XTAL_N_CLK_PATH; +    } +    digital_interface_mode_t get_digital_interface_mode() { +        return AD9361_DDR_FDD_LVDS; +    } +    digital_interface_delays_t get_digital_interface_timing() { +        digital_interface_delays_t delays; +        delays.rx_clk_delay = 0; +        delays.rx_data_delay = 0; +        delays.tx_clk_delay = 0; +        delays.tx_data_delay = 2; +        return delays; +    } +}; + +n230_resource_manager::n230_resource_manager( +    const std::vector<std::string> ip_addrs, +    const bool safe_mode +) : +    _safe_mode(safe_mode), +    _last_host_enpoint(0) +{ +    if (_safe_mode) UHD_MSG(warning) << "Initializing device in safe mode\n"; +    UHD_MSG(status) << "Setup basic communication...\n"; + +    //Discover ethernet interfaces +    BOOST_FOREACH(const std::string& addr, ip_addrs) { +        n230_eth_conn_t conn_iface; +        conn_iface.ip_addr = addr; + +        boost::uint32_t iface_id = usrp3::usrp3_fw_ctrl_iface::get_iface_id( +            conn_iface.ip_addr, BOOST_STRINGIZE(N230_FW_COMMS_UDP_PORT), N230_FW_PRODUCT_ID); +        switch (iface_id) { +            case N230_ETH0_IFACE_ID: conn_iface.type = ETH0; break; +            case N230_ETH1_IFACE_ID: conn_iface.type = ETH1; break; +            default: throw uhd::runtime_error("N230 Initialization Error: Could not detect iface.)"); +        } +        _eth_conns.push_back(conn_iface); +    } +    if (_eth_conns.size() < 1) { +        throw uhd::runtime_error("N230 Initialization Error: No eth interfaces specified.)"); +    } + +    //Create firmware communication interface +    _fw_ctrl = usrp3::usrp3_fw_ctrl_iface::make( +        transport::udp_simple::make_connected( +            _get_conn(PRI_ETH).ip_addr, BOOST_STRINGIZE(N230_FW_COMMS_UDP_PORT)), N230_FW_PRODUCT_ID); +    if (_fw_ctrl.get() == NULL) { +        throw uhd::runtime_error("N230 Initialization Error: Could not create n230_ctrl_iface.)"); +    } +    _check_fw_compat(); + +    //Start the device claimer +    _claimer_task = uhd::task::make(boost::bind(&n230_resource_manager::_claimer_loop, this)); + +    //Create common settings interface +    const sid_t core_sid = _generate_sid(CORE, _get_conn(PRI_ETH).type); + +    transport::udp_zero_copy::buff_params dummy_out_params; +    transport::zero_copy_if::sptr core_xport = +        _create_transport(_get_conn(PRI_ETH), core_sid, device_addr_t(), dummy_out_params); +    if (core_xport.get() == NULL) { +        throw uhd::runtime_error("N230 Initialization Error: Could not create settings transport.)"); +    } +    _core_ctrl = radio_ctrl_core_3000::make( +        fpga::CVITA_BIG_ENDIAN, core_xport, core_xport, core_sid.get()); +    if (_core_ctrl.get() == NULL) { +        throw uhd::runtime_error("N230 Initialization Error: Could not create settings ctrl.)"); +    } +    _check_fpga_compat(); + +    UHD_MSG(status) << boost::format("Version signatures... Firmware:%s FPGA:%s...\n") +        % _fw_version.get_hash_str() % _fpga_version.get_hash_str(); + +    _core_radio_ctrl_reg.initialize(*_core_ctrl, true /*flush*/); +    _core_misc_reg.initialize(*_core_ctrl, true /*flush*/); +    _core_pps_sel_reg.initialize(*_core_ctrl, true /*flush*/); +    _core_status_reg.initialize(*_core_ctrl); + +    //Create common SPI interface +    _core_spi_ctrl = n230_core_spi_core::make(_core_ctrl); +    if (_core_spi_ctrl.get() == NULL) { +        throw uhd::runtime_error("N230 Initialization Error: Could not create SPI ctrl.)"); +    } + +    //Create Catalina interface +    UHD_MSG(status) << "Initializing CODEC...\n"; +    _codec_ctrl = ad9361_ctrl::make_spi( +        boost::make_shared<n230_ad9361_client_t>(), _core_spi_ctrl, fpga::AD9361_SPI_SLAVE_NUM); +    if (_codec_ctrl.get() == NULL) { +        throw uhd::runtime_error("N230 Initialization Error: Could not create Catalina ctrl.)"); +    } +    _codec_ctrl->set_clock_rate(fpga::CODEC_DEFAULT_CLK_RATE); + +    //Create AD4001 interface +    _ref_pll_ctrl = boost::make_shared<n230_ref_pll_ctrl>(_core_spi_ctrl); +    if (_ref_pll_ctrl.get() == NULL) { +        throw uhd::runtime_error("N230 Initialization Error: Could not create ADF4001 ctrl.)"); +    } + +    //Reset SERDES interface and synchronize to frame sync from AD9361 +    _reset_codec_digital_interface(); + +    std::vector<time_core_3000::sptr> time_cores; +    std::vector<gpio_core_200_32wo::sptr> gpio_cores; +    for (size_t i = 0; i < fpga::NUM_RADIOS; i++) { +        _initialize_radio(i); +        time_cores.push_back(_radios[i].time); +        gpio_cores.push_back(_radios[i].gpio_atr); +    } + +    //Create clock and PPS control interface +    _clk_pps_ctrl = n230_clk_pps_ctrl::make( +        _codec_ctrl, _ref_pll_ctrl, _core_misc_reg, _core_pps_sel_reg, _core_status_reg, time_cores); +    if (_clk_pps_ctrl.get() == NULL) { +        throw uhd::runtime_error("N230 Initialization Error: Could not create clock and PPS ctrl.)"); +    } + +    //Create front-end control interface +    _frontend_ctrl = n230_frontend_ctrl::make(_core_ctrl, _core_misc_reg, _codec_ctrl, gpio_cores); +    if (_frontend_ctrl.get() == NULL) { +        throw uhd::runtime_error("N230 Initialization Error: Could not create front-end ctrl.)"); +    } + +    //Create GPSDO interface +    const sid_t gps_uart_sid = _generate_sid(GPS_UART, _get_conn(PRI_ETH).type); +    transport::zero_copy_if::sptr gps_uart_xport = +        _create_transport(_get_conn(PRI_ETH), gps_uart_sid, device_addr_t(), dummy_out_params); +    _gps_uart = n230_uart::make(gps_uart_xport, uhd::htonx(gps_uart_sid.get())); +    _gps_uart->set_baud_divider(fpga::BUS_CLK_RATE/115200); +    _gps_uart->write_uart("\n"); //cause the baud and response to be setup +    boost::this_thread::sleep(boost::posix_time::seconds(1)); //allow for a little propagation +    if ((_core_ctrl->peek32(fpga::RB_CORE_STATUS) & 0xff) != fpga::GPSDO_ST_NONE) +    { +        UHD_MSG(status) << "Detecting GPSDO.... " << std::flush; +        try { +            _gps_ctrl = gps_ctrl::make(_gps_uart); +        } catch(std::exception &e) { +            UHD_MSG(error) << "An error occurred making GPSDO control: " << e.what() << std::endl; +        } +        if (not (_gps_ctrl and _gps_ctrl->gps_detected())) { +            _core_ctrl->poke32(fpga::sr_addr(fpga::SR_CORE_GPSDO_ST), fpga::GPSDO_ST_NONE); +        } +    } + +    //Perform data self-tests +    _frontend_ctrl->set_stream_state(TXRX_STREAMING, TXRX_STREAMING); +    for (size_t i = 0; i < fpga::NUM_RADIOS; i++) { +        _frontend_ctrl->set_self_test_mode(LOOPBACK_RADIO); +        bool radio_selftest_pass = _radio_data_loopback_self_test(_radios[i].ctrl); +        if (!radio_selftest_pass) { +            throw uhd::runtime_error("N230 Initialization Error: Data loopback test failed.)"); +        } + +        _frontend_ctrl->set_self_test_mode(LOOPBACK_CODEC); +        bool codec_selftest_pass = _radio_data_loopback_self_test(_radios[i].ctrl); +        if (!codec_selftest_pass) { +            throw uhd::runtime_error("N230 Initialization Error: Codec loopback test failed.)"); +        } +    } +    _frontend_ctrl->set_self_test_mode(LOOPBACK_DISABLED); +    _frontend_ctrl->set_stream_state(NONE_STREAMING, NONE_STREAMING); +} + +n230_resource_manager::~n230_resource_manager() +{ +    _claimer_task.reset(); +    {   //Critical section +        boost::mutex::scoped_lock(_claimer_mutex); +        _fw_ctrl->poke32(N230_FW_HOST_SHMEM_OFFSET(claim_time), 0); +        _fw_ctrl->poke32(N230_FW_HOST_SHMEM_OFFSET(claim_src), 0); +    } +} + +transport::zero_copy_if::sptr n230_resource_manager::create_transport( +    n230_data_dir_t direction, +    size_t radio_instance, +    const device_addr_t ¶ms, +    sid_t& sid_pair, +    transport::udp_zero_copy::buff_params& buff_out_params) +{ +    const n230_eth_conn_t& conn = _get_conn((radio_instance==1)?SEC_ETH:PRI_ETH); +    const sid_t temp_sid_pair = +        _generate_sid(direction==RX_DATA?RADIO_RX_DATA:RADIO_TX_DATA, conn.type, radio_instance); +    transport::zero_copy_if::sptr xport = _create_transport(conn, temp_sid_pair, params, buff_out_params); +    if (xport.get() == NULL) { +        throw uhd::runtime_error("N230 Create Data Transport: Could not create data transport.)"); +    } else { +        sid_pair = temp_sid_pair; +    } +    return xport; +} + +bool n230_resource_manager::is_device_claimed(uhd::usrp::usrp3::usrp3_fw_ctrl_iface::sptr fw_ctrl) +{ +    boost::mutex::scoped_lock(_claimer_mutex); + +    //If timed out then device is definitely unclaimed +    if (fw_ctrl->peek32(N230_FW_HOST_SHMEM_OFFSET(claim_status)) == 0) +        return false; + +    //otherwise check claim src to determine if another thread with the same src has claimed the device +    return fw_ctrl->peek32(N230_FW_HOST_SHMEM_OFFSET(claim_src)) != get_process_hash(); +} + +void n230_resource_manager::_claimer_loop() +{ +    {   //Critical section +        boost::mutex::scoped_lock(_claimer_mutex); +        _fw_ctrl->poke32(N230_FW_HOST_SHMEM_OFFSET(claim_time), time(NULL)); +        _fw_ctrl->poke32(N230_FW_HOST_SHMEM_OFFSET(claim_src), get_process_hash()); +    } +    boost::this_thread::sleep(boost::posix_time::milliseconds(N230_CLAIMER_TIMEOUT_IN_MS / 2)); +} + +void n230_resource_manager::_initialize_radio(size_t instance) +{ +    radio_resource_t& radio = _radios[instance]; + +    //Create common settings interface +    const sid_t ctrl_sid = _generate_sid(RADIO_CONTROL, _get_conn(PRI_ETH).type, instance); +    transport::udp_zero_copy::buff_params buff_out_params; +    transport::zero_copy_if::sptr ctrl_xport = +        _create_transport(_get_conn(PRI_ETH), ctrl_sid, device_addr_t(), buff_out_params); +    if (ctrl_xport.get() == NULL) { +        throw uhd::runtime_error("N230 Initialization Error: Could not create radio transport.)"); +    } +    radio.ctrl = radio_ctrl_core_3000::make( +        fpga::CVITA_BIG_ENDIAN, ctrl_xport, ctrl_xport, ctrl_sid.get()); +    if (radio.ctrl.get() == NULL) { +        throw uhd::runtime_error("N230 Initialization Error: Could not create radio ctrl.)"); +    } + +    //Perform register loopback test to verify the radio clock +    bool reg_selftest_pass = _radio_register_loopback_self_test(radio.ctrl); +    if (!reg_selftest_pass) { +        throw uhd::runtime_error("N230 Initialization Error: Register loopback test failed.)"); +    } + +    //Write-only ATR interface +    radio.gpio_atr = gpio_core_200_32wo::make(radio.ctrl, fpga::sr_addr(fpga::SR_RADIO_ATR)); + +    //Core VITA time interface +    time_core_3000::readback_bases_type time_bases; +    time_bases.rb_now = fpga::rb_addr(fpga::RB_RADIO_TIME_NOW); +    time_bases.rb_pps = fpga::rb_addr(fpga::RB_RADIO_TIME_PPS); +    radio.time = time_core_3000::make(radio.ctrl, fpga::sr_addr(fpga::SR_RADIO_TIME), time_bases); +    if (radio.time.get() == NULL) { +        throw uhd::runtime_error("N230 Initialization Error: Could not create time core.)"); +    } + +    //RX DSP +    radio.framer = rx_vita_core_3000::make( +        radio.ctrl, fpga::sr_addr(fpga::SR_RADIO_RX_CTRL)); +    radio.ddc = rx_dsp_core_3000::make(radio.ctrl, fpga::sr_addr(fpga::SR_RADIO_RX_DSP), true /*old DDC?*/); +    if (radio.framer.get() == NULL || radio.ddc.get() == NULL) { +        throw uhd::runtime_error("N230 Initialization Error: Could not create RX DSP interface.)"); +    } +    radio.ddc->set_link_rate(fpga::N230_LINK_RATE_BPS); + +    //TX DSP +    radio.deframer = tx_vita_core_3000::make(radio.ctrl, fpga::sr_addr(fpga::SR_RADIO_TX_CTRL)); +    radio.duc = tx_dsp_core_3000::make(radio.ctrl, fpga::sr_addr(fpga::SR_RADIO_TX_DSP)); +    if (radio.deframer.get() == NULL || radio.duc.get() == NULL) { +        throw uhd::runtime_error("N230 Initialization Error: Could not create RX DSP interface.)"); +    } +    radio.duc->set_link_rate(fpga::N230_LINK_RATE_BPS); + +    //User settings +    radio.user_settings = user_settings_core_3000::make(radio.ctrl, +        fpga::sr_addr(fpga::SR_RADIO_USER_SR), fpga::rb_addr(fpga::SR_RADIO_USER_RB)); +    if (radio.user_settings.get() == NULL) { +        throw uhd::runtime_error("N230 Initialization Error: Could not create user settings bus.)"); +    } +} + +boost::uint8_t xb_ep_to_sid(fpga::xb_endpoint_t ep) { +    return static_cast<boost::uint8_t>(ep) << 4; +} + +const sid_t n230_resource_manager::_generate_sid(const n230_endpoint_t type, const n230_eth_port_t xport, size_t instance) +{ +    fpga::xb_endpoint_t xb_dest_ep; +    boost::uint8_t sid_dest_ep = 0; +    fpga::xb_endpoint_t xb_ret_ep = (xport == ETH1) ? fpga::N230_XB_DST_E1 : fpga::N230_XB_DST_E0; +    boost::uint8_t sid_ret_addr = (xport == ETH1) ? N230_HOST_SRC_ADDR_ETH1 : N230_HOST_SRC_ADDR_ETH0; + +    if (type == CORE or type == GPS_UART) { +        //Non-radio endpoints +        xb_dest_ep = (type == CORE) ? fpga::N230_XB_DST_GCTRL : fpga::N230_XB_DST_UART; +        sid_dest_ep = xb_ep_to_sid(xb_dest_ep); +    } else { +        //Radio endpoints +        xb_dest_ep = (instance == 1) ? fpga::N230_XB_DST_R1 : fpga::N230_XB_DST_R0; +        sid_dest_ep = xb_ep_to_sid(xb_dest_ep); +        switch (type) { +        case RADIO_TX_DATA: +            sid_dest_ep |= fpga::RADIO_DATA_SUFFIX; +            break; +        case RADIO_RX_DATA: +            sid_dest_ep |= fpga::RADIO_FC_SUFFIX; +            break; +        default: +            sid_dest_ep |= fpga::RADIO_CTRL_SUFFIX; +            break; +        } +    } + +    //Increment last host logical endpoint +    sid_t sid(sid_ret_addr, ++_last_host_enpoint, N230_HOST_DEST_ADDR, sid_dest_ep); + +    //Program the crossbar addr +    _fw_ctrl->poke32(fw::reg_addr(fw::WB_SBRB_BASE, fw::SR_ZPU_XB_LOCAL), sid.get_dst_addr()); +    // Program CAM entry for returning packets to us +    // This type of packet does not match the XB_LOCAL address and is looked up in the lower half of the CAM +    _fw_ctrl->poke32(fw::reg_addr(fw::WB_XB_SBRB_BASE, sid.get_src_addr()), static_cast<boost::uint32_t>(xb_ret_ep)); +    // Program CAM entry for outgoing packets matching a N230 resource (for example a Radio) +    // This type of packet does matches the XB_LOCAL address and is looked up in the upper half of the CAM +    _fw_ctrl->poke32(fw::reg_addr(fw::WB_XB_SBRB_BASE, 256 + sid.get_dst_endpoint()), static_cast<boost::uint32_t>(xb_dest_ep)); + +    return sid; +} + +transport::zero_copy_if::sptr n230_resource_manager::_create_transport( +    const n230_eth_conn_t& eth_conn, +    const sid_t& sid, const device_addr_t &buff_params, +    transport::udp_zero_copy::buff_params& buff_params_out) +{ +    transport::zero_copy_xport_params default_buff_args; +    default_buff_args.recv_frame_size = transport::udp_simple::mtu; +    default_buff_args.send_frame_size = transport::udp_simple::mtu; +    default_buff_args.num_recv_frames = 32; +    default_buff_args.num_send_frames = 32; + +    transport::zero_copy_if::sptr xport = transport::udp_zero_copy::make( +        eth_conn.ip_addr, boost::lexical_cast<std::string>(fpga::CVITA_UDP_PORT), +        default_buff_args, buff_params_out, buff_params); + +    if (xport.get()) { +        _program_dispatcher(*xport, eth_conn.type, sid); +    } +    return xport; +} + +void n230_resource_manager::_program_dispatcher( +    transport::zero_copy_if& xport, const n230_eth_port_t port, const sid_t& sid) +{ +    //Send a mini packet with SID into the ZPU +    //ZPU will reprogram the ethernet framer +    transport::managed_send_buffer::sptr buff = xport.get_send_buff(); +    buff->cast<boost::uint32_t *>()[0] = 0; //eth dispatch looks for != 0 +    buff->cast<boost::uint32_t *>()[1] = uhd::htonx(sid.get()); +    buff->commit(8); +    buff.reset(); + +    //reprogram the ethernet dispatcher's udp port (should be safe to always set) +    uint32_t disp_base_offset = +        ((port == ETH1) ? fw::SR_ZPU_ETHINT1 : fw::SR_ZPU_ETHINT0) + fw::SR_ZPU_ETHINT_DISPATCHER_BASE; +    _fw_ctrl->poke32(fw::reg_addr(fw::WB_SBRB_BASE, disp_base_offset + fw::ETH_FRAMER_SRC_UDP_PORT), fpga::CVITA_UDP_PORT); + +    //Do a peek to an arbitrary address to guarantee that the +    //ethernet framer has been programmed before we return. +    _fw_ctrl->peek32(0); +} + +void n230_resource_manager::_reset_codec_digital_interface() +{ +    //Set timing registers +    _core_ctrl->poke32(fpga::sr_addr(fpga::SR_CORE_DATA_DELAY), fpga::CODEC_DATA_DELAY); +    _core_ctrl->poke32(fpga::sr_addr(fpga::SR_CORE_CLK_DELAY), fpga::CODEC_CLK_DELAY); + +    _core_radio_ctrl_reg.write(fpga::core_radio_ctrl_reg_t::CODEC_ARST, 1); +    boost::this_thread::sleep(boost::posix_time::milliseconds(10)); +    _core_radio_ctrl_reg.write(fpga::core_radio_ctrl_reg_t::CODEC_ARST, 0); +} + +bool n230_resource_manager::_radio_register_loopback_self_test(wb_iface::sptr iface) +{ +    bool test_fail = false; +    size_t hash = static_cast<size_t>(time(NULL)); +    for (size_t i = 0; i < 100; i++) { +        boost::hash_combine(hash, i); +        iface->poke32(fpga::sr_addr(fpga::SR_RADIO_TEST), boost::uint32_t(hash)); +        test_fail = iface->peek32(fpga::rb_addr(fpga::RB_RADIO_TEST)) != boost::uint32_t(hash); +        if (test_fail) break; //exit loop on any failure +    } +    return !test_fail; +} + +bool n230_resource_manager::_radio_data_loopback_self_test(wb_iface::sptr iface) +{ +   bool test_fail = false; +    size_t hash = size_t(time(NULL)); +    for (size_t i = 0; i < 100; i++) { +        boost::hash_combine(hash, i); +        const boost::uint32_t word32 = boost::uint32_t(hash) & (IF_DATA_I_MASK | IF_DATA_Q_MASK); +        iface->poke32(fpga::sr_addr(fpga::SR_RADIO_CODEC_IDLE), word32); +        iface->peek64(fpga::rb_addr(fpga::RB_RADIO_CODEC_DATA)); //block until request completes +        boost::this_thread::sleep(boost::posix_time::microseconds(100)); //wait for loopback to propagate through codec +        const boost::uint64_t rb_word64 = iface->peek64(fpga::rb_addr(fpga::RB_RADIO_CODEC_DATA)); +        const boost::uint32_t rb_tx = boost::uint32_t(rb_word64 >> 32); +        const boost::uint32_t rb_rx = boost::uint32_t(rb_word64 & 0xffffffff); +        test_fail = word32 != rb_tx or word32 != rb_rx; +        if (test_fail) +            UHD_MSG(fastpath) << boost::format("mismatch (exp:%x, got:%x and %x)... ") % word32 % rb_tx % rb_rx; +            break; //exit loop on any failure +        } + +    /* Zero out the idle data. */ +    iface->poke32(fpga::sr_addr(fpga::SR_RADIO_CODEC_IDLE), 0); +    return !test_fail; +} + +void n230_resource_manager::_check_fw_compat() +{ +    boost::uint32_t compat_num = _fw_ctrl->peek32(N230_FW_HOST_SHMEM_OFFSET(fw_compat_num)); +    _fw_version.compat_major = compat_num >> 16; +    _fw_version.compat_minor = compat_num; +    _fw_version.version_hash = _fw_ctrl->peek32(N230_FW_HOST_SHMEM_OFFSET(fw_version_hash)); + +    if (_fw_version.compat_major != N230_FW_COMPAT_NUM_MAJOR){ +        throw uhd::runtime_error(str(boost::format( +            "Expected firmware compatibility number %d.x, but got %d.%d\n" +            "The firmware build is not compatible with the host code build.\n" +            "%s" +            ) % static_cast<boost::uint32_t>(N230_FW_COMPAT_NUM_MAJOR) +              % static_cast<boost::uint32_t>(_fw_version.compat_major) +              % static_cast<boost::uint32_t>(_fw_version.compat_minor) +              % print_utility_error("uhd_images_downloader.py"))); +    } +} + +void n230_resource_manager::_check_fpga_compat() +{ +    const boost::uint64_t compat = _core_ctrl->peek64(fpga::rb_addr(fpga::RB_CORE_SIGNATUE)); +    const boost::uint32_t signature = boost::uint32_t(compat >> 32); +    const boost::uint16_t product_id = boost::uint8_t(compat >> 24); +    _fpga_version.compat_major = static_cast<boost::uint8_t>(compat >> 16); +    _fpga_version.compat_minor = static_cast<boost::uint16_t>(compat); + +    const boost::uint64_t version_hash = _core_ctrl->peek64(fpga::rb_addr(fpga::RB_CORE_VERSION_HASH)); +    _fpga_version.version_hash = boost::uint32_t(version_hash); + +    if (signature != 0x0ACE0BA5E || product_id != fpga::RB_N230_PRODUCT_ID) +        throw uhd::runtime_error("Signature check failed. Please contact support."); + +    bool is_safe_image = (_fpga_version.compat_major > fpga::RB_N230_COMPAT_SAFE); + +    if (is_safe_image && !_safe_mode) { +        throw uhd::runtime_error( +            "The device appears to have the failsafe FPGA image loaded\n" +            "This could have happened because the production FPGA image in the flash was either corrupt or non-existent\n" +            "To remedy this error, please burn a valid FPGA image to the flash.\n" +            "To continue using the failsafe image with UHD, create the UHD device with the \"safe_mode\" device arg.\n" +            "Radio functionality/performance not guaranteed when operating in safe mode.\n"); +    } else if (_fpga_version.compat_major != fpga::RB_N230_COMPAT_MAJOR && !is_safe_image) { +        throw uhd::runtime_error(str(boost::format( +            "Expected FPGA compatibility number %d.x, but got %d.%d:\n" +            "The FPGA build is not compatible with the host code build.\n" +            "%s" +            ) % static_cast<boost::uint32_t>(fpga::RB_N230_COMPAT_MAJOR) +              % static_cast<boost::uint32_t>(_fpga_version.compat_major) +              % static_cast<boost::uint32_t>(_fpga_version.compat_minor) +              % print_utility_error("uhd_images_downloader.py"))); +    } +} + +}}} //namespace diff --git a/host/lib/usrp/n230/n230_resource_manager.hpp b/host/lib/usrp/n230/n230_resource_manager.hpp new file mode 100644 index 000000000..0cda460fd --- /dev/null +++ b/host/lib/usrp/n230/n230_resource_manager.hpp @@ -0,0 +1,295 @@ +// +// Copyright 2014 Ettus Research LLC +// +// 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/>. +// + +#ifndef INCLUDED_N230_RESOURCE_MANAGER_HPP +#define INCLUDED_N230_RESOURCE_MANAGER_HPP + +#include "radio_ctrl_core_3000.hpp" +#include "spi_core_3000.hpp" +#include "gpio_core_200.hpp" +#include "rx_vita_core_3000.hpp" +#include "tx_vita_core_3000.hpp" +#include "time_core_3000.hpp" +#include "rx_dsp_core_3000.hpp" +#include "tx_dsp_core_3000.hpp" +#include "user_settings_core_3000.hpp" +#include "ad9361_ctrl.hpp" +#include <uhd/utils/tasks.hpp> +#include <uhd/types/sid.hpp> +#include <uhd/types/device_addr.hpp> +#include <uhd/utils/soft_register.hpp> +#include <uhd/transport/udp_zero_copy.hpp> +#include <uhd/transport/bounded_buffer.hpp> +#include <uhd/usrp/gps_ctrl.hpp> + +#include "usrp3_fw_ctrl_iface.hpp" +#include "n230_clk_pps_ctrl.hpp" +#include "n230_cores.hpp" +#include "n230_fpga_defs.h" +#include "n230_frontend_ctrl.hpp" +#include "n230_uart.hpp" + +namespace uhd { namespace usrp { namespace n230 { + +enum n230_eth_port_t { +    ETH0, +    ETH1 +}; + +enum n230_eth_pref_t { +    PRI_ETH, +    SEC_ETH +}; + +enum n230_endpoint_t { +    RADIO_TX_DATA, +    RADIO_RX_DATA, +    RADIO_CONTROL, +    CORE, +    GPS_UART +}; + +enum n230_ver_src_t { +    SOFTWARE, +    FIRMWARE, +    FPGA +}; + +enum n230_version_t { +    COMPAT_MAJOR, +    COMPAT_MINOR +}; + +enum n230_data_dir_t { +    RX_DATA, TX_DATA +}; + +//Radio resources +class radio_resource_t : public boost::noncopyable { +public: +    radio_ctrl_core_3000::sptr      ctrl; +    gpio_core_200_32wo::sptr        gpio_atr; +    time_core_3000::sptr            time; +    rx_vita_core_3000::sptr         framer; +    rx_dsp_core_3000::sptr          ddc; +    tx_vita_core_3000::sptr         deframer; +    tx_dsp_core_3000::sptr          duc; +    user_settings_core_3000::sptr   user_settings; +}; + +class n230_resource_manager : public boost::noncopyable +{ +public:     //Methods +    n230_resource_manager(const std::vector<std::string> ip_addrs, const bool safe_mode); +    virtual ~n230_resource_manager(); + +    static bool is_device_claimed(uhd::usrp::usrp3::usrp3_fw_ctrl_iface::sptr fw_ctrl); + +    inline bool is_device_claimed() { +        if (_fw_ctrl.get()) { +            return is_device_claimed(_fw_ctrl); +        } else { +            return false; +        } +    } + +    inline boost::uint32_t get_version(n230_ver_src_t src, n230_version_t type) { +        switch (src) { +            case FPGA:      return _fpga_version.get(type); +            case FIRMWARE:  return _fw_version.get(type); +            default:        return 0; +        } +    } + +    inline const std::string get_version_hash(n230_ver_src_t src) { +        switch (src) { +            case FPGA:      return _fpga_version.get_hash_str(); +            case FIRMWARE:  return _fw_version.get_hash_str(); +            default:        return ""; +        } +    } + +    //Firmware control interface +    inline wb_iface& get_fw_ctrl() const { +        return *_fw_ctrl; +    } +    inline wb_iface::sptr get_fw_ctrl_sptr() { +        return _fw_ctrl; +    } + +    //Core settings control interface +    inline radio_ctrl_core_3000& get_core_ctrl() const { +        return *_core_ctrl; +    } +    inline radio_ctrl_core_3000::sptr get_core_ctrl_sptr() { +        return _core_ctrl; +    } + +    //Catalina control interface +    inline ad9361_ctrl& get_codec_ctrl() const { +        return *_codec_ctrl; +    } +    inline ad9361_ctrl::sptr get_codec_ctrl_sptr() { +        return _codec_ctrl; +    } + +    //Clock PPS controls +    inline n230_ref_pll_ctrl& get_ref_pll_ctrl() const { +        return *_ref_pll_ctrl; +    } +    inline n230_ref_pll_ctrl::sptr get_ref_pll_ctrl_sptr() { +        return _ref_pll_ctrl; +    } + +    //Clock PPS controls +    inline n230_clk_pps_ctrl& get_clk_pps_ctrl() const { +        return *_clk_pps_ctrl; +    } +    inline n230_clk_pps_ctrl::sptr get_clk_pps_ctrl_sptr() { +        return _clk_pps_ctrl; +    } + +    //Front-end control +    inline n230_frontend_ctrl& get_frontend_ctrl() const { +        return *_frontend_ctrl; +    } +    inline n230_frontend_ctrl::sptr get_frontend_ctrl_sptr() { +        return _frontend_ctrl; +    } + +    //GPSDO control +    inline uhd::gps_ctrl::sptr get_gps_ctrl(void) { +        return _gps_ctrl; +    } + +    inline radio_resource_t& get_radio(size_t instance) { +        return _radios[instance]; +    } + +    //Transport to stream data +    transport::zero_copy_if::sptr create_transport( +        n230_data_dir_t direction, size_t radio_instance, +        const device_addr_t ¶ms, sid_t& sid, +        transport::udp_zero_copy::buff_params& buff_out_params); + +    //Misc +    inline double get_max_link_rate() { +        return fpga::N230_LINK_RATE_BPS * _eth_conns.size(); +    } + +private: +    struct ver_info_t { +        boost::uint8_t  compat_major; +        boost::uint16_t compat_minor; +        boost::uint32_t version_hash; + +        boost::uint32_t get(n230_version_t type) { +            switch (type) { +                case COMPAT_MAJOR: return compat_major; +                case COMPAT_MINOR: return compat_minor; +                default:           return 0; +            } +        } + +        const std::string get_hash_str() { +            return (str(boost::format("%07x%s") +                % (version_hash & 0x0FFFFFFF) +                % ((version_hash & 0xF0000000) ? "(modified)" : ""))); +        } +    }; + +    struct n230_eth_conn_t { +        std::string ip_addr; +        n230_eth_port_t type; +    }; + +    //-- Functions -- + +    void _claimer_loop(); + +    void _initialize_radio(size_t instance); + +    void _check_fw_compat(); +    void _check_fpga_compat(); + +    const sid_t _generate_sid( +        const n230_endpoint_t type, const n230_eth_port_t xport, size_t instance = 0); + +    transport::zero_copy_if::sptr _create_transport( +        const n230_eth_conn_t& eth_conn, +        const sid_t& sid, const device_addr_t &buff_params, +        transport::udp_zero_copy::buff_params& buff_params_out); + +    void _program_dispatcher( +        transport::zero_copy_if& xport, const n230_eth_port_t port, const sid_t& sid); + +    void _reset_codec_digital_interface(); + +    bool _radio_register_loopback_self_test(wb_iface::sptr iface); + +    bool _radio_data_loopback_self_test(wb_iface::sptr iface); + +    inline const n230_eth_conn_t& _get_conn(const n230_eth_pref_t pref) { +        if (_eth_conns.size() == 1) +            return _eth_conns[0]; +        else +            return _eth_conns[(pref==PRI_ETH)?0:1]; +    } + +    //-- Members -- + +    std::vector<n230_eth_conn_t>    _eth_conns; +    const bool                      _safe_mode; +    ver_info_t                      _fw_version; +    ver_info_t                      _fpga_version; + +    //Firmware register interface +    uhd::usrp::usrp3::usrp3_fw_ctrl_iface::sptr   _fw_ctrl; +    uhd::task::sptr                 _claimer_task; +    static boost::mutex             _claimer_mutex;  //All claims and checks in this process are serialized + +    //Transport +    boost::uint8_t                  _last_host_enpoint; + +    //Radio settings interface +    radio_ctrl_core_3000::sptr      _core_ctrl; +    n230_core_spi_core::sptr        _core_spi_ctrl; +    ad9361_ctrl::sptr               _codec_ctrl; + +    //Core Registers +    fpga::core_radio_ctrl_reg_t     _core_radio_ctrl_reg; +    fpga::core_misc_reg_t           _core_misc_reg; +    fpga::core_pps_sel_reg_t        _core_pps_sel_reg; +    fpga::core_radio_status_reg_t   _core_status_reg; + +    //Radio peripherals +    radio_resource_t                _radios[fpga::NUM_RADIOS]; + +    //Misc IO peripherals +    n230_ref_pll_ctrl::sptr         _ref_pll_ctrl; +    n230_clk_pps_ctrl::sptr         _clk_pps_ctrl; +    n230_frontend_ctrl::sptr        _frontend_ctrl; + +    //GPSDO +    n230_uart::sptr                 _gps_uart; +    uhd::gps_ctrl::sptr             _gps_ctrl; + +}; + +}}} //namespace + +#endif //INCLUDED_N230_RESOURCE_MANAGER_HPP diff --git a/host/lib/usrp/n230/n230_stream_manager.cpp b/host/lib/usrp/n230/n230_stream_manager.cpp new file mode 100644 index 000000000..e7624ecd6 --- /dev/null +++ b/host/lib/usrp/n230/n230_stream_manager.cpp @@ -0,0 +1,562 @@ +// +// Copyright 2013 Ettus Research LLC +// +// 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 "n230_stream_manager.hpp" + +#include "../../transport/super_recv_packet_handler.hpp" +#include "../../transport/super_send_packet_handler.hpp" +#include "async_packet_handler.hpp" +#include <uhd/transport/bounded_buffer.hpp> +#include <boost/bind.hpp> +#include <uhd/utils/tasks.hpp> +#include <uhd/utils/log.hpp> +#include <boost/foreach.hpp> +#include <boost/make_shared.hpp> + +static const double N230_RX_SW_BUFF_FULL_FACTOR   = 0.90;     //Buffer should ideally be 90% full. +static const size_t N230_RX_FC_REQUEST_FREQ       = 32;       //per flow-control window +static const size_t N230_TX_MAX_ASYNC_MESSAGES    = 1000; +static const size_t N230_TX_MAX_SPP               = 4092; +static const size_t N230_TX_FC_RESPONSE_FREQ      = 10;       //per flow-control window + +static const boost::uint32_t N230_EVENT_CODE_FLOW_CTRL = 0; + +namespace uhd { namespace usrp { namespace n230 { + +using namespace uhd::transport; + +n230_stream_manager::~n230_stream_manager() +{ +} + +/*********************************************************************** + * Receive streamer + **********************************************************************/ +n230_stream_manager::n230_stream_manager( +    const n230_device_args_t& dev_args, +    boost::shared_ptr<n230_resource_manager> resource_mgr, +    boost::weak_ptr<property_tree> prop_tree +) : +    _dev_args(dev_args), +    _resource_mgr(resource_mgr), +    _tree(prop_tree) +{ +    _async_md_queue.reset(new async_md_queue_t(N230_TX_MAX_ASYNC_MESSAGES)); +} + +/*********************************************************************** + * Receive streamer + **********************************************************************/ +rx_streamer::sptr n230_stream_manager::get_rx_stream(const uhd::stream_args_t &args_) +{ +    boost::mutex::scoped_lock lock(_stream_setup_mutex); + +    stream_args_t args = args_; + +    //setup defaults for unspecified values +    if (args.otw_format.empty()) args.otw_format = "sc16"; +    args.channels = args.channels.empty()? std::vector<size_t>(1, 0) : args.channels; + +    boost::shared_ptr<sph::recv_packet_streamer> my_streamer; +    for (size_t stream_i = 0; stream_i < args.channels.size(); stream_i++) +    { +        const size_t chan = args.channels[stream_i]; +        radio_resource_t& perif = _resource_mgr->get_radio(chan); + +        //setup transport hints (default to a large recv buff) +        //TODO: Propagate the device_args class into streamer in the future +        device_addr_t device_addr = args.args; +        if (not device_addr.has_key("recv_buff_size")) { +            device_addr["recv_buff_size"] = boost::lexical_cast<std::string>(_dev_args.get_recv_buff_size()); +        } +        if (not device_addr.has_key("recv_frame_size")) { +            device_addr["recv_frame_size"] = boost::lexical_cast<std::string>(_dev_args.get_recv_frame_size()); +        } +        if (not device_addr.has_key("num_recv_frames")) { +            device_addr["num_recv_frames"] = boost::lexical_cast<std::string>(_dev_args.get_num_recv_frames()); +        } + +        transport::udp_zero_copy::buff_params buff_params_out; +        sid_t sid; +        zero_copy_if::sptr xport = _resource_mgr->create_transport( +            RX_DATA, chan, device_addr, sid, buff_params_out); + +        //calculate packet size +        static const size_t hdr_size = 0 +            + vrt::max_if_hdr_words32*sizeof(boost::uint32_t) +            //+ sizeof(vrt::if_packet_info_t().tlr) //no longer using trailer +            - sizeof(vrt::if_packet_info_t().cid) //no class id ever used +            - sizeof(vrt::if_packet_info_t().tsi) //no int time ever used +        ; +        const size_t bpp = xport->get_recv_frame_size() - hdr_size; +        const size_t bpi = convert::get_bytes_per_item(args.otw_format); +        size_t spp = unsigned(args.args.cast<double>("spp", bpp/bpi)); +        spp = std::min<size_t>(N230_TX_MAX_SPP, spp); //FPGA FIFO maximum for framing at full rate + +        //make the new streamer given the samples per packet +        if (not my_streamer) my_streamer = boost::make_shared<sph::recv_packet_streamer>(spp); +        my_streamer->resize(args.channels.size()); + +        //init some streamer stuff +        my_streamer->set_vrt_unpacker(&n230_stream_manager::_cvita_hdr_unpack); + +        //set the converter +        uhd::convert::id_type id; +        id.input_format = args.otw_format + "_item32_be"; +        id.num_inputs = 1; +        id.output_format = args.cpu_format; +        id.num_outputs = 1; +        my_streamer->set_converter(id); + +        perif.framer->clear(); +        perif.framer->set_nsamps_per_packet(spp); +        perif.framer->set_sid(sid.reversed().get()); +        perif.framer->setup(args); +        perif.ddc->setup(args); + +        //Give the streamer a functor to get the recv_buffer +        //bind requires a zero_copy_if::sptr to add a streamer->xport lifetime dependency +        my_streamer->set_xport_chan_get_buff( +            stream_i, +            boost::bind(&zero_copy_if::get_recv_buff, xport, _1), +            true /*flush*/ +        ); + +        my_streamer->set_overflow_handler(stream_i, boost::bind( +            &n230_stream_manager::_handle_overflow, this, chan +        )); + +        my_streamer->set_issue_stream_cmd(stream_i, boost::bind( +            &rx_vita_core_3000::issue_stream_command, perif.framer, _1 +        )); + +        const size_t fc_window = _get_rx_flow_control_window( +            xport->get_recv_frame_size(), buff_params_out.recv_buff_size); +        const size_t fc_handle_window = std::max<size_t>(1, fc_window / N230_RX_FC_REQUEST_FREQ); + +        perif.framer->configure_flow_control(fc_window); + +        //Give the streamer a functor to send flow control messages +        //handle_rx_flowctrl is static and has no lifetime issues +        boost::shared_ptr<rx_fc_cache_t> fc_cache(new rx_fc_cache_t()); +        my_streamer->set_xport_handle_flowctrl( +            stream_i, boost::bind(&n230_stream_manager::_handle_rx_flowctrl, sid.get(), xport, fc_cache, _1), +            fc_handle_window, +            true/*init*/ +        ); + +        //Store a weak pointer to prevent a streamer->manager->streamer circular dependency +        _rx_streamers[chan] = my_streamer; //store weak pointer +        _rx_stream_cached_args[chan] = args; + +        //Sets tick and samp rates on all streamer +        update_tick_rate(_get_tick_rate()); + +        //TODO: Find a way to remove this dependency +        property_tree::sptr prop_tree = _tree.lock(); +        if (prop_tree) { +            //TODO: Update this to support multiple motherboards +            const fs_path mb_path = "/mboards/0"; +            prop_tree->access<double>(mb_path / "rx_dsps" / boost::lexical_cast<std::string>(chan) / "rate" / "value").update(); +        } +    } +    update_stream_states(); + +    return my_streamer; +} + +/*********************************************************************** + * Transmit streamer + **********************************************************************/ +tx_streamer::sptr n230_stream_manager::get_tx_stream(const uhd::stream_args_t &args_) +{ +    boost::mutex::scoped_lock lock(_stream_setup_mutex); + +    uhd::stream_args_t args = args_; + +    //setup defaults for unspecified values +    if (not args.otw_format.empty() and args.otw_format != "sc16") { +        throw uhd::value_error("n230_impl::get_tx_stream only supports otw_format sc16"); +    } +    args.otw_format = "sc16"; +    args.channels = args.channels.empty()? std::vector<size_t>(1, 0) : args.channels; + +    //shared async queue for all channels in streamer +    boost::shared_ptr<async_md_queue_t> async_md(new async_md_queue_t(N230_TX_MAX_ASYNC_MESSAGES)); + +    boost::shared_ptr<sph::send_packet_streamer> my_streamer; +    for (size_t stream_i = 0; stream_i < args.channels.size(); stream_i++) +    { +        const size_t chan = args.channels[stream_i]; +        radio_resource_t& perif = _resource_mgr->get_radio(chan); + +        //setup transport hints (default to a large recv buff) +        //TODO: Propagate the device_args class into streamer in the future +        device_addr_t device_addr = args.args; +        if (not device_addr.has_key("send_buff_size")) { +            device_addr["send_buff_size"] = boost::lexical_cast<std::string>(_dev_args.get_send_buff_size()); +        } +        if (not device_addr.has_key("send_frame_size")) { +            device_addr["send_frame_size"] = boost::lexical_cast<std::string>(_dev_args.get_send_frame_size()); +        } +        if (not device_addr.has_key("num_send_frames")) { +            device_addr["num_send_frames"] = boost::lexical_cast<std::string>(_dev_args.get_num_send_frames()); +        } + +        transport::udp_zero_copy::buff_params buff_params_out; +        sid_t sid; +        zero_copy_if::sptr xport = _resource_mgr->create_transport( +            TX_DATA, chan, device_addr, sid, buff_params_out); + +        //calculate packet size +        static const size_t hdr_size = 0 +            + vrt::num_vrl_words32*sizeof(boost::uint32_t) +            + vrt::max_if_hdr_words32*sizeof(boost::uint32_t) +            //+ sizeof(vrt::if_packet_info_t().tlr) //forced to have trailer +            - sizeof(vrt::if_packet_info_t().cid) //no class id ever used +            - sizeof(vrt::if_packet_info_t().tsi) //no int time ever used +        ; +        const size_t bpp = xport->get_send_frame_size() - hdr_size; +        const size_t bpi = convert::get_bytes_per_item(args.otw_format); +        const size_t spp = unsigned(args.args.cast<double>("spp", bpp/bpi)); + +        //make the new streamer given the samples per packet +        if (not my_streamer) my_streamer = boost::make_shared<sph::send_packet_streamer>(spp); +        my_streamer->resize(args.channels.size()); +        my_streamer->set_vrt_packer(&n230_stream_manager::_cvita_hdr_pack); + +        //set the converter +        uhd::convert::id_type id; +        id.input_format = args.cpu_format; +        id.num_inputs = 1; +        id.output_format = args.otw_format + "_item32_be"; +        id.num_outputs = 1; +        my_streamer->set_converter(id); + +        perif.deframer->clear(); +        perif.deframer->setup(args); +        perif.duc->setup(args); + +        //flow control setup +        size_t fc_window = _get_tx_flow_control_window( +            bpp, device_addr.cast<size_t>("send_buff_size", _dev_args.get_send_buff_size())); +        //In packets +        const size_t fc_handle_window = (fc_window / N230_TX_FC_RESPONSE_FREQ); + +        perif.deframer->configure_flow_control(0/*cycs off*/, fc_handle_window); +        boost::shared_ptr<tx_fc_cache_t> fc_cache(new tx_fc_cache_t()); +        fc_cache->stream_channel = stream_i; +        fc_cache->device_channel = chan; +        fc_cache->async_queue = async_md; +        fc_cache->old_async_queue = _async_md_queue; + +        tick_rate_retriever_t get_tick_rate_fn = boost::bind(&n230_stream_manager::_get_tick_rate, this); +        task::sptr task = task::make( +            boost::bind(&n230_stream_manager::_handle_tx_async_msgs, +                fc_cache, xport, get_tick_rate_fn)); + +        //Give the streamer a functor to get the send buffer +        //get_tx_buff_with_flowctrl is static so bind has no lifetime issues +        //xport.send (sptr) is required to add streamer->data-transport lifetime dependency +        //task (sptr) is required to add  a streamer->async-handler lifetime dependency +        my_streamer->set_xport_chan_get_buff( +            stream_i, +            boost::bind(&n230_stream_manager::_get_tx_buff_with_flowctrl, task, fc_cache, xport, fc_window, _1) +        ); +        //Give the streamer a functor handled received async messages +        my_streamer->set_async_receiver( +            boost::bind(&async_md_queue_t::pop_with_timed_wait, async_md, _1, _2) +        ); +        my_streamer->set_xport_chan_sid(stream_i, true, sid.get()); +        my_streamer->set_enable_trailer(false); //TODO not implemented trailer support yet + +        //Store a weak pointer to prevent a streamer->manager->streamer circular dependency +        _tx_streamers[chan] = boost::weak_ptr<sph::send_packet_streamer>(my_streamer); +        _tx_stream_cached_args[chan] = args; + +        //Sets tick and samp rates on all streamer +        update_tick_rate(_get_tick_rate()); + +        //TODO: Find a way to remove this dependency +        property_tree::sptr prop_tree = _tree.lock(); +        if (prop_tree) { +            //TODO: Update this to support multiple motherboards +            const fs_path mb_path = "/mboards/0"; +            prop_tree->access<double>(mb_path / "tx_dsps" / boost::lexical_cast<std::string>(chan) / "rate" / "value").update(); +        } +    } +    update_stream_states(); + +    return my_streamer; +} + +/*********************************************************************** + * Async Message Receiver + **********************************************************************/ +bool n230_stream_manager::recv_async_msg(async_metadata_t &async_metadata, double timeout) +{ +    return _async_md_queue->pop_with_timed_wait(async_metadata, timeout); +} + +/*********************************************************************** + * Sample Rate Updaters + **********************************************************************/ +void n230_stream_manager::update_rx_samp_rate(const size_t dspno, const double rate) +{ +    boost::shared_ptr<sph::recv_packet_streamer> my_streamer = +        boost::dynamic_pointer_cast<sph::recv_packet_streamer>(_rx_streamers[dspno].lock()); +    if (not my_streamer) return; +    my_streamer->set_samp_rate(rate); +    const double adj = _resource_mgr->get_radio(dspno).ddc->get_scaling_adjustment(); +    my_streamer->set_scale_factor(adj); +} + +void n230_stream_manager::update_tx_samp_rate(const size_t dspno, const double rate) +{ +    boost::shared_ptr<sph::send_packet_streamer> my_streamer = +        boost::dynamic_pointer_cast<sph::send_packet_streamer>(_tx_streamers[dspno].lock()); +    if (not my_streamer) return; +    my_streamer->set_samp_rate(rate); +    const double adj = _resource_mgr->get_radio(dspno).duc->get_scaling_adjustment(); +    my_streamer->set_scale_factor(adj); +} + +/*********************************************************************** + * Tick Rate Updater + **********************************************************************/ +void n230_stream_manager::update_tick_rate(const double rate) +{ +    for (size_t i = 0; i < fpga::NUM_RADIOS; i++) { +        radio_resource_t& perif = _resource_mgr->get_radio(i); + +        boost::shared_ptr<sph::recv_packet_streamer> my_rx_streamer = +            boost::dynamic_pointer_cast<sph::recv_packet_streamer>(_rx_streamers[i].lock()); +        if (my_rx_streamer) my_rx_streamer->set_tick_rate(rate); +        perif.framer->set_tick_rate(rate); + +        boost::shared_ptr<sph::send_packet_streamer> my_tx_streamer = +            boost::dynamic_pointer_cast<sph::send_packet_streamer>(_tx_streamers[i].lock()); +        if (my_tx_streamer) my_tx_streamer->set_tick_rate(rate); +    } +} + +/*********************************************************************** + * Stream State Updater + **********************************************************************/ +void n230_stream_manager::update_stream_states() +{ +    //extract settings from state variables +    const bool enb_tx0 = bool(_tx_streamers[0].lock()); +    const bool enb_rx0 = bool(_rx_streamers[0].lock()); +    const bool enb_tx1 = bool(_tx_streamers[1].lock()); +    const bool enb_rx1 = bool(_rx_streamers[1].lock()); + +    fe_state_t fe0_state = NONE_STREAMING; +    if (enb_tx0 && enb_rx0) fe0_state = TXRX_STREAMING; +    else if (enb_tx0)       fe0_state = TX_STREAMING; +    else if (enb_rx0)       fe0_state = RX_STREAMING; + +    fe_state_t fe1_state = NONE_STREAMING; +    if (enb_tx1 && enb_rx1) fe1_state = TXRX_STREAMING; +    else if (enb_tx1)       fe1_state = TX_STREAMING; +    else if (enb_rx1)       fe1_state = RX_STREAMING; + +    _resource_mgr->get_frontend_ctrl().set_stream_state(fe0_state, fe1_state); +} + +size_t n230_stream_manager::_get_rx_flow_control_window(size_t frame_size, size_t sw_buff_size) +{ +    double sw_buff_max = sw_buff_size * N230_RX_SW_BUFF_FULL_FACTOR; +    size_t window_in_pkts = (static_cast<size_t>(sw_buff_max) / frame_size); +    if (window_in_pkts == 0) { +        throw uhd::value_error("recv_buff_size must be larger than the recv_frame_size."); +    } +    return window_in_pkts; +} + +void n230_stream_manager::_handle_overflow(const size_t i) +{ +    boost::shared_ptr<sph::recv_packet_streamer> my_streamer = +            boost::dynamic_pointer_cast<sph::recv_packet_streamer>(_rx_streamers[i].lock()); +    if (my_streamer->get_num_channels() == 2) { +        //MIMO +        //find out if we were in continuous mode before stopping +        const bool in_continuous_streaming_mode = _resource_mgr->get_radio(i).framer->in_continuous_streaming_mode(); +        //stop streaming +        my_streamer->issue_stream_cmd(stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS); +        //restart streaming +        if (in_continuous_streaming_mode) { +            stream_cmd_t stream_cmd(stream_cmd_t::STREAM_MODE_START_CONTINUOUS); +            stream_cmd.stream_now = false; +            stream_cmd.time_spec = _resource_mgr->get_radio(i).time->get_time_now() + time_spec_t(0.01); +            my_streamer->issue_stream_cmd(stream_cmd); +        } +    } else { +        _resource_mgr->get_radio(i).framer->handle_overflow(); +    } +} + +void n230_stream_manager::_handle_rx_flowctrl( +    const sid_t& sid, +    zero_copy_if::sptr xport, +    boost::shared_ptr<rx_fc_cache_t> fc_cache, +    const size_t last_seq) +{ +    static const size_t RXFC_PACKET_LEN_IN_WORDS    = 2; +    static const size_t RXFC_CMD_CODE_OFFSET        = 0; +    static const size_t RXFC_SEQ_NUM_OFFSET         = 1; + +    managed_send_buffer::sptr buff = xport->get_send_buff(0.0); +    if (not buff) { +        throw uhd::runtime_error("handle_rx_flowctrl timed out getting a send buffer"); +    } +    boost::uint32_t *pkt = buff->cast<boost::uint32_t *>(); + +    //recover seq32 +    size_t& seq_sw = fc_cache->last_seq_in; +    const size_t seq_hw = seq_sw & HW_SEQ_NUM_MASK; +    if (last_seq < seq_hw) seq_sw += (HW_SEQ_NUM_MASK + 1); +    seq_sw &= ~HW_SEQ_NUM_MASK; +    seq_sw |= last_seq; + +    //load packet info +    vrt::if_packet_info_t packet_info; +    packet_info.packet_type = vrt::if_packet_info_t::PACKET_TYPE_CONTEXT; +    packet_info.num_payload_words32 = RXFC_PACKET_LEN_IN_WORDS; +    packet_info.num_payload_bytes = packet_info.num_payload_words32*sizeof(boost::uint32_t); +    packet_info.packet_count = seq_sw; +    packet_info.sob = false; +    packet_info.eob = false; +    packet_info.sid = sid.get(); +    packet_info.has_sid = true; +    packet_info.has_cid = false; +    packet_info.has_tsi = false; +    packet_info.has_tsf = false; +    packet_info.has_tlr = false; + +    //load header +    _cvita_hdr_pack(pkt, packet_info); + +    //load payload +    pkt[packet_info.num_header_words32 + RXFC_CMD_CODE_OFFSET] = uhd::htonx<boost::uint32_t>(N230_EVENT_CODE_FLOW_CTRL); +    pkt[packet_info.num_header_words32 + RXFC_SEQ_NUM_OFFSET] = uhd::htonx<boost::uint32_t>(seq_sw); + +    //send the buffer over the interface +    buff->commit(sizeof(boost::uint32_t)*(packet_info.num_packet_words32)); +} + +void n230_stream_manager::_handle_tx_async_msgs( +    boost::shared_ptr<tx_fc_cache_t> fc_cache, +    zero_copy_if::sptr xport, +    tick_rate_retriever_t get_tick_rate) +{ +    managed_recv_buffer::sptr buff = xport->get_recv_buff(); +    if (not buff) return; + +    //extract packet info +    vrt::if_packet_info_t if_packet_info; +    if_packet_info.num_packet_words32 = buff->size()/sizeof(boost::uint32_t); +    const boost::uint32_t *packet_buff = buff->cast<const boost::uint32_t *>(); + +    //unpacking can fail +    uint32_t (*endian_conv)(uint32_t) = uhd::ntohx; +    try { +        _cvita_hdr_unpack(packet_buff, if_packet_info); +        endian_conv = uhd::ntohx; +    } catch(const std::exception &ex) { +        UHD_MSG(error) << "Error parsing async message packet: " << ex.what() << std::endl; +        return; +    } + +    //fill in the async metadata +    async_metadata_t metadata; +    load_metadata_from_buff( +        endian_conv, metadata, if_packet_info, packet_buff, +        get_tick_rate(), fc_cache->stream_channel); + +    //The FC response and the burst ack are two indicators that the radio +    //consumed packets. Use them to update the FC metadata +    if (metadata.event_code == N230_EVENT_CODE_FLOW_CTRL or +        metadata.event_code == async_metadata_t::EVENT_CODE_BURST_ACK +    ) { +        const size_t seq = metadata.user_payload[0]; +        fc_cache->seq_queue.push_with_pop_on_full(seq); +    } + +    //FC responses don't propagate up to the user so filter them here +    if (metadata.event_code != N230_EVENT_CODE_FLOW_CTRL) { +        fc_cache->async_queue->push_with_pop_on_full(metadata); +        metadata.channel = fc_cache->device_channel; +        fc_cache->old_async_queue->push_with_pop_on_full(metadata); +        standard_async_msg_prints(metadata); +    } +} + +managed_send_buffer::sptr n230_stream_manager::_get_tx_buff_with_flowctrl( +    task::sptr /*holds ref*/, +    boost::shared_ptr<tx_fc_cache_t> fc_cache, +    zero_copy_if::sptr xport, +    size_t fc_pkt_window, +    const double timeout) +{ +    while (true) +    { +        const size_t delta = (fc_cache->last_seq_out & HW_SEQ_NUM_MASK) - (fc_cache->last_seq_ack & HW_SEQ_NUM_MASK); +        if ((delta & HW_SEQ_NUM_MASK) <= fc_pkt_window) break; + +        const bool ok = fc_cache->seq_queue.pop_with_timed_wait(fc_cache->last_seq_ack, timeout); +        if (not ok) return managed_send_buffer::sptr(); //timeout waiting for flow control +    } + +    managed_send_buffer::sptr buff = xport->get_send_buff(timeout); +    if (buff) fc_cache->last_seq_out++; //update seq, this will actually be a send +    return buff; +} + +size_t n230_stream_manager::_get_tx_flow_control_window( +    size_t payload_size, +    size_t hw_buff_size) +{ +    size_t window_in_pkts = hw_buff_size / payload_size; +    if (window_in_pkts == 0) { +        throw uhd::value_error("send_buff_size must be larger than the send_frame_size."); +    } +    return window_in_pkts; +} + +double n230_stream_manager::_get_tick_rate() +{ +    return _resource_mgr->get_clk_pps_ctrl().get_tick_rate(); +} + +void n230_stream_manager::_cvita_hdr_unpack( +    const boost::uint32_t *packet_buff, +    vrt::if_packet_info_t &if_packet_info) +{ +    if_packet_info.link_type = vrt::if_packet_info_t::LINK_TYPE_CHDR; +    return vrt::if_hdr_unpack_be(packet_buff, if_packet_info); +} + +void n230_stream_manager::_cvita_hdr_pack( +    boost::uint32_t *packet_buff, +    vrt::if_packet_info_t &if_packet_info) +{ +    if_packet_info.link_type = vrt::if_packet_info_t::LINK_TYPE_CHDR; +    return vrt::if_hdr_pack_be(packet_buff, if_packet_info); +} + +}}} //namespace diff --git a/host/lib/usrp/n230/n230_stream_manager.hpp b/host/lib/usrp/n230/n230_stream_manager.hpp new file mode 100644 index 000000000..7a496c4e9 --- /dev/null +++ b/host/lib/usrp/n230/n230_stream_manager.hpp @@ -0,0 +1,151 @@ +// +// Copyright 2014 Ettus Research LLC +// +// 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/>. +// + +#ifndef INCLUDED_N230_STREAM_MANAGER_HPP +#define INCLUDED_N230_STREAM_MANAGER_HPP + +#include "time_core_3000.hpp" +#include "rx_vita_core_3000.hpp" +#include <uhd/types/sid.hpp> +#include <uhd/types/device_addr.hpp> +#include <uhd/types/metadata.hpp> +#include <uhd/transport/zero_copy.hpp> +#include <uhd/transport/bounded_buffer.hpp> +#include <uhd/transport/vrt_if_packet.hpp> +#include <uhd/property_tree.hpp> +#include <uhd/utils/tasks.hpp> +#include <boost/smart_ptr.hpp> +#include "n230_device_args.hpp" +#include "n230_resource_manager.hpp" + +namespace uhd { namespace usrp { namespace n230 { + +class n230_stream_manager : public boost::noncopyable +{ +public:     //Methods +    n230_stream_manager( +        const n230_device_args_t& dev_args, +        boost::shared_ptr<n230_resource_manager> resource_mgr, +        boost::weak_ptr<property_tree> prop_tree); +    virtual ~n230_stream_manager(); + +    rx_streamer::sptr get_rx_stream( +        const uhd::stream_args_t &args); + +    tx_streamer::sptr get_tx_stream( +        const uhd::stream_args_t &args_); + +    bool recv_async_msg( +        async_metadata_t &async_metadata, +        double timeout); + +    void update_stream_states(); + +    void update_rx_samp_rate( +        const size_t dspno, const double rate); + +    void update_tx_samp_rate( +        const size_t dspno, const double rate); + +    void update_tick_rate( +        const double rate); + +private: +    typedef transport::bounded_buffer<async_metadata_t> async_md_queue_t; + +    struct rx_fc_cache_t +    { +        rx_fc_cache_t(): +            last_seq_in(0){} +        size_t last_seq_in; +    }; + +    struct tx_fc_cache_t +    { +        tx_fc_cache_t(): +            stream_channel(0), +            device_channel(0), +            last_seq_out(0), +            last_seq_ack(0), +            seq_queue(1){} +        size_t stream_channel; +        size_t device_channel; +        size_t last_seq_out; +        size_t last_seq_ack; +        transport::bounded_buffer<size_t> seq_queue; +        boost::shared_ptr<async_md_queue_t> async_queue; +        boost::shared_ptr<async_md_queue_t> old_async_queue; +    }; + +    typedef boost::function<double(void)> tick_rate_retriever_t; + +    void _handle_overflow(const size_t i); + +    double _get_tick_rate(); + +    static size_t _get_rx_flow_control_window( +        size_t frame_size, size_t sw_buff_size); + +    static void _handle_rx_flowctrl( +        const sid_t& sid, +        transport::zero_copy_if::sptr xport, +        boost::shared_ptr<rx_fc_cache_t> fc_cache, +        const size_t last_seq); + +    static void _handle_tx_async_msgs( +        boost::shared_ptr<tx_fc_cache_t> guts, +        transport::zero_copy_if::sptr xport, +        tick_rate_retriever_t get_tick_rate); + +    static transport::managed_send_buffer::sptr _get_tx_buff_with_flowctrl( +        task::sptr /*holds ref*/, +        boost::shared_ptr<tx_fc_cache_t> guts, +        transport::zero_copy_if::sptr xport, +        size_t fc_pkt_window, +        const double timeout); + +    static size_t _get_tx_flow_control_window( +        size_t payload_size, +        size_t hw_buff_size); + +    static void _cvita_hdr_unpack( +        const boost::uint32_t *packet_buff, +        transport::vrt::if_packet_info_t &if_packet_info); + +    static void _cvita_hdr_pack( +        boost::uint32_t *packet_buff, +        transport::vrt::if_packet_info_t &if_packet_info); + +    const n230_device_args_t                  _dev_args; +    boost::shared_ptr<n230_resource_manager>  _resource_mgr; +    //TODO: Find a way to remove this dependency +    boost::weak_ptr<property_tree>          _tree; + +    boost::mutex                            _stream_setup_mutex; +    uhd::msg_task::sptr                     _async_task; +    boost::shared_ptr<async_md_queue_t>     _async_md_queue; +    boost::weak_ptr<uhd::tx_streamer>       _tx_streamers[fpga::NUM_RADIOS]; +    boost::weak_ptr<uhd::rx_streamer>       _rx_streamers[fpga::NUM_RADIOS]; +    stream_args_t                           _tx_stream_cached_args[fpga::NUM_RADIOS]; +    stream_args_t                           _rx_stream_cached_args[fpga::NUM_RADIOS]; + +    static const boost::uint32_t HW_SEQ_NUM_MASK    = 0xFFF; +}; + +}}} //namespace + +#endif //INCLUDED_N230_STREAM_MANAGER_HPP diff --git a/host/lib/usrp/n230/n230_uart.cpp b/host/lib/usrp/n230/n230_uart.cpp new file mode 100644 index 000000000..20936c303 --- /dev/null +++ b/host/lib/usrp/n230/n230_uart.cpp @@ -0,0 +1,131 @@ +// +// Copyright 2013 Ettus Research LLC +// +// 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 "n230_uart.hpp" + +#include <uhd/transport/bounded_buffer.hpp> +#include <uhd/transport/vrt_if_packet.hpp> +#include <uhd/utils/byteswap.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/types/time_spec.hpp> +#include <uhd/exception.hpp> + +using namespace uhd; +using namespace uhd::transport; + +namespace uhd { namespace usrp { namespace n230 { + +struct n230_uart_impl : n230_uart +{ +    n230_uart_impl(zero_copy_if::sptr xport, const boost::uint32_t sid): +        _xport(xport), +        _sid(sid), +        _count(0), +        _char_queue(4096) +    { +        //this default baud divider is over 9000 +        this->set_baud_divider(9001); + +        //create a task to handle incoming packets +        _recv_task = uhd::task::make(boost::bind(&n230_uart_impl::handle_recv, this)); +    } + +    void send_char(const char ch) +    { +        managed_send_buffer::sptr buff = _xport->get_send_buff(); +        UHD_ASSERT_THROW(bool(buff)); + +        vrt::if_packet_info_t packet_info; +        packet_info.link_type = vrt::if_packet_info_t::LINK_TYPE_CHDR; +        packet_info.packet_type = vrt::if_packet_info_t::PACKET_TYPE_CONTEXT; +        packet_info.num_payload_words32 = 2; +        packet_info.num_payload_bytes = packet_info.num_payload_words32*sizeof(boost::uint32_t); +        packet_info.packet_count = _count++; +        packet_info.sob = false; +        packet_info.eob = false; +        packet_info.sid = _sid; +        packet_info.has_sid = true; +        packet_info.has_cid = false; +        packet_info.has_tsi = false; +        packet_info.has_tsf = false; +        packet_info.has_tlr = false; + +        boost::uint32_t *packet_buff = buff->cast<boost::uint32_t *>(); +        vrt::if_hdr_pack_le(packet_buff, packet_info); +        packet_buff[packet_info.num_header_words32+0] = uhd::htonx(boost::uint32_t(_baud_div)); +        packet_buff[packet_info.num_header_words32+1] = uhd::htonx(boost::uint32_t(ch)); +        buff->commit(packet_info.num_packet_words32*sizeof(boost::uint32_t)); +    } + +    void write_uart(const std::string &buff) +    { +        static bool r_sent = false; +        for (size_t i = 0; i < buff.size(); i++) +        { +            if (buff[i] == '\n' and not r_sent) this->send_char('\r'); +            this->send_char(buff[i]); +            r_sent = (buff[i] == '\r'); +        } +    } + +    std::string read_uart(double timeout) +    { +        std::string line; +        char ch = '\0'; +        while (_char_queue.pop_with_timed_wait(ch, timeout)) +        { +            if (ch == '\r') continue; +            line += std::string(&ch, 1); +            if (ch == '\n') return line; +        } +        return line; +    } + +    void handle_recv(void) +    { +        managed_recv_buffer::sptr buff = _xport->get_recv_buff(); +        if (not buff) +            return; +        const boost::uint32_t *packet_buff = buff->cast<const boost::uint32_t *>(); +        vrt::if_packet_info_t packet_info; +        packet_info.link_type = vrt::if_packet_info_t::LINK_TYPE_CHDR; +        packet_info.num_packet_words32 = buff->size()/sizeof(boost::uint32_t); +        vrt::if_hdr_unpack_be(packet_buff, packet_info); +        const char ch = char(uhd::ntohx(packet_buff[packet_info.num_header_words32+1])); +        _char_queue.push_with_pop_on_full(ch); +    } + +    void set_baud_divider(const double baud_div) +    { +        _baud_div = size_t(baud_div + 0.5); +    } + +    const zero_copy_if::sptr _xport; +    const boost::uint32_t _sid; +    size_t _count; +    size_t _baud_div; +    bounded_buffer<char> _char_queue; +    uhd::task::sptr _recv_task; +}; + + +n230_uart::sptr n230_uart::make(zero_copy_if::sptr xport, const boost::uint32_t sid) +{ +    return n230_uart::sptr(new n230_uart_impl(xport, sid)); +} + +}}} //namespace diff --git a/host/lib/usrp/n230/n230_uart.hpp b/host/lib/usrp/n230/n230_uart.hpp new file mode 100644 index 000000000..0bde12ab2 --- /dev/null +++ b/host/lib/usrp/n230/n230_uart.hpp @@ -0,0 +1,38 @@ +// +// Copyright 2013 Ettus Research LLC +// +// 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/>. +// + +#ifndef INCLUDED_N230_UART_HPP +#define INCLUDED_N230_UART_HPP + +#include <uhd/transport/zero_copy.hpp> +#include <uhd/types/serial.hpp> //uart iface +#include <uhd/utils/tasks.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> +namespace uhd { namespace usrp { namespace n230 { + +class n230_uart: boost::noncopyable, public uhd::uart_iface +{ +public: +    typedef boost::shared_ptr<n230_uart> sptr; +    static sptr make(uhd::transport::zero_copy_if::sptr, const boost::uint32_t sid); +    virtual void set_baud_divider(const double baud_div) = 0; +}; + +}}} //namespace + +#endif /* INCLUDED_N230_UART_HPP */  | 
