A simplified beam switching simulation for rfsimulator. This works
mainly in 1 gNB - N UEs scenarios. This is not a real beamformer with
antenna, channel and propagation model. The way it works is that the
peers inform each other about the beams they transmit in, and on
reception before channel modelling, the beam data is combined based on
preconfigured rx_beam/tx_beam gain matrix.
Usage:
- Add --rfsimulator.enable_beams to enable beam simulation
- Add --rfsimulator.beam_gains <comma separated Toepliz form matrix of gains in dB>.
Example list: 0,-5,-10
Resulting matrix:
[[0, -5, -10]
[-5, 0, -5]]
[-10, -5, 0]]
- Add --telnetsrv if you want to control the beams manually. The command in telnet
is "rfsimu setbeam <beam_map>", where beam_map is a uint64_t controling the
reception beam and transmission beam. If the application is switching the beams
automatically the beams will be overwritten as soon as the application chooses to
switch beams.
Co-authored-by: Laurent Thomas <laurent.thomas@open-cells.com>
1497 lines
55 KiB
C++
1497 lines
55 KiB
C++
/*
|
|
* 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
|
|
*
|
|
* Author and copyright: Laurent Thomas, open-cells.com
|
|
*
|
|
* 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
|
|
*/
|
|
|
|
/*
|
|
* Open issues and limitations
|
|
* The read and write should be called in the same thread, that is not new USRP UHD design
|
|
* When the opposite side switch from passive reading to active R+Write, the synchro is not fully deterministic
|
|
*/
|
|
|
|
#include <sys/socket.h>
|
|
#include <sys/types.h>
|
|
#include <netinet/in.h>
|
|
#include <arpa/inet.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <stdbool.h>
|
|
#include <errno.h>
|
|
#include <sys/epoll.h>
|
|
#include <netdb.h>
|
|
|
|
#include <common/utils/assertions.h>
|
|
#include <common/utils/LOG/log.h>
|
|
#include <common/utils/telnetsrv/telnetsrv.h>
|
|
#include <common/config/config_userapi.h>
|
|
#include "common_lib.h"
|
|
#define CHANNELMOD_DYNAMICLOAD
|
|
extern "C" {
|
|
#include <common/utils/load_module_shlib.h>
|
|
#include <openair1/SIMULATION/TOOLS/sim.h>
|
|
}
|
|
#include "rfsimulator.h"
|
|
#include <queue>
|
|
#include <mutex>
|
|
#include <vector>
|
|
#include <sstream>
|
|
|
|
#define PORT 4043 // default TCP port for this simulator
|
|
//
|
|
// CirSize defines the number of samples inquired for a read cycle
|
|
// It is bounded by a slot read capability (which depends on bandwidth and numerology)
|
|
// up to multiple slots read to allow I/Q buffering of the I/Q TCP stream
|
|
//
|
|
// As a rule of thumb:
|
|
// -it can't be less than the number of samples for a slot
|
|
// -it can range up to multiple slots
|
|
//
|
|
// The default value is chosen for 10ms buffering which makes 23040*20 = 460800 samples
|
|
// The previous value is kept below in comment it was computed for 100ms 1x 20MHz
|
|
// #define CirSize 6144000 // 100ms SiSo 20MHz LTE
|
|
#define minCirSize 460800 // 10ms SiSo 40Mhz 3/4 sampling NR78 FR1
|
|
#define sampleToByte(a, b) ((a) * (b) * sizeof(sample_t))
|
|
#define byteToSample(a, b) ((a) / (sizeof(sample_t) * (b)))
|
|
|
|
#define GENERATE_CHANNEL 10 // each frame (or slot?) in DL
|
|
#define MAX_FD_RFSIMU 250
|
|
#define SEND_BUFF_SIZE 100000000 // Socket buffer size
|
|
#define MAX_BEAMS 64
|
|
|
|
// Simulator role
|
|
typedef enum { SIMU_ROLE_SERVER = 1, SIMU_ROLE_CLIENT } simuRole;
|
|
|
|
//
|
|
|
|
#define RFSIMU_SECTION "rfsimulator"
|
|
#define RFSIMU_OPTIONS_PARAMNAME "options"
|
|
#define RFSIMU_BEAM_GAINS "beam_gains"
|
|
|
|
#define RFSIM_CONFIG_HELP_OPTIONS \
|
|
" list of comma separated options to enable rf simulator functionalities. Available options: \n" \
|
|
" chanmod: enable channel modelisation\n" \
|
|
" saviq: enable saving written iqs to a file\n"
|
|
|
|
// clang-format off
|
|
/*-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------*/
|
|
/* configuration parameters for the rfsimulator device */
|
|
/* optname helpstr paramflags XXXptr defXXXval type numelt */
|
|
/*-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------*/
|
|
#define simOpt PARAMFLAG_NOFREE | PARAMFLAG_CMDLINE_NOPREFIXENABLED
|
|
#define RFSIMULATOR_PARAMS_DESC { \
|
|
{"serveraddr", "<ip address to connect to>\n", simOpt, .strptr=&rfsimulator->ip, .defstrval="127.0.0.1", TYPE_STRING, 0 },\
|
|
{"serverport", "<port to connect to>\n", simOpt, .u16ptr = &(rfsimulator->port), .defuintval = PORT, TYPE_UINT16, 0}, \
|
|
{RFSIMU_OPTIONS_PARAMNAME, RFSIM_CONFIG_HELP_OPTIONS, 0, .strlistptr = NULL, .defstrlistval = NULL, TYPE_STRINGLIST, 0}, \
|
|
{"IQfile", "<file path to use when saving IQs>\n",simOpt, .strptr=&saveF, .defstrval="/tmp/rfsimulator.iqs",TYPE_STRING, 0 },\
|
|
{"modelname", "<channel model name>\n", simOpt, .strptr = &modelname, .defstrval = "AWGN", TYPE_STRING, 0}, \
|
|
{"ploss", "<channel path loss in dB>\n", simOpt, .dblptr = &(rfsimulator->chan_pathloss), .defdblval = 0, TYPE_DOUBLE, 0}, \
|
|
{"forgetfact", "<channel forget factor ((0 to 1)>\n", simOpt, .dblptr=&(rfsimulator->chan_forgetfact),.defdblval=0, TYPE_DOUBLE, 0 },\
|
|
{"offset", "<channel offset in samps>\n", simOpt, .u64ptr = &(rfsimulator->chan_offset), .defint64val = 0, TYPE_UINT64, 0}, \
|
|
{"prop_delay", "<propagation delay in ms>\n", simOpt, .dblptr=&(rfsimulator->prop_delay_ms), .defdblval=0.0, TYPE_DOUBLE, 0 },\
|
|
{"wait_timeout", "<wait timeout if no UE connected>\n", simOpt, .iptr=&(rfsimulator->wait_timeout), .defintval=1, TYPE_INT, 0 },\
|
|
{"enable_beams", "enable simplified beam simulation", PARAMFLAG_BOOL, .iptr=&(beam_ctrl->enable_beams), .defintval=0, TYPE_INT, 0},\
|
|
{"num_concurrent_beams", "number of concurrent beams supported", simOpt, .iptr=&(beam_ctrl->num_concurrent_beams), .defintval=1, TYPE_INT, 0},\
|
|
{"tx_beam_map", "initial beam map", simOpt, .u64ptr=&(beam_ctrl->tx.beam_map), .defintval=1, TYPE_UINT64, 0},\
|
|
{"rx_beam_map", "initial beam map", simOpt, .u64ptr=&(beam_ctrl->rx.beam_map), .defintval=1, TYPE_UINT64, 0},\
|
|
{RFSIMU_BEAM_GAINS, "beam gain matrix in toeplitz form (first row only)", simOpt, .strptr=NULL, .defstrval=NULL, TYPE_STRING, 0}, \
|
|
};
|
|
// clang-format on
|
|
static void getset_currentchannels_type(char *buf, int debug, webdatadef_t *tdata, telnet_printfunc_t prnt);
|
|
extern int get_currentchannels_type(const char *buf,
|
|
int debug,
|
|
webdatadef_t *tdata,
|
|
telnet_printfunc_t prnt); // in random_channel.c
|
|
static int rfsimu_setchanmod_cmd(char *buff, int debug, telnet_printfunc_t prnt, void *arg);
|
|
static int rfsimu_setdistance_cmd(char *buff, int debug, telnet_printfunc_t prnt, void *arg);
|
|
static int rfsimu_getdistance_cmd(char *buff, int debug, telnet_printfunc_t prnt, void *arg);
|
|
static int rfsimu_vtime_cmd(char *buff, int debug, telnet_printfunc_t prnt, void *arg);
|
|
static int rfsimu_set_beam(char *buff, int debug, telnet_printfunc_t prnt, void *arg);
|
|
// clang-format off
|
|
static telnetshell_cmddef_t rfsimu_cmdarray[] = {
|
|
{"show models", "", (cmdfunc_t)rfsimu_setchanmod_cmd, {(webfunc_t)getset_currentchannels_type}, TELNETSRV_CMDFLAG_WEBSRVONLY | TELNETSRV_CMDFLAG_GETWEBTBLDATA, NULL},
|
|
{"setmodel", "<model name> <model type>", (cmdfunc_t)rfsimu_setchanmod_cmd, {NULL}, TELNETSRV_CMDFLAG_PUSHINTPOOLQ | TELNETSRV_CMDFLAG_TELNETONLY, NULL},
|
|
{"setdistance", "<model name> <distance>", (cmdfunc_t)rfsimu_setdistance_cmd, {NULL}, TELNETSRV_CMDFLAG_PUSHINTPOOLQ | TELNETSRV_CMDFLAG_NEEDPARAM },
|
|
{"getdistance", "<model name>", (cmdfunc_t)rfsimu_getdistance_cmd, {NULL}, TELNETSRV_CMDFLAG_PUSHINTPOOLQ},
|
|
{"vtime", "", (cmdfunc_t)rfsimu_vtime_cmd, {NULL}, TELNETSRV_CMDFLAG_PUSHINTPOOLQ | TELNETSRV_CMDFLAG_AUTOUPDATE},
|
|
{"setbeam", "beam_map", (cmdfunc_t)rfsimu_set_beam, {NULL}, TELNETSRV_CMDFLAG_PUSHINTPOOLQ},
|
|
{"", "", NULL},
|
|
};
|
|
// clang-format on
|
|
static telnetshell_cmddef_t *setmodel_cmddef = &(rfsimu_cmdarray[1]);
|
|
|
|
static telnetshell_vardef_t rfsimu_vardef[] = {{"", 0, 0, NULL}};
|
|
static uint64_t CirSize = minCirSize;
|
|
typedef c16_t sample_t; // 2*16 bits complex number
|
|
|
|
typedef struct beam_switch_command_t {
|
|
uint64_t beam_map;
|
|
openair0_timestamp timestamp;
|
|
} beam_switch_command_t;
|
|
|
|
typedef struct {
|
|
uint64_t beam_map;
|
|
std::queue<beam_switch_command_t> cmd_queue;
|
|
std::mutex mutex;
|
|
} beam_state_t;
|
|
|
|
typedef struct buffer_s {
|
|
int conn_sock;
|
|
openair0_timestamp lastReceivedTS;
|
|
bool headerMode;
|
|
bool trashingPacket;
|
|
samplesBlockHeader_t th;
|
|
char *transferPtr;
|
|
uint64_t remainToTransfer;
|
|
char *circularBufEnd;
|
|
sample_t *circularBuf;
|
|
channel_desc_t *channel_model;
|
|
char *beamPtr;
|
|
size_t beam_ptr_sz;
|
|
size_t remainToTransferBeam;
|
|
} buffer_t;
|
|
|
|
typedef struct {
|
|
int enable_beams;
|
|
int num_concurrent_beams;
|
|
std::vector<std::vector<float>> beam_gains;
|
|
beam_state_t tx;
|
|
beam_state_t rx;
|
|
} rfsim_beam_ctrl_t;
|
|
|
|
typedef struct {
|
|
int listen_sock, epollfd;
|
|
pthread_mutex_t Sockmutex;
|
|
unsigned int nb_cnx;
|
|
openair0_timestamp nextRxTstamp;
|
|
openair0_timestamp lastWroteTS;
|
|
simuRole role;
|
|
char *ip;
|
|
uint16_t port;
|
|
int saveIQfile;
|
|
buffer_t buf[MAX_FD_RFSIMU];
|
|
int next_buf;
|
|
int rx_num_channels;
|
|
int tx_num_channels;
|
|
double sample_rate;
|
|
double rx_freq;
|
|
double tx_bw;
|
|
int channelmod;
|
|
double chan_pathloss;
|
|
double chan_forgetfact;
|
|
uint64_t chan_offset;
|
|
void *telnetcmd_qid;
|
|
poll_telnetcmdq_func_t poll_telnetcmdq;
|
|
int wait_timeout;
|
|
double prop_delay_ms;
|
|
rfsim_beam_ctrl_t *beam_ctrl;
|
|
} rfsimulator_state_t;
|
|
|
|
/**
|
|
* @brief Get the current beam map for a given timestamp and number of samples.
|
|
*
|
|
* This function retrieves the beam map from the beam state, considering any queued beam switch commands.
|
|
*
|
|
* @param beam_state Pointer to the beam_state_t structure containing the current beam map and command queue.
|
|
* @param timestamp The timestamp for which the beam map is requested.
|
|
* @param nsamps The number of samples to process.
|
|
* @param nsamps_out output pointer to receive the number of samples until the next beam switch.
|
|
* @return The beam map (uint64_t) valid for the given timestamp.
|
|
*/
|
|
static uint64_t get_beam_map(beam_state_t *beam_state, openair0_timestamp timestamp, uint32_t nsamps, uint32_t *nsamps_out)
|
|
{
|
|
std::lock_guard<std::mutex> lock(beam_state->mutex);
|
|
uint64_t current_map = beam_state->beam_map;
|
|
uint32_t samples_to_next_switch = nsamps;
|
|
|
|
// Find the latest beam_switch_command_t with timestamp <= requested timestamp
|
|
if (!beam_state->cmd_queue.empty()) {
|
|
// Copy queue to avoid modifying original
|
|
std::queue<beam_switch_command_t> queue_copy = beam_state->cmd_queue;
|
|
while (!queue_copy.empty()) {
|
|
const beam_switch_command_t &cmd = queue_copy.front();
|
|
if (cmd.timestamp <= timestamp) {
|
|
current_map = cmd.beam_map;
|
|
queue_copy.pop();
|
|
} else {
|
|
samples_to_next_switch = (cmd.timestamp > timestamp) ? (cmd.timestamp - timestamp) : nsamps;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Clamp samples_to_next_switch to nsamps
|
|
if (samples_to_next_switch > nsamps)
|
|
samples_to_next_switch = nsamps;
|
|
|
|
*nsamps_out = samples_to_next_switch;
|
|
|
|
return current_map;
|
|
}
|
|
|
|
/**
|
|
* @brief Clears outdated beam switch commands from the queue and updates the current beam map.
|
|
*
|
|
* This function processes the beam switch command queue in the given beam_state_t.
|
|
* For each command with a timestamp less than or equal to the provided timestamp,
|
|
* it updates the current beam_map and removes the command from the queue.
|
|
* The queue is left unchanged for commands with timestamps greater than the provided timestamp.
|
|
*
|
|
* @param beam_state Pointer to the beam_state_t structure containing the command queue and current beam map.
|
|
* @param timestamp The timestamp up to which commands should be processed and removed.
|
|
*/
|
|
static void clear_beam_queue(beam_state_t *beam_state, openair0_timestamp timestamp)
|
|
{
|
|
std::lock_guard<std::mutex> lock(beam_state->mutex);
|
|
while (!beam_state->cmd_queue.empty()) {
|
|
if (beam_state->cmd_queue.front().timestamp <= timestamp) {
|
|
beam_state->beam_map = beam_state->cmd_queue.front().beam_map;
|
|
beam_state->cmd_queue.pop();
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool flushInput(rfsimulator_state_t *t, int timeout, bool first_time);
|
|
|
|
static buffer_t *allocCirBuf(rfsimulator_state_t *bridge, int sock)
|
|
{
|
|
uint64_t buff_index = bridge->next_buf++ % MAX_FD_RFSIMU;
|
|
buffer_t *ptr = &bridge->buf[buff_index];
|
|
bridge->nb_cnx++;
|
|
ptr->circularBuf = static_cast<sample_t *>(calloc(1, sampleToByte(CirSize, 1)));
|
|
if (ptr->circularBuf == NULL) {
|
|
LOG_E(HW, "malloc(%lu) failed\n", sampleToByte(CirSize, 1));
|
|
return NULL;
|
|
}
|
|
ptr->circularBufEnd = ((char *)ptr->circularBuf) + sampleToByte(CirSize, 1);
|
|
ptr->conn_sock = sock;
|
|
ptr->lastReceivedTS = 0;
|
|
ptr->headerMode = true;
|
|
ptr->trashingPacket = true;
|
|
ptr->transferPtr = (char *)&ptr->th;
|
|
ptr->remainToTransfer = sizeof(samplesBlockHeader_t);
|
|
int sendbuff = SEND_BUFF_SIZE;
|
|
if (setsockopt(sock, SOL_SOCKET, SO_SNDBUF, &sendbuff, sizeof(sendbuff)) != 0) {
|
|
LOG_E(HW, "setsockopt(SO_SNDBUF) failed\n");
|
|
return NULL;
|
|
}
|
|
struct epoll_event ev = {0};
|
|
ev.events = EPOLLIN | EPOLLRDHUP;
|
|
ev.data.ptr = ptr;
|
|
if (epoll_ctl(bridge->epollfd, EPOLL_CTL_ADD, sock, &ev) != 0) {
|
|
LOG_E(HW, "epoll_ctl(EPOLL_CTL_ADD) failed\n");
|
|
return NULL;
|
|
}
|
|
|
|
if (bridge->channelmod > 0) {
|
|
// create channel simulation model for this mode reception
|
|
char modelname[30];
|
|
snprintf(modelname,
|
|
sizeofArray(modelname),
|
|
"rfsimu_channel_%s%d",
|
|
(bridge->role == SIMU_ROLE_SERVER) ? "ue" : "enB",
|
|
bridge->nb_cnx - 1);
|
|
ptr->channel_model = find_channel_desc_fromname(modelname); // path_loss in dB
|
|
if (!ptr->channel_model) {
|
|
// Use legacy method to find channel model - this will use the same channel model for all clients
|
|
const char *legacy_model_name = (bridge->role == SIMU_ROLE_SERVER) ? "rfsimu_channel_ue0" : "rfsimu_channel_enB0";
|
|
ptr->channel_model = find_channel_desc_fromname(legacy_model_name);
|
|
if (!ptr->channel_model) {
|
|
LOG_E(HW, "Channel model %s/%s not found, check config file\n", modelname, legacy_model_name);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
set_channeldesc_owner(ptr->channel_model, RFSIMU_MODULEID);
|
|
set_channeldesc_direction(ptr->channel_model, bridge->role == SIMU_ROLE_SERVER);
|
|
random_channel(ptr->channel_model, false);
|
|
LOG_I(HW, "Random channel %s in rfsimulator activated\n", modelname);
|
|
}
|
|
return ptr;
|
|
}
|
|
|
|
static void removeCirBuf(rfsimulator_state_t *bridge, buffer_t *buf)
|
|
{
|
|
if (epoll_ctl(bridge->epollfd, EPOLL_CTL_DEL, buf->conn_sock, NULL) != 0) {
|
|
LOG_E(HW, "epoll_ctl(EPOLL_CTL_DEL) failed\n");
|
|
}
|
|
close(buf->conn_sock);
|
|
free(buf->circularBuf);
|
|
// Fixme: no free_channel_desc_scm(bridge->buf[sock].channel_model) implemented
|
|
// a lot of mem leaks
|
|
// free(bridge->buf[sock].channel_model);
|
|
memset(buf, 0, sizeof(buffer_t));
|
|
buf->conn_sock = -1;
|
|
bridge->nb_cnx--;
|
|
}
|
|
|
|
static void socketError(rfsimulator_state_t *bridge, buffer_t *buf)
|
|
{
|
|
if (buf->conn_sock != -1) {
|
|
LOG_W(HW, "Lost socket\n");
|
|
removeCirBuf(bridge, buf);
|
|
|
|
if (bridge->role == SIMU_ROLE_CLIENT)
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
enum blocking_t { notBlocking, blocking };
|
|
|
|
static int setblocking(int sock, enum blocking_t active)
|
|
{
|
|
int opts = fcntl(sock, F_GETFL);
|
|
if (opts < 0) {
|
|
LOG_E(HW, "fcntl(F_GETFL) failed, errno(%d)\n", errno);
|
|
return -1;
|
|
}
|
|
|
|
if (active == blocking)
|
|
opts = opts & ~O_NONBLOCK;
|
|
else
|
|
opts = opts | O_NONBLOCK;
|
|
|
|
opts = fcntl(sock, F_SETFL, opts);
|
|
if (opts < 0) {
|
|
LOG_E(HW, "fcntl(F_SETFL) failed, errno(%d)\n", errno);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void fullwrite(int fd, void *_buf, ssize_t count, rfsimulator_state_t *t)
|
|
{
|
|
if (t->saveIQfile != -1) {
|
|
if (write(t->saveIQfile, _buf, count) != count)
|
|
LOG_E(HW, "write() in save iq file failed (%d)\n", errno);
|
|
}
|
|
|
|
char *buf = static_cast<char *>(_buf);
|
|
ssize_t l;
|
|
|
|
while (count) {
|
|
l = write(fd, buf, count);
|
|
|
|
if (l == 0) {
|
|
LOG_E(HW, "write() failed, returned 0\n");
|
|
return;
|
|
}
|
|
|
|
if (l < 0) {
|
|
if (errno == EINTR)
|
|
continue;
|
|
|
|
if (errno == EAGAIN) {
|
|
LOG_D(HW, "write() failed, errno(%d)\n", errno);
|
|
usleep(250);
|
|
continue;
|
|
} else {
|
|
LOG_E(HW, "write() failed, errno(%d)\n", errno);
|
|
return;
|
|
}
|
|
}
|
|
|
|
count -= l;
|
|
buf += l;
|
|
}
|
|
}
|
|
|
|
static float get_rx_gain_db(rfsimulator_state_t *rfsimulator, uint rx_beam, uint tx_beam)
|
|
{
|
|
if (!rfsimulator->beam_ctrl->enable_beams) {
|
|
return 0;
|
|
}
|
|
AssertFatal(rx_beam < rfsimulator->beam_ctrl->beam_gains.size() && tx_beam < rfsimulator->beam_ctrl->beam_gains[rx_beam].size(),
|
|
"Beam gain for this combination was not provided rx_beam %d tx_beam %d\n",
|
|
rx_beam,
|
|
tx_beam);
|
|
return rfsimulator->beam_ctrl->beam_gains[rx_beam][tx_beam];
|
|
}
|
|
|
|
static int rfsimulator_set_beams(openair0_device *device, uint64_t beam_map, openair0_timestamp timestamp)
|
|
{
|
|
rfsimulator_state_t *s = static_cast<rfsimulator_state_t *>(device->priv);
|
|
rfsim_beam_ctrl_t *beam_ctrl = s->beam_ctrl;
|
|
std::lock_guard<std::mutex> lock_tx(beam_ctrl->tx.mutex);
|
|
std::lock_guard<std::mutex> lock_rx(beam_ctrl->rx.mutex);
|
|
beam_switch_command_t command = {.beam_map = beam_map, .timestamp = timestamp};
|
|
beam_ctrl->rx.cmd_queue.emplace(command);
|
|
beam_ctrl->tx.cmd_queue.emplace(command);
|
|
return 0;
|
|
}
|
|
|
|
static void process_gains(char *str, rfsim_beam_ctrl_t *beam_ctrl)
|
|
{
|
|
int num_gains = 0;
|
|
float gain_array[MAX_BEAMS];
|
|
std::stringstream ss(str);
|
|
std::string token;
|
|
while (std::getline(ss, token, ',') && num_gains < MAX_BEAMS) {
|
|
gain_array[num_gains++] = std::stof(token);
|
|
}
|
|
|
|
if (num_gains != 0) {
|
|
for (int i = 0; i < num_gains; i++) {
|
|
std::vector<float> beam_gains;
|
|
for (int j = 0; j < num_gains; j++) {
|
|
int diag = abs(i - j);
|
|
beam_gains.push_back(gain_array[diag]);
|
|
}
|
|
beam_ctrl->beam_gains.push_back(beam_gains);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void rfsimulator_readconfig(rfsimulator_state_t *rfsimulator)
|
|
{
|
|
char *saveF = NULL;
|
|
char *modelname = NULL;
|
|
rfsim_beam_ctrl_t *beam_ctrl = rfsimulator->beam_ctrl;
|
|
paramdef_t rfsimu_params[] = RFSIMULATOR_PARAMS_DESC;
|
|
int p = config_paramidx_fromname(rfsimu_params, sizeofArray(rfsimu_params), RFSIMU_OPTIONS_PARAMNAME);
|
|
int ret = config_get(config_get_if(), rfsimu_params, sizeofArray(rfsimu_params), RFSIMU_SECTION);
|
|
AssertFatal(ret >= 0, "configuration couldn't be performed\n");
|
|
|
|
rfsimulator->saveIQfile = -1;
|
|
|
|
for (int i = 0; i < rfsimu_params[p].numelt; i++) {
|
|
if (strcmp(rfsimu_params[p].strlistptr[i], "saviq") == 0) {
|
|
rfsimulator->saveIQfile = open(saveF, O_APPEND | O_CREAT | O_TRUNC | O_WRONLY, 0666);
|
|
|
|
if (rfsimulator->saveIQfile != -1)
|
|
LOG_I(HW, "Will save written IQ samples in %s\n", saveF);
|
|
else {
|
|
LOG_E(HW, "open(%s) failed for IQ saving, errno(%d)\n", saveF, errno);
|
|
exit(-1);
|
|
}
|
|
|
|
break;
|
|
} else if (strcmp(rfsimu_params[p].strlistptr[i], "chanmod") == 0) {
|
|
init_channelmod();
|
|
load_channellist(rfsimulator->tx_num_channels,
|
|
rfsimulator->rx_num_channels,
|
|
rfsimulator->sample_rate,
|
|
rfsimulator->rx_freq,
|
|
rfsimulator->tx_bw);
|
|
rfsimulator->channelmod = true;
|
|
} else {
|
|
fprintf(stderr, "unknown rfsimulator option: %s\n", rfsimu_params[p].strlistptr[i]);
|
|
exit(-1);
|
|
}
|
|
}
|
|
|
|
int beam_gains_param_index = config_paramidx_fromname(rfsimu_params, sizeofArray(rfsimu_params), RFSIMU_BEAM_GAINS);
|
|
if (rfsimu_params[beam_gains_param_index].strptr) {
|
|
process_gains(*rfsimu_params[beam_gains_param_index].strptr, beam_ctrl);
|
|
}
|
|
|
|
if (strncasecmp(rfsimulator->ip, "enb", 3) == 0 || strncasecmp(rfsimulator->ip, "server", 3) == 0)
|
|
rfsimulator->role = SIMU_ROLE_SERVER;
|
|
else
|
|
rfsimulator->role = SIMU_ROLE_CLIENT;
|
|
}
|
|
|
|
static int rfsimu_set_beam(char *buff, int debug, telnet_printfunc_t prnt, void *arg)
|
|
{
|
|
rfsimulator_state_t *t = (rfsimulator_state_t *)arg;
|
|
rfsim_beam_ctrl_t *beam_ctrl = t->beam_ctrl;
|
|
AssertFatal(beam_ctrl->enable_beams, "Beam simualtion is disabled, cannot set beams\n");
|
|
uint64_t beam_map = strtoull(buff, NULL, 0);
|
|
std::lock_guard<std::mutex> lock_tx(beam_ctrl->tx.mutex);
|
|
std::lock_guard<std::mutex> lock_rx(beam_ctrl->rx.mutex);
|
|
beam_ctrl->rx.beam_map = beam_map;
|
|
beam_ctrl->tx.beam_map = beam_map;
|
|
prnt("Beam map set to 0x%lx\n", beam_map);
|
|
return CMDSTATUS_FOUND;
|
|
}
|
|
|
|
static int rfsimu_setchanmod_cmd(char *buff, int debug, telnet_printfunc_t prnt, void *arg)
|
|
{
|
|
char *modelname = NULL;
|
|
char *modeltype = NULL;
|
|
rfsimulator_state_t *t = (rfsimulator_state_t *)arg;
|
|
if (t->channelmod == false) {
|
|
prnt("%s: ERROR channel modelisation disabled...\n", __func__);
|
|
return 0;
|
|
}
|
|
if (buff == NULL) {
|
|
prnt("%s: ERROR wrong rfsimu setchannelmod command...\n", __func__);
|
|
return 0;
|
|
}
|
|
if (debug)
|
|
prnt("%s: rfsimu_setchanmod_cmd buffer \"%s\"\n", __func__, buff);
|
|
int s = sscanf(buff, "%m[^ ] %ms\n", &modelname, &modeltype);
|
|
|
|
if (s == 2) {
|
|
int channelmod = modelid_fromstrtype(modeltype);
|
|
|
|
if (channelmod < 0)
|
|
prnt("%s: ERROR: model type %s unknown\n", __func__, modeltype);
|
|
else {
|
|
rfsimulator_state_t *t = (rfsimulator_state_t *)arg;
|
|
int found = 0;
|
|
for (int i = 0; i < MAX_FD_RFSIMU; i++) {
|
|
buffer_t *b = &t->buf[i];
|
|
if (b->channel_model == NULL)
|
|
continue;
|
|
if (b->channel_model->model_name == NULL)
|
|
continue;
|
|
if (b->conn_sock >= 0 && (strcmp(b->channel_model->model_name, modelname) == 0)) {
|
|
channel_desc_t *newmodel = new_channel_desc_scm(t->tx_num_channels,
|
|
t->rx_num_channels,
|
|
static_cast<SCM_t>(channelmod),
|
|
t->sample_rate,
|
|
t->rx_freq,
|
|
t->tx_bw,
|
|
30e-9, // TDL delay-spread parameter
|
|
0.0,
|
|
CORR_LEVEL_LOW,
|
|
t->chan_forgetfact, // forgetting_factor
|
|
t->chan_offset, // propagation delay in samples
|
|
t->chan_pathloss,
|
|
0); // noise_power
|
|
set_channeldesc_owner(newmodel, RFSIMU_MODULEID);
|
|
set_channeldesc_direction(newmodel, t->role == SIMU_ROLE_SERVER);
|
|
set_channeldesc_name(newmodel, modelname);
|
|
random_channel(newmodel, false);
|
|
channel_desc_t *oldmodel = b->channel_model;
|
|
b->channel_model = newmodel;
|
|
free_channel_desc_scm(oldmodel);
|
|
prnt("%s: New model type %s applied to channel %s connected to sock %d\n", __func__, modeltype, modelname, i);
|
|
found = 1;
|
|
break;
|
|
}
|
|
} /* for */
|
|
if (found == 0)
|
|
prnt("%s: Channel %s not found or not currently used\n", __func__, modelname);
|
|
}
|
|
} else {
|
|
prnt("%s: ERROR: 2 parameters required: model name and model type (%i found)\n", __func__, s);
|
|
}
|
|
|
|
free(modelname);
|
|
free(modeltype);
|
|
return CMDSTATUS_FOUND;
|
|
}
|
|
|
|
static void getset_currentchannels_type(char *buf, int debug, webdatadef_t *tdata, telnet_printfunc_t prnt)
|
|
{
|
|
if (strncmp(buf, "set", 3) == 0) {
|
|
char cmd[256];
|
|
snprintf(cmd, sizeof(cmd), "setmodel %s %s", tdata->lines[0].val[1], tdata->lines[0].val[3]);
|
|
push_telnetcmd_func_t push_telnetcmd = (push_telnetcmd_func_t)get_shlibmodule_fptr("telnetsrv", TELNET_PUSHCMD_FNAME);
|
|
push_telnetcmd(setmodel_cmddef, cmd, prnt);
|
|
} else {
|
|
get_currentchannels_type("modify type", debug, tdata, prnt);
|
|
}
|
|
}
|
|
|
|
static int rfsimu_setdistance_cmd(char *buff, int debug, telnet_printfunc_t prnt, void *arg)
|
|
{
|
|
if (debug)
|
|
prnt("%s() buffer \"%s\"\n", __func__, buff);
|
|
|
|
char *modelname;
|
|
int distance;
|
|
int s = sscanf(buff, "%m[^ ] %d\n", &modelname, &distance);
|
|
if (s != 2) {
|
|
prnt("%s: require exact two parameters\n", __func__);
|
|
return CMDSTATUS_VARNOTFOUND;
|
|
}
|
|
|
|
rfsimulator_state_t *t = (rfsimulator_state_t *)arg;
|
|
const double sample_rate = t->sample_rate;
|
|
const double c = 299792458; /* 3e8 */
|
|
|
|
const uint64_t new_offset = (double)distance * sample_rate / c;
|
|
const double new_distance = (double)new_offset * c / sample_rate;
|
|
const double new_delay_ms = new_offset * 1000.0 / sample_rate;
|
|
|
|
prnt("\n%s: new_offset %lu, new (exact) distance %.3f m, new delay %f ms\n", __func__, new_offset, new_distance, new_delay_ms);
|
|
t->prop_delay_ms = new_delay_ms;
|
|
t->chan_offset = new_offset;
|
|
|
|
/* Set distance in rfsim and channel model, update channel and ringbuffer */
|
|
for (int i = 0; i < MAX_FD_RFSIMU; i++) {
|
|
buffer_t *b = &t->buf[i];
|
|
if (b->conn_sock <= 0 || b->channel_model == NULL || b->channel_model->model_name == NULL
|
|
|| strcmp(b->channel_model->model_name, modelname) != 0) {
|
|
if (b->channel_model != NULL && b->channel_model->model_name != NULL)
|
|
prnt(" %s: model %s unmodified\n", __func__, b->channel_model->model_name);
|
|
continue;
|
|
}
|
|
|
|
channel_desc_t *cd = b->channel_model;
|
|
cd->channel_offset = new_offset;
|
|
}
|
|
|
|
free(modelname);
|
|
|
|
return CMDSTATUS_FOUND;
|
|
}
|
|
|
|
static int rfsimu_getdistance_cmd(char *buff, int debug, telnet_printfunc_t prnt, void *arg)
|
|
{
|
|
if (debug)
|
|
prnt("%s() buffer \"%s\"\n", __func__, (buff != NULL) ? buff : "NULL");
|
|
|
|
rfsimulator_state_t *t = (rfsimulator_state_t *)arg;
|
|
const double sample_rate = t->sample_rate;
|
|
const double c = 299792458; /* 3e8 */
|
|
|
|
for (int i = 0; i < MAX_FD_RFSIMU; i++) {
|
|
buffer_t *b = &t->buf[i];
|
|
if (b->conn_sock <= 0 || b->channel_model == NULL || b->channel_model->model_name == NULL)
|
|
continue;
|
|
|
|
channel_desc_t *cd = b->channel_model;
|
|
const uint64_t offset = cd->channel_offset;
|
|
const double distance = (double)offset * c / sample_rate;
|
|
prnt("%s: %s offset %lu distance %.3f m\n", __func__, cd->model_name, offset, distance);
|
|
}
|
|
prnt("%s: <default> offset %lu delay %f ms\n", __func__, t->chan_offset, t->prop_delay_ms);
|
|
|
|
return CMDSTATUS_FOUND;
|
|
}
|
|
|
|
static int rfsimu_vtime_cmd(char *buff, int debug, telnet_printfunc_t prnt, void *arg)
|
|
{
|
|
rfsimulator_state_t *t = (rfsimulator_state_t *)arg;
|
|
const openair0_timestamp ts = t->nextRxTstamp;
|
|
const double sample_rate = t->sample_rate;
|
|
prnt("%s: vtime measurement: TS %llu sample_rate %.3f\n", __func__, ts, sample_rate);
|
|
return CMDSTATUS_FOUND;
|
|
}
|
|
|
|
static int startServer(openair0_device *device)
|
|
{
|
|
int sock = -1;
|
|
struct addrinfo *results = NULL;
|
|
struct addrinfo *rp = NULL;
|
|
|
|
rfsimulator_state_t *t = (rfsimulator_state_t *)device->priv;
|
|
t->role = SIMU_ROLE_SERVER;
|
|
|
|
char port[6];
|
|
snprintf(port, sizeof(port), "%d", t->port);
|
|
|
|
struct addrinfo hints = {
|
|
.ai_flags = AI_PASSIVE,
|
|
.ai_family = AF_INET6,
|
|
.ai_socktype = SOCK_STREAM,
|
|
};
|
|
|
|
int s = getaddrinfo(NULL, port, &hints, &results);
|
|
if (s != 0) {
|
|
LOG_E(HW, "getaddrinfo: %s\n", gai_strerror(s));
|
|
freeaddrinfo(results);
|
|
return -1;
|
|
}
|
|
|
|
int enable = 1;
|
|
int disable = 0;
|
|
for (rp = results; rp != NULL; rp = rp->ai_next) {
|
|
sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
|
|
if (sock == -1) {
|
|
continue;
|
|
}
|
|
|
|
if (setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &disable, sizeof(int)) != 0) {
|
|
continue;
|
|
}
|
|
|
|
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) != 0) {
|
|
continue;
|
|
}
|
|
|
|
if (bind(sock, rp->ai_addr, rp->ai_addrlen) == 0) {
|
|
break;
|
|
}
|
|
|
|
close(sock);
|
|
sock = -1;
|
|
}
|
|
|
|
freeaddrinfo(results);
|
|
|
|
if (sock <= 0) {
|
|
LOG_E(HW, "could not open a socket\n");
|
|
return -1;
|
|
}
|
|
t->listen_sock = sock;
|
|
|
|
if (listen(t->listen_sock, 5) != 0) {
|
|
LOG_E(HW, "listen() failed, errno(%d)\n", errno);
|
|
return -1;
|
|
}
|
|
struct epoll_event ev = {0};
|
|
ev.events = EPOLLIN;
|
|
ev.data.ptr = NULL;
|
|
if (epoll_ctl(t->epollfd, EPOLL_CTL_ADD, t->listen_sock, &ev) != 0) {
|
|
LOG_E(HW, "epoll_ctl(EPOLL_CTL_ADD) failed, errno(%d)\n", errno);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int client_try_connect(const char *host, uint16_t port)
|
|
{
|
|
int sock = -1;
|
|
int s;
|
|
struct addrinfo *result = NULL;
|
|
struct addrinfo *rp = NULL;
|
|
|
|
char dport[6];
|
|
snprintf(dport, sizeof(dport), "%d", port);
|
|
|
|
struct addrinfo hints = {
|
|
.ai_family = AF_UNSPEC,
|
|
.ai_socktype = SOCK_STREAM,
|
|
};
|
|
|
|
s = getaddrinfo(host, dport, &hints, &result);
|
|
if (s != 0) {
|
|
LOG_E(HW, "getaddrinfo: %s\n", gai_strerror(s));
|
|
return -1;
|
|
}
|
|
for (rp = result; rp != NULL; rp = rp->ai_next) {
|
|
sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
|
|
if (sock == -1) {
|
|
continue;
|
|
}
|
|
|
|
if (connect(sock, rp->ai_addr, rp->ai_addrlen) != -1) {
|
|
break;
|
|
}
|
|
|
|
close(sock);
|
|
sock = -1;
|
|
}
|
|
|
|
freeaddrinfo(result);
|
|
|
|
return sock;
|
|
}
|
|
|
|
static int startClient(openair0_device *device)
|
|
{
|
|
rfsimulator_state_t *t = static_cast<rfsimulator_state_t *>(device->priv);
|
|
t->role = SIMU_ROLE_CLIENT;
|
|
int sock;
|
|
|
|
while (true) {
|
|
LOG_I(HW, "Trying to connect to %s:%d\n", t->ip, t->port);
|
|
sock = client_try_connect(t->ip, t->port);
|
|
|
|
if (sock > 0) {
|
|
LOG_I(HW, "Connection to %s:%d established\n", t->ip, t->port);
|
|
break;
|
|
}
|
|
|
|
LOG_I(HW, "connect() to %s:%d failed, errno(%d)\n", t->ip, t->port, errno);
|
|
sleep(1);
|
|
}
|
|
|
|
if (setblocking(sock, notBlocking) == -1) {
|
|
return -1;
|
|
}
|
|
buffer_t *b = allocCirBuf(t, sock);
|
|
if (!b)
|
|
return -1;
|
|
// read a 1 sample block to initialize the current time
|
|
bool have_to_wait;
|
|
do {
|
|
have_to_wait = true;
|
|
flushInput(t, 3, true);
|
|
if (b->lastReceivedTS)
|
|
have_to_wait = false;
|
|
} while (have_to_wait);
|
|
if (b->lastReceivedTS > 0)
|
|
b->lastReceivedTS--;
|
|
t->nextRxTstamp = b->lastReceivedTS;
|
|
LOG_D(HW, "Client got first timestamp: starting at %lu\n", t->nextRxTstamp);
|
|
if (b->channel_model)
|
|
b->channel_model->start_TS = t->nextRxTstamp;
|
|
return 0;
|
|
}
|
|
|
|
static int rfsimulator_write_internal(rfsimulator_state_t *t,
|
|
openair0_timestamp timestamp,
|
|
void ***samplesVoid,
|
|
int nsamps,
|
|
int nbAnt,
|
|
uint64_t beam_map,
|
|
int flags)
|
|
{
|
|
mutexlock(t->Sockmutex);
|
|
LOG_D(HW, "Sending %d samples at time: %ld, nbAnt %d\n", nsamps, timestamp, nbAnt);
|
|
|
|
for (int i = 0; i < MAX_FD_RFSIMU; i++) {
|
|
buffer_t *b = &t->buf[i];
|
|
|
|
if (b->conn_sock >= 0) {
|
|
if (!nbAnt)
|
|
LOG_E(HW, "rfsimulator sending 0 tx antennas\n");
|
|
samplesBlockHeader_t header = {(uint32_t)nsamps, (uint32_t)nbAnt, (uint64_t)timestamp, 0, 0, beam_map};
|
|
fullwrite(b->conn_sock, &header, sizeof(header), t);
|
|
if (t->beam_ctrl->enable_beams) {
|
|
int num_beams = __builtin_popcountll(beam_map);
|
|
AssertFatal(num_beams > 0, "Must set at least one bit in beam_map\n");
|
|
sample_t tmpSamples[nsamps][num_beams][nbAnt];
|
|
for (int beam = 0; beam < num_beams; beam++) {
|
|
for (int a = 0; a < nbAnt; a++) {
|
|
sample_t *in = (sample_t *)samplesVoid[beam][a];
|
|
for (int s = 0; s < nsamps; s++)
|
|
tmpSamples[s][beam][a] = in[s];
|
|
}
|
|
}
|
|
fullwrite(b->conn_sock, (void *)tmpSamples, sampleToByte(nsamps, nbAnt * num_beams), t);
|
|
} else {
|
|
if (nbAnt == 1) {
|
|
fullwrite(b->conn_sock, samplesVoid[0][0], sampleToByte(nsamps, nbAnt), t);
|
|
} else {
|
|
sample_t tmpSamples[nsamps][nbAnt];
|
|
for (int a = 0; a < nbAnt; a++) {
|
|
sample_t *in = (sample_t *)samplesVoid[0][a];
|
|
for (int s = 0; s < nsamps; s++)
|
|
tmpSamples[s][a] = in[s];
|
|
}
|
|
fullwrite(b->conn_sock, (void *)tmpSamples, sampleToByte(nsamps, nbAnt), t);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (t->lastWroteTS != 0 && fabs((double)t->lastWroteTS - timestamp) > (double)CirSize)
|
|
LOG_W(HW, "Discontinuous TX gap too large Tx:%lu, %lu\n", t->lastWroteTS, timestamp);
|
|
|
|
if (t->lastWroteTS > timestamp)
|
|
LOG_W(HW, "Not supported to send Tx out of order %lu, %lu\n", t->lastWroteTS, timestamp);
|
|
|
|
if ((flags != TX_BURST_START) && (flags != TX_BURST_START_AND_END) && (t->lastWroteTS < timestamp))
|
|
LOG_W(HW,
|
|
"Gap in writing to USRP: last written %lu, now %lu, gap %lu\n",
|
|
t->lastWroteTS,
|
|
timestamp,
|
|
timestamp - t->lastWroteTS);
|
|
|
|
t->lastWroteTS = timestamp + nsamps;
|
|
mutexunlock(t->Sockmutex);
|
|
|
|
LOG_D(HW,
|
|
"Sent %d samples at time: %ld->%ld, energy in first antenna: %d\n",
|
|
nsamps,
|
|
timestamp,
|
|
timestamp + nsamps,
|
|
signal_energy(static_cast<int32_t *>(samplesVoid[0][0]), nsamps));
|
|
|
|
/* trace only first antenna */
|
|
T(T_USRP_TX_ANT0, T_INT(timestamp), T_BUFFER(samplesVoid[0], (int)sampleToByte(nsamps, 1)));
|
|
|
|
return nsamps;
|
|
}
|
|
|
|
static int rfsimulator_write_beams(openair0_device *device,
|
|
openair0_timestamp timestamp,
|
|
void ***samplesVoid,
|
|
int nsamps,
|
|
int nbAnt,
|
|
int num_beams,
|
|
int flags)
|
|
{
|
|
timestamp -= device->openair0_cfg->command_line_sample_advance;
|
|
int nsamps_initial = nsamps;
|
|
rfsimulator_state_t *t = static_cast<rfsimulator_state_t *>(device->priv);
|
|
void *samples[num_beams][nbAnt];
|
|
void **samples_ptr[num_beams];
|
|
for (int beam = 0; beam < num_beams; beam++) {
|
|
samples_ptr[beam] = samples[beam];
|
|
for (int aatx = 0; aatx < nbAnt; aatx++) {
|
|
samples[beam][aatx] = samplesVoid[beam][aatx];
|
|
}
|
|
}
|
|
while (nsamps > 0) {
|
|
uint32_t nsamps_beam_map;
|
|
uint64_t beam_map = get_beam_map(&t->beam_ctrl->tx, timestamp, nsamps, &nsamps_beam_map);
|
|
rfsimulator_write_internal(t, timestamp, samples_ptr, nsamps_beam_map, nbAnt, beam_map, flags);
|
|
for (int beam = 0; beam < num_beams; beam++) {
|
|
for (int aatx = 0; aatx < nbAnt; aatx++) {
|
|
char *ptr = (char *)samples_ptr[beam][aatx];
|
|
samples_ptr[beam][aatx] = (void *)(ptr + nsamps_beam_map * sizeof(sample_t));
|
|
}
|
|
}
|
|
timestamp += nsamps_beam_map;
|
|
nsamps -= nsamps_beam_map;
|
|
}
|
|
clear_beam_queue(&t->beam_ctrl->tx, timestamp + nsamps);
|
|
return nsamps_initial;
|
|
}
|
|
|
|
static int rfsimulator_write(openair0_device *device, openair0_timestamp timestamp, void **buff, int nsamps, int cc, int flags)
|
|
{
|
|
void **tmp = buff;
|
|
return rfsimulator_write_beams(device, timestamp, &tmp, nsamps, cc, 1, flags);
|
|
}
|
|
|
|
static bool add_client(rfsimulator_state_t *t)
|
|
{
|
|
struct sockaddr_storage sa = {0};
|
|
socklen_t socklen = sizeof(sa);
|
|
int conn_sock = accept(t->listen_sock, (struct sockaddr *)&sa, &socklen);
|
|
if (conn_sock == -1) {
|
|
LOG_E(HW, "accept() failed, errno(%d)\n", errno);
|
|
return false;
|
|
}
|
|
if (setblocking(conn_sock, notBlocking)) {
|
|
return false;
|
|
}
|
|
mutexlock(t->Sockmutex);
|
|
buffer_t *new_buf = allocCirBuf(t, conn_sock);
|
|
if (new_buf == NULL) {
|
|
mutexunlock(t->Sockmutex);
|
|
return false;
|
|
}
|
|
new_buf->lastReceivedTS = t->lastWroteTS;
|
|
char ip[INET6_ADDRSTRLEN];
|
|
getnameinfo((struct sockaddr *)&sa, socklen, ip, sizeof(ip), NULL, 0, NI_NUMERICHOST);
|
|
uint16_t port = ((struct sockaddr_in *)&sa)->sin_port;
|
|
LOG_I(HW, "Client connects from %s:%d\n", ip, port);
|
|
c16_t v = {0};
|
|
void *samplesVoid[t->tx_num_channels];
|
|
for (int i = 0; i < t->tx_num_channels; i++)
|
|
samplesVoid[i] = (void *)&v;
|
|
samplesBlockHeader_t header = {1, (uint32_t)t->tx_num_channels, (uint64_t)t->lastWroteTS, 0, 0, 1};
|
|
fullwrite(conn_sock, &header, sizeof(header), t);
|
|
fullwrite(conn_sock, samplesVoid, sampleToByte(1, t->tx_num_channels), t);
|
|
|
|
if (new_buf->channel_model)
|
|
new_buf->channel_model->start_TS = t->lastWroteTS;
|
|
mutexunlock(t->Sockmutex);
|
|
return true;
|
|
}
|
|
|
|
static void process_recv_header(rfsimulator_state_t *t, buffer_t *b, bool first_time)
|
|
{
|
|
// check the header and start block transfer
|
|
if (b->remainToTransfer != 0)
|
|
return;
|
|
|
|
b->headerMode = false; // We got the header
|
|
|
|
if (first_time) {
|
|
b->lastReceivedTS = b->th.timestamp;
|
|
b->trashingPacket = true;
|
|
} else {
|
|
if (b->lastReceivedTS < (int64_t)b->th.timestamp) {
|
|
// We have a transmission hole to fill, like TDD
|
|
// we create no signal samples up to the beginning of this reception
|
|
int nbAnt = b->th.nbAnt;
|
|
if (!nbAnt)
|
|
LOG_E(HW, "rfsimulator receive 0 rx antennas\n");
|
|
if (b->th.timestamp - b->lastReceivedTS < CirSize) {
|
|
// case we wrap at circular buffer end
|
|
for (uint64_t index = b->lastReceivedTS; index < b->th.timestamp; index++) {
|
|
for (int a = 0; a < nbAnt; a++) {
|
|
b->circularBuf[(index * nbAnt + a) % CirSize] = (c16_t){0};
|
|
}
|
|
}
|
|
} else {
|
|
// case no circular buffer wrap
|
|
memset(b->circularBuf, 0, sampleToByte(CirSize, 1));
|
|
}
|
|
b->lastReceivedTS = b->th.timestamp;
|
|
} else if (b->lastReceivedTS > (int64_t)b->th.timestamp) {
|
|
LOG_W(HW, "Received data in past: current is %lu, new reception: %lu!\n", b->lastReceivedTS, b->th.timestamp);
|
|
b->trashingPacket = true;
|
|
}
|
|
// verify the time gap is not too large
|
|
mutexlock(t->Sockmutex);
|
|
if (t->lastWroteTS != 0 && (fabs((double)t->lastWroteTS - b->lastReceivedTS) > (double)CirSize))
|
|
LOG_W(HW, "UEsock(%d) Tx/Rx shift too large Tx:%lu, Rx:%lu\n", b->conn_sock, t->lastWroteTS, b->lastReceivedTS);
|
|
mutexunlock(t->Sockmutex);
|
|
}
|
|
// move back the pointer to overwrite the header, we don't need it anymore
|
|
b->transferPtr = (char *)&b->circularBuf[(b->lastReceivedTS * b->th.nbAnt) % CirSize];
|
|
// we now need to read the samples
|
|
b->remainToTransfer = sampleToByte(b->th.size, b->th.nbAnt);
|
|
|
|
if (t->beam_ctrl->enable_beams) {
|
|
int num_beams = __builtin_popcountll(b->th.beam_map);
|
|
AssertFatal(num_beams > 0, "Needs at least one beam\n");
|
|
int num_ant = b->th.nbAnt;
|
|
int num_samples = b->th.size;
|
|
size_t beam_packet_size = sizeof(sample_t) * num_ant * num_beams * num_samples;
|
|
if (b->beamPtr == NULL || beam_packet_size > b->beam_ptr_sz) {
|
|
free(b->beamPtr);
|
|
b->beamPtr = static_cast<char *>(malloc(beam_packet_size));
|
|
}
|
|
b->remainToTransferBeam = beam_packet_size;
|
|
}
|
|
return;
|
|
}
|
|
|
|
static void process_recv(rfsimulator_state_t *t, buffer_t *b, bool first_time)
|
|
{
|
|
if (b->transferPtr == b->circularBufEnd)
|
|
b->transferPtr = (char *)b->circularBuf;
|
|
|
|
if (!b->trashingPacket) {
|
|
b->lastReceivedTS = b->th.timestamp + b->th.size - byteToSample(b->remainToTransfer, b->th.nbAnt);
|
|
LOG_D(HW, "UEsock: %d Set b->lastReceivedTS %ld\n", b->conn_sock, b->lastReceivedTS);
|
|
}
|
|
|
|
if (b->remainToTransfer == 0) {
|
|
LOG_D(HW, "UEsock: %d Completed block reception: %ld\n", b->conn_sock, b->lastReceivedTS);
|
|
b->headerMode = true;
|
|
b->transferPtr = (char *)&b->th;
|
|
b->remainToTransfer = sizeof(samplesBlockHeader_t);
|
|
b->trashingPacket = false;
|
|
}
|
|
}
|
|
|
|
static bool flushInput(rfsimulator_state_t *t, int timeout, bool first_time)
|
|
{
|
|
// Process all incoming events on sockets
|
|
// store the data in lists
|
|
struct epoll_event events[MAX_FD_RFSIMU] = {{0}};
|
|
int nfds = epoll_wait(t->epollfd, events, MAX_FD_RFSIMU, timeout);
|
|
|
|
if (nfds == -1) {
|
|
if (!(errno == EINTR || errno == EAGAIN))
|
|
LOG_W(HW, "epoll_wait() failed, errno(%d)\n", errno);
|
|
return false;
|
|
}
|
|
|
|
for (int nbEv = 0; nbEv < nfds; ++nbEv) {
|
|
buffer_t *b = static_cast<buffer_t *>(events[nbEv].data.ptr);
|
|
|
|
if (events[nbEv].events & EPOLLIN && b == NULL) {
|
|
bool ret = add_client(t);
|
|
if (!ret)
|
|
return ret;
|
|
continue;
|
|
} else {
|
|
if (events[nbEv].events & (EPOLLHUP | EPOLLERR | EPOLLRDHUP)) {
|
|
socketError(t, b);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (b->circularBuf == NULL) {
|
|
LOG_E(HW, "Received data on not connected socket %d\n", events[nbEv].data.fd);
|
|
continue;
|
|
}
|
|
|
|
ssize_t blockSz;
|
|
if (b->headerMode)
|
|
blockSz = b->remainToTransfer;
|
|
else {
|
|
blockSz =
|
|
b->transferPtr + b->remainToTransfer <= b->circularBufEnd ? b->remainToTransfer : b->circularBufEnd - b->transferPtr;
|
|
}
|
|
|
|
ssize_t sz = 0;
|
|
if (!b->headerMode && t->beam_ctrl->enable_beams) {
|
|
int num_beams = __builtin_popcountll(b->th.beam_map);
|
|
AssertFatal(num_beams > 0, "Needs at least one beam\n");
|
|
int num_ant = b->th.nbAnt;
|
|
int num_samples = b->th.size;
|
|
size_t beam_packet_size = sizeof(sample_t) * num_beams * num_ant * num_samples;
|
|
size_t bytes_received = beam_packet_size - b->remainToTransferBeam;
|
|
ssize_t recv_size = recv(b->conn_sock, &b->beamPtr[bytes_received], b->remainToTransferBeam, MSG_DONTWAIT);
|
|
if (recv_size <= 0) {
|
|
if (recv_size < 0 && errno != EAGAIN)
|
|
LOG_E(HW, "recv() failed, errno(%d)\n", errno);
|
|
continue;
|
|
}
|
|
b->remainToTransferBeam -= recv_size;
|
|
|
|
int beam_sample_sz = num_beams * num_ant * sizeof(sample_t);
|
|
int previously_written_sample = bytes_received / beam_sample_sz;
|
|
int last_full_sample = (bytes_received + recv_size) / beam_sample_sz;
|
|
int num_samples_received = last_full_sample - previously_written_sample;
|
|
int first_sample = previously_written_sample + 1;
|
|
openair0_timestamp sample_timestamp = b->th.timestamp + first_sample;
|
|
|
|
int tx_beam_ids[MAX_BEAMS];
|
|
int *tx_beam_ids_p = tx_beam_ids;
|
|
for (int i = 0; i < MAX_BEAMS; i++) {
|
|
if (b->th.beam_map & (1ULL << i)) {
|
|
*tx_beam_ids_p++ = i;
|
|
}
|
|
}
|
|
c16_t *in = (c16_t *)&b->beamPtr[first_sample * beam_sample_sz];
|
|
int num_samples_to_process = num_samples_received;
|
|
openair0_timestamp first_sample_timestamp = sample_timestamp;
|
|
while (num_samples_to_process > 0) {
|
|
uint32_t nsamps_out;
|
|
uint64_t rx_beam_map = get_beam_map(&t->beam_ctrl->rx, first_sample_timestamp, num_samples_to_process, &nsamps_out);
|
|
AssertFatal(__builtin_popcountll(rx_beam_map) == 1, "Only supports 1 beam\n");
|
|
int rx_beam_id = 0;
|
|
for (int i = 0; i < MAX_BEAMS; i++) {
|
|
if (rx_beam_map & (1ULL << i)) {
|
|
rx_beam_id = i;
|
|
break;
|
|
}
|
|
}
|
|
for (uint i = 0; i < nsamps_out; i++) {
|
|
for (int aatx = 0; aatx < num_ant; aatx++) {
|
|
c16_t *out = &b->circularBuf[((first_sample_timestamp + i) * num_ant + aatx) % CirSize];
|
|
cf_t sample = {.r = 0, .i = 0};
|
|
for (int beam = 0; beam < num_beams; beam++) {
|
|
float gain_dB = get_rx_gain_db(t, rx_beam_id, tx_beam_ids[beam]);
|
|
float gain_linear = powf(10, gain_dB / 20.0);
|
|
sample.r += in->r * gain_linear;
|
|
sample.i += in->i * gain_linear;
|
|
in++;
|
|
}
|
|
out->r = sample.r;
|
|
out->i = sample.i;
|
|
}
|
|
}
|
|
first_sample_timestamp += nsamps_out;
|
|
num_samples_to_process -= nsamps_out;
|
|
}
|
|
int antenna_samples_sz = num_ant * sizeof(sample_t);
|
|
// pretend we received some samples without beams
|
|
sz = num_samples_received * antenna_samples_sz;
|
|
} else {
|
|
sz = recv(b->conn_sock, b->transferPtr, blockSz, MSG_DONTWAIT);
|
|
if (sz <= 0) {
|
|
if (sz < 0 && errno != EAGAIN)
|
|
LOG_E(HW, "recv() failed, errno(%d)\n", errno);
|
|
continue;
|
|
}
|
|
}
|
|
LOG_D(HW, "Socket rcv %zd bytes\n", sz);
|
|
b->remainToTransfer -= sz;
|
|
b->transferPtr += sz;
|
|
if (b->headerMode)
|
|
process_recv_header(t, b, first_time);
|
|
else
|
|
process_recv(t, b, first_time);
|
|
}
|
|
return nfds > 0;
|
|
}
|
|
|
|
static int rfsimulator_read(openair0_device *device, openair0_timestamp *ptimestamp, void **samplesVoid, int nsamps, int nbAnt)
|
|
{
|
|
rfsimulator_state_t *t = static_cast<rfsimulator_state_t *>(device->priv);
|
|
LOG_D(HW,
|
|
"Enter rfsimulator_read, expect %d samples, will release at TS: %ld, nbAnt %d\n",
|
|
nsamps,
|
|
t->nextRxTstamp + nsamps,
|
|
nbAnt);
|
|
|
|
// deliver data from received data
|
|
// check if a UE is connected
|
|
int first_sock;
|
|
|
|
for (first_sock = 0; first_sock < MAX_FD_RFSIMU; first_sock++)
|
|
if (t->buf[first_sock].circularBuf != NULL)
|
|
break;
|
|
|
|
if (first_sock == MAX_FD_RFSIMU) {
|
|
// no connected device (we are eNB, no UE is connected)
|
|
if (t->nextRxTstamp == 0)
|
|
LOG_I(HW, "No connected device, generating void samples...\n");
|
|
|
|
if (!flushInput(t, t->wait_timeout, false)) {
|
|
for (int x = 0; x < nbAnt; x++)
|
|
memset(samplesVoid[x], 0, sampleToByte(nsamps, 1));
|
|
|
|
t->nextRxTstamp += nsamps;
|
|
|
|
if (((t->nextRxTstamp / nsamps) % 100) == 0)
|
|
LOG_D(HW, "No UE, Generating void samples for Rx: %ld\n", t->nextRxTstamp);
|
|
|
|
*ptimestamp = t->nextRxTstamp - nsamps;
|
|
return nsamps;
|
|
}
|
|
} else {
|
|
bool have_to_wait;
|
|
do {
|
|
have_to_wait = false;
|
|
buffer_t *b = NULL;
|
|
for (int sock = 0; sock < MAX_FD_RFSIMU; sock++) {
|
|
b = &t->buf[sock];
|
|
if (b->circularBuf && (t->nextRxTstamp + nsamps) > b->lastReceivedTS) {
|
|
have_to_wait = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (have_to_wait) {
|
|
LOG_D(HW,
|
|
"Waiting on socket, current last ts: %ld, expected at least : %ld\n",
|
|
b->lastReceivedTS,
|
|
t->nextRxTstamp + nsamps);
|
|
flushInput(t, 3, false);
|
|
}
|
|
} while (have_to_wait);
|
|
}
|
|
|
|
struct timespec start_time;
|
|
int ret = clock_gettime(CLOCK_REALTIME, &start_time);
|
|
AssertFatal(ret == 0, "clock_gettime() failed: errno %d, %s\n", errno, strerror(errno));
|
|
|
|
if (t->poll_telnetcmdq)
|
|
t->poll_telnetcmdq(t->telnetcmd_qid, t);
|
|
|
|
// Clear the output buffer
|
|
for (int a = 0; a < nbAnt; a++)
|
|
memset(samplesVoid[a], 0, sampleToByte(nsamps, 1));
|
|
cf_t temp_array[nbAnt][nsamps];
|
|
bool apply_noise_per_channel = get_noise_power_dBFS() == INVALID_DBFS_VALUE;
|
|
int num_chanmod_channels = 0;
|
|
// Add all input nodes signal in the output buffer
|
|
for (int sock = 0; sock < MAX_FD_RFSIMU; sock++) {
|
|
buffer_t *ptr = &t->buf[sock];
|
|
|
|
if (ptr->circularBuf && !ptr->trashingPacket) {
|
|
bool reGenerateChannel = false;
|
|
|
|
// fixme: when do we regenerate
|
|
// it seems legacy behavior is: never in UL, each frame in DL
|
|
if (reGenerateChannel)
|
|
random_channel(ptr->channel_model, 0);
|
|
|
|
for (int a_rx = 0; a_rx < nbAnt; a_rx++) { // loop over number of Rx antennas
|
|
if (ptr->channel_model != NULL) { // apply a channel model
|
|
if (num_chanmod_channels == 0) {
|
|
memset(temp_array, 0, sizeof(temp_array));
|
|
}
|
|
num_chanmod_channels++;
|
|
rxAddInput(ptr->circularBuf,
|
|
temp_array[a_rx],
|
|
a_rx,
|
|
ptr->channel_model,
|
|
nsamps,
|
|
t->nextRxTstamp,
|
|
CirSize,
|
|
apply_noise_per_channel);
|
|
} else { // no channel modeling
|
|
int nbAnt_tx = ptr->th.nbAnt; // number of Tx antennas
|
|
int firstIndex = (CirSize + t->nextRxTstamp - t->chan_offset) % CirSize;
|
|
sample_t *out = (sample_t *)samplesVoid[a_rx];
|
|
if (nbAnt_tx == 1 && t->nb_cnx == 1) { // optimized for 1 Tx and 1 UE
|
|
sample_t *firstSample = (sample_t *)&(ptr->circularBuf[firstIndex]);
|
|
if ((uint64_t)firstIndex + (uint64_t)nsamps > CirSize) {
|
|
int tailSz = CirSize - firstIndex;
|
|
memcpy(out, firstSample, sampleToByte(tailSz, 1));
|
|
memcpy(out + tailSz, &ptr->circularBuf[0], sampleToByte(nsamps - tailSz, 1));
|
|
} else {
|
|
memcpy(out, firstSample, sampleToByte(nsamps, 1));
|
|
}
|
|
} else {
|
|
// SIMD (with simde) optimization might be added here later
|
|
LOG_D(HW, "nbAnt_tx %d nbAnt %d\n", nbAnt_tx, nbAnt);
|
|
double H_awgn_mimo_coeff[nbAnt_tx];
|
|
for (int a_tx = 0; a_tx < nbAnt_tx; a_tx++) {
|
|
uint32_t ant_diff = abs(a_tx - a_rx);
|
|
H_awgn_mimo_coeff[a_tx] = ant_diff ? (0.2 / ant_diff) : 1.0;
|
|
}
|
|
for (int i = 0; i < nsamps; i++) { // loop over nsamps
|
|
for (int a_tx = 0; a_tx < nbAnt_tx; a_tx++) { // sum up signals from nbAnt_tx antennas
|
|
out[i].r += (short)(ptr->circularBuf[((firstIndex + i) * nbAnt_tx + a_tx) % CirSize].r * H_awgn_mimo_coeff[a_tx]);
|
|
out[i].i += (short)(ptr->circularBuf[((firstIndex + i) * nbAnt_tx + a_tx) % CirSize].i * H_awgn_mimo_coeff[a_tx]);
|
|
} // end for a_tx
|
|
} // end for i (number of samps)
|
|
} // end of 1 tx antenna optimization
|
|
} // end of no channel modeling
|
|
} // end for a (number of rx antennas)
|
|
}
|
|
}
|
|
if (apply_noise_per_channel && num_chanmod_channels > 0) {
|
|
// Noise is already applied through the channel model
|
|
for (int a = 0; a < nbAnt; a++) {
|
|
sample_t *out = (sample_t *)samplesVoid[a];
|
|
for (int i = 0; i < nsamps; i++) {
|
|
out[i].r += lroundf(temp_array[a][i].r);
|
|
out[i].i += lroundf(temp_array[a][i].i);
|
|
}
|
|
}
|
|
} else if (num_chanmod_channels > 0) {
|
|
// Apply noise from global setting
|
|
int16_t noise_power = (int16_t)(32767.0 / powf(10.0, .05 * -get_noise_power_dBFS()));
|
|
for (int a = 0; a < nbAnt; a++) {
|
|
sample_t *out = (sample_t *)samplesVoid[a];
|
|
for (int i = 0; i < nsamps; i++) {
|
|
out[i].r += lroundf(temp_array[a][i].r + noise_power * gaussZiggurat(0.0, 1.0));
|
|
out[i].i += lroundf(temp_array[a][i].i + noise_power * gaussZiggurat(0.0, 1.0));
|
|
}
|
|
}
|
|
}
|
|
|
|
struct timespec end_time;
|
|
ret = clock_gettime(CLOCK_REALTIME, &end_time);
|
|
AssertFatal(ret == 0, "clock_gettime() failed: errno %d, %s\n", errno, strerror(errno));
|
|
double diff_ns = (end_time.tv_sec - start_time.tv_sec) * 1000000000 + (end_time.tv_nsec - start_time.tv_nsec);
|
|
static double average = 0.0;
|
|
average = (average * 0.98) + (nsamps / (diff_ns / 1e9) * 0.02);
|
|
static int calls = 0;
|
|
if (calls++ % 10000 == 0) {
|
|
LOG_D(HW, "Rfsimulator: velocity %.2f Msps, realtime requirements %.2f Msps\n", average / 1e6, t->sample_rate / 1e6);
|
|
}
|
|
|
|
*ptimestamp = t->nextRxTstamp; // return the time of the first sample
|
|
t->nextRxTstamp += nsamps;
|
|
clear_beam_queue(&t->beam_ctrl->rx, t->nextRxTstamp);
|
|
LOG_D(HW,
|
|
"Rx to upper layer: %d from %ld to %ld, energy in first antenna %d\n",
|
|
nsamps,
|
|
*ptimestamp,
|
|
t->nextRxTstamp,
|
|
signal_energy(static_cast<int32_t *>(samplesVoid[0]), nsamps));
|
|
|
|
/* trace only first antenna */
|
|
T(T_USRP_RX_ANT0, T_INT(t->nextRxTstamp), T_BUFFER(samplesVoid[0], (int)sampleToByte(nsamps, 1)));
|
|
|
|
return nsamps;
|
|
}
|
|
|
|
static int rfsimulator_get_stats(openair0_device *device)
|
|
{
|
|
return 0;
|
|
}
|
|
static int rfsimulator_reset_stats(openair0_device *device)
|
|
{
|
|
return 0;
|
|
}
|
|
static void rfsimulator_end(openair0_device *device)
|
|
{
|
|
rfsimulator_state_t *s = static_cast<rfsimulator_state_t *>(device->priv);
|
|
for (int i = 0; i < MAX_FD_RFSIMU; i++) {
|
|
buffer_t *b = &s->buf[i];
|
|
if (b->conn_sock >= 0)
|
|
removeCirBuf(s, b);
|
|
}
|
|
close(s->epollfd);
|
|
free(s);
|
|
}
|
|
static void stopServer(openair0_device *device)
|
|
{
|
|
rfsimulator_state_t *t = (rfsimulator_state_t *)device->priv;
|
|
DevAssert(t != NULL);
|
|
close(t->listen_sock);
|
|
rfsimulator_end(device);
|
|
}
|
|
|
|
static int rfsimulator_stop(openair0_device *device)
|
|
{
|
|
return 0;
|
|
}
|
|
static int rfsimulator_set_freq(openair0_device *device, openair0_config_t *openair0_cfg)
|
|
{
|
|
rfsimulator_state_t *s = static_cast<rfsimulator_state_t *>(device->priv);
|
|
s->rx_freq = openair0_cfg->rx_freq[0];
|
|
return 0;
|
|
}
|
|
static int rfsimulator_set_gains(openair0_device *device, openair0_config_t *openair0_cfg)
|
|
{
|
|
return 0;
|
|
}
|
|
static int rfsimulator_write_init(openair0_device *device)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
extern "C" __attribute__((__visibility__("default"))) int device_init(openair0_device *device, openair0_config_t *openair0_cfg)
|
|
{
|
|
// to change the log level, use this on command line
|
|
// --log_config.hw_log_level debug
|
|
rfsimulator_state_t *rfsimulator = static_cast<rfsimulator_state_t *>(calloc(sizeof(rfsimulator_state_t), 1));
|
|
// initialize channel simulation
|
|
rfsimulator->tx_num_channels = openair0_cfg->tx_num_channels;
|
|
rfsimulator->rx_num_channels = openair0_cfg->rx_num_channels;
|
|
rfsimulator->sample_rate = openair0_cfg->sample_rate;
|
|
rfsimulator->rx_freq = openair0_cfg->rx_freq[0];
|
|
rfsimulator->tx_bw = openair0_cfg->tx_bw;
|
|
rfsimulator->beam_ctrl = new rfsim_beam_ctrl_t;
|
|
rfsimulator_readconfig(rfsimulator);
|
|
if (rfsimulator->prop_delay_ms > 0.0)
|
|
rfsimulator->chan_offset = ceil(rfsimulator->sample_rate * rfsimulator->prop_delay_ms / 1000);
|
|
if (rfsimulator->chan_offset != 0) {
|
|
if (CirSize < minCirSize + rfsimulator->chan_offset) {
|
|
CirSize = minCirSize + rfsimulator->chan_offset;
|
|
LOG_I(HW, "CirSize = %lu\n", CirSize);
|
|
}
|
|
rfsimulator->prop_delay_ms = rfsimulator->chan_offset * 1000 / rfsimulator->sample_rate;
|
|
LOG_I(HW, "propagation delay %f ms, %lu samples\n", rfsimulator->prop_delay_ms, rfsimulator->chan_offset);
|
|
}
|
|
mutexinit(rfsimulator->Sockmutex);
|
|
LOG_I(HW,
|
|
"Running as %s\n",
|
|
rfsimulator->role == SIMU_ROLE_SERVER ? "server waiting opposite rfsimulators to connect"
|
|
: "client: will connect to a rfsimulator server side");
|
|
device->trx_start_func = rfsimulator->role == SIMU_ROLE_SERVER ? startServer : startClient;
|
|
device->trx_get_stats_func = rfsimulator_get_stats;
|
|
device->trx_reset_stats_func = rfsimulator_reset_stats;
|
|
device->trx_end_func = rfsimulator->role == SIMU_ROLE_SERVER ? stopServer : rfsimulator_end;
|
|
device->trx_stop_func = rfsimulator_stop;
|
|
device->trx_set_freq_func = rfsimulator_set_freq;
|
|
device->trx_set_gains_func = rfsimulator_set_gains;
|
|
device->trx_write_func = rfsimulator_write;
|
|
device->trx_write_beams_func = rfsimulator_write_beams;
|
|
device->trx_read_func = rfsimulator_read;
|
|
/* let's pretend to be a b2x0 */
|
|
device->type = RFSIMULATOR;
|
|
openair0_cfg[0].rx_gain[0] = 0;
|
|
device->openair0_cfg = &openair0_cfg[0];
|
|
device->priv = rfsimulator;
|
|
device->trx_write_init = rfsimulator_write_init;
|
|
device->trx_set_beams = rfsimulator_set_beams;
|
|
|
|
for (int i = 0; i < MAX_FD_RFSIMU; i++)
|
|
rfsimulator->buf[i].conn_sock = -1;
|
|
rfsimulator->next_buf = 0;
|
|
|
|
AssertFatal((rfsimulator->epollfd = epoll_create1(0)) != -1, "epoll_create1() failed, errno(%d)", errno);
|
|
// we need to call randominit() for telnet server (use gaussdouble=>uniformrand)
|
|
randominit();
|
|
set_taus_seed(0);
|
|
/* look for telnet server, if it is loaded, add the channel modeling commands to it */
|
|
add_telnetcmd_func_t addcmd = (add_telnetcmd_func_t)get_shlibmodule_fptr("telnetsrv", TELNET_ADDCMD_FNAME);
|
|
|
|
if (addcmd != NULL) {
|
|
rfsimulator->poll_telnetcmdq = (poll_telnetcmdq_func_t)get_shlibmodule_fptr("telnetsrv", TELNET_POLLCMDQ_FNAME);
|
|
addcmd("rfsimu", rfsimu_vardef, rfsimu_cmdarray);
|
|
|
|
for (int i = 0; rfsimu_cmdarray[i].cmdfunc != NULL; i++) {
|
|
if (rfsimu_cmdarray[i].qptr != NULL) {
|
|
rfsimulator->telnetcmd_qid = rfsimu_cmdarray[i].qptr;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* write on a socket fails if the other end is closed and we get SIGPIPE */
|
|
if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) {
|
|
perror("SIGPIPE");
|
|
exit(1);
|
|
}
|
|
|
|
return 0;
|
|
}
|