/*
 * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The OpenAirInterface Software Alliance licenses this file to You under
 * the OAI Public License, Version 1.1  (the "License"); you may not use this file
 * except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.openairinterface.org/?page_id=698
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *-------------------------------------------------------------------------------
 * For more information about the OpenAirInterface (OAI) Software Alliance:
 *      contact@openairinterface.org
 */

/** usrp_lib.cpp
 *
 * \author: HongliangXU : hong-liang-xu@agilent.com
 */

#include <string.h>
#include <pthread.h>
#include <unistd.h>
#include <stdio.h>
#include <uhd/version.hpp>
#if UHD_VERSION < 3110000
#include <uhd/utils/thread_priority.hpp>
#else
#include <uhd/utils/thread.hpp>
#endif
#include <uhd/usrp/multi_usrp.hpp>
#include <uhd/version.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/thread.hpp>
#include <boost/format.hpp>
#include <iostream>
#include <complex>
#include <fstream>
#include <cmath>
#include <time.h>
#include "common/utils/LOG/log.h"
#include "common_lib.h"
#include "assertions.h"
#include <sys/sysinfo.h>
#include <sys/resource.h>

#ifdef __SSE4_1__
#  include <smmintrin.h>
#endif

#ifdef __AVX2__
#  include <immintrin.h>
#endif

#ifdef __arm__
#  include <arm_neon.h>
#endif

/** @addtogroup _USRP_PHY_RF_INTERFACE_
 * @{
 */

/*! \brief USRP Configuration */
typedef struct {

  // --------------------------------
  // variables for USRP configuration
  // --------------------------------
  //! USRP device pointer
  uhd::usrp::multi_usrp::sptr usrp;

  //create a send streamer and a receive streamer
  //! USRP TX Stream
  uhd::tx_streamer::sptr tx_stream;
  //! USRP RX Stream
  uhd::rx_streamer::sptr rx_stream;

  //! USRP TX Metadata
  uhd::tx_metadata_t tx_md;
  //! USRP RX Metadata
  uhd::rx_metadata_t rx_md;

  //! Sampling rate
  double sample_rate;

  //! TX forward samples. We use usrp_time_offset to get this value
  int tx_forward_nsamps; //166 for 20Mhz

  // --------------------------------
  // Debug and output control
  // --------------------------------
  int num_underflows;
  int num_overflows;
  int num_seq_errors;
  int64_t tx_count;
  int64_t rx_count;
  int wait_for_first_pps;
  int use_gps;
  int first_tx;
  //! timestamp of RX packet
  openair0_timestamp rx_timestamp;

} usrp_state_t;

//void print_notes(void)
//{
// Helpful notes
//  std::cout << boost::format("**************************************Helpful Notes on Clock/PPS Selection**************************************\n");
//  std::cout << boost::format("As you can see, the default 10 MHz Reference and 1 PPS signals are now from the GPSDO.\n");
//  std::cout << boost::format("If you would like to use the internal reference(TCXO) in other applications, you must configure that explicitly.\n");
//  std::cout << boost::format("You can no longer select the external SMAs for 10 MHz or 1 PPS signaling.\n");
//  std::cout << boost::format("****************************************************************************************************************\n");
//}

int check_ref_locked(usrp_state_t *s,size_t mboard) {
  std::vector<std::string> sensor_names = s->usrp->get_mboard_sensor_names(mboard);
  bool ref_locked = false;

  if(std::find(sensor_names.begin(), sensor_names.end(), "ref_locked") != sensor_names.end()) {
    std::cout << "Waiting for reference lock..." << std::flush;

    for (int i = 0; i < 30 and not ref_locked; i++) {
      ref_locked = s->usrp->get_mboard_sensor("ref_locked", mboard).to_bool();
      
      if (not ref_locked) {
	std::cout << "." << std::flush;
	boost::this_thread::sleep(boost::posix_time::seconds(1));
      }
    }

    if(ref_locked) {
      std::cout << "LOCKED" << std::endl;
    } else {
      std::cout << "FAILED" << std::endl;
    }

  } else {
    std::cout << boost::format("ref_locked sensor not present on this board.\n");
  }

  return ref_locked;

}

static int sync_to_gps(openair0_device *device) {
  uhd::set_thread_priority_safe();
  //std::string args;
  //Set up program options
  //po::options_description desc("Allowed options");
  //desc.add_options()
  //("help", "help message")
  //("args", po::value<std::string>(&args)->default_value(""), "USRP device arguments")
  //;
  //po::variables_map vm;
  //po::store(po::parse_command_line(argc, argv, desc), vm);
  //po::notify(vm);
  //Print the help message
  //if (vm.count("help"))
  //{
  //  std::cout << boost::format("Synchronize USRP to GPS %s") % desc << std::endl;
  // return EXIT_FAILURE;
  //}
  //Create a USRP device
  //std::cout << boost::format("\nCreating the USRP device with: %s...\n") % args;
  //uhd::usrp::multi_usrp::sptr usrp = uhd::usrp::multi_usrp::make(args);
  //std::cout << boost::format("Using Device: %s\n") % usrp->get_pp_string();
  usrp_state_t *s = (usrp_state_t *)device->priv;

  try {
    size_t num_mboards = s->usrp->get_num_mboards();
    size_t num_gps_locked = 0;

    for (size_t mboard = 0; mboard < num_mboards; mboard++) {
      std::cout << "Synchronizing mboard " << mboard << ": " << s->usrp->get_mboard_name(mboard) << std::endl;

      bool ref_locked = check_ref_locked(s,mboard);

      if (ref_locked) {
	std::cout << boost::format("Ref Locked\n");
      } else {
	std::cout << "Failed to lock to GPSDO 10 MHz Reference. Exiting." << std::endl;
	exit(EXIT_FAILURE);
      }

      //Wait for GPS lock
      bool gps_locked = s->usrp->get_mboard_sensor("gps_locked", mboard).to_bool();

      if(gps_locked) {
        num_gps_locked++;
        std::cout << boost::format("GPS Locked\n");
      } else {
        LOG_W(HW,"WARNING:  GPS not locked - time will not be accurate until locked\n");
      }

      //Set to GPS time
      uhd::time_spec_t gps_time = uhd::time_spec_t(time_t(s->usrp->get_mboard_sensor("gps_time", mboard).to_int()));
      //s->usrp->set_time_next_pps(gps_time+1.0, mboard);
      s->usrp->set_time_next_pps(uhd::time_spec_t(0.0));
      //Wait for it to apply
      //The wait is 2 seconds because N-Series has a known issue where
      //the time at the last PPS does not properly update at the PPS edge
      //when the time is actually set.
      boost::this_thread::sleep(boost::posix_time::seconds(2));
      //Check times
      gps_time = uhd::time_spec_t(time_t(s->usrp->get_mboard_sensor("gps_time", mboard).to_int()));
      uhd::time_spec_t time_last_pps = s->usrp->get_time_last_pps(mboard);
      std::cout << "USRP time: " << (boost::format("%0.9f") % time_last_pps.get_real_secs()) << std::endl;
      std::cout << "GPSDO time: " << (boost::format("%0.9f") % gps_time.get_real_secs()) << std::endl;
      //if (gps_time.get_real_secs() == time_last_pps.get_real_secs())
      //    std::cout << std::endl << "SUCCESS: USRP time synchronized to GPS time" << std::endl << std::endl;
      //else
      //    std::cerr << std::endl << "ERROR: Failed to synchronize USRP time to GPS time" << std::endl << std::endl;
    }

    if (num_gps_locked == num_mboards and num_mboards > 1) {
      //Check to see if all USRP times are aligned
      //First, wait for PPS.
      uhd::time_spec_t time_last_pps = s->usrp->get_time_last_pps();

      while (time_last_pps == s->usrp->get_time_last_pps()) {
        boost::this_thread::sleep(boost::posix_time::milliseconds(1));
      }

      //Sleep a little to make sure all devices have seen a PPS edge
      boost::this_thread::sleep(boost::posix_time::milliseconds(200));
      //Compare times across all mboards
      bool all_matched = true;
      uhd::time_spec_t mboard0_time = s->usrp->get_time_last_pps(0);

      for (size_t mboard = 1; mboard < num_mboards; mboard++) {
        uhd::time_spec_t mboard_time = s->usrp->get_time_last_pps(mboard);

        if (mboard_time != mboard0_time) {
          all_matched = false;
          std::cerr << (boost::format("ERROR: Times are not aligned: USRP 0=%0.9f, USRP %d=%0.9f")
                        % mboard0_time.get_real_secs()
                        % mboard
                        % mboard_time.get_real_secs()) << std::endl;
        }
      }

      if (all_matched) {
        std::cout << "SUCCESS: USRP times aligned" << std::endl << std::endl;
      } else {
        std::cout << "ERROR: USRP times are not aligned" << std::endl << std::endl;
      }
    }
  } catch (std::exception &e) {
    std::cout << boost::format("\nError: %s") % e.what();
    std::cout << boost::format("This could mean that you have not installed the GPSDO correctly.\n\n");
    std::cout << boost::format("Visit one of these pages if the problem persists:\n");
    std::cout << boost::format(" * N2X0/E1X0: http://files.ettus.com/manual/page_gpsdo.html");
    std::cout << boost::format(" * X3X0: http://files.ettus.com/manual/page_gpsdo_x3x0.html\n\n");
    std::cout << boost::format(" * E3X0: http://files.ettus.com/manual/page_usrp_e3x0.html#e3x0_hw_gps\n\n");
    exit(EXIT_FAILURE);
  }

  return EXIT_SUCCESS;
}

