//
// 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 "libusb1_base.hpp"
#include
#include
#include
#include
#include
#include
using namespace uhd::transport;
const int libusb_debug_level = 0;
const int libusb_timeout = 0;
/***********************************************************************
* 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 {
private:
libusb_device_handle *_dev_handle;
libusb_context *_ctx;
int _endpoint;
bool _input;
size_t _transfer_size;
size_t _num_transfers;
// Transfer state lists (transfers are free, pending, or completed)
std::list _free_list;
std::list _pending_list;
std::list _completed_list;
// Calls for processing asynchronous I/O
libusb_transfer *allocate_transfer(int buff_len);
bool cancel(libusb_transfer *lut);
bool cancel_all();
bool reap_pending_list();
bool reap_pending_list_timeout();
bool reap_completed_list();
// Transfer state manipulators
void free_list_add(libusb_transfer *lut);
void pending_list_add(libusb_transfer *lut);
void completed_list_add(libusb_transfer *lut);
libusb_transfer *free_list_get();
libusb_transfer *completed_list_get();
bool pending_list_remove(libusb_transfer *lut);
// Debug use
void print_transfer_status(libusb_transfer *lut);
public:
usb_endpoint(libusb_device_handle *dev_handle,
libusb_context *ctx, int endpoint, bool input,
size_t transfer_size, size_t num_transfers);
~usb_endpoint();
// Exposed interface for submitting / retrieving transfer buffers
bool submit(libusb_transfer *lut);
libusb_transfer *get_completed_transfer();
libusb_transfer *get_free_transfer();
//Callback use only
void callback_handle_transfer(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)
{
if (!pending_list_remove(lut)) {
std::cerr << "USB: pending remove failed" << std::endl;
return;
}
completed_list_add(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 *dev_handle,
libusb_context *ctx, int endpoint, bool input,
size_t transfer_size, size_t num_transfers)
: _dev_handle(dev_handle),
_ctx(ctx), _endpoint(endpoint), _input(input),
_transfer_size(transfer_size), _num_transfers(num_transfers)
{
unsigned int i;
for (i = 0; i < _num_transfers; i++) {
free_list_add(allocate_transfer(_transfer_size));
if (_input)
submit(free_list_get());
}
}
/*
* 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()
{
cancel_all();
while (!_pending_list.empty()) {
if (!reap_pending_list())
std::cerr << "error: destructor failed to reap" << std::endl;
}
while (!_completed_list.empty()) {
if (!reap_completed_list())
std::cerr << "error: destructor failed to reap" << std::endl;
}
while (!_free_list.empty()) {
libusb_free_transfer(free_list_get());
}
}
/*
* 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 buff_len size of the individual buffer held by each transfer
* \return pointer to an allocated libusb_transfer
*/
libusb_transfer *usb_endpoint::allocate_transfer(int buff_len)
{
libusb_transfer *lut = libusb_alloc_transfer(0);
unsigned char *buff = new unsigned char[buff_len];
unsigned int endpoint = ((_endpoint & 0x7f) | (_input ? 0x80 : 0));
libusb_fill_bulk_transfer(lut, // transfer
_dev_handle, // dev_handle
endpoint, // endpoint
buff, // buffer
buff_len, // length
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
*/
bool usb_endpoint::submit(libusb_transfer *lut)
{
int retval;
if ((retval = libusb_submit_transfer(lut)) < 0) {
std::cerr << "error: libusb_submit_transfer: " << retval << std::endl;
return false;
}
pending_list_add(lut);
return true;
}
/*
* Cancel a pending transfer
* Search the pending list for the transfer and cancel if found.
* \param lut pointer to libusb_transfer to cancel
* \return true on success or false if transfer is not found
*
* Note: success only indicates submission of cancelation request.
* Sucessful cancelation is not known until the callback occurs.
*/
bool usb_endpoint::cancel(libusb_transfer *lut)
{
std::list::iterator iter;
for (iter = _pending_list.begin(); iter != _pending_list.end(); iter++) {
if (*iter == lut) {
libusb_cancel_transfer(lut);
return true;
}
}
return false;
}
/*
* Cancel all pending transfers
* \return bool true if cancelation request is submitted
*
* Note: success only indicates submission of cancelation request.
* Sucessful cancelation is not known until the callback occurs.
*/
bool usb_endpoint::cancel_all()
{
std::list::iterator iter;
for (iter = _pending_list.begin(); iter != _pending_list.end(); iter++) {
if (libusb_cancel_transfer(*iter) < 0) {
std::cerr << "error: libusb_cancal_transfer() failed" << std::endl;
return false;
}
}
return true;
}
/*
* Reap completed transfers
* return true if at least one transfer was reaped, false otherwise.
* Check completed transfers for errors and mark as free. This is a
* blocking call.
* \return bool true if a libusb transfer is reaped, false otherwise
*/
bool usb_endpoint::reap_completed_list()
{
libusb_transfer *lut;
if (_completed_list.empty()) {
if (!reap_pending_list_timeout())
return false;
}
while (!_completed_list.empty()) {
lut = completed_list_get();
print_transfer_status(lut);
free_list_add(lut);
}
return true;
}
/*
* Print status errors of a completed transfer
* \param lut pointer to an libusb_transfer
*/
void usb_endpoint::print_transfer_status(libusb_transfer *lut)
{
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;
}
}
/*
* Reap pending transfers without timeout
* This is a blocking call. Reaping submitted transfers is
* handled by libusb and the assigned callback function.
* Block until at least one transfer is reaped.
* \return true true if a transfer was reaped or false otherwise
*/
bool usb_endpoint::reap_pending_list()
{
int retval;
if ((retval = libusb_handle_events(_ctx)) < 0) {
std::cerr << "error: libusb_handle_events: " << retval << std::endl;
return false;
}
return true;
}
/*
* Reap pending transfers with timeout
* This call blocks until a transfer is reaped or timeout.
* Reaping submitted transfers is handled by libusb and the
* assigned callback function. Block until at least one
* transfer is reaped or timeout occurs.
* \return true if a transfer was reaped or false otherwise
*/
bool usb_endpoint::reap_pending_list_timeout()
{
int retval;
timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 100000; //100ms
size_t pending_list_size = _pending_list.size();
if ((retval = libusb_handle_events_timeout(_ctx, &tv)) < 0) {
std::cerr << "error: libusb_handle_events: " << retval << std::endl;
return false;
}
if (_pending_list.size() < pending_list_size) {
return true;
}
else {
return false;
}
}
/*
* Get a free transfer
* The transfer has an empty data bufer for OUT requests
* \return pointer to a libusb_transfer
*/
libusb_transfer *usb_endpoint::get_free_transfer()
{
if (_free_list.empty()) {
if (!reap_completed_list())
return NULL;
}
return free_list_get();
}
/*
* Get a completed transfer
* The transfer has a full data buffer for IN requests
* \return pointer to libusb_transfer
*/
libusb_transfer *usb_endpoint::get_completed_transfer()
{
if (_completed_list.empty()) {
if (!reap_pending_list_timeout())
return NULL;
}
return completed_list_get();
}
/*
* List operations
*/
void usb_endpoint::free_list_add(libusb_transfer *lut)
{
_free_list.push_back(lut);
}
void usb_endpoint::pending_list_add(libusb_transfer *lut)
{
_pending_list.push_back(lut);
}
void usb_endpoint::completed_list_add(libusb_transfer *lut)
{
_completed_list.push_back(lut);
}
/*
* Free and completed lists don't have ordered content
* Pop transfers from the front as needed
*/
libusb_transfer *usb_endpoint::free_list_get()
{
libusb_transfer *lut;
if (_free_list.size() == 0) {
return NULL;
}
else {
lut = _free_list.front();
_free_list.pop_front();
return lut;
}
}
/*
* Free and completed lists don't have ordered content
* Pop transfers from the front as needed
*/
libusb_transfer *usb_endpoint::completed_list_get()
{
libusb_transfer *lut;
if (_completed_list.empty()) {
return NULL;
}
else {
lut = _completed_list.front();
_completed_list.pop_front();
return lut;
}
}
/*
* Search and remove transfer from pending list
* Assuming that the callbacks occur in order, the front element
* should yield the correct transfer. If not, then something else
* is going on. If no transfers match, then something went wrong.
*/
bool usb_endpoint::pending_list_remove(libusb_transfer *lut)
{
std::list::iterator iter;
for (iter = _pending_list.begin(); iter != _pending_list.end(); iter++) {
if (*iter == lut) {
_pending_list.erase(iter);
return true;
}
}
return false;
}
/***********************************************************************
* Managed buffers
**********************************************************************/
/*
* Libusb managed receive buffer
* Construct a recv buffer from a libusb transfer. The memory held by
* the libusb transfer is exposed through the managed buffer interface.
* Upon destruction, the transfer and buffer are resubmitted to the
* endpoint for further use.
*/
class libusb_managed_recv_buffer_impl : public managed_recv_buffer {
public:
libusb_managed_recv_buffer_impl(libusb_transfer *lut,
usb_endpoint *endpoint)
: _buff(lut->buffer, lut->length)
{
_lut = lut;
_endpoint = endpoint;
}
~libusb_managed_recv_buffer_impl()
{
if (!_endpoint->submit(_lut))
std::cerr << "USB: failed to submit IN transfer" << std::endl;
}
private:
const boost::asio::const_buffer &get() const
{
return _buff;
}
libusb_transfer *_lut;
usb_endpoint *_endpoint;
const boost::asio::const_buffer _buff;
};
/*
* Libusb managed send buffer
* Construct a send buffer from a libusb transfer. The memory held by
* the libusb transfer is exposed through the managed buffer interface.
* Committing the buffer will set the data length and submit the buffer
* to the endpoint. Submitting a buffer multiple times or destroying
* the buffer before committing is an error. For the latter, the transfer
* is returned to the endpoint with no data for reuse.
*/
class libusb_managed_send_buffer_impl : public managed_send_buffer {
public:
libusb_managed_send_buffer_impl(libusb_transfer *lut,
usb_endpoint *endpoint,
size_t buff_size)
: _buff(lut->buffer, buff_size), _committed(false)
{
_lut = lut;
_endpoint = endpoint;
}
~libusb_managed_send_buffer_impl()
{
if (!_committed) {
_lut->length = 0;
_lut->actual_length = 0;
_endpoint->submit(_lut);
}
}
ssize_t commit(size_t num_bytes)
{
if (_committed) {
std::cerr << "UHD: send buffer already committed" << std::endl;
return 0;
}
UHD_ASSERT_THROW(num_bytes <= boost::asio::buffer_size(_buff));
_lut->length = num_bytes;
_lut->actual_length = 0;
if (_endpoint->submit(_lut)) {
_committed = true;
return num_bytes;
}
else {
return 0;
}
}
private:
const boost::asio::mutable_buffer &get() const
{
return _buff;
}
libusb_transfer *_lut;
usb_endpoint *_endpoint;
const boost::asio::mutable_buffer _buff;
bool _committed;
};
/***********************************************************************
* USB zero_copy device class
**********************************************************************/
class libusb_zero_copy_impl : public usb_zero_copy
{
private:
usb_endpoint *_rx_ep;
usb_endpoint *_tx_ep;
// Maintain libusb values
libusb_context *_rx_ctx;
libusb_context *_tx_ctx;
libusb_device_handle *_rx_dev_handle;
libusb_device_handle *_tx_dev_handle;
size_t _recv_buff_size;
size_t _send_buff_size;
size_t _num_frames;
public:
typedef boost::shared_ptr sptr;
libusb_zero_copy_impl(usb_device_handle::sptr handle,
unsigned int rx_endpoint,
unsigned int tx_endpoint,
size_t recv_buff_size,
size_t send_buff_size);
~libusb_zero_copy_impl();
managed_recv_buffer::sptr get_recv_buff(void);
managed_send_buffer::sptr get_send_buff(void);
size_t get_num_recv_frames(void) const { return _num_frames; }
size_t get_num_send_frames(void) const { return _num_frames; }
};
/*
* 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(usb_device_handle::sptr handle,
unsigned int rx_endpoint,
unsigned int tx_endpoint,
size_t buff_size,
size_t block_size)
: _rx_ctx(NULL), _tx_ctx(NULL), _rx_dev_handle(NULL), _tx_dev_handle(NULL),
_recv_buff_size(block_size), _send_buff_size(block_size),
_num_frames(buff_size / block_size)
{
// Initialize libusb with separate contexts to allow
// thread safe operation of transmit and receive
libusb::init(&_rx_ctx, libusb_debug_level);
libusb::init(&_tx_ctx, libusb_debug_level);
UHD_ASSERT_THROW((_rx_ctx != NULL) && (_tx_ctx != NULL));
// Find and open the libusb_device corresponding to the
// given handle and return the libusb_device_handle
// that can be used for I/O purposes.
_rx_dev_handle = libusb::open_device(_rx_ctx, handle);
_tx_dev_handle = libusb::open_device(_tx_ctx, handle);
// Open USB interfaces for tx/rx using magic values.
// IN interface: 2
// OUT interface: 1
// Control interface: 0
libusb::open_interface(_rx_dev_handle, 2);
libusb::open_interface(_tx_dev_handle, 1);
_rx_ep = new usb_endpoint(_rx_dev_handle, // libusb device_handle
_rx_ctx, // libusb context
rx_endpoint, // USB endpoint number
true, // IN endpoint
_recv_buff_size, // buffer size per transfer
_num_frames); // number of libusb transfers
_tx_ep = new usb_endpoint(_tx_dev_handle, // libusb device_handle
_tx_ctx, // libusb context
tx_endpoint, // USB endpoint number
false, // OUT endpoint
_send_buff_size, // buffer size per transfer
_num_frames); // number of libusb transfers
}
libusb_zero_copy_impl::~libusb_zero_copy_impl()
{
delete _rx_ep;
delete _tx_ep;
libusb_close(_rx_dev_handle);
libusb_close(_tx_dev_handle);
libusb_exit(_rx_ctx);
libusb_exit(_tx_ctx);
}
/*
* 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()
{
libusb_transfer *lut = _rx_ep->get_completed_transfer();
if (lut == NULL) {
return managed_recv_buffer::sptr();
}
else {
return managed_recv_buffer::sptr(
new libusb_managed_recv_buffer_impl(lut,
_rx_ep));
}
}
/*
* 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()
{
libusb_transfer *lut = _tx_ep->get_free_transfer();
if (lut == NULL) {
return managed_send_buffer::sptr();
}
else {
return managed_send_buffer::sptr(
new libusb_managed_send_buffer_impl(lut,
_tx_ep,
_send_buff_size));
}
}
/***********************************************************************
* USB zero_copy make functions
**********************************************************************/
usb_zero_copy::sptr usb_zero_copy::make(usb_device_handle::sptr handle,
unsigned int rx_endpoint,
unsigned int tx_endpoint,
size_t buff_size,
size_t block_size)
{
return sptr(new libusb_zero_copy_impl(handle,
rx_endpoint,
tx_endpoint,
buff_size,
block_size));
}