From 6bd3545a78034179db88873686e10150b272c3c9 Mon Sep 17 00:00:00 2001 From: Bartosz Podrygajlo Date: Wed, 8 Oct 2025 22:38:48 +0200 Subject: [PATCH 01/19] RFsimulator C++ conversion Convert rfsimulator C source code to C++ to access to STL. --- common/config/config_cmdline.c | 2 +- common/config/config_common.c | 18 +++--- common/config/config_common.h | 16 +++--- common/config/config_load_configmodule.c | 2 +- common/config/config_load_configmodule.h | 4 +- common/config/config_paramdesc.h | 4 +- common/config/config_userapi.c | 8 +-- common/config/config_userapi.h | 10 ++-- common/config/yaml/tests/test_yaml_config.cpp | 3 +- common/utils/load_module_shlib.c | 2 +- common/utils/load_module_shlib.h | 2 +- common/utils/telnetsrv/telnetsrv.h | 4 +- openair1/SIMULATION/TOOLS/random_channel.c | 10 ++-- openair1/SIMULATION/TOOLS/sim.h | 2 +- radio/rfsimulator/CMakeLists.txt | 2 +- .../{simulator.c => simulator.cpp} | 55 ++++++++++--------- 16 files changed, 73 insertions(+), 71 deletions(-) rename radio/rfsimulator/{simulator.c => simulator.cpp} (95%) diff --git a/common/config/config_cmdline.c b/common/config/config_cmdline.c index c3f3d5ae21..f8b10ad14b 100644 --- a/common/config/config_cmdline.c +++ b/common/config/config_cmdline.c @@ -213,7 +213,7 @@ int config_check_unknown_cmdlineopt(configmodule_interface_t *cfg, char *prefix) return unknowndetected; } /* config_check_unknown_cmdlineopt */ -int config_process_cmdline(configmodule_interface_t *cfg, paramdef_t *cfgoptions, int numoptions, char *prefix) +int config_process_cmdline(configmodule_interface_t *cfg, paramdef_t *cfgoptions, int numoptions, const char *prefix) { int c = cfg->argc; int i,j; diff --git a/common/config/config_common.c b/common/config/config_common.c index c617c426d7..0b4b8f76d6 100644 --- a/common/config/config_common.c +++ b/common/config/config_common.c @@ -118,7 +118,7 @@ void *config_allocate_new(configmodule_interface_t *cfg, int sz, bool autoFree) return ptr; } -int config_setdefault_int(configmodule_interface_t *cfg, paramdef_t *cfgoptions, char *prefix) +int config_setdefault_int(configmodule_interface_t *cfg, paramdef_t *cfgoptions, const char *prefix) { int status = 0; config_check_valptr(cfg, cfgoptions, sizeof(*cfgoptions->iptr), 1); @@ -132,7 +132,7 @@ int config_setdefault_int(configmodule_interface_t *cfg, paramdef_t *cfgoptions, return status; } -int config_setdefault_int64(configmodule_interface_t *cfg, paramdef_t *cfgoptions, char *prefix) +int config_setdefault_int64(configmodule_interface_t *cfg, paramdef_t *cfgoptions, const char *prefix) { int status = 0; config_check_valptr(cfg, cfgoptions, sizeof(*cfgoptions->i64ptr), 1); @@ -150,7 +150,7 @@ int config_setdefault_int64(configmodule_interface_t *cfg, paramdef_t *cfgoption return status; } -int config_setdefault_intlist(configmodule_interface_t *cfg, paramdef_t *cfgoptions, char *prefix) +int config_setdefault_intlist(configmodule_interface_t *cfg, paramdef_t *cfgoptions, const char *prefix) { int status = 0; @@ -167,7 +167,7 @@ int config_setdefault_intlist(configmodule_interface_t *cfg, paramdef_t *cfgopti return status; } -int config_setdefault_string(configmodule_interface_t *cfg, paramdef_t *cfgoptions, char *prefix) +int config_setdefault_string(configmodule_interface_t *cfg, paramdef_t *cfgoptions, const char *prefix) { int status = 0; @@ -190,7 +190,7 @@ int config_setdefault_string(configmodule_interface_t *cfg, paramdef_t *cfgoptio return status; } -int config_setdefault_stringlist(configmodule_interface_t *cfg, paramdef_t *cfgoptions, char *prefix) +int config_setdefault_stringlist(configmodule_interface_t *cfg, paramdef_t *cfgoptions, const char *prefix) { int status = 0; @@ -210,7 +210,7 @@ int config_setdefault_stringlist(configmodule_interface_t *cfg, paramdef_t *cfgo return status; } -int config_setdefault_double(configmodule_interface_t *cfg, paramdef_t *cfgoptions, char *prefix) +int config_setdefault_double(configmodule_interface_t *cfg, paramdef_t *cfgoptions, const char *prefix) { int status = 0; config_check_valptr(cfg, cfgoptions, sizeof(*cfgoptions->dblptr), 1); @@ -224,7 +224,7 @@ int config_setdefault_double(configmodule_interface_t *cfg, paramdef_t *cfgoptio return status; } -int config_assign_ipv4addr(configmodule_interface_t *cfg, paramdef_t *cfgoptions, char *ipv4addr) +int config_assign_ipv4addr(configmodule_interface_t *cfg, paramdef_t *cfgoptions, const char *ipv4addr) { config_check_valptr(cfg, cfgoptions, sizeof(*cfgoptions->uptr), 1); int rst=inet_pton(AF_INET, ipv4addr,cfgoptions->uptr ); @@ -246,7 +246,7 @@ int config_assign_ipv4addr(configmodule_interface_t *cfg, paramdef_t *cfgoptions return 0; } -int config_setdefault_ipv4addr(configmodule_interface_t *cfg, paramdef_t *cfgoptions, char *prefix) +int config_setdefault_ipv4addr(configmodule_interface_t *cfg, paramdef_t *cfgoptions, const char *prefix) { int status = 0; @@ -322,7 +322,7 @@ void config_assign_int(configmodule_interface_t *cfg, paramdef_t *cfgoptions, ch * @param[in] prefix Optional prefix for the parameter name (can be NULL). * @return 1 if the default value was set, 0 if ignored, or -1 on error/unsupported type. */ -int config_common_getdefault(configmodule_interface_t *cfg, paramdef_t *cfgoption, char *prefix) +int config_common_getdefault(configmodule_interface_t *cfg, paramdef_t *cfgoption, const char *prefix) { if( (cfgoption->paramflags & PARAMFLAG_DONOTREAD) != 0) { return 0; diff --git a/common/config/config_common.h b/common/config/config_common.h index 00211c9519..0400e703c7 100644 --- a/common/config/config_common.h +++ b/common/config/config_common.h @@ -41,16 +41,16 @@ extern "C" #endif void config_check_valptr(configmodule_interface_t *cfg, paramdef_t *cfgoptions, int elt_sz, int nb_elt); /* functions to set a parameter to its default value */ -int config_setdefault_int(configmodule_interface_t *cfg, paramdef_t *cfgoptions, char *prefix); -int config_setdefault_int64(configmodule_interface_t *cfg, paramdef_t *cfgoptions, char *prefix); -int config_setdefault_intlist(configmodule_interface_t *cfg, paramdef_t *cfgoptions, char *prefix); -int config_setdefault_string(configmodule_interface_t *cfg, paramdef_t *cfgoptions, char *prefix); -int config_setdefault_stringlist(configmodule_interface_t *cfg, paramdef_t *cfgoptions, char *prefix); -int config_setdefault_double(configmodule_interface_t *cfg, paramdef_t *cfgoptions, char *prefix); -int config_setdefault_ipv4addr(configmodule_interface_t *cfg, paramdef_t *cfgoptions, char *prefix); +int config_setdefault_int(configmodule_interface_t *cfg, paramdef_t *cfgoptions, const char *prefix); +int config_setdefault_int64(configmodule_interface_t *cfg, paramdef_t *cfgoptions, const char *prefix); +int config_setdefault_intlist(configmodule_interface_t *cfg, paramdef_t *cfgoptions, const char *prefix); +int config_setdefault_string(configmodule_interface_t *cfg, paramdef_t *cfgoptions, const char *prefix); +int config_setdefault_stringlist(configmodule_interface_t *cfg, paramdef_t *cfgoptions, const char *prefix); +int config_setdefault_double(configmodule_interface_t *cfg, paramdef_t *cfgoptions, const char *prefix); +int config_setdefault_ipv4addr(configmodule_interface_t *cfg, paramdef_t *cfgoptions, const char *prefix); void *config_allocate_new(configmodule_interface_t *cfg, int sz, bool autoFree); void config_assign_int(configmodule_interface_t *cfg, paramdef_t *cfgoptions, char *fullname, int val); -int config_common_getdefault(configmodule_interface_t *cfg, paramdef_t *cfgoption, char *prefix); +int config_common_getdefault(configmodule_interface_t *cfg, paramdef_t *cfgoption, const char *prefix); #ifdef __cplusplus } diff --git a/common/config/config_load_configmodule.c b/common/config/config_load_configmodule.c index f5dfe6137f..5d8bb2cfcc 100644 --- a/common/config/config_load_configmodule.c +++ b/common/config/config_load_configmodule.c @@ -150,7 +150,7 @@ int config_cmdlineonly_getlist(configmodule_interface_t *cfg, return 0; } -int config_cmdlineonly_get(configmodule_interface_t *cfg, paramdef_t *cfgoptions, int numoptions, char *prefix) +int config_cmdlineonly_get(configmodule_interface_t *cfg, paramdef_t *cfgoptions, int numoptions, const char *prefix) { int defval; int fatalerror=0; diff --git a/common/config/config_load_configmodule.h b/common/config/config_load_configmodule.h index d5f87d0f8b..2c45f788d4 100644 --- a/common/config/config_load_configmodule.h +++ b/common/config/config_load_configmodule.h @@ -59,10 +59,10 @@ #define CONFIG_ABORT (1<<21) // config failed,abort execution #define CONFIG_NOOOPT (1<<22) // no -O option found when parsing command line struct configmodule_interface; -typedef int (*configmodule_getfunc_t)(struct configmodule_interface *, paramdef_t *, int numparams, char *prefix); +typedef int (*configmodule_getfunc_t)(struct configmodule_interface *, paramdef_t *, int numparams, const char *prefix); typedef int ( *configmodule_getlistfunc_t)(struct configmodule_interface *, paramlist_def_t *, paramdef_t *, int numparams, char *prefix); -typedef int (*configmodule_setfunc_t)(paramdef_t *cfgoptions, int numoptions, char *prefix); +typedef int (*configmodule_setfunc_t)(paramdef_t *cfgoptions, int numoptions, const char *prefix); typedef void (*configmodule_endfunc_t)(struct configmodule_interface *cfg); typedef int (*configmodule_initfunc_t)(struct configmodule_interface *cfg); diff --git a/common/config/config_paramdesc.h b/common/config/config_paramdesc.h index 8ddd507ee6..904b9dfcb0 100644 --- a/common/config/config_paramdesc.h +++ b/common/config/config_paramdesc.h @@ -105,7 +105,7 @@ typedef union checkedparam { #define DEFAULT_EXTRA_SZ 256 typedef struct paramdef { char optname[MAX_OPTNAME_SIZE]; /* parameter name, can be used as long command line option */ - char *helpstr; /* help string */ + const char *helpstr; /* help string */ unsigned int paramflags; /* value is a "ored" combination of above PARAMFLAG_XXXX values */ union { /* pointer to the parameter value, completed by the config module */ char **strptr; @@ -122,7 +122,7 @@ typedef struct paramdef { void *voidptr; } ; union { /* default parameter value, to be used when PARAMFLAG_MANDATORY is not specified */ - char *defstrval; + const char *defstrval; char **defstrlistval; uint32_t defuintval; int defintval; diff --git a/common/config/config_userapi.c b/common/config/config_userapi.c index 9f7477f5cd..1a831d28c5 100644 --- a/common/config/config_userapi.c +++ b/common/config/config_userapi.c @@ -71,7 +71,7 @@ int config_get_processedint(configmodule_interface_t *cfg, paramdef_t *cfgoption return ret; } -void config_printhelp(paramdef_t *params,int numparams, char *prefix) { +void config_printhelp(paramdef_t *params,int numparams, const char *prefix) { printf("\n-----Help for section %-26s: %03i entries------\n",(prefix==NULL)?"(root section)":prefix,numparams); for (int i=0 ; irtflags |= P; } #define CONFIG_CLEARRTFLAG(P) if (config_get_if()) { config_get_if()->rtflags &= (~P); } #define CONFIG_ISPARAMFLAGSET(P,F) ( !!(P.paramflags & F)) -int config_paramidx_fromname(paramdef_t *params, int numparams, char *name); +int config_paramidx_fromname(paramdef_t *params, int numparams, const char *name); /* utility functions, to be used by configuration module and/or configuration libraries */ -void config_printhelp(paramdef_t *, int numparams, char *prefix); -int config_process_cmdline(configmodule_interface_t *cfg, paramdef_t *params, int numparams, char *prefix); -int config_assign_ipv4addr(configmodule_interface_t *cfg, paramdef_t *cfgoptions, char *ipv4addr); +void config_printhelp(paramdef_t *, int numparams, const char *prefix); +int config_process_cmdline(configmodule_interface_t *cfg, paramdef_t *params, int numparams, const char *prefix); +int config_assign_ipv4addr(configmodule_interface_t *cfg, paramdef_t *cfgoptions, const char *ipv4addr); /* apis to get/check parameters, to be used by oai modules, at configuration time */ #define CONFIG_CHECKALLSECTIONS "ALLSECTIONS" int config_check_unknown_cmdlineopt(configmodule_interface_t *cfg, char *prefix); -int config_get(configmodule_interface_t *cfg, paramdef_t *params, int numparams, char *prefix); +int config_get(configmodule_interface_t *cfg, paramdef_t *params, int numparams, const char *prefix); int config_getlist(configmodule_interface_t *cfg, paramlist_def_t *ParamList, paramdef_t *params, int numparams, char *prefix); /* apis to set some of the paramdef_t fields before using the get/getlist api's */ diff --git a/common/config/yaml/tests/test_yaml_config.cpp b/common/config/yaml/tests/test_yaml_config.cpp index 814e3a3635..b2bd53e349 100644 --- a/common/config/yaml/tests/test_yaml_config.cpp +++ b/common/config/yaml/tests/test_yaml_config.cpp @@ -411,7 +411,7 @@ TEST(yaml_config, test_read_ipv4) { config_yaml_get(cfg, &p, 1, prefix); printf("%x\n", addr); - p.defstrval = strdup("10.0.0.1"); + p.defstrval = "10.0.0.1"; strncpy(p.optname, "ipv4_3", sizeof(p.optname) - 1); config_yaml_get(cfg, &p, 1, prefix); printf("%x\n", addr); @@ -419,7 +419,6 @@ TEST(yaml_config, test_read_ipv4) { config_yaml_end(cfg); free(cfg->cfgP[0]); end_configmodule(cfg); - free(p.defstrval); } TEST(yaml_config, yaml_read_str_as_int) { diff --git a/common/utils/load_module_shlib.c b/common/utils/load_module_shlib.c index 825012ce6a..13eec39776 100644 --- a/common/utils/load_module_shlib.c +++ b/common/utils/load_module_shlib.c @@ -252,7 +252,7 @@ load_module_shlib_exit: return ret; } -void * get_shlibmodule_fptr(char *modname, char *fname) +void * get_shlibmodule_fptr(const char *modname, const char *fname) { for (int i=0; i" static telnetshell_cmddef_t channelmod_cmdarray[] = { @@ -1696,7 +1696,7 @@ channel_desc_t *new_channel_desc_scm(uint8_t nb_tx, return(chan_desc); } /* channel_desc_t *new_channel_desc_scm */ -channel_desc_t *find_channel_desc_fromname( char *modelname ) { +channel_desc_t *find_channel_desc_fromname(const char *modelname) { for(int i=0; imodel_name,modelname) == 0) @@ -2029,7 +2029,7 @@ static int channelmod_print_help(char *buff, int debug, telnet_printfunc_t prnt static char *pnames[] = {"riceanf", "aoa", "randaoa", "ploss", "noise_power_dB", "offset", "forgetf", NULL}; static char *pformat[] = {"%lf", "%lf", "%i", "%lf", "%lf", "%i", "%lf", NULL}; -int get_channel_params(char *buf, int debug, void *vdata, telnet_printfunc_t prnt) +int get_channel_params(const char *buf, int debug, void *vdata, telnet_printfunc_t prnt) { if (buf == NULL) { LOG_I(UTIL, "%s received NULL buffer\n", __FUNCTION__); @@ -2111,7 +2111,7 @@ static void display_channelmodel(channel_desc_t *cd,int debug, telnet_printfunc_ } } -int get_currentchannels_type(char *buf, int debug, void *vdata, telnet_printfunc_t prnt) +int get_currentchannels_type(const char *buf, int debug, void *vdata, telnet_printfunc_t prnt) { webdatadef_t *tdata; if (buf == NULL) { diff --git a/openair1/SIMULATION/TOOLS/sim.h b/openair1/SIMULATION/TOOLS/sim.h index 7104d524e2..6e60ce76de 100644 --- a/openair1/SIMULATION/TOOLS/sim.h +++ b/openair1/SIMULATION/TOOLS/sim.h @@ -347,7 +347,7 @@ channel_desc_t *new_channel_desc_scm(uint8_t nb_tx, double path_loss_dB, float noise_power_dB); -channel_desc_t *find_channel_desc_fromname( char *modelname ); +channel_desc_t *find_channel_desc_fromname(const char *modelname); /** diff --git a/radio/rfsimulator/CMakeLists.txt b/radio/rfsimulator/CMakeLists.txt index ad4d92f108..2b04f7dd89 100644 --- a/radio/rfsimulator/CMakeLists.txt +++ b/radio/rfsimulator/CMakeLists.txt @@ -1,5 +1,5 @@ add_library(rfsimulator MODULE - simulator.c + simulator.cpp apply_channelmod.c ../../openair1/PHY/TOOLS/signal_energy.c ) diff --git a/radio/rfsimulator/simulator.c b/radio/rfsimulator/simulator.cpp similarity index 95% rename from radio/rfsimulator/simulator.c rename to radio/rfsimulator/simulator.cpp index c9093be912..bb61a48e89 100644 --- a/radio/rfsimulator/simulator.c +++ b/radio/rfsimulator/simulator.cpp @@ -42,12 +42,14 @@ #include #include -#include #include #include #include "common_lib.h" #define CHANNELMOD_DYNAMICLOAD +extern "C" { +#include #include +} #include "rfsimulator.h" #define PORT 4043 // default TCP port for this simulator @@ -104,7 +106,7 @@ typedef enum { SIMU_ROLE_SERVER = 1, SIMU_ROLE_CLIENT } simuRole; }; // clang-format on static void getset_currentchannels_type(char *buf, int debug, webdatadef_t *tdata, telnet_printfunc_t prnt); -extern int get_currentchannels_type(char *buf, int debug, webdatadef_t *tdata, telnet_printfunc_t prnt); // in random_channel.c +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); @@ -172,7 +174,7 @@ 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 = calloc(1, sampleToByte(CirSize, 1)); + ptr->circularBuf = static_cast(calloc(1, sampleToByte(CirSize, 1))); if (ptr->circularBuf == NULL) { LOG_E(HW, "malloc(%lu) failed\n", sampleToByte(CirSize, 1)); return NULL; @@ -208,7 +210,7 @@ static buffer_t *allocCirBuf(rfsimulator_state_t *bridge, int sock) 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 - char *legacy_model_name = (bridge->role == SIMU_ROLE_SERVER) ? "rfsimu_channel_ue0" : "rfsimu_channel_enB0"; + 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); @@ -282,7 +284,7 @@ static void fullwrite(int fd, void *_buf, ssize_t count, rfsimulator_state_t *t) LOG_E(HW, "write() in save iq file failed (%d)\n", errno); } - char *buf = _buf; + char *buf = static_cast(_buf); ssize_t l; while (count) { @@ -384,7 +386,7 @@ static int rfsimu_setchanmod_cmd(char *buff, int debug, telnet_printfunc_t prnt, 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, - channelmod, + static_cast(channelmod), t->sample_rate, t->rx_freq, t->tx_bw, @@ -520,9 +522,9 @@ static int startServer(openair0_device *device) snprintf(port, sizeof(port), "%d", t->port); struct addrinfo hints = { - .ai_family = AF_INET6, - .ai_socktype = SOCK_STREAM, - .ai_flags = AI_PASSIVE, + .ai_flags = AI_PASSIVE, + .ai_family = AF_INET6, + .ai_socktype = SOCK_STREAM, }; int s = getaddrinfo(NULL, port, &hints, &results); @@ -619,7 +621,7 @@ static int client_try_connect(const char *host, uint16_t port) static int startClient(openair0_device *device) { - rfsimulator_state_t *t = device->priv; + rfsimulator_state_t *t = static_cast(device->priv); t->role = SIMU_ROLE_CLIENT; int sock; @@ -673,9 +675,9 @@ static int rfsimulator_write_internal(rfsimulator_state_t *t, buffer_t *b = &t->buf[i]; if (b->conn_sock >= 0) { - samplesBlockHeader_t header = {nsamps, nbAnt, timestamp}; if (!nbAnt) LOG_E(HW, "rfsimulator sending 0 tx antennas\n"); + samplesBlockHeader_t header = {(uint32_t)nsamps, (uint32_t)nbAnt, (uint64_t)timestamp}; fullwrite(b->conn_sock, &header, sizeof(header), t); if (nbAnt == 1) { fullwrite(b->conn_sock, samplesVoid[0], sampleToByte(nsamps, nbAnt), t); @@ -712,10 +714,10 @@ static int rfsimulator_write_internal(rfsimulator_state_t *t, nsamps, timestamp, timestamp + nsamps, - signal_energy(samplesVoid[0], nsamps)); + signal_energy(static_cast(samplesVoid[0]), nsamps)); /* trace only first antenna */ - T(T_USRP_TX_ANT0, T_INT(timestamp), T_BUFFER(samplesVoid[0], sampleToByte(nsamps, 1))); + T(T_USRP_TX_ANT0, T_INT(timestamp), T_BUFFER(samplesVoid[0], (int)sampleToByte(nsamps, 1))); return nsamps; } @@ -728,7 +730,8 @@ static int rfsimulator_write(openair0_device *device, int flags) { timestamp -= device->openair0_cfg->command_line_sample_advance; - return rfsimulator_write_internal(device->priv, timestamp, samplesVoid, nsamps, nbAnt, flags); + rfsimulator_state_t *t = static_cast(device->priv); + return rfsimulator_write_internal(t, timestamp, samplesVoid, nsamps, nbAnt, flags); } static bool add_client(rfsimulator_state_t *t) @@ -758,7 +761,7 @@ static bool add_client(rfsimulator_state_t *t) void *samplesVoid[t->tx_num_channels]; for (int i = 0; i < t->tx_num_channels; i++) samplesVoid[i] = (void *)&v; - samplesBlockHeader_t header = {1, t->tx_num_channels, t->lastWroteTS}; + samplesBlockHeader_t header = {(uint32_t)1, (uint32_t)t->tx_num_channels, (uint64_t)t->lastWroteTS}; fullwrite(conn_sock, &header, sizeof(header), t); fullwrite(conn_sock, samplesVoid, sampleToByte(1, t->tx_num_channels), t); @@ -780,7 +783,7 @@ static void process_recv_header(rfsimulator_state_t *t, buffer_t *b, bool first_ b->lastReceivedTS = b->th.timestamp; b->trashingPacket = true; } else { - if (b->lastReceivedTS < b->th.timestamp) { + 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; @@ -798,7 +801,7 @@ static void process_recv_header(rfsimulator_state_t *t, buffer_t *b, bool first_ memset(b->circularBuf, 0, sampleToByte(CirSize, 1)); } b->lastReceivedTS = b->th.timestamp; - } else if (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; } @@ -850,7 +853,7 @@ static bool flushInput(rfsimulator_state_t *t, int timeout, bool first_time) } for (int nbEv = 0; nbEv < nfds; ++nbEv) { - buffer_t *b = events[nbEv].data.ptr; + buffer_t *b = static_cast(events[nbEv].data.ptr); if (events[nbEv].events & EPOLLIN && b == NULL) { bool ret = add_client(t); @@ -895,7 +898,7 @@ static bool flushInput(rfsimulator_state_t *t, int timeout, bool first_time) static int rfsimulator_read(openair0_device *device, openair0_timestamp *ptimestamp, void **samplesVoid, int nsamps, int nbAnt) { - rfsimulator_state_t *t = device->priv; + rfsimulator_state_t *t = static_cast(device->priv); LOG_D(HW, "Enter rfsimulator_read, expect %d samples, will release at TS: %ld, nbAnt %d\n", nsamps, @@ -995,7 +998,7 @@ static int rfsimulator_read(openair0_device *device, openair0_timestamp *ptimest 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 (firstIndex + nsamps > CirSize) { + 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)); @@ -1060,10 +1063,10 @@ static int rfsimulator_read(openair0_device *device, openair0_timestamp *ptimest nsamps, *ptimestamp, t->nextRxTstamp, - signal_energy(samplesVoid[0], nsamps)); + signal_energy(static_cast(samplesVoid[0]), nsamps)); /* trace only first antenna */ - T(T_USRP_RX_ANT0, T_INT(t->nextRxTstamp), T_BUFFER(samplesVoid[0], sampleToByte(nsamps, 1))); + T(T_USRP_RX_ANT0, T_INT(t->nextRxTstamp), T_BUFFER(samplesVoid[0], (int)sampleToByte(nsamps, 1))); return nsamps; } @@ -1075,7 +1078,7 @@ static int rfsimulator_reset_stats(openair0_device *device) { return 0; } static void rfsimulator_end(openair0_device *device) { - rfsimulator_state_t *s = device->priv; + rfsimulator_state_t *s = static_cast(device->priv); for (int i = 0; i < MAX_FD_RFSIMU; i++) { buffer_t *b = &s->buf[i]; if (b->conn_sock >= 0) @@ -1096,7 +1099,7 @@ 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 = device->priv; + rfsimulator_state_t *s = static_cast(device->priv); s->rx_freq = openair0_cfg->rx_freq[0]; return 0; } @@ -1107,11 +1110,11 @@ static int rfsimulator_write_init(openair0_device *device) { return 0; } -__attribute__((__visibility__("default"))) +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 = calloc(sizeof(rfsimulator_state_t), 1); + rfsimulator_state_t *rfsimulator = static_cast(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; From c4b94cff4badd26bdae83815850482ad9f2569e4 Mon Sep 17 00:00:00 2001 From: Bartosz Podrygajlo Date: Wed, 8 Oct 2025 16:03:44 +0200 Subject: [PATCH 02/19] Change trx_write_func to add beams definition over multiple tx antennas per beam Co-authored-by: Laurent Thomas --- radio/COMMON/common_lib.h | 24 +++++++++++++++++++--- radio/rfsimulator/simulator.cpp | 35 +++++++++++++++++++++------------ 2 files changed, 43 insertions(+), 16 deletions(-) diff --git a/radio/COMMON/common_lib.h b/radio/COMMON/common_lib.h index b87a6a8abf..b98c0c82f8 100644 --- a/radio/COMMON/common_lib.h +++ b/radio/COMMON/common_lib.h @@ -491,12 +491,29 @@ struct openair0_device_t { /*! \brief Called to send samples to the RF target @param device pointer to the device structure specific to the RF hardware target @param timestamp The timestamp at whicch the first sample MUST be sent - @param buff Buffer which holds the samples (2 dimensional) + @param buff Buffer which holds the samples (3 dimensional) @param nsamps number of samples to be sent - @param number of antennas + @param nb_antennas_tx number of antennas + @param num_beams number of beams @param flags flags must be set to true if timestamp parameter needs to be applied */ - int (*trx_write_func)(openair0_device *device, openair0_timestamp timestamp, void **buff, int nsamps,int antenna_id, int flags); + int (*trx_write_beams_func)(openair0_device *device, + openair0_timestamp timestamp, + void ***buff, + int nsamps, + int nb_antennas_tx, + int num_beams, + int flags); + + /*! \brief Called to send samples to the RF target + @param device pointer to the device structure specific to the RF hardware target + @param timestamp The timestamp at whicch the first sample MUST be sent + @param buff Buffer which holds the samples (2 dimensional) + @param nsamps number of samples to be sent + @param nb_antennas_tx number of antennas + @param flags flags must be set to true if timestamp parameter needs to be applied + */ + int (*trx_write_func)(openair0_device *device, openair0_timestamp timestamp, void **buff, int nsamps, int nb_antennas_tx, int flags); /*! \brief Called to send samples to the RF target @param device pointer to the device structure specific to the RF hardware target @@ -629,6 +646,7 @@ typedef struct { uint64_t timestamp; // Timestamp value of first sample uint32_t option_value; // Option value uint32_t option_flag; // Option flag + uint64_t beam_map; } samplesBlockHeader_t; #ifdef __cplusplus diff --git a/radio/rfsimulator/simulator.cpp b/radio/rfsimulator/simulator.cpp index bb61a48e89..fe3335009d 100644 --- a/radio/rfsimulator/simulator.cpp +++ b/radio/rfsimulator/simulator.cpp @@ -663,9 +663,10 @@ static int startClient(openair0_device *device) static int rfsimulator_write_internal(rfsimulator_state_t *t, openair0_timestamp timestamp, - void **samplesVoid, + void ***samplesVoid, int nsamps, int nbAnt, + uint64_t beam_map, int flags) { mutexlock(t->Sockmutex); @@ -677,14 +678,14 @@ static int rfsimulator_write_internal(rfsimulator_state_t *t, 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}; + 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 (nbAnt == 1) { - fullwrite(b->conn_sock, samplesVoid[0], sampleToByte(nsamps, nbAnt), t); + 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[a]; + sample_t *in = (sample_t *)samplesVoid[0][a]; for (int s = 0; s < nsamps; s++) tmpSamples[s][a] = in[s]; } @@ -714,7 +715,7 @@ static int rfsimulator_write_internal(rfsimulator_state_t *t, nsamps, timestamp, timestamp + nsamps, - signal_energy(static_cast(samplesVoid[0]), nsamps)); + signal_energy(static_cast(samplesVoid[0][0]), nsamps)); /* trace only first antenna */ T(T_USRP_TX_ANT0, T_INT(timestamp), T_BUFFER(samplesVoid[0], (int)sampleToByte(nsamps, 1))); @@ -722,16 +723,23 @@ static int rfsimulator_write_internal(rfsimulator_state_t *t, return nsamps; } -static int rfsimulator_write(openair0_device *device, - openair0_timestamp timestamp, - void **samplesVoid, - int nsamps, - int nbAnt, - int flags) +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; rfsimulator_state_t *t = static_cast(device->priv); - return rfsimulator_write_internal(t, timestamp, samplesVoid, nsamps, nbAnt, flags); + return rfsimulator_write_internal(t, timestamp, samplesVoid, nsamps, nbAnt, 1, flags); +} + +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) @@ -761,7 +769,7 @@ static bool add_client(rfsimulator_state_t *t) void *samplesVoid[t->tx_num_channels]; for (int i = 0; i < t->tx_num_channels; i++) samplesVoid[i] = (void *)&v; - samplesBlockHeader_t header = {(uint32_t)1, (uint32_t)t->tx_num_channels, (uint64_t)t->lastWroteTS}; + 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); @@ -1145,6 +1153,7 @@ int device_init(openair0_device *device, openair0_config_t *openair0_cfg) { 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; From d355e0412d30f07e8bfcd86a4d2b4fbcd21f3745 Mon Sep 17 00:00:00 2001 From: Bartosz Podrygajlo Date: Fri, 26 Sep 2025 15:07:16 +0200 Subject: [PATCH 03/19] Add prototype for trx_set_beams function This function is to be called by the application to switch beams on reception and transmission. When receiving new samples using trx_read/trx_write the application is expected to be able to determine which beam the samples belong to. --- radio/COMMON/common_lib.h | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/radio/COMMON/common_lib.h b/radio/COMMON/common_lib.h index b98c0c82f8..3fd9d6243d 100644 --- a/radio/COMMON/common_lib.h +++ b/radio/COMMON/common_lib.h @@ -588,6 +588,21 @@ struct openair0_device_t { */ int (*trx_set_gains_func)(openair0_device *device, openair0_config_t *openair0_cfg); + /*! \brief Set tx/rx beams + * + * Set the tx/rx beams. This has to be done in advance of the reception in order to + * allow the underlying device to change receiver configuration. The exact time depends + * on the device. + * + * NOTICE: the samples returned from trx_read_func may belong to more than one beam. It is up + * to the application to determine the beam of the received samples. + * + * \param device the hardware to use + * \param beam_map the beams to receive + * \return 0 on success + */ + int (*trx_set_beams)(openair0_device *device, uint64_t beam_map, openair0_timestamp timestamp); + /*! \brief RRU Configuration callback * \param idx RU index * \param arg pointer to capabilities or configuration From 143c545928ed78f569274f834e686caf85324130 Mon Sep 17 00:00:00 2001 From: Bartosz Podrygajlo Date: Wed, 1 Oct 2025 16:52:36 +0200 Subject: [PATCH 04/19] Beam simulation for rfsimulator 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 . 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 ", 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 --- radio/rfsimulator/simulator.cpp | 393 ++++++++++++++++++++++++++++---- 1 file changed, 347 insertions(+), 46 deletions(-) diff --git a/radio/rfsimulator/simulator.cpp b/radio/rfsimulator/simulator.cpp index fe3335009d..6d78ab456a 100644 --- a/radio/rfsimulator/simulator.cpp +++ b/radio/rfsimulator/simulator.cpp @@ -51,6 +51,10 @@ extern "C" { #include } #include "rfsimulator.h" +#include +#include +#include +#include #define PORT 4043 // default TCP port for this simulator // @@ -72,6 +76,7 @@ extern "C" { #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; @@ -80,6 +85,7 @@ 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" \ @@ -103,14 +109,23 @@ typedef enum { SIMU_ROLE_SERVER = 1, SIMU_ROLE_CLIENT } simuRole; {"offset", "\n", simOpt, .u64ptr = &(rfsimulator->chan_offset), .defint64val = 0, TYPE_UINT64, 0}, \ {"prop_delay", "\n", simOpt, .dblptr=&(rfsimulator->prop_delay_ms), .defdblval=0.0, TYPE_DOUBLE, 0 },\ {"wait_timeout", "\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 +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}, @@ -118,6 +133,7 @@ static telnetshell_cmddef_t rfsimu_cmdarray[] = { {"setdistance", " ", (cmdfunc_t)rfsimu_setdistance_cmd, {NULL}, TELNETSRV_CMDFLAG_PUSHINTPOOLQ | TELNETSRV_CMDFLAG_NEEDPARAM }, {"getdistance", "", (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 @@ -127,6 +143,17 @@ 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 cmd_queue; + std::mutex mutex; +} beam_state_t; + typedef struct buffer_s { int conn_sock; openair0_timestamp lastReceivedTS; @@ -138,8 +165,19 @@ typedef struct buffer_s { 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> beam_gains; + beam_state_t tx; + beam_state_t rx; +} rfsim_beam_ctrl_t; + typedef struct { int listen_sock, epollfd; pthread_mutex_t Sockmutex; @@ -165,8 +203,75 @@ typedef struct { 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 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 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 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) @@ -252,10 +357,7 @@ static void socketError(rfsimulator_state_t *bridge, buffer_t *buf) } } -enum blocking_t { - notBlocking, - blocking -}; +enum blocking_t { notBlocking, blocking }; static int setblocking(int sock, enum blocking_t active) { @@ -278,7 +380,8 @@ static int setblocking(int sock, enum blocking_t active) return 0; } -static void fullwrite(int fd, void *_buf, ssize_t count, rfsimulator_state_t *t) { +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); @@ -314,9 +417,57 @@ static void fullwrite(int fd, void *_buf, ssize_t count, rfsimulator_state_t *t) } } -static void rfsimulator_readconfig(rfsimulator_state_t *rfsimulator) { +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(device->priv); + rfsim_beam_ctrl_t *beam_ctrl = s->beam_ctrl; + std::lock_guard lock_tx(beam_ctrl->tx.mutex); + std::lock_guard 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 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); @@ -338,7 +489,11 @@ static void rfsimulator_readconfig(rfsimulator_state_t *rfsimulator) { 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); + 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]); @@ -346,14 +501,33 @@ static void rfsimulator_readconfig(rfsimulator_state_t *rfsimulator) { } } - if ( strncasecmp(rfsimulator->ip,"enb",3) == 0 || - strncasecmp(rfsimulator->ip,"server",3) == 0 ) + 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_setchanmod_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) +{ + 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 lock_tx(beam_ctrl->tx.mutex); + std::lock_guard 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; @@ -461,7 +635,8 @@ static int rfsimu_setdistance_cmd(char *buff, int debug, telnet_printfunc_t prnt /* 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->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; @@ -522,9 +697,9 @@ static int startServer(openair0_device *device) snprintf(port, sizeof(port), "%d", t->port); struct addrinfo hints = { - .ai_flags = AI_PASSIVE, - .ai_family = AF_INET6, - .ai_socktype = SOCK_STREAM, + .ai_flags = AI_PASSIVE, + .ai_family = AF_INET6, + .ai_socktype = SOCK_STREAM, }; int s = getaddrinfo(NULL, port, &hints, &results); @@ -680,16 +855,30 @@ static int rfsimulator_write_internal(rfsimulator_state_t *t, 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 (nbAnt == 1) { - fullwrite(b->conn_sock, samplesVoid[0][0], sampleToByte(nsamps, nbAnt), 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 { - 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]; + 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); } - fullwrite(b->conn_sock, (void *)tmpSamples, sampleToByte(nsamps, nbAnt), t); } } } @@ -732,8 +921,31 @@ static int rfsimulator_write_beams(openair0_device *device, int flags) { timestamp -= device->openair0_cfg->command_line_sample_advance; + int nsamps_initial = nsamps; rfsimulator_state_t *t = static_cast(device->priv); - return rfsimulator_write_internal(t, timestamp, samplesVoid, nsamps, nbAnt, 1, flags); + 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) @@ -823,8 +1035,19 @@ static void process_recv_header(rfsimulator_state_t *t, buffer_t *b, bool first_ 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 (!b->remainToTransfer) - b->headerMode = true; // We got a header with 0 antennas, no I/Q to read + + 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(malloc(beam_packet_size)); + } + b->remainToTransferBeam = beam_packet_size; + } return; } @@ -883,15 +1106,83 @@ static bool flushInput(rfsimulator_state_t *t, int timeout, bool first_time) ssize_t blockSz; if (b->headerMode) blockSz = b->remainToTransfer; - else + else { blockSz = b->transferPtr + b->remainToTransfer <= b->circularBufEnd ? b->remainToTransfer : b->circularBufEnd - b->transferPtr; + } - ssize_t 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; + 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; @@ -946,8 +1237,8 @@ static int rfsimulator_read(openair0_device *device, openair0_timestamp *ptimest 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; + have_to_wait = true; + break; } } @@ -965,6 +1256,9 @@ static int rfsimulator_read(openair0_device *device, openair0_timestamp *ptimest 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)); @@ -983,9 +1277,6 @@ static int rfsimulator_read(openair0_device *device, openair0_timestamp *ptimest if (reGenerateChannel) random_channel(ptr->channel_model, 0); - if (t->poll_telnetcmdq) - t->poll_telnetcmdq(t->telnetcmd_qid, t); - 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) { @@ -1066,6 +1357,7 @@ static int rfsimulator_read(openair0_device *device, openair0_timestamp *ptimest *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, @@ -1079,13 +1371,16 @@ static int rfsimulator_read(openair0_device *device, openair0_timestamp *ptimest return nsamps; } -static int rfsimulator_get_stats(openair0_device *device) { +static int rfsimulator_get_stats(openair0_device *device) +{ return 0; } -static int rfsimulator_reset_stats(openair0_device *device) { +static int rfsimulator_reset_stats(openair0_device *device) +{ return 0; } -static void rfsimulator_end(openair0_device *device) { +static void rfsimulator_end(openair0_device *device) +{ rfsimulator_state_t *s = static_cast(device->priv); for (int i = 0; i < MAX_FD_RFSIMU; i++) { buffer_t *b = &s->buf[i]; @@ -1103,23 +1398,27 @@ static void stopServer(openair0_device *device) rfsimulator_end(device); } -static int rfsimulator_stop(openair0_device *device) { +static int rfsimulator_stop(openair0_device *device) +{ return 0; } -static int rfsimulator_set_freq(openair0_device *device, openair0_config_t *openair0_cfg) { +static int rfsimulator_set_freq(openair0_device *device, openair0_config_t *openair0_cfg) +{ rfsimulator_state_t *s = static_cast(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) { +static int rfsimulator_set_gains(openair0_device *device, openair0_config_t *openair0_cfg) +{ return 0; } -static int rfsimulator_write_init(openair0_device *device) { +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) { +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(calloc(sizeof(rfsimulator_state_t), 1)); @@ -1129,6 +1428,7 @@ int device_init(openair0_device *device, openair0_config_t *openair0_cfg) { 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); @@ -1161,6 +1461,7 @@ int device_init(openair0_device *device, openair0_config_t *openair0_cfg) { 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; From f0d4325cd7e1c6141d07c7b06e4186e7d017cda2 Mon Sep 17 00:00:00 2001 From: Bartosz Podrygajlo Date: Mon, 13 Oct 2025 15:02:30 +0200 Subject: [PATCH 05/19] rfsim: process received packet when its received completely The simplifies some code in packet reception and is a prerequisite for processing received without relying on circular buffer --- radio/rfsimulator/simulator.cpp | 194 +++++++++++++------------------- 1 file changed, 76 insertions(+), 118 deletions(-) diff --git a/radio/rfsimulator/simulator.cpp b/radio/rfsimulator/simulator.cpp index 6d78ab456a..983f9e4c39 100644 --- a/radio/rfsimulator/simulator.cpp +++ b/radio/rfsimulator/simulator.cpp @@ -27,6 +27,7 @@ * When the opposite side switch from passive reading to active R+Write, the synchro is not fully deterministic */ +#include "utils.h" #include #include #include @@ -165,8 +166,8 @@ typedef struct buffer_s { char *circularBufEnd; sample_t *circularBuf; channel_desc_t *channel_model; - char *beamPtr; - size_t beam_ptr_sz; + char *packet_ptr; + size_t payload_sz; size_t remainToTransferBeam; } buffer_t; @@ -993,10 +994,6 @@ static bool add_client(rfsimulator_state_t *t) 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) { @@ -1031,42 +1028,65 @@ static void process_recv_header(rfsimulator_state_t *t, buffer_t *b, bool first_ 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(malloc(beam_packet_size)); - } - b->remainToTransferBeam = beam_packet_size; + int num_beams = __builtin_popcountll(b->th.beam_map); + AssertFatal(b->th.beam_map == 1ULL || t->beam_ctrl->enable_beams == 1, + "The transmitter has enabled beam simulation while this receiver has not\n"); + size_t payload_sz = sampleToByte(b->th.size, b->th.nbAnt) * num_beams; + if (b->packet_ptr == NULL || payload_sz > b->payload_sz) { + free(b->packet_ptr); + b->packet_ptr = static_cast(calloc_or_fail(1, payload_sz)); + b->payload_sz = payload_sz; } + b->transferPtr = b->packet_ptr; + b->remainToTransfer = payload_sz; 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); + 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; + 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; + } } - - 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; + c16_t *in = (c16_t *)b->packet_ptr; + int num_samples_to_process = num_samples; + openair0_timestamp first_sample_timestamp = b->th.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; } } @@ -1103,94 +1123,32 @@ static bool flushInput(rfsimulator_state_t *t, int timeout, bool first_time) 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; - } + ssize_t sz = recv(b->conn_sock, b->transferPtr, b->remainToTransfer, 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); + if (b->remainToTransfer == 0) { + if (b->headerMode) + process_recv_header(t, b, first_time); + else { + 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); + + if (!b->trashingPacket) { + process_recv(t, b, first_time); + b->lastReceivedTS = b->th.timestamp + b->th.size; + LOG_D(HW, "UEsock: %d Set b->lastReceivedTS %ld\n", b->conn_sock, b->lastReceivedTS); + } + b->trashingPacket = false; + } + } } return nfds > 0; } From 4ec69cad1ffb9e81cf3b6752129a204ffc78c3ee Mon Sep 17 00:00:00 2001 From: Bartosz Podrygajlo Date: Mon, 13 Oct 2025 17:18:01 +0200 Subject: [PATCH 06/19] Fix formatting for apply_channelmod.c --- radio/rfsimulator/apply_channelmod.c | 108 ++++++++++++++++----------- radio/rfsimulator/rfsimulator.h | 61 ++++++++------- 2 files changed, 93 insertions(+), 76 deletions(-) diff --git a/radio/rfsimulator/apply_channelmod.c b/radio/rfsimulator/apply_channelmod.c index aca8a6b030..481c089796 100644 --- a/radio/rfsimulator/apply_channelmod.c +++ b/radio/rfsimulator/apply_channelmod.c @@ -1,26 +1,25 @@ /* -* 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 -*/ - + * 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 + */ #include #include @@ -53,11 +52,14 @@ void rxAddInput(const c16_t *input_sig, { static uint64_t last_TS = 0; - if ((channelDesc->sat_height > 0) && (channelDesc->enable_dynamic_delay || channelDesc->enable_dynamic_Doppler)) { // model for transparent satellite on circular orbit + if ((channelDesc->sat_height > 0) + && (channelDesc->enable_dynamic_delay + || channelDesc->enable_dynamic_Doppler)) { // model for transparent satellite on circular orbit /* assumptions: - The Earth is spherical, the ground station is static, and that the Earth does not rotate. - An access or link is possible from the satellite to the ground station at all times. - - The ground station is located at the North Pole (positive Zaxis), and the satellite starts from the initial elevation angle 0° in the second quadrant of the YZplane. + - The ground station is located at the North Pole (positive Zaxis), and the satellite starts from the initial elevation angle + 0° in the second quadrant of the YZplane. - Satellite moves in the clockwise direction in its circular orbit. */ const double radius_earth = 6377900; // m @@ -78,7 +80,7 @@ void rxAddInput(const c16_t *input_sig, const double pos_sat_z = radius_sat * cos(w_sat * t); const double vel_sat_x = 0; - const double vel_sat_y = w_sat * radius_sat * cos(w_sat * t); + const double vel_sat_y = w_sat * radius_sat * cos(w_sat * t); const double vel_sat_z = -w_sat * radius_sat * sin(w_sat * t); const double pos_ue_x = 0; @@ -157,9 +159,17 @@ void rxAddInput(const c16_t *input_sig, if (channelDesc->enable_dynamic_Doppler) channelDesc->Doppler_phase_inc = 2 * M_PI * f_Doppler_shift_ue_sat / channelDesc->sampling_rate; - if(TS - last_TS >= channelDesc->sampling_rate) { + if (TS - last_TS >= channelDesc->sampling_rate) { last_TS = TS; - LOG_I(HW, "Satellite orbit: time %f s, Position = (%f, %f, %f), Velocity = (%f, %f, %f)\n", t, pos_sat_x, pos_sat_y, pos_sat_z, vel_sat_x, vel_sat_y, vel_sat_z); + LOG_I(HW, + "Satellite orbit: time %f s, Position = (%f, %f, %f), Velocity = (%f, %f, %f)\n", + t, + pos_sat_x, + pos_sat_y, + pos_sat_z, + vel_sat_x, + vel_sat_y, + vel_sat_z); LOG_I(HW, "Uplink delay %f ms, Doppler shift UE->SAT %f kHz\n", prop_delay * 1000, f_Doppler_shift_ue_sat / 1000); LOG_I(HW, "Satellite velocity towards gNB: %f m/s, acceleration towards gNB: %f m/s²\n", -vel_sat_gnb, acc_sat_gnb); } @@ -216,9 +226,17 @@ void rxAddInput(const c16_t *input_sig, if (channelDesc->enable_dynamic_Doppler) channelDesc->Doppler_phase_inc = 2 * M_PI * f_Doppler_shift_sat_ue / channelDesc->sampling_rate; - if(TS - last_TS >= channelDesc->sampling_rate) { + if (TS - last_TS >= channelDesc->sampling_rate) { last_TS = TS; - LOG_I(HW, "Satellite orbit: time %f s, Position = (%f, %f, %f), Velocity = (%f, %f, %f)\n", t, pos_sat_x, pos_sat_y, pos_sat_z, vel_sat_x, vel_sat_y, vel_sat_z); + LOG_I(HW, + "Satellite orbit: time %f s, Position = (%f, %f, %f), Velocity = (%f, %f, %f)\n", + t, + pos_sat_x, + pos_sat_y, + pos_sat_z, + vel_sat_x, + vel_sat_y, + vel_sat_z); LOG_I(HW, "Downlink delay %f ms, Doppler shift SAT->UE %f kHz\n", prop_delay * 1000, f_Doppler_shift_sat_ue / 1000); } } @@ -228,24 +246,24 @@ void rxAddInput(const c16_t *input_sig, // so, in actual RF: tx gain + path loss + rx gain (+antenna gain, ...) // UE and NB gain control to be added // Fixme: not sure when it is "volts" so dB is 20*log10(...) or "power", so dB is 10*log10(...) - const double pathLossLinear = pow(10,channelDesc->path_loss_dB/20.0); + const double pathLossLinear = pow(10, channelDesc->path_loss_dB / 20.0); // Energy in one sample to calibrate input noise // the normalized OAI value seems to be 256 as average amplitude (numerical amplification = 1) - const double noise_per_sample = add_noise ? pow(10,channelDesc->noise_power_dB/10.0) * 256 : 0; + const double noise_per_sample = add_noise ? pow(10, channelDesc->noise_power_dB / 10.0) * 256 : 0; const uint64_t dd = channelDesc->channel_offset; - const int nbTx=channelDesc->nb_tx; + const int nbTx = channelDesc->nb_tx; double Doppler_phase_cur = channelDesc->Doppler_phase_cur[rxAnt]; Doppler_phase_cur -= 2 * M_PI * round(Doppler_phase_cur / (2 * M_PI)); - for (int i=0; ich[rxAnt+(txAnt*channelDesc->nb_rx)]; + for (int txAnt = 0; txAnt < nbTx; txAnt++) { + const struct complexd *channelModel = channelDesc->ch[rxAnt + (txAnt * channelDesc->nb_rx)]; - //const struct complex *channelModelEnd=channelModel+channelDesc->channel_length; - for (int l = 0; l<(int)channelDesc->channel_length; l++) { + // const struct complex *channelModelEnd=channelModel+channelDesc->channel_length; + for (int l = 0; l < (int)channelDesc->channel_length; l++) { // let's assume TS+i >= l // fixme: the rfsimulator current structure is interleaved antennas // this has been designed to not have to wait a full block transmission @@ -257,7 +275,7 @@ void rxAddInput(const c16_t *input_sig, const struct complex16 tx16 = input_sig[idx]; rx_tmp.r += tx16.r * channelModel[l].r - tx16.i * channelModel[l].i; rx_tmp.i += tx16.i * channelModel[l].r + tx16.r * channelModel[l].i; - } //l + } // l } if (channelDesc->Doppler_phase_inc != 0.0) { @@ -279,12 +297,12 @@ void rxAddInput(const c16_t *input_sig, channelDesc->Doppler_phase_cur[rxAnt] = Doppler_phase_cur; - if ( (TS*nbTx)%CirSize+nbSamples <= CirSize ) + if ((TS * nbTx) % CirSize + nbSamples <= CirSize) // Cast to a wrong type for compatibility ! - LOG_D(HW,"Input power %f, output power: %f, channel path loss %f, noise coeff: %f \n", - 10*log10((double)signal_energy((int32_t *)&input_sig[(TS*nbTx)%CirSize], nbSamples)), - 10*log10((double)signal_energy((int32_t *)after_channel_sig, nbSamples)), + LOG_D(HW, + "Input power %f, output power: %f, channel path loss %f, noise coeff: %f \n", + 10 * log10((double)signal_energy((int32_t *)&input_sig[(TS * nbTx) % CirSize], nbSamples)), + 10 * log10((double)signal_energy((int32_t *)after_channel_sig, nbSamples)), channelDesc->path_loss_dB, - 10*log10(noise_per_sample)); + 10 * log10(noise_per_sample)); } - diff --git a/radio/rfsimulator/rfsimulator.h b/radio/rfsimulator/rfsimulator.h index 11a57193ca..7410d10cbb 100644 --- a/radio/rfsimulator/rfsimulator.h +++ b/radio/rfsimulator/rfsimulator.h @@ -1,37 +1,36 @@ /* -* 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 -*/ + * 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 + */ #ifndef __RFSIMULATOR_H -#define __RFSIMULATOR_H +#define __RFSIMULATOR_H #include -void rxAddInput( const c16_t *input_sig, - cf_t *after_channel_sig, - int rxAnt, - channel_desc_t *channelDesc, - int nbSamples, - uint64_t TS, - uint32_t CirSize, - bool apply_noise - ); +void rxAddInput(const c16_t *input_sig, + cf_t *after_channel_sig, + int rxAnt, + channel_desc_t *channelDesc, + int nbSamples, + uint64_t TS, + uint32_t CirSize, + bool apply_noise); #endif From 669be5b42e73dae5195cb9d941529ecf9975e6d8 Mon Sep 17 00:00:00 2001 From: Bartosz Podrygajlo Date: Tue, 14 Oct 2025 08:14:51 +0200 Subject: [PATCH 07/19] Rfsim: Process received packets on demand - Received packets are dynamically allocated and kept in a queue for as long as they are needed. - The layout of the packets was changed from interleaved antennas to non-interleaved antennas. - Generate a contiguous buffer from saved packets as needed. There is extra work caused by this change due to recv() call no longer saving data directly into the circular buffer, however further processing is easier due to contiguous nature of the tx antenna buffers prepared. The impact on performance was not measured. --- radio/rfsimulator/apply_channelmod.c | 35 +-- radio/rfsimulator/rfsimulator.h | 6 +- radio/rfsimulator/simulator.cpp | 366 ++++++++++++++------------- 3 files changed, 199 insertions(+), 208 deletions(-) diff --git a/radio/rfsimulator/apply_channelmod.c b/radio/rfsimulator/apply_channelmod.c index 481c089796..2eaa6a66c2 100644 --- a/radio/rfsimulator/apply_channelmod.c +++ b/radio/rfsimulator/apply_channelmod.c @@ -41,14 +41,12 @@ either we regenerate the channel (call again random_channel(desc,0)), or we keep it over subframes legacy: we regenerate each sub frame in UL, and each frame only in DL */ -void rxAddInput(const c16_t *input_sig, +void rxAddInput(const c16_t **input_sig, cf_t *after_channel_sig, int rxAnt, channel_desc_t *channelDesc, int nbSamples, - uint64_t TS, - uint32_t CirSize, - bool add_noise) + uint64_t TS) { static uint64_t last_TS = 0; @@ -249,8 +247,7 @@ void rxAddInput(const c16_t *input_sig, const double pathLossLinear = pow(10, channelDesc->path_loss_dB / 20.0); // Energy in one sample to calibrate input noise // the normalized OAI value seems to be 256 as average amplitude (numerical amplification = 1) - const double noise_per_sample = add_noise ? pow(10, channelDesc->noise_power_dB / 10.0) * 256 : 0; - const uint64_t dd = channelDesc->channel_offset; + const double noise_per_sample = pow(10, channelDesc->noise_power_dB / 10.0) * 256; const int nbTx = channelDesc->nb_tx; double Doppler_phase_cur = channelDesc->Doppler_phase_cur[rxAnt]; Doppler_phase_cur -= 2 * M_PI * round(Doppler_phase_cur / (2 * M_PI)); @@ -264,15 +261,8 @@ void rxAddInput(const c16_t *input_sig, // const struct complex *channelModelEnd=channelModel+channelDesc->channel_length; for (int l = 0; l < (int)channelDesc->channel_length; l++) { - // let's assume TS+i >= l - // fixme: the rfsimulator current structure is interleaved antennas - // this has been designed to not have to wait a full block transmission - // but it is not very usefull - // it would be better to split out each antenna in a separate flow - // that will allow to mix ru antennas freely - // (X + cirSize) % cirSize to ensure that index is positive - const int idx = ((TS + i - l - dd) * nbTx + txAnt + CirSize) % CirSize; - const struct complex16 tx16 = input_sig[idx]; + const int idx = i - l + channelDesc->channel_length - 1; + const struct complex16 tx16 = input_sig[txAnt][idx]; rx_tmp.r += tx16.r * channelModel[l].r - tx16.i * channelModel[l].i; rx_tmp.i += tx16.i * channelModel[l].r + tx16.r * channelModel[l].i; } // l @@ -297,12 +287,11 @@ void rxAddInput(const c16_t *input_sig, channelDesc->Doppler_phase_cur[rxAnt] = Doppler_phase_cur; - if ((TS * nbTx) % CirSize + nbSamples <= CirSize) - // Cast to a wrong type for compatibility ! - LOG_D(HW, - "Input power %f, output power: %f, channel path loss %f, noise coeff: %f \n", - 10 * log10((double)signal_energy((int32_t *)&input_sig[(TS * nbTx) % CirSize], nbSamples)), - 10 * log10((double)signal_energy((int32_t *)after_channel_sig, nbSamples)), - channelDesc->path_loss_dB, - 10 * log10(noise_per_sample)); + // Cast to a wrong type for compatibility ! + LOG_D(HW, + "Input power %f, output power: %f, channel path loss %f, noise coeff: %f \n", + 10 * log10((double)signal_energy((int32_t *)&input_sig[0], nbSamples)), + 10 * log10((double)signal_energy((int32_t *)after_channel_sig, nbSamples)), + channelDesc->path_loss_dB, + 10 * log10(noise_per_sample)); } diff --git a/radio/rfsimulator/rfsimulator.h b/radio/rfsimulator/rfsimulator.h index 7410d10cbb..0ebdef0d6d 100644 --- a/radio/rfsimulator/rfsimulator.h +++ b/radio/rfsimulator/rfsimulator.h @@ -24,13 +24,11 @@ #ifndef __RFSIMULATOR_H #define __RFSIMULATOR_H #include -void rxAddInput(const c16_t *input_sig, +void rxAddInput(const c16_t **input_sig, cf_t *after_channel_sig, int rxAnt, channel_desc_t *channelDesc, int nbSamples, - uint64_t TS, - uint32_t CirSize, - bool apply_noise); + uint64_t TS); #endif diff --git a/radio/rfsimulator/simulator.cpp b/radio/rfsimulator/simulator.cpp index 983f9e4c39..4ad40b96c8 100644 --- a/radio/rfsimulator/simulator.cpp +++ b/radio/rfsimulator/simulator.cpp @@ -27,6 +27,7 @@ * When the opposite side switch from passive reading to active R+Write, the synchro is not fully deterministic */ +#include "PHY/defs_common.h" #include "utils.h" #include #include @@ -50,27 +51,15 @@ extern "C" { #include #include -} #include "rfsimulator.h" +} #include #include #include #include +#include #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))) @@ -141,7 +130,6 @@ static telnetshell_cmddef_t rfsimu_cmdarray[] = { 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 { @@ -155,20 +143,25 @@ typedef struct { std::mutex mutex; } beam_state_t; +typedef struct { + samplesBlockHeader_t header; + char payload[1]; +} rfsim_packet_t; + typedef struct buffer_s { int conn_sock; openair0_timestamp lastReceivedTS; bool headerMode; bool trashingPacket; samplesBlockHeader_t th; + uint nbAnt; char *transferPtr; uint64_t remainToTransfer; - char *circularBufEnd; - sample_t *circularBuf; channel_desc_t *channel_model; - char *packet_ptr; + rfsim_packet_t *packet_ptr; size_t payload_sz; size_t remainToTransferBeam; + std::queue received_packets; } buffer_t; typedef struct { @@ -273,6 +266,29 @@ static void clear_beam_queue(beam_state_t *beam_state, openair0_timestamp timest } } +/** + * @brief Clears old packets from the received_packets queue based on a threshold timestamp. + * + * This function iterates through the received_packets queue and removes packets whose + * timestamp plus size is less than or equal to the provided threshold_timestamp. + * It frees the memory allocated for each removed packet. + * + * @param received_packets Reference to the queue of rfsim_packet_t pointers representing received packets. + * @param threshold_timestamp The timestamp threshold used to determine which packets to remove. + */ +static void clear_old_packets(std::queue &received_packets, uint64_t threshold_timestamp) +{ + while (!received_packets.empty()) { + rfsim_packet_t *pkt = received_packets.front(); + if (pkt->header.timestamp + pkt->header.size <= threshold_timestamp) { + free(pkt); + received_packets.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) @@ -280,18 +296,13 @@ 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(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); + ptr->received_packets = std::queue(); int sendbuff = SEND_BUFF_SIZE; if (setsockopt(sock, SOL_SOCKET, SO_SNDBUF, &sendbuff, sizeof(sendbuff)) != 0) { LOG_E(HW, "setsockopt(SO_SNDBUF) failed\n"); @@ -338,11 +349,11 @@ static void removeCirBuf(rfsimulator_state_t *bridge, buffer_t *buf) 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)); + clear_old_packets(buf->received_packets, INT64_MAX); + *buf = buffer_t{}; buf->conn_sock = -1; bridge->nb_cnx--; } @@ -856,37 +867,17 @@ static int rfsimulator_write_internal(rfsimulator_state_t *t, 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); + int num_beams = __builtin_popcountll(beam_map); + AssertFatal(num_beams > 0, "Must set at least one bit in beam_map\n"); + for (int beam = 0; beam < num_beams; beam++) { + for (int a = 0; a < nbAnt; a++) { + sample_t *in = (sample_t *)samplesVoid[beam][a]; + fullwrite(b->conn_sock, (void *)in, sampleToByte(nsamps, 1), 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); @@ -995,99 +986,110 @@ static bool add_client(rfsimulator_state_t *t) static void process_recv_header(rfsimulator_state_t *t, buffer_t *b, bool first_time) { b->headerMode = false; // We got the header - + AssertFatal(b->th.nbAnt != 0, "Number of antennas not set\n"); + if (b->nbAnt != b->th.nbAnt) { + LOG_A(HW, "RFsim: Number of antennas changed from %d to %d\n", b->nbAnt, b->th.nbAnt); + b->nbAnt = b->th.nbAnt; + } 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); } int num_beams = __builtin_popcountll(b->th.beam_map); AssertFatal(b->th.beam_map == 1ULL || t->beam_ctrl->enable_beams == 1, "The transmitter has enabled beam simulation while this receiver has not\n"); size_t payload_sz = sampleToByte(b->th.size, b->th.nbAnt) * num_beams; - if (b->packet_ptr == NULL || payload_sz > b->payload_sz) { - free(b->packet_ptr); - b->packet_ptr = static_cast(calloc_or_fail(1, payload_sz)); - b->payload_sz = payload_sz; - } - b->transferPtr = b->packet_ptr; + b->packet_ptr = static_cast(calloc_or_fail(1, payload_sz + sizeof(samplesBlockHeader_t))); + b->packet_ptr->header = b->th; + b->transferPtr = b->packet_ptr->payload; b->remainToTransfer = payload_sz; return; } -static void process_recv(rfsimulator_state_t *t, buffer_t *b, bool first_time) +static std::vector get_beam_ids(uint64_t beam_map) { - int num_beams = __builtin_popcountll(b->th.beam_map); + int num_beams = __builtin_popcountll(beam_map); AssertFatal(num_beams > 0, "Needs at least one beam\n"); - int num_ant = b->th.nbAnt; - int num_samples = b->th.size; - int tx_beam_ids[MAX_BEAMS]; - int *tx_beam_ids_p = tx_beam_ids; + std::vector beam_ids; for (int i = 0; i < MAX_BEAMS; i++) { - if (b->th.beam_map & (1ULL << i)) { - *tx_beam_ids_p++ = i; + if (beam_map & (1ULL << i)) { + beam_ids.push_back(i); } } - c16_t *in = (c16_t *)b->packet_ptr; - int num_samples_to_process = num_samples; - openair0_timestamp first_sample_timestamp = b->th.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; - } + return beam_ids; +} + +/** + * @brief Combines samples from received packets into a single buffer for the rx beam + * + * This function processes the received_packets queue and combines the transmitted beams + * into a single buffer for each antenna. It applies the appropriate gain and handles + * overlapping timestamps. + * + * + * @param t Pointer to the rfsimulator_state_t structure. + * @param received_packets Reference to the queue of rfsim_packet_t pointers representing received packets. + * @param start_timestamp The start timestamp for the combination process. + * @param num_aatx The number of antennas to process. + * @param num_samples The number of samples to process. + * @param rx_beam_id Receiver configured beam id for the timestamp + * @return A vector of vectors containing the combined samples for each antenna. + */ +static std::vector> combine_received_beams(rfsimulator_state_t *t, + std::queue &received_packets, + uint64_t start_timestamp, + int num_aatx, + size_t num_samples, + int rx_beam_id) +{ + // Assume received_packets is ordered by timestamp + std::vector> ant_buffers(num_aatx, std::vector(num_samples, {0, 0})); + std::queue packets_copy = received_packets; + while (!packets_copy.empty()) { + rfsim_packet_t *pkt = packets_copy.front(); + if (pkt->header.timestamp + pkt->header.size <= start_timestamp) { + // This packet is before the start timestamp, discard it + packets_copy.pop(); + continue; } - 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++; + if (pkt->header.timestamp > start_timestamp + num_samples) { + // This packet is after the end of the buffer, stop processing + break; + } + + std::vector tx_beams = get_beam_ids(pkt->header.beam_map); + for (uint beam = 0; beam < tx_beams.size(); beam++) { + float gain_dB = get_rx_gain_db(t, rx_beam_id, tx_beams[beam]); + float gain_linear = powf(10, gain_dB / 20.0); + uint64_t overlap_start = std::max(start_timestamp, pkt->header.timestamp); + uint64_t overlap_end = std::min(start_timestamp + num_samples, pkt->header.timestamp + pkt->header.size); + int write_start_idx = overlap_start - start_timestamp; + int write_end_idx = overlap_end - start_timestamp; + int read_start_idx = overlap_start - pkt->header.timestamp; + for (int aatx = 0; aatx < num_aatx; aatx++) { + c16_t *buffer = (c16_t *)pkt->payload; + c16_t *tx_ant_buffer_in = &buffer[(num_aatx * beam + aatx) * pkt->header.size + read_start_idx]; + for (int s = write_start_idx; s < write_end_idx; s++) { + ant_buffers[aatx][s].r += tx_ant_buffer_in->r * gain_linear; + ant_buffers[aatx][s].i += tx_ant_buffer_in->i * gain_linear; + tx_ant_buffer_in++; } - out->r = sample.r; - out->i = sample.i; } } - first_sample_timestamp += nsamps_out; - num_samples_to_process -= nsamps_out; + packets_copy.pop(); } + return ant_buffers; } static bool flushInput(rfsimulator_state_t *t, int timeout, bool first_time) @@ -1118,7 +1120,7 @@ static bool flushInput(rfsimulator_state_t *t, int timeout, bool first_time) } } - if (b->circularBuf == NULL) { + if (b->conn_sock == -1) { LOG_E(HW, "Received data on not connected socket %d\n", events[nbEv].data.fd); continue; } @@ -1142,10 +1144,13 @@ static bool flushInput(rfsimulator_state_t *t, int timeout, bool first_time) b->remainToTransfer = sizeof(samplesBlockHeader_t); if (!b->trashingPacket) { - process_recv(t, b, first_time); b->lastReceivedTS = b->th.timestamp + b->th.size; LOG_D(HW, "UEsock: %d Set b->lastReceivedTS %ld\n", b->conn_sock, b->lastReceivedTS); + b->received_packets.emplace(b->packet_ptr); + } else { + free(b->packet_ptr); } + b->packet_ptr = NULL; b->trashingPacket = false; } } @@ -1167,7 +1172,7 @@ static int rfsimulator_read(openair0_device *device, openair0_timestamp *ptimest int first_sock; for (first_sock = 0; first_sock < MAX_FD_RFSIMU; first_sock++) - if (t->buf[first_sock].circularBuf != NULL) + if (t->buf[first_sock].conn_sock != -1) break; if (first_sock == MAX_FD_RFSIMU) { @@ -1194,7 +1199,7 @@ static int rfsimulator_read(openair0_device *device, openair0_timestamp *ptimest 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) { + if (b->conn_sock != -1 && (t->nextRxTstamp + nsamps) > b->lastReceivedTS) { have_to_wait = true; break; } @@ -1221,13 +1226,15 @@ static int rfsimulator_read(openair0_device *device, openair0_timestamp *ptimest 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; + memset(temp_array, 0, sizeof(temp_array)); // 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) { + uint64_t timestamp_processed = t->nextRxTstamp; + + if (ptr->conn_sock != -1 && !ptr->received_packets.empty()) { + AssertFatal(ptr->nbAnt != 0, "Number of antennas not set\n"); bool reGenerateChannel = false; // fixme: when do we regenerate @@ -1235,70 +1242,69 @@ static int rfsimulator_read(openair0_device *device, openair0_timestamp *ptimest 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)); + std::vector rx_beams_ids = get_beam_ids(t->beam_ctrl->rx.beam_map); + + if (ptr->channel_model != NULL) { // apply a channel model + const uint64_t dd = ptr->channel_model->channel_offset; + const uint64_t channel_length = ptr->channel_model->channel_length; + timestamp_processed = t->nextRxTstamp - dd; + for (uint beam = 0; beam < rx_beams_ids.size(); beam++) { + std::vector> ant_buffers = combine_received_beams(t, + ptr->received_packets, + t->nextRxTstamp - dd, + ptr->nbAnt, + nsamps + channel_length - 1, + rx_beams_ids[beam]); + const c16_t *input[ant_buffers.size()]; + for (uint aatx = 0; aatx < ant_buffers.size(); aatx++) { + input[aatx] = ant_buffers[aatx].data(); } - 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)); + for (int aarx = 0; aarx < nbAnt; aarx++) { + rxAddInput(input, temp_array[aarx], aarx, ptr->channel_model, nsamps, t->nextRxTstamp); + } + } + } else { + for (uint beam = 0; beam < rx_beams_ids.size(); beam++) { + std::vector> ant_buffers = + combine_received_beams(t, ptr->received_packets, t->nextRxTstamp, ptr->th.nbAnt, nsamps, rx_beams_ids[beam]); + for (int aarx = 0; aarx < nbAnt; aarx++) { + double H_awgn_mimo_coeff[ant_buffers.size()]; + for (int aatx = 0; aatx < (int)ant_buffers.size(); aatx++) { + uint32_t ant_diff = std::abs(aatx - aarx); + H_awgn_mimo_coeff[aatx] = ant_diff ? (0.2 / ant_diff) : 1.0; } - } 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]); + + for (uint aatx = 0; aatx < ant_buffers.size(); aatx++) { // sum up signals from nbAnt_tx antennas + c16_t *out = (sample_t *)samplesVoid[aarx]; + for (int i = 0; i < nsamps; i++) { // loop over nsamps + out[i].r += ant_buffers[aatx][i].r * H_awgn_mimo_coeff[aatx]; + out[i].i += ant_buffers[aatx][i].i * H_awgn_mimo_coeff[aatx]; } // 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) + } // end for a_rx + } + } + } + + clear_old_packets(ptr->received_packets, timestamp_processed); + } + + bool apply_global_noise = get_noise_power_dBFS() != INVALID_DBFS_VALUE; + if (apply_global_noise) { + for (int a = 0; a < nbAnt; a++) { + for (int i = 0; i < nsamps; i++) { + int16_t noise_power = (int16_t)(32767.0 / powf(10.0, .05 * -get_noise_power_dBFS())); + temp_array[a][i].r += noise_power + gaussZiggurat(0.0, 1.0); + temp_array[a][i].i += noise_power * gaussZiggurat(0.0, 1.0); + } } } - 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)); - } + + 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); } } @@ -1345,6 +1351,8 @@ static void rfsimulator_end(openair0_device *device) if (b->conn_sock >= 0) removeCirBuf(s, b); } + clear_beam_queue(&s->beam_ctrl->tx, INT64_MAX); + clear_beam_queue(&s->beam_ctrl->rx, INT64_MAX); close(s->epollfd); free(s); } @@ -1391,10 +1399,6 @@ extern "C" __attribute__((__visibility__("default"))) int device_init(openair0_d 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); } From 643be87832d90e1a22c69a1b23df555152db4ad2 Mon Sep 17 00:00:00 2001 From: Bartosz Podrygajlo Date: Tue, 14 Oct 2025 13:27:46 +0200 Subject: [PATCH 08/19] RFsim: read_beams function Added a new function for reading multiple beams concurrently. The TX to RX beam combination is simplistic and based on beam gain. --- radio/COMMON/common_lib.h | 22 +++- radio/rfsimulator/simulator.cpp | 213 +++++++++++++++++++------------- 2 files changed, 150 insertions(+), 85 deletions(-) diff --git a/radio/COMMON/common_lib.h b/radio/COMMON/common_lib.h index 3fd9d6243d..cb1b0c8ddc 100644 --- a/radio/COMMON/common_lib.h +++ b/radio/COMMON/common_lib.h @@ -535,7 +535,27 @@ struct openair0_device_t { * antennas from which to receive samples \returns the number of sample read */ - int (*trx_read_func)(openair0_device *device, openair0_timestamp *ptimestamp, void **buff, int nsamps,int num_antennas); + int (*trx_read_func)(openair0_device *device, openair0_timestamp *ptimestamp, void **buff, int nsamps, int num_antennas); + + /*! \brief Receive samples from hardware. + * Read 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 nsamps. + * \param nsamps Number of samples. One sample is 2 byte I + 2 byte Q => 4 byte. + * \param num_antennas number of antennas from which to receive samples + * \param num_beams number of beams from which to receive samples + * \returns the number of sample read + */ + int (*trx_read_beams_func)(openair0_device *device, + openair0_timestamp *ptimestamp, + void ***buff, + int nsamps, + int num_antennas, + int num_beams); /*! \brief Receive samples from hardware, this version provides a single antenna at a time and returns. * Read nsamps samples from each channel to buffers. buff[0] is the array for diff --git a/radio/rfsimulator/simulator.cpp b/radio/rfsimulator/simulator.cpp index 4ad40b96c8..5c01322fa6 100644 --- a/radio/rfsimulator/simulator.cpp +++ b/radio/rfsimulator/simulator.cpp @@ -27,6 +27,7 @@ * When the opposite side switch from passive reading to active R+Write, the synchro is not fully deterministic */ +#include "PHY/TOOLS/tools_defs.h" #include "PHY/defs_common.h" #include "utils.h" #include @@ -1158,7 +1159,90 @@ static bool flushInput(rfsimulator_state_t *t, int timeout, bool first_time) return nfds > 0; } -static int rfsimulator_read(openair0_device *device, openair0_timestamp *ptimestamp, void **samplesVoid, int nsamps, int nbAnt) +static void rfsimulator_read_internal(rfsimulator_state_t *t, + c16_t **samples, + openair0_timestamp timestamp, + int nsamps, + int nbAnt, + int rx_beam_id) +{ + cf_t temp_array[nbAnt][nsamps]; + memset(temp_array, 0, sizeof(temp_array)); + // 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->conn_sock != -1 && !ptr->received_packets.empty()) { + AssertFatal(ptr->nbAnt != 0, "Number of antennas not set\n"); + 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); + + if (ptr->channel_model != NULL) { // apply a channel model + const uint64_t dd = ptr->channel_model->channel_offset; + const uint64_t channel_length = ptr->channel_model->channel_length; + std::vector> ant_buffers = combine_received_beams(t, + ptr->received_packets, + timestamp - dd - (channel_length - 1), + ptr->nbAnt, + nsamps + channel_length - 1, + rx_beam_id); + const c16_t *input[ant_buffers.size()]; + for (uint aatx = 0; aatx < ant_buffers.size(); aatx++) { + input[aatx] = ant_buffers[aatx].data(); + } + for (int aarx = 0; aarx < nbAnt; aarx++) { + rxAddInput(input, temp_array[aarx], aarx, ptr->channel_model, nsamps, timestamp); + } + } else { + std::vector> ant_buffers = + combine_received_beams(t, ptr->received_packets, timestamp, ptr->nbAnt, nsamps, rx_beam_id); + for (int aarx = 0; aarx < nbAnt; aarx++) { + double H_awgn_mimo_coeff[ant_buffers.size()]; + for (int aatx = 0; aatx < (int)ant_buffers.size(); aatx++) { + uint32_t ant_diff = std::abs(aatx - aarx); + H_awgn_mimo_coeff[aatx] = ant_diff ? (0.2 / ant_diff) : 1.0; + } + + for (uint aatx = 0; aatx < ant_buffers.size(); aatx++) { // sum up signals from nbAnt_tx antennas + for (int i = 0; i < nsamps; i++) { // loop over nsamps + samples[aarx][i].r += ant_buffers[aatx][i].r * H_awgn_mimo_coeff[aatx]; + samples[aarx][i].i += ant_buffers[aatx][i].i * H_awgn_mimo_coeff[aatx]; + } // end for a_tx + } // end for i (number of samps) + } // end for a_rx + } + } + } + + bool apply_global_noise = get_noise_power_dBFS() != INVALID_DBFS_VALUE; + if (apply_global_noise) { + for (int a = 0; a < nbAnt; a++) { + for (int i = 0; i < nsamps; i++) { + int16_t noise_power = (int16_t)(32767.0 / powf(10.0, .05 * -get_noise_power_dBFS())); + temp_array[a][i].r += noise_power + gaussZiggurat(0.0, 1.0); + temp_array[a][i].i += noise_power * gaussZiggurat(0.0, 1.0); + } + } + } + + for (int a = 0; a < nbAnt; a++) { + for (int i = 0; i < nsamps; i++) { + samples[a][i].r += lroundf(temp_array[a][i].r); + samples[a][i].i += lroundf(temp_array[a][i].i); + } + } +} + +static int rfsimulator_read_beams(openair0_device *device, + openair0_timestamp *ptimestamp, + void ***samplesVoid, + int nsamps, + int nbAnt, + int num_beams) { rfsimulator_state_t *t = static_cast(device->priv); LOG_D(HW, @@ -1181,8 +1265,9 @@ static int rfsimulator_read(openair0_device *device, openair0_timestamp *ptimest 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)); + for (int beam = 0; beam < num_beams; beam++) + for (int x = 0; x < nbAnt; x++) + memset(samplesVoid[beam][x], 0, sampleToByte(nsamps, 1)); t->nextRxTstamp += nsamps; @@ -1223,89 +1308,31 @@ static int rfsimulator_read(openair0_device *device, openair0_timestamp *ptimest 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]; - memset(temp_array, 0, sizeof(temp_array)); - // Add all input nodes signal in the output buffer - for (int sock = 0; sock < MAX_FD_RFSIMU; sock++) { - buffer_t *ptr = &t->buf[sock]; + for (int beam = 0; beam < num_beams; beam++) + for (int a = 0; a < nbAnt; a++) + memset(samplesVoid[beam][a], 0, sampleToByte(nsamps, 1)); - uint64_t timestamp_processed = t->nextRxTstamp; - - if (ptr->conn_sock != -1 && !ptr->received_packets.empty()) { - AssertFatal(ptr->nbAnt != 0, "Number of antennas not set\n"); - 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); - - std::vector rx_beams_ids = get_beam_ids(t->beam_ctrl->rx.beam_map); - - if (ptr->channel_model != NULL) { // apply a channel model - const uint64_t dd = ptr->channel_model->channel_offset; - const uint64_t channel_length = ptr->channel_model->channel_length; - timestamp_processed = t->nextRxTstamp - dd; - for (uint beam = 0; beam < rx_beams_ids.size(); beam++) { - std::vector> ant_buffers = combine_received_beams(t, - ptr->received_packets, - t->nextRxTstamp - dd, - ptr->nbAnt, - nsamps + channel_length - 1, - rx_beams_ids[beam]); - const c16_t *input[ant_buffers.size()]; - for (uint aatx = 0; aatx < ant_buffers.size(); aatx++) { - input[aatx] = ant_buffers[aatx].data(); - } - for (int aarx = 0; aarx < nbAnt; aarx++) { - rxAddInput(input, temp_array[aarx], aarx, ptr->channel_model, nsamps, t->nextRxTstamp); - } - } - } else { - for (uint beam = 0; beam < rx_beams_ids.size(); beam++) { - std::vector> ant_buffers = - combine_received_beams(t, ptr->received_packets, t->nextRxTstamp, ptr->th.nbAnt, nsamps, rx_beams_ids[beam]); - for (int aarx = 0; aarx < nbAnt; aarx++) { - double H_awgn_mimo_coeff[ant_buffers.size()]; - for (int aatx = 0; aatx < (int)ant_buffers.size(); aatx++) { - uint32_t ant_diff = std::abs(aatx - aarx); - H_awgn_mimo_coeff[aatx] = ant_diff ? (0.2 / ant_diff) : 1.0; - } - - for (uint aatx = 0; aatx < ant_buffers.size(); aatx++) { // sum up signals from nbAnt_tx antennas - c16_t *out = (sample_t *)samplesVoid[aarx]; - for (int i = 0; i < nsamps; i++) { // loop over nsamps - out[i].r += ant_buffers[aatx][i].r * H_awgn_mimo_coeff[aatx]; - out[i].i += ant_buffers[aatx][i].i * H_awgn_mimo_coeff[aatx]; - } // end for a_tx - } // end for i (number of samps) - } // end for a_rx - } + openair0_timestamp timestamp = t->nextRxTstamp; + int nsamps_to_process = nsamps; + while (nsamps_to_process > 0) { + uint32_t nsamps_beam_map; + uint64_t beam_map = get_beam_map(&t->beam_ctrl->tx, timestamp, nsamps_to_process, &nsamps_beam_map); + std::vector rx_beam_ids = get_beam_ids(beam_map); + if ((int)rx_beam_ids.size() != num_beams) { + LOG_D(HW, + "Number of beams does not match application request num_beams %d, beam_map beams %lu\n", + num_beams, + rx_beam_ids.size()); + } + for (int beam = 0; beam < num_beams && beam < (int)rx_beam_ids.size(); beam++) { + c16_t *samples_beam[nbAnt]; + for (int i = 0; i < nbAnt; i++) { + samples_beam[i] = (c16_t *)samplesVoid[beam][i] + timestamp - t->nextRxTstamp; } + rfsimulator_read_internal(t, samples_beam, timestamp, nsamps_beam_map, nbAnt, rx_beam_ids[beam]); } - - clear_old_packets(ptr->received_packets, timestamp_processed); - } - - bool apply_global_noise = get_noise_power_dBFS() != INVALID_DBFS_VALUE; - if (apply_global_noise) { - for (int a = 0; a < nbAnt; a++) { - for (int i = 0; i < nsamps; i++) { - int16_t noise_power = (int16_t)(32767.0 / powf(10.0, .05 * -get_noise_power_dBFS())); - temp_array[a][i].r += noise_power + gaussZiggurat(0.0, 1.0); - temp_array[a][i].i += noise_power * gaussZiggurat(0.0, 1.0); - } - } - } - - 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); - } + timestamp += nsamps_beam_map; + nsamps_to_process -= nsamps_beam_map; } struct timespec end_time; @@ -1327,14 +1354,31 @@ static int rfsimulator_read(openair0_device *device, openair0_timestamp *ptimest nsamps, *ptimestamp, t->nextRxTstamp, - signal_energy(static_cast(samplesVoid[0]), nsamps)); + signal_energy(static_cast(samplesVoid[0][0]), nsamps)); /* trace only first antenna */ T(T_USRP_RX_ANT0, T_INT(t->nextRxTstamp), T_BUFFER(samplesVoid[0], (int)sampleToByte(nsamps, 1))); + for (int sock = 0; sock < MAX_FD_RFSIMU; sock++) { + buffer_t *ptr = &t->buf[sock]; + + if (ptr->conn_sock != -1 && !ptr->received_packets.empty()) { + openair0_timestamp timestamp_to_free = t->nextRxTstamp - 1; + if (ptr->channel_model) { + timestamp_to_free -= ptr->channel_model->channel_length - ptr->channel_model->channel_offset; + } + clear_old_packets(ptr->received_packets, timestamp_to_free); + } + } + return nsamps; } +static int rfsimulator_read(openair0_device *device, openair0_timestamp *ptimestamp, void **samplesVoid, int nsamps, int nbAnt) +{ + return rfsimulator_read_beams(device, ptimestamp, &samplesVoid, nsamps, nbAnt, 1); +} + static int rfsimulator_get_stats(openair0_device *device) { return 0; @@ -1417,6 +1461,7 @@ extern "C" __attribute__((__visibility__("default"))) int device_init(openair0_d device->trx_write_func = rfsimulator_write; device->trx_write_beams_func = rfsimulator_write_beams; device->trx_read_func = rfsimulator_read; + device->trx_read_beams_func = rfsimulator_read_beams; /* let's pretend to be a b2x0 */ device->type = RFSIMULATOR; openair0_cfg[0].rx_gain[0] = 0; From 435dfd57d82186b4ba79598449fbfffeb7e449f7 Mon Sep 17 00:00:00 2001 From: Bartosz Podrygajlo Date: Tue, 14 Oct 2025 13:43:43 +0200 Subject: [PATCH 09/19] RFsim: Optimization for operation without chanmod --- radio/rfsimulator/simulator.cpp | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/radio/rfsimulator/simulator.cpp b/radio/rfsimulator/simulator.cpp index 5c01322fa6..a940f6f023 100644 --- a/radio/rfsimulator/simulator.cpp +++ b/radio/rfsimulator/simulator.cpp @@ -1167,7 +1167,7 @@ static void rfsimulator_read_internal(rfsimulator_state_t *t, int rx_beam_id) { cf_t temp_array[nbAnt][nsamps]; - memset(temp_array, 0, sizeof(temp_array)); + bool channel_modelling = false; // Add all input nodes signal in the output buffer for (int sock = 0; sock < MAX_FD_RFSIMU; sock++) { buffer_t *ptr = &t->buf[sock]; @@ -1182,6 +1182,10 @@ static void rfsimulator_read_internal(rfsimulator_state_t *t, random_channel(ptr->channel_model, 0); if (ptr->channel_model != NULL) { // apply a channel model + if (!channel_modelling) { + memset(temp_array, 0, sizeof(temp_array)); + channel_modelling = true; + } const uint64_t dd = ptr->channel_model->channel_offset; const uint64_t channel_length = ptr->channel_model->channel_length; std::vector> ant_buffers = combine_received_beams(t, @@ -1220,19 +1224,25 @@ static void rfsimulator_read_internal(rfsimulator_state_t *t, bool apply_global_noise = get_noise_power_dBFS() != INVALID_DBFS_VALUE; if (apply_global_noise) { + if (!channel_modelling) { + memset(temp_array, 0, sizeof(temp_array)); + channel_modelling = true; + } + int16_t noise_power = (int16_t)(32767.0 / powf(10.0, .05 * -get_noise_power_dBFS())); for (int a = 0; a < nbAnt; a++) { for (int i = 0; i < nsamps; i++) { - int16_t noise_power = (int16_t)(32767.0 / powf(10.0, .05 * -get_noise_power_dBFS())); temp_array[a][i].r += noise_power + gaussZiggurat(0.0, 1.0); temp_array[a][i].i += noise_power * gaussZiggurat(0.0, 1.0); } } } - for (int a = 0; a < nbAnt; a++) { - for (int i = 0; i < nsamps; i++) { - samples[a][i].r += lroundf(temp_array[a][i].r); - samples[a][i].i += lroundf(temp_array[a][i].i); + if (channel_modelling) { + for (int a = 0; a < nbAnt; a++) { + for (int i = 0; i < nsamps; i++) { + samples[a][i].r += lroundf(temp_array[a][i].r); + samples[a][i].i += lroundf(temp_array[a][i].i); + } } } } From 05de66d811c448d419aadc7580187a7392d20e1a Mon Sep 17 00:00:00 2001 From: Bartosz Podrygajlo Date: Fri, 17 Oct 2025 08:24:27 +0200 Subject: [PATCH 10/19] Make RFSIMULATOR_PARAMS_DESC C++ compatible. Add a set of macros with designated initializers to simplify using paramdef_t arrays in C++ source code. Fix clang error with RFSIMULATOR_PARAMS_DESC using mixed designated and non-designated initializers. --- common/config/config_paramdesc.h | 44 ++++++++++++++++++++++++++++++++ radio/rfsimulator/simulator.cpp | 42 +++++++++++++++--------------- 2 files changed, 65 insertions(+), 21 deletions(-) diff --git a/common/config/config_paramdesc.h b/common/config/config_paramdesc.h index 904b9dfcb0..9333a778af 100644 --- a/common/config/config_paramdesc.h +++ b/common/config/config_paramdesc.h @@ -161,6 +161,50 @@ typedef struct paramdef { #define TYPE_LIST 55 #define ANY_IPV4ADDR_STRING "0.0.0.0" + +// Macros to simplify defining parameters in paramdef_t arrays in C++ source code +#define OPTNAME(x) .optname = x +#define HELPSTR(x) .helpstr = x +#define PARAMFLAG(x) .paramflags = x +#define PARAMTYPE(x) .type = x +#define STRINGPARAM(name, help, flags, ptr, defval) \ + { \ + OPTNAME(name), HELPSTR(help), PARAMFLAG(flags), .strptr = ptr, .defstrval = defval, PARAMTYPE(TYPE_STRING), .numelt = 0 \ + } +#define DOUBLEPARAM(name, help, flags, ptr, defval) \ + { \ + OPTNAME(name), HELPSTR(help), PARAMFLAG(flags), .dblptr = ptr, .defdblval = defval, PARAMTYPE(TYPE_DOUBLE), .numelt = 0 \ + } +#define INTPARAM(name, help, flags, ptr, defval) \ + { \ + OPTNAME(name), HELPSTR(help), PARAMFLAG(flags), .iptr = ptr, .defintval = defval, PARAMTYPE(TYPE_INT), .numelt = 0 \ + } +#define UINT16PARAM(name, help, flags, ptr, defval) \ + { \ + OPTNAME(name), HELPSTR(help), PARAMFLAG(flags), .u16ptr = ptr, .defuintval = defval, PARAMTYPE(TYPE_UINT16), .numelt = 0 \ + } +#define STRLISTPARAM(name, help, flags, ptr, defval) \ + { \ + OPTNAME(name), HELPSTR(help), PARAMFLAG(flags), .strlistptr = ptr, .defstrlistval = defval, PARAMTYPE(TYPE_STRINGLIST), \ + .numelt = 0 \ + } +#define INT64PARAM(name, help, flags, ptr, defval) \ + { \ + OPTNAME(name), HELPSTR(help), PARAMFLAG(flags), .i64ptr = ptr, .defint64val = defval, PARAMTYPE(TYPE_INT64), .numelt = 0 \ + } +#define INTPARAM(name, help, flags, ptr, defval) \ + { \ + OPTNAME(name), HELPSTR(help), PARAMFLAG(flags), .iptr = ptr, .defintval = defval, PARAMTYPE(TYPE_INT), .numelt = 0 \ + } +#define BOOLPARAM(name, help, flags, ptr, defval) \ + { \ + OPTNAME(name), HELPSTR(help), PARAMFLAG(flags), .iptr = ptr, .defintval = defval, PARAMTYPE(TYPE_INT), .numelt = 0 \ + } +#define UINT64PARAM(name, help, flags, ptr, defval) \ + { \ + OPTNAME(name), HELPSTR(help), PARAMFLAG(flags), .u64ptr = ptr, .defuintval = defval, PARAMTYPE(TYPE_UINT64), .numelt = 0 \ + } + typedef struct paramlist_def { char listname[MAX_OPTNAME_SIZE]; paramdef_t **paramarray; diff --git a/radio/rfsimulator/simulator.cpp b/radio/rfsimulator/simulator.cpp index a940f6f023..e06e9f5bab 100644 --- a/radio/rfsimulator/simulator.cpp +++ b/radio/rfsimulator/simulator.cpp @@ -83,29 +83,29 @@ typedef enum { SIMU_ROLE_SERVER = 1, SIMU_ROLE_CLIENT } simuRole; " 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 +// clang-format off +/*----------------------------------------------------------------------------------------------------------------------------------------------------*/ +/* configuration parameters for the rfsimulator device */ +/* optname helpstr paramflags XXXptr defXXXval */ +/*----------------------------------------------------------------------------------------------------------------------------------------------------*/ #define RFSIMULATOR_PARAMS_DESC { \ - {"serveraddr", "\n", simOpt, .strptr=&rfsimulator->ip, .defstrval="127.0.0.1", TYPE_STRING, 0 },\ - {"serverport", "\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", "\n",simOpt, .strptr=&saveF, .defstrval="/tmp/rfsimulator.iqs",TYPE_STRING, 0 },\ - {"modelname", "\n", simOpt, .strptr = &modelname, .defstrval = "AWGN", TYPE_STRING, 0}, \ - {"ploss", "\n", simOpt, .dblptr = &(rfsimulator->chan_pathloss), .defdblval = 0, TYPE_DOUBLE, 0}, \ - {"forgetfact", "\n", simOpt, .dblptr=&(rfsimulator->chan_forgetfact),.defdblval=0, TYPE_DOUBLE, 0 },\ - {"offset", "\n", simOpt, .u64ptr = &(rfsimulator->chan_offset), .defint64val = 0, TYPE_UINT64, 0}, \ - {"prop_delay", "\n", simOpt, .dblptr=&(rfsimulator->prop_delay_ms), .defdblval=0.0, TYPE_DOUBLE, 0 },\ - {"wait_timeout", "\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}, \ - }; + STRINGPARAM("serveraddr", "\n", simOpt, &rfsimulator->ip, "127.0.0.1"), \ + UINT16PARAM("serverport", "\n", simOpt, &rfsimulator->port, PORT), \ + STRLISTPARAM(RFSIMU_OPTIONS_PARAMNAME, RFSIM_CONFIG_HELP_OPTIONS, simOpt, NULL, NULL), \ + STRINGPARAM("IQfile", "\n", simOpt, &saveF, "/tmp/rfsimulator.iqs"),\ + STRINGPARAM("modelname", "\n", simOpt, &modelname, "AWGN"), \ + DOUBLEPARAM("ploss", "\n", simOpt, &rfsimulator->chan_pathloss, 0), \ + DOUBLEPARAM("forgetfact", "\n", simOpt, &rfsimulator->chan_forgetfact, 0), \ + UINT64PARAM("offset", "\n", simOpt, &rfsimulator->chan_offset, 0L), \ + DOUBLEPARAM("prop_delay", "\n", simOpt, &(rfsimulator->prop_delay_ms), 0.0), \ + INTPARAM("wait_timeout", "\n", simOpt, &rfsimulator->wait_timeout, 1), \ + BOOLPARAM("enable_beams", "\n", simOpt, &(beam_ctrl->enable_beams), 0), \ + INTPARAM("num_concurrent_beams", "\n", simOpt, &beam_ctrl->num_concurrent_beams, 1), \ + UINT64PARAM("tx_beam_map", "\n", simOpt, &beam_ctrl->tx.beam_map, 1), \ + UINT64PARAM("rx_beam_map", "\n", simOpt, &beam_ctrl->rx.beam_map, 1), \ + STRINGPARAM(RFSIMU_BEAM_GAINS, "\n", simOpt, NULL, NULL), \ +}; // 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, From c587aab6b6933dab2750122ef3c8c4f2fbef69b2 Mon Sep 17 00:00:00 2001 From: Bartosz Podrygajlo Date: Fri, 17 Oct 2025 14:39:50 +0200 Subject: [PATCH 11/19] RFsim: Fix freeing packets in case of non-negative channel offset In rfsim, received packets are dynamically allocated and freed when no longer needed, which is dependent on the channel model, packet timestamp and packet size (number of samples carried). This commit fixes the packet age check for channels with nonzero channel offset (most notalby NTN channels). Also present: - extract the NTN satellite position update code to a separate function and call it once per trx_read call instead of once per rx beam. --- radio/rfsimulator/apply_channelmod.c | 44 +++++++++++++++------------- radio/rfsimulator/rfsimulator.h | 2 +- radio/rfsimulator/simulator.cpp | 14 +++++++-- 3 files changed, 35 insertions(+), 25 deletions(-) diff --git a/radio/rfsimulator/apply_channelmod.c b/radio/rfsimulator/apply_channelmod.c index 2eaa6a66c2..2dac26c4e6 100644 --- a/radio/rfsimulator/apply_channelmod.c +++ b/radio/rfsimulator/apply_channelmod.c @@ -27,29 +27,9 @@ #include "openair2/LAYER2/NR_MAC_gNB/mac_config.h" #include "rfsimulator.h" -/* - Legacy study: - The parameters are: - gain&loss (decay, signal power, ...) - either a fixed gain in dB, a target power in dBm or ACG (automatic control gain) to a target average - => don't redo the AGC, as it was used in UE case, that must have a AGC inside the UE - will be better to handle the "set_gain()" called by UE to apply it's gain (enable test of UE power loop) - lin_amp = pow(10.0,.05*txpwr_dBm)/sqrt(nb_tx_antennas); - a lot of operations in legacy, grouped in one simulation signal decay: txgain*decay*rxgain - - multi_path (auto convolution, ISI, ...) - either we regenerate the channel (call again random_channel(desc,0)), or we keep it over subframes - legacy: we regenerate each sub frame in UL, and each frame only in DL -*/ -void rxAddInput(const c16_t **input_sig, - cf_t *after_channel_sig, - int rxAnt, - channel_desc_t *channelDesc, - int nbSamples, - uint64_t TS) +void update_channel_model(channel_desc_t *channelDesc, uint64_t TS) { static uint64_t last_TS = 0; - if ((channelDesc->sat_height > 0) && (channelDesc->enable_dynamic_delay || channelDesc->enable_dynamic_Doppler)) { // model for transparent satellite on circular orbit @@ -239,7 +219,29 @@ void rxAddInput(const c16_t **input_sig, } } } +} +/* + Legacy study: + The parameters are: + gain&loss (decay, signal power, ...) + either a fixed gain in dB, a target power in dBm or ACG (automatic control gain) to a target average + => don't redo the AGC, as it was used in UE case, that must have a AGC inside the UE + will be better to handle the "set_gain()" called by UE to apply it's gain (enable test of UE power loop) + lin_amp = pow(10.0,.05*txpwr_dBm)/sqrt(nb_tx_antennas); + a lot of operations in legacy, grouped in one simulation signal decay: txgain*decay*rxgain + + multi_path (auto convolution, ISI, ...) + either we regenerate the channel (call again random_channel(desc,0)), or we keep it over subframes + legacy: we regenerate each sub frame in UL, and each frame only in DL +*/ +void rxAddInput(const c16_t **input_sig, + cf_t *after_channel_sig, + int rxAnt, + channel_desc_t *channelDesc, + int nbSamples, + uint64_t TS) +{ // channelDesc->path_loss_dB should contain the total path gain // so, in actual RF: tx gain + path loss + rx gain (+antenna gain, ...) // UE and NB gain control to be added diff --git a/radio/rfsimulator/rfsimulator.h b/radio/rfsimulator/rfsimulator.h index 0ebdef0d6d..90c2be2f82 100644 --- a/radio/rfsimulator/rfsimulator.h +++ b/radio/rfsimulator/rfsimulator.h @@ -30,5 +30,5 @@ void rxAddInput(const c16_t **input_sig, channel_desc_t *channelDesc, int nbSamples, uint64_t TS); - +void update_channel_model(channel_desc_t *channelDesc, uint64_t TS); #endif diff --git a/radio/rfsimulator/simulator.cpp b/radio/rfsimulator/simulator.cpp index e06e9f5bab..793dfedf18 100644 --- a/radio/rfsimulator/simulator.cpp +++ b/radio/rfsimulator/simulator.cpp @@ -1186,11 +1186,11 @@ static void rfsimulator_read_internal(rfsimulator_state_t *t, memset(temp_array, 0, sizeof(temp_array)); channel_modelling = true; } - const uint64_t dd = ptr->channel_model->channel_offset; + const uint64_t channel_offset = ptr->channel_model->channel_offset; const uint64_t channel_length = ptr->channel_model->channel_length; std::vector> ant_buffers = combine_received_beams(t, ptr->received_packets, - timestamp - dd - (channel_length - 1), + timestamp - channel_offset - (channel_length - 1), ptr->nbAnt, nsamps + channel_length - 1, rx_beam_id); @@ -1314,6 +1314,14 @@ static int rfsimulator_read_beams(openair0_device *device, int ret = clock_gettime(CLOCK_REALTIME, &start_time); AssertFatal(ret == 0, "clock_gettime() failed: errno %d, %s\n", errno, strerror(errno)); + for (int sock = 0; sock < MAX_FD_RFSIMU; sock++) { + buffer_t *ptr = &t->buf[sock]; + + if (ptr->conn_sock != -1 && ptr->channel_model != NULL) { + update_channel_model(ptr->channel_model, t->nextRxTstamp); + } + } + if (t->poll_telnetcmdq) t->poll_telnetcmdq(t->telnetcmd_qid, t); @@ -1375,7 +1383,7 @@ static int rfsimulator_read_beams(openair0_device *device, if (ptr->conn_sock != -1 && !ptr->received_packets.empty()) { openair0_timestamp timestamp_to_free = t->nextRxTstamp - 1; if (ptr->channel_model) { - timestamp_to_free -= ptr->channel_model->channel_length - ptr->channel_model->channel_offset; + timestamp_to_free -= (ptr->channel_model->channel_length - 1) + ptr->channel_model->channel_offset; } clear_old_packets(ptr->received_packets, timestamp_to_free); } From 033aed736e8b2555180fb5221b52ff55c967134a Mon Sep 17 00:00:00 2001 From: Bartosz Podrygajlo Date: Wed, 22 Oct 2025 21:08:17 +0200 Subject: [PATCH 12/19] Rfsim: Optimization for simple configurations - Added first beam optimization to combine_tx_buffers. - Added first beam & first peer optimization to rfsimulator_read_internal This results in significant speed up for the first rx beam, first tx beam and first peer which is the base case with beam simulation disabled. --- radio/rfsimulator/apply_channelmod.c | 2 +- radio/rfsimulator/rfsimulator.h | 2 +- radio/rfsimulator/simulator.cpp | 109 ++++++++++++++++++--------- 3 files changed, 76 insertions(+), 37 deletions(-) diff --git a/radio/rfsimulator/apply_channelmod.c b/radio/rfsimulator/apply_channelmod.c index 2dac26c4e6..3f2e8d85c1 100644 --- a/radio/rfsimulator/apply_channelmod.c +++ b/radio/rfsimulator/apply_channelmod.c @@ -235,7 +235,7 @@ void update_channel_model(channel_desc_t *channelDesc, uint64_t TS) either we regenerate the channel (call again random_channel(desc,0)), or we keep it over subframes legacy: we regenerate each sub frame in UL, and each frame only in DL */ -void rxAddInput(const c16_t **input_sig, +void rxAddInput(c16_t **input_sig, cf_t *after_channel_sig, int rxAnt, channel_desc_t *channelDesc, diff --git a/radio/rfsimulator/rfsimulator.h b/radio/rfsimulator/rfsimulator.h index 90c2be2f82..802a2a2e34 100644 --- a/radio/rfsimulator/rfsimulator.h +++ b/radio/rfsimulator/rfsimulator.h @@ -24,7 +24,7 @@ #ifndef __RFSIMULATOR_H #define __RFSIMULATOR_H #include -void rxAddInput(const c16_t **input_sig, +void rxAddInput(c16_t **input_sig, cf_t *after_channel_sig, int rxAnt, channel_desc_t *channelDesc, diff --git a/radio/rfsimulator/simulator.cpp b/radio/rfsimulator/simulator.cpp index 793dfedf18..b4b4b12ea2 100644 --- a/radio/rfsimulator/simulator.cpp +++ b/radio/rfsimulator/simulator.cpp @@ -1045,17 +1045,18 @@ static std::vector get_beam_ids(uint64_t beam_map) * @param num_aatx The number of antennas to process. * @param num_samples The number of samples to process. * @param rx_beam_id Receiver configured beam id for the timestamp + * @param samples Pointer to the output buffer where combined samples will be stored. * @return A vector of vectors containing the combined samples for each antenna. */ -static std::vector> combine_received_beams(rfsimulator_state_t *t, - std::queue &received_packets, - uint64_t start_timestamp, - int num_aatx, - size_t num_samples, - int rx_beam_id) +static void combine_received_beams(rfsimulator_state_t *t, + std::queue &received_packets, + uint64_t start_timestamp, + int num_aatx, + size_t num_samples, + int rx_beam_id, + c16_t **samples) { // Assume received_packets is ordered by timestamp - std::vector> ant_buffers(num_aatx, std::vector(num_samples, {0, 0})); std::queue packets_copy = received_packets; while (!packets_copy.empty()) { rfsim_packet_t *pkt = packets_copy.front(); @@ -1081,16 +1082,38 @@ static std::vector> combine_received_beams(rfsimulator_state_ for (int aatx = 0; aatx < num_aatx; aatx++) { c16_t *buffer = (c16_t *)pkt->payload; c16_t *tx_ant_buffer_in = &buffer[(num_aatx * beam + aatx) * pkt->header.size + read_start_idx]; - for (int s = write_start_idx; s < write_end_idx; s++) { - ant_buffers[aatx][s].r += tx_ant_buffer_in->r * gain_linear; - ant_buffers[aatx][s].i += tx_ant_buffer_in->i * gain_linear; - tx_ant_buffer_in++; + if (beam == 0) { + // For the first beam, we can directly copy the samples + if (gain_dB == 0.0f) { + // If gain is 0 dB, we can use memcpy for efficiency + memcpy(&samples[aatx][write_start_idx], tx_ant_buffer_in, (write_end_idx - write_start_idx) * sizeof(c16_t)); + } else { + for (int s = write_start_idx; s < write_end_idx; s++) { + samples[aatx][s].r = tx_ant_buffer_in->r * gain_linear; + samples[aatx][s].i = tx_ant_buffer_in->i * gain_linear; + tx_ant_buffer_in++; + } + } + } else { + // For subsequent beams, we need to ensure we accumulate the samples + if (gain_dB == 0.0f) { + for (int s = write_start_idx; s < write_end_idx; s++) { + samples[aatx][s].r += tx_ant_buffer_in->r; + samples[aatx][s].i += tx_ant_buffer_in->i; + tx_ant_buffer_in++; + } + } else { + for (int s = write_start_idx; s < write_end_idx; s++) { + samples[aatx][s].r += tx_ant_buffer_in->r * gain_linear; + samples[aatx][s].i += tx_ant_buffer_in->i * gain_linear; + tx_ant_buffer_in++; + } + } } } } packets_copy.pop(); } - return ant_buffers; } static bool flushInput(rfsimulator_state_t *t, int timeout, bool first_time) @@ -1164,11 +1187,13 @@ static void rfsimulator_read_internal(rfsimulator_state_t *t, openair0_timestamp timestamp, int nsamps, int nbAnt, - int rx_beam_id) + int rx_beam_id, + bool is_first_beam) { cf_t temp_array[nbAnt][nsamps]; bool channel_modelling = false; // Add all input nodes signal in the output buffer + bool is_first_peer = true; for (int sock = 0; sock < MAX_FD_RFSIMU; sock++) { buffer_t *ptr = &t->buf[sock]; @@ -1188,38 +1213,52 @@ static void rfsimulator_read_internal(rfsimulator_state_t *t, } const uint64_t channel_offset = ptr->channel_model->channel_offset; const uint64_t channel_length = ptr->channel_model->channel_length; - std::vector> ant_buffers = combine_received_beams(t, - ptr->received_packets, - timestamp - channel_offset - (channel_length - 1), - ptr->nbAnt, - nsamps + channel_length - 1, - rx_beam_id); - const c16_t *input[ant_buffers.size()]; + std::vector> ant_buffers(ptr->nbAnt, std::vector(nsamps + channel_length - 1, {0, 0})); + c16_t *input[ant_buffers.size()]; for (uint aatx = 0; aatx < ant_buffers.size(); aatx++) { input[aatx] = ant_buffers[aatx].data(); } + + combine_received_beams(t, + ptr->received_packets, + timestamp - channel_offset - (channel_length - 1), + ptr->nbAnt, + nsamps + channel_length - 1, + rx_beam_id, + input); + for (int aarx = 0; aarx < nbAnt; aarx++) { rxAddInput(input, temp_array[aarx], aarx, ptr->channel_model, nsamps, timestamp); } } else { - std::vector> ant_buffers = - combine_received_beams(t, ptr->received_packets, timestamp, ptr->nbAnt, nsamps, rx_beam_id); - for (int aarx = 0; aarx < nbAnt; aarx++) { - double H_awgn_mimo_coeff[ant_buffers.size()]; - for (int aatx = 0; aatx < (int)ant_buffers.size(); aatx++) { - uint32_t ant_diff = std::abs(aatx - aarx); - H_awgn_mimo_coeff[aatx] = ant_diff ? (0.2 / ant_diff) : 1.0; + if (is_first_beam && is_first_peer && (ptr->nbAnt == 1 || nbAnt == 1)) { + // optimization: The buffer is uninitialized so samples can be written directly in the buffer + combine_received_beams(t, ptr->received_packets, timestamp, 1, nsamps, rx_beam_id, samples); + } else { + std::vector> ant_buffers(ptr->nbAnt, std::vector(nsamps, {0, 0})); + c16_t *input[ant_buffers.size()]; + for (uint aatx = 0; aatx < ant_buffers.size(); aatx++) { + input[aatx] = ant_buffers[aatx].data(); } + combine_received_beams(t, ptr->received_packets, timestamp, ptr->nbAnt, nsamps, rx_beam_id, input); + for (int aarx = 0; aarx < nbAnt; aarx++) { + double H_awgn_mimo_coeff[ant_buffers.size()]; + for (int aatx = 0; aatx < (int)ant_buffers.size(); aatx++) { + uint32_t ant_diff = std::abs(aatx - aarx); + H_awgn_mimo_coeff[aatx] = ant_diff ? (0.2 / ant_diff) : 1.0; + } - for (uint aatx = 0; aatx < ant_buffers.size(); aatx++) { // sum up signals from nbAnt_tx antennas - for (int i = 0; i < nsamps; i++) { // loop over nsamps - samples[aarx][i].r += ant_buffers[aatx][i].r * H_awgn_mimo_coeff[aatx]; - samples[aarx][i].i += ant_buffers[aatx][i].i * H_awgn_mimo_coeff[aatx]; - } // end for a_tx - } // end for i (number of samps) - } // end for a_rx + for (uint aatx = 0; aatx < ant_buffers.size(); aatx++) { + for (int i = 0; i < nsamps; i++) { + samples[aarx][i].r += ant_buffers[aatx][i].r * H_awgn_mimo_coeff[aatx]; + samples[aarx][i].i += ant_buffers[aatx][i].i * H_awgn_mimo_coeff[aatx]; + } + } + } + } } } + is_first_peer = false; } bool apply_global_noise = get_noise_power_dBFS() != INVALID_DBFS_VALUE; @@ -1347,7 +1386,7 @@ static int rfsimulator_read_beams(openair0_device *device, for (int i = 0; i < nbAnt; i++) { samples_beam[i] = (c16_t *)samplesVoid[beam][i] + timestamp - t->nextRxTstamp; } - rfsimulator_read_internal(t, samples_beam, timestamp, nsamps_beam_map, nbAnt, rx_beam_ids[beam]); + rfsimulator_read_internal(t, samples_beam, timestamp, nsamps_beam_map, nbAnt, rx_beam_ids[beam], beam == 0); } timestamp += nsamps_beam_map; nsamps_to_process -= nsamps_beam_map; From b10617607aed6240823812139528cd07cbeca54b Mon Sep 17 00:00:00 2001 From: Bartosz Podrygajlo Date: Thu, 23 Oct 2025 16:18:34 +0200 Subject: [PATCH 13/19] RFsim: Allow arbitrary order of beams. Allow arbitrary order of beam ids in set_beams2 function. This allows the application to select any arbitrary order of beam ids. The underlying transport mechanism still uses beam_map so the beam_ids are ordered before sending. Also replaced tx_beam_map/rx_beam_map into beam_map. Also fixed an issue where --rfsimulator.enable_beams required an argument. --- radio/COMMON/common_lib.h | 16 +++++ radio/rfsimulator/simulator.cpp | 116 +++++++++++++++++++++----------- 2 files changed, 91 insertions(+), 41 deletions(-) diff --git a/radio/COMMON/common_lib.h b/radio/COMMON/common_lib.h index cb1b0c8ddc..5705b9de45 100644 --- a/radio/COMMON/common_lib.h +++ b/radio/COMMON/common_lib.h @@ -623,6 +623,22 @@ struct openair0_device_t { */ int (*trx_set_beams)(openair0_device *device, uint64_t beam_map, openair0_timestamp timestamp); + /*! \brief Set tx/rx beams + * + * Set the tx/rx beams. This has to be done in advance of the reception in order to + * allow the underlying device to change receiver configuration. The exact time depends + * on the device. + * + * NOTICE: the samples returned from trx_read_func may belong to more than one beam. It is up + * to the application to determine the beam of the received samples. + * + * \param device the hardware to use + * \param beams pointer to array of beam ids + * \param num_beams number of beams + * \return 0 on success + */ + int (*trx_set_beams2)(openair0_device *device, int* beams, int num_beams, openair0_timestamp timestamp); + /*! \brief RRU Configuration callback * \param idx RU index * \param arg pointer to capabilities or configuration diff --git a/radio/rfsimulator/simulator.cpp b/radio/rfsimulator/simulator.cpp index b4b4b12ea2..0ec98f1a4d 100644 --- a/radio/rfsimulator/simulator.cpp +++ b/radio/rfsimulator/simulator.cpp @@ -30,6 +30,7 @@ #include "PHY/TOOLS/tools_defs.h" #include "PHY/defs_common.h" #include "utils.h" +#include #include #include #include @@ -59,6 +60,7 @@ extern "C" { #include #include #include +#include #define PORT 4043 // default TCP port for this simulator #define sampleToByte(a, b) ((a) * (b) * sizeof(sample_t)) @@ -84,6 +86,7 @@ typedef enum { SIMU_ROLE_SERVER = 1, SIMU_ROLE_CLIENT } simuRole; " saviq: enable saving written iqs to a file\n" #define simOpt PARAMFLAG_NOFREE | PARAMFLAG_CMDLINE_NOPREFIXENABLED +#define simBool PARAMFLAG_BOOL | PARAMFLAG_NOFREE | PARAMFLAG_CMDLINE_NOPREFIXENABLED // clang-format off /*----------------------------------------------------------------------------------------------------------------------------------------------------*/ /* configuration parameters for the rfsimulator device */ @@ -100,10 +103,9 @@ typedef enum { SIMU_ROLE_SERVER = 1, SIMU_ROLE_CLIENT } simuRole; UINT64PARAM("offset", "\n", simOpt, &rfsimulator->chan_offset, 0L), \ DOUBLEPARAM("prop_delay", "\n", simOpt, &(rfsimulator->prop_delay_ms), 0.0), \ INTPARAM("wait_timeout", "\n", simOpt, &rfsimulator->wait_timeout, 1), \ - BOOLPARAM("enable_beams", "\n", simOpt, &(beam_ctrl->enable_beams), 0), \ + BOOLPARAM("enable_beams", "\n", simBool,&(beam_ctrl->enable_beams), 0), \ INTPARAM("num_concurrent_beams", "\n", simOpt, &beam_ctrl->num_concurrent_beams, 1), \ - UINT64PARAM("tx_beam_map", "\n", simOpt, &beam_ctrl->tx.beam_map, 1), \ - UINT64PARAM("rx_beam_map", "\n", simOpt, &beam_ctrl->rx.beam_map, 1), \ + UINT64PARAM("beam_map", "\n", simOpt, &beam_map, 1), \ STRINGPARAM(RFSIMU_BEAM_GAINS, "\n", simOpt, NULL, NULL), \ }; // clang-format on @@ -134,12 +136,12 @@ static telnetshell_vardef_t rfsimu_vardef[] = {{"", 0, 0, NULL}}; typedef c16_t sample_t; // 2*16 bits complex number typedef struct beam_switch_command_t { - uint64_t beam_map; + std::vector beams; openair0_timestamp timestamp; } beam_switch_command_t; typedef struct { - uint64_t beam_map; + std::vector beams; std::queue cmd_queue; std::mutex mutex; } beam_state_t; @@ -212,10 +214,10 @@ typedef struct { * @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) +static std::vector get_beams(beam_state_t *beam_state, openair0_timestamp timestamp, uint32_t nsamps, uint32_t *nsamps_out) { std::lock_guard lock(beam_state->mutex); - uint64_t current_map = beam_state->beam_map; + std::vector current_beams = beam_state->beams; uint32_t samples_to_next_switch = nsamps; // Find the latest beam_switch_command_t with timestamp <= requested timestamp @@ -225,7 +227,7 @@ static uint64_t get_beam_map(beam_state_t *beam_state, openair0_timestamp timest while (!queue_copy.empty()) { const beam_switch_command_t &cmd = queue_copy.front(); if (cmd.timestamp <= timestamp) { - current_map = cmd.beam_map; + current_beams = cmd.beams; queue_copy.pop(); } else { samples_to_next_switch = (cmd.timestamp > timestamp) ? (cmd.timestamp - timestamp) : nsamps; @@ -240,9 +242,33 @@ static uint64_t get_beam_map(beam_state_t *beam_state, openair0_timestamp timest *nsamps_out = samples_to_next_switch; - return current_map; + return current_beams; } + +static std::vector beam_map_to_beams(uint64_t beam_map) +{ + int num_beams = __builtin_popcountll(beam_map); + AssertFatal(num_beams > 0, "Needs at least one beam\n"); + std::vector beam_ids; + for (int i = 0; i < MAX_BEAMS; i++) { + if (beam_map & (1ULL << i)) { + beam_ids.push_back(i); + } + } + return beam_ids; +} + +static uint64_t beams_to_beam_map(const std::vector &beam_ids) +{ + uint64_t beam_map = 0; + for (size_t i = 0; i < beam_ids.size(); i++) { + beam_map |= (1ULL << beam_ids[i]); + } + return beam_map; +} + + /** * @brief Clears outdated beam switch commands from the queue and updates the current beam map. * @@ -259,7 +285,7 @@ static void clear_beam_queue(beam_state_t *beam_state, openair0_timestamp timest std::lock_guard 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->beams = beam_state->cmd_queue.front().beams; beam_state->cmd_queue.pop(); } else { break; @@ -448,7 +474,19 @@ static int rfsimulator_set_beams(openair0_device *device, uint64_t beam_map, ope rfsim_beam_ctrl_t *beam_ctrl = s->beam_ctrl; std::lock_guard lock_tx(beam_ctrl->tx.mutex); std::lock_guard lock_rx(beam_ctrl->rx.mutex); - beam_switch_command_t command = {.beam_map = beam_map, .timestamp = timestamp}; + beam_switch_command_t command = {.beams = beam_map_to_beams(beam_map), .timestamp = timestamp}; + beam_ctrl->rx.cmd_queue.emplace(command); + beam_ctrl->tx.cmd_queue.emplace(command); + return 0; +} + +static int rfsimulator_set_beams_vector(openair0_device *device, int* beams, int num_beams, openair0_timestamp timestamp) +{ + rfsimulator_state_t *s = static_cast(device->priv); + rfsim_beam_ctrl_t *beam_ctrl = s->beam_ctrl; + std::lock_guard lock_tx(beam_ctrl->tx.mutex); + std::lock_guard lock_rx(beam_ctrl->rx.mutex); + beam_switch_command_t command = {.beams = std::vector(beams, beams + num_beams), .timestamp = timestamp}; beam_ctrl->rx.cmd_queue.emplace(command); beam_ctrl->tx.cmd_queue.emplace(command); return 0; @@ -481,6 +519,7 @@ static void rfsimulator_readconfig(rfsimulator_state_t *rfsimulator) char *saveF = NULL; char *modelname = NULL; rfsim_beam_ctrl_t *beam_ctrl = rfsimulator->beam_ctrl; + uint64_t beam_map = 1; 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); @@ -519,6 +558,10 @@ static void rfsimulator_readconfig(rfsimulator_state_t *rfsimulator) process_gains(*rfsimu_params[beam_gains_param_index].strptr, beam_ctrl); } + std::vector initial_beams = beam_map_to_beams(beam_map); + beam_ctrl->rx.beams = initial_beams; + beam_ctrl->tx.beams = initial_beams; + if (strncasecmp(rfsimulator->ip, "enb", 3) == 0 || strncasecmp(rfsimulator->ip, "server", 3) == 0) rfsimulator->role = SIMU_ROLE_SERVER; else @@ -533,8 +576,8 @@ static int rfsimu_set_beam(char *buff, int debug, telnet_printfunc_t prnt, void uint64_t beam_map = strtoull(buff, NULL, 0); std::lock_guard lock_tx(beam_ctrl->tx.mutex); std::lock_guard lock_rx(beam_ctrl->rx.mutex); - beam_ctrl->rx.beam_map = beam_map; - beam_ctrl->tx.beam_map = beam_map; + beam_ctrl->rx.beams = beam_map_to_beams(beam_map); + beam_ctrl->tx.beams = beam_map_to_beams(beam_map); prnt("Beam map set to 0x%lx\n", beam_map); return CMDSTATUS_FOUND; } @@ -854,7 +897,7 @@ static int rfsimulator_write_internal(rfsimulator_state_t *t, void ***samplesVoid, int nsamps, int nbAnt, - uint64_t beam_map, + std::vector tx_beams, int flags) { mutexlock(t->Sockmutex); @@ -864,15 +907,18 @@ static int rfsimulator_write_internal(rfsimulator_state_t *t, 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}; + samplesBlockHeader_t header = {(uint32_t)nsamps, (uint32_t)nbAnt, (uint64_t)timestamp, 0, 0, beams_to_beam_map(tx_beams)}; fullwrite(b->conn_sock, &header, sizeof(header), t); - int num_beams = __builtin_popcountll(beam_map); + int num_beams = tx_beams.size(); + // Send beams in order of beam index. This is required for beam_map to work correctly on the receiver side. + std::vector indices(tx_beams.size()); + std::iota(indices.begin(), indices.end(), 0); + std::sort(indices.begin(), indices.end(), [&](size_t i, size_t j) { return tx_beams[i] < tx_beams[j]; }); + AssertFatal(num_beams > 0, "Must set at least one bit in beam_map\n"); for (int beam = 0; beam < num_beams; beam++) { for (int a = 0; a < nbAnt; a++) { - sample_t *in = (sample_t *)samplesVoid[beam][a]; + sample_t *in = (sample_t *)samplesVoid[indices[beam]][a]; fullwrite(b->conn_sock, (void *)in, sampleToByte(nsamps, 1), t); } } @@ -926,8 +972,8 @@ static int rfsimulator_write_beams(openair0_device *device, } 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); + std::vector beams = get_beams(&t->beam_ctrl->tx, timestamp, nsamps, &nsamps_beam_map); + rfsimulator_write_internal(t, timestamp, samples_ptr, nsamps_beam_map, nbAnt, beams, flags); for (int beam = 0; beam < num_beams; beam++) { for (int aatx = 0; aatx < nbAnt; aatx++) { char *ptr = (char *)samples_ptr[beam][aatx]; @@ -1018,19 +1064,6 @@ static void process_recv_header(rfsimulator_state_t *t, buffer_t *b, bool first_ return; } -static std::vector get_beam_ids(uint64_t beam_map) -{ - int num_beams = __builtin_popcountll(beam_map); - AssertFatal(num_beams > 0, "Needs at least one beam\n"); - std::vector beam_ids; - for (int i = 0; i < MAX_BEAMS; i++) { - if (beam_map & (1ULL << i)) { - beam_ids.push_back(i); - } - } - return beam_ids; -} - /** * @brief Combines samples from received packets into a single buffer for the rx beam * @@ -1070,7 +1103,8 @@ static void combine_received_beams(rfsimulator_state_t *t, break; } - std::vector tx_beams = get_beam_ids(pkt->header.beam_map); + // The beams transmitted in are ordered by beam index + std::vector tx_beams = beam_map_to_beams(pkt->header.beam_map); for (uint beam = 0; beam < tx_beams.size(); beam++) { float gain_dB = get_rx_gain_db(t, rx_beam_id, tx_beams[beam]); float gain_linear = powf(10, gain_dB / 20.0); @@ -1373,20 +1407,19 @@ static int rfsimulator_read_beams(openair0_device *device, int nsamps_to_process = nsamps; while (nsamps_to_process > 0) { uint32_t nsamps_beam_map; - uint64_t beam_map = get_beam_map(&t->beam_ctrl->tx, timestamp, nsamps_to_process, &nsamps_beam_map); - std::vector rx_beam_ids = get_beam_ids(beam_map); - if ((int)rx_beam_ids.size() != num_beams) { + std::vector rx_beams = get_beams(&t->beam_ctrl->tx, timestamp, nsamps_to_process, &nsamps_beam_map); + if ((int)rx_beams.size() != num_beams) { LOG_D(HW, "Number of beams does not match application request num_beams %d, beam_map beams %lu\n", num_beams, - rx_beam_ids.size()); + rx_beams.size()); } - for (int beam = 0; beam < num_beams && beam < (int)rx_beam_ids.size(); beam++) { + for (int beam = 0; beam < num_beams && beam < (int)rx_beams.size(); beam++) { c16_t *samples_beam[nbAnt]; for (int i = 0; i < nbAnt; i++) { samples_beam[i] = (c16_t *)samplesVoid[beam][i] + timestamp - t->nextRxTstamp; } - rfsimulator_read_internal(t, samples_beam, timestamp, nsamps_beam_map, nbAnt, rx_beam_ids[beam], beam == 0); + rfsimulator_read_internal(t, samples_beam, timestamp, nsamps_beam_map, nbAnt, rx_beams[beam], beam == 0); } timestamp += nsamps_beam_map; nsamps_to_process -= nsamps_beam_map; @@ -1526,6 +1559,7 @@ extern "C" __attribute__((__visibility__("default"))) int device_init(openair0_d device->priv = rfsimulator; device->trx_write_init = rfsimulator_write_init; device->trx_set_beams = rfsimulator_set_beams; + device->trx_set_beams2 = rfsimulator_set_beams_vector; for (int i = 0; i < MAX_FD_RFSIMU; i++) rfsimulator->buf[i].conn_sock = -1; From d6da7a5605a99e536a381a50c2ccbda4e3b03c67 Mon Sep 17 00:00:00 2001 From: Bartosz Podrygajlo Date: Sat, 25 Oct 2025 22:20:11 +0200 Subject: [PATCH 14/19] vrtsim: add stubs to beam functions Add stubbed versions of _beam_ apis to vrtsim. --- radio/vrtsim/vrtsim.c | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/radio/vrtsim/vrtsim.c b/radio/vrtsim/vrtsim.c index 9e678a5305..37f1f1a382 100644 --- a/radio/vrtsim/vrtsim.c +++ b/radio/vrtsim/vrtsim.c @@ -519,6 +519,18 @@ static int vrtsim_write(openair0_device *device, openair0_timestamp timestamp, v : vrtsim_write_internal(vrtsim_state, timestamp, (c16_t *)samplesVoid[0], nsamps, 0, flags, 0); } +static int vrtsim_write_beams(openair0_device *device, + openair0_timestamp timestamp, + void ***buff, + int nsamps, + int nb_antennas_tx, + int num_beams, + int flags) +{ + vrtsim_write(device, timestamp, (void **)buff[0], nsamps, nb_antennas_tx, flags); + return nsamps; +} + static int vrtsim_read(openair0_device *device, openair0_timestamp *ptimestamp, void **samplesVoid, int nsamps, int nbAnt) { vrtsim_state_t *vrtsim_state = (vrtsim_state_t *)device->priv; @@ -628,6 +640,16 @@ static int vrtsim_set_freq(openair0_device *device, openair0_config_t *openair0_ return 0; } +static int vrtsim_set_beams(openair0_device *device, uint64_t beam_map, openair0_timestamp timestamp) +{ + return 0; +} + +static int vrtsim_set_beams2(openair0_device *device, int *beam_ids, int num_beams, openair0_timestamp timestamp) +{ + return 0; +} + __attribute__((__visibility__("default"))) int device_init(openair0_device *device, openair0_config_t *openair0_cfg) { vrtsim_state_t *vrtsim_state = calloc_or_fail(1, sizeof(vrtsim_state_t)); @@ -643,6 +665,9 @@ __attribute__((__visibility__("default"))) int device_init(openair0_device *devi device->trx_set_gains_func = vrtsim_stub2; device->trx_write_func = vrtsim_write; device->trx_read_func = vrtsim_read; + device->trx_write_beams_func = vrtsim_write_beams; + device->trx_set_beams = vrtsim_set_beams; + device->trx_set_beams2 = vrtsim_set_beams2; device->type = RFSIMULATOR; device->openair0_cfg = &openair0_cfg[0]; From d6bd2d295fac86f773039422b4580d9f9e603ab4 Mon Sep 17 00:00:00 2001 From: Bartosz Podrygajlo Date: Wed, 29 Oct 2025 08:06:30 +0100 Subject: [PATCH 15/19] Add beam section to RFSimulator README.md --- radio/rfsimulator/README.md | 51 +++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/radio/rfsimulator/README.md b/radio/rfsimulator/README.md index 40688b755a..3b4c91d61d 100644 --- a/radio/rfsimulator/README.md +++ b/radio/rfsimulator/README.md @@ -209,3 +209,54 @@ to reduce the amount of transported samples: A possible, unimplemented optimization would be to compress samples. You can further [tune your machine](../../doc/tuning_and_security.md) + +# Beam simulation + +RFsimulator supports beam domain simulation. + +## Configuration + +Several new CLI parameters were added + +* `--rfsimulator.enable_beams` : enable beam domain simulation. Should match on server and all clients. +* `--rfsimulator.beam_gains ` : define a matrix of additional pathloss values (in dB) +to simulate the effect of different beam combinations in the RF simulator. You provide a comma-separated +list of numbers, which forms the first row of a square matrix. The rest of the matrix is filled by projecting +these values onto the diagonals, making it symmetric. + + Example list: `0,-2,-3` + Resulting matrix: + ``` + [[0,-2,-3], + [-2,0,-2], + [-3,-2,0]] + ``` + + The beam gain matrix will be used during beam combining. RX beam selects the row and TX beam selects column. + Using the example above, if gNB is receiving in beam 1 and the UE is transmitting in beam 1, an additional + 2 dB pathloss will be applied on top of the pathloss from the channel model (if present). + +* `--rfsimulator.beam_map ` : where `` is a `uint64_t` value where each bit is an enabled TX/RX beam. + For gNB: Initial beam_map, i.e. which beams gNB transmits/receives before calling beam APIs. + For UE: Beam position in beam space in the simulation. The UE is not expected to use the beam APIs for now. + +## Runtime commands + +### Moving the UE in beam space + +Use telnet command `rfsimu setbeams `. It works similar to the CLI command `--rfsimulator.beam_map `. +Suggest to only use single bits here + +### Modifying the gNB beam + +It is possible to test the beam domain simulation without implementing the beam APIs. The same telnet command can be used +to modify the tx/rx beam of the gNB. The gNB does not need to be beam-aware and use the new APIs. + +## Programming guide + +RFsimulator is attempting to simulate hardware device operation, but there are differences. Like with real hardware, +it is expected that you provide the configured beams in `set_beams` function ahead of sample reception. This is due +to the fact that a hardware device will buffer received samples before the driver requests them. + +For rfsimulator, as long as the call to `set_beams` with timestamp `n` is done before `trx_read` which is expected +to return sample `n` the beam switch command will apply to the received samples. From 56970390f241ff65146ff0fdfc21623234f5e315 Mon Sep 17 00:00:00 2001 From: Bartosz Podrygajlo Date: Thu, 30 Oct 2025 11:42:21 +0100 Subject: [PATCH 16/19] RFsim: handle additional channel offset from CLI Handle extra channel offset coming from CLI parameter prop_delay. --- radio/rfsimulator/simulator.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/radio/rfsimulator/simulator.cpp b/radio/rfsimulator/simulator.cpp index 0ec98f1a4d..c42f216a47 100644 --- a/radio/rfsimulator/simulator.cpp +++ b/radio/rfsimulator/simulator.cpp @@ -1267,14 +1267,14 @@ static void rfsimulator_read_internal(rfsimulator_state_t *t, } else { if (is_first_beam && is_first_peer && (ptr->nbAnt == 1 || nbAnt == 1)) { // optimization: The buffer is uninitialized so samples can be written directly in the buffer - combine_received_beams(t, ptr->received_packets, timestamp, 1, nsamps, rx_beam_id, samples); + combine_received_beams(t, ptr->received_packets, timestamp - t->chan_offset, 1, nsamps, rx_beam_id, samples); } else { std::vector> ant_buffers(ptr->nbAnt, std::vector(nsamps, {0, 0})); c16_t *input[ant_buffers.size()]; for (uint aatx = 0; aatx < ant_buffers.size(); aatx++) { input[aatx] = ant_buffers[aatx].data(); } - combine_received_beams(t, ptr->received_packets, timestamp, ptr->nbAnt, nsamps, rx_beam_id, input); + combine_received_beams(t, ptr->received_packets, timestamp - t->chan_offset, ptr->nbAnt, nsamps, rx_beam_id, input); for (int aarx = 0; aarx < nbAnt; aarx++) { double H_awgn_mimo_coeff[ant_buffers.size()]; for (int aatx = 0; aatx < (int)ant_buffers.size(); aatx++) { @@ -1455,7 +1455,10 @@ static int rfsimulator_read_beams(openair0_device *device, if (ptr->conn_sock != -1 && !ptr->received_packets.empty()) { openair0_timestamp timestamp_to_free = t->nextRxTstamp - 1; if (ptr->channel_model) { - timestamp_to_free -= (ptr->channel_model->channel_length - 1) + ptr->channel_model->channel_offset; + timestamp_to_free -= + (ptr->channel_model->channel_length - 1) + std::max(ptr->channel_model->channel_offset, t->chan_offset); + } else { + timestamp_to_free -= t->chan_offset; } clear_old_packets(ptr->received_packets, timestamp_to_free); } From 8a36b9b85baadef61600669a7c498e1583057545 Mon Sep 17 00:00:00 2001 From: Bartosz Podrygajlo Date: Mon, 3 Nov 2025 21:22:48 +0100 Subject: [PATCH 17/19] rfsimulator: fix beam_ctrl_t leak. --- radio/rfsimulator/simulator.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/radio/rfsimulator/simulator.cpp b/radio/rfsimulator/simulator.cpp index c42f216a47..306d24084c 100644 --- a/radio/rfsimulator/simulator.cpp +++ b/radio/rfsimulator/simulator.cpp @@ -1490,6 +1490,7 @@ static void rfsimulator_end(openair0_device *device) } clear_beam_queue(&s->beam_ctrl->tx, INT64_MAX); clear_beam_queue(&s->beam_ctrl->rx, INT64_MAX); + delete s->beam_ctrl; close(s->epollfd); free(s); } From ac97ba4e645ab9e939fce3e0f8e6e34a1f5998e4 Mon Sep 17 00:00:00 2001 From: Bartosz Podrygajlo Date: Tue, 4 Nov 2025 16:43:16 +0100 Subject: [PATCH 18/19] RFsim: Disable beam API if beam simulation is disabled --- radio/rfsimulator/simulator.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/radio/rfsimulator/simulator.cpp b/radio/rfsimulator/simulator.cpp index 306d24084c..a1cf97bbf6 100644 --- a/radio/rfsimulator/simulator.cpp +++ b/radio/rfsimulator/simulator.cpp @@ -1553,9 +1553,11 @@ extern "C" __attribute__((__visibility__("default"))) int device_init(openair0_d 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; - device->trx_read_beams_func = rfsimulator_read_beams; + if (rfsimulator->beam_ctrl->enable_beams) { + device->trx_write_beams_func = rfsimulator_write_beams; + device->trx_read_beams_func = rfsimulator_read_beams; + } /* let's pretend to be a b2x0 */ device->type = RFSIMULATOR; openair0_cfg[0].rx_gain[0] = 0; From ade7901fb26a102e6e7367fc772e648050c2e905 Mon Sep 17 00:00:00 2001 From: Bartosz Podrygajlo Date: Thu, 27 Nov 2025 12:15:17 +0100 Subject: [PATCH 19/19] RFSim: Add beamids list based user interface for beam domain simulation Add CLI parameter rfsimulator.beam_ids which uses a list of beam ids instead of a beam map. The beam map API is still present and can be used interchangably. Add telnet command setbeamids which has similar semantics --- radio/rfsimulator/README.md | 5 +++-- radio/rfsimulator/simulator.cpp | 35 +++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/radio/rfsimulator/README.md b/radio/rfsimulator/README.md index 3b4c91d61d..298c7ecfda 100644 --- a/radio/rfsimulator/README.md +++ b/radio/rfsimulator/README.md @@ -239,13 +239,14 @@ these values onto the diagonals, making it symmetric. * `--rfsimulator.beam_map ` : where `` is a `uint64_t` value where each bit is an enabled TX/RX beam. For gNB: Initial beam_map, i.e. which beams gNB transmits/receives before calling beam APIs. For UE: Beam position in beam space in the simulation. The UE is not expected to use the beam APIs for now. +* `--rfsimulator.beam_ids ` : where `` is a comma separated list. Same as above but added for convenience. ## Runtime commands ### Moving the UE in beam space -Use telnet command `rfsimu setbeams `. It works similar to the CLI command `--rfsimulator.beam_map `. -Suggest to only use single bits here +Use telnet command `rfsimu setbeams ` or `rfsimu setbeamids `. They correspond to the CLI parameters described +above and work the same way. ### Modifying the gNB beam diff --git a/radio/rfsimulator/simulator.cpp b/radio/rfsimulator/simulator.cpp index a1cf97bbf6..a29001e0bf 100644 --- a/radio/rfsimulator/simulator.cpp +++ b/radio/rfsimulator/simulator.cpp @@ -79,6 +79,7 @@ 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 RFSIMU_BEAM_IDS "beam_ids" #define RFSIM_CONFIG_HELP_OPTIONS \ " list of comma separated options to enable rf simulator functionalities. Available options: \n" \ @@ -106,6 +107,7 @@ typedef enum { SIMU_ROLE_SERVER = 1, SIMU_ROLE_CLIENT } simuRole; BOOLPARAM("enable_beams", "\n", simBool,&(beam_ctrl->enable_beams), 0), \ INTPARAM("num_concurrent_beams", "\n", simOpt, &beam_ctrl->num_concurrent_beams, 1), \ UINT64PARAM("beam_map", "\n", simOpt, &beam_map, 1), \ + STRINGPARAM(RFSIMU_BEAM_IDS, "\n", simOpt, NULL, NULL), \ STRINGPARAM(RFSIMU_BEAM_GAINS, "\n", simOpt, NULL, NULL), \ }; // clang-format on @@ -119,6 +121,7 @@ static int rfsimu_setdistance_cmd(char *buff, int debug, telnet_printfunc_t prnt 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); +static int rfsimu_set_beamids(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}, @@ -127,6 +130,7 @@ static telnetshell_cmddef_t rfsimu_cmdarray[] = { {"getdistance", "", (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}, + {"setbeamids", "beam_id1,beam_id2,...", (cmdfunc_t)rfsimu_set_beamids, {NULL}, TELNETSRV_CMDFLAG_PUSHINTPOOLQ}, {"", "", NULL}, }; // clang-format on @@ -562,6 +566,18 @@ static void rfsimulator_readconfig(rfsimulator_state_t *rfsimulator) beam_ctrl->rx.beams = initial_beams; beam_ctrl->tx.beams = initial_beams; + int beam_ids_param_index = config_paramidx_fromname(rfsimu_params, sizeofArray(rfsimu_params), RFSIMU_BEAM_IDS); + if (rfsimu_params[beam_ids_param_index].strptr) { + std::vector beam_ids; + std::stringstream ss(*rfsimu_params[beam_ids_param_index].strptr); + std::string token; + while (std::getline(ss, token, ',')) { + beam_ids.push_back(std::stoi(token)); + } + beam_ctrl->rx.beams = beam_ids; + beam_ctrl->tx.beams = beam_ids; + } + if (strncasecmp(rfsimulator->ip, "enb", 3) == 0 || strncasecmp(rfsimulator->ip, "server", 3) == 0) rfsimulator->role = SIMU_ROLE_SERVER; else @@ -582,6 +598,25 @@ static int rfsimu_set_beam(char *buff, int debug, telnet_printfunc_t prnt, void return CMDSTATUS_FOUND; } +static int rfsimu_set_beamids(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"); + std::vector beam_ids; + std::stringstream ss(buff); + std::string token; + while (std::getline(ss, token, ',')) { + beam_ids.push_back(std::stoi(token)); + } + std::lock_guard lock_tx(beam_ctrl->tx.mutex); + std::lock_guard lock_rx(beam_ctrl->rx.mutex); + beam_ctrl->rx.beams = beam_ids; + beam_ctrl->tx.beams = beam_ids; + prnt("Beam ids set\n"); + return CMDSTATUS_FOUND; +} + static int rfsimu_setchanmod_cmd(char *buff, int debug, telnet_printfunc_t prnt, void *arg) { char *modelname = NULL;