//
// 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 .
//
#include "e300_network.hpp"
#ifdef E300_NATIVE
#include "e300_impl.hpp"
#include "ad9361_ctrl.hpp"
#include "e300_sensor_manager.hpp"
#include "e300_fifo_config.hpp"
#include "e300_spi.hpp"
#include "e300_i2c.hpp"
#include "e300_defaults.hpp"
#include "e300_common.hpp"
#include "e300_remote_codec_ctrl.hpp"
#include
#include
#include
#include
#include
#include
#include
#include
using namespace uhd;
using namespace uhd::transport;
namespace asio = boost::asio;
namespace fs = boost::filesystem;
namespace uhd { namespace usrp { namespace e300 {
static const size_t E300_NETWORK_DEBUG = false;
static inline bool wait_for_recv_ready(int sock_fd, const size_t timeout_ms)
{
//setup timeval for timeout
timeval tv;
tv.tv_sec = 0;
tv.tv_usec = timeout_ms*1000;
//setup rset for timeout
fd_set rset;
FD_ZERO(&rset);
FD_SET(sock_fd, &rset);
//call select with timeout on receive socket
return ::select(sock_fd+1, &rset, NULL, NULL, &tv) > 0;
}
static boost::mutex endpoint_mutex;
/***********************************************************************
* Receive tunnel - forwards recv interface to send socket
**********************************************************************/
static void e300_recv_tunnel(
const std::string &name,
uhd::transport::zero_copy_if::sptr recver,
boost::shared_ptr sender,
asio::ip::udp::endpoint *endpoint,
bool *running
)
{
asio::ip::udp::endpoint _tx_endpoint;
try
{
while (*running)
{
//step 1 - get the buffer
managed_recv_buffer::sptr buff = recver->get_recv_buff();
if (not buff) continue;
if (E300_NETWORK_DEBUG) UHD_MSG(status) << name << " got " << buff->size() << std::endl;
//step 1.5 -- update endpoint
{
boost::mutex::scoped_lock l(endpoint_mutex);
_tx_endpoint = *endpoint;
}
//step 2 - send to the socket
sender->send_to(asio::buffer(buff->cast(), buff->size()), _tx_endpoint);
}
}
catch(const std::exception &ex)
{
UHD_MSG(error) << "e300_recv_tunnel exit " << name << " " << ex.what() << std::endl;
}
catch(...)
{
UHD_MSG(error) << "e300_recv_tunnel exit " << name << std::endl;
}
UHD_MSG(status) << "e300_recv_tunnel exit " << name << std::endl;
*running = false;
}
/***********************************************************************
* Send tunnel - forwards recv socket to send interface
**********************************************************************/
static void e300_send_tunnel(
const std::string &name,
boost::shared_ptr recver,
uhd::transport::zero_copy_if::sptr sender,
asio::ip::udp::endpoint *endpoint,
bool *running
)
{
asio::ip::udp::endpoint _rx_endpoint;
try
{
while (*running)
{
//step 1 - get the buffer
managed_send_buffer::sptr buff = sender->get_send_buff();
if (not buff) continue;
//step 2 - recv from socket
while (not wait_for_recv_ready(recver->native(), 100) and *running){}
if (not *running) break;
const size_t num_bytes = recver->receive_from(asio::buffer(buff->cast(), buff->size()), _rx_endpoint);
if (E300_NETWORK_DEBUG) UHD_MSG(status) << name << " got " << num_bytes << std::endl;
//step 2.5 -- update endpoint
{
boost::mutex::scoped_lock l(endpoint_mutex);
*endpoint = _rx_endpoint;
}
//step 3 - commit the buffer
buff->commit(num_bytes);
}
}
catch(const std::exception &ex)
{
UHD_MSG(error) << "e300_send_tunnel exit " << name << " " << ex.what() << std::endl;
}
catch(...)
{
UHD_MSG(error) << "e300_send_tunnel exit " << name << std::endl;
}
UHD_MSG(status) << "e300_send_tunnel exit " << name << std::endl;
*running = false;
}
static void e300_codec_ctrl_tunnel(
const std::string &name,
boost::shared_ptr socket,
ad9361_ctrl::sptr _codec_ctrl,
asio::ip::udp::endpoint *endpoint,
bool *running
)
{
asio::ip::udp::endpoint _endpoint;
try
{
while (*running)
{
uint8_t in_buff[64] = {};
uint8_t out_buff[64] = {};
const size_t num_bytes = socket->receive_from(asio::buffer(in_buff), *endpoint);
typedef e300_remote_codec_ctrl::transaction_t codec_xact_t;
if (num_bytes < sizeof(codec_xact_t)) {
std::cout << "Received short packet of " << num_bytes << std::endl;
continue;
}
codec_xact_t *in = reinterpret_cast(in_buff);
codec_xact_t *out = reinterpret_cast(out_buff);
std::memcpy(out, in, sizeof(codec_xact_t));
std::string which_str;
switch (uhd::ntohx(in->which)) {
case codec_xact_t::CHAIN_TX1:
which_str = "TX1"; break;
case codec_xact_t::CHAIN_TX2:
which_str = "TX2"; break;
case codec_xact_t::CHAIN_RX1:
which_str = "RX1"; break;
case codec_xact_t::CHAIN_RX2:
which_str = "RX2"; break;
default:
which_str = ""; break;
}
switch (uhd::ntohx(in->action)) {
case codec_xact_t::ACTION_SET_GAIN:
out->gain = _codec_ctrl->set_gain(which_str, in->gain);
break;
case codec_xact_t::ACTION_SET_CLOCK_RATE:
out->rate = _codec_ctrl->set_clock_rate(in->rate);
break;
case codec_xact_t::ACTION_SET_ACTIVE_CHANS:
_codec_ctrl->set_active_chains(
uhd::ntohx(in->bits) & (1<<0),
uhd::ntohx(in->bits) & (1<<1),
uhd::ntohx(in->bits) & (1<<2),
uhd::ntohx(in->bits) & (1<<3));
break;
case codec_xact_t::ACTION_TUNE:
out->freq = _codec_ctrl->tune(which_str, in->freq);
break;
case codec_xact_t::ACTION_SET_LOOPBACK:
_codec_ctrl->data_port_loopback(
uhd::ntohx(in->bits) & 1);
break;
case codec_xact_t::ACTION_GET_RSSI:
out->rssi = _codec_ctrl->get_rssi(which_str).to_real();
break;
default:
UHD_MSG(status) << "Got unknown request?!" << std::endl;
//Zero out actions to fail this request on client
out->action = uhd::htonx(0);
}
socket->send_to(asio::buffer(out_buff, 64), *endpoint);
}
}
catch(const std::exception &ex)
{
UHD_MSG(error) << "e300_ctrl_tunnel exit " << name << " " << ex.what() << std::endl;
}
catch(...)
{
UHD_MSG(error) << "e300_ctrl_tunnel exit " << name << std::endl;
}
UHD_MSG(status) << "e300_ctrl_tunnel exit " << name << std::endl;
*running = false;
}
static void e300_global_regs_tunnel(
const std::string &name,
boost::shared_ptr socket,
global_regs::sptr regs,
asio::ip::udp::endpoint *endpoint,
bool *running
)
{
UHD_ASSERT_THROW(regs);
asio::ip::udp::endpoint _endpoint;
try
{
while (*running)
{
uint8_t in_buff[16] = {};
const size_t num_bytes = socket->receive_from(asio::buffer(in_buff), *endpoint);
if (num_bytes < 16) {
std::cout << "Received short packet: " << num_bytes << std::endl;
continue;
}
global_regs_transaction_t *in =
reinterpret_cast(in_buff);
if(uhd::ntohx(in->is_poke)) {
regs->poke32(uhd::ntohx(in->addr), uhd::ntohx(in->data));
}
else {
in->data = uhd::htonx(regs->peek32(uhd::ntohx(in->addr)));
socket->send_to(asio::buffer(in_buff, 16), *endpoint);
}
}
}
catch(const std::exception &ex)
{
UHD_MSG(error) << "e300_gregs_tunnel exit " << name << " " << ex.what() << std::endl;
}
catch(...)
{
UHD_MSG(error) << "e300_gregs_tunnel exit " << name << std::endl;
}
UHD_MSG(status) << "e300_gregs_tunnel exit " << name << std::endl;
*running = false;
}
static void e300_sensor_tunnel(
const std::string &name,
boost::shared_ptr socket,
e300_sensor_manager::sptr sensor_manager,
asio::ip::udp::endpoint *endpoint,
bool *running
)
{
asio::ip::udp::endpoint _endpoint;
try
{
while (*running)
{
uint8_t in_buff[128] = {};
const size_t num_bytes = socket->receive_from(asio::buffer(in_buff), *endpoint);
if (num_bytes < sizeof(sensor_transaction_t)) {
std::cout << "Received short packet: " << num_bytes << std::endl;
continue;
}
uhd::usrp::e300::sensor_transaction_t *in =
reinterpret_cast(in_buff);
if (uhd::ntohx(in->which) == ZYNQ_TEMP) {
sensor_value_t temp = sensor_manager->get_mb_temp();
// TODO: This is ugly ... use proper serialization
in->value = uhd::htonx(
e300_sensor_manager::pack_float_in_uint32_t(temp.to_real()));
} else if (uhd::ntohx(in->which) == GPS_FOUND) {
in->value = uhd::htonx(
sensor_manager->get_gps_found() ? 1 : 0);
} else if (uhd::ntohx(in->which) == GPS_LOCK) {
in->value = uhd::htonx(
sensor_manager->get_gps_lock().to_bool() ? 1 : 0);
} else if (uhd::ntohx(in->which) == GPS_TIME) {
in->value = uhd::htonx(
sensor_manager->get_gps_time().to_int());
} else
UHD_MSG(status) << "Got unknown request?!" << std::endl;
socket->send_to(asio::buffer(in_buff, sizeof(sensor_transaction_t)), *endpoint);
}
}
catch(const std::exception &ex)
{
UHD_MSG(error) << "e300_sensor_tunnel exit " << name << " " << ex.what() << std::endl;
}
catch(...)
{
UHD_MSG(error) << "e300_sensor_tunnel exit " << name << std::endl;
}
UHD_MSG(status) << "e300_sensor_tunnel exit " << name << std::endl;
*running = false;
}
static void e300_i2c_tunnel(
const std::string &name,
boost::shared_ptr socket,
uhd::usrp::e300::i2c::sptr i2c,
asio::ip::udp::endpoint *endpoint,
bool *running
)
{
UHD_ASSERT_THROW(i2c);
asio::ip::udp::endpoint _endpoint;
try
{
while (*running)
{
uint8_t in_buff[sizeof(uhd::usrp::e300::i2c_transaction_t)];
const size_t num_bytes = socket->receive_from(asio::buffer(in_buff), *endpoint);
if (num_bytes < sizeof(uhd::usrp::e300::i2c_transaction_t)) {
std::cout << "Received short packet: " << num_bytes << std::endl;
continue;
}
uhd::usrp::e300::i2c_transaction_t *in =
reinterpret_cast(in_buff);
// byte addressed accesses go through here
if(in->type & i2c::ONEBYTE) {
if(in->type & i2c::WRITE) {
i2c->set_i2c_reg8(
in->addr,
uhd::ntohx(in->reg), in->data);
} else {
in->data = i2c->get_i2c_reg8(in->addr, uhd::ntohx(in->reg));
socket->send_to(asio::buffer(in_buff, sizeof(in_buff)), *endpoint);
}
// 2 byte addressed accesses go through here
} else if (in->type & i2c::TWOBYTE) {
if(in->type & i2c::WRITE) {
i2c->set_i2c_reg16(
in->addr,
uhd::ntohx(in->reg), in->data);
} else {
in->data = i2c->get_i2c_reg16(in->addr, uhd::ntohx(in->reg));
socket->send_to(asio::buffer(in_buff, sizeof(in_buff)), *endpoint);
}
} else {
UHD_MSG(error) << "e300_i2c_tunnel could not handle message." << std::endl;
}
}
}
catch(const std::exception &ex)
{
UHD_MSG(error) << "e300_i2c_tunnel exit " << name << " " << ex.what() << std::endl;
}
catch(...)
{
UHD_MSG(error) << "e300_i2c_tunnel exit " << name << std::endl;
}
UHD_MSG(status) << "e300_i2c_tunnel exit " << name << std::endl;
*running = false;
}
class network_server_impl : public network_server
{
public:
network_server_impl(const uhd::device_addr_t &device_addr);
virtual ~network_server_impl(void);
void run(void);
private:
struct xports_t
{
uhd::transport::zero_copy_if::sptr send_ctrl_xport;
uhd::transport::zero_copy_if::sptr recv_ctrl_xport;
uhd::transport::zero_copy_if::sptr tx_data_xport;
uhd::transport::zero_copy_if::sptr tx_flow_xport;
uhd::transport::zero_copy_if::sptr rx_data_xport;
uhd::transport::zero_copy_if::sptr rx_flow_xport;
};
private:
void _run_server(
const std::string &port,
const std::string &what,
const size_t fe);
private:
boost::shared_ptr _fifo_iface;
xports_t _xports[2];
boost::shared_ptr _codec_ctrl;
boost::shared_ptr _global_regs;
boost::shared_ptr _sensor_manager;
boost::shared_ptr _eeprom_manager;
};
network_server_impl::~network_server_impl(void)
{
}
/***********************************************************************
* The UDP server itself
**********************************************************************/
void network_server_impl::_run_server(
const std::string &port,
const std::string &what,
const size_t fe)
{
asio::io_service io_service;
asio::ip::udp::resolver resolver(io_service);
asio::ip::udp::resolver::query query(asio::ip::udp::v4(), "0.0.0.0", port);
asio::ip::udp::endpoint endpoint = *resolver.resolve(query);
//boost::shared_ptr acceptor(new asio::ip::udp::acceptor(io_service, endpoint));
while (not boost::this_thread::interruption_requested())
{
UHD_MSG(status) << "e300 run server on port " << port << " for " << what << std::endl;
try
{
//while (not wait_for_recv_ready(acceptor->native(), 100))
//{
// if (boost::this_thread::interruption_requested()) return;
//}
boost::shared_ptr socket;
socket.reset(new asio::ip::udp::socket(io_service, endpoint));
//acceptor->accept(*socket);
UHD_MSG(status) << "e300 socket accept on port " << port << " for " << what << std::endl;
//asio::ip::udp::no_delay option(true);
//socket->set_option(option);
boost::thread_group tg;
bool running = true;
xports_t &perif = _xports[fe];
if (what == "RX") {
tg.create_thread(boost::bind(&e300_recv_tunnel, "RX data tunnel", perif.rx_data_xport, socket, &endpoint, &running));
tg.create_thread(boost::bind(&e300_send_tunnel, "RX flow tunnel", socket, perif.rx_flow_xport, &endpoint, &running));
}
if (what == "TX") {
tg.create_thread(boost::bind(&e300_recv_tunnel, "TX flow tunnel", perif.tx_flow_xport, socket, &endpoint, &running));
tg.create_thread(boost::bind(&e300_send_tunnel, "TX data tunnel", socket, perif.tx_data_xport, &endpoint, &running));
}
if (what == "CTRL") {
tg.create_thread(boost::bind(&e300_recv_tunnel, "response tunnel", perif.recv_ctrl_xport, socket, &endpoint, &running));
tg.create_thread(boost::bind(&e300_send_tunnel, "control tunnel", socket, perif.send_ctrl_xport, &endpoint, &running));
}
if (what == "CODEC") {
tg.create_thread(boost::bind(&e300_codec_ctrl_tunnel, "CODEC tunnel", socket, _codec_ctrl, &endpoint, &running));
}
if (what == "I2C") {
tg.create_thread(boost::bind(&e300_i2c_tunnel, "I2C tunnel", socket, _eeprom_manager->get_i2c_sptr(), &endpoint, &running));
}
if (what == "GREGS") {
tg.create_thread(boost::bind(&e300_global_regs_tunnel, "GREGS tunnel", socket, _global_regs, &endpoint, &running));
}
if (what == "SENSOR") {
tg.create_thread(boost::bind(&e300_sensor_tunnel, "SENSOR tunnel", socket, _sensor_manager, &endpoint, &running));
}
tg.join_all();
socket->close();
socket.reset();
}
catch(...){}
}
}
void network_server_impl::run()
{
for(;;)
{
boost::thread_group tg;
tg.create_thread(boost::bind(&network_server_impl::_run_server, this, E300_SERVER_RX_PORT0, "RX",0));
tg.create_thread(boost::bind(&network_server_impl::_run_server, this, E300_SERVER_TX_PORT0, "TX",0));
tg.create_thread(boost::bind(&network_server_impl::_run_server, this, E300_SERVER_CTRL_PORT0, "CTRL",0));
tg.create_thread(boost::bind(&network_server_impl::_run_server, this, E300_SERVER_RX_PORT1, "RX",1));
tg.create_thread(boost::bind(&network_server_impl::_run_server, this, E300_SERVER_TX_PORT1, "TX",1));
tg.create_thread(boost::bind(&network_server_impl::_run_server, this, E300_SERVER_CTRL_PORT1, "CTRL",1));
tg.create_thread(boost::bind(&network_server_impl::_run_server, this, E300_SERVER_SENSOR_PORT, "SENSOR", 0 /*don't care */));
tg.create_thread(boost::bind(&network_server_impl::_run_server, this, E300_SERVER_CODEC_PORT, "CODEC", 0 /*don't care */));
tg.create_thread(boost::bind(&network_server_impl::_run_server, this, E300_SERVER_GREGS_PORT, "GREGS", 0 /*don't care */));
tg.create_thread(boost::bind(&network_server_impl::_run_server, this, E300_SERVER_I2C_PORT, "I2C", 0 /*don't care */));
tg.join_all();
}
}
network_server_impl::network_server_impl(const uhd::device_addr_t &device_addr)
{
_eeprom_manager = boost::make_shared(i2c::make_i2cdev(E300_I2CDEV_DEVICE));
if (not device_addr.has_key("no_reload_fpga")) {
// Load FPGA image if provided via args
if (device_addr.has_key("fpga")) {
common::load_fpga_image(device_addr["fpga"]);
// Else load the FPGA image based on the product ID
} else {
//extract the FPGA path for the e300
const boost::uint16_t pid = boost::lexical_cast(
_eeprom_manager->get_mb_eeprom()["product"]);
std::string fpga_image;
switch(e300_eeprom_manager::get_mb_type(pid)) {
case e300_eeprom_manager::USRP_E310_MB:
fpga_image = find_image_path(E310_FPGA_FILE_NAME);
break;
case e300_eeprom_manager::USRP_E300_MB:
fpga_image = find_image_path(E300_FPGA_FILE_NAME);
break;
case e300_eeprom_manager::UNKNOWN:
default:
UHD_MSG(warning) << "Unknown motherboard type, loading e300 image."
<< std::endl;
fpga_image = find_image_path(E300_FPGA_FILE_NAME);
break;
}
common::load_fpga_image(fpga_image);
}
}
uhd::transport::zero_copy_xport_params ctrl_xport_params;
ctrl_xport_params.recv_frame_size = e300::DEFAULT_CTRL_FRAME_SIZE;
ctrl_xport_params.num_recv_frames = e300::DEFAULT_CTRL_NUM_FRAMES;
ctrl_xport_params.send_frame_size = e300::DEFAULT_CTRL_FRAME_SIZE;
ctrl_xport_params.num_send_frames = e300::DEFAULT_CTRL_NUM_FRAMES;
uhd::transport::zero_copy_xport_params data_xport_params;
data_xport_params.recv_frame_size = device_addr.cast("recv_frame_size", e300::DEFAULT_RX_DATA_FRAME_SIZE);
data_xport_params.num_recv_frames = device_addr.cast("num_recv_frames", e300::DEFAULT_RX_DATA_NUM_FRAMES);
data_xport_params.send_frame_size = device_addr.cast("send_frame_size", e300::DEFAULT_TX_DATA_FRAME_SIZE);
data_xport_params.num_send_frames = device_addr.cast("num_send_frames", e300::DEFAULT_TX_DATA_NUM_FRAMES);
// until we figure out why this goes wrong we'll keep this hack around
data_xport_params.recv_frame_size =
std::min(e300::MAX_NET_RX_DATA_FRAME_SIZE, data_xport_params.recv_frame_size);
data_xport_params.send_frame_size =
std::min(e300::MAX_NET_TX_DATA_FRAME_SIZE, data_xport_params.send_frame_size);
e300_fifo_config_t fifo_cfg;
try {
fifo_cfg = e300_read_sysfs();
} catch (uhd::lookup_error &e) {
throw uhd::runtime_error("Failed to get driver parameters from sysfs.");
}
_fifo_iface = e300_fifo_interface::make(fifo_cfg);
_global_regs = global_regs::make(_fifo_iface->get_global_regs_base());
// static mapping, boooohhhhhh
_xports[0].send_ctrl_xport = _fifo_iface->make_send_xport(E300_R0_CTRL_STREAM, ctrl_xport_params);
_xports[0].recv_ctrl_xport = _fifo_iface->make_recv_xport(E300_R0_CTRL_STREAM, ctrl_xport_params);
_xports[0].tx_data_xport = _fifo_iface->make_send_xport(E300_R0_TX_DATA_STREAM, data_xport_params);
_xports[0].tx_flow_xport = _fifo_iface->make_recv_xport(E300_R0_TX_DATA_STREAM, ctrl_xport_params);
_xports[0].rx_data_xport = _fifo_iface->make_recv_xport(E300_R0_RX_DATA_STREAM, data_xport_params);
_xports[0].rx_flow_xport = _fifo_iface->make_send_xport(E300_R0_RX_DATA_STREAM, ctrl_xport_params);
_xports[1].send_ctrl_xport = _fifo_iface->make_send_xport(E300_R1_CTRL_STREAM, ctrl_xport_params);
_xports[1].recv_ctrl_xport = _fifo_iface->make_recv_xport(E300_R1_CTRL_STREAM, ctrl_xport_params);
_xports[1].tx_data_xport = _fifo_iface->make_send_xport(E300_R1_TX_DATA_STREAM, data_xport_params);
_xports[1].tx_flow_xport = _fifo_iface->make_recv_xport(E300_R1_TX_DATA_STREAM, ctrl_xport_params);
_xports[1].rx_data_xport = _fifo_iface->make_recv_xport(E300_R1_RX_DATA_STREAM, data_xport_params);
_xports[1].rx_flow_xport = _fifo_iface->make_send_xport(E300_R1_RX_DATA_STREAM, ctrl_xport_params);
ad9361_params::sptr client_settings = boost::make_shared();
_codec_ctrl = ad9361_ctrl::make_spi(client_settings, spi::make(E300_SPIDEV_DEVICE), 1);
// This is horrible ... why do I have to sleep here?
boost::this_thread::sleep(boost::posix_time::milliseconds(100));
_sensor_manager = e300_sensor_manager::make_local(
gps::ublox::ubx::control::make("/dev/ttyPS1", 9600));
}
}}} // namespace
using namespace uhd::usrp::e300;
network_server::sptr network_server::make(const uhd::device_addr_t &device_addr)
{
return sptr(new network_server_impl(device_addr));
}
#else
using namespace uhd::usrp::e300;
network_server::sptr network_server::make(const uhd::device_addr_t &)
{
throw uhd::assertion_error("network_server::make() !E300_NATIVE");
}
#endif