diff options
Diffstat (limited to 'host/lib/usrp/n230')
24 files changed, 4469 insertions, 0 deletions
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..9d704b702 --- /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_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_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_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_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..3e0a21e04 --- /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_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..58c702ec1 --- /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 "n230_cores.hpp" +#include "n230_fpga_defs.h" +#include "n230_fw_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.h b/host/lib/usrp/n230/n230_eeprom.h new file mode 100644 index 000000000..b6c2a0c76 --- /dev/null +++ b/host/lib/usrp/n230/n230_eeprom.h @@ -0,0 +1,124 @@ +// +// 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_EEPROM_H +#define INCLUDED_N230_EEPROM_H + +#include <stdint.h> +#ifndef __cplusplus +#include <stdbool.h> +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#define N230_NUM_ETH_PORTS 2 +#define N230_MAX_NUM_ETH_PORTS 2 + +#if (N230_NUM_ETH_PORTS > N230_MAX_NUM_ETH_PORTS) +#error +#endif + +#define N230_EEPROM_VER_MAJOR 1 +#define N230_EEPROM_VER_MINOR 1 +#define N230_EEPROM_SERIAL_LEN 9 +#define N230_EEPROM_NAME_LEN 32 + +typedef struct +{ + uint8_t mac_addr[6]; + uint8_t _pad[2]; + uint32_t subnet; + uint32_t ip_addr; +} n230_eth_eeprom_map_t; + +typedef struct +{ + //Data format version + uint16_t data_version_major; + uint16_t data_version_minor; + + //HW identification info + uint16_t hw_revision; + uint16_t hw_product; + uint8_t serial[N230_EEPROM_SERIAL_LEN]; + uint8_t _pad_serial; + uint16_t hw_revision_compat; + uint8_t _pad0[18 - (N230_EEPROM_SERIAL_LEN + 1)]; + + //Ethernet specific + uint32_t gateway; + n230_eth_eeprom_map_t eth_info[N230_MAX_NUM_ETH_PORTS]; + + //User specific + uint8_t user_name[N230_EEPROM_NAME_LEN]; +} n230_eeprom_map_t; + +#ifdef __cplusplus +} //extern "C" +#endif + +// The following definitions are only useful in firmware. Exclude in host code. +#ifndef __cplusplus + +/*! + * Read the eeprom and update caches. + * Returns true if read was successful. + * If the read was not successful then the cache is initialized with + * default values and marked as dirty. + */ +bool read_n230_eeprom(); + +/*! + * Write the contents of the cache to the eeprom. + * Returns true if write was successful. + */ +bool write_n230_eeprom(); + +/*! + * Returns the dirty state of the cache. + */ +bool is_n230_eeprom_cache_dirty(); + +/*! + * Returns a const pointer to the EEPROM map. + */ +const n230_eeprom_map_t* get_n230_const_eeprom_map(); + +/*! + * Returns the settings for the the 'iface'th ethernet interface + */ +const n230_eth_eeprom_map_t* get_n230_ethernet_info(uint32_t iface); + +/*! + * Returns a non-const pointer to the EEPROM map. Will mark the cache as dirty. + */ +n230_eeprom_map_t* get_n230_eeprom_map(); + +/*! + * FPGA Image operations + */ +inline void read_n230_fpga_image_page(uint32_t offset, void *buf, uint32_t num_bytes); + +inline bool write_n230_fpga_image_page(uint32_t offset, const void *buf, uint32_t num_bytes); + +inline bool erase_n230_fpga_image_sector(uint32_t offset); + +#endif //ifdef __cplusplus + +#endif /* INCLUDED_N230_EEPROM_H */ 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..b19deb23a --- /dev/null +++ b/host/lib/usrp/n230/n230_eeprom_manager.cpp @@ -0,0 +1,207 @@ +// +// Copyright 2013-2014,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 "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; + + uint16_t ver_major = uhd::htonx<boost::uint16_t>(map.data_version_major); + uint16_t ver_minor = uhd::htonx<boost::uint16_t>(map.data_version_minor); + + _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)); + //The revision_compat field does not exist in version 1.0 + //EEPROM version 1.0 will only exist on HW revision 1 so it is safe to set + //revision_compat = revision + if (ver_major == 1 and ver_minor == 0) { + _mb_eeprom["revision_compat"] = _mb_eeprom["revision"]; + } else { + _mb_eeprom["revision_compat"] = boost::lexical_cast<std::string>( + uhd::htonx<boost::uint16_t>(map.hw_revision_compat)); + } + _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; + + // Automatic version upgrade handling + uint16_t old_ver_major = uhd::htonx<boost::uint16_t>(map.data_version_major); + uint16_t old_ver_minor = uhd::htonx<boost::uint16_t>(map.data_version_minor); + + //The revision_compat field does not exist for version 1.0 so force write it + //EEPROM version 1.0 will only exist on HW revision 1 so it is safe to set + //revision_compat = revision for the upgrade + bool force_write_version_compat = (old_ver_major == 1 and old_ver_minor == 0); + + 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("revision_compat")) { + map.hw_revision_compat = uhd::htonx<boost::uint16_t>( + boost::lexical_cast<boost::uint16_t>(_mb_eeprom["revision_compat"])); + } else if (force_write_version_compat) { + map.hw_revision_compat = map.hw_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..cc5aee9f3 --- /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 <boost/thread/mutex.hpp> +#include <uhd/transport/udp_simple.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/usrp/mboard_eeprom.hpp> +#include "n230_fw_host_iface.h" + +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..3aa96643f --- /dev/null +++ b/host/lib/usrp/n230/n230_fpga_defs.h @@ -0,0 +1,207 @@ +// +// 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 SR_CORE_MS0_GPIO = 50; +static const uint32_t SR_CORE_MS1_GPIO = 58; + +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; +static const uint32_t RB_CORE_MS0_GPIO = 5; +static const uint32_t RB_CORE_MS1_GPIO = 6; + +/******************************************************************* + * 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 + *******************************************************************/ +static const uint32_t GPSDO_UART_BAUDRATE = 115200; +static const uint32_t GPSDO_ST_ABSENT = 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_status_reg_t : public soft_reg64_ro_t { +public: + UHD_DEFINE_SOFT_REG_FIELD(REF_LOCKED, /*width*/ 1, /*shift*/ 0); //[0] + UHD_DEFINE_SOFT_REG_FIELD(GPSDO_STATUS, /*width*/ 8, /*shift*/ 32); //[32:39] + + core_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..e0820d9b2 --- /dev/null +++ b/host/lib/usrp/n230/n230_frontend_ctrl.cpp @@ -0,0 +1,243 @@ +// +// Copyright 2013-2014,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 "n230_frontend_ctrl.hpp" + +#include <uhd/utils/msg.hpp> +#include <uhd/exception.hpp> +#include <uhd/types/dict.hpp> +#include <boost/format.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); + +using namespace uhd::usrp; + +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_atr::gpio_atr_3000::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( + str(boost::format("n230: unknown stream index option: %d") % 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_atr::gpio_atr_3000::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_atr::gpio_atr_3000::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..377d23ba8 --- /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_atr_3000.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_atr::gpio_atr_3000::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_fw_defs.h b/host/lib/usrp/n230/n230_fw_defs.h new file mode 100644 index 000000000..fbdc67ebb --- /dev/null +++ b/host/lib/usrp/n230/n230_fw_defs.h @@ -0,0 +1,137 @@ +// +// 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_FW_DEFS_H +#define INCLUDED_N230_FW_DEFS_H + +#include <stdint.h> + +/*! + * Constants specific to N230 firmware. + * This header is shared by the firmware and host code. + * Therefore, this header may only contain valid C code. + * However, if it is included from within the host code, + * it will be namespaced appropriately + */ +#ifdef __cplusplus +namespace uhd { +namespace usrp { +namespace n230 { +namespace fw { +#endif + +static inline uint32_t reg_addr(uint32_t base, uint32_t offset) { + return ((base) + (offset)*4); +} + +/******************************************************************* + * Global + *******************************************************************/ +static const uint32_t CPU_CLOCK_FREQ = 80000000; +static const uint32_t PER_MILLISEC_CRON_JOBID = 0; +static const uint32_t PER_SECOND_CRON_JOBID = 1; + +/******************************************************************* + * Wishbone slave addresses + *******************************************************************/ +static const uint32_t WB_MAIN_RAM_BASE = 0x0000; +static const uint32_t WB_PKT_RAM_BASE = 0x8000; +static const uint32_t WB_SBRB_BASE = 0xa000; +static const uint32_t WB_SPI_FLASH_BASE = 0xb000; +static const uint32_t WB_ETH0_MAC_BASE = 0xc000; +static const uint32_t WB_ETH1_MAC_BASE = 0xd000; +static const uint32_t WB_XB_SBRB_BASE = 0xe000; +static const uint32_t WB_ETH0_I2C_BASE = 0xf600; +static const uint32_t WB_ETH1_I2C_BASE = 0xf700; +static const uint32_t WB_DBG_UART_BASE = 0xf900; + +/******************************************************************* + * Seting Register Base addresses + *******************************************************************/ +static const uint32_t SR_ZPU_SW_RST = 0; +static const uint32_t SR_ZPU_BOOT_DONE = 1; +static const uint32_t SR_ZPU_LEDS = 2; +static const uint32_t SR_ZPU_XB_LOCAL = 4; +static const uint32_t SR_ZPU_SFP_CTRL0 = 16; +static const uint32_t SR_ZPU_SFP_CTRL1 = 17; +static const uint32_t SR_ZPU_ETHINT0 = 64; +static const uint32_t SR_ZPU_ETHINT1 = 80; + +static const uint32_t SR_ZPU_SW_RST_NONE = 0x0; +static const uint32_t SR_ZPU_SW_RST_PHY = 0x1; +static const uint32_t SR_ZPU_SW_RST_RADIO = 0x2; + +/******************************************************************* + * Readback addresses + *******************************************************************/ +static const uint32_t RB_ZPU_COMPAT = 0; +static const uint32_t RB_ZPU_COUNTER = 1; +static const uint32_t RB_ZPU_SFP_STATUS0 = 2; +static const uint32_t RB_ZPU_SFP_STATUS1 = 3; +static const uint32_t RB_ZPU_ETH0_PKT_CNT = 6; +static const uint32_t RB_ZPU_ETH1_PKT_CNT = 7; + +/******************************************************************* + * Ethernet + *******************************************************************/ +static const uint32_t WB_PKT_RAM_CTRL_OFFSET = 0x1FFC; + +static const uint32_t SR_ZPU_ETHINT_FRAMER_BASE = 0; +static const uint32_t SR_ZPU_ETHINT_DISPATCHER_BASE = 8; + +//Eth framer constants +static const uint32_t ETH_FRAMER_SRC_MAC_HI = 0; +static const uint32_t ETH_FRAMER_SRC_MAC_LO = 1; +static const uint32_t ETH_FRAMER_SRC_IP_ADDR = 2; +static const uint32_t ETH_FRAMER_SRC_UDP_PORT = 3; +static const uint32_t ETH_FRAMER_DST_RAM_ADDR = 4; +static const uint32_t ETH_FRAMER_DST_IP_ADDR = 5; +static const uint32_t ETH_FRAMER_DST_UDP_MAC = 6; +static const uint32_t ETH_FRAMER_DST_MAC_LO = 7; + +/******************************************************************* + * CODEC + *******************************************************************/ +static const uint32_t CODEC_SPI_CLOCK_FREQ = 4000000; //4MHz +static const uint32_t ADF4001_SPI_CLOCK_FREQ = 200000; //200kHz + +/******************************************************************* + * UART + *******************************************************************/ +static const uint32_t DBG_UART_BAUD = 115200; + +/******************************************************************* + * Build Compatability Numbers + *******************************************************************/ +static const uint8_t PRODUCT_NUM = 0x01; +static const uint8_t COMPAT_MAJOR = 0x00; +static const uint16_t COMPAT_MINOR = 0x0000; + +static inline uint8_t get_prod_num(uint32_t compat_reg) { + return (compat_reg >> 24) & 0xFF; +} +static inline uint8_t get_compat_major(uint32_t compat_reg) { + return (compat_reg >> 16) & 0xFF; +} +static inline uint8_t get_compat_minor(uint32_t compat_reg) { + return compat_reg & 0xFFFF; +} + +#ifdef __cplusplus +}}}} //namespace +#endif +#endif /* INCLUDED_N230_FW_DEFS_H */ diff --git a/host/lib/usrp/n230/n230_fw_host_iface.h b/host/lib/usrp/n230/n230_fw_host_iface.h new file mode 100644 index 000000000..0391af0d9 --- /dev/null +++ b/host/lib/usrp/n230/n230_fw_host_iface.h @@ -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_FW_HOST_IFACE_H +#define INCLUDED_N230_FW_HOST_IFACE_H + +#include <stdint.h> + +/*! + * Structs and constants for N230 communication between firmware and host. + * This header is shared by the firmware and host code. + * Therefore, this header may only contain valid C code. + */ +#ifdef __cplusplus +extern "C" { +#endif + +//-------------------------------------------------- +// Ethernet related +// +#define N230_DEFAULT_ETH0_MAC {0x00, 0x50, 0xC2, 0x85, 0x3f, 0xff} +#define N230_DEFAULT_ETH1_MAC {0x00, 0x50, 0xC2, 0x85, 0x3f, 0x33} +#define N230_DEFAULT_ETH0_IP (192 << 24 | 168 << 16 | 10 << 8 | 2 << 0) +#define N230_DEFAULT_ETH1_IP (192 << 24 | 168 << 16 | 20 << 8 | 2 << 0) +#define N230_DEFAULT_ETH0_MASK (255 << 24 | 255 << 16 | 255 << 8 | 0 << 0) +#define N230_DEFAULT_ETH1_MASK (255 << 24 | 255 << 16 | 255 << 8 | 0 << 0) +#define N230_DEFAULT_GATEWAY (192 << 24 | 168 << 16 | 10 << 8 | 1 << 0) + +#define N230_FW_COMMS_UDP_PORT 49152 +#define N230_FW_COMMS_CVITA_PORT 49153 +#define N230_FW_COMMS_FLASH_PROG_PORT 49154 +// +//-------------------------------------------------- + +//-------------------------------------------------- +// Memory shared with host +// +#define N230_FW_HOST_SHMEM_BASE_ADDR 0x10000 +#define N230_FW_HOST_SHMEM_RW_BASE_ADDR 0x1000C +#define N230_FW_HOST_SHMEM_NUM_WORDS (sizeof(n230_host_shared_mem_data_t)/sizeof(uint32_t)) + +#define N230_FW_HOST_SHMEM_MAX_ADDR \ + (N230_FW_HOST_SHMEM_BASE_ADDR + ((N230_FW_HOST_SHMEM_NUM_WORDS - 1) * sizeof(uint32_t))) + +#define N230_FW_HOST_SHMEM_OFFSET(member) \ + (N230_FW_HOST_SHMEM_BASE_ADDR + ((uint32_t)offsetof(n230_host_shared_mem_data_t, member))) + +//The shared memory block can only be accessed on 32-bit boundaries +typedef struct { //All fields must be 32-bit wide to avoid packing directives + //Read-Only fields (N230_FW_HOST_SHMEM_BASE_ADDR) + uint32_t fw_compat_num; //Compat number must be at offset 0 + uint32_t fw_version_hash; + uint32_t claim_status; + + //Read-Write fields (N230_FW_HOST_SHMEM_RW_BASE_ADDR) + uint32_t scratch; + uint32_t claim_time; + uint32_t claim_src; +} n230_host_shared_mem_data_t; + +typedef union +{ + uint32_t buff[N230_FW_HOST_SHMEM_NUM_WORDS]; + n230_host_shared_mem_data_t data; +} n230_host_shared_mem_t; + +#define N230_FW_PRODUCT_ID 1 +#define N230_FW_COMPAT_NUM_MAJOR 32 +#define N230_FW_COMPAT_NUM_MINOR 0 +#define N230_FW_COMPAT_NUM (((N230_FW_COMPAT_NUM_MAJOR & 0xFF) << 16) | (N230_FW_COMPAT_NUM_MINOR & 0xFFFF)) +// +//-------------------------------------------------- + +//-------------------------------------------------- +// Flash read-write interface for host +// +#define N230_FLASH_COMM_FLAGS_ACK 0x00000001 +#define N230_FLASH_COMM_FLAGS_CMD_MASK 0x00000FF0 +#define N230_FLASH_COMM_FLAGS_ERROR_MASK 0xFF000000 + +#define N230_FLASH_COMM_CMD_READ_NV_DATA 0x00000010 +#define N230_FLASH_COMM_CMD_WRITE_NV_DATA 0x00000020 +#define N230_FLASH_COMM_CMD_READ_FPGA 0x00000030 +#define N230_FLASH_COMM_CMD_WRITE_FPGA 0x00000040 +#define N230_FLASH_COMM_CMD_ERASE_FPGA 0x00000050 + +#define N230_FLASH_COMM_ERR_PKT_ERROR 0x80000000 +#define N230_FLASH_COMM_ERR_CMD_ERROR 0x40000000 +#define N230_FLASH_COMM_ERR_SIZE_ERROR 0x20000000 + +#define N230_FLASH_COMM_MAX_PAYLOAD_SIZE 128 + +typedef struct +{ + uint32_t flags; + uint32_t seq; + uint32_t offset; + uint32_t size; + uint8_t data[N230_FLASH_COMM_MAX_PAYLOAD_SIZE]; +} n230_flash_prog_t; +// +//-------------------------------------------------- + +#define N230_HW_REVISION_COMPAT 1 +#define N230_HW_REVISION_MIN 1 + + +#define N230_CLAIMER_TIMEOUT_IN_MS 2000 + +#ifdef __cplusplus +} +#endif + +#endif /* INCLUDED_N230_FW_HOST_IFACE_H */ 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..9dd4a252d --- /dev/null +++ b/host/lib/usrp/n230/n230_image_loader.cpp @@ -0,0 +1,209 @@ +// +// 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/utils/paths.hpp> +#include <uhd/transport/udp_simple.hpp> +#include <boost/algorithm/string.hpp> +#include <boost/filesystem.hpp> +#include <boost/format.hpp> +#include "n230_fw_host_iface.h" +#include "n230_impl.hpp" + +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 + std::string fpga_img_path = loader_args.fpga_path; + bool fpga_path_specified = !loader_args.fpga_path.empty(); + if (not fpga_path_specified) { + fpga_img_path = ( + fs::path(uhd::get_pkg_path()) / "share" / "uhd" / "images" / "usrp_n230_fpga.bit" + ).string(); + } + + if (not boost::filesystem::exists(fpga_img_path)) { + if (fpga_path_specified) { + throw uhd::runtime_error(str(boost::format("The file \"%s\" does not exist.") % fpga_img_path)); + } else { + throw uhd::runtime_error(str(boost::format( + "Could not find the default FPGA image: %s.\n" + "Either specify the --fpga-path argument or download the latest prebuilt images:\n" + "%s\n") + % fpga_img_path % print_utility_error("uhd_images_downloader.py"))); + } + } + xil_bitfile_hdr_t hdr; + _parse_bitfile_header(fpga_img_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"] % fpga_img_path; + + // Write image + std::ifstream image(fpga_img_path.c_str(), std::ios::binary); + size_t image_size = boost::filesystem::file_size(fpga_img_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 << "FPGA image loaded successfully." << std::endl; + std::cout << std::endl << "Power-cycle the device to run the image." << 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.") + % fpga_img_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..b005182cf --- /dev/null +++ b/host/lib/usrp/n230/n230_impl.cpp @@ -0,0 +1,591 @@ +// +// 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 "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/types/direction.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 "../common/fw_comm_protocol.h" +#include "n230_defaults.h" +#include "n230_fpga_defs.h" +#include "n230_fw_defs.h" +#include "n230_fw_host_iface.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)); + + //Corner case: If two devices have the same IP but different MAC + //addresses and are used back-to-back it takes a while for ARP tables + //on the host to update in which period brodcasts will respond but + //connected communication can fail. Retry the following call to allow + //the stack to update + size_t first_conn_retries = 10; + usrp3::usrp3_fw_ctrl_iface::sptr fw_ctrl; + while (first_conn_retries > 0) { + try { + fw_ctrl = usrp3::usrp3_fw_ctrl_iface::make(ctrl_xport, N230_FW_PRODUCT_ID, false /*verbose*/); + break; + } catch (uhd::io_error& ex) { + boost::this_thread::sleep(boost::posix_time::milliseconds(500)); + first_conn_retries--; + } + } + if (first_conn_retries > 0) { + uint32_t compat_reg = fw_ctrl->peek32(fw::reg_addr(fw::WB_SBRB_BASE, fw::RB_ZPU_COMPAT)); + if (fw::get_prod_num(compat_reg) == 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 addresses + 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"]); + } + + //Read EEPROM and perform version checks before talking to HW + _eeprom_mgr = boost::make_shared<n230_eeprom_manager>(ip_addrs[0]); + const mboard_eeprom_t& mb_eeprom = _eeprom_mgr->get_mb_eeprom(); + bool recover_mb_eeprom = dev_addr.has_key("recover_mb_eeprom"); + if (recover_mb_eeprom) { + UHD_MSG(warning) << "UHD is operating in EEPROM Recovery Mode which disables hardware version " + "checks.\nOperating in this mode may cause hardware damage and unstable " + "radio performance!"<< std::endl; + } + boost::uint16_t hw_rev = boost::lexical_cast<boost::uint16_t>(mb_eeprom["revision"]); + boost::uint16_t hw_rev_compat = boost::lexical_cast<boost::uint16_t>(mb_eeprom["revision_compat"]); + if (not recover_mb_eeprom) { + if (hw_rev_compat > N230_HW_REVISION_COMPAT) { + throw uhd::runtime_error(str(boost::format( + "Hardware is too new for this software. Please upgrade to a driver that supports hardware revision %d.") + % hw_rev)); + } + } + + //Initialize all subsystems + _resource_mgr = boost::make_shared<n230_resource_manager>(ip_addrs, _dev_args.get_safe_mode()); + _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); + + //------------------------------------------------------------------ + // MiniSAS GPIO + //------------------------------------------------------------------ + _tree->create<boost::uint32_t>(mb_path / "gpio" / "FP0" / "DDR") + .set(0) + .subscribe(boost::bind(&gpio_atr::gpio_atr_3000::set_gpio_attr, + _resource_mgr->get_minisas_gpio_ctrl_sptr(0), gpio_atr::GPIO_DDR, _1)); + _tree->create<boost::uint32_t>(mb_path / "gpio" / "FP1" / "DDR") + .set(0) + .subscribe(boost::bind(&gpio_atr::gpio_atr_3000::set_gpio_attr, + _resource_mgr->get_minisas_gpio_ctrl_sptr(1), gpio_atr::GPIO_DDR, _1)); + _tree->create<boost::uint32_t>(mb_path / "gpio" / "FP0" / "OUT") + .set(0) + .subscribe(boost::bind(&gpio_atr::gpio_atr_3000::set_gpio_attr, + _resource_mgr->get_minisas_gpio_ctrl_sptr(0), gpio_atr::GPIO_OUT, _1)); + _tree->create<boost::uint32_t>(mb_path / "gpio" / "FP1" / "OUT") + .set(0) + .subscribe(boost::bind(&gpio_atr::gpio_atr_3000::set_gpio_attr, + _resource_mgr->get_minisas_gpio_ctrl_sptr(1), gpio_atr::GPIO_OUT, _1)); + _tree->create<boost::uint32_t>(mb_path / "gpio" / "FP0" / "READBACK") + .publish(boost::bind(&gpio_atr::gpio_atr_3000::read_gpio, _resource_mgr->get_minisas_gpio_ctrl_sptr(0))); + _tree->create<boost::uint32_t>(mb_path / "gpio" / "FP1" / "READBACK") + .publish(boost::bind(&gpio_atr::gpio_atr_3000::read_gpio, _resource_mgr->get_minisas_gpio_ctrl_sptr(1))); + + //------------------------------------------------------------------ + // GPSDO sensors + //------------------------------------------------------------------ + if (_resource_mgr->is_gpsdo_present()) { + uhd::gps_ctrl::sptr gps_ctrl = _resource_mgr->get_gps_ctrl(); + 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<direction_t> data_directions = boost::assign::list_of(RX_DIRECTION)(TX_DIRECTION); + BOOST_FOREACH(direction_t direction, data_directions) { + const std::string dir_str = (direction == RX_DIRECTION) ? "rx" : "tx"; + const std::string key = boost::to_upper_copy(dir_str) + str(boost::format("%u") % (instance + 1)); + const fs_path rf_fe_path = mb_path / "dboards" / "A" / (dir_str + "_frontends") / ((instance==0)?"A":"B"); + + //CODEC subtree + _resource_mgr->get_codec_mgr().populate_frontend_subtree(_tree->subtree(rf_fe_path), key, direction); + + //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..f13dd0b33 --- /dev/null +++ b/host/lib/usrp/n230/n230_resource_manager.cpp @@ -0,0 +1,569 @@ +// +// 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 "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/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> +#include "n230_fw_defs.h" +#include "n230_fw_host_iface.h" + +#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 + bool dual_eth_expected = (ip_addrs.size() > 1); + BOOST_FOREACH(const std::string& addr, ip_addrs) { + n230_eth_conn_t conn_iface; + conn_iface.ip_addr = addr; + + boost::uint32_t iface_id = 0xFFFFFFFF; + try { + 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); + } catch (uhd::io_error&) { + throw uhd::io_error(str(boost::format( + "Could not communicate with the device over address %s") % + conn_iface.ip_addr)); + } + switch (iface_id) { + case N230_ETH0_IFACE_ID: conn_iface.type = ETH0; break; + case N230_ETH1_IFACE_ID: conn_iface.type = ETH1; break; + default: { + if (dual_eth_expected) { + throw uhd::runtime_error("N230 Initialization Error: Could not detect ethernet port number."); + } else { + //For backwards compatibility, if only one port is specified, assume that a detection + //failure means that the device does not support dual-ethernet behavior. + conn_iface.type = ETH0; break; + } + } + } + _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 AD9361 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); + _codec_mgr = ad936x_manager::make(_codec_ctrl, fpga::NUM_RADIOS); + _codec_mgr->init_codec(); + + //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_atr::gpio_atr_3000::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 miniSAS GPIO interfaces + _ms0_gpio = gpio_atr::gpio_atr_3000::make( + _core_ctrl, fpga::sr_addr(fpga::SR_CORE_MS0_GPIO), fpga::rb_addr(fpga::RB_CORE_MS0_GPIO)); + _ms0_gpio->set_atr_mode(gpio_atr::MODE_GPIO,gpio_atr::gpio_atr_3000::MASK_SET_ALL); + _ms1_gpio = gpio_atr::gpio_atr_3000::make( + _core_ctrl, fpga::sr_addr(fpga::SR_CORE_MS1_GPIO), fpga::rb_addr(fpga::RB_CORE_MS1_GPIO)); + _ms1_gpio->set_atr_mode(gpio_atr::MODE_GPIO,gpio_atr::gpio_atr_3000::MASK_SET_ALL); + + //Create GPSDO interface + if (_core_status_reg.read(fpga::core_status_reg_t::GPSDO_STATUS) != fpga::GPSDO_ST_ABSENT) { + UHD_MSG(status) << "Detecting GPSDO.... " << std::flush; + try { + 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/fpga::GPSDO_UART_BAUDRATE); + _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 + _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 is_gpsdo_present()) { + _core_ctrl->poke32(fpga::sr_addr(fpga::SR_CORE_GPSDO_ST), fpga::GPSDO_ST_ABSENT); + } + } + + //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_atr::gpio_atr_3000::make_write_only(radio.ctrl, fpga::sr_addr(fpga::SR_RADIO_ATR)); + radio.gpio_atr->set_atr_mode(gpio_atr::MODE_ATR,gpio_atr::gpio_atr_3000::MASK_SET_ALL); + + //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; +} + +std::string n230_resource_manager::_get_fpga_upgrade_msg() { + std::string img_loader_path = + (fs::path(uhd::get_pkg_path()) / "bin" / "uhd_image_loader").string(); + + return str(boost::format( + "\nDownload the appropriate FPGA images for this version of UHD.\n" + "%s\n\n" + "Then burn a new image to the on-board flash storage of your\n" + "USRP N230 device using the image loader utility. Use this command:\n" + "\n \"%s\" --args=\"type=n230,addr=%s\"\n") + % print_utility_error("uhd_images_downloader.py") + % img_loader_path % _get_conn(PRI_ETH).ip_addr); + +} + +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) + % _get_fpga_upgrade_msg())); + } +} + +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) + % _get_fpga_upgrade_msg())); + } +} + +}}} //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..0a1178bd2 --- /dev/null +++ b/host/lib/usrp/n230/n230_resource_manager.hpp @@ -0,0 +1,318 @@ +// +// 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_atr_3000.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 "ad936x_manager.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_atr::gpio_atr_3000::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; + } + + //AD931 control interface + inline ad9361_ctrl& get_codec_ctrl() const { + return *_codec_ctrl; + } + inline ad9361_ctrl::sptr get_codec_ctrl_sptr() { + return _codec_ctrl; + } + inline uhd::usrp::ad936x_manager& get_codec_mgr() const { + return *_codec_mgr; + } + + //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; + } + + //MiniSAS GPIO control + inline gpio_atr::gpio_atr_3000::sptr get_minisas_gpio_ctrl_sptr(size_t idx) { + return idx == 0 ? _ms0_gpio : _ms1_gpio; + } + + inline gpio_atr::gpio_atr_3000& get_minisas_gpio_ctrl(size_t idx) { + return *get_minisas_gpio_ctrl_sptr(idx); + } + + //GPSDO control + inline bool is_gpsdo_present() { + return _gps_ctrl.get() and _gps_ctrl->gps_detected(); + } + + 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); + + std::string _get_fpga_upgrade_msg(); + 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; + uhd::usrp::ad936x_manager::sptr _codec_mgr; + + //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_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; + + //miniSAS GPIO + gpio_atr::gpio_atr_3000::sptr _ms0_gpio; + gpio_atr::gpio_atr_3000::sptr _ms1_gpio; + + //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 */ |