/******************************************************************************* OpenAirInterface Copyright(c) 1999 - 2014 Eurecom OpenAirInterface is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. OpenAirInterface is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenAirInterface.The full GNU General Public License is included in this distribution in the file called "COPYING". If not, see <http://www.gnu.org/licenses/>. Contact Information OpenAirInterface Admin: openair_admin@eurecom.fr OpenAirInterface Tech : openair_tech@eurecom.fr OpenAirInterface Dev : openair4g-devel@eurecom.fr Address : Eurecom, Campus SophiaTech, 450 Route des Chappes, CS 50193 - 06904 Biot Sophia Antipolis cedex, FRANCE *******************************************************************************/ /*! \file eNB_transport_IQ.c * \brief eNB transport IQ sampels * \author Katerina Trilyraki, Navid Nikaein, Raymond Knopp * \date 2015 * \version 0.1 * \company Eurecom * \maintainer: navid.nikaein@eurecom.fr * \note * \warning very experimental */ #include <unistd.h> #include <time.h> #include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <signal.h> #include "common_lib.h" #include "PHY/defs.h" #include "rrh_gw.h" #include "rrh_gw_externs.h" #include "rt_wrapper.h" #define RRH_eNB_PORT 50000 #define RRH_eNB_DEST_IP "192.168.12.31" #define PRINTF_PERIOD 3750 #define HEADER_SIZE ((sizeof(int32_t) + sizeof(openair0_timestamp))>>2) //#undef LOWLATENCY /****************************************************************************** ** FUNCTION PROTOTYPES ** ******************************************************************************/ void *rrh_eNB_rx_thread(void *); void *rrh_eNB_tx_thread(void *); void *rrh_proc_eNB_thread(void *); void *rrh_eNB_thread(void *); void set_rt_period( openair0_config_t openair0_cfg); void print_dev_config(openair0_device device); openair0_timestamp timestamp_eNB_tx[4]= {0,0,0,0},timestamp_eNB_rx[4]= {0,0,0,0}; openair0_timestamp nrt_eNB_counter[4]= {0,0,0,0}; pthread_t main_rrh_eNB_thread; pthread_attr_t attr, attr_proc; struct sched_param sched_param_rrh; pthread_cond_t sync_eNB_cond[4]; pthread_mutex_t sync_eNB_mutex[4]; int32_t overflow_rx_buffer_eNB[4]= {0,0,0,0}; int32_t nsamps_eNB[4]= {0,0,0,0}; int32_t eNB_tx_started=0,eNB_rx_started=0; int32_t counter_eNB_rx[4]= {0,0,0,0}; int32_t counter_eNB_tx[4]= {0,0,0,0}; uint8_t RT_flag_eNB,NRT_flag_eNB; int32_t **tx_buffer_eNB, **rx_buffer_eNB; void *rrh_eNB_thread_status; void *rx_eNB[2]; //to be fixed void *tx_eNB[2]; //to be fixed int sync_eNB_rx[4]= {-1,-1,-1,-1}; unsigned int rx_pos=0, next_rx_pos=0; unsigned int tx_pos=0, prev_tx_pos=0; /*! \fn void create_eNB_trx_threads( rrh_module_t *dev_enb, uint8_t RT_flag,uint8_t NRT_flag) * \brief this function * \param[in] * \param[out] * \return * \note * @ingroup _oai */ void create_eNB_trx_threads( rrh_module_t *dev_enb, uint8_t RT_flag, uint8_t NRT_flag){ //int i; int error_code_eNB;//, error_code_proc_eNB; RT_flag_eNB=RT_flag; NRT_flag_eNB=NRT_flag; pthread_attr_init(&attr); sched_param_rrh.sched_priority = sched_get_priority_max(SCHED_FIFO); pthread_attr_setschedparam(&attr,&sched_param_rrh); pthread_attr_setschedpolicy(&attr,SCHED_FIFO); /* for (i=0; i<4; i++) { pthread_mutex_init(&sync_eNB_mutex[i],NULL); pthread_cond_init(&sync_eNB_cond[i],NULL); }*/ error_code_eNB = pthread_create(&main_rrh_eNB_thread, &attr, rrh_eNB_thread, (void *)dev_enb); if (error_code_eNB) { printf("Error while creating eNB thread\n"); exit(-1); } } /*! \fn void *rrh_eNB_thread(void *arg) * \brief this function * \param[in] * \param[out] * \return * \note * @ingroup _oai */ void *rrh_eNB_thread(void *arg) { rrh_module_t *dev=(rrh_module_t *)arg; pthread_t eNB_rx_thread, eNB_tx_thread; int error_code_eNB_rx, error_code_eNB_tx; int16_t i,j; void *tmp; unsigned int samples_per_frame=0; while (rrh_exit==0) { VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME( VCD_SIGNAL_DUMPER_FUNCTIONS_eNB_TRX, 1 ); /* handshake with client to exchange parameters */ dev->eth_dev.trx_reply_func(&dev->eth_dev);//change port make it plus_id /* prints received configuration parameters */ print_dev_config(dev->eth_dev); set_rt_period(dev->eth_dev.openair0_cfg); samples_per_frame = dev->eth_dev.openair0_cfg.samples_per_frame; /* allocate memory for TX/RX buffers each antenna port has a TX and a RX buffer each TX and RX buffer is of (samples_per_frame + HEADER_SIZE) samples(int32_t) */ rx_buffer_eNB = (int32_t**)malloc16(dev->eth_dev.openair0_cfg.rx_num_channels*sizeof(int32_t*)); tx_buffer_eNB = (int32_t**)malloc16(dev->eth_dev.openair0_cfg.tx_num_channels*sizeof(int32_t*)); LOG_D(RRH,"rx ch %d %p and tx ch %d %p\n", dev->eth_dev.openair0_cfg.rx_num_channels, rx_buffer_eNB, dev->eth_dev.openair0_cfg.tx_num_channels, tx_buffer_eNB); /* rx_buffer_eNB points to the beginning of data */ for (i=0; i<dev->eth_dev.openair0_cfg.rx_num_channels; i++) { tmp=(void *)malloc(sizeof(int32_t)*(samples_per_frame + HEADER_SIZE)); memset(tmp,0,sizeof(int32_t)*(samples_per_frame + HEADER_SIZE)); rx_buffer_eNB[i]=( tmp + (HEADER_SIZE*sizeof(int32_t)) ); LOG_D(RRH," rx ch %d %p |%p header=%d size=%d sample=%d spf=%d\n", i, rx_buffer_eNB[i],tmp,HEADER_SIZE,(sizeof(int32_t)*(samples_per_frame + HEADER_SIZE)),sizeof(int32_t),samples_per_frame); } /* tx_buffer_eNB points to the beginning of data */ for (i=0; i<dev->eth_dev.openair0_cfg.tx_num_channels; i++) { tmp=(void *)malloc(sizeof(int32_t)*(samples_per_frame + HEADER_SIZE)); memset(tmp,0,sizeof(int32_t)*(samples_per_frame + HEADER_SIZE)); tx_buffer_eNB[i]=( tmp + (HEADER_SIZE*sizeof(int32_t)) ); LOG_D(RRH," tx ch %d %p| %p \n", i,tx_buffer_eNB[i],tmp); } /* dummy initialization for TX/RX buffers */ for (i=0; i<dev->eth_dev.openair0_cfg.tx_num_channels; i++) { for (j=0; j<samples_per_frame; j++) { rx_buffer_eNB[i][j]=32+i; tx_buffer_eNB[i][j]=13+i; } } #ifdef LOWLATENCY error_code_eNB_rx = pthread_create(&eNB_rx_thread, NULL, rrh_eNB_rx_thread, (void *)dev); error_code_eNB_tx = pthread_create(&eNB_tx_thread, NULL, rrh_eNB_tx_thread, (void *)dev); LOG_D(RRH,"[eNB][SCHED] deadline scheduling applied to TX/RX threads\n"); #else pthread_attr_t attr_eNB_rx, attr_eNB_tx; struct sched_param sched_param_eNB_rx, sched_param_eNB_tx; pthread_attr_init(&attr_eNB_rx); pthread_attr_init(&attr_eNB_tx); sched_param_eNB_rx.sched_priority = sched_get_priority_max(SCHED_FIFO); sched_param_eNB_tx.sched_priority = sched_get_priority_max(SCHED_FIFO); pthread_attr_setschedparam(&attr_eNB_rx,&sched_param_eNB_rx); pthread_attr_setschedparam(&attr_eNB_tx,&sched_param_eNB_tx); pthread_attr_setschedpolicy(&attr_eNB_rx,SCHED_FIFO); pthread_attr_setschedpolicy(&attr_eNB_tx,SCHED_FIFO); error_code_eNB_rx = pthread_create(&eNB_rx_thread, &attr_eNB_rx, rrh_eNB_rx_thread, (void *)dev); error_code_eNB_tx = pthread_create(&eNB_tx_thread, &attr_eNB_tx, rrh_eNB_tx_thread, (void *)dev); LOG_D(RRH,"[eNB][SCHED] FIFO scheduling applied to TX/RX threads\n"); #endif if (error_code_eNB_rx) { LOG_E(RRH,"[eNB] Error while creating eNB RX thread\n"); exit(-1); } if (error_code_eNB_tx) { LOG_E(RRH,"[eNB] Error while creating eNB TX thread\n"); exit(-1); } while (rrh_exit==0) sleep(1); VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME( VCD_SIGNAL_DUMPER_FUNCTIONS_eNB_TRX,0 ); } //while (eNB_exit==0) rrh_eNB_thread_status = 0; pthread_exit(&rrh_eNB_thread_status); return(0); } /*! \fn void *rrh_eNB_rx_thread(void *arg) * \brief this function * \param[in] * \param[out] * \return * \note * @ingroup _oai */ void *rrh_eNB_rx_thread(void *arg){ /* measuremnt related vars */ struct timespec time0,time1,time2; unsigned long long max_rx_time=0, min_rx_time=rt_period, total_rx_time=0, average_rx_time=rt_period, s_period=0, trial=0; int trace_cnt=0; struct timespec time_req_1us, time_rem_1us; rrh_module_t *dev = (rrh_module_t *)arg; ssize_t bytes_sent; int i, spp ,pck_rx=0; openair0_vtimestamp last_hw_counter=0; //volatile int64_t last_hw_counter=0;---> read unsigned int samples_per_frame=0; uint8_t loopback=0,measurements=0; //RTIME sleep_ns=1000; time_req_1us.tv_sec = 0; time_req_1us.tv_nsec =1000; //time_req_1us.tv_nsec = (int)rt_period/2;--->granularity issue spp = dev->eth_dev.openair0_cfg.samples_per_packet; samples_per_frame = dev->eth_dev.openair0_cfg.samples_per_frame; loopback = dev->loopback; measurements = dev->measurements; next_rx_pos = spp; LOG_I(RRH,"rt period is set to %d\n",rt_period); #ifdef LOWLATENCY struct sched_attr attr; unsigned int flags = 0; attr.size = sizeof(attr); attr.sched_flags = 0; attr.sched_nice = 0; attr.sched_priority = 0; attr.sched_policy = SCHED_DEADLINE; attr.sched_runtime = (0.1 * 100) * 10000; // attr.sched_deadline = rt_period;// 0.1 * 1000000; // attr.sched_period = rt_period; //0.1 * 1000000; // each TX/RX thread has if (sched_setattr(0, &attr, flags) < 0 ) { perror("[SCHED] eNB RX thread: sched_setattr failed (run with sudo)\n"); exit(-1); } #endif while (rrh_exit == 0) { VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME( VCD_SIGNAL_DUMPER_FUNCTIONS_eNB_RX, 1 ); VCD_SIGNAL_DUMPER_DUMP_VARIABLE_BY_NAME( VCD_SIGNAL_DUMPER_VARIABLES_RX_HWCNT, hw_counter ); VCD_SIGNAL_DUMPER_DUMP_VARIABLE_BY_NAME( VCD_SIGNAL_DUMPER_VARIABLES_RX_LHWCNT, last_hw_counter ); VCD_SIGNAL_DUMPER_DUMP_VARIABLE_BY_NAME( VCD_SIGNAL_DUMPER_VARIABLES_RX_PCK, pck_rx ); for (i=0; i<dev->eth_dev.openair0_cfg.rx_num_channels; i++){ if (!eNB_rx_started) { eNB_rx_started=1; // set this flag to 1 to indicate that eNB started if (RT_flag_eNB==1) { last_hw_counter=hw_counter; } } else { if (RT_flag_eNB==1) { if (hw_counter > last_hw_counter+1) { printf("LR"); } else { while (hw_counter < last_hw_counter+1){ VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME( VCD_SIGNAL_DUMPER_FUNCTIONS_eNB_RX_SLEEP, 1 ); nanosleep(&time_req_1us,&time_rem_1us); //rt_sleep_ns(sleep_ns); VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME( VCD_SIGNAL_DUMPER_FUNCTIONS_eNB_RX_SLEEP, 0 ); } } } } if (measurements == 1 ) clock_gettime(CLOCK_MONOTONIC,&time1); /* LOG_I(RRH,"send for%d at %d with %d |%d|%d| \n",i,rx_pos,timestamp_eNB_rx[i],((timestamp_eNB_rx[i]+spp)%samples_per_frame),next_rx_pos ); if ((timestamp_UE_tx[i]%samples_per_frame < next_rx_pos) && (UE_tx_started==1)) { printf("eNB underflow\n"); if (NRT_flag_eNB==1) { while ((timestamp_UE_tx[i]%samples_per_frame) < spp) nanosleep(&time_req_1us,&time_rem_1us); } } if (((rx_pos)< timestamp_UE_tx[i]%samples_per_frame) && (next_rx_pos > (timestamp_UE_tx[i]%samples_per_frame)) && (UE_tx_started==1)) { printf("eNB underflow\n"); if (NRT_flag_eNB==1) { while (next_rx_pos > (timestamp_UE_tx[i]%samples_per_frame)) nanosleep(&time_req_1us,&time_rem_1us); } }*/ if (loopback == 1 ) { if (sync_eNB_rx[i]==0) { rx_eNB[i] = (void*)&tx_buffer_eNB[i][tx_pos]; LOG_I(RRH,"tx_buffer_eNB[i][tx_pos]=%d ,tx_pos=%d\n",tx_buffer_eNB[i][tx_pos],tx_pos); } else{ rx_eNB[i] = (void*)&rx_buffer_eNB[i][rx_pos]; LOG_I(RRH,"rx_buffer_eNB[i][rx_pos]=%d ,rx_pos=%d\n",rx_buffer_eNB[i][rx_pos],rx_pos); } } rx_eNB[i] = (void*)&rx_buffer_eNB[i][rx_pos]; VCD_SIGNAL_DUMPER_DUMP_VARIABLE_BY_NAME( VCD_SIGNAL_DUMPER_VARIABLES_RXCNT, rx_pos ); VCD_SIGNAL_DUMPER_DUMP_VARIABLE_BY_NAME( VCD_SIGNAL_DUMPER_VARIABLES_RX_TS, timestamp_eNB_rx[i]&0xffffffff ); VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME( VCD_SIGNAL_DUMPER_FUNCTIONS_TRX_WRITE, 1 ); //LOG_D(RRH,"rx_buffer_eNB[i][rx_pos]=%p ,rx_pos=%d, i=%d ts=%d\n",&rx_buffer_eNB[i][rx_pos],rx_pos,i,timestamp_eNB_rx[i]); if ((bytes_sent = dev->eth_dev.trx_write_func (&dev->eth_dev, timestamp_eNB_rx[i], rx_eNB, spp, i, 0))<0){ perror("RRH eNB : sendto for RX"); } VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME( VCD_SIGNAL_DUMPER_FUNCTIONS_TRX_WRITE, 0 ); timestamp_eNB_rx[i]+=spp; last_hw_counter=hw_counter; if (measurements == 1 ) { clock_gettime(CLOCK_MONOTONIC,&time2); if (trace_cnt++ > 10) { total_rx_time = (unsigned int)(time2.tv_nsec - time0.tv_nsec); if (total_rx_time < 0) total_rx_time=1000000000-total_rx_time; if ((total_rx_time > 0) && (total_rx_time < 1000000000)) { trial++; if (total_rx_time < min_rx_time) min_rx_time = total_rx_time; if (total_rx_time > max_rx_time){ max_rx_time = total_rx_time; LOG_D(RRH,"Max value %d update at rx_position %d \n",timestamp_eNB_rx[i], rx_pos); } average_rx_time = (long long unsigned int)((average_rx_time*trial)+total_rx_time)/(trial+1); } if (s_period++ == PRINTF_PERIOD) { s_period=0; LOG_I(RRH,"Average eNB RX time : %lu\tMax RX time : %lu\tMin RX time : %lu\n",average_rx_time,max_rx_time,min_rx_time); } } memcpy(&time0,&time2,sizeof(struct timespec)); } if (loopback == 1 ){ pthread_mutex_lock(&sync_eNB_mutex[i]); sync_eNB_rx[i]--; pthread_mutex_unlock(&sync_eNB_mutex[i]); } }//for each antenna rx_pos += spp; pck_rx++; next_rx_pos=(rx_pos+spp); if (next_rx_pos >= samples_per_frame) next_rx_pos -= samples_per_frame; if (rx_pos >= samples_per_frame) rx_pos -= samples_per_frame; VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME( VCD_SIGNAL_DUMPER_FUNCTIONS_eNB_RX, 0 ); } //while (eNB_exit==0) return(0); } /*! \fn void *rrh_eNB_tx_thread(void *arg) * \brief this function * \param[in] * \param[out] * \return * \note * @ingroup _oai */ void *rrh_eNB_tx_thread(void *arg){ struct timespec time0a,time0,time1,time2; rrh_module_t *dev = (rrh_module_t *)arg; struct timespec time_req_1us, time_rem_1us; ssize_t bytes_received; int spp,i; openair0_timestamp last_hw_counter=0; unsigned int samples_per_frame=0; uint8_t loopback=0,measurements=0; #ifdef LOWLATENCY struct sched_attr attr; unsigned int flags = 0; attr.size = sizeof(attr); attr.sched_flags = 0; attr.sched_nice = 0; attr.sched_priority = 0; attr.sched_policy = SCHED_DEADLINE; attr.sched_runtime = (0.1 * 100) * 10000; // attr.sched_deadline = rt_period;//0.1 * 1000000; // attr.sched_period = rt_period;//0.1 * 1000000; // each TX/RX thread has if (sched_setattr(0, &attr, flags) < 0 ) { perror("[SCHED] eNB TX thread: sched_setattr failed\n"); exit(-1); } #endif tx_pos=0; time_req_1us.tv_sec = 0; time_req_1us.tv_nsec = 1000; spp = dev->eth_dev.openair0_cfg.samples_per_packet; samples_per_frame = dev->eth_dev.openair0_cfg.samples_per_frame; loopback = dev->loopback; measurements = dev->measurements; while (rrh_exit == 0) { VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_eNB_TX, 1 ); VCD_SIGNAL_DUMPER_DUMP_VARIABLE_BY_NAME( VCD_SIGNAL_DUMPER_VARIABLES_TX_HWCNT, hw_counter ); VCD_SIGNAL_DUMPER_DUMP_VARIABLE_BY_NAME( VCD_SIGNAL_DUMPER_VARIABLES_TX_LHWCNT, last_hw_counter ); if (measurements == 1 ) clock_gettime(CLOCK_MONOTONIC,&time0a); for (i=0; i<dev->eth_dev.openair0_cfg.tx_num_channels; i++){ if (!eNB_tx_started) { eNB_tx_started=1; // set this flag to 1 to indicate that eNB started if (RT_flag_eNB==1) { last_hw_counter=hw_counter; } } else { if (RT_flag_eNB==1) { if (hw_counter > last_hw_counter+1) { printf("LT"); } else { while (hw_counter < last_hw_counter+1){ VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME( VCD_SIGNAL_DUMPER_FUNCTIONS_eNB_TX_SLEEP, 1 ); nanosleep(&time_req_1us,&time_rem_1us); VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME( VCD_SIGNAL_DUMPER_FUNCTIONS_eNB_TX_SLEEP, 0 ); } } } } if (measurements == 1 ) clock_gettime(CLOCK_MONOTONIC,&time1); tx_eNB[i] = (void*)&tx_buffer_eNB[i][tx_pos]; VCD_SIGNAL_DUMPER_DUMP_VARIABLE_BY_NAME( VCD_SIGNAL_DUMPER_VARIABLES_TXCNT, tx_pos ); VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME( VCD_SIGNAL_DUMPER_FUNCTIONS_TRX_READ, 1 ); bytes_received = dev->eth_dev.trx_read_func(&dev->eth_dev, ×tamp_eNB_tx[i], tx_eNB, spp, i); VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME( VCD_SIGNAL_DUMPER_FUNCTIONS_TRX_READ, 0 ); VCD_SIGNAL_DUMPER_DUMP_VARIABLE_BY_NAME( VCD_SIGNAL_DUMPER_VARIABLES_TX_TS, timestamp_eNB_tx[i]&0xffffffff ); if (NRT_flag_eNB==1) { nrt_eNB_counter[i]++; } prev_tx_pos=tx_pos; tx_pos += spp; if (tx_pos >= samples_per_frame) tx_pos -= samples_per_frame; last_hw_counter=hw_counter; if (loopback ==1 ) { while (sync_eNB_rx[i]==0) nanosleep(&time_req_1us,&time_rem_1us); pthread_mutex_lock(&sync_eNB_mutex[i]); sync_eNB_rx[i]++; pthread_mutex_unlock(&sync_eNB_mutex[i]); } } if (measurements == 1 ) { clock_gettime(CLOCK_MONOTONIC,&time2); memcpy(&time0,&time2,sizeof(struct timespec)); } VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_eNB_TX, 0 ); } return(0); } //needs to be fixed void set_rt_period( openair0_config_t openair0_cfg){ AssertFatal(openair0_cfg.samples_per_frame > 0, "invalide samples per frame !%u\n",openair0_cfg.samples_per_frame); AssertFatal(openair0_cfg.samples_per_packet > 0, "invalide samples per packets !%u\n",openair0_cfg.samples_per_packet); rt_period= (double)(openair0_cfg.samples_per_packet/(openair0_cfg.samples_per_frame/10.0)*1000000); AssertFatal(rt_period > 0, "invalide rt period !%u\n", rt_period); LOG_I(RRH,"Real time period is set to %d ns.\n",rt_period); // set the calibration parameters for ETH later: check with RK openair0_cfg.tx_delay=0; openair0_cfg.tx_forward_nsamps=0; //only in case of NRT with emulated UE create_timer_thread(); // usleep(rt_period*1000); } void print_dev_config(openair0_device device){ printf("\tMod_id=%d\n \tlog level=%d\n \tDL_RB=%d\n \tspf=%d\n \tsr=%f\n \tspp=%d\n \ttx_delay=%d\n \ttx_forwd_samps=%d\n \trx_ch=%d\n \ttx_ch=%d\n \trx_freq=%f\n \ttx_freq=%f\n \trxg_mode=%d\n \trx_gain=%f\n \ttx_gain=%f\n \trx_bw=%f\n \ttx_bw=%f\n \tautocal=%d\n \trem_addr %s:%d\n \tmy_addr %s:%d\n", device.openair0_cfg.Mod_id, device.openair0_cfg.log_level, device.openair0_cfg.num_rb_dl, device.openair0_cfg.samples_per_frame, device.openair0_cfg.sample_rate, device.openair0_cfg.samples_per_packet, device.openair0_cfg.tx_delay, device.openair0_cfg.tx_forward_nsamps, device.openair0_cfg.rx_num_channels, device.openair0_cfg.tx_num_channels, device.openair0_cfg.rx_freq[0], device.openair0_cfg.tx_freq[0], device.openair0_cfg.rxg_mode[0], device.openair0_cfg.rx_gain[0], device.openair0_cfg.tx_gain[0], device.openair0_cfg.rx_bw, device.openair0_cfg.tx_bw, device.openair0_cfg.autocal[0], device.openair0_cfg.remote_ip, device.openair0_cfg.remote_port, device.openair0_cfg.my_ip, device.openair0_cfg.my_port ); }