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