Hi, attached is a new version for fusb_linux.cc.
The current implementation uses three std::list lists for free, pending and completed urbs, so submitting a single urb causes three allocs and three frees (pushing and popping of the list). The new implementation uses a circular list for the urbs, where each urb is marked as free, pending or completed. As the total number of allocated urbs is constant, no allocs or frees are needed. Benchmark: usrp/host/apps/test_usrp_standard_tx -B 512 -N 64 -M 128 old code needs ~990e6 instructions, new code 690e6 instructions. The call to usrp_basic_tx::write goes down from 380e6 to 80e6 (so almost down to a fifth ...), the remaining instructions is the pattern fill for the sample buffer. Regards, Stefan -- Stefan Brüns / Bergstraße 21 / 52062 Aachen phone: +49 241 53809034 mobile: +49 151 50412019
/* -*- 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 <fusb_linux_2.h> #include <usb.h> // libusb header #include <stdexcept> #ifdef HAVE_LINUX_COMPILER_H #include <linux/compiler.h> #endif #include <linux/usbdevice_fs.h> // interface to kernel portion of user mode usb driver #include <sys/ioctl.h> #include <assert.h> #include <string.h> #include <algorithm> #include <errno.h> #include <string.h> #include <iostream> #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 enum urb_state_t { FREE, PENDING, COMPLETED }; struct _urb_private_data { fusb_ephandle_linux* handle; enum urb_state_t state; // int count readers; }; // 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_private_data*)(urb->usercontext))->handle = handle; } inline static fusb_ephandle_linux * urb_get_ephandle (usbdevfs_urb *urb) { return ((_urb_private_data*)(urb->usercontext))->handle; } // ------------------------------------------------------------------------ // 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->usercontext = new _urb_private_data; ((_urb_private_data*)urb->usercontext)->state=FREE; 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_private_data*)(urb->usercontext); 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. } 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); } /* * 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; } urb_get_ephandle(urb)->set_pending(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... if (!ok_to_block_p) { 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); } urb_get_ephandle(urb)->set_completed(urb); nreaped++; } if (nreaped > 0) // if we got any, return w/o blocking return true; return false; } ret = ioctl (fd, USBDEVFS_REAPURB, &urb); if (ret < 0){ perror ("fusb::_reap"); return false; } urb_get_ephandle(urb)->set_completed(urb); return true; } // ------------------------------------------------------------------------ // 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 (d_num_free = 0; d_num_free < d_nblocks; d_num_free++) d_urb_list.push_back (alloc_urb (this, d_block_size, d_endpoint, d_input_p, d_write_buffer)); d_next_free = d_next_completed = d_num_completed = 0; } 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); delete [] d_write_buffer; } // ---------------------------------------------------------------- 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){ set_free (d_write_work_in_progress); d_write_work_in_progress = 0; } if (d_read_work_in_progress){ set_free (d_read_work_in_progress); d_read_work_in_progress = 0; d_read_buffer = 0; d_read_buffer_end = 0; } cancel_pending(); d_devhandle->_reap (false); while (1){ if ( (d_num_free+d_num_completed) == 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); } set_free(urb); } } void fusb_ephandle_linux::wait_for_completion () { while ((d_num_free+d_num_completed)<d_nblocks) if (!d_devhandle->_reap(true)) break; } // ---------------------------------------------------------------- // 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; do { 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; } while (n < nbytes); 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; set_free (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::set_free (usbdevfs_urb *urb) { urb->actual_length = 0; ((_urb_private_data*)(urb->usercontext))->state = FREE; d_num_free++; } usbdevfs_urb * fusb_ephandle_linux::free_list_get () { if (d_num_free==0) return 0; usbdevfs_urb *urb; do { urb = d_urb_list[d_next_free++]; if (d_next_free == d_nblocks) d_next_free = 0; } while ( ((_urb_private_data*)(urb->usercontext))->state != FREE ); d_num_free--; return urb; } void fusb_ephandle_linux::set_completed (usbdevfs_urb *urb) { ((_urb_private_data*)(urb->usercontext))->state = COMPLETED; d_num_completed++; } usbdevfs_urb * fusb_ephandle_linux::completed_list_get () { if (d_num_completed==0) return 0; usbdevfs_urb *urb; do { urb = d_urb_list[d_next_completed++]; if (d_next_completed == d_nblocks) d_next_completed = 0; } while ( ((_urb_private_data*)(urb->usercontext))->state != COMPLETED ); d_num_completed--; return urb; } void fusb_ephandle_linux::set_pending (usbdevfs_urb *urb) { ((_urb_private_data*)(urb->usercontext))->state = PENDING; } bool fusb_ephandle_linux::cancel_pending () { int i = 0; usbdevfs_urb *urb; while ( i < d_nblocks ) { urb = d_urb_list[i++]; if ( urb && ((_urb_private_data*)(urb->usercontext))->state == PENDING ) { d_devhandle->_cancel_urb(urb); } } while ((d_num_free+d_num_completed)<d_nblocks) d_devhandle->_reap(true); return true; } /* * 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"); set_free (urb); return false; } return true; }
signature.asc
Description: This is a digitally signed message part.
/* -*- 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. */ // Fast USB interface #ifndef _FUSB_LINUX_2_H_ #define _FUSB_LINUX_2_H_ #include <fusb.h> #include <vector> #include <time.h> struct usbdevfs_urb; class fusb_ephandle_linux; /*! * \brief linux specific implementation of fusb_devhandle using usbdevice_fs */ class fusb_devhandle_linux : public fusb_devhandle { public: // CREATORS fusb_devhandle_linux (usb_dev_handle *udh); virtual ~fusb_devhandle_linux (); // MANIPULATORS virtual fusb_ephandle *make_ephandle (int endpoint, bool input_p, int block_size = 0, int nblocks = 0); // internal use only bool _submit_urb (usbdevfs_urb *urb); bool _cancel_urb (usbdevfs_urb *urb); bool _reap (bool ok_to_block_p); }; /*! * \brief linux specific implementation of fusb_ephandle using usbdevice_fs */ class fusb_ephandle_linux : public fusb_ephandle { private: fusb_devhandle_linux *d_devhandle; std::vector<usbdevfs_urb*> d_urb_list; int d_next_free; int d_next_completed; int d_num_free; int d_num_completed; usbdevfs_urb *d_write_work_in_progress; unsigned char *d_write_buffer; usbdevfs_urb *d_read_work_in_progress; unsigned char *d_read_buffer; unsigned char *d_read_buffer_end; usbdevfs_urb *get_write_work_in_progress (); usbdevfs_urb *free_list_get (); usbdevfs_urb *completed_list_get (); void reap_complete_writes (); bool reload_read_buffer (); bool submit_urb (usbdevfs_urb *urb); bool cancel_pending (); void set_completed (usbdevfs_urb* urb); void set_free (usbdevfs_urb* urb); void set_pending (usbdevfs_urb* urb); friend class fusb_devhandle_linux; public: fusb_ephandle_linux (fusb_devhandle_linux *dh, int endpoint, bool input_p, int block_size = 0, int nblocks = 0); virtual ~fusb_ephandle_linux (); virtual bool start (); //!< begin streaming i/o virtual bool stop (); //!< stop streaming i/o /*! * \returns \p nbytes if write was successfully enqueued, else -1. * Will block if no free buffers available. */ virtual int write (const void *buffer, int nbytes); /*! * \returns number of bytes read or -1 if error. * number of bytes read will be <= nbytes. * Will block if no input available. */ virtual int read (void *buffer, int nbytes); /* * block until all outstanding writes have completed */ virtual void wait_for_completion (); }; #endif /* _FUSB_LINUX_2_H_ */
signature.asc
Description: This is a digitally signed message part.
_______________________________________________ Discuss-gnuradio mailing list Discuss-gnuradio@gnu.org http://lists.gnu.org/mailman/listinfo/discuss-gnuradio