//
// Copyright 2010-2011 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 "libusb1_base.hpp"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace uhd;
using namespace uhd::transport;
static const double CLEANUP_TIMEOUT = 0.2; //seconds
static const size_t DEFAULT_NUM_XFERS = 16; //num xfers
static const size_t DEFAULT_XFER_SIZE = 32*512; //bytes
/***********************************************************************
* Helper functions
***********************************************************************/
/*
* Print the values of a libusb_transfer struct
* http://libusb.sourceforge.net/api-1.0/structlibusb__transfer.html
*/
void pp_transfer(libusb_transfer *lut)
{
std::cout << "Libusb transfer" << std::endl;
std::cout << " flags: 0x" << std::hex << (unsigned int) lut->flags << std::endl;
std::cout << " endpoint: 0x" << std::hex << (unsigned int) lut->endpoint << std::endl;
std::cout << " type: 0x" << std::hex << (unsigned int) lut->type << std::endl;
std::cout << " timeout: " << std::dec << lut->timeout << std::endl;
std::cout << " status: 0x" << std::hex << lut->status << std::endl;
std::cout << " length: " << std::dec << lut->length << std::endl;
std::cout << " actual_length: " << std::dec << lut->actual_length << std::endl;
}
/***********************************************************************
* USB asynchronous zero_copy endpoint
* This endpoint implementation provides asynchronous I/O to libusb-1.0
* devices. Each endpoint is directional and two can be combined to
* create a bidirectional interface. It is a zero copy implementation
* with respect to libusb, however, each send and recv requires a copy
* operation from kernel to userspace; this is due to the usbfs
* interface provided by the kernel.
**********************************************************************/
class usb_endpoint {
public:
typedef boost::shared_ptr sptr;
usb_endpoint(
libusb::device_handle::sptr handle,
int endpoint,
bool input,
size_t transfer_size,
size_t num_transfers
);
~usb_endpoint(void);
// Exposed interface for submitting / retrieving transfer buffers
//! Submit a new transfer that was presumably just filled or emptied.
void submit(libusb_transfer *lut);
/*!
* Get an available transfer:
* For inputs, this is a just filled transfer.
* For outputs, this is a just emptied transfer.
* \param timeout the timeout to wait for a lut
* \return the transfer pointer or NULL if timeout
*/
libusb_transfer *get_lut_with_wait(double timeout);
//Callback use only
void callback_handle_transfer(libusb_transfer *lut);
private:
libusb::device_handle::sptr _handle;
int _endpoint;
bool _input;
//! hold a bounded buffer of completed transfers
typedef bounded_buffer lut_buff_type;
lut_buff_type::sptr _completed_list;
//! a list of all transfer structs we allocated
std::vector _all_luts;
//! memory allocated for the transfer buffers
buffer_pool::sptr _buffer_pool;
// Calls for processing asynchronous I/O
libusb_transfer *allocate_transfer(void *mem, size_t len);
void print_transfer_status(libusb_transfer *lut);
};
/*
* Callback function called when submitted transfers complete.
* The endpoint upon which the transfer is part of is recovered
* and the transfer moved from pending to completed state.
* Callbacks occur during the reaping calls where libusb_handle_events()
* is used. The callback only modifies the transfer state by moving
* it from the pending to completed status list.
* \param lut pointer to libusb_transfer
*/
static void callback(libusb_transfer *lut){
usb_endpoint *endpoint = (usb_endpoint *) lut->user_data;
endpoint->callback_handle_transfer(lut);
}
/*
* Accessor call to allow list access from callback space
* \param pointer to libusb_transfer
*/
void usb_endpoint::callback_handle_transfer(libusb_transfer *lut){
boost::this_thread::disable_interruption di; //disable because the wait can throw
_completed_list->push_with_wait(lut);
}
/*
* Constructor
* Allocate libusb transfers and mark as free. For IN endpoints,
* submit the transfers so that they're ready to return when
* data is available.
*/
usb_endpoint::usb_endpoint(
libusb::device_handle::sptr handle,
int endpoint,
bool input,
size_t transfer_size,
size_t num_transfers
):
_handle(handle),
_endpoint(endpoint),
_input(input)
{
_completed_list = lut_buff_type::make(num_transfers);
_buffer_pool = buffer_pool::make(num_transfers, transfer_size);
for (size_t i = 0; i < num_transfers; i++){
_all_luts.push_back(allocate_transfer(_buffer_pool->at(i), transfer_size));
//input luts are immediately submitted to be filled
//output luts go into the completed list as free buffers
if (_input) this->submit(_all_luts.back());
else _completed_list->push_with_wait(_all_luts.back());
}
}
/*
* Destructor
* Make sure all the memory is freed. Cancel any pending transfers.
* When all completed transfers are moved to the free list, release
* the transfers. Libusb will deallocate the data buffer held by
* each transfer.
*/
usb_endpoint::~usb_endpoint(void){
//cancel all transfers
BOOST_FOREACH(libusb_transfer *lut, _all_luts){
libusb_cancel_transfer(lut);
}
//collect canceled transfers (drain the queue)
while (this->get_lut_with_wait(CLEANUP_TIMEOUT) != NULL){};
//free all transfers
BOOST_FOREACH(libusb_transfer *lut, _all_luts){
libusb_free_transfer(lut);
}
}
/*
* Allocate a libusb transfer
* The allocated transfer - and buffer it contains - is repeatedly
* submitted, reaped, and reused and should not be freed until shutdown.
* \param mem a pointer to the buffer memory
* \param len size of the individual buffer
* \return pointer to an allocated libusb_transfer
*/
libusb_transfer *usb_endpoint::allocate_transfer(void *mem, size_t len){
libusb_transfer *lut = libusb_alloc_transfer(0);
UHD_ASSERT_THROW(lut != NULL);
unsigned int endpoint = ((_endpoint & 0x7f) | (_input ? 0x80 : 0));
unsigned char *buff = reinterpret_cast(mem);
libusb_transfer_cb_fn lut_callback = libusb_transfer_cb_fn(&callback);
libusb_fill_bulk_transfer(lut, // transfer
_handle->get(), // dev_handle
endpoint, // endpoint
buff, // buffer
len, // length
lut_callback, // callback
this, // user_data
0); // timeout
return lut;
}
/*
* Asynchonous transfer submission
* Submit a libusb transfer to libusb add pending status
* \param lut pointer to libusb_transfer
* \return true on success or false on error
*/
void usb_endpoint::submit(libusb_transfer *lut){
UHD_ASSERT_THROW(libusb_submit_transfer(lut) == 0);
}
/*
* Print status errors of a completed transfer
* \param lut pointer to an libusb_transfer
*/
void usb_endpoint::print_transfer_status(libusb_transfer *lut){
std::cout << "here " << lut->status << std::endl;
switch (lut->status) {
case LIBUSB_TRANSFER_COMPLETED:
if (lut->actual_length < lut->length) {
std::cerr << "USB: transfer completed with short write,"
<< " length = " << lut->length
<< " actual = " << lut->actual_length << std::endl;
}
if ((lut->actual_length < 0) || (lut->length < 0)) {
std::cerr << "USB: transfer completed with invalid response"
<< std::endl;
}
break;
case LIBUSB_TRANSFER_CANCELLED:
break;
case LIBUSB_TRANSFER_NO_DEVICE:
std::cerr << "USB: device was disconnected" << std::endl;
break;
case LIBUSB_TRANSFER_OVERFLOW:
std::cerr << "USB: device sent more data than requested" << std::endl;
break;
case LIBUSB_TRANSFER_TIMED_OUT:
std::cerr << "USB: transfer timed out" << std::endl;
break;
case LIBUSB_TRANSFER_STALL:
std::cerr << "USB: halt condition detected (stalled)" << std::endl;
break;
case LIBUSB_TRANSFER_ERROR:
std::cerr << "USB: transfer failed" << std::endl;
break;
default:
std::cerr << "USB: received unknown transfer status" << std::endl;
}
}
libusb_transfer *usb_endpoint::get_lut_with_wait(double timeout){
boost::this_thread::disable_interruption di; //disable because the wait can throw
libusb_transfer *lut;
if (_completed_list->pop_with_timed_wait(lut, timeout)) return lut;
return NULL;
}
/***********************************************************************
* USB zero_copy device class
**********************************************************************/
class libusb_zero_copy_impl : public usb_zero_copy, public boost::enable_shared_from_this {
public:
libusb_zero_copy_impl(
libusb::device_handle::sptr handle,
size_t recv_endpoint,
size_t send_endpoint,
const device_addr_t &hints
);
~libusb_zero_copy_impl(void){
_threads_running = false;
_thread_group.join_all();
}
managed_recv_buffer::sptr get_recv_buff(double);
managed_send_buffer::sptr get_send_buff(double);
size_t get_num_recv_frames(void) const { return _num_recv_frames; }
size_t get_num_send_frames(void) const { return _num_send_frames; }
size_t get_recv_frame_size(void) const { return _recv_frame_size; }
size_t get_send_frame_size(void) const { return _send_frame_size; }
private:
void release(libusb_transfer *lut){
_recv_ep->submit(lut);
}
void commit(libusb_transfer *lut, size_t num_bytes){
lut->length = num_bytes;
try{
_send_ep->submit(lut);
}
catch(const std::exception &e){
std::cerr << "Error in commit: " << e.what() << std::endl;
}
}
libusb::device_handle::sptr _handle;
const size_t _recv_frame_size, _num_recv_frames;
const size_t _send_frame_size, _num_send_frames;
usb_endpoint::sptr _recv_ep, _send_ep;
//event handler threads
boost::thread_group _thread_group;
bool _threads_running;
void run_event_loop(void){
set_thread_priority_safe();
libusb::session::sptr session = libusb::session::get_global_session();
_threads_running = true;
while(_threads_running){
timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 100000; //100ms
libusb_handle_events_timeout(session->get_context(), &tv);
}
}
};
/*
* Constructor
* Initializes libusb, opens devices, and sets up interfaces for I/O.
* Finally, creates endpoints for asynchronous I/O.
*/
libusb_zero_copy_impl::libusb_zero_copy_impl(
libusb::device_handle::sptr handle,
size_t recv_endpoint,
size_t send_endpoint,
const device_addr_t &hints
):
_handle(handle),
_recv_frame_size(size_t(hints.cast("recv_frame_size", DEFAULT_XFER_SIZE))),
_num_recv_frames(size_t(hints.cast("num_recv_frames", DEFAULT_NUM_XFERS))),
_send_frame_size(size_t(hints.cast("send_frame_size", DEFAULT_XFER_SIZE))),
_num_send_frames(size_t(hints.cast("num_send_frames", DEFAULT_NUM_XFERS)))
{
_handle->claim_interface(2 /*in interface*/);
_handle->claim_interface(1 /*out interface*/);
_recv_ep = usb_endpoint::sptr(new usb_endpoint(
_handle, // libusb device_handle
recv_endpoint, // USB endpoint number
true, // IN endpoint
this->get_recv_frame_size(), // buffer size per transfer
this->get_num_recv_frames() // number of libusb transfers
));
_send_ep = usb_endpoint::sptr(new usb_endpoint(
_handle, // libusb device_handle
send_endpoint, // USB endpoint number
false, // OUT endpoint
this->get_send_frame_size(), // buffer size per transfer
this->get_num_send_frames() // number of libusb transfers
));
//spawn the event handler threads
size_t concurrency = hints.cast("concurrency_hint", 1);
for (size_t i = 0; i < concurrency; i++) _thread_group.create_thread(
boost::bind(&libusb_zero_copy_impl::run_event_loop, this)
);
}
/*
* Construct a managed receive buffer from a completed libusb transfer
* (happy with buffer full of data) obtained from the receive endpoint.
* Return empty pointer if no transfer is available (timeout or error).
* \return pointer to a managed receive buffer
*/
managed_recv_buffer::sptr libusb_zero_copy_impl::get_recv_buff(double timeout){
libusb_transfer *lut = _recv_ep->get_lut_with_wait(timeout);
if (lut == NULL) {
return managed_recv_buffer::sptr();
}
else {
return managed_recv_buffer::make_safe(
boost::asio::const_buffer(lut->buffer, lut->actual_length),
boost::bind(&libusb_zero_copy_impl::release, shared_from_this(), lut)
);
}
}
/*
* Construct a managed send buffer from a free libusb transfer (with
* empty buffer). Return empty pointer of no transfer is available
* (timeout or error).
* \return pointer to a managed send buffer
*/
managed_send_buffer::sptr libusb_zero_copy_impl::get_send_buff(double timeout){
libusb_transfer *lut = _send_ep->get_lut_with_wait(timeout);
if (lut == NULL) {
return managed_send_buffer::sptr();
}
else {
return managed_send_buffer::make_safe(
boost::asio::mutable_buffer(lut->buffer, this->get_send_frame_size()),
boost::bind(&libusb_zero_copy_impl::commit, shared_from_this(), lut, _1)
);
}
}
/***********************************************************************
* USB zero_copy make functions
**********************************************************************/
usb_zero_copy::sptr usb_zero_copy::make(
usb_device_handle::sptr handle,
size_t recv_endpoint,
size_t send_endpoint,
const device_addr_t &hints
){
libusb::device_handle::sptr dev_handle(libusb::device_handle::get_cached_handle(
boost::static_pointer_cast(handle)->get_device()
));
return sptr(new libusb_zero_copy_impl(
dev_handle, recv_endpoint, send_endpoint, hints
));
}