X-Git-Url: https://git.gag.com/?a=blobdiff_plain;f=usrp%2Fhost%2Flib%2Ffusb_linux.cc;fp=usrp%2Fhost%2Flib%2Ffusb_linux.cc;h=6c484569f36562b148d7d35396705e9e32eb53b7;hb=8a9ddbb0675f9bfcc6e03b457fba6c79474a3693;hp=0000000000000000000000000000000000000000;hpb=82d471b9b4a8b389b5da44b19c69c36420828382;p=debian%2Fgnuradio diff --git a/usrp/host/lib/fusb_linux.cc b/usrp/host/lib/fusb_linux.cc new file mode 100644 index 00000000..6c484569 --- /dev/null +++ b/usrp/host/lib/fusb_linux.cc @@ -0,0 +1,692 @@ +/* -*- c++ -*- */ +/* + * Copyright 2003 Free Software Foundation, Inc. + * + * This file is part of GNU Radio + * + * GNU Radio 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, or (at your option) + * any later version. + * + * GNU Radio 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 GNU Radio; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include // libusb header +#include +#ifdef HAVE_LINUX_COMPILER_H +#include +#endif +#include // interface to kernel portion of user mode usb driver +#include +#include +#include +#include +#include +#include +#include + +#define MINIMIZE_TX_BUFFERING 1 // must be defined to 0 or 1 + + +static const int MAX_BLOCK_SIZE = fusb_sysconfig::max_block_size(); // hard limit +static const int DEFAULT_BLOCK_SIZE = MAX_BLOCK_SIZE; +static const int DEFAULT_BUFFER_SIZE = 4 * (1L << 20); // 4 MB / endpoint + + +// Totally evil and fragile extraction of file descriptor from +// guts of libusb. They don't install usbi.h, which is what we'd need +// to do this nicely. +// +// FIXME if everything breaks someday in the future, look here... + +static int +fd_from_usb_dev_handle (usb_dev_handle *udh) +{ + return *((int *) udh); +} + +inline static void +urb_set_ephandle (usbdevfs_urb *urb, fusb_ephandle_linux *handle) +{ + urb->usercontext = handle; +} + +inline static fusb_ephandle_linux * +urb_get_ephandle (usbdevfs_urb *urb) +{ + return (fusb_ephandle_linux *) urb->usercontext; +} + +// ------------------------------------------------------------------------ +// USB request block (urb) allocation +// ------------------------------------------------------------------------ + +static usbdevfs_urb * +alloc_urb (fusb_ephandle_linux *self, int buffer_length, int endpoint, + bool input_p, unsigned char *write_buffer) +{ + usbdevfs_urb *urb = new usbdevfs_urb; + memset (urb, 0, sizeof (*urb)); + + urb->buffer_length = buffer_length; + + // We allocate dedicated memory only for input buffers. + // For output buffers we reuse the same buffer (the kernel + // copies the data at submital time) + + if (input_p) + urb->buffer = new unsigned char [buffer_length]; + else + urb->buffer = write_buffer; + + // init common values + + urb->type = USBDEVFS_URB_TYPE_BULK; + urb->endpoint = (endpoint & 0x7f) | (input_p ? 0x80 : 0); + + // USBDEVFS_URB_QUEUE_BULK goes away in linux 2.5, but is needed if + // we are using a 2.4 usb-uhci host controller driver. This is + // unlikely since we're almost always going to be plugged into a + // high speed host controller (ehci) +#if 0 && defined (USBDEVFS_URB_QUEUE_BULK) + urb->flags = USBDEVFS_URB_QUEUE_BULK; +#endif + + urb->signr = 0; + urb_set_ephandle (urb, self); + + return urb; +} + +static void +free_urb (usbdevfs_urb *urb) +{ + // if this was an input urb, free the buffer + if (urb->endpoint & 0x80) + delete [] ((unsigned char *) urb->buffer); + + delete urb; +} + +// ------------------------------------------------------------------------ +// device handle +// ------------------------------------------------------------------------ + +fusb_devhandle_linux::fusb_devhandle_linux (usb_dev_handle *udh) + : fusb_devhandle (udh) +{ + // that's all +} + +fusb_devhandle_linux::~fusb_devhandle_linux () +{ + // if there are any pending requests, cancel them and free the urbs. + + std::list::reverse_iterator it; + + for (it = d_pending_rqsts.rbegin (); it != d_pending_rqsts.rend (); it++){ + _cancel_urb (*it); + free_urb (*it); + } +} + +fusb_ephandle * +fusb_devhandle_linux::make_ephandle (int endpoint, bool input_p, + int block_size, int nblocks) +{ + return new fusb_ephandle_linux (this, endpoint, input_p, + block_size, nblocks); +} + + +// Attempt to cancel all transactions associated with eph. + +void +fusb_devhandle_linux::_cancel_pending_rqsts (fusb_ephandle_linux *eph) +{ + std::list::reverse_iterator it; + + for (it = d_pending_rqsts.rbegin (); it != d_pending_rqsts.rend (); it++){ + if (urb_get_ephandle (*it) == eph) + _cancel_urb (*it); + } +} + +void +fusb_devhandle_linux::pending_add (usbdevfs_urb *urb) +{ + d_pending_rqsts.push_back (urb); +} + +usbdevfs_urb * +fusb_devhandle_linux::pending_get () +{ + if (d_pending_rqsts.empty ()) + return 0; + + usbdevfs_urb *urb = d_pending_rqsts.front (); + d_pending_rqsts.pop_front (); + return urb; +} + +bool +fusb_devhandle_linux::pending_remove (usbdevfs_urb *urb) +{ + std::list::iterator result = find (d_pending_rqsts.begin (), + d_pending_rqsts.end (), + urb); + if (result == d_pending_rqsts.end ()){ + fprintf (stderr, "fusb::pending_remove: failed to find urb in pending_rqsts: %p\n", urb); + return false; + } + d_pending_rqsts.erase (result); + return true; +} + +/* + * Submit the urb to the kernel. + * iff successful, the urb will be placed on the devhandle's pending list. + */ +bool +fusb_devhandle_linux::_submit_urb (usbdevfs_urb *urb) +{ + int ret; + + ret = ioctl (fd_from_usb_dev_handle (d_udh), USBDEVFS_SUBMITURB, urb); + if (ret < 0){ + perror ("fusb::_submit_urb"); + return false; + } + + pending_add (urb); + return true; +} + +/* + * Attempt to cancel the in pending or in-progress urb transaction. + * Return true iff transaction was sucessfully cancelled. + * + * Failure to cancel should not be considered a problem. This frequently + * occurs if the transaction has already completed in the kernel but hasn't + * yet been reaped by the user mode code. + * + * urbs which were cancelled have their status field set to -ENOENT when + * they are reaped. + */ +bool +fusb_devhandle_linux::_cancel_urb (usbdevfs_urb *urb) +{ + int ret = ioctl (fd_from_usb_dev_handle (d_udh), USBDEVFS_DISCARDURB, urb); + if (ret < 0){ + // perror ("fusb::_cancel_urb"); + return false; + } + return true; +} + +/* + * Check with the kernel and see if any of our outstanding requests + * have completed. For each completed transaction, remove it from the + * devhandle's pending list and append it to the completed list for + * the corresponding endpoint. + * + * If any transactions are reaped return true. + * + * If ok_to_block_p is true, then this will block until at least one + * transaction completes or an unrecoverable error occurs. + */ +bool +fusb_devhandle_linux::_reap (bool ok_to_block_p) +{ + int ret; + int nreaped = 0; + usbdevfs_urb *urb = 0; + + int fd = fd_from_usb_dev_handle (d_udh); + + // try to reap as many as possible without blocking... + + while ((ret = ioctl (fd, USBDEVFS_REAPURBNDELAY, &urb)) == 0){ + if (urb->status != 0 && urb->status != -ENOENT){ + fprintf (stderr, "_reap: usb->status = %d, actual_length = %5d\n", + urb->status, urb->actual_length); + } + pending_remove (urb); + urb_get_ephandle (urb)->completed_list_add (urb); + nreaped++; + } + + if (nreaped > 0) // if we got any, return w/o blocking + return true; + + if (!ok_to_block_p) + return false; + + ret = ioctl (fd, USBDEVFS_REAPURB, &urb); + if (ret < 0){ + perror ("fusb::_reap"); + return false; + } + + pending_remove (urb); + urb_get_ephandle (urb)->completed_list_add (urb); + return true; +} + +void +fusb_devhandle_linux::_wait_for_completion () +{ + while (!d_pending_rqsts.empty ()) + if (!_reap(true)) + break; +} + // ------------------------------------------------------------------------ +// end point handle +// ------------------------------------------------------------------------ + +fusb_ephandle_linux::fusb_ephandle_linux (fusb_devhandle_linux *devhandle, + int endpoint, + bool input_p, + int block_size, int nblocks) + : fusb_ephandle (endpoint, input_p, block_size, nblocks), + d_devhandle (devhandle), + d_write_work_in_progress (0), d_write_buffer (0), + d_read_work_in_progress (0), d_read_buffer (0), d_read_buffer_end (0) +{ + + if (d_block_size < 0 || d_block_size > MAX_BLOCK_SIZE) + throw std::out_of_range ("fusb_ephandle_linux: block_size"); + + if (d_nblocks < 0) + throw std::out_of_range ("fusb_ephandle_linux: nblocks"); + + if (d_block_size == 0) + d_block_size = DEFAULT_BLOCK_SIZE; + + if (d_nblocks == 0) + d_nblocks = std::max (1, DEFAULT_BUFFER_SIZE / d_block_size); + + if (!d_input_p) + if (!MINIMIZE_TX_BUFFERING) + d_write_buffer = new unsigned char [d_block_size]; + + if (0) + fprintf(stderr, "fusb_ephandle_linux::ctor: d_block_size = %d d_nblocks = %d\n", + d_block_size, d_nblocks); + + // allocate urbs + + for (int i = 0; i < d_nblocks; i++) + d_free_list.push_back (alloc_urb (this, d_block_size, d_endpoint, + d_input_p, d_write_buffer)); +} + +fusb_ephandle_linux::~fusb_ephandle_linux () +{ + stop (); + + usbdevfs_urb *urb; + + while ((urb = free_list_get ()) != 0) + free_urb (urb); + + while ((urb = completed_list_get ()) != 0) + free_urb (urb); + + if (d_write_work_in_progress) + free_urb (d_write_work_in_progress); + + delete [] d_write_buffer; + + if (d_read_work_in_progress) + free_urb (d_read_work_in_progress); +} + +// ---------------------------------------------------------------- + +bool +fusb_ephandle_linux::start () +{ + if (d_started) + return true; // already running + + d_started = true; + + if (d_input_p){ // fire off all the reads + usbdevfs_urb *urb; + + int nerrors = 0; + while ((urb = free_list_get ()) != 0 && nerrors < d_nblocks){ + if (!submit_urb (urb)) + nerrors++; + } + } + + return true; +} + +// +// kill all i/o in progress. +// kill any completed but unprocessed transactions. +// +bool +fusb_ephandle_linux::stop () +{ + if (!d_started) + return true; + + if (d_write_work_in_progress){ + free_list_add (d_write_work_in_progress); + d_write_work_in_progress = 0; + } + + if (d_read_work_in_progress){ + free_list_add (d_read_work_in_progress); + d_read_work_in_progress = 0; + d_read_buffer = 0; + d_read_buffer_end = 0; + } + + d_devhandle->_cancel_pending_rqsts (this); + d_devhandle->_reap (false); + + while (1){ + usbdevfs_urb *urb; + while ((urb = completed_list_get ()) != 0) + free_list_add (urb); + + if (d_free_list.size () == (unsigned) d_nblocks) + break; + + if (!d_devhandle->_reap(true)) + break; + } + + d_started = false; + return true; +} + +// ---------------------------------------------------------------- +// routines for writing +// ---------------------------------------------------------------- + +#if (MINIMIZE_TX_BUFFERING) + +int +fusb_ephandle_linux::write(const void *buffer, int nbytes) +{ + if (!d_started) + return -1; + + if (d_input_p) + return -1; + + assert(nbytes % 512 == 0); + + unsigned char *src = (unsigned char *) buffer; + + int n = 0; + while (n < nbytes){ + + usbdevfs_urb *urb = get_write_work_in_progress(); + if (!urb) + return -1; + assert(urb->actual_length == 0); + int m = std::min(nbytes - n, MAX_BLOCK_SIZE); + urb->buffer = src; + urb->buffer_length = m; + + n += m; + src += m; + + if (!submit_urb(urb)) + return -1; + + d_write_work_in_progress = 0; + } + + return n; +} + +#else + +int +fusb_ephandle_linux::write (const void *buffer, int nbytes) +{ + if (!d_started) + return -1; + + if (d_input_p) + return -1; + + unsigned char *src = (unsigned char *) buffer; + + int n = 0; + while (n < nbytes){ + + usbdevfs_urb *urb = get_write_work_in_progress (); + if (!urb) + return -1; + unsigned char *dst = (unsigned char *) urb->buffer; + int m = std::min (nbytes - n, urb->buffer_length - urb->actual_length); + + memcpy (&dst[urb->actual_length], &src[n], m); + urb->actual_length += m; + n += m; + + if (urb->actual_length == urb->buffer_length){ + if (!submit_urb (urb)) + return -1; + d_write_work_in_progress = 0; + } + } + + return n; +} + +#endif + +usbdevfs_urb * +fusb_ephandle_linux::get_write_work_in_progress () +{ + // if we've already got some work in progress, return it + + if (d_write_work_in_progress) + return d_write_work_in_progress; + + while (1){ + + reap_complete_writes (); + + usbdevfs_urb *urb = free_list_get (); + + if (urb != 0){ + assert (urb->actual_length == 0); + d_write_work_in_progress = urb; + return urb; + } + + // The free list is empty. Tell the device handle to reap. + // Anything it reaps for us will end up on our completed list. + + if (!d_devhandle->_reap (true)) + return 0; + } +} + +void +fusb_ephandle_linux::reap_complete_writes () +{ + // take a look at the completed_list and xfer to free list after + // checking for errors. + + usbdevfs_urb *urb; + + while ((urb = completed_list_get ()) != 0){ + + // Check for any errors or short writes that were reported in the urb. + // The kernel sets status, actual_length and error_count. + // error_count is only used for ISO xfers. + // status is 0 if successful, else is an errno kind of thing + + if (urb->status != 0){ + fprintf (stderr, "fusb: (status %d) %s\n", urb->status, strerror (-urb->status)); + } + else if (urb->actual_length != urb->buffer_length){ + fprintf (stderr, "fusb: short write xfer: %d != %d\n", + urb->actual_length, urb->buffer_length); + } + + free_list_add (urb); + } +} + +void +fusb_ephandle_linux::wait_for_completion () +{ + d_devhandle->_wait_for_completion (); +} + +// ---------------------------------------------------------------- +// routines for reading +// ---------------------------------------------------------------- + +int +fusb_ephandle_linux::read (void *buffer, int nbytes) +{ + if (!d_started) + return -1; + + if (!d_input_p) + return -1; + + unsigned char *dst = (unsigned char *) buffer; + + int n = 0; + while (n < nbytes){ + + if (d_read_buffer >= d_read_buffer_end) + if (!reload_read_buffer ()) + return -1; + + int m = std::min (nbytes - n, (int) (d_read_buffer_end - d_read_buffer)); + + memcpy (&dst[n], d_read_buffer, m); + d_read_buffer += m; + n += m; + } + + return n; +} + +bool +fusb_ephandle_linux::reload_read_buffer () +{ + assert (d_read_buffer >= d_read_buffer_end); + + usbdevfs_urb *urb; + + if (d_read_work_in_progress){ + // We're done with this urb. Fire off a read to refill it. + urb = d_read_work_in_progress; + d_read_work_in_progress = 0; + d_read_buffer = 0; + d_read_buffer_end = 0; + urb->actual_length = 0; + if (!submit_urb (urb)) + return false; + } + + while (1){ + + while ((urb = completed_list_get ()) == 0) + if (!d_devhandle->_reap (true)) + return false; + + // check result of completed read + + if (urb->status != 0){ + // We've got a problem. Report it and fail. + fprintf (stderr, "fusb: (rd status %d) %s\n", urb->status, strerror (-urb->status)); + urb->actual_length = 0; + free_list_add (urb); + return false; + } + + // we've got a happy urb, full of data... + + d_read_work_in_progress = urb; + d_read_buffer = (unsigned char *) urb->buffer; + d_read_buffer_end = d_read_buffer + urb->actual_length; + + return true; + } +} + +// ---------------------------------------------------------------- + +void +fusb_ephandle_linux::free_list_add (usbdevfs_urb *urb) +{ + assert (urb_get_ephandle (urb) == this); + urb->actual_length = 0; + d_free_list.push_back (urb); +} + +usbdevfs_urb * +fusb_ephandle_linux::free_list_get () +{ + if (d_free_list.empty ()) + return 0; + + usbdevfs_urb *urb = d_free_list.front (); + d_free_list.pop_front (); + return urb; +} + +void +fusb_ephandle_linux::completed_list_add (usbdevfs_urb *urb) +{ + assert (urb_get_ephandle (urb) == this); + d_completed_list.push_back (urb); +} + +usbdevfs_urb * +fusb_ephandle_linux::completed_list_get () +{ + if (d_completed_list.empty ()) + return 0; + + usbdevfs_urb *urb = d_completed_list.front (); + d_completed_list.pop_front (); + return urb; +} + +/* + * Submit the urb. If successful the urb ends up on the devhandle's + * pending list, otherwise, it's back on our free list. + */ +bool +fusb_ephandle_linux::submit_urb (usbdevfs_urb *urb) +{ + if (!d_devhandle->_submit_urb (urb)){ // FIXME record the problem somewhere + fprintf (stderr, "_submit_urb failed\n"); + free_list_add (urb); + return false; + } + return true; +}