switch source package format to 3.0 quilt
[debian/gnuradio] / usrp2 / host / lib / usrp2_impl.cc
index 68cf676c6bc0c311e6ae0b7799c5e7761e63fcdc..333e2d1e78b0aff85e1256f219093a45a09d2e83 100644 (file)
@@ -1,6 +1,6 @@
 /* -*- c++ -*- */
 /*
- * Copyright 2008 Free Software Foundation, Inc.
+ * Copyright 2008,2009,2010 Free Software Foundation, Inc.
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
 #include <usrp2/tune_result.h>
 #include <usrp2/copiers.h>
 #include <gruel/inet.h>
+#include <gruel/realtime.h>
+#include <boost/bind.hpp>
 #include <usrp2_types.h>
 #include "usrp2_impl.h"
-#include "usrp2_thread.h"
 #include "eth_buffer.h"
 #include "pktfilter.h"
 #include "control.h"
@@ -46,7 +47,6 @@
 #endif
 
 static const int DEFAULT_RX_SCALE = 1024;
-static const int DEFAULT_TX_SCALE = 3000;
 
 namespace usrp2 {
 
@@ -68,12 +68,19 @@ namespace usrp2 {
     case OP_CONFIG_TX_REPLY_V2: return "OP_CONFIG_TX_REPLY_V2";
     case OP_START_RX_STREAMING: return "OP_START_RX_STREAMING";
     case OP_STOP_RX: return "OP_STOP_RX";
-#if 0
-    case OP_WRITE_REG: return "OP_WRITE_REG";
-    case OP_WRITE_REG_MASKED: return "OP_WRITE_REG_MASKED";
-    case OP_READ_REG: return "OP_READ_REG";
-    case OP_READ_REG_REPLY: return "OP_READ_REG_REPLY";
-#endif
+    case OP_CONFIG_MIMO: return "OP_CONFIG_MIMO";
+    case OP_DBOARD_INFO: return "OP_DBOARD_INFO";
+    case OP_DBOARD_INFO_REPLY: return "OP_DBOARD_INFO_REPLY";
+    case OP_SYNC_TO_PPS: return "OP_SYNC_TO_PPS";
+    case OP_PEEK: return "OP_PEEK";
+    case OP_PEEK_REPLY: return "OP_PEEK_REPLY";
+    case OP_SET_TX_LO_OFFSET: return "OP_SET_TX_LO_OFFSET";
+    case OP_SET_TX_LO_OFFSET_REPLY: return "OP_SET_TX_LO_OFFSET_REPLY";
+    case OP_SET_RX_LO_OFFSET: return "OP_SET_RX_LO_OFFSET";
+    case OP_SET_RX_LO_OFFSET_REPLY: return "OP_SET_RX_LO_OFFSET_REPLY";
+    case OP_SYNC_EVERY_PPS: return "OP_SYNC_EVERY_PPS";
+    case OP_SYNC_EVERY_PPS_REPLY: return "OP_SYNC_EVERY_PPS_REPLY";
+
     default:
       char buf[64];
       snprintf(buf, sizeof(buf), "<unknown opcode: %d>", opcode);
@@ -100,14 +107,18 @@ namespace usrp2 {
     //assert((((uintptr_t) p) % 4) == 0);              // must be 4-byte aligned
 
     u2_fixed_hdr_t *fh = static_cast<u2_fixed_hdr_t *>(p);
-    
+
     // FIXME unaligned loads!
     md->word0 = u2p_word0(fh);
     md->timestamp = u2p_timestamp(fh);
 
+    // FIXME when we've got more info
     // md->start_of_burst = (md->word0 & XXX) != 0;
     // md->end_of_burst =   (md->word0 & XXX) != 0;
     // md->rx_overrun =     (md->word0 & XXX) != 0;
+    md->start_of_burst = 0;
+    md->end_of_burst =   0;
+    md->rx_overrun =     0;
 
     *items = (uint32_t *)(&fh[1]);
     size_t nbytes = payload_len_in_bytes - sizeof(u2_fixed_hdr_t);
@@ -118,56 +129,100 @@ namespace usrp2 {
   }
 
 
-  usrp2::impl::impl(const std::string &ifc, props *p)
-    : d_eth_buf(new eth_buffer()), d_pf(0), d_bg_thread(0), d_bg_running(false),
-      d_rx_decim(0), d_rx_seqno(-1), d_tx_seqno(0), d_next_rid(0),
-      d_num_rx_frames(0), d_num_rx_missing(0), d_num_rx_overruns(0), d_num_rx_bytes(0), 
-      d_num_enqueued(0), d_enqueued_mutex(), d_bg_pending_cond(&d_enqueued_mutex),
-      d_channel_rings(NCHANS)
+  usrp2::impl::impl(const std::string &ifc, props *p, size_t rx_bufsize)
+    : d_eth_buf(new eth_buffer(rx_bufsize)), d_interface_name(ifc), d_pf(0),
+      d_bg_running(false), d_rx_seqno(-1), d_tx_seqno(0), d_next_rid(0),
+      d_num_rx_frames(0), d_num_rx_missing(0), d_num_rx_overruns(0), d_num_rx_bytes(0),
+      d_num_enqueued(0), d_enqueued_mutex(), d_bg_pending_cond(),
+      d_channel_rings(NCHANS), d_tx_interp(0), d_rx_decim(0), d_dont_enqueue(true)
   {
     if (!d_eth_buf->open(ifc, htons(U2_ETHERTYPE)))
       throw std::runtime_error("Unable to register USRP2 protocol");
-    
-    d_pf = pktfilter::make_ethertype_inbound(U2_ETHERTYPE, d_eth_buf->mac());
+
+    d_addr = p->addr;
+
+    // Create a packet filter for U2_ETHERTYPE packets sourced from target USRP2
+    u2_mac_addr_t usrp_mac;
+    parse_mac_addr(d_addr, &usrp_mac);
+    d_pf = pktfilter::make_ethertype_inbound_target(U2_ETHERTYPE, (const unsigned char*)&(usrp_mac.addr));
     if (!d_pf || !d_eth_buf->attach_pktfilter(d_pf))
       throw std::runtime_error("Unable to attach packet filter.");
-    
-    d_addr = p->addr;
-    
+
     if (USRP2_IMPL_DEBUG)
       std::cerr << "usrp2 constructor: using USRP2 at " << d_addr << std::endl;
 
     memset(d_pending_replies, 0, sizeof(d_pending_replies));
 
-    d_bg_thread = new usrp2_thread(this);
-    d_bg_thread->start();
+    // Kick off receive thread
+    start_bg();
+
+    // In case the USRP2 was left streaming RX
+    // FIXME: only one channel right now
+    stop_rx_streaming(0);
+
+    if (!dboard_info())                // we're hosed
+      throw std::runtime_error("Unable to retrieve daughterboard info");
+
+    if (0){
+      int dbid;
+
+      tx_daughterboard_id(&dbid);
+      fprintf(stderr, "Tx dboard 0x%x\n", dbid);
+      fprintf(stderr, "  freq_min = %g\n", tx_freq_min());
+      fprintf(stderr, "  freq_max = %g\n", tx_freq_max());
+      fprintf(stderr, "  gain_min = %g\n", tx_gain_min());
+      fprintf(stderr, "  gain_max = %g\n", tx_gain_max());
+      fprintf(stderr, "  gain_db_per_step = %g\n", tx_gain_db_per_step());
+
+      rx_daughterboard_id(&dbid);
+      fprintf(stderr, "Rx dboard 0x%x\n", dbid);
+      fprintf(stderr, "  freq_min = %g\n", rx_freq_min());
+      fprintf(stderr, "  freq_max = %g\n", rx_freq_max());
+      fprintf(stderr, "  gain_min = %g\n", rx_gain_min());
+      fprintf(stderr, "  gain_max = %g\n", rx_gain_max());
+      fprintf(stderr, "  gain_db_per_step = %g\n", rx_gain_db_per_step());
+    }
+
+    // Ensure any custom values in hardware are cleared
+    if (!reset_db())
+      std::cerr << "usrp2::ctor reset_db failed\n";
+
+    // default gains to mid point
+    if (!set_tx_gain((tx_gain_min() + tx_gain_max()) / 2))
+      std::cerr << "usrp2::ctor set_tx_gain failed\n";
+
+    if (!set_rx_gain((rx_gain_min() + rx_gain_max()) / 2))
+      std::cerr << "usrp2::ctor set_rx_gain failed\n";
+
+    // default interp and decim
+    if (!set_tx_interp(12))
+      std::cerr << "usrp2::ctor set_tx_interp failed\n";
+
+    if (!set_rx_decim(12))
+      std::cerr << "usrp2::ctor set_rx_decim failed\n";
 
     // set workable defaults for scaling
     if (!set_rx_scale_iq(DEFAULT_RX_SCALE, DEFAULT_RX_SCALE))
       std::cerr << "usrp2::ctor set_rx_scale_iq failed\n";
-
-    if (!set_tx_scale_iq(DEFAULT_TX_SCALE, DEFAULT_TX_SCALE))
-      std::cerr << "usrp2::ctor set_tx_scale_iq failed\n";
   }
-  
+
   usrp2::impl::~impl()
   {
     stop_bg();
-    d_bg_thread = 0; // thread class deletes itself
     delete d_pf;
     d_eth_buf->close();
     delete d_eth_buf;
-    
+
     if (USRP2_IMPL_DEBUG) {
       std::cerr << std::endl
-                << "usrp2 destructor: received " << d_num_rx_frames 
+                << "usrp2 destructor: received " << d_num_rx_frames
                << " frames, with " << d_num_rx_missing << " lost ("
                << (d_num_rx_frames == 0 ? 0 : (int)(100.0*d_num_rx_missing/d_num_rx_frames))
                << "%), totaling " << d_num_rx_bytes
                << " bytes" << std::endl;
     }
   }
-  
+
   bool
   usrp2::impl::parse_mac_addr(const std::string &s, u2_mac_addr_t *p)
   {
@@ -177,14 +232,14 @@ namespace usrp2 {
     p->addr[3] = 0x85;
     p->addr[4] = 0x30;
     p->addr[5] = 0x00;
-    
+
     int len = s.size();
-    
+
     switch (len){
-      
+
     case 5:
       return sscanf(s.c_str(), "%hhx:%hhx", &p->addr[4], &p->addr[5]) == 2;
-      
+
     case 17:
       return sscanf(s.c_str(), "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx",
                    &p->addr[0], &p->addr[1], &p->addr[2],
@@ -193,36 +248,36 @@ namespace usrp2 {
       return false;
     }
   }
-  
+
   void
   usrp2::impl::init_et_hdrs(u2_eth_packet_t *p, const std::string &dst)
   {
     p->ehdr.ethertype = htons(U2_ETHERTYPE);
-    parse_mac_addr(dst, &p->ehdr.dst); 
+    parse_mac_addr(dst, &p->ehdr.dst);
     memcpy(&p->ehdr.src, d_eth_buf->mac(), 6);
     p->thdr.flags = 0; // FIXME transport header values?
     p->thdr.seqno = d_tx_seqno++;
     p->thdr.ack = 0;
   }
-  
-  void 
+
+  void
   usrp2::impl::init_etf_hdrs(u2_eth_packet_t *p, const std::string &dst,
                             int word0_flags, int chan, uint32_t timestamp)
   {
     init_et_hdrs(p, dst);
     u2p_set_word0(&p->fixed, word0_flags, chan);
     u2p_set_timestamp(&p->fixed, timestamp);
-    
+
     if (chan == CONTROL_CHAN) { // no sequence numbers, back it out
       p->thdr.seqno = 0;
       d_tx_seqno--;
     }
   }
-  
+
   void
   usrp2::impl::init_config_rx_v2_cmd(op_config_rx_v2_cmd *cmd)
   {
-    memset(cmd, 0, sizeof(*cmd)); 
+    memset(cmd, 0, sizeof(*cmd));
     init_etf_hdrs(&cmd->h, d_addr, 0, CONTROL_CHAN, -1);
     cmd->op.opcode = OP_CONFIG_RX_V2;
     cmd->op.len = sizeof(cmd->op);
@@ -234,7 +289,7 @@ namespace usrp2 {
   void
   usrp2::impl::init_config_tx_v2_cmd(op_config_tx_v2_cmd *cmd)
   {
-    memset(cmd, 0, sizeof(*cmd)); 
+    memset(cmd, 0, sizeof(*cmd));
     init_etf_hdrs(&cmd->h, d_addr, 0, CONTROL_CHAN, -1);
     cmd->op.opcode = OP_CONFIG_TX_V2;
     cmd->op.len = sizeof(cmd->op);
@@ -243,22 +298,35 @@ namespace usrp2 {
     cmd->eop.len = sizeof(cmd->eop);
   }
 
+
+  bool
+  usrp2::impl::transmit_cmd(void *cmd_, size_t len_)
+  {
+    const void *cmd = cmd_;
+    int len = len_;
+    unsigned char tmp[64];
+
+    if (len_ < 64){            // pad to minimum ethernet frame size
+      memset(tmp, 0, sizeof(tmp));
+      memcpy(tmp, cmd_, len_);
+      cmd = tmp;
+      len = sizeof(tmp);
+    }
+
+    return d_eth_buf->tx_frame(cmd, len) == eth_buffer::EB_OK;
+  }
+
   bool
-  usrp2::impl::transmit_cmd(void *cmd, size_t len, pending_reply *p, double secs)
+  usrp2::impl::transmit_cmd_and_wait(void *cmd, size_t len, pending_reply *p, double secs)
   {
-    if (p)    
-      d_pending_replies[p->rid()] = p;
-    
-    // Transmit command
-    if (d_eth_buf->tx_frame(cmd, len) != eth_buffer::EB_OK) {
+    d_pending_replies[p->rid()] = p;
+
+    if (!transmit_cmd(cmd, len)){
       d_pending_replies[p->rid()] = 0;
       return false;
     }
 
-    int res = 1;
-    if (p)
-      res = p->wait(secs);
-      
+    int res = p->wait_for_completion(secs);
     d_pending_replies[p->rid()] = 0;
     return res == 1;
   }
@@ -267,19 +335,25 @@ namespace usrp2 {
   //        Background loop: received packet demuxing
   // ----------------------------------------------------------------
 
+  void
+  usrp2::impl::start_bg()
+  {
+    d_rx_tg.create_thread(boost::bind(&usrp2::impl::bg_loop, this));
+  }
+
   void
   usrp2::impl::stop_bg()
   {
     d_bg_running = false;
-    d_bg_pending_cond.signal();
-    
-    void *dummy_status;
-    d_bg_thread->join(&dummy_status);  
+    d_bg_pending_cond.notify_one(); // FIXME: check if needed
+    d_rx_tg.join_all();
   }
-  
+
   void
   usrp2::impl::bg_loop()
   {
+    gruel::enable_realtime_scheduling();
+
     d_bg_running = true;
     while(d_bg_running) {
       DEBUG_LOG(":");
@@ -288,20 +362,20 @@ namespace usrp2 {
       // rings, and signal blocked API threads
       int res = d_eth_buf->rx_frames(this, 100); // FIXME magic timeout
       if (res == eth_buffer::EB_ERROR)
-       break;  
+       break;
 
       // Wait for user API thread(s) to process all enqueued packets.
-      // The channel ring thread that decrements d_num_enqueued to zero 
+      // The channel ring thread that decrements d_num_enqueued to zero
       // will signal this thread to continue.
       {
-        omni_mutex_lock l(d_enqueued_mutex);
+        gruel::scoped_lock l(d_enqueued_mutex);
         while(d_num_enqueued > 0 && d_bg_running)
-         d_bg_pending_cond.wait();
+         d_bg_pending_cond.wait(l);
       }
     }
     d_bg_running = false;
   }
-  
+
   //
   // passed to eth_buffer::rx_frames
   //
@@ -318,6 +392,10 @@ namespace usrp2 {
       return handle_control_packet(base, len);
     }
     else {                             // data packets
+
+      if (d_dont_enqueue)              // toss packet
+       return data_handler::RELEASE;
+
       return handle_data_packet(base, len);
     }
 
@@ -329,11 +407,11 @@ namespace usrp2 {
   {
     // point to beginning of payload (subpackets)
     unsigned char *p = (unsigned char *)base + sizeof(u2_eth_packet_t);
-    
+
     // FIXME (p % 4) == 2.  Not good.  Must watch for unaligned loads.
 
     // FIXME iterate over payload, handling more than a single subpacket.
-    
+
     int opcode = p[0];
     unsigned int oplen = p[1];
     unsigned int rid = p[2];
@@ -345,11 +423,11 @@ namespace usrp2 {
        std::cerr << "usrp2: mismatched command reply length (expected: "
                  << buflen << " got: " << oplen << "). "
                  << "op = " << opcode_to_string(opcode) << std::endl;
-      }     
-    
+      }
+
       // Copy reply into caller's buffer
       memcpy(rp->buffer(), p, std::min(oplen, buflen));
-      rp->signal();
+      rp->notify_completion();
       d_pending_replies[rid] = 0;
       return data_handler::RELEASE;
     }
@@ -358,26 +436,26 @@ namespace usrp2 {
     DEBUG_LOG("l");
     return data_handler::RELEASE;
   }
-  
+
   data_handler::result
   usrp2::impl::handle_data_packet(const void *base, size_t len)
   {
     u2_eth_samples_t *pkt = (u2_eth_samples_t *)base;
     d_num_rx_frames++;
     d_num_rx_bytes += len;
-    
+
     /* --- FIXME start of fake transport layer handler --- */
 
     if (d_rx_seqno != -1) {
       int expected_seqno = (d_rx_seqno + 1) & 0xFF;
-      int seqno = pkt->hdrs.thdr.seqno; 
-      
+      int seqno = pkt->hdrs.thdr.seqno;
+
       if (seqno != expected_seqno) {
        ::write(2, "S", 1); // missing sequence number
        int missing = seqno - expected_seqno;
        if (missing < 0)
          missing += 256;
-       
+
        d_num_rx_overruns++;
        d_num_rx_missing += missing;
       }
@@ -390,59 +468,57 @@ namespace usrp2 {
     // FIXME unaligned load!
     unsigned int chan = u2p_chan(&pkt->hdrs.fixed);
 
-    if (!d_channel_rings[chan]) {
-      DEBUG_LOG("!");
-      return data_handler::RELEASE;    // discard packet, no channel handler
-    }
+    {
+      gruel::scoped_lock l(d_channel_rings_mutex);
+
+      if (!d_channel_rings[chan]) {
+       DEBUG_LOG("!");
+       return data_handler::RELEASE;   // discard packet, no channel handler
+      }
 
-    // Strip off ethernet header and transport header and enqueue the rest
+      // Strip off ethernet header and transport header and enqueue the rest
 
-    size_t offset = offsetof(u2_eth_samples_t, hdrs.fixed);
-    if (d_channel_rings[chan]->enqueue(&pkt->hdrs.fixed, len-offset)) {
-      inc_enqueued();
-      DEBUG_LOG("+");
-      return data_handler::KEEP;       // channel ring runner will mark frame done
+      size_t offset = offsetof(u2_eth_samples_t, hdrs.fixed);
+      if (d_channel_rings[chan]->enqueue(&pkt->hdrs.fixed, len-offset)) {
+       inc_enqueued();
+       DEBUG_LOG("+");
+       return data_handler::KEEP;      // channel ring runner will mark frame done
+      }
+      else {
+       DEBUG_LOG("!");
+       return data_handler::RELEASE;   // discard, no room in channel ring
+      }
+      return data_handler::RELEASE;
     }
-    else {
-      DEBUG_LOG("!");
-      return data_handler::RELEASE;    // discard, no room in channel ring
-    }          
-    return data_handler::RELEASE;
   }
 
 
   // ----------------------------------------------------------------
-  //                      misc commands
+  //                          Receive
   // ----------------------------------------------------------------
 
   bool
-  usrp2::impl::burn_mac_addr(const std::string &new_addr)
-  {
-    op_burn_mac_addr_cmd cmd;
+  usrp2::impl::set_rx_antenna(int ant){
+    op_config_mimo_cmd cmd;
     op_generic_t reply;
 
     memset(&cmd, 0, sizeof(cmd));
     init_etf_hdrs(&cmd.h, d_addr, 0, CONTROL_CHAN, -1);
-    cmd.op.opcode = OP_BURN_MAC_ADDR;
+    cmd.op.opcode = OP_RX_ANTENNA;
     cmd.op.len = sizeof(cmd.op);
     cmd.op.rid = d_next_rid++;
-    if (!parse_mac_addr(new_addr, &cmd.op.addr))
-      return false;
+    cmd.op.flags = ant;
+    cmd.eop.opcode = OP_EOP;
+    cmd.eop.len = sizeof(cmd.eop);
 
     pending_reply p(cmd.op.rid, &reply, sizeof(reply));
-    if (!transmit_cmd(&cmd, sizeof(cmd), &p, 4*DEF_CMD_TIMEOUT))
+    if (!transmit_cmd_and_wait(&cmd, sizeof(cmd), &p, DEF_CMD_TIMEOUT))
       return false;
 
-    bool success = (ntohx(reply.ok) == 1);
-    return success;
+    return ntohx(reply.ok) == 1;
   }
 
-
-  // ----------------------------------------------------------------
-  //                          Receive
-  // ----------------------------------------------------------------
-
-  bool 
+  bool
   usrp2::impl::set_rx_gain(double gain)
   {
     op_config_rx_v2_cmd cmd;
@@ -451,15 +527,42 @@ namespace usrp2 {
     init_config_rx_v2_cmd(&cmd);
     cmd.op.valid = htons(CFGV_GAIN);
     cmd.op.gain = htons(u2_double_to_fxpt_gain(gain));
-    
+
+    pending_reply p(cmd.op.rid, &reply, sizeof(reply));
+    if (!transmit_cmd_and_wait(&cmd, sizeof(cmd), &p, DEF_CMD_TIMEOUT))
+      return false;
+
+    bool success = (ntohx(reply.ok) == 1);
+    return success;
+  }
+
+  bool
+  usrp2::impl::set_rx_lo_offset(double frequency)
+  {
+    op_freq_cmd cmd;
+    op_generic_t reply;
+
+    memset(&cmd, 0, sizeof(cmd));
+    init_etf_hdrs(&cmd.h, d_addr, 0, CONTROL_CHAN, -1);
+    cmd.op.opcode = OP_SET_RX_LO_OFFSET;
+    cmd.op.len = sizeof(cmd.op);
+    cmd.op.rid = d_next_rid++;
+
+    u2_fxpt_freq_t fxpt = u2_double_to_fxpt_freq(frequency);
+    cmd.op.freq_hi = htonl(u2_fxpt_freq_hi(fxpt));
+    cmd.op.freq_lo = htonl(u2_fxpt_freq_lo(fxpt));
+
+    cmd.eop.opcode = OP_EOP;
+    cmd.eop.len = sizeof(cmd.eop);
+
     pending_reply p(cmd.op.rid, &reply, sizeof(reply));
-    if (!transmit_cmd(&cmd, sizeof(cmd), &p, DEF_CMD_TIMEOUT))
+    if (!transmit_cmd_and_wait(&cmd, sizeof(cmd), &p, DEF_CMD_TIMEOUT))
       return false;
 
     bool success = (ntohx(reply.ok) == 1);
     return success;
   }
-  
+
   bool
   usrp2::impl::set_rx_center_freq(double frequency, tune_result *result)
   {
@@ -471,26 +574,26 @@ namespace usrp2 {
     u2_fxpt_freq_t fxpt = u2_double_to_fxpt_freq(frequency);
     cmd.op.freq_hi = htonl(u2_fxpt_freq_hi(fxpt));
     cmd.op.freq_lo = htonl(u2_fxpt_freq_lo(fxpt));
-    
+
     pending_reply p(cmd.op.rid, &reply, sizeof(reply));
-    if (!transmit_cmd(&cmd, sizeof(cmd), &p, DEF_CMD_TIMEOUT))
+    if (!transmit_cmd_and_wait(&cmd, sizeof(cmd), &p, DEF_CMD_TIMEOUT))
       return false;
 
     bool success = (ntohx(reply.ok) == 1);
     if (result && success) {
       result->baseband_freq =
-        u2_fxpt_freq_to_double( 
-         u2_fxpt_freq_from_hilo(ntohl(reply.baseband_freq_hi), 
+        u2_fxpt_freq_to_double(
+         u2_fxpt_freq_from_hilo(ntohl(reply.baseband_freq_hi),
                                 ntohl(reply.baseband_freq_lo)));
 
       result->dxc_freq =
-        u2_fxpt_freq_to_double( 
-         u2_fxpt_freq_from_hilo(ntohl(reply.ddc_freq_hi), 
+        u2_fxpt_freq_to_double(
+         u2_fxpt_freq_from_hilo(ntohl(reply.ddc_freq_hi),
                                 ntohl(reply.ddc_freq_lo)));
 
       result->residual_freq =
-        u2_fxpt_freq_to_double( 
-        u2_fxpt_freq_from_hilo(ntohl(reply.residual_freq_hi), 
+        u2_fxpt_freq_to_double(
+        u2_fxpt_freq_from_hilo(ntohl(reply.residual_freq_hi),
                                ntohl(reply.residual_freq_lo)));
 
       result->spectrum_inverted = (bool)(ntohx(reply.inverted) == 1);
@@ -498,7 +601,7 @@ namespace usrp2 {
 
     return success;
   }
-  
+
   bool
   usrp2::impl::set_rx_decim(int decimation_factor)
   {
@@ -508,15 +611,17 @@ namespace usrp2 {
     init_config_rx_v2_cmd(&cmd);
     cmd.op.valid = htons(CFGV_INTERP_DECIM);
     cmd.op.decim = htonl(decimation_factor);
-    
+
     pending_reply p(cmd.op.rid, &reply, sizeof(reply));
-    if (!transmit_cmd(&cmd, sizeof(cmd), &p, DEF_CMD_TIMEOUT))
+    if (!transmit_cmd_and_wait(&cmd, sizeof(cmd), &p, DEF_CMD_TIMEOUT))
       return false;
 
     bool success = (ntohx(reply.ok) == 1);
+    if (success)
+      d_rx_decim = decimation_factor;
     return success;
   }
-  
+
   bool
   usrp2::impl::set_rx_scale_iq(int scale_i, int scale_q)
   {
@@ -526,15 +631,15 @@ namespace usrp2 {
     init_config_rx_v2_cmd(&cmd);
     cmd.op.valid = htons(CFGV_SCALE_IQ);
     cmd.op.scale_iq = htonl(((scale_i & 0xffff) << 16) | (scale_q & 0xffff));
-    
+
     pending_reply p(cmd.op.rid, &reply, sizeof(reply));
-    if (!transmit_cmd(&cmd, sizeof(cmd), &p, DEF_CMD_TIMEOUT))
+    if (!transmit_cmd_and_wait(&cmd, sizeof(cmd), &p, DEF_CMD_TIMEOUT))
       return false;
 
     bool success = (ntohx(reply.ok) == 1);
     return success;
   }
-  
+
   bool
   usrp2::impl::start_rx_streaming(unsigned int channel, unsigned int items_per_frame)
   {
@@ -550,37 +655,155 @@ namespace usrp2 {
       return false;
     }
 
-    if (d_channel_rings[channel]) {
+    {
+      gruel::scoped_lock l(d_channel_rings_mutex);
+      if (d_channel_rings[channel]) {
+       std::cerr << "usrp2: channel " << channel
+                 << " already streaming" << std::endl;
+       return false;
+      }
+
+      if (items_per_frame == 0)
+       items_per_frame = U2_MAX_SAMPLES;               // minimize overhead
+
+      op_start_rx_streaming_cmd cmd;
+      op_generic_t reply;
+
+      memset(&cmd, 0, sizeof(cmd));
+      init_etf_hdrs(&cmd.h, d_addr, 0, CONTROL_CHAN, -1);
+      cmd.op.opcode = OP_START_RX_STREAMING;
+      cmd.op.len = sizeof(cmd.op);
+      cmd.op.rid = d_next_rid++;
+      cmd.op.items_per_frame = htonl(items_per_frame);
+      cmd.eop.opcode = OP_EOP;
+      cmd.eop.len = sizeof(cmd.eop);
+
+      d_dont_enqueue = false;
+      bool success = false;
+      pending_reply p(cmd.op.rid, &reply, sizeof(reply));
+      success = transmit_cmd_and_wait(&cmd, sizeof(cmd), &p, DEF_CMD_TIMEOUT);
+      success = success && (ntohx(reply.ok) == 1);
+
+      if (success)
+       d_channel_rings[channel] = ring_sptr(new ring(d_eth_buf->max_frames()));
+      else
+       d_dont_enqueue = true;
+
+      //fprintf(stderr, "usrp2::start_rx_streaming: success = %d\n", success);
+      return success;
+    }
+  }
+
+  bool
+  usrp2::impl::start_rx_streaming_at(unsigned int channel, unsigned int items_per_frame, unsigned int time)
+  {
+    if (channel > MAX_CHAN) {
+      std::cerr << "usrp2: invalid channel number (" << channel
+               << ")" << std::endl;
+      return false;
+    }
+
+    if (channel > 0) { // until firmware supports multiple streams
       std::cerr << "usrp2: channel " << channel
-               << " already streaming" << std::endl;
+               << " not implemented" << std::endl;
       return false;
     }
 
-    d_channel_rings[channel] = ring_sptr(new ring(d_eth_buf->max_frames()));
+    {
+      gruel::scoped_lock guard(d_channel_rings_mutex);
+      if (d_channel_rings[channel]) {
+       std::cerr << "usrp2: channel " << channel
+                 << " already streaming" << std::endl;
+       return false;
+      }
 
-    if (items_per_frame == 0)
-      items_per_frame = U2_MAX_SAMPLES;                // minimize overhead
-    
-    op_start_rx_streaming_cmd cmd;
-    op_generic_t reply;
+      if (items_per_frame == 0)
+       items_per_frame = U2_MAX_SAMPLES;               // minimize overhead
+
+      op_start_rx_streaming_cmd cmd;
+      op_generic_t reply;
+
+      memset(&cmd, 0, sizeof(cmd));
+      init_etf_hdrs(&cmd.h, d_addr, 0, CONTROL_CHAN, time);
+      cmd.op.opcode = OP_START_RX_STREAMING;
+      cmd.op.len = sizeof(cmd.op);
+      cmd.op.rid = d_next_rid++;
+      cmd.op.items_per_frame = htonl(items_per_frame);
+      cmd.eop.opcode = OP_EOP;
+      cmd.eop.len = sizeof(cmd.eop);
+
+      d_dont_enqueue = false;
+      bool success = false;
+      pending_reply p(cmd.op.rid, &reply, sizeof(reply));
+      success = transmit_cmd_and_wait(&cmd, sizeof(cmd), &p, DEF_CMD_TIMEOUT);
+      success = success && (ntohx(reply.ok) == 1);
+
+      if (success)
+       d_channel_rings[channel] = ring_sptr(new ring(d_eth_buf->max_frames()));
+      else
+       d_dont_enqueue = true;
+
+      return success;
+    }
+  }
 
-    memset(&cmd, 0, sizeof(cmd));
-    init_etf_hdrs(&cmd.h, d_addr, 0, CONTROL_CHAN, -1);
-    cmd.op.opcode = OP_START_RX_STREAMING;
-    cmd.op.len = sizeof(cmd.op);
-    cmd.op.rid = d_next_rid++;
-    cmd.op.items_per_frame = htonl(items_per_frame);
-    cmd.eop.opcode = OP_EOP;
-    cmd.eop.len = sizeof(cmd.eop);
-    
-    pending_reply p(cmd.op.rid, &reply, sizeof(reply));
-    if (!transmit_cmd(&cmd, sizeof(cmd), &p, DEF_CMD_TIMEOUT))
+  bool
+  usrp2::impl::sync_and_start_rx_streaming_at(unsigned int channel, unsigned int items_per_frame, unsigned int time)
+  {
+
+    if (channel > MAX_CHAN) {
+      std::cerr << "usrp2: invalid channel number (" << channel
+               << ")" << std::endl;
+      return false;
+    }
+
+    if (channel > 0) { // until firmware supports multiple streams
+      std::cerr << "usrp2: channel " << channel
+               << " not implemented" << std::endl;
       return false;
+    }
 
-    bool success = (ntohx(reply.ok) == 1);
-    return success;
+    {
+      gruel::scoped_lock guard(d_channel_rings_mutex);
+      if (d_channel_rings[channel]) {
+       std::cerr << "usrp2: channel " << channel
+                 << " already streaming" << std::endl;
+       return false;
+      }
+
+      if (items_per_frame == 0)
+       items_per_frame = U2_MAX_SAMPLES;               // minimize overhead
+
+      op_sync_and_start_rx_streaming_cmd cmd;
+      op_generic_t reply;
+
+      memset(&cmd, 0, sizeof(cmd));
+      init_etf_hdrs(&cmd.h, d_addr, 0, CONTROL_CHAN, time);
+      cmd.sync_op.opcode = OP_SYNC_TO_PPS;
+      cmd.sync_op.len = sizeof(cmd.sync_op);
+      cmd.sync_op.rid = d_next_rid++;
+      cmd.rx_op.opcode = OP_START_RX_STREAMING;
+      cmd.rx_op.len = sizeof(cmd.rx_op);
+      cmd.rx_op.rid = d_next_rid++;
+      cmd.rx_op.items_per_frame = htonl(items_per_frame);
+      cmd.eop.opcode = OP_EOP;
+      cmd.eop.len = sizeof(cmd.eop);
+
+      d_dont_enqueue = false;
+      bool success = false;
+      pending_reply p(cmd.sync_op.rid, &reply, sizeof(reply));
+      success = transmit_cmd_and_wait(&cmd, sizeof(cmd), &p, DEF_CMD_TIMEOUT);
+      success = success && (ntohx(reply.ok) == 1);
+
+      if (success)
+       d_channel_rings[channel] = ring_sptr(new ring(d_eth_buf->max_frames()));
+      else
+       d_dont_enqueue = true;
+
+      return success;
+    }
   }
-  
+
   bool
   usrp2::impl::stop_rx_streaming(unsigned int channel)
   {
@@ -596,36 +819,33 @@ namespace usrp2 {
       return false;
     }
 
-#if 0 // don't be overzealous.    
-    if (!d_channel_rings[channel]) {
-      std::cerr << "usrp2: channel " << channel
-               << " not streaming" << std::endl;
-      return false;
-    }
-#endif
+    d_dont_enqueue = true;     // no new samples
+    flush_rx_samples(channel); // dump any we may already have
 
     op_stop_rx_cmd cmd;
     op_generic_t reply;
 
-    memset(&cmd, 0, sizeof(cmd));
-    init_etf_hdrs(&cmd.h, d_addr, 0, CONTROL_CHAN, -1);
-    cmd.op.opcode = OP_STOP_RX;
-    cmd.op.len = sizeof(cmd.op);
-    cmd.op.rid = d_next_rid++;
-    cmd.eop.opcode = OP_EOP;
-    cmd.eop.len = sizeof(cmd.eop);
-    
-    pending_reply p(cmd.op.rid, &reply, sizeof(reply));
-    if (!transmit_cmd(&cmd, sizeof(cmd), &p, DEF_CMD_TIMEOUT))
-      return false;
-
-    bool success = (ntohx(reply.ok) == 1);
-    if (success)
+    {
+      gruel::scoped_lock l(d_channel_rings_mutex);
+
+      memset(&cmd, 0, sizeof(cmd));
+      init_etf_hdrs(&cmd.h, d_addr, 0, CONTROL_CHAN, -1);
+      cmd.op.opcode = OP_STOP_RX;
+      cmd.op.len = sizeof(cmd.op);
+      cmd.op.rid = d_next_rid++;
+      cmd.eop.opcode = OP_EOP;
+      cmd.eop.len = sizeof(cmd.eop);
+
+      bool success = false;
+      pending_reply p(cmd.op.rid, &reply, sizeof(reply));
+      success = transmit_cmd_and_wait(&cmd, sizeof(cmd), &p, DEF_CMD_TIMEOUT);
+      success = success && (ntohx(reply.ok) == 1);
       d_channel_rings[channel].reset();
-
-    return success;
+      d_rx_seqno = -1;
+      //fprintf(stderr, "usrp2::stop_rx_streaming:  success = %d\n", success);
+      return success;
+    }
   }
-  
 
   bool
   usrp2::impl::rx_samples(unsigned int channel, rx_sample_handler *handler)
@@ -635,25 +855,25 @@ namespace usrp2 {
                 << " )" << std::endl;
       return false;
     }
-    
+
     if (channel > 0) {
       std::cerr << "usrp2: channel " << channel
                 << " not implemented" << std::endl;
       return false;
     }
-    
+
     ring_sptr rp = d_channel_rings[channel];
     if (!rp){
       std::cerr << "usrp2: channel " << channel
                 << " not receiving" << std::endl;
       return false;
     }
-    
+
     // Wait for frames available in channel ring
     DEBUG_LOG("W");
     rp->wait_for_not_empty();
     DEBUG_LOG("s");
-    
+
     // Iterate through frames and present to user
     void *p;
     size_t frame_len_in_bytes;
@@ -675,11 +895,62 @@ namespace usrp2 {
     return true;
   }
 
+  bool
+  usrp2::impl::flush_rx_samples(unsigned int channel)
+  {
+    if (channel > MAX_CHAN) {
+      std::cerr << "usrp2: invalid channel (" << channel
+                << " )" << std::endl;
+      return false;
+    }
+
+    if (channel > 0) {
+      std::cerr << "usrp2: channel " << channel
+                << " not implemented" << std::endl;
+      return false;
+    }
+
+    ring_sptr rp = d_channel_rings[channel];
+    if (!rp){
+      return false;
+    }
+
+    // Iterate through frames and drop them
+    void *p;
+    size_t frame_len_in_bytes;
+    while (rp->dequeue(&p, &frame_len_in_bytes)) {
+      d_eth_buf->release_frame(p);
+      dec_enqueued();
+    }
+    return true;
+  }
+
   // ----------------------------------------------------------------
   //                           Transmit
   // ----------------------------------------------------------------
 
-  bool 
+  bool
+  usrp2::impl::set_tx_antenna(int ant){
+    op_config_mimo_cmd cmd;
+    op_generic_t reply;
+
+    memset(&cmd, 0, sizeof(cmd));
+    init_etf_hdrs(&cmd.h, d_addr, 0, CONTROL_CHAN, -1);
+    cmd.op.opcode = OP_TX_ANTENNA;
+    cmd.op.len = sizeof(cmd.op);
+    cmd.op.rid = d_next_rid++;
+    cmd.op.flags = ant;
+    cmd.eop.opcode = OP_EOP;
+    cmd.eop.len = sizeof(cmd.eop);
+
+    pending_reply p(cmd.op.rid, &reply, sizeof(reply));
+    if (!transmit_cmd_and_wait(&cmd, sizeof(cmd), &p, DEF_CMD_TIMEOUT))
+      return false;
+
+    return ntohx(reply.ok) == 1;
+  }
+
+  bool
   usrp2::impl::set_tx_gain(double gain)
   {
     op_config_tx_v2_cmd cmd;
@@ -688,15 +959,42 @@ namespace usrp2 {
     init_config_tx_v2_cmd(&cmd);
     cmd.op.valid = htons(CFGV_GAIN);
     cmd.op.gain = htons(u2_double_to_fxpt_gain(gain));
-    
+
     pending_reply p(cmd.op.rid, &reply, sizeof(reply));
-    if (!transmit_cmd(&cmd, sizeof(cmd), &p, DEF_CMD_TIMEOUT))
+    if (!transmit_cmd_and_wait(&cmd, sizeof(cmd), &p, DEF_CMD_TIMEOUT))
       return false;
 
     bool success = (ntohx(reply.ok) == 1);
     return success;
   }
-  
+
+  bool
+  usrp2::impl::set_tx_lo_offset(double frequency)
+  {
+    op_freq_cmd cmd;
+    op_generic_t reply;
+
+    memset(&cmd, 0, sizeof(cmd));
+    init_etf_hdrs(&cmd.h, d_addr, 0, CONTROL_CHAN, -1);
+    cmd.op.opcode = OP_SET_TX_LO_OFFSET;
+    cmd.op.len = sizeof(cmd.op);
+    cmd.op.rid = d_next_rid++;
+
+    u2_fxpt_freq_t fxpt = u2_double_to_fxpt_freq(frequency);
+    cmd.op.freq_hi = htonl(u2_fxpt_freq_hi(fxpt));
+    cmd.op.freq_lo = htonl(u2_fxpt_freq_lo(fxpt));
+
+    cmd.eop.opcode = OP_EOP;
+    cmd.eop.len = sizeof(cmd.eop);
+
+    pending_reply p(cmd.op.rid, &reply, sizeof(reply));
+    if (!transmit_cmd_and_wait(&cmd, sizeof(cmd), &p, DEF_CMD_TIMEOUT))
+      return false;
+
+    bool success = (ntohx(reply.ok) == 1);
+    return success;
+  }
+
   bool
   usrp2::impl::set_tx_center_freq(double frequency, tune_result *result)
   {
@@ -708,26 +1006,26 @@ namespace usrp2 {
     u2_fxpt_freq_t fxpt = u2_double_to_fxpt_freq(frequency);
     cmd.op.freq_hi = htonl(u2_fxpt_freq_hi(fxpt));
     cmd.op.freq_lo = htonl(u2_fxpt_freq_lo(fxpt));
-    
+
     pending_reply p(cmd.op.rid, &reply, sizeof(reply));
-    if (!transmit_cmd(&cmd, sizeof(cmd), &p, DEF_CMD_TIMEOUT))
+    if (!transmit_cmd_and_wait(&cmd, sizeof(cmd), &p, DEF_CMD_TIMEOUT))
       return false;
 
     bool success = (ntohx(reply.ok) == 1);
     if (result && success) {
       result->baseband_freq =
-        u2_fxpt_freq_to_double( 
-         u2_fxpt_freq_from_hilo(ntohl(reply.baseband_freq_hi), 
+        u2_fxpt_freq_to_double(
+         u2_fxpt_freq_from_hilo(ntohl(reply.baseband_freq_hi),
                                 ntohl(reply.baseband_freq_lo)));
 
       result->dxc_freq =
-        u2_fxpt_freq_to_double( 
-         u2_fxpt_freq_from_hilo(ntohl(reply.duc_freq_hi), 
+        u2_fxpt_freq_to_double(
+         u2_fxpt_freq_from_hilo(ntohl(reply.duc_freq_hi),
                                 ntohl(reply.duc_freq_lo)));
 
       result->residual_freq =
-        u2_fxpt_freq_to_double( 
-        u2_fxpt_freq_from_hilo(ntohl(reply.residual_freq_hi), 
+        u2_fxpt_freq_to_double(
+        u2_fxpt_freq_from_hilo(ntohl(reply.residual_freq_hi),
                                ntohl(reply.residual_freq_lo)));
 
       result->spectrum_inverted = (bool)(ntohx(reply.inverted) == 1);
@@ -735,7 +1033,7 @@ namespace usrp2 {
 
     return success;
   }
-  
+
   bool
   usrp2::impl::set_tx_interp(int interpolation_factor)
   {
@@ -745,17 +1043,50 @@ namespace usrp2 {
     init_config_tx_v2_cmd(&cmd);
     cmd.op.valid = htons(CFGV_INTERP_DECIM);
     cmd.op.interp = htonl(interpolation_factor);
-    
+
     pending_reply p(cmd.op.rid, &reply, sizeof(reply));
-    if (!transmit_cmd(&cmd, sizeof(cmd), &p, DEF_CMD_TIMEOUT))
+    if (!transmit_cmd_and_wait(&cmd, sizeof(cmd), &p, DEF_CMD_TIMEOUT))
       return false;
 
     bool success = (ntohx(reply.ok) == 1);
+    if (success) {
+      d_tx_interp = interpolation_factor;
+
+      // Auto-set TX scaling based on interpolation rate
+      int scale_i, scale_q;
+      default_tx_scale_iq(d_tx_interp, &scale_i, &scale_q);
+      return set_tx_scale_iq(scale_i, scale_q);
+    }
+
     return success;
   }
-  
-  bool
-  usrp2::impl::set_tx_scale_iq(int scale_i, int scale_q)
+
+  void
+  usrp2::impl::default_tx_scale_iq(int interpolation_factor, int *scale_i, int *scale_q)
+  {
+    // Calculate CIC interpolation (i.e., without halfband interpolators)
+    int i = interpolation_factor;
+    if (i > 128)
+      i = i >> 1;
+    if (i > 128)
+      i = i >> 1;
+
+    // Calculate dsp_core_tx gain absent scale multipliers
+    float gain = (1.65*i*i*i)/(4096*pow(2, ceil(log2(i*i*i))));
+
+    // Calculate closest multiplier constant to reverse gain
+    int scale = (int)rint(1.0/gain);
+    // fprintf(stderr, "if=%i i=%i gain=%f scale=%i\n", interpolation_factor, i, gain, scale);
+
+    // Both I and Q are identical in this case
+    if (scale_i)
+      *scale_i = scale;
+    if (scale_q)
+      *scale_q = scale;
+  }
+
+  bool
+  usrp2::impl::set_tx_scale_iq(int scale_i, int scale_q)
   {
     op_config_tx_v2_cmd cmd;
     op_config_tx_reply_v2_t reply;
@@ -763,9 +1094,9 @@ namespace usrp2 {
     init_config_tx_v2_cmd(&cmd);
     cmd.op.valid = htons(CFGV_SCALE_IQ);
     cmd.op.scale_iq = htonl(((scale_i & 0xffff) << 16) | (scale_q & 0xffff));
-    
+
     pending_reply p(cmd.op.rid, &reply, sizeof(reply));
-    if (!transmit_cmd(&cmd, sizeof(cmd), &p, DEF_CMD_TIMEOUT))
+    if (!transmit_cmd_and_wait(&cmd, sizeof(cmd), &p, DEF_CMD_TIMEOUT))
       return false;
 
     bool success = (ntohx(reply.ok) == 1);
@@ -773,21 +1104,21 @@ namespace usrp2 {
   }
 
   bool
-  usrp2::impl::tx_complex_float(unsigned int channel,
-                               const std::complex<float> *samples,
-                               size_t nsamples,
-                               const tx_metadata *metadata)
+  usrp2::impl::tx_32fc(unsigned int channel,
+                      const std::complex<float> *samples,
+                      size_t nsamples,
+                      const tx_metadata *metadata)
   {
     uint32_t items[nsamples];
-    copy_host_complex_float_to_u2_complex_16(nsamples, samples, items);
+    copy_host_32fc_to_u2_16sc(nsamples, samples, items);
     return tx_raw(channel, items, nsamples, metadata);
   }
 
   bool
-  usrp2::impl::tx_complex_int16(unsigned int channel,
-                               const std::complex<int16_t> *samples,
-                               size_t nsamples,
-                               const tx_metadata *metadata)
+  usrp2::impl::tx_16sc(unsigned int channel,
+                      const std::complex<int16_t> *samples,
+                      size_t nsamples,
+                      const tx_metadata *metadata)
   {
 #ifdef WORDS_BIGENDIAN
 
@@ -800,7 +1131,7 @@ namespace usrp2 {
 #else
 
     uint32_t items[nsamples];
-    copy_host_complex_16_to_u2_complex_16(nsamples, samples, items);
+    copy_host_16sc_to_u2_16sc(nsamples, samples, items);
     return tx_raw(channel, items, nsamples, metadata);
 
 #endif
@@ -815,10 +1146,8 @@ namespace usrp2 {
     if (nitems == 0)
       return true;
 
-    // FIXME there's the possibility that we send fewer than 9 items in a frame.
-    // That would end up glitching the transmitter, since the ethernet will pad to
-    // 64-bytes total (9 items).  We really need some part of the stack to
-    // carry the real length (thdr?).
+    // FIXME can't deal with nitems < U2_MIN_SAMPLES (will be fixed in VRT)
+    // FIXME need to check the MTU instead of assuming 1500 bytes
 
     // fragment as necessary then fire away
 
@@ -848,7 +1177,12 @@ namespace usrp2 {
 
       init_etf_hdrs(&hdrs, d_addr, flags, channel, timestamp);
 
-      size_t i = std::min((size_t) U2_MAX_SAMPLES, nitems - n);
+      // Avoid short packet by splitting last two packets if reqd
+      size_t i;
+      if ((nitems - n) > U2_MAX_SAMPLES && (nitems - n) < (U2_MAX_SAMPLES + U2_MIN_SAMPLES))
+       i = (nitems - n) / 2;
+      else
+       i = std::min((size_t) U2_MAX_SAMPLES, nitems - n);
 
       eth_iovec iov[2];
       iov[0].iov_base = &hdrs;
@@ -856,6 +1190,10 @@ namespace usrp2 {
       iov[1].iov_base = const_cast<uint32_t *>(&items[n]);
       iov[1].iov_len = i * sizeof(uint32_t);
 
+      size_t total = iov[0].iov_len + iov[1].iov_len;
+      if (total < 64)
+       fprintf(stderr, "usrp2::tx_raw: FIXME: short packet: %zd items (%zd bytes)\n", i, total);
+
       if (d_eth_buf->tx_framev(iov, 2) != eth_buffer::EB_OK){
        return false;
       }
@@ -866,5 +1204,464 @@ namespace usrp2 {
     return true;
   }
 
+  // ----------------------------------------------------------------
+  //                      misc commands
+  // ----------------------------------------------------------------
+
+  bool
+  usrp2::impl::config_mimo(int flags)
+  {
+    op_config_mimo_cmd cmd;
+    op_generic_t reply;
+
+    memset(&cmd, 0, sizeof(cmd));
+    init_etf_hdrs(&cmd.h, d_addr, 0, CONTROL_CHAN, -1);
+    cmd.op.opcode = OP_CONFIG_MIMO;
+    cmd.op.len = sizeof(cmd.op);
+    cmd.op.rid = d_next_rid++;
+    cmd.op.flags = flags;
+    cmd.eop.opcode = OP_EOP;
+    cmd.eop.len = sizeof(cmd.eop);
+
+    pending_reply p(cmd.op.rid, &reply, sizeof(reply));
+    if (!transmit_cmd_and_wait(&cmd, sizeof(cmd), &p, DEF_CMD_TIMEOUT))
+      return false;
+
+    return ntohx(reply.ok) == 1;
+  }
+
+  bool
+  usrp2::impl::fpga_master_clock_freq(long *freq)
+  {
+    *freq = 100000000L;                // 100 MHz
+    return true;
+  }
+
+  bool
+  usrp2::impl::adc_rate(long *rate)
+  {
+    return fpga_master_clock_freq(rate);
+  }
+
+  bool
+  usrp2::impl::dac_rate(long *rate)
+  {
+    return fpga_master_clock_freq(rate);
+  }
+
+  bool
+  usrp2::impl::tx_daughterboard_id(int *dbid)
+  {
+    *dbid = d_tx_db_info.dbid;
+    return true;
+  }
+
+  bool
+  usrp2::impl::rx_daughterboard_id(int *dbid)
+  {
+    *dbid = d_rx_db_info.dbid;
+    return true;
+  }
+
+
+  // ----------------------------------------------------------------
+  //                   low-level commands
+  // ----------------------------------------------------------------
+
+  bool
+  usrp2::impl::burn_mac_addr(const std::string &new_addr)
+  {
+    op_burn_mac_addr_cmd cmd;
+    op_generic_t reply;
+
+    memset(&cmd, 0, sizeof(cmd));
+    init_etf_hdrs(&cmd.h, d_addr, 0, CONTROL_CHAN, -1);
+    cmd.op.opcode = OP_BURN_MAC_ADDR;
+    cmd.op.len = sizeof(cmd.op);
+    cmd.op.rid = d_next_rid++;
+    if (!parse_mac_addr(new_addr, &cmd.op.addr))
+      return false;
+
+    pending_reply p(cmd.op.rid, &reply, sizeof(reply));
+    if (!transmit_cmd_and_wait(&cmd, sizeof(cmd), &p, 4*DEF_CMD_TIMEOUT))
+      return false;
+
+    bool success = (ntohx(reply.ok) == 1);
+    return success;
+  }
+
+  static void
+  fill_dboard_info(db_info *dst, const u2_db_info_t *src)
+  {
+    dst->dbid = ntohl(src->dbid);
+
+    dst->freq_min =
+      u2_fxpt_freq_to_double(u2_fxpt_freq_from_hilo(ntohl(src->freq_min_hi),
+                                                   ntohl(src->freq_min_lo)));
+    dst->freq_max =
+      u2_fxpt_freq_to_double(u2_fxpt_freq_from_hilo(ntohl(src->freq_max_hi),
+                                                   ntohl(src->freq_max_lo)));
+
+    dst->gain_min = u2_fxpt_gain_to_double(ntohs(src->gain_min));
+    dst->gain_max = u2_fxpt_gain_to_double(ntohs(src->gain_max));
+    dst->gain_step_size = u2_fxpt_gain_to_double(ntohs(src->gain_step_size));
+  }
+
+  bool
+  usrp2::impl::dboard_info()
+  {
+    op_dboard_info_cmd         cmd;
+    op_dboard_info_reply_t     reply;
+
+    memset(&cmd, 0, sizeof(cmd));
+    init_etf_hdrs(&cmd.h, d_addr, 0, CONTROL_CHAN, -1);
+    cmd.op.opcode = OP_DBOARD_INFO;
+    cmd.op.len = sizeof(cmd.op);
+    cmd.op.rid = d_next_rid++;
+    cmd.eop.opcode = OP_EOP;
+    cmd.eop.len = sizeof(cmd.eop);
+
+    pending_reply p(cmd.op.rid, &reply, sizeof(reply));
+    if (!transmit_cmd_and_wait(&cmd, sizeof(cmd), &p, DEF_CMD_TIMEOUT))
+      return false;
+
+    bool success = (ntohx(reply.ok) == 1);
+    if (success){
+      fill_dboard_info(&d_tx_db_info, &reply.tx_db_info);
+      fill_dboard_info(&d_rx_db_info, &reply.rx_db_info);
+    }
+    return success;
+  }
+
+
+  bool
+  usrp2::impl::sync_to_pps()
+  {
+    op_generic_cmd cmd;
+    op_generic_t   reply;
+
+    memset(&cmd, 0, sizeof(cmd));
+    init_etf_hdrs(&cmd.h, d_addr, 0, CONTROL_CHAN, -1);
+    cmd.op.opcode = OP_SYNC_TO_PPS;
+    cmd.op.len = sizeof(cmd.op);
+    cmd.op.rid = d_next_rid++;
+    cmd.eop.opcode = OP_EOP;
+    cmd.eop.len = sizeof(cmd.eop);
+
+    pending_reply p(cmd.op.rid, &reply, sizeof(reply));
+    if (!transmit_cmd_and_wait(&cmd, sizeof(cmd), &p, DEF_CMD_TIMEOUT))
+      return false;
+
+    return ntohx(reply.ok) == 1;
+  }
+
+  bool
+  usrp2::impl::sync_every_pps(bool enable)
+  {
+    op_generic_cmd cmd;
+    op_generic_t   reply;
+
+    memset(&cmd, 0, sizeof(cmd));
+    init_etf_hdrs(&cmd.h, d_addr, 0, CONTROL_CHAN, -1);
+    cmd.op.opcode = OP_SYNC_EVERY_PPS;
+    cmd.op.len = sizeof(cmd.op);
+    cmd.op.rid = d_next_rid++;
+    cmd.op.ok = enable ? 1 : 0;
+    cmd.eop.opcode = OP_EOP;
+    cmd.eop.len = sizeof(cmd.eop);
+
+    pending_reply p(cmd.op.rid, &reply, sizeof(reply));
+    if (!transmit_cmd_and_wait(&cmd, sizeof(cmd), &p, DEF_CMD_TIMEOUT))
+      return false;
+
+    return ntohx(reply.ok) == 1;
+  }
+
+  std::vector<uint32_t>
+  usrp2::impl::peek32(uint32_t addr, uint32_t words)
+  {
+    std::vector<uint32_t> result; // zero sized on error return
+    // fprintf(stderr, "usrp2::peek: addr=%08X words=%u\n", addr, words);
+
+    if (addr % 4 != 0) {
+      fprintf(stderr, "usrp2::peek: addr (=%08X) must be 32-bit word aligned\n", addr);
+      return result;
+    }
+
+    if (words == 0)
+      return result;
+
+    op_peek_cmd   cmd;
+    op_generic_t *reply;
+
+    int wlen = sizeof(uint32_t);
+    int rlen = sizeof(op_generic_t);
+    size_t bytes = words*wlen;
+
+    memset(&cmd, 0, sizeof(cmd));
+    init_etf_hdrs(&cmd.h, d_addr, 0, CONTROL_CHAN, -1);
+    cmd.op.opcode = OP_PEEK;
+    cmd.op.len = sizeof(cmd.op);
+    cmd.op.rid = d_next_rid++;
+    cmd.eop.opcode = OP_EOP;
+    cmd.eop.len = sizeof(cmd.eop);
+
+    cmd.op.addr = htonl(addr);
+    cmd.op.bytes = htonl(bytes);
+
+    reply = (op_generic_t *)malloc(rlen+bytes);
+    pending_reply p(cmd.op.rid, reply, rlen+bytes);
+    if (transmit_cmd_and_wait(&cmd, sizeof(cmd), &p, DEF_CMD_TIMEOUT)) {
+      uint32_t nwords = (reply->len-rlen)/sizeof(uint32_t);
+      uint32_t *data = (uint32_t *)(reply+rlen/wlen);
+      for (unsigned int i = 0; i < nwords; i++)
+       result.push_back(ntohl(data[i]));
+    }
+
+    free(reply);
+    return result;
+  }
+
+  bool
+  usrp2::impl::poke32(uint32_t addr, const std::vector<uint32_t> &data)
+  {
+    if (addr % 4 != 0) {
+      fprintf(stderr, "usrp2::poke32: addr (=%08X) must be 32-bit word aligned\n", addr);
+      return false;
+    }
+
+    int plen = sizeof(op_poke_cmd);
+    int wlen = sizeof(uint32_t);
+    int max_words = (MAX_SUBPKT_LEN-plen)/wlen;
+    int words = data.size();
+
+    if (words > max_words) {
+      fprintf(stderr, "usrp2::poke32: write size (=%u) exceeds maximum of %u words\n",
+             words, max_words);
+      return false;
+    }
+
+    //fprintf(stderr, "usrp2::poke32: addr=%08X words=%u\n", addr, words);
+
+    if (words == 0)
+      return true; // NOP
+
+    op_poke_cmd  *cmd;
+    op_generic_t *eop;
+
+    // Allocate, clear, and initialize command packet
+    int bytes = words*wlen;
+    int l = plen+bytes+sizeof(*eop); // op_poke_cmd+data+eop
+    cmd = (op_poke_cmd *)malloc(l);
+    //fprintf(stderr, "cmd=%p l=%i\n", cmd, l);
+    memset(cmd, 0, l);
+    init_etf_hdrs(&cmd->h, d_addr, 0, CONTROL_CHAN, -1);
+    cmd->op.opcode = OP_POKE;
+    cmd->op.len = sizeof(cmd->op)+bytes;
+    cmd->op.rid = d_next_rid++;
+    cmd->op.addr = htonl(addr);
+
+    // Copy data from vector into packet space
+    uint32_t *dest = (uint32_t *)((uint8_t *)cmd+plen);
+    for (int i = 0; i < words; i++) {
+      //fprintf(stderr, "%03i@%p\n", i, dest);
+      *dest++ = htonl(data[i]);
+    }
+
+    // Write end-of-packet subpacket
+    eop = (op_generic_t *)dest;
+    eop->opcode = OP_EOP;
+    eop->len = sizeof(*eop);
+    //fprintf(stderr, "eop=%p len=%i\n", eop, eop->len);
+
+    // Send command to device and retrieve reply
+    bool ok = false;
+    op_generic_t reply;
+    pending_reply p(cmd->op.rid, &reply, sizeof(reply));
+    if (transmit_cmd_and_wait(cmd, l, &p, DEF_CMD_TIMEOUT))
+      ok = (ntohx(reply.ok) == 1);
+
+    free(cmd);
+    return ok;
+  }
+
+  bool
+  usrp2::impl::reset_db()
+  {
+    op_generic_cmd cmd;
+    op_generic_t reply;
+
+    memset(&cmd, 0, sizeof(cmd));
+    init_etf_hdrs(&cmd.h, d_addr, 0, CONTROL_CHAN, -1);
+    cmd.op.opcode = OP_RESET_DB;
+    cmd.op.len = sizeof(cmd.op);
+    cmd.op.rid = d_next_rid++;
+    cmd.eop.opcode = OP_EOP;
+    cmd.eop.len = sizeof(cmd.eop);
+
+    pending_reply p(cmd.op.rid, &reply, sizeof(reply));
+    if (!transmit_cmd_and_wait(&cmd, sizeof(cmd), &p, DEF_CMD_TIMEOUT))
+      return false;
+
+    bool success = (ntohx(reply.ok) == 1);
+    return success;
+  }
+
+  bool usrp2::impl::set_gpio_ddr(int bank, uint16_t value, uint16_t mask)
+  {
+    if (bank != GPIO_TX_BANK && bank != GPIO_RX_BANK) {
+      fprintf(stderr, "set_gpio_ddr: bank must be one of GPIO_RX_BANK or GPIO_TX_BANK\n");
+      return false;
+    }
+
+    op_gpio_cmd cmd;
+    op_generic_t reply;
+
+    memset(&cmd, 0, sizeof(cmd));
+    init_etf_hdrs(&cmd.h, d_addr, 0, CONTROL_CHAN, -1);
+    cmd.op.opcode = OP_GPIO_SET_DDR;
+    cmd.op.len = sizeof(cmd.op);
+    cmd.op.rid = d_next_rid++;
+    cmd.op.bank = static_cast<uint8_t>(bank);
+    cmd.op.value = htons(value);
+    cmd.op.mask = htons(mask);
+    cmd.eop.opcode = OP_EOP;
+    cmd.eop.len = sizeof(cmd.eop);
+
+    pending_reply p(cmd.op.rid, &reply, sizeof(reply));
+    if (!transmit_cmd_and_wait(&cmd, sizeof(cmd), &p, DEF_CMD_TIMEOUT))
+      return false;
+
+    bool success = (ntohx(reply.ok) == 1);
+    return success;
+  }
+
+  bool usrp2::impl::set_gpio_sels(int bank, std::string sels)
+  {
+    if (bank != GPIO_TX_BANK && bank != GPIO_RX_BANK) {
+      fprintf(stderr, "set_gpio_ddr: bank must be one of GPIO_RX_BANK or GPIO_TX_BANK\n");
+      return false;
+    }
+
+    if (sels.size() != 16) {
+      fprintf(stderr, "set_gpio_sels: sels must be exactly 16 bytes\n");
+      return false;
+    }
+
+    op_gpio_set_sels_cmd cmd;
+    op_generic_t reply;
+
+    memset(&cmd, 0, sizeof(cmd));
+    init_etf_hdrs(&cmd.h, d_addr, 0, CONTROL_CHAN, -1);
+    cmd.op.opcode = OP_GPIO_SET_SELS;
+    cmd.op.len = sizeof(cmd.op);
+    cmd.op.rid = d_next_rid++;
+    cmd.op.bank = static_cast<uint8_t>(bank);
+    memcpy(&cmd.op.sels, sels.c_str(), 16);
+    cmd.eop.opcode = OP_EOP;
+    cmd.eop.len = sizeof(cmd.eop);
+
+    pending_reply p(cmd.op.rid, &reply, sizeof(reply));
+    if (!transmit_cmd_and_wait(&cmd, sizeof(cmd), &p, DEF_CMD_TIMEOUT))
+      return false;
+
+    bool success = (ntohx(reply.ok) == 1);
+    return success;
+  }
+
+  bool usrp2::impl::write_gpio(int bank, uint16_t value, uint16_t mask)
+  {
+    if (bank != GPIO_TX_BANK && bank != GPIO_RX_BANK) {
+      fprintf(stderr, "set_gpio_ddr: bank must be one of GPIO_RX_BANK or GPIO_TX_BANK\n");
+      return false;
+    }
+
+    op_gpio_cmd cmd;
+    op_generic_t reply;
+
+    memset(&cmd, 0, sizeof(cmd));
+    init_etf_hdrs(&cmd.h, d_addr, 0, CONTROL_CHAN, -1);
+    cmd.op.opcode = OP_GPIO_WRITE;
+    cmd.op.len = sizeof(cmd.op);
+    cmd.op.rid = d_next_rid++;
+    cmd.op.bank = static_cast<uint8_t>(bank);
+    cmd.op.value = htons(value);
+    cmd.op.mask = htons(mask);
+    cmd.eop.opcode = OP_EOP;
+    cmd.eop.len = sizeof(cmd.eop);
+
+    pending_reply p(cmd.op.rid, &reply, sizeof(reply));
+    if (!transmit_cmd_and_wait(&cmd, sizeof(cmd), &p, DEF_CMD_TIMEOUT))
+      return false;
+
+    bool success = (ntohx(reply.ok) == 1);
+    return success;
+  }
+
+  bool usrp2::impl::read_gpio(int bank, uint16_t *value)
+  {
+    if (bank != GPIO_TX_BANK && bank != GPIO_RX_BANK) {
+      fprintf(stderr, "set_gpio_ddr: bank must be one of GPIO_RX_BANK or GPIO_TX_BANK\n");
+      return false;
+    }
+
+    op_gpio_cmd cmd;
+    op_gpio_read_reply_t reply;
+
+    memset(&cmd, 0, sizeof(cmd));
+    init_etf_hdrs(&cmd.h, d_addr, 0, CONTROL_CHAN, -1);
+    cmd.op.opcode = OP_GPIO_READ;
+    cmd.op.len = sizeof(cmd.op);
+    cmd.op.rid = d_next_rid++;
+    cmd.op.bank = static_cast<uint8_t>(bank);
+    cmd.op.value = 0; // not used
+    cmd.op.mask = 0;  // not used
+    cmd.eop.opcode = OP_EOP;
+    cmd.eop.len = sizeof(cmd.eop);
+
+    pending_reply p(cmd.op.rid, &reply, sizeof(reply));
+    if (!transmit_cmd_and_wait(&cmd, sizeof(cmd), &p, DEF_CMD_TIMEOUT))
+      return false;
+
+    bool success = (ntohx(reply.ok) == 1);
+    if (success && (value != NULL))
+      *value = ntohs(reply.value);
+
+    return success;
+  }
+
+  bool usrp2::impl::enable_gpio_streaming(int bank, int enable)
+  {
+    if (bank != GPIO_RX_BANK) {
+      fprintf(stderr, "enable_gpio_streaming: only RX streaming is currently implemented\n");
+      return false;
+    }
+
+    if ((enable & ~0x03) != 0) {
+      fprintf(stderr, "enable_gpio_streaming: invalid enable format\n");
+      return false;
+    }
+
+    op_gpio_cmd cmd;
+    op_generic_t reply;
+
+    memset(&cmd, 0, sizeof(cmd));
+    init_etf_hdrs(&cmd.h, d_addr, 0, CONTROL_CHAN, -1);
+    cmd.op.opcode = OP_GPIO_STREAM;
+    cmd.op.len = sizeof(cmd.op);
+    cmd.op.rid = d_next_rid++;
+    cmd.op.bank = static_cast<uint8_t>(bank);
+    cmd.op.value = htons((uint16_t)enable);
+    cmd.op.mask = 0;  // not used
+    cmd.eop.opcode = OP_EOP;
+    cmd.eop.len = sizeof(cmd.eop);
+
+    pending_reply p(cmd.op.rid, &reply, sizeof(reply));
+    if (!transmit_cmd_and_wait(&cmd, sizeof(cmd), &p, DEF_CMD_TIMEOUT))
+      return false;
+
+    bool success = (ntohx(reply.ok) == 1);
+    return success;
+  }
 
 } // namespace usrp2