#if defined(USRP_REC_PLAY)
#include "usrp_lib.h"
static FILE    *pFile = NULL;
int             mmapfd = 0;
int             iqfd = 0;
int             use_mmap = 1; // default is to use mmap
struct stat     sb;
iqrec_t        *ms_sample = NULL;                      // memory for all subframes
unsigned int    nb_samples = 0;
unsigned int    cur_samples = 0;
int64_t         wrap_count = 0;
int64_t         wrap_ts = 0;
unsigned int    u_sf_mode = 0;                         // 1=record, 2=replay
unsigned int    u_sf_record = 0;                       // record mode
unsigned int    u_sf_replay = 0;                       // replay mode
char            u_sf_filename[1024] = "";              // subframes file path
unsigned int    u_sf_max = DEF_NB_SF;                  // max number of recorded subframes
unsigned int    u_sf_loops = DEF_SF_NB_LOOP;           // number of loops in replay mode
unsigned int    u_sf_read_delay = DEF_SF_DELAY_READ;   // read delay in replay mode
unsigned int    u_sf_write_delay = DEF_SF_DELAY_WRITE; // write delay in replay mode

char config_opt_sf_file[] = CONFIG_OPT_SF_FILE;
char config_def_sf_file[] = DEF_SF_FILE;
char config_hlp_sf_file[] = CONFIG_HLP_SF_FILE;
char config_opt_sf_rec[] = CONFIG_OPT_SF_REC;
char config_hlp_sf_rec[] = CONFIG_HLP_SF_REC;
char config_opt_sf_rep[] = CONFIG_OPT_SF_REP;
char config_hlp_sf_rep[] = CONFIG_HLP_SF_REP;
char config_opt_sf_max[] = CONFIG_OPT_SF_MAX;
char config_hlp_sf_max[] = CONFIG_HLP_SF_MAX;
char config_opt_sf_loops[] = CONFIG_OPT_SF_LOOPS;
char config_hlp_sf_loops[] = CONFIG_HLP_SF_LOOPS;
char config_opt_sf_rdelay[] = CONFIG_OPT_SF_RDELAY;
char config_hlp_sf_rdelay[] = CONFIG_HLP_SF_RDELAY;
char config_opt_sf_wdelay[] = CONFIG_OPT_SF_WDELAY;
char config_hlp_sf_wdelay[] = CONFIG_HLP_SF_WDELAY;

#endif

/*! \brief Called to start the USRP transceiver. Return 0 if OK, < 0 if error
    @param device pointer to the device structure specific to the RF hardware target
*/
static int trx_usrp_start(openair0_device *device) {
#if defined(USRP_REC_PLAY)

  if (u_sf_mode != 2) { // not replay mode
#endif
    uhd::set_thread_priority_safe(1.0);
    usrp_state_t *s = (usrp_state_t *)device->priv;
    // setup GPIO for TDD, GPIO(4) = ATR_RX
    //set data direction register (DDR) to output
    s->usrp->set_gpio_attr("FP0", "DDR", 0x7f, 0x7f);
    //set control register to ATR
    s->usrp->set_gpio_attr("FP0", "CTRL", 0x7f,0x7f);
    //set ATR register
    s->usrp->set_gpio_attr("FP0", "ATR_RX", (1<<4)|(1<<6), 0x7f);
    // init recv and send streaming
    uhd::stream_cmd_t cmd(uhd::stream_cmd_t::STREAM_MODE_START_CONTINUOUS);
    LOG_I(HW,"Time in secs now: %llu \n", s->usrp->get_time_now().to_ticks(s->sample_rate));
    LOG_I(HW,"Time in secs last pps: %llu \n", s->usrp->get_time_last_pps().to_ticks(s->sample_rate));

    if (s->use_gps == 1 || device->openair0_cfg[0].time_source == external) {
      s->wait_for_first_pps = 1;
      cmd.time_spec = s->usrp->get_time_last_pps() + uhd::time_spec_t(1.0);
    } else {
      s->wait_for_first_pps = 0;
      cmd.time_spec = s->usrp->get_time_now() + uhd::time_spec_t(0.005);
    }

    cmd.stream_now = false; // start at constant delay
    s->rx_stream->issue_stream_cmd(cmd);
    /*s->tx_md.time_spec = cmd.time_spec + uhd::time_spec_t(1-(double)s->tx_forward_nsamps/s->sample_rate);
    s->tx_md.has_time_spec = true;
    s->tx_md.start_of_burst = true;
    s->tx_md.end_of_burst = false;*/
    s->rx_count = 0;
    s->tx_count = 0;
    s->rx_timestamp = 0;
#if defined(USRP_REC_PLAY)
  }

#endif
  return 0;
}
/*! \brief Terminate operation of the USRP transceiver -- free all associated resources
 * \param device the hardware to use
 */
static void trx_usrp_end(openair0_device *device) {
#if defined(USRP_REC_PLAY) // For some ugly reason, this can be called several times...
  static int done = 0;

  if (done == 1) return;

  done = 1;


  if (u_sf_mode != 2) { // not subframes replay
#endif
    usrp_state_t *s = (usrp_state_t *)device->priv;
    s->rx_stream->issue_stream_cmd(uhd::stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS);
    //send a mini EOB packet
    s->tx_md.end_of_burst = true;
    s->tx_stream->send("", 0, s->tx_md);
    s->tx_md.end_of_burst = false;
    sleep(1);
#if defined(USRP_REC_PLAY)
  }

#endif
#if defined(USRP_REC_PLAY)

  if (u_sf_mode == 1) { // subframes store
    pFile = fopen (u_sf_filename,"wb+");

    if (pFile == NULL) {
      std::cerr << "Cannot open " << u_sf_filename << std::endl;
    } else {
      unsigned int i = 0;
      unsigned int modu = 0;

      if ((modu = nb_samples % 10) != 0) {
        nb_samples -= modu; // store entire number of frames
      }

      std::cerr << "Writing " << nb_samples << " subframes to " << u_sf_filename << " ..." << std::endl;

      for (i = 0; i < nb_samples; i++) {
        fwrite(ms_sample+i, sizeof(unsigned char), sizeof(iqrec_t), pFile);
      }

      fclose (pFile);
      std::cerr << "File " << u_sf_filename << " closed." << std::endl;
    }
  }

  if (u_sf_mode == 1) { // record
    if (ms_sample != NULL) {
      free((void *)ms_sample);
      ms_sample = NULL;
    }
  }

  if (u_sf_mode == 2) { // replay
    if (use_mmap) {
      if (ms_sample != MAP_FAILED) {
        munmap(ms_sample, sb.st_size);
        ms_sample = NULL;
      }

      if (mmapfd != 0) {
        close(mmapfd);
        mmapfd = 0;
      }
    } else {
      if (ms_sample != NULL) {
        free(ms_sample);
        ms_sample = NULL;
      }

      if (iqfd != 0) {
        close(iqfd);
        iqfd = 0;
      }
    }
  }

#endif
}

