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;
}

Attachment: 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_ */

Attachment: 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

Reply via email to