//
// Copyright 2010 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
#include //mtu
#include
#include
#include
#include
#include
#include
using namespace uhd::transport;
/***********************************************************************
* Constants
**********************************************************************/
//enough buffering for half a second of samples at full rate on usrp2
static const size_t MIN_RECV_SOCK_BUFF_SIZE = size_t(sizeof(boost::uint32_t) * 25e6 * 0.5);
//Large buffers cause more underflow at high rates.
//Perhaps this is due to the kernel scheduling,
//but may change with host-based flow control.
static const size_t MIN_SEND_SOCK_BUFF_SIZE = size_t(10e3);
static const double RECV_TIMEOUT = 0.1; //100 ms
/***********************************************************************
* Zero Copy UDP implementation with ASIO:
* This is the portable zero copy implementation for systems
* where a faster, platform specific solution is not available.
* However, it is not a true zero copy implementation as each
* send and recv requires a copy operation to/from userspace.
**********************************************************************/
class udp_zero_copy_impl:
public phony_zero_copy_recv_if,
public phony_zero_copy_send_if,
public udp_zero_copy
{
public:
typedef boost::shared_ptr sptr;
udp_zero_copy_impl(
const std::string &addr,
const std::string &port
):
phony_zero_copy_recv_if(udp_simple::mtu),
phony_zero_copy_send_if(udp_simple::mtu)
{
//std::cout << boost::format("Creating udp transport for %s %s") % addr % port << std::endl;
// resolve the address
boost::asio::ip::udp::resolver resolver(_io_service);
boost::asio::ip::udp::resolver::query query(boost::asio::ip::udp::v4(), addr, port);
boost::asio::ip::udp::endpoint receiver_endpoint = *resolver.resolve(query);
// create, open, and connect the socket
_socket = new boost::asio::ip::udp::socket(_io_service);
_socket->open(boost::asio::ip::udp::v4());
_socket->connect(receiver_endpoint);
_sock_fd = _socket->native();
}
~udp_zero_copy_impl(void){
delete _socket;
}
//get size for internal socket buffer
template size_t get_buff_size(void) const{
Opt option;
_socket->get_option(option);
return option.value();
}
//set size for internal socket buffer
template size_t resize_buff(size_t num_bytes){
Opt option(num_bytes);
_socket->set_option(option);
return get_buff_size();
}
//The number of frames is approximately the buffer size divided by the max datagram size.
//In reality, this is a phony zero-copy interface and the number of frames is infinite.
//However, its sensible to advertise a frame count that is approximate to buffer size.
//This way, the transport caller will have an idea about how much buffering to create.
size_t get_num_recv_frames(void) const{
return this->get_buff_size()/udp_simple::mtu;
}
size_t get_num_send_frames(void) const{
return this->get_buff_size()/udp_simple::mtu;
}
private:
boost::asio::ip::udp::socket *_socket;
boost::asio::io_service _io_service;
int _sock_fd;
ssize_t recv(const boost::asio::mutable_buffer &buff){
//setup timeval for timeout
timeval tv;
tv.tv_sec = 0;
tv.tv_usec = int(RECV_TIMEOUT*1e6);
//setup rset for timeout
fd_set rset;
FD_ZERO(&rset);
FD_SET(_sock_fd, &rset);
//call select to perform timed wait
if (::select(_sock_fd+1, &rset, NULL, NULL, &tv) <= 0) return 0;
return ::recv(
_sock_fd,
boost::asio::buffer_cast(buff),
boost::asio::buffer_size(buff), 0
);
}
ssize_t send(const boost::asio::const_buffer &buff){
return ::send(
_sock_fd,
boost::asio::buffer_cast(buff),
boost::asio::buffer_size(buff), 0
);
}
};
/***********************************************************************
* UDP zero copy make function
**********************************************************************/
template static void resize_buff_helper(
udp_zero_copy_impl::sptr udp_trans,
size_t target_size,
const std::string &name
){
size_t min_sock_buff_size = 0;
if (name == "recv") min_sock_buff_size = MIN_RECV_SOCK_BUFF_SIZE;
if (name == "send") min_sock_buff_size = MIN_SEND_SOCK_BUFF_SIZE;
//resize the buffer if size was provided
if (target_size > 0){
size_t actual_size = udp_trans->resize_buff(target_size);
if (target_size != actual_size) std::cout << boost::format(
"Target %s sock buff size: %d bytes\n"
"Actual %s sock buff size: %d bytes"
) % name % target_size % name % actual_size << std::endl;
else std::cout << boost::format(
"Current %s sock buff size: %d bytes"
) % name % actual_size << std::endl;
if (actual_size < target_size) uhd::print_warning(str(boost::format(
"The %s buffer is smaller than the requested size.\n"
"The minimum recommended buffer size is %d bytes.\n"
"See the USRP2 application notes on buffer resizing.\n"
) % name % min_sock_buff_size));
}
//only enable on platforms that are happy with the large buffer resize
#if defined(UHD_PLATFORM_LINUX) || defined(UHD_PLATFORM_WIN32)
//otherwise, ensure that the buffer is at least the minimum size
else if (udp_trans->get_buff_size() < min_sock_buff_size){
resize_buff_helper(udp_trans, min_sock_buff_size, name);
}
#endif /*defined(UHD_PLATFORM_LINUX) || defined(UHD_PLATFORM_WIN32)*/
}
udp_zero_copy::sptr udp_zero_copy::make(
const std::string &addr,
const std::string &port,
size_t recv_buff_size,
size_t send_buff_size
){
udp_zero_copy_impl::sptr udp_trans(new udp_zero_copy_impl(addr, port));
//call the helper to resize send and recv buffers
resize_buff_helper(udp_trans, recv_buff_size, "recv");
resize_buff_helper (udp_trans, send_buff_size, "send");
return udp_trans;
}