/*! \brief Called to send samples to the USRP RF target
      @param device pointer to the device structure specific to the RF hardware target
      @param timestamp The timestamp at which the first sample MUST be sent
      @param buff Buffer which holds the samples
      @param nsamps number of samples to be sent
      @param antenna_id index of the antenna if the device has multiple antennas
      @param flags flags must be set to TRUE if timestamp parameter needs to be applied
*/
static int trx_usrp_write(openair0_device *device, openair0_timestamp timestamp, void **buff, int nsamps, int cc, int flags) {
  int ret=0;
#if defined(USRP_REC_PLAY)

  if (u_sf_mode != 2) { // not replay mode
#endif
    usrp_state_t *s = (usrp_state_t *)device->priv;

    int nsamps2;  // aligned to upper 32 or 16 byte boundary

#if defined(__x86_64) || defined(__i386__)
  #ifdef __AVX2__
      nsamps2 = (nsamps+7)>>3;
      __m256i buff_tx[2][nsamps2];
  #else
    nsamps2 = (nsamps+3)>>2;
    __m128i buff_tx[2][nsamps2];
  #endif
#elif defined(__arm__)
    nsamps2 = (nsamps+3)>>2;
    int16x8_t buff_tx[2][nsamps2];
#else
    #error Unsupported CPU architecture, USRP device cannot be built
#endif

    // bring RX data into 12 LSBs for softmodem RX
    for (int i=0; i<cc; i++) {
      for (int j=0; j<nsamps2; j++) {
#if defined(__x86_64__) || defined(__i386__)
#ifdef __AVX2__
        buff_tx[i][j] = _mm256_slli_epi16(((__m256i *)buff[i])[j],4);
#else
        buff_tx[i][j] = _mm_slli_epi16(((__m128i *)buff[i])[j],4);
#endif
#elif defined(__arm__)
        buff_tx[i][j] = vshlq_n_s16(((int16x8_t *)buff[i])[j],4);
#endif
      }
    }

    boolean_t first_packet_state=false,last_packet_state=false;

    if (flags == 2) { // start of burst
      //      s->tx_md.start_of_burst = true;
      //      s->tx_md.end_of_burst = false;
      first_packet_state = true;
      last_packet_state  = false;
    } else if (flags == 3) { // end of burst
      //s->tx_md.start_of_burst = false;
      //s->tx_md.end_of_burst = true;
      first_packet_state = false;
      last_packet_state  = true;
    } else if (flags == 4) { // start and end
    //  s->tx_md.start_of_burst = true;
    //  s->tx_md.end_of_burst = true;
      first_packet_state = true;
      last_packet_state  = true;
    } else if (flags==1) { // middle of burst
    //  s->tx_md.start_of_burst = false;
    //  s->tx_md.end_of_burst = false;
      first_packet_state = false;
      last_packet_state  = false;
    }
    else if (flags==10) { // fail safe mode
     // s->tx_md.has_time_spec = false;
     // s->tx_md.start_of_burst = false;
     // s->tx_md.end_of_burst = true;
     first_packet_state = false;
     last_packet_state  = true;
    }

    s->tx_md.has_time_spec  = true;
    s->tx_md.start_of_burst = (s->tx_count==0) ? true : first_packet_state; 
    s->tx_md.end_of_burst   = last_packet_state;
    s->tx_md.time_spec      = uhd::time_spec_t::from_ticks(timestamp, s->sample_rate);

    s->tx_count++;

    if (cc>1) {
       std::vector<void *> buff_ptrs;

       for (int i=0; i<cc; i++)
         buff_ptrs.push_back(&(((int16_t*)buff_tx[i])[0]));

       ret = (int)s->tx_stream->send(buff_ptrs, nsamps, s->tx_md);
    } else ret = (int)s->tx_stream->send(&(((int16_t *)buff_tx[0])[0]), nsamps, s->tx_md);

     if (ret != nsamps) LOG_E(HW,"[xmit] tx samples %d != %d\n",ret,nsamps);
 
#if defined(USRP_REC_PLAY)
  } else {
    struct timespec req;
    req.tv_sec = 0;
    req.tv_nsec = u_sf_write_delay * 1000;
    nanosleep(&req, NULL);
    ret = nsamps;
  }

#endif
  return ret;
}

