diff options
| -rw-r--r-- | host/lib/convert/convert_unpack_sc12.cpp | 61 | ||||
| -rw-r--r-- | host/lib/transport/libusb1_base.cpp | 19 | ||||
| -rw-r--r-- | host/lib/transport/libusb1_zero_copy.cpp | 99 | ||||
| -rw-r--r-- | host/lib/usrp/b200/b200_iface.cpp | 20 | ||||
| -rw-r--r-- | host/lib/usrp/b200/b200_io_impl.cpp | 4 | 
5 files changed, 141 insertions, 62 deletions
diff --git a/host/lib/convert/convert_unpack_sc12.cpp b/host/lib/convert/convert_unpack_sc12.cpp index f578b6c95..e98ab73f1 100644 --- a/host/lib/convert/convert_unpack_sc12.cpp +++ b/host/lib/convert/convert_unpack_sc12.cpp @@ -32,6 +32,17 @@ struct item32_sc12_3x      item32_t line2;  }; +/* + * convert_sc12_item32_3_to_star_4 takes in 3 lines with 32 bit each + * and converts them 4 samples of type 'std::complex<type>'. + * The structure of the 3 lines is as follows: + *  _ _ _ _ _ _ _ _ + * |_ _ _1_ _ _|_ _| + * |_2_ _ _|_ _ _3_| + * |_ _|_ _ _4_ _ _| + * + * The numbers mark the position of one complex sample. + */  template <typename type, tohost32_type tohost>  void convert_sc12_item32_3_to_star_4  ( @@ -84,17 +95,48 @@ struct convert_sc12_item32_1_to_star_1 : public converter          _scalar = scalar/unpack_growth;      } +    /* +     * This converter takes in 24 bits complex samples, 12 bits I and 12 bits Q, and converts them to type 'std::complex<type>'. +     * 'type' is usually 'float'. +     * For the converter to work correctly the used managed_buffer which holds all samples of one packet has to be 32 bits aligned. +     * We assume 32 bits to be one line. This said the converter must be aware where it is supposed to start within 3 lines. +     * +     */      void operator()(const input_type &inputs, const output_type &outputs, const size_t nsamps)      { -        const item32_sc12_3x *input = reinterpret_cast<const item32_sc12_3x *>(size_t(inputs[0]) & ~0x3); +        /* +         * Looking at the line structure above we can identify 4 cases. +         * Each corresponds to the start of a different sample within a 3 line block. +         * head_samps derives the number of samples left within one block. +         * Then the number of bytes the converter has to rewind are calculated. +         */ +        const size_t head_samps = size_t(inputs[0]) & 0x3; +        size_t rewind = 0; +        switch(head_samps) +        { +            case 0: break; +            case 1: rewind = 9; break; +            case 2: rewind = 6; break; +            case 3: rewind = 3; break; +        } + +        /* +         * The pointer *input now points to the head of a 3 line block. +         */ +        const item32_sc12_3x *input = reinterpret_cast<const item32_sc12_3x *>(size_t(inputs[0]) - rewind);          std::complex<type> *output = reinterpret_cast<std::complex<type> *>(outputs[0]);          //helper variables          std::complex<type> dummy0, dummy1, dummy2;          size_t i = 0, o = 0; -        //handle the head case -        const size_t head_samps = size_t(inputs[0]) & 0x3; +        /* +         * handle the head case +         * head_samps holds the number of samples left in a block. +         * The 3 line converter is called for the whole block and already processed samples are dumped. +         * We don't run into the risk of a SIGSEGV because input will always point to valid memory within a managed_buffer. +         * Furthermore the bytes in a buffer remain unchanged after they have been copied into it. +         */          switch (head_samps)          {          case 0: break; //no head @@ -111,7 +153,18 @@ struct convert_sc12_item32_1_to_star_1 : public converter              i++; o += 4;          } -        //handle the tail case +        /* +         * handle the tail case +         * The converter can be called with any number of samples to be converted. +         * This can end up in only a part of a block to be converted in one call. +         * We never have to worry about SIGSEGVs here as long as we end in the middle of a managed_buffer. +         * If we are at the end of managed_buffer there are 2 precautions to prevent SIGSEGVs. +         * Firstly only a read operation is performed. +         * Secondly managed_buffers allocate a fixed size memory which is always larger than the actually used size. +         * e.g. The current sample maximum is 2000 samples in a packet over USB. +         * With sc12 samples a packet consists of 6000kb but managed_buffers allocate 16kb each. +         * Thus we don't run into problems here either. +         */          const size_t tail_samps = nsamps - o;          switch (tail_samps)          { diff --git a/host/lib/transport/libusb1_base.cpp b/host/lib/transport/libusb1_base.cpp index 0ef53db0a..4cf3ea17d 100644 --- a/host/lib/transport/libusb1_base.cpp +++ b/host/lib/transport/libusb1_base.cpp @@ -19,10 +19,12 @@  #include <uhd/exception.hpp>  #include <uhd/utils/msg.hpp>  #include <uhd/utils/log.hpp> +#include <uhd/utils/tasks.hpp>  #include <uhd/types/dict.hpp>  #include <boost/weak_ptr.hpp>  #include <boost/thread/mutex.hpp>  #include <boost/foreach.hpp> +#include <boost/bind.hpp>  #include <cstdlib>  #include <iostream> @@ -37,9 +39,11 @@ public:      libusb_session_impl(void){          UHD_ASSERT_THROW(libusb_init(&_context) == 0);          libusb_set_debug(_context, debug_level); +        task_handler = task::make(boost::bind(&libusb_session_impl::libusb_event_handler_task, this, _context));      }      ~libusb_session_impl(void){ +        task_handler.reset();          libusb_exit(_context);      } @@ -49,6 +53,21 @@ public:  private:      libusb_context *_context; +    task::sptr task_handler; + +    /* +     * Task to handle libusb events.  There should only be one thread per libusb_context handling events. +     * Using more than one thread can result in excessive CPU usage in kernel space (presumably from locking/waiting). +     * The libusb documentation says it is safe, which it is, but it neglects to state the cost in CPU usage. +     * Just don't do it! +     */ +    UHD_INLINE void libusb_event_handler_task(libusb_context *context) +    { +        timeval tv; +        tv.tv_sec = 0; +        tv.tv_usec = 100000; +        libusb_handle_events_timeout(context, &tv); +    }  };  libusb::session::sptr libusb::session::get_global_session(void){ diff --git a/host/lib/transport/libusb1_zero_copy.cpp b/host/lib/transport/libusb1_zero_copy.cpp index 197e257da..2d18e1623 100644 --- a/host/lib/transport/libusb1_zero_copy.cpp +++ b/host/lib/transport/libusb1_zero_copy.cpp @@ -73,6 +73,15 @@ struct lut_result_t      int completed;      libusb_transfer_status status;      int actual_length; +    boost::mutex mut; +    boost::condition_variable usb_transfer_complete; +}; + +// Created to be used as an argument to boost::condition_variable::timed_wait() function +struct lut_result_completed { +    const lut_result_t& _result; +    lut_result_completed(const lut_result_t& result):_result(result) {} +    bool operator()() const {return (_result.completed ? true : false);}  };  /*! @@ -84,48 +93,11 @@ struct lut_result_t  static void LIBUSB_CALL libusb_async_cb(libusb_transfer *lut)  {      lut_result_t *r = (lut_result_t *)lut->user_data; -    r->completed = 1; +    boost::lock_guard<boost::mutex> lock(r->mut);      r->status = lut->status;      r->actual_length = lut->actual_length; -} - -/*! - * Wait for a managed buffer to become complete. - * - * This routine processes async events until the transaction completes. - * We must call the libusb handle events in a loop because the handler - * may complete managed buffers other than the one we are waiting on. - * - * We cannot determine if handle events timed out or processed an event. - * Therefore, the timeout condition is handled by using boost system time. - * - * \param ctx the libusb context structure - * \param timeout the wait timeout in seconds - * \param completed a reference to the completed flag - * \return true for completion, false for timeout - */ -UHD_INLINE bool wait_for_completion(libusb_context *ctx, const double timeout, int &completed) -{ -    //already completed by a previous call? -    if (completed) return true; - -    //perform a non-blocking event handle -    timeval tv; -    tv.tv_sec = 0; -    tv.tv_usec = 0; -    libusb_handle_events_timeout_completed(ctx, &tv, &completed); -    if (completed) return true; - -    //finish the rest with a timeout loop -    const boost::system_time timeout_time = boost::get_system_time() + boost::posix_time::microseconds(long(timeout*1000000)); -    while (not completed and (boost::get_system_time() < timeout_time)){ -        timeval tv; -        tv.tv_sec = 0; -        tv.tv_usec = 10000; /*10ms*/ -        libusb_handle_events_timeout_completed(ctx, &tv, &completed); -    } - -    return completed; +    r->completed = 1; +    r->usb_transfer_complete.notify_one();  // wake up thread waiting in wait_for_completion() member function below  }  /*********************************************************************** @@ -154,7 +126,7 @@ public:      template <typename buffer_type>      UHD_INLINE typename buffer_type::sptr get_new(const double timeout)      { -        if (wait_for_completion(_ctx, timeout, result.completed)) +        if (wait_for_completion(timeout))          {              if (result.status != LIBUSB_TRANSFER_COMPLETED) throw uhd::runtime_error(str(boost::format(                  "usb %s transfer status: %d") % _name % int(result.status))); @@ -164,9 +136,31 @@ public:          return typename buffer_type::sptr();      } +    // This is public because it is accessed from the libusb_zero_copy_single constructor      lut_result_t result; +    /*! +     * Wait for a managed buffer to become complete. +     * +     * \param timeout the wait timeout in seconds.  A negative value will wait forever. +     * \return true for completion, false for timeout +     */ +    UHD_INLINE bool wait_for_completion(const double timeout) +    { +        boost::unique_lock<boost::mutex> lock(result.mut); +        if (!result.completed) { +            if (timeout < 0.0) { +                result.usb_transfer_complete.wait(lock); +            } else { +                const boost::system_time timeout_time = boost::get_system_time() + boost::posix_time::microseconds(long(timeout*1000000)); +                result.usb_transfer_complete.timed_wait(lock, timeout_time, lut_result_completed(result)); +            } +        } +        return result.completed; +    } +  private: +      boost::function<void(libusb_zero_copy_mb *)> _release_cb;      const bool _is_recv;      const std::string _name; @@ -252,8 +246,6 @@ public:      ~libusb_zero_copy_single(void)      { -        libusb_context *ctx = libusb::session::get_global_session()->get_context(); -          //cancel all transfers          BOOST_FOREACH(libusb_transfer *lut, _all_luts)          { @@ -261,8 +253,10 @@ public:          }          //process all transfers until timeout occurs -        int completed = 0; -        wait_for_completion(ctx, 0.01, completed); +        BOOST_FOREACH(libusb_zero_copy_mb *mb, _enqueued) +        { +            mb->wait_for_completion(0.01); +        }          //free all transfers          BOOST_FOREACH(libusb_transfer *lut, _all_luts) @@ -276,19 +270,18 @@ public:      {          typename buffer_type::sptr buff;          libusb_zero_copy_mb *front = NULL; +        boost::mutex::scoped_lock lock(_mutex); +        if (_enqueued.empty())          { -            boost::mutex::scoped_lock l(_mutex); -            if (_enqueued.empty()) -            { -                _cond.timed_wait(l, boost::posix_time::microseconds(long(timeout*1e6))); -            } -            if (_enqueued.empty()) return buff; -            front = _enqueued.front(); +            _cond.timed_wait(lock, boost::posix_time::microseconds(long(timeout*1e6)));          } +        if (_enqueued.empty()) return buff; +        front = _enqueued.front(); +        lock.unlock();          buff = front->get_new<buffer_type>(timeout); +        lock.lock(); -        boost::mutex::scoped_lock l(_mutex);          if (buff) _enqueued.pop_front();          this->submit_what_we_can();          return buff; diff --git a/host/lib/usrp/b200/b200_iface.cpp b/host/lib/usrp/b200/b200_iface.cpp index 1d05e159c..a74e058d0 100644 --- a/host/lib/usrp/b200/b200_iface.cpp +++ b/host/lib/usrp/b200/b200_iface.cpp @@ -69,6 +69,11 @@ const static boost::uint8_t FX3_STATE_RUNNING = 0x04;  const static boost::uint8_t FX3_STATE_UNCONFIGURED = 0x05;  const static boost::uint8_t FX3_STATE_ERROR = 0x06; +const static int VREQ_MAX_SIZE_USB2 = 64; +const static int VREQ_MAX_SIZE_USB3 = 512; +const static int VREQ_DEFAULT_SIZE  = VREQ_MAX_SIZE_USB2; +const static int VREQ_MAX_SIZE      = VREQ_MAX_SIZE_USB3; +  typedef boost::uint32_t hash_type; @@ -484,8 +489,17 @@ public:          hash_type hash = generate_hash(filename);          hash_type loaded_hash; usrp_get_fpga_hash(loaded_hash);          if (hash == loaded_hash) return 0; - -        unsigned char out_buff[64]; +         +        int transfer_size = VREQ_DEFAULT_SIZE; +        int current_usb_speed = get_usb_speed(); +        if (current_usb_speed == 3) +            transfer_size = VREQ_MAX_SIZE_USB3; +        else if (current_usb_speed != 2) +            throw uhd::io_error("load_fpga: get_usb_speed returned invalid USB speed (not 2 or 3)"); +         +        UHD_ASSERT_THROW(transfer_size <= VREQ_MAX_SIZE); + +        unsigned char out_buff[VREQ_MAX_SIZE];          memset(out_buff, 0x00, sizeof(out_buff));          fx3_control_write(B200_VREQ_FPGA_CONFIG, 0, 0, out_buff, 1, 1000); @@ -535,7 +549,7 @@ public:          size_t bytes_sent = 0;          while(!file.eof()) { -            file.read((char *) out_buff, sizeof(out_buff)); +            file.read((char *) out_buff, transfer_size);              const std::streamsize n = file.gcount();              if(n == 0) continue; diff --git a/host/lib/usrp/b200/b200_io_impl.cpp b/host/lib/usrp/b200/b200_io_impl.cpp index 1eddeb8bc..7b09c87df 100644 --- a/host/lib/usrp/b200/b200_io_impl.cpp +++ b/host/lib/usrp/b200/b200_io_impl.cpp @@ -249,14 +249,14 @@ rx_streamer::sptr b200_impl::get_rx_stream(const uhd::stream_args_t &args_)          //calculate packet size          static const size_t hdr_size = 0              + vrt::max_if_hdr_words32*sizeof(boost::uint32_t) -            + sizeof(vrt::if_packet_info_t().tlr) //forced to have trailer +            //+ sizeof(vrt::if_packet_info_t().tlr) //forced to have trailer              - sizeof(vrt::if_packet_info_t().cid) //no class id ever used              - sizeof(vrt::if_packet_info_t().tsi) //no int time ever used          ;          const size_t bpp = _data_transport->get_recv_frame_size() - hdr_size;          const size_t bpi = convert::get_bytes_per_item(args.otw_format);          size_t spp = unsigned(args.args.cast<double>("spp", bpp/bpi)); -        spp = std::min<size_t>(2000, spp); //magic maximum for framing at full rate +        spp = std::min<size_t>(2044, spp); //magic maximum for framing at full rate          //make the new streamer given the samples per packet          if (not my_streamer) my_streamer = boost::make_shared<sph::recv_packet_streamer>(spp);  | 