/*! \brief Receive samples from hardware.
 * Read \ref nsamps samples from each channel to buffers. buff[0] is the array for
 * the first channel. *ptimestamp is the time at which the first sample
 * was received.
 * \param device the hardware to use
 * \param[out] ptimestamp the time at which the first sample was received.
 * \param[out] buff An array of pointers to buffers for received samples. The buffers must be large enough to hold the number of samples \ref nsamps.
 * \param nsamps Number of samples. One sample is 2 byte I + 2 byte Q => 4 byte.
 * \param antenna_id Index of antenna for which to receive samples
 * \returns the number of sample read
*/
static int trx_usrp_read(openair0_device *device, openair0_timestamp *ptimestamp, void **buff, int nsamps, int cc) {
  usrp_state_t *s = (usrp_state_t *)device->priv;
  int samples_received=0;
  int nsamps2;  // aligned to upper 32 or 16 byte boundary
#if defined(USRP_REC_PLAY)

  if (u_sf_mode != 2) { // not replay mode
#endif
#if defined(__x86_64) || defined(__i386__)
#ifdef __AVX2__
    nsamps2 = (nsamps+7)>>3;
    __m256i buff_tmp[2][nsamps2];
#else
    nsamps2 = (nsamps+3)>>2;
    __m128i buff_tmp[2][nsamps2];
#endif
#elif defined(__arm__)
    nsamps2 = (nsamps+3)>>2;
    int16x8_t buff_tmp[2][nsamps2];
#endif

    if (device->type == USRP_B200_DEV) {
      if (cc>1) {
        // receive multiple channels (e.g. RF A and RF B)
        std::vector<void *> buff_ptrs;

        for (int i=0; i<cc; i++) buff_ptrs.push_back(buff_tmp[i]);

        samples_received = s->rx_stream->recv(buff_ptrs, nsamps, s->rx_md);
      } else {
        // receive a single channel (e.g. from connector RF A)
        samples_received=0;

        while (samples_received != nsamps) {
          samples_received += s->rx_stream->recv(buff_tmp[0]+samples_received,
                                                 nsamps-samples_received, s->rx_md);

          if  ((s->wait_for_first_pps == 0) && (s->rx_md.error_code!=uhd::rx_metadata_t::ERROR_CODE_NONE))
            break;

          if ((s->wait_for_first_pps == 1) && (samples_received != nsamps)) {
            printf("sleep...\n"); //usleep(100);
          }
        }

        if (samples_received == nsamps) s->wait_for_first_pps=0;
      }

      // bring RX data into 12 LSBs for softmodem RX
      for (int i=0; i<cc; i++) {
        for (int j=0; j<nsamps2; j++) {
#if defined(__x86_64__) || defined(__i386__)
#ifdef __AVX2__
	  // FK: in some cases the buffer might not be 32 byte aligned, so we cannot use avx2

	  if ((((uintptr_t) buff[i])&0x1F)==0) {
	    ((__m256i *)buff[i])[j] = _mm256_srai_epi16(buff_tmp[i][j],4);
	  }
	  else {
	    ((__m128i *)buff[i])[2*j] = _mm_srai_epi16(((__m128i*)buff_tmp[i])[2*j],4);
	    ((__m128i *)buff[i])[2*j+1] = _mm_srai_epi16(((__m128i*)buff_tmp[i])[2*j+1],4);
	  }
#else
          ((__m128i *)buff[i])[j] = _mm_srai_epi16(buff_tmp[i][j],4);
#endif
#elif defined(__arm__)
          ((int16x8_t *)buff[i])[j] = vshrq_n_s16(buff_tmp[i][j],4);
#endif
        }
      }
    } else if (device->type == USRP_X300_DEV) {
      if (cc>1) {
        // receive multiple channels (e.g. RF A and RF B)
        std::vector<void *> buff_ptrs;

        for (int i=0; i<cc; i++) buff_ptrs.push_back(buff[i]);

        samples_received = s->rx_stream->recv(buff_ptrs, nsamps, s->rx_md,1.0);
      } else {
        // receive a single channel (e.g. from connector RF A)
        samples_received = s->rx_stream->recv(buff[0], nsamps, s->rx_md,1.0);
      }
    }

    if (samples_received < nsamps)
      LOG_E(HW,"[recv] received %d samples out of %d\n",samples_received,nsamps);

    if ( s->rx_md.error_code != uhd::rx_metadata_t::ERROR_CODE_NONE)
      LOG_E(HW, "%s\n", s->rx_md.to_pp_string(true).c_str());

    s->rx_count += nsamps;
    s->rx_timestamp = s->rx_md.time_spec.to_ticks(s->sample_rate);
    *ptimestamp = s->rx_timestamp;
#if defined (USRP_REC_PLAY)
  }

#endif
#if defined(USRP_REC_PLAY)

  if (u_sf_mode == 1) { // record mode
    // Copy subframes to memory (later dump on a file)
    if (nb_samples < u_sf_max) {
      (ms_sample+nb_samples)->header = BELL_LABS_IQ_HEADER;
      (ms_sample+nb_samples)->ts = *ptimestamp;
      memcpy((ms_sample+nb_samples)->samples, buff[0], nsamps*4);
      nb_samples++;
    }
  } else if (u_sf_mode == 2) { // replay mode
    if (cur_samples == nb_samples) {
      cur_samples = 0;
      wrap_count++;

      if (wrap_count == u_sf_loops) {
        std::cerr << "USRP device terminating subframes replay mode after " << u_sf_loops << " loops." << std::endl;
        return 0; // should make calling process exit
      }

      wrap_ts = wrap_count * (nb_samples * (((int)(device->openair0_cfg[0].sample_rate)) / 1000));

      if (!use_mmap) {
        if (lseek(iqfd, 0, SEEK_SET) == 0) {
          std::cerr << "Seeking at the beginning of IQ file" << std::endl;
        } else {
          std::cerr << "Problem seeking at the beginning of IQ file" << std::endl;
        }
      }
    }

    if (use_mmap) {
      if (cur_samples < nb_samples) {
        *ptimestamp = (ms_sample[0].ts + (cur_samples * (((int)(device->openair0_cfg[0].sample_rate)) / 1000))) + wrap_ts;

        if (cur_samples == 0) {
          std::cerr << "starting subframes file with wrap_count=" << wrap_count << " wrap_ts=" << wrap_ts
                    << " ts=" << *ptimestamp << std::endl;
        }

        memcpy(buff[0], &ms_sample[cur_samples].samples[0], nsamps*4);
        cur_samples++;
      }
    } else {
      // read sample from file
      if (read(iqfd, ms_sample, sizeof(iqrec_t)) != sizeof(iqrec_t)) {
        std::cerr << "pb reading iqfile at index " << sizeof(iqrec_t)*cur_samples << std::endl;
        close(iqfd);
        free(ms_sample);
        ms_sample = NULL;
        iqfd = 0;
        exit(-1);
      }

      if (cur_samples < nb_samples) {
        static int64_t ts0 = 0;

        if ((cur_samples == 0) && (wrap_count == 0)) {
          ts0 = ms_sample->ts;
        }

        *ptimestamp = ts0 + (cur_samples * (((int)(device->openair0_cfg[0].sample_rate)) / 1000)) + wrap_ts;

        if (cur_samples == 0) {
          std::cerr << "starting subframes file with wrap_count=" << wrap_count << " wrap_ts=" << wrap_ts
                    << " ts=" << *ptimestamp << std::endl;
        }

        memcpy(buff[0], &ms_sample->samples[0], nsamps*4);
        cur_samples++;
        // Prepare for next read
        off_t where = lseek(iqfd, cur_samples * sizeof(iqrec_t), SEEK_SET);
      }
    }

    struct timespec req;

    req.tv_sec = 0;

    req.tv_nsec = u_sf_read_delay * 1000;

    nanosleep(&req, NULL);

    return nsamps;
  }

#endif
  return samples_received;
}

/*! \brief Compares two variables within precision
 * \param a first variable
 * \param b second variable
*/
static bool is_equal(double a, double b) {
  return std::fabs(a-b) < std::numeric_limits<double>::epsilon();
}

void *freq_thread(void *arg) {
  openair0_device *device=(openair0_device *)arg;
  usrp_state_t *s = (usrp_state_t *)device->priv;
  s->usrp->set_tx_freq(device->openair0_cfg[0].tx_freq[0]);
  s->usrp->set_rx_freq(device->openair0_cfg[0].rx_freq[0]);
  return NULL;
}
/*! \brief Set frequencies (TX/RX). Spawns a thread to handle the frequency change to not block the calling thread
 * \param device the hardware to use
 * \param openair0_cfg RF frontend parameters set by application
 * \param dummy dummy variable not used
 * \returns 0 in success
 */
int trx_usrp_set_freq(openair0_device *device, openair0_config_t *openair0_cfg, int dont_block) {
  usrp_state_t *s = (usrp_state_t *)device->priv;
  pthread_t f_thread;
  printf("Setting USRP TX Freq %f, RX Freq %f\n",openair0_cfg[0].tx_freq[0],openair0_cfg[0].rx_freq[0]);

  // spawn a thread to handle the frequency change to not block the calling thread
  if (dont_block == 1)
    pthread_create(&f_thread,NULL,freq_thread,(void *)device);
  else {
    s->usrp->set_tx_freq(device->openair0_cfg[0].tx_freq[0]);
    s->usrp->set_rx_freq(device->openair0_cfg[0].rx_freq[0]);
  }

  return(0);
}

/*! \brief Set RX frequencies
 * \param device the hardware to use
 * \param openair0_cfg RF frontend parameters set by application
 * \returns 0 in success
 */
int openair0_set_rx_frequencies(openair0_device *device, openair0_config_t *openair0_cfg) {
  usrp_state_t *s = (usrp_state_t *)device->priv;
  uhd::tune_request_t rx_tune_req(openair0_cfg[0].rx_freq[0]);
  rx_tune_req.rf_freq_policy = uhd::tune_request_t::POLICY_MANUAL;
  rx_tune_req.rf_freq = openair0_cfg[0].rx_freq[0];
  s->usrp->set_rx_freq(rx_tune_req);
  return(0);
}

/*! \brief Set Gains (TX/RX)
 * \param device the hardware to use
 * \param openair0_cfg RF frontend parameters set by application
 * \returns 0 in success
 */
int trx_usrp_set_gains(openair0_device *device,
                       openair0_config_t *openair0_cfg) {
  usrp_state_t *s = (usrp_state_t *)device->priv;
  ::uhd::gain_range_t gain_range_tx = s->usrp->get_tx_gain_range(0);
  s->usrp->set_tx_gain(gain_range_tx.stop()-openair0_cfg[0].tx_gain[0]);
  ::uhd::gain_range_t gain_range = s->usrp->get_rx_gain_range(0);

  // limit to maximum gain
  if (openair0_cfg[0].rx_gain[0]-openair0_cfg[0].rx_gain_offset[0] > gain_range.stop()) {
    LOG_E(HW,"RX Gain 0 too high, reduce by %f dB\n",
          openair0_cfg[0].rx_gain[0]-openair0_cfg[0].rx_gain_offset[0] - gain_range.stop());
    exit(-1);
  }

  s->usrp->set_rx_gain(openair0_cfg[0].rx_gain[0]-openair0_cfg[0].rx_gain_offset[0]);
  LOG_I(HW,"Setting USRP RX gain to %f (rx_gain %f,gain_range.stop() %f)\n",
        openair0_cfg[0].rx_gain[0]-openair0_cfg[0].rx_gain_offset[0],
        openair0_cfg[0].rx_gain[0],gain_range.stop());
  return(0);
}

/*! \brief Stop USRP
 * \param card refers to the hardware index to use
 */
int trx_usrp_stop(openair0_device *device) {
  return(0);
}

/*! \brief USRPB210 RX calibration table */
rx_gain_calib_table_t calib_table_b210[] = {
  {3500000000.0,44.0},
  {2660000000.0,49.0},
  {2300000000.0,50.0},
  {1880000000.0,53.0},
  {816000000.0,58.0},
  {-1,0}
};

/*! \brief USRPB210 RX calibration table */
rx_gain_calib_table_t calib_table_b210_38[] = {
  {3500000000.0,44.0},
  {2660000000.0,49.8},
  {2300000000.0,51.0},
  {1880000000.0,53.0},
  {816000000.0,57.0},
  {-1,0}
};

/*! \brief USRPx310 RX calibration table */
rx_gain_calib_table_t calib_table_x310[] = {
  {3500000000.0,77.0},
  {2660000000.0,81.0},
  {2300000000.0,81.0},
  {1880000000.0,82.0},
  {816000000.0,85.0},
  {-1,0}
};

/*! \brief Set RX gain offset
 * \param openair0_cfg RF frontend parameters set by application
 * \param chain_index RF chain to apply settings to
 * \returns 0 in success
 */
void set_rx_gain_offset(openair0_config_t *openair0_cfg, int chain_index,int bw_gain_adjust) {
  int i=0;
  // loop through calibration table to find best adjustment factor for RX frequency
  double min_diff = 6e9,diff,gain_adj=0.0;

  if (bw_gain_adjust==1) {
    switch ((int)openair0_cfg[0].sample_rate) {

      case 46080000:
        break;

      case 30720000:
        break;

      case 23040000:
        gain_adj=1.25;
        break;

      case 15360000:
        gain_adj=3.0;
        break;

      case 7680000:
        gain_adj=6.0;
        break;

      case 3840000:
        gain_adj=9.0;
        break;

      case 1920000:
        gain_adj=12.0;
        break;

      default:
        LOG_E(HW,"unknown sampling rate %d\n",(int)openair0_cfg[0].sample_rate);
        //exit(-1);
        break;
    }
  }

  while (openair0_cfg->rx_gain_calib_table[i].freq>0) {
    diff = fabs(openair0_cfg->rx_freq[chain_index] - openair0_cfg->rx_gain_calib_table[i].freq);
    LOG_I(HW,"cal %d: freq %f, offset %f, diff %f\n",
          i,
          openair0_cfg->rx_gain_calib_table[i].freq,
          openair0_cfg->rx_gain_calib_table[i].offset,diff);

    if (min_diff > diff) {
      min_diff = diff;
      openair0_cfg->rx_gain_offset[chain_index] = openair0_cfg->rx_gain_calib_table[i].offset+gain_adj;
    }

    i++;
  }
}

/*! \brief print the USRP statistics
* \param device the hardware to use
* \returns  0 on success
*/
int trx_usrp_get_stats(openair0_device *device) {
  return(0);
}

/*! \brief Reset the USRP statistics
 * \param device the hardware to use
 * \returns  0 on success
 */
int trx_usrp_reset_stats(openair0_device *device) {
  return(0);
}

/*! \brief Set uhd priority
 */
static void uhd_set_thread_priority(void) {
  uhd::set_thread_priority_safe(1.0);
}

#if defined(USRP_REC_PLAY)
extern "C" {
  /*! \brief Initializer for USRP record/playback config
   * \param parameter array description
   * \returns  0 on success
   */
  int trx_usrp_recplay_config_init(paramdef_t *usrp_recplay_params) {
    // --subframes-file
    memcpy(usrp_recplay_params[0].optname, config_opt_sf_file, strlen(config_opt_sf_file));
    usrp_recplay_params[0].helpstr = config_hlp_sf_file;
    usrp_recplay_params[0].paramflags=PARAMFLAG_NOFREE;
    usrp_recplay_params[0].strptr=(char **)&u_sf_filename;
    usrp_recplay_params[0].defstrval = NULL;
    usrp_recplay_params[0].type=TYPE_STRING;
    usrp_recplay_params[0].numelt=sizeof(u_sf_filename);
    // --subframes-record
    memcpy(usrp_recplay_params[1].optname, config_opt_sf_rec, strlen(config_opt_sf_rec));
    usrp_recplay_params[1].helpstr = config_hlp_sf_rec;
    usrp_recplay_params[1].paramflags=PARAMFLAG_BOOL;
    usrp_recplay_params[1].uptr=&u_sf_record;
    usrp_recplay_params[1].defuintval=0;
    usrp_recplay_params[1].type=TYPE_UINT;
    usrp_recplay_params[1].numelt=0;
    // --subframes-replay
    memcpy(usrp_recplay_params[2].optname, config_opt_sf_rep, strlen(config_opt_sf_rep));
    usrp_recplay_params[2].helpstr = config_hlp_sf_rep;
    usrp_recplay_params[2].paramflags=PARAMFLAG_BOOL;
    usrp_recplay_params[2].uptr=&u_sf_replay;
    usrp_recplay_params[2].defuintval=0;
    usrp_recplay_params[2].type=TYPE_UINT;
    usrp_recplay_params[2].numelt=0;
    // --subframes-max
    memcpy(usrp_recplay_params[3].optname, config_opt_sf_max, strlen(config_opt_sf_max));
    usrp_recplay_params[3].helpstr = config_hlp_sf_max;
    usrp_recplay_params[3].paramflags=0;
    usrp_recplay_params[3].uptr=&u_sf_max;
    usrp_recplay_params[3].defuintval=DEF_NB_SF;
    usrp_recplay_params[3].type=TYPE_UINT;
    usrp_recplay_params[3].numelt=0;
    // --subframes-loops
    memcpy(usrp_recplay_params[4].optname, config_opt_sf_loops, strlen(config_opt_sf_loops));
    usrp_recplay_params[4].helpstr = config_hlp_sf_loops;
    usrp_recplay_params[4].paramflags=0;
    usrp_recplay_params[4].uptr=&u_sf_loops;
    usrp_recplay_params[4].defuintval=DEF_SF_NB_LOOP;
    usrp_recplay_params[4].type=TYPE_UINT;
    usrp_recplay_params[4].numelt=0;
    // --subframes-read-delay
    memcpy(usrp_recplay_params[5].optname, config_opt_sf_rdelay, strlen(config_opt_sf_rdelay));
    usrp_recplay_params[5].helpstr = config_hlp_sf_rdelay;
    usrp_recplay_params[5].paramflags=0;
    usrp_recplay_params[5].uptr=&u_sf_read_delay;
    usrp_recplay_params[5].defuintval=DEF_SF_DELAY_READ;
    usrp_recplay_params[5].type=TYPE_UINT;
    usrp_recplay_params[5].numelt=0;
    // --subframes-write-delay
    memcpy(usrp_recplay_params[6].optname, config_opt_sf_wdelay, strlen(config_opt_sf_wdelay));
    usrp_recplay_params[6].helpstr = config_hlp_sf_wdelay;
    usrp_recplay_params[6].paramflags=0;
    usrp_recplay_params[6].uptr=&u_sf_write_delay;
    usrp_recplay_params[6].defuintval=DEF_SF_DELAY_WRITE;
    usrp_recplay_params[6].type=TYPE_UINT;
    usrp_recplay_params[6].numelt=0;
    return 0; // always ok
  }
}
#endif

extern "C" {
  int device_init(openair0_device *device, openair0_config_t *openair0_cfg) {
#if defined(USRP_REC_PLAY)
    paramdef_t usrp_recplay_params[7];
    struct sysinfo systeminfo;
    // to check
    static int done = 0;

    if (done == 1) {
      return 0;
    } // prevent from multiple init

    done = 1;
    // end to check
    // Use mmap for IQ files for systems with less than 6GB total RAM
    sysinfo(&systeminfo);

    if (systeminfo.totalram < 6144000000) {
      use_mmap = 0;
    }

    memset(usrp_recplay_params, 0, 7*sizeof(paramdef_t));
    memset(&u_sf_filename[0], 0, 1024);

    if (trx_usrp_recplay_config_init(usrp_recplay_params) != 0) {
      std::cerr << "USRP device record/replay mode configuration error exiting" << std::endl;
      return -1;
    }

    config_process_cmdline(usrp_recplay_params,sizeof(usrp_recplay_params)/sizeof(paramdef_t),NULL);

    if (strlen(u_sf_filename) == 0) {
      (void) strcpy(u_sf_filename, DEF_SF_FILE);
    }

    if (u_sf_replay == 1) u_sf_mode = 2;

    if (u_sf_record == 1) u_sf_mode = 1;

    if (u_sf_mode == 2) {
      // Replay subframes from from file
      int bw_gain_adjust=0;
      device->openair0_cfg = openair0_cfg;
      device->type = USRP_B200_DEV;
      openair0_cfg[0].rx_gain_calib_table = calib_table_b210_38;
      bw_gain_adjust=1;
      openair0_cfg[0].tx_sample_advance     = 80;
      openair0_cfg[0].tx_bw                 = 20e6;
      openair0_cfg[0].rx_bw                 = 20e6;
      openair0_cfg[0].iq_txshift = 4;//shift
      openair0_cfg[0].iq_rxrescale = 15;//rescale iqs
      set_rx_gain_offset(&openair0_cfg[0],0,bw_gain_adjust);
      device->priv = NULL;
      device->trx_start_func = trx_usrp_start;
      device->trx_write_func = trx_usrp_write;
      device->trx_read_func  = trx_usrp_read;
      device->trx_get_stats_func = trx_usrp_get_stats;
      device->trx_reset_stats_func = trx_usrp_reset_stats;
      device->trx_end_func   = trx_usrp_end;
      device->trx_stop_func  = trx_usrp_stop;
      device->trx_set_freq_func = trx_usrp_set_freq;
      device->trx_set_gains_func   = trx_usrp_set_gains;
      device->openair0_cfg = openair0_cfg;
      device->uhd_set_thread_priority = uhd_set_thread_priority;
      std::cerr << "USRP device initialized in subframes replay mode for " << u_sf_loops << " loops. Use mmap="
                << use_mmap << std::endl;
    } else {
#endif
      usrp_state_t *s = (usrp_state_t *)calloc(sizeof(usrp_state_t),1);

      // Initialize USRP device
      device->openair0_cfg = openair0_cfg;
      int vers=0,subvers=0,subsubvers=0;
      int bw_gain_adjust=0;
#if defined(USRP_REC_PLAY)

      if (u_sf_mode == 1) {
        std::cerr << "USRP device initialized in subframes record mode" << std::endl;
      }

#endif
      sscanf(uhd::get_version_string().c_str(),"%d.%d.%d",&vers,&subvers,&subsubvers);
      LOG_I(HW,"UHD version %s (%d.%d.%d)\n",
            uhd::get_version_string().c_str(),vers,subvers,subsubvers);

      std::string args;
      if (openair0_cfg[0].sdr_addrs == NULL) {
        args = "type=b200";
      } else {
        args = openair0_cfg[0].sdr_addrs;
	LOG_I(HW,"Checking for USRP with args %s\n",openair0_cfg[0].sdr_addrs);
      }

      uhd::device_addrs_t device_adds = uhd::device::find(args);

      if (device_adds.size() == 0) {
        LOG_E(HW,"No USRP Device Found.\n ");
        free(s);
        return -1;
      } else if (device_adds.size() > 1) {
        LOG_E(HW,"More than one USRP Device Found. Please specify device more precisely in config file.\n");
	free(s);
	return -1;
      }

      LOG_I(HW,"Found USRP %s\n", device_adds[0].get("type").c_str());
      double usrp_master_clock;

      if (device_adds[0].get("type") == "b200") {
        printf("Found USRP b200\n");
        device->type = USRP_B200_DEV;
        usrp_master_clock = 30.72e6;
        args += boost::str(boost::format(",master_clock_rate=%f") % usrp_master_clock);
        args += ",num_send_frames=256,num_recv_frames=256, send_frame_size=7680, recv_frame_size=7680" ;
      }
      if (device_adds[0].get("type") == "n3xx") {
        printf("Found USRP n300\n");
        device->type=USRP_X300_DEV; //treat it as X300 for now
        usrp_master_clock = 122.88e6;
        args += boost::str(boost::format(",master_clock_rate=%f") % usrp_master_clock);
        //args += ", send_buff_size=33554432";
      }

      if (device_adds[0].get("type") == "x300") {
        printf("Found USRP x300\n");
        device->type=USRP_X300_DEV;
        usrp_master_clock = 184.32e6;
        args += boost::str(boost::format(",master_clock_rate=%f") % usrp_master_clock);
        // USRP recommended: https://files.ettus.com/manual/page_usrp_x3x0_config.html
        if ( 0 != system("sysctl -w net.core.rmem_max=33554432 net.core.wmem_max=33554432") )
        	LOG_W(HW,"Can't set kernel parameters for X3xx\n");
      }

      s->usrp = uhd::usrp::multi_usrp::make(args);
      device->priv = s;

      if (args.find("clock_source")==std::string::npos) {
	LOG_I(HW, "Using clock_source == '%d'\n", openair0_cfg[0].clock_source);

	if (openair0_cfg[0].clock_source == internal) {
	  //in UHD 3.14 we could use
	  //s->usrp->set_sync_source("clock_source=internal","time_source=internal");
	  s->usrp->set_time_source("internal");
	  s->usrp->set_clock_source("internal");
	}
	else if (openair0_cfg[0].clock_source == external ) {
	  //s->usrp->set_sync_source("clock_source=external","time_source=external");
	  s->usrp->set_time_source("external");
	  s->usrp->set_clock_source("external");
	}
	else if (openair0_cfg[0].clock_source==gpsdo) {
	  s->usrp->set_clock_source("gpsdo");
	  s->usrp->set_time_source("gpsdo");
	}
      } else {
	LOG_W(HW, "clock_source already specified in device arguments! Ignoring command line parameter\n");
      }

      if (s->usrp->get_clock_source(0) == "gpsdo") {
	s->use_gps = 1;
        if (sync_to_gps(device)==EXIT_SUCCESS) {
          LOG_I(HW,"USRP synced with GPS!\n");
	}
	else {
          LOG_I(HW,"USRP fails to sync with GPS. Exiting.\n");
          exit(EXIT_FAILURE);
        }
      }	else if (s->usrp->get_clock_source(0) == "external") {
	if (check_ref_locked(s,0)) {
	  LOG_I(HW,"USRP locked to external reference!\n");
	} else {
	  LOG_I(HW,"Failed to lock to external reference. Exiting.\n");
	  exit(EXIT_FAILURE);
	}
      }
      
      
      if (device->type==USRP_X300_DEV) {
        openair0_cfg[0].rx_gain_calib_table = calib_table_x310;
#if defined(USRP_REC_PLAY)
        std::cerr << "-- Using calibration table: calib_table_x310" << std::endl; // Bell Labs info
#endif
        LOG_I(HW,"%s() sample_rate:%u\n", __FUNCTION__, (int)openair0_cfg[0].sample_rate);

        switch ((int)openair0_cfg[0].sample_rate) {
          case 122880000:
            // from usrp_time_offset
            //openair0_cfg[0].samples_per_packet    = 2048;
            openair0_cfg[0].tx_sample_advance     = 15; //to be checked
            openair0_cfg[0].tx_bw                 = 80e6;
            openair0_cfg[0].rx_bw                 = 80e6;
            break;

          case 92160000:
            // from usrp_time_offset
            //openair0_cfg[0].samples_per_packet    = 2048;
            openair0_cfg[0].tx_sample_advance     = 15; //to be checked
            openair0_cfg[0].tx_bw                 = 80e6;
            openair0_cfg[0].rx_bw                 = 80e6;
            break;
            
          case 61440000:
            // from usrp_time_offset
            //openair0_cfg[0].samples_per_packet    = 2048;
            openair0_cfg[0].tx_sample_advance     = 15;
            openair0_cfg[0].tx_bw                 = 40e6;
            openair0_cfg[0].rx_bw                 = 40e6;
            break;

 	  case 46080000:
            //openair0_cfg[0].samples_per_packet    = 1024;
            openair0_cfg[0].tx_sample_advance     = 115;
            openair0_cfg[0].tx_bw                 = 40e6;
            openair0_cfg[0].rx_bw                 = 40e6;
            break;

          case 30720000:
            // from usrp_time_offset
            //openair0_cfg[0].samples_per_packet    = 2048;
            openair0_cfg[0].tx_sample_advance     = 15;
            openair0_cfg[0].tx_bw                 = 20e6;
            openair0_cfg[0].rx_bw                 = 20e6;
            break;

          case 15360000:
            //openair0_cfg[0].samples_per_packet    = 2048;
            openair0_cfg[0].tx_sample_advance     = 45;
            openair0_cfg[0].tx_bw                 = 10e6;
            openair0_cfg[0].rx_bw                 = 10e6;
            break;

          case 7680000:
            //openair0_cfg[0].samples_per_packet    = 2048;
            openair0_cfg[0].tx_sample_advance     = 50;
            openair0_cfg[0].tx_bw                 = 5e6;
            openair0_cfg[0].rx_bw                 = 5e6;
            break;

          case 1920000:
            //openair0_cfg[0].samples_per_packet    = 2048;
            openair0_cfg[0].tx_sample_advance     = 50;
            openair0_cfg[0].tx_bw                 = 1.25e6;
            openair0_cfg[0].rx_bw                 = 1.25e6;
            break;

          default:
            LOG_E(HW,"Error: unknown sampling rate %f\n",openair0_cfg[0].sample_rate);
            exit(-1);
            break;
        }
      }

      if (device->type == USRP_B200_DEV) {
        if ((vers == 3) && (subvers == 9) && (subsubvers>=2)) {
          openair0_cfg[0].rx_gain_calib_table = calib_table_b210;
          bw_gain_adjust=0;
#if defined(USRP_REC_PLAY)
          std::cerr << "-- Using calibration table: calib_table_b210" << std::endl; // Bell Labs info
#endif
        } else {
          openair0_cfg[0].rx_gain_calib_table = calib_table_b210_38;
          bw_gain_adjust=1;
#if defined(USRP_REC_PLAY)
          std::cerr << "-- Using calibration table: calib_table_b210_38" << std::endl; // Bell Labs info
#endif
        }

        switch ((int)openair0_cfg[0].sample_rate) {
	case 46080000:
            s->usrp->set_master_clock_rate(46.08e6);
            //openair0_cfg[0].samples_per_packet    = 1024;
            openair0_cfg[0].tx_sample_advance     = 115;
            openair0_cfg[0].tx_bw                 = 40e6;
            openair0_cfg[0].rx_bw                 = 40e6;
            break;

	case 30720000:
            s->usrp->set_master_clock_rate(30.72e6);
            //openair0_cfg[0].samples_per_packet    = 1024;
            openair0_cfg[0].tx_sample_advance     = 115;
            openair0_cfg[0].tx_bw                 = 20e6;
            openair0_cfg[0].rx_bw                 = 20e6;
            break;

          case 23040000:
            s->usrp->set_master_clock_rate(23.04e6); //to be checked
            //openair0_cfg[0].samples_per_packet    = 1024;
            openair0_cfg[0].tx_sample_advance     = 113;
            openair0_cfg[0].tx_bw                 = 20e6;
            openair0_cfg[0].rx_bw                 = 20e6;
            break;

          case 15360000:
            s->usrp->set_master_clock_rate(30.72e06);
            //openair0_cfg[0].samples_per_packet    = 1024;
            openair0_cfg[0].tx_sample_advance     = 103;
            openair0_cfg[0].tx_bw                 = 20e6;
            openair0_cfg[0].rx_bw                 = 20e6;
            break;

          case 7680000:
            s->usrp->set_master_clock_rate(30.72e6);
            //openair0_cfg[0].samples_per_packet    = 1024;
            openair0_cfg[0].tx_sample_advance     = 80;
            openair0_cfg[0].tx_bw                 = 20e6;
            openair0_cfg[0].rx_bw                 = 20e6;
            break;

          case 1920000:
            s->usrp->set_master_clock_rate(30.72e6);
            //openair0_cfg[0].samples_per_packet    = 1024;
            openair0_cfg[0].tx_sample_advance     = 40;
            openair0_cfg[0].tx_bw                 = 20e6;
            openair0_cfg[0].rx_bw                 = 20e6;
            break;

          default:
            LOG_E(HW,"Error: unknown sampling rate %f\n",openair0_cfg[0].sample_rate);
            exit(-1);
            break;
        }
      }

      /* device specific */
      //openair0_cfg[0].txlaunch_wait = 1;//manage when TX processing is triggered
      //openair0_cfg[0].txlaunch_wait_slotcount = 1; //manage when TX processing is triggered
      openair0_cfg[0].iq_txshift = 4;//shift
      openair0_cfg[0].iq_rxrescale = 15;//rescale iqs

      for(int i=0; i<((int) s->usrp->get_rx_num_channels()); i++) {
        if (i<openair0_cfg[0].rx_num_channels) {
          s->usrp->set_rx_rate(openair0_cfg[0].sample_rate,i);
          s->usrp->set_rx_freq(openair0_cfg[0].rx_freq[i],i);
          set_rx_gain_offset(&openair0_cfg[0],i,bw_gain_adjust);
          ::uhd::gain_range_t gain_range = s->usrp->get_rx_gain_range(i);
          // limit to maximum gain
          AssertFatal( openair0_cfg[0].rx_gain[i]-openair0_cfg[0].rx_gain_offset[i] <= gain_range.stop(),
                       "RX Gain too high, lower by %f dB\n",
                       openair0_cfg[0].rx_gain[i]-openair0_cfg[0].rx_gain_offset[i] - gain_range.stop());
          s->usrp->set_rx_gain(openair0_cfg[0].rx_gain[i]-openair0_cfg[0].rx_gain_offset[i],i);
          LOG_I(HW,"RX Gain %d %f (%f) => %f (max %f)\n",i,
                openair0_cfg[0].rx_gain[i],openair0_cfg[0].rx_gain_offset[i],
                openair0_cfg[0].rx_gain[i]-openair0_cfg[0].rx_gain_offset[i],gain_range.stop());
        }
      }

      LOG_D(HW, "usrp->get_tx_num_channels() == %zd\n", s->usrp->get_tx_num_channels());
      LOG_D(HW, "openair0_cfg[0].tx_num_channels == %d\n", openair0_cfg[0].tx_num_channels);

      for(int i=0; i<((int) s->usrp->get_tx_num_channels()); i++) {
        ::uhd::gain_range_t gain_range_tx = s->usrp->get_tx_gain_range(i);

        if (i<openair0_cfg[0].tx_num_channels) {
          s->usrp->set_tx_rate(openair0_cfg[0].sample_rate,i);
          s->usrp->set_tx_freq(openair0_cfg[0].tx_freq[i],i);
          s->usrp->set_tx_gain(gain_range_tx.stop()-openair0_cfg[0].tx_gain[i],i);
          LOG_I(HW,"USRP TX_GAIN:%3.2lf gain_range:%3.2lf tx_gain:%3.2lf\n", gain_range_tx.stop()-openair0_cfg[0].tx_gain[i], gain_range_tx.stop(), openair0_cfg[0].tx_gain[i]);
        }
      }

      //s->usrp->set_clock_source("external");
      //s->usrp->set_time_source("external");
      // display USRP settings
      LOG_I(HW,"Actual master clock: %fMHz...\n",s->usrp->get_master_clock_rate()/1e6);
      sleep(1);

      // create tx & rx streamer
      uhd::stream_args_t stream_args_rx("sc16", "sc16");
      int samples=openair0_cfg[0].sample_rate;
      int max=s->usrp->get_rx_stream(stream_args_rx)->get_max_num_samps();
      samples/=10000;
      LOG_I(HW,"RF board max packet size %u, size for 100µs jitter %d \n", max, samples);

      if ( samples < max ) {
        stream_args_rx.args["spp"] = str(boost::format("%d") % samples );
      }

      LOG_I(HW,"rx_max_num_samps %zu\n",
            s->usrp->get_rx_stream(stream_args_rx)->get_max_num_samps());

      for (int i = 0; i<openair0_cfg[0].rx_num_channels; i++)
        stream_args_rx.channels.push_back(i);

      s->rx_stream = s->usrp->get_rx_stream(stream_args_rx);
      uhd::stream_args_t stream_args_tx("sc16", "sc16");

      for (int i = 0; i<openair0_cfg[0].tx_num_channels; i++)
        stream_args_tx.channels.push_back(i);

      s->tx_stream = s->usrp->get_tx_stream(stream_args_tx);

      /* Setting TX/RX BW after streamers are created due to USRP calibration issue */
      for(int i=0; i<((int) s->usrp->get_tx_num_channels()) && i<openair0_cfg[0].tx_num_channels; i++)
        s->usrp->set_tx_bandwidth(openair0_cfg[0].tx_bw,i);

      for(int i=0; i<((int) s->usrp->get_rx_num_channels()) && i<openair0_cfg[0].rx_num_channels; i++)
        s->usrp->set_rx_bandwidth(openair0_cfg[0].rx_bw,i);

      for (int i=0; i<openair0_cfg[0].rx_num_channels; i++) {
        LOG_I(HW,"RX Channel %d\n",i);
        LOG_I(HW,"  Actual RX sample rate: %fMSps...\n",s->usrp->get_rx_rate(i)/1e6);
        LOG_I(HW,"  Actual RX frequency: %fGHz...\n", s->usrp->get_rx_freq(i)/1e9);
        LOG_I(HW,"  Actual RX gain: %f...\n", s->usrp->get_rx_gain(i));
        LOG_I(HW,"  Actual RX bandwidth: %fM...\n", s->usrp->get_rx_bandwidth(i)/1e6);
        LOG_I(HW,"  Actual RX antenna: %s...\n", s->usrp->get_rx_antenna(i).c_str());
      }

      for (int i=0; i<openair0_cfg[0].tx_num_channels; i++) {
        LOG_I(HW,"TX Channel %d\n",i);
        LOG_I(HW,"  Actual TX sample rate: %fMSps...\n", s->usrp->get_tx_rate(i)/1e6);
        LOG_I(HW,"  Actual TX frequency: %fGHz...\n", s->usrp->get_tx_freq(i)/1e9);
        LOG_I(HW,"  Actual TX gain: %f...\n", s->usrp->get_tx_gain(i));
        LOG_I(HW,"  Actual TX bandwidth: %fM...\n", s->usrp->get_tx_bandwidth(i)/1e6);
        LOG_I(HW,"  Actual TX antenna: %s...\n", s->usrp->get_tx_antenna(i).c_str());
	LOG_I(HW,"  Actual TX packet size: %lu\n",s->tx_stream->get_max_num_samps());
      }

      LOG_I(HW,"Device timestamp: %f...\n", s->usrp->get_time_now().get_real_secs());
      device->priv = s;
      device->trx_start_func = trx_usrp_start;
      device->trx_write_func = trx_usrp_write;
      device->trx_read_func  = trx_usrp_read;
      device->trx_get_stats_func = trx_usrp_get_stats;
      device->trx_reset_stats_func = trx_usrp_reset_stats;
      device->trx_end_func   = trx_usrp_end;
      device->trx_stop_func  = trx_usrp_stop;
      device->trx_set_freq_func = trx_usrp_set_freq;
      device->trx_set_gains_func   = trx_usrp_set_gains;
      device->openair0_cfg = openair0_cfg;
      device->uhd_set_thread_priority = uhd_set_thread_priority;
      s->sample_rate = openair0_cfg[0].sample_rate;

      // TODO:
      // init tx_forward_nsamps based usrp_time_offset ex
      if(is_equal(s->sample_rate, (double)30.72e6))
        s->tx_forward_nsamps  = 176;

      if(is_equal(s->sample_rate, (double)15.36e6))
        s->tx_forward_nsamps = 90;

      if(is_equal(s->sample_rate, (double)7.68e6))
        s->tx_forward_nsamps = 50;


#if defined(USRP_REC_PLAY)
    }

#endif
#if defined(USRP_REC_PLAY)

    if (u_sf_mode == 1) { // record mode
      ms_sample = (iqrec_t *) malloc(u_sf_max * sizeof(iqrec_t));

      if (ms_sample == NULL) {
        std::cerr<< "Memory allocation failed for subframe record or replay mode." << std::endl;
        exit(-1);
      }

      memset(ms_sample, 0, u_sf_max * BELL_LABS_IQ_BYTES_PER_SF);
    }

    if (u_sf_mode == 2) {
      if (use_mmap) {
        // use mmap
        mmapfd = open(u_sf_filename, O_RDONLY | O_LARGEFILE);

        if (mmapfd != 0) {
          fstat(mmapfd, &sb);
          std::cerr << "Loading subframes using mmap() from " << u_sf_filename << " size=" << (uint64_t)sb.st_size << " bytes ..." << std::endl;
          ms_sample = (iqrec_t *) mmap(NULL, sb.st_size, PROT_WRITE, MAP_PRIVATE, mmapfd, 0);

          if (ms_sample != MAP_FAILED) {
            nb_samples = (sb.st_size / sizeof(iqrec_t));
            int aligned = (((unsigned long)ms_sample & 31) == 0)? 1:0;
            std::cerr<< "Loaded "<< nb_samples << " subframes." << std::endl;

            if (aligned == 0) {
              std::cerr<< "mmap address is not 32 bytes aligned, exiting." << std::endl;
              close(mmapfd);
              exit(-1);
            }
          } else {
            std::cerr << "Cannot mmap file, exiting." << std::endl;
            close(mmapfd);
            exit(-1);
          }
        } else {
          std::cerr << "Cannot open " << u_sf_filename << " , exiting." << std::endl;
          exit(-1);
        }
      } else {
        iqfd = open(u_sf_filename, O_RDONLY | O_LARGEFILE);

        if (iqfd != 0) {
          fstat(iqfd, &sb);
          nb_samples = (sb.st_size / sizeof(iqrec_t));
          std::cerr << "Loading " << nb_samples << " subframes from " << u_sf_filename
                    << " size=" << (uint64_t)sb.st_size << " bytes ..." << std::endl;
          // allocate buffer for 1 sample at a time
          ms_sample = (iqrec_t *) malloc(sizeof(iqrec_t));

          if (ms_sample == NULL) {
            std::cerr<< "Memory allocation failed for individual subframe replay mode." << std::endl;
            close(iqfd);
            exit(-1);
          }

          memset(ms_sample, 0, sizeof(iqrec_t));

          // point at beginning of file
          if (lseek(iqfd, 0, SEEK_SET) == 0) {
            std::cerr << "Initial seek at beginning of the file" << std::endl;
          } else {
            std::cerr << "Problem initial seek at beginning of the file" << std::endl;
          }
        } else {
          std::cerr << "Cannot open " << u_sf_filename << " , exiting." << std::endl;
          exit(-1);
        }
      }
    }

#endif
    return 0;
  }


}
/*@}*